diff --git a/notizen.md b/notizen.md index 22f74d9..362397e 100644 --- a/notizen.md +++ b/notizen.md @@ -3,6 +3,4 @@ ## Zu erledigen - sanitizing überprüfen -- Persistenz mit SQLite - Datum für Dokumente ergänzen -- für jedes bestehende Dokument soll es Funktionen zum Aufbereiten und Herunderladen geben in den Formaten pdf/odt (pandoc nutzen) und ogg diff --git a/readme.md b/readme.md index cba5e94..6ba4f79 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,7 @@ proof-of-concept für Partei-Arbeits-Dokumenten-Verwaltung, welche hörbare Vers ### Voraussetzungen - [PHP](https://www.php.net/)-Interpreter für Kommandozeile (Debian-Paket-Name: `php-cli`) +- SQLite3-Modul für PHP (Debian-Paket-Name: `php-sqlite3`) - [FFmpeg](https://ffmpeg.org/) (Debian-Paket-Name: `ffmpeg`) - Browser diff --git a/source/entities/doc.php b/source/entities/doc.php new file mode 100644 index 0000000..1dd4f46 --- /dev/null +++ b/source/entities/doc.php @@ -0,0 +1,43 @@ +title = $title; + $this->authors = $authors; + $this->content = $content; + $this->reasoning = $reasoning; + } +} + + +/** + */ +function empty_() : entity +{ + return (new entity( + '', + [], + '', + null, + )); +} diff --git a/source/helpers/list.php b/source/helpers/list.php new file mode 100644 index 0000000..020ee3f --- /dev/null +++ b/source/helpers/list.php @@ -0,0 +1,39 @@ + diff --git a/source/helpers/sqlite.php b/source/helpers/sqlite.php new file mode 100644 index 0000000..2746947 --- /dev/null +++ b/source/helpers/sqlite.php @@ -0,0 +1,112 @@ + $template, + 'arguments' => $arguments, + ] + ) + ); + */ + $connection = new \SQLite3($path); + $statement = $connection->prepare($template); + foreach ($arguments as $key => $value) + { + $statement->bindValue(placeholder($key), $value); + } + $result = $statement->execute(); + $last_insert_id = $connection->lastInsertRowID(); + return [ + 'result' => $result, + 'last_insert_id' => $last_insert_id, + ]; +} + + ?> diff --git a/source/helpers/storage-interface.php b/source/helpers/storage-interface.php new file mode 100644 index 0000000..cc61c0c --- /dev/null +++ b/source/helpers/storage-interface.php @@ -0,0 +1,25 @@ + diff --git a/source/helpers/storage.php b/source/helpers/storage-jsonfile.php similarity index 81% rename from source/helpers/storage.php rename to source/helpers/storage-jsonfile.php index 94149c9..74615a8 100644 --- a/source/helpers/storage.php +++ b/source/helpers/storage-jsonfile.php @@ -2,32 +2,31 @@ namespace rosavox\helpers\storage; -/** - */ -interface interface_ -{ - public function list_() : array; - - public function read(int $id); - - public function create($value) : int; - - public function update(int $id, $value) : void; - - public function delete(int $id) : void; -} +require_once('helpers/storage-interface.php'); /** */ -class class_jsonfile implements interface_ +class class_jsonfile implements interface_/**/ { + + /** + */ private string $path; + + /** + */ private \Closure $id_encode; + + /** + */ private \Closure $id_decode; + + /** + */ public function __construct( string $path, \Closure $id_encode, @@ -39,6 +38,9 @@ class class_jsonfile implements interface_ $this->id_decode = $id_decode; } + + /** + */ private function get() : array { $content = (\file_exists($this->path) ? \file_get_contents($this->path) : null); @@ -51,12 +53,26 @@ class class_jsonfile implements interface_ ); } + + /** + */ private function put(array $data) : void { $content = \json_encode($data, \JSON_PRETTY_PRINT); \file_put_contents($this->path, $content); } + + /** + */ + public function setup() : void + { + // do nothing + } + + + /** + */ public function list_() : array { $data = $this->get(); @@ -69,7 +85,10 @@ class class_jsonfile implements interface_ ); } - public function read(int $id) + + /** + */ + public function read($id) { $data = $this->get(); $id_encoded = ($this->id_encode)($id); @@ -83,7 +102,7 @@ class class_jsonfile implements interface_ } } - public function create($value) : int + public function create($value) { $data = $this->get(); $id = ($data['last_id'] + 1); @@ -94,7 +113,10 @@ class class_jsonfile implements interface_ return $id; } - public function update(int $id, $value) : void + + /** + */ + public function update($id, $value) : void { $data = $this->get(); $id_encoded = ($this->id_encode)($id); @@ -109,7 +131,10 @@ class class_jsonfile implements interface_ } } - public function delete(int $id) : void + + /** + */ + public function delete($id) : void { $data = $this->get(); $id_encoded = ($this->id_encode)($id); @@ -123,6 +148,7 @@ class class_jsonfile implements interface_ $this->put($data); } } + } ?> diff --git a/source/helpers/storage-sqlitetable.php b/source/helpers/storage-sqlitetable.php new file mode 100644 index 0000000..23dabeb --- /dev/null +++ b/source/helpers/storage-sqlitetable.php @@ -0,0 +1,311 @@ +>*/ +{ + + /** + */ + public static function field_name( + array $field + ) : string + { + return \rosavox\helpers\string_\coin( + 'field_{{name}}', + [ + 'name' => $field['name'], + ] + ); + } + + + /** + */ + private string $path; + + + /** + */ + private string $name; + + + /** + * @param $fields { + * list< + * record< + * name:string, + * type:string, + * nullable:boolean, + * > + * > + * } + */ + private array $fields; + + + /** + */ + public function __construct( + string $path, + string $name, + array $fields + ) + { + $this->path = $path; + $this->name = $name; + $this->fields = $fields; + } + + + /** + */ + public function setup() : void + { + $result = \rosavox\helpers\sqlite\query( + $this->path, + \rosavox\helpers\string_\coin( + 'CREATE TABLE IF NOT EXISTS {{name}}(id INTEGER PRIMARY KEY AUTOINCREMENT, {{fields}});', + [ + 'name' => $this->name, + 'fields' => \implode( + ', ', + \array_map( + fn ($field) => \rosavox\helpers\string_\coin( + '{{name}} {{type}}{{macro_nullable}}', + [ + 'name' => self::field_name($field), + 'type' => \rosavox\helpers\sqlite\map_type($field['type']), + 'macro_nullable' => ($field['nullable'] ? '' : ' NOT NULL'), + ] + ), + $this->fields + ) + ), + ] + ), + [ + ] + ); + } + + + /** + */ + public function list_() : array + { + $result = \rosavox\helpers\sqlite\query( + $this->path, + \rosavox\helpers\string_\coin( + 'SELECT id,{{fields}} FROM {{name}}', + [ + 'fields' => \implode( + ', ', + \rosavox\helpers\list_\map( + $this->fields, + fn ($field) => self::field_name($field) + ) + ), + 'name' => $this->name, + ] + ), + [ + ] + ); + $entries = []; + while (true) + { + $row = $result['result']->fetchArray(\SQLITE3_NUM); + if ($row !== false) + { + \array_push( + $entries, + [ + 'id' => $row[0], + 'value' => \array_slice($row, 1), + ] + ); + } + else + { + break; + } + } + return $entries; + } + + + /** + */ + public function read($id) + { + $result = \rosavox\helpers\sqlite\query( + $this->path, + \rosavox\helpers\string_\coin( + 'SELECT {{fields}} FROM {{name}} WHERE (id = {{id}});', + [ + 'fields' => \implode( + ', ', + \rosavox\helpers\list_\map( + $this->fields, + fn ($field) => self::field_name($field) + ) + ), + 'name' => $this->name, + 'id' => \rosavox\helpers\sqlite\placeholder('id'), + ] + ), + [ + 'id' => $id, + ] + ); + $rows = []; + while (true) + { + $row = $result['result']->fetchArray(\SQLITE3_NUM); + if ($row !== false) + { + \array_push($rows, $row); + } + else + { + break; + } + } + $count = \count($rows); + if ($count === 0) + { + throw (new \Exception('not found')); + } + else + { + if ($count > 1) + { + throw (new \Exception('ambiguous')); + } + else + { + return $rows[0]; + } + } + } + + + /** + */ + public function create($row) + { + $result = \rosavox\helpers\sqlite\query( + $this->path, + \rosavox\helpers\string_\coin( + 'INSERT INTO {{name}}({{schema}}) VALUES ({{values}});', + [ + 'name' => $this->name, + 'schema' => \implode( + ', ', + \array_map( + fn ($field) => self::field_name($field), + $this->fields + ) + ), + 'values' => \implode( + ', ', + \array_map( + fn ($field) => \rosavox\helpers\sqlite\placeholder( + self::field_name($field) + ), + $this->fields + ) + ), + ] + ), + \rosavox\helpers\list_\to_map( + \rosavox\helpers\list_\map( + \rosavox\helpers\list_\sequence( + \count($this->fields) + ), + fn ($index) => [ + 'key' => self::field_name($this->fields[$index]), + 'value' => $row[$index], + ] + ) + ) + ); + return $result['last_insert_id']; + } + + + /** + */ + public function update($id, $row) : void + { + $result = \rosavox\helpers\sqlite\query( + $this->path, + \rosavox\helpers\string_\coin( + 'UPDATE {{name}} SET {{sets}} WHERE (id = :id);', + [ + 'name' => $this->name, + 'sets' => \implode( + ', ', + \array_map( + fn ($field) => \rosavox\helpers\string_\coin( + '{{name}} = {{placeholder}}', + [ + 'name' => self::field_name($field), + 'placeholder' => \rosavox\helpers\sqlite\placeholder(self::field_name($field)), + ] + ), + $this->fields + ) + ), + ] + ), + \array_merge( + [ + 'id' => $id, + ], + \rosavox\helpers\list_\to_map( + \rosavox\helpers\list_\map( + \rosavox\helpers\list_\sequence( + \count($this->fields) + ), + fn ($index) => [ + 'key' => self::field_name($this->fields[$index]), + 'value' => $row[$index], + ] + ) + ) + ) + ); + } + + + /** + */ + public function delete($id) : void + { + $result = \rosavox\helpers\sqlite\query( + $this->path, + \rosavox\helpers\string_\coin( + 'DELETE * FROM {{name}} WHERE (id = {{id}});', + [ + 'name' => $this->name, + 'id' => \rosavox\helpers\sqlite\placeholder('id'), + ] + ), + [ + 'id' => $id, + ] + ); + } + +} + + ?> diff --git a/source/index.html.php b/source/index.html.php index 30b4fbb..e95c55e 100644 --- a/source/index.html.php +++ b/source/index.html.php @@ -1,208 +1,5 @@ $mode]); - return ( - '?' - . - \implode( - '&', - \array_map( - fn ($key) => \rosavox\helpers\string_\coin( - '{{key}}={{value}}', - [ - 'key' => $key, - 'value' => $a[$key], - ] - ), - \array_keys($a) - ) - ) - ); -} - - -/** - */ -function nav(string $mode, array $args) : void -{ - \rosavox\helpers\misc\navigate(make_link($mode, $args)); -} - - -/** - */ -function render_list() : string -{ - return \rosavox\helpers\misc\render( - 'docs-list', - [ - 'label_make' => \rosavox\helpers\misc\translate('action.make'), - 'link_make' => make_link('make', []), - 'entries' => \implode( - "\n", - \array_map( - fn ($entry) => \rosavox\helpers\misc\render( - 'docs-list-entry', - [ - 'label_read' => \rosavox\helpers\misc\translate('action.read'), - 'value_text' => $entry['value']['title'], - 'value_link_open' => make_link('edit', ['id' => \sprintf('%u', $entry['id'])]), - 'value_link_read' => \rosavox\services\doc\readable_path($entry['id']), - 'area_download_readable' => ( - (fn ($name, $path) => ( - (! \file_exists($path)) - ? - \rosavox\helpers\misc\render( - 'state-waiting', - [ - 'info' => \rosavox\helpers\misc\translate('state.generating'), - ] - ) - : - \rosavox\helpers\misc\render( - 'download', - [ - 'text' => \rosavox\helpers\misc\translate('item.readable.short'), - 'tooltip' => \rosavox\helpers\string_\coin( - '{{item}} {{action}}', - [ - 'action' => \rosavox\helpers\misc\translate('action.download'), - 'item' => \rosavox\helpers\misc\translate('item.readable.long'), - ] - ), - 'link' => $path, - 'name' => \rosavox\helpers\string_\coin( - '{{name}}.md', - [ - 'name' => $name, - ] - ), - 'type' => 'text/markdown', - ] - ) - )) ( - \rosavox\services\doc\name($entry['id']), - \rosavox\services\doc\readable_path($entry['id']) - ) - ), - 'area_download_audible' => ( - (fn ($name, $path) => ( - (! \file_exists($path)) - ? - \rosavox\helpers\misc\render( - 'state-waiting', - [ - 'info' => \rosavox\helpers\misc\translate('state.generating'), - ] - ) - : - \rosavox\helpers\misc\render( - 'download', - [ - 'text' => \rosavox\helpers\misc\translate('item.audible.short'), - 'tooltip' => \rosavox\helpers\string_\coin( - '{{item}} {{action}}', - [ - 'action' => \rosavox\helpers\misc\translate('action.download'), - 'item' => \rosavox\helpers\misc\translate('item.audible.long'), - ] - ), - 'link' => $path, - 'name' => \rosavox\helpers\string_\coin( - '{{name}}.oga', - [ - 'name' => $name, - ] - ), - 'type' => 'audio/ogg', - ] - ) - )) ( - \rosavox\services\doc\name($entry['id']), - \rosavox\services\doc\audible_path($entry['id']) - ) - ), - 'area_hear' => ( - (fn ($name, $path) => ( - (! \file_exists($path)) - ? - \rosavox\helpers\misc\render( - 'state-waiting', - [ - 'info' => \rosavox\helpers\misc\translate('state.generating'), - ] - ) - : - \rosavox\helpers\misc\render( - 'player', - [ - 'source_path' => $path, - ] - ) - )) ( - \rosavox\services\doc\name($entry['id']), - \rosavox\services\doc\audible_path($entry['id']) - ) - ), - ] - ), - \rosavox\services\doc\list_() - ) - ), - ] - ); -} - - -/** - */ -function render_edit(?int $id) : string -{ - $doc = ( - ($id === null) - ? - \rosavox\services\doc\empty_() - : - \rosavox\services\doc\read($id) - ); - return \rosavox\helpers\misc\render( - 'docs-edit', - [ - 'label_action_back' => \rosavox\helpers\misc\translate('action.back'), - 'label_action_save' => \rosavox\helpers\misc\translate('action.save'), - 'label_action_delete' => \rosavox\helpers\misc\translate('action.delete'), - 'label_doc_title' => \rosavox\helpers\misc\translate('domain.doc.title'), - 'label_doc_authors' => \rosavox\helpers\misc\translate('domain.doc.authors'), - 'label_doc_content' => \rosavox\helpers\misc\translate('domain.doc.content'), - 'label_doc_reasoning' => \rosavox\helpers\misc\translate('domain.doc.reasoning'), - 'value_action_back' => make_link('list', []), - 'value_action_save' => make_link('save', (($id === null) ? [] : ['id' => \strval($id)])), - 'value_action_delete' => make_link('delete', (($id === null) ? [] : ['id' => \strval($id)])), - 'value_doc_title' => $doc['title'], - 'value_doc_authors' => \implode(', ', $doc['authors']), - 'value_doc_content' => $doc['content'], - 'value_doc_reasoning' => ($doc['reasoning'] ?? ''), - ] - ); -} - - +require_once('main.php'); ?> @@ -213,63 +10,6 @@ function render_edit(?int $id) : string

rosavox

- $_POST['title'], - 'authors' => \explode(',', $_POST['authors']), - 'content' => $_POST['content'], - 'reasoning' => (empty($_POST['reasoning']) ? null : $_GET['reasoning']), - ]; - if ($id === null) - { - $id = \rosavox\services\doc\create($doc); - } - else - { - \rosavox\services\doc\update($id, $doc); - } - // nav('edit', ['id' => \rosavox\services\doc\id_encode($id)]); - nav('list', []); - break; - } - case 'list': - { - echo(render_list()); - break; - } - case 'make': - { - echo(render_edit(null)); - break; - } - case 'edit': - { - echo(render_edit($id)); - break; - } - default: - { - throw (new \Exception(\sprintf('invalid mode: %s', $mode))); - break; - } - } - ?> + diff --git a/source/main.php b/source/main.php new file mode 100644 index 0000000..a8f1174 --- /dev/null +++ b/source/main.php @@ -0,0 +1,90 @@ + \strval($id)]); + \rosavox\nav('list', []); + break; + } + case 'list': + { + echo(\rosavox\renderings\doc_list()); + break; + } + case 'make': + { + echo(\rosavox\renderings\doc_edit(null)); + break; + } + case 'edit': + { + echo(\rosavox\renderings\doc_edit($id)); + break; + } + default: + { + throw (new \Exception(\sprintf('invalid mode: %s', $mode))); + break; + } + } +} + ?> diff --git a/source/nav.php b/source/nav.php new file mode 100644 index 0000000..8116118 --- /dev/null +++ b/source/nav.php @@ -0,0 +1,42 @@ + $mode]); + return ( + '?' + . + \implode( + '&', + \rosavox\helpers\list_\map( + \array_keys($a), + fn ($key) => \rosavox\helpers\string_\coin( + '{{key}}={{value}}', + [ + 'key' => $key, + 'value' => $a[$key], + ] + ) + ) + ) + ); +} + + +/** + */ +function nav(string $mode, array $args) : void +{ + \rosavox\helpers\misc\navigate(make_link($mode, $args)); +} + + ?> diff --git a/source/renderings.php b/source/renderings.php new file mode 100644 index 0000000..450d88c --- /dev/null +++ b/source/renderings.php @@ -0,0 +1,170 @@ + \rosavox\helpers\misc\translate('action.make'), + 'link_make' => \rosavox\make_link('make', []), + 'entries' => \implode( + "\n", + \rosavox\helpers\list_\map( + \rosavox\services\doc\dump(), + fn ($entry) => \rosavox\helpers\misc\render( + 'docs-list-entry', + [ + 'label_read' => \rosavox\helpers\misc\translate('action.read'), + 'value_text' => $entry['value']->title, + 'value_link_open' => \rosavox\make_link('edit', ['id' => \sprintf('%u', $entry['id'])]), + 'value_link_read' => \rosavox\services\doc\readable_path($entry['id']), + 'area_download_readable' => ( + (fn ($name, $path) => ( + (! \file_exists($path)) + ? + \rosavox\helpers\misc\render( + 'state-waiting', + [ + 'info' => \rosavox\helpers\misc\translate('state.generating'), + ] + ) + : + \rosavox\helpers\misc\render( + 'download', + [ + 'text' => \rosavox\helpers\misc\translate('item.readable.short'), + 'tooltip' => \rosavox\helpers\string_\coin( + '{{item}} {{action}}', + [ + 'action' => \rosavox\helpers\misc\translate('action.download'), + 'item' => \rosavox\helpers\misc\translate('item.readable.long'), + ] + ), + 'link' => $path, + 'name' => \rosavox\helpers\string_\coin( + '{{name}}.md', + [ + 'name' => $name, + ] + ), + 'type' => 'text/markdown', + ] + ) + )) ( + \rosavox\services\doc\name($entry['id']), + \rosavox\services\doc\readable_path($entry['id']) + ) + ), + 'area_download_audible' => ( + (fn ($name, $path) => ( + (! \file_exists($path)) + ? + \rosavox\helpers\misc\render( + 'state-waiting', + [ + 'info' => \rosavox\helpers\misc\translate('state.generating'), + ] + ) + : + \rosavox\helpers\misc\render( + 'download', + [ + 'text' => \rosavox\helpers\misc\translate('item.audible.short'), + 'tooltip' => \rosavox\helpers\string_\coin( + '{{item}} {{action}}', + [ + 'action' => \rosavox\helpers\misc\translate('action.download'), + 'item' => \rosavox\helpers\misc\translate('item.audible.long'), + ] + ), + 'link' => $path, + 'name' => \rosavox\helpers\string_\coin( + '{{name}}.oga', + [ + 'name' => $name, + ] + ), + 'type' => 'audio/ogg', + ] + ) + )) ( + \rosavox\services\doc\name($entry['id']), + \rosavox\services\doc\audible_path($entry['id']) + ) + ), + 'area_hear' => ( + (fn ($name, $path) => ( + (! \file_exists($path)) + ? + \rosavox\helpers\misc\render( + 'state-waiting', + [ + 'info' => \rosavox\helpers\misc\translate('state.generating'), + ] + ) + : + \rosavox\helpers\misc\render( + 'player', + [ + 'source_path' => $path, + ] + ) + )) ( + \rosavox\services\doc\name($entry['id']), + \rosavox\services\doc\audible_path($entry['id']) + ) + ), + ] + ) + ) + ), + ] + ); +} + + +/** + */ +function doc_edit(?int $id) : string +{ + $doc = ( + ($id === null) + ? + \rosavox\entities\doc\empty_() + : + \rosavox\services\doc\get($id) + ); + return \rosavox\helpers\misc\render( + 'docs-edit', + [ + 'label_action_back' => \rosavox\helpers\misc\translate('action.back'), + 'label_action_save' => \rosavox\helpers\misc\translate('action.save'), + 'label_action_delete' => \rosavox\helpers\misc\translate('action.delete'), + 'label_doc_title' => \rosavox\helpers\misc\translate('domain.doc.title'), + 'label_doc_authors' => \rosavox\helpers\misc\translate('domain.doc.authors'), + 'label_doc_content' => \rosavox\helpers\misc\translate('domain.doc.content'), + 'label_doc_reasoning' => \rosavox\helpers\misc\translate('domain.doc.reasoning'), + 'value_action_back' => \rosavox\make_link('list', []), + 'value_action_save' => \rosavox\make_link('save', (($id === null) ? [] : ['id' => \strval($id)])), + 'value_action_delete' => \rosavox\make_link('delete', (($id === null) ? [] : ['id' => \strval($id)])), + 'value_doc_title' => $doc->title, + 'value_doc_authors' => \implode(', ', $doc->authors), + 'value_doc_content' => $doc->content, + 'value_doc_reasoning' => ($doc->reasoning ?? ''), + ] + ); +} + + ?> diff --git a/source/repositories/doc.php b/source/repositories/doc.php new file mode 100644 index 0000000..efee76b --- /dev/null +++ b/source/repositories/doc.php @@ -0,0 +1,164 @@ +*/ +{ + + /** + */ + private \rosavox\helpers\storage\interface_ $core; + + + /** + */ + private function __construct() + { + /* + $this->core = new \rosavox\helpers\storage\class_jsonfile( + 'docs.json', + fn ($id) => \sprintf('%u', $id), + fn ($id_encoded) => \intval($id_encoded) + ); + */ + $this->core = new \rosavox\helpers\storage\class_sqlitetable( + 'data.sqlite', + 'docs', + [ + [ + 'name' => 'title', + 'type' => 'string', + 'nullable' => false, + ], + [ + 'name' => 'authors', + 'type' => 'string', + 'nullable' => false, + ], + [ + 'name' => 'content', + 'type' => 'string', + 'nullable' => false, + ], + [ + 'name' => 'reasoning', + 'type' => 'string', + 'nullable' => true, + ], + ] + ); + } + + + /** + */ + private static ?repo $instance = null; + + + /** + */ + public static function get_instance() + { + if (self::$instance === null) + { + self::$instance = new self(); + } + return self::$instance; + } + + + /** + */ + private static function encode(\rosavox\entities\doc\entity $doc) : array + { + return [ + $doc->title, + \implode(',', $doc->authors), + $doc->content, + $doc->reasoning, + ]; + } + + + /** + */ + private static function decode(array $row) : \rosavox\entities\doc\entity + { + return (new \rosavox\entities\doc\entity( + $row[0], + \explode(',', $row[1]), + $row[2], + $row[3], + )); + } + + + /** + * [implementation] + */ + public function setup() : void + { + $this->core->setup(); + } + + + /** + * [implementation] + */ + public function list_() : array + { + return \rosavox\helpers\list_\map( + $this->core->list_(), + fn ($entry) => [ + 'id' => $entry['id'], + 'value' => self::decode($entry['value']), + ] + ); + } + + + /** + * [implementation] + */ + public function read($id) + { + return self::decode($this->core->read($id)); + } + + + /** + * [implementation] + */ + public function create($doc) + { + return $this->core->create(self::encode($doc)); + } + + + /** + * [implementation] + */ + public function update($id, $doc) : void + { + $this->core->update($id, self::encode($doc)); + } + + + /** + * [implementation] + */ + public function delete($id) : void + { + $this->core->delete($id); + } + +} diff --git a/source/services/doc.php b/source/services/doc.php index 04a4a06..56cb92e 100644 --- a/source/services/doc.php +++ b/source/services/doc.php @@ -3,16 +3,10 @@ namespace rosavox\services\doc; require_once('helpers/string.php'); -require_once('helpers/storage.php'); require_once('helpers/misc.php'); - -/** - */ -class state -{ - public static ?\rosavox\helpers\storage\interface_ $storage = null; -} +require_once('entities/doc.php'); +require_once('repositories/doc.php'); /** @@ -41,7 +35,7 @@ function audible_path(int $id) : string /** */ -function generate_readable(int $id, $doc) : void +function generate_readable(int $id, \rosavox\entities\doc\entity $doc) : void { $markdown = \rosavox\helpers\string_\coin( "# {{value_title}} @@ -58,7 +52,7 @@ function generate_readable(int $id, $doc) : void {{macro_reasoning}}", [ 'label_title' => \rosavox\helpers\misc\translate('domain.doc.title'), - 'value_title' => $doc['title'], + 'value_title' => $doc->title, 'label_authors' => \rosavox\helpers\misc\translate('domain.doc.authors'), 'value_authors' => \implode( "\n", @@ -69,13 +63,13 @@ function generate_readable(int $id, $doc) : void 'author' => $author, ] ), - $doc['authors'] + $doc->authors ) ), 'label_content' => \rosavox\helpers\misc\translate('domain.doc.content'), - 'value_content' => $doc['content'], + 'value_content' => $doc->content, 'macro_reasoning' => ( - ($doc['reasoning'] === null) + ($doc->reasoning === null) ? '' : @@ -86,7 +80,7 @@ function generate_readable(int $id, $doc) : void {{value_reasoning}}", [ 'label_reasoning' => \rosavox\helpers\misc\translate('domain.doc.reasoning'), - 'value_reasoning' => $doc['reasoning'], + 'value_reasoning' => $doc->reasoning, ] ) ) @@ -101,9 +95,7 @@ function generate_readable(int $id, $doc) : void /** */ -function remove_readable( - int $id -) : void +function remove_readable(int $id) : void { $path = readable_path($id); if (! \file_exists($path)) @@ -119,10 +111,7 @@ function remove_readable( /** */ -function generate_audible( - int $id, - $doc -) : void +function generate_audible(int $id, \rosavox\entities\doc\entity $doc) : void { $pause = " .\n"; $text = \rosavox\helpers\string_\coin( @@ -130,13 +119,13 @@ function generate_audible( [ 'pause' => $pause, 'label_title' => \rosavox\helpers\misc\translate('domain.doc.title'), - 'value_title' => $doc['title'], + 'value_title' => $doc->title, 'label_authors' => \rosavox\helpers\misc\translate('domain.doc.authors'), - 'value_authors' => \implode($pause, $doc['authors']), + 'value_authors' => \implode($pause, $doc->authors), 'label_content' => \rosavox\helpers\misc\translate('domain.doc.content'), - 'value_content' => $doc['content'], + 'value_content' => $doc->content, 'macro_reasoning' => ( - ($doc['reasoning'] === null) + ($doc->reasoning === null) ? '' : @@ -145,7 +134,7 @@ function generate_audible( [ 'pause' => $pause, 'label_reasoning' => \rosavox\helpers\misc\translate('domain.doc.reasoning'), - 'value_reasoning' => $doc['reasoning'], + 'value_reasoning' => $doc->reasoning, ] ) ) @@ -163,9 +152,7 @@ function generate_audible( /** */ -function remove_audible( - int $id -) : void +function remove_audible(int $id) : void { $path = audible_path($id); if (! \file_exists($path)) @@ -179,41 +166,28 @@ function remove_audible( } + /** */ -function empty_() : array +function dump() : array { - return [ - 'title' => '', - 'authors' => [], - 'content' => '', - 'reasoning' => null, - ]; + return \rosavox\repositories\doc\repo::get_instance()->list_(); } /** */ -function list_() : array +function get(int $id) : \rosavox\entities\doc\entity { - return state::$storage->list_(); + return \rosavox\repositories\doc\repo::get_instance()->read($id); } /** */ -function read(int $id) : array +function add(\rosavox\entities\doc\entity $doc) : int { - return state::$storage->read($id); -} - - -/** - * @todo async generating - */ -function create(array $doc) : int -{ - $id = state::$storage->create($doc); + $id = \rosavox\repositories\doc\repo::get_instance()->create($doc); generate_readable($id, $doc); generate_audible($id, $doc); return $id; @@ -222,9 +196,40 @@ function create(array $doc) : int /** */ -function update(int $id, array $doc) : void +function add_examples() : void { - state::$storage->update($id, $doc); + $entities = [ + new \rosavox\entities\doc\entity( + 'Freibier bei Parteitagen', + [ + 'Björn Biernot', + 'Doreen Dauerdurst', + ], + 'Der Landesverband möge beschließen, dass zu Beginn eines jeden Parteitags für jeden Deligierten mindestens zwei Flaschen Bier auf den zugehörigen Platz zu stellen sind.', + 'Wir haben Durst!', + ), + new \rosavox\entities\doc\entity( + 'Götterdämmerung', + [ + 'Fenriswolf', + ], + 'Der Allvater hat mich betrogen. Ich werde ihn und seine elende Asenbrut verschlingen. Diese Welt wird enden.', + null, + ), + ]; + foreach ($entities as $entity) + { + add($entity); + } +} + + + +/** + */ +function change(int $id, \rosavox\entities\doc\entity $doc) : void +{ + \rosavox\repositories\doc\repo::get_instance()->update($id, $doc); remove_readable($id); remove_audible($id); generate_readable($id, $doc); @@ -234,52 +239,21 @@ function update(int $id, array $doc) : void /** */ -function delete(int $id) : void +function remove(int $id) : void { - state::$storage->delete($id); + \rosavox\repositories\doc\repo::get_instance()->delete($id); remove_readable($id); remove_audible($id); } -/** - */ -function add_examples() : void -{ - create( - [ - 'title' => 'Freibier bei Parteitagen', - 'authors' => [ - 'Björn Biernot', - 'Doreen Dauerdurst', - ], - 'content' => 'Der Landesverband möge beschließen, dass zu Beginn eines jeden Parteitags für jeden Deligierten mindestens zwei Flaschen Bier auf den zugehörigen Platz zu stellen sind.', - 'reasoning' => 'Wir haben Durst!', - ] - ); - create( - [ - 'title' => 'Götterdämmerung', - 'authors' => [ - 'Fenriswolf', - ], - 'content' => 'Der Allvater hat mich betrogen. Ich werde ihn und seine elende Asenbrut verschlingen. Diese Welt wird enden.', - 'reasoning' => null, - ] - ); -} - - /** */ function init() : void { - state::$storage = new \rosavox\helpers\storage\class_jsonfile( - 'docs.json', - fn ($id) => \sprintf('%u', $id), - fn ($id_encoded) => \intval($id_encoded) - ); - if (empty(state::$storage->list_())) + \rosavox\repositories\doc\repo::get_instance()->setup(); + + if (empty(dump())) { add_examples(); } diff --git a/source/style.css b/source/style.css index b6f95a0..ff5ce72 100644 --- a/source/style.css +++ b/source/style.css @@ -82,16 +82,6 @@ button font-size: 1.0em; } -.docs-list-entry -{ - margin-bottom: 16px; -} - -.docs-list-entry > * -{ - vertical-align: middle; -} - .state-waiting { cursor: progress; @@ -101,3 +91,37 @@ button { content: "\21A7 "; } + +.docs-list > ul +{ + padding: 0; + margin: 0 0 0 16px; +} + +.docs-list-entry +{ + padding-top: 24px; + padding-bottom: 24px; + list-style-type: none; + +} + +.docs-list-entry:not(:first-child) +{ + border-top: 2px solid hsl(var(--hue), 0%, 50%); +} + +.docs-list-entry-main +{ +} + +.docs-list-entry-actions +{ + margin-top: 8px; + display: block; +} + +.docs-list-entry-actions > * +{ + vertical-align: middle; +} diff --git a/source/templates/docs-edit.html.tpl b/source/templates/docs-edit.html.tpl index b28a95b..bca03b3 100644 --- a/source/templates/docs-edit.html.tpl +++ b/source/templates/docs-edit.html.tpl @@ -1,4 +1,4 @@ -
+