diff options
| -rw-r--r-- | Digitigrade/Model/Note.php | 19 | ||||
| -rw-r--r-- | routes/pages_fragment.php | 56 | ||||
| -rw-r--r-- | static/form.css | 11 | ||||
| -rw-r--r-- | static/note.css | 1 | ||||
| -rw-r--r-- | static/pages.css | 8 | ||||
| -rw-r--r-- | templates/note/note.php | 5 | ||||
| -rw-r--r-- | templates/pages/editable.php | 39 | ||||
| -rw-r--r-- | templates/pages/page.php | 23 | ||||
| -rw-r--r-- | templates/pages/page_item.php | 55 |
9 files changed, 185 insertions, 32 deletions
diff --git a/Digitigrade/Model/Note.php b/Digitigrade/Model/Note.php index 9a7aa4c..0f75f5b 100644 --- a/Digitigrade/Model/Note.php +++ b/Digitigrade/Model/Note.php @@ -197,25 +197,30 @@ class Note extends PushableModel implements TimelineIncludeable, Notifyable { return $notes; } - public static function findAllWithExtension(string $extensionUri, ?int $limit = null, int $offset = 0): array { + public static function findAllWithExtension(string $extensionUri, ?int $limit = null, int $offset = 0, bool $includeDeleted = false): array { $pdo = Db::getInstance()->getPdo(); - $query = 'SELECT note_id FROM note_extension LEFT JOIN note ON note.id = note_id ' - . 'WHERE note_extension.uri = ? ORDER BY note.created DESC'; + $query = 'SELECT note.* FROM note_extension LEFT JOIN note ON note.id = note_id ' + . 'WHERE note_extension.uri = ?'; + if (!$includeDeleted) { + $query .= ' AND note.deleted = false'; + } + $query .= ' ORDER BY note.created DESC'; if ($limit != null) { $query .= " LIMIT $limit OFFSET $offset"; } $stmt = $pdo->prepare($query); $stmt->execute([$extensionUri]); $notes = []; - while ($id = $stmt->fetchColumn(0)) { - $notes[] = self::find($id); + while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $notes[] = self::fromDbRow($row); } return $notes; } - public static function countWithExtension(string $extensionUri): int { + public static function countWithExtension(string $extensionUri, bool $includeDeleted = false): int { $pdo = Db::getInstance()->getPdo(); - $stmt = $pdo->prepare('SELECT COUNT(*) FROM note_extension WHERE uri = ?'); + $stmt = $pdo->prepare('SELECT COUNT(*) FROM note_extension LEFT JOIN note ON note_id = note.id WHERE note_extension.uri = ?' + . ($includeDeleted ? '' : ' AND note.deleted = false')); $stmt->execute([$extensionUri]); return $stmt->fetchColumn(0); } diff --git a/routes/pages_fragment.php b/routes/pages_fragment.php new file mode 100644 index 0000000..886fc81 --- /dev/null +++ b/routes/pages_fragment.php @@ -0,0 +1,56 @@ +<?php + +use Digitigrade\FormattingProvider\CommonMark; +use Digitigrade\HttpResponseStatus\BadRequest; +use Digitigrade\HttpResponseStatus\Forbidden; +use Digitigrade\HttpResponseStatus\PolicyRejected; +use Digitigrade\Model\Note; +use Digitigrade\Model\UserAccount; +use Digitigrade\Notification\AdminEditedNoteNotif; +use Digitigrade\PolicyManager; +use Digitigrade\Router; + +Router::getInstance()->mount('/fragment/page/:id', function (array $args) { + $note = Note::find($args['id']); + render_template('pages/page_item', ['note' => $note, 'page' => $note->getPage()]); +}); + +Router::getInstance()->mount('/fragment/page/:id/edit', function (array $args) { + // edit form for a page + $note = Note::find($args['id']); + $user = UserAccount::requireByCurrentSession(); + if ($note->author != $user->actor && !$user->isAdmin) { + throw new Forbidden('you may not edit that page!'); + } + if (!$note->author->isLocal) { + throw new BadRequest("can't edit a non-local page"); + } + + if (isset($_POST['title'])) { + $title = trim($_POST['title']); + $body = trim($_POST['body']); + + $page = $note->getPage(); + $page->title = $title; + $page->content = [ + 'text/markdown' => $body, + 'text/html' => CommonMark::getInstance()->renderToHtml($body) + ]; + + $note->setPage($page); + $note->modified = new DateTimeImmutable(); + if (!PolicyManager::getInstance()->check($note)) { + throw new PolicyRejected(); + } + $note->save(); + $note->publish(); + + if ($user->actor != $note->author) { + (new AdminEditedNoteNotif($user, $note))->processNotifications(); + } + + render_template('pages/page_item', ['note' => $note, 'page' => $page]); + } else { + render_template('pages/editable', ['note' => $note, 'page' => $note->getPage()]); + } +});
\ No newline at end of file diff --git a/static/form.css b/static/form.css index 12e3ab3..f807e4f 100644 --- a/static/form.css +++ b/static/form.css @@ -197,6 +197,17 @@ form:not(.nopanel) { } } + &.pageEditForm { + max-width: 100%; + input, + textarea { + width: 100%; + } + textarea { + height: 16lh; + } + } + .actionButtons { display: grid; justify-content: end; diff --git a/static/note.css b/static/note.css index 8fa8424..09dbd60 100644 --- a/static/note.css +++ b/static/note.css @@ -75,6 +75,7 @@ article.note { padding: var(--spacing-half) 0; overflow-x: hidden; overflow-wrap: break-word; + word-break: break-word; .selected & { font-size: var(--font-big-size); } diff --git a/static/pages.css b/static/pages.css index e476d04..84c872f 100644 --- a/static/pages.css +++ b/static/pages.css @@ -14,6 +14,13 @@ margin: var(--spacing-double) 0; } } + +.pageHeader { + display: grid; + grid-template-columns: 1fr max-content; + align-items: end; +} + .pageAuthor { display: flex; gap: var(--spacing-single); @@ -40,6 +47,7 @@ margin-top: var(--spacing-double); overflow-x: hidden; overflow-wrap: break-word; + word-break: break-word; p, ul, diff --git a/templates/note/note.php b/templates/note/note.php index 0d48272..94f4895 100644 --- a/templates/note/note.php +++ b/templates/note/note.php @@ -113,8 +113,7 @@ if ($user != null) { ]); ?> <div class="right"> - <?php call_template('menu_button', [], function () { - global $note; + <?php call_template('menu_button', [], function () use ($note) { $user = UserAccount::findByCurrentSession(); ?> <li> @@ -128,7 +127,7 @@ if ($user != null) { <span><?= __('action.share') ?></span> </button> </li> - <?php if ($note->author->isLocal && ($note->author == $user?->actor || $user?->isAdmin)): ?> + <?php if ($note->author->isLocal && ($note->author == $user?->actor || $user?->isAdmin) && !$note->hasPage()): ?> <li> <button hx-get="/fragment/note/<?= $note->id ?>/edit" hx-target="closest .note" hx-swap="outerHTML"> <span class="material-symbols edit-outline"></span> diff --git a/templates/pages/editable.php b/templates/pages/editable.php new file mode 100644 index 0000000..67c97ca --- /dev/null +++ b/templates/pages/editable.php @@ -0,0 +1,39 @@ +<article> + <form class="pageEditForm nobackground" hx-post="/fragment/page/<?= $note->id ?>/edit" hx-target="closest article" + hx-swap="outerHTML"> + <div class="row"> + <label for="pageTitle"><?= __('note.page.new.title.label') ?></label> + <input type="text" id="pageTitle" name="title" value="<?= htmlspecialchars($page->title) ?>" required> + </div> + + <div class="row"> + <a class="pageAuthor" href="/@/<?= htmlspecialchars($note->author->getFullHandle()) ?>"> + <?php call_template('actor/avatar', ['actor' => $note->author]); ?> + <span><?= __f( + 'note.page.author', + '<span class="displayName">' . htmlspecialchars($note->author->displayName) . '</span>' + ) ?></span> + </a> + <div class="created"> + <?= __f('note.page.created', format_datetime($note->created)) ?> + </div> + <?php if (isset($note->modified) && $note->modified != $note->created): ?> + <div class="modified"> + <?= __f('note.page.modified', format_datetime($note->modified)) ?> + </div> + <?php endif; ?> + <hr> + </div> + + <div class="row"> + <label for="pageBody"><?= __('note.page.new.body.label') ?></label> + <textarea id="pageBody" name="body" required><?= $page->content['text/markdown'] ?></textarea> + </div> + + <div class="row actionButtons"> + <button type="button" class="secondary" hx-get="/fragment/page/<?= $note->id ?>" hx-target="closest article" + hx-swap="outerHTML"><?= __('form.discardChanges') ?></button> + <button type="submit" class="primary"><?= __('form.saveChanges') ?></button> + </div> + </form> +</article>
\ No newline at end of file diff --git a/templates/pages/page.php b/templates/pages/page.php index 97cea6d..4babb9d 100644 --- a/templates/pages/page.php +++ b/templates/pages/page.php @@ -13,27 +13,6 @@ call_template('skeleton', [ 'ogHandle' => '@' . $note->author->getFullHandle(true) ], function () use ($note, $page) { ?> <div class="notePage"> - <article lang="<?= htmlspecialchars($note->language) ?>"> - <h1><?= htmlspecialchars($page->title) ?></h1> - <a class="pageAuthor" href="/@/<?= htmlspecialchars($note->author->getFullHandle()) ?>"> - <?php call_template('actor/avatar', ['actor' => $note->author]); ?> - <span><?= __f( - 'note.page.author', - '<span class="displayName">' . htmlspecialchars($note->author->displayName) . '</span>' - ) ?></span> - </a> - <div class="created"> - <?= __f('note.page.created', format_datetime($note->created)) ?> - </div> - <?php if (isset($note->modified) && $note->modified != $note->created): ?> - <div class="modified"> - <?= __f('note.page.modified', format_datetime($note->modified)) ?> - </div> - <?php endif; ?> - <hr> - <div class="pageContent"> - <?= $page->getBestContentAsHtml() ?> - </div> - </article> + <?php call_template('pages/page_item', ['note' => $note, 'page' => $page]); ?> </div> <?php });
\ No newline at end of file diff --git a/templates/pages/page_item.php b/templates/pages/page_item.php new file mode 100644 index 0000000..201a2f9 --- /dev/null +++ b/templates/pages/page_item.php @@ -0,0 +1,55 @@ +<?php + +use Digitigrade\Model\UserAccount; + +/** @var \Digitigrade\Model\Note $note */ +/** @var \Digitigrade\Page $page */ + +?> +<article lang="<?= htmlspecialchars($note->language) ?>"> + <div class="pageHeader"> + <div> + <h1><?= htmlspecialchars($page->title) ?></h1> + <a class="pageAuthor" href="/@/<?= htmlspecialchars($note->author->getFullHandle()) ?>"> + <?php call_template('actor/avatar', ['actor' => $note->author]); ?> + <span><?= __f( + 'note.page.author', + '<span class="displayName">' . htmlspecialchars($note->author->displayName) . '</span>' + ) ?></span> + </a> + <div class="created"> + <?= __f('note.page.created', format_datetime($note->created)) ?> + </div> + <?php if (isset($note->modified) && $note->modified != $note->created): ?> + <div class="modified"> + <?= __f('note.page.modified', format_datetime($note->modified)) ?> + </div> + <?php endif; ?> + </div> + <div class="pageActions"> + <?php call_template('menu_button', [], function () use ($note, $page) { + $user = UserAccount::findByCurrentSession(); + if ($note->author->isLocal && ($note->author == $user?->actor || $user?->isAdmin)) { ?> + <li> + <button hx-get="/fragment/page/<?= $note->id ?>/edit" hx-target="closest article" hx-swap="outerHTML"> + <span class="material-symbols edit-outline"></span> + <span><?= __('note.action.edit') ?></span> + </button> + </li> + <li> + <button hx-delete="/fragment/note/<?= $note->id ?>" + hx-confirm="<?= __('note.action.delete.confirmation') ?>" hx-target="closest article" + hx-swap="delete" _="on htmx:afterOnLoad go back"> + <span class="material-symbols delete-outline"></span> + <span><?= __('note.action.delete') ?></span> + </button> + </li> + <?php } + }); ?> + </div> + </div> + <hr> + <div class="pageContent"> + <?= $page->getBestContentAsHtml() ?> + </div> +</article>
\ No newline at end of file |
