aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Digitigrade/Model/Note.php19
-rw-r--r--routes/pages_fragment.php56
-rw-r--r--static/form.css11
-rw-r--r--static/note.css1
-rw-r--r--static/pages.css8
-rw-r--r--templates/note/note.php5
-rw-r--r--templates/pages/editable.php39
-rw-r--r--templates/pages/page.php23
-rw-r--r--templates/pages/page_item.php55
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