aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--locale/en_GB.json5
-rw-r--r--routes/note_fragment.php42
-rw-r--r--static/form.css15
-rw-r--r--static/note.css3
-rw-r--r--templates/note/editable.php30
-rw-r--r--templates/note/info_line.php28
-rw-r--r--templates/note/note.php47
7 files changed, 133 insertions, 37 deletions
diff --git a/locale/en_GB.json b/locale/en_GB.json
index c0bd6a0..22cc291 100644
--- a/locale/en_GB.json
+++ b/locale/en_GB.json
@@ -39,13 +39,16 @@
"note.action.dislike": "Dislike",
"note.action.reshare": "Reshare",
"note.action.reply": "Reply",
- "note.action.delete": "Delete note",
+ "note.action.delete": "Delete",
"note.action.delete.confirmation": "Are you sure you want to permanently delete this note?",
+ "note.action.edit": "Edit",
+ "note.editForm.label": "Edit note",
"note.openRemote": "Open on original instance",
"note.openPage": "View page",
"note.page.summaryMessage": "Published a new page: \"%s\"",
"note.info.replyTo": "In reply to %s",
"note.info.mentions": "Mentions %s",
+ "note.info.modified": "Last modified %s",
"note.privacy.scope.none": "Private",
"note.privacy.scope.mutuals": "Mutuals only",
"note.privacy.scope.followers": "Followers only",
diff --git a/routes/note_fragment.php b/routes/note_fragment.php
index 94ebc07..9cc9143 100644
--- a/routes/note_fragment.php
+++ b/routes/note_fragment.php
@@ -14,16 +14,24 @@ use Digitigrade\Model\StorageObject;
use Digitigrade\Model\UserAccount;
use Digitigrade\PolicyManager;
use Digitigrade\Router;
-use Digitigrade\StorageProvider;
use Digitigrade\StorageProvider\FilesystemStorage;
Router::getInstance()->mount('/fragment/note/:id', function (array $args) {
+ $note = Note::find($args['id']);
if ($_SERVER['REQUEST_METHOD'] != 'DELETE') {
- throw new RuntimeException('method not implemented');
+ // just render the note
+ // but check the current user is allowed to see it
+ if ($note->privacy->scope != NotePrivacyScope::PUBLIC ) {
+ $user = UserAccount::requireByCurrentSession();
+ if (!in_array($user->actor, $note->getRelevantActors())) {
+ throw new Forbidden();
+ }
+ }
+ render_template('note/note', ['note' => Note::find($args['id'])]);
+ return;
}
// assuming we're deleting the note
$user = UserAccount::requireByCurrentSession();
- $note = Note::find($args['id']);
if ($note->author != $user->actor && !$user->isAdmin) {
throw new Forbidden('you may not delete that note!');
}
@@ -108,6 +116,34 @@ Router::getInstance()->mount('/fragment/note/:id/reply', function (array $args)
render_template('note/reply_form', ['note' => $note]);
});
+Router::getInstance()->mount('/fragment/note/:id/edit', function (array $args) {
+ // editing an existing note
+ $user = UserAccount::requireByCurrentSession();
+ $note = Note::find($args['id']);
+ if ($note->author != $user->actor && !$user->isAdmin) {
+ throw new Forbidden('you may not edit that note!');
+ }
+ if (!$note->author->isLocal) {
+ throw new BadRequest("can't edit a non-local note");
+ }
+
+ if (isset($_POST['content']) && strlen(trim($_POST['content'])) > 0) {
+ $note->summary = trim($_POST['summary'] ?? '') ?: null;
+ $content = trim($_POST['content']);
+ $note->plainContent = CommonMark::getInstance()->renderToPlainText($content);
+ $note->formattedContent = [
+ 'text/markdown' => $content,
+ 'text/html' => CommonMark::getInstance()->renderToHtml($content)
+ ];
+ $note->modified = new DateTimeImmutable();
+ $note->save();
+ $note->publish();
+ render_template('note/note', ['note' => $note]);
+ } else {
+ render_template('note/editable', ['note' => $note]);
+ }
+});
+
Router::getInstance()->mount('/fragment/note', function (array $args) {
// posting a new note
$author = UserAccount::requireByCurrentSession();
diff --git a/static/form.css b/static/form.css
index 6f474c1..bb8655d 100644
--- a/static/form.css
+++ b/static/form.css
@@ -61,7 +61,8 @@ form:not(.nopanel) {
margin: 0;
}
- &.noteReplyForm {
+ &.noteReplyForm,
+ &.noteEditForm {
background: none;
border: none;
padding: 0;
@@ -76,8 +77,12 @@ form:not(.nopanel) {
box-sizing: border-box;
}
}
+ &.noteEditForm {
+ margin-top: var(--spacing-single);
+ }
&.noteReplyForm,
+ &.noteEditForm,
&#writeNoteForm {
details {
summary {
@@ -178,6 +183,14 @@ form:not(.nopanel) {
font-size: var(--font-normal-size);
}
}
+
+ .actionButtons {
+ display: grid;
+ justify-content: end;
+ align-items: baseline;
+ grid-auto-flow: column;
+ gap: var(--spacing-double);
+ }
}
input[type="checkbox"] {
diff --git a/static/note.css b/static/note.css
index a224508..6d3b215 100644
--- a/static/note.css
+++ b/static/note.css
@@ -148,7 +148,8 @@ article.note {
}
}
- .replyInfo {
+ .replyInfo,
+ .modifiedInfo {
font-size: var(--font-small-size);
color: var(--clr-lesser-foreground);
display: flex;
diff --git a/templates/note/editable.php b/templates/note/editable.php
new file mode 100644
index 0000000..7a864a9
--- /dev/null
+++ b/templates/note/editable.php
@@ -0,0 +1,30 @@
+<article class="note">
+ <a href="/@/<?= $note->author->getFullHandle() ?>">
+ <?php call_template('actor/avatar', ['actor' => $note->author]); ?>
+ </a>
+
+ <div>
+
+ <?php call_template('note/info_line', ['note' => $note]); ?>
+
+ <form hx-post="/fragment/note/<?= $note->id ?>/edit" hx-target="closest .note" hx-swap="outerHTML"
+ class="noteEditForm">
+ <div class="row summaryAndContent">
+ <label for="noteContent-<?= $note->id ?>"><?= __('note.editForm.label') ?></label>
+ <input name="summary" type="text" placeholder="<?= __('writeNote.summary.label') ?>"
+ value="<?= htmlspecialchars($note->summary) ?>">
+ <textarea id="noteContent-<?= $note->id ?>" name="content" required
+ placeholder="<?= __('writeNote.placeholder') ?>"><?= htmlspecialchars(
+ $note->formattedContent['text/markdown'] ?? $note->plainContent
+ ) ?></textarea>
+ <?php call_template('formatting_hint'); ?>
+ </div>
+ <div class="row actionButtons">
+ <button type="button" class="secondary"
+ hx-get="/fragment/note/<?= $note->id ?>"><?= __('form.discardChanges') ?></button>
+ <button type="submit" class="primary"><?= __('form.saveChanges') ?></button>
+ </div>
+ </form>
+
+ </div>
+</article> \ No newline at end of file
diff --git a/templates/note/info_line.php b/templates/note/info_line.php
new file mode 100644
index 0000000..acc85d7
--- /dev/null
+++ b/templates/note/info_line.php
@@ -0,0 +1,28 @@
+<?php
+
+use Digitigrade\Model\NotePrivacyScope;
+
+?>
+<div class="infoLine">
+ <a class="authorName"
+ href="/@/<?= htmlspecialchars($note->author->getFullHandle()) ?>"><?= htmlspecialchars($note->author->displayName) ?></a>
+ <span class="authorPronouns">
+ <?php if (isset($note->author->pronouns) && trim($note->author->pronouns) != '') {
+ echo htmlspecialchars($note->author->pronouns);
+ } ?>
+ </span>
+ <a class="timestamp" href="<?= htmlspecialchars($note->getLocalUiHref()) ?>">
+ <?= format_datetime($note->created) ?>
+ </a>
+ <span class="privacyScope inlineIcon material-symbols <?= match ($note->privacy->scope) {
+ NotePrivacyScope::NONE => 'lock',
+ NotePrivacyScope::MUTUALS => 'group',
+ NotePrivacyScope::FOLLOWERS => 'groups',
+ NotePrivacyScope::PUBLIC => 'public'
+ } ?>" title="<?= match ($note->privacy->scope) {
+ NotePrivacyScope::NONE => __('note.privacy.scope.none'),
+ NotePrivacyScope::MUTUALS => __('note.privacy.scope.mutuals'),
+ NotePrivacyScope::FOLLOWERS => __('note.privacy.scope.followers'),
+ NotePrivacyScope::PUBLIC => __('note.privacy.scope.public')
+ } ?>"></span>
+</div> \ No newline at end of file
diff --git a/templates/note/note.php b/templates/note/note.php
index 7cef342..0d48272 100644
--- a/templates/note/note.php
+++ b/templates/note/note.php
@@ -3,7 +3,6 @@
use Digitigrade\Model\Actor;
use Digitigrade\Model\InteractionKind;
-use Digitigrade\Model\NotePrivacyScope;
use Digitigrade\Model\UserAccount;
use Digitigrade\UserSettings;
@@ -17,7 +16,6 @@ if ($user != null) {
$interactions = $note->getInteractionsWithAuthor($user->actor);
$interKinds = array_map(fn($inter) => $inter->kind, $interactions);
}
-$pathToSelf = $note->getLocalUiHref();
?>
<article class="note <?= ($selected ?? false) ? 'selected' : '' ?>">
@@ -27,40 +25,14 @@ $pathToSelf = $note->getLocalUiHref();
<div>
- <div class="infoLine">
- <a class="authorName"
- href="/@/<?= htmlspecialchars($note->author->getFullHandle()) ?>"><?= htmlspecialchars($note->author->displayName) ?></a>
- <span class="authorPronouns">
- <?php if (isset($note->author->pronouns) && trim($note->author->pronouns) != '') {
- echo htmlspecialchars($note->author->pronouns);
- } ?>
- </span>
- <a class="timestamp" href="<?= htmlspecialchars($pathToSelf) ?>">
- <?= format_datetime($note->created)
- . (isset($note->modified) && $note->modified != $note->created
- ? ' (' . format_datetime($note->modified) . ')'
- : ''
- ) ?>
- </a>
- <span class="privacyScope inlineIcon material-symbols <?= match ($note->privacy->scope) {
- NotePrivacyScope::NONE => 'lock',
- NotePrivacyScope::MUTUALS => 'group',
- NotePrivacyScope::FOLLOWERS => 'groups',
- NotePrivacyScope::PUBLIC => 'public'
- } ?>" title="<?= match ($note->privacy->scope) {
- NotePrivacyScope::NONE => __('note.privacy.scope.none'),
- NotePrivacyScope::MUTUALS => __('note.privacy.scope.mutuals'),
- NotePrivacyScope::FOLLOWERS => __('note.privacy.scope.followers'),
- NotePrivacyScope::PUBLIC => __('note.privacy.scope.public')
- } ?>"></span>
- </div>
+ <?php call_template('note/info_line', ['note' => $note]); ?>
<?php if (isset($note->inReplyTo)): ?>
<div class="replyInfo">
<span class="material-symbols reply inlineIcon"></span>
<a
href="/@/<?= htmlspecialchars($note->inReplyTo->author->getFullHandle()) ?>/note/<?= $note->inReplyTo->id ?>">
- <?= sprintf(__('note.info.replyTo'),
+ <?= __f('note.info.replyTo',
'<b>' . htmlspecialchars($note->inReplyTo->author->displayName)) . '</b>' ?>
</a>
</div>
@@ -68,7 +40,7 @@ $pathToSelf = $note->getLocalUiHref();
<div class="replyInfo">
<span class="material-symbols alternate-email inlineIcon"></span>
<span>
- <?= sprintf(__('note.info.mentions'), implode(', ', array_map(
+ <?= __f('note.info.mentions', implode(', ', array_map(
fn(Actor $m) => '<b><a href="/@/' . htmlspecialchars($m->getFullHandle()) . '">' . htmlspecialchars($m->displayName) . '</a></b>',
$note->mentions
))) ?>
@@ -76,6 +48,13 @@ $pathToSelf = $note->getLocalUiHref();
</div>
<?php endif; ?>
+ <?php if (isset($note->modified) && $note->modified != $note->created): ?>
+ <div class="modifiedInfo">
+ <span class="material-symbols edit-outline inlineIcon"></span>
+ <span><?= __f('note.info.modified', format_datetime($note->modified)) ?></span>
+ </div>
+ <?php endif; ?>
+
<?php if (isset($note->summary)): ?>
<details>
<summary><?= htmlspecialchars($note->summary) ?></summary>
@@ -151,6 +130,12 @@ $pathToSelf = $note->getLocalUiHref();
</li>
<?php if ($note->author->isLocal && ($note->author == $user?->actor || $user?->isAdmin)): ?>
<li>
+ <button hx-get="/fragment/note/<?= $note->id ?>/edit" hx-target="closest .note" 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 .note"
hx-swap="delete">