aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorwinter2025-01-27 19:45:39 +0000
committerwinter2025-01-27 19:45:39 +0000
commit5a6300f473b8be0e90a16e2b5992299b0ea1ddff (patch)
tree844d3422a1c9575a4cecd56f32c1c7eba0358efd
parent9d59fd65b70b5b7588e4786f52f9cff1c3807426 (diff)
context menus and note deletion
-rw-r--r--Digitigrade/Timeline/HomeTimeline.php27
-rw-r--r--locale/en_GB.json2
-rw-r--r--routes/note_fragment.php16
-rw-r--r--static/icons.css6
-rw-r--r--static/note.css9
-rw-r--r--static/popup-menu.css38
-rw-r--r--static/style.css1
-rw-r--r--static/theme.css1
-rw-r--r--templates/actor_profile.php24
-rw-r--r--templates/actor_profile_block_button.php8
-rw-r--r--templates/infinite_scroll_trigger.php2
-rw-r--r--templates/menu_button.php14
-rw-r--r--templates/note.php17
13 files changed, 143 insertions, 22 deletions
diff --git a/Digitigrade/Timeline/HomeTimeline.php b/Digitigrade/Timeline/HomeTimeline.php
index 9f420e5..a89c29a 100644
--- a/Digitigrade/Timeline/HomeTimeline.php
+++ b/Digitigrade/Timeline/HomeTimeline.php
@@ -13,20 +13,27 @@ class HomeTimeline extends Timeline {
}
protected function getItems(int $limit, int $offset): array {
- return array_map(
+ return $this->filterItems(array_map(
fn(HomeTimelineItem $hti) => $hti->toTimelineItem(),
HomeTimelineItem::findAllForUser($this->user, $limit, $offset)
- );
+ ));
+ }
+
+ private function filterItems(array $items): array {
+ return array_filter($items, function (TimelineItem $item) {
+ $obj = $item->getObject();
+ if (!($obj instanceof Note))
+ return true;
+ $blocked = $this->user->actor->blocks($obj->author);
+ $deleted = $obj->deleted;
+ return !$blocked && !$deleted;
+ });
}
public function getItemsSince(\DateTimeInterface $when, ?int $limit = null, int $offset = 0): array {
- return array_filter(
- array_map(
- fn(HomeTimelineItem $hti) => $hti->toTimelineItem(),
- HomeTimelineItem::findAllForUser($this->user, $limit, $offset, $when)
- ),
- // filter out notes from blocked users
- fn(TimelineItem $item) => !($item instanceof Note && $this->user->actor->block($item->author))
- );
+ return $this->filterItems(array_map(
+ fn(HomeTimelineItem $hti) => $hti->toTimelineItem(),
+ HomeTimelineItem::findAllForUser($this->user, $limit, $offset, $when)
+ ));
}
} \ No newline at end of file
diff --git a/locale/en_GB.json b/locale/en_GB.json
index c8c8f9f..c7cc90a 100644
--- a/locale/en_GB.json
+++ b/locale/en_GB.json
@@ -34,6 +34,8 @@
"note.action.dislike": "Dislike",
"note.action.reshare": "Reshare",
"note.action.reply": "Reply",
+ "note.action.delete": "Delete note",
+ "note.action.delete.confirmation": "Are you sure you want to permanently delete this note?",
"note.info.replyTo": "In reply to %s",
"note.info.mentions": "Mentions %s",
"note.privacy.scope.none": "Private",
diff --git a/routes/note_fragment.php b/routes/note_fragment.php
index 74e987e..5c6326d 100644
--- a/routes/note_fragment.php
+++ b/routes/note_fragment.php
@@ -1,6 +1,7 @@
<?php
use Digitigrade\HttpResponseStatus\BadRequest;
+use Digitigrade\HttpResponseStatus\Forbidden;
use Digitigrade\Model\Actor;
use Digitigrade\Model\Interaction;
use Digitigrade\Model\InteractionKind;
@@ -9,6 +10,21 @@ use Digitigrade\Model\NotePrivacyScope;
use Digitigrade\Model\UserAccount;
use Digitigrade\Router;
+Router::getInstance()->mount('/fragment/note/:id', function (array $args) {
+ if ($_SERVER['REQUEST_METHOD'] != 'DELETE') {
+ throw new RuntimeException('method not implemented');
+ }
+ // 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!');
+ }
+
+ $note->markDeleted();
+ // no need to return any template because it's just gone
+});
+
Router::getInstance()->mount('/fragment/note/:id/:action', function (array $args) {
$user = UserAccount::requireByCurrentSession();
$action = $args['action'];
diff --git a/static/icons.css b/static/icons.css
index 39755a2..efbf8ca 100644
--- a/static/icons.css
+++ b/static/icons.css
@@ -93,4 +93,10 @@
&.alternate-email {
--svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12v1.45q0 1.475-1.012 2.513T18.5 17q-.875 0-1.65-.375t-1.3-1.075q-.725.725-1.638 1.088T12 17q-2.075 0-3.537-1.463T7 12t1.463-3.537T12 7t3.538 1.463T17 12v1.45q0 .65.425 1.1T18.5 15t1.075-.45t.425-1.1V12q0-3.35-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20h5v2zm0-7q1.25 0 2.125-.875T15 12t-.875-2.125T12 9t-2.125.875T9 12t.875 2.125T12 15'/%3E%3C/svg%3E");
}
+ &.more-horiz {
+ --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M6 14q-.825 0-1.412-.587T4 12t.588-1.412T6 10t1.413.588T8 12t-.587 1.413T6 14m6 0q-.825 0-1.412-.587T10 12t.588-1.412T12 10t1.413.588T14 12t-.587 1.413T12 14m6 0q-.825 0-1.412-.587T16 12t.588-1.412T18 10t1.413.588T20 12t-.587 1.413T18 14'/%3E%3C/svg%3E");
+ }
+ &.delete-outline {
+ --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zM17 6H7v13h10zM9 17h2V8H9zm4 0h2V8h-2zM7 6v13z'/%3E%3C/svg%3E");
+ }
}
diff --git a/static/note.css b/static/note.css
index 28f6fff..9b9eb65 100644
--- a/static/note.css
+++ b/static/note.css
@@ -133,8 +133,8 @@ article.note {
padding: 0;
display: grid;
grid-auto-flow: column;
+ grid-template-columns: auto auto auto auto 1fr;
gap: var(--spacing-double);
- width: max-content;
.interactButtonContainer {
display: flex;
@@ -155,6 +155,13 @@ article.note {
}
}
+ .right {
+ justify-self: end;
+ color: var(--clr-action-default);
+ display: flex;
+ align-items: center;
+ }
+
label {
margin-left: var(--spacing-half);
font-weight: bold;
diff --git a/static/popup-menu.css b/static/popup-menu.css
new file mode 100644
index 0000000..e0ec6c9
--- /dev/null
+++ b/static/popup-menu.css
@@ -0,0 +1,38 @@
+.menuButton {
+ display: flex;
+ &:not(:has(li)) {
+ display: none;
+ }
+}
+
+ul.popupMenu {
+ position: absolute;
+ border: var(--border);
+ border-radius: var(--border-radius);
+ background: var(--clr-panel-background);
+ z-index: 10;
+ box-shadow: var(--overlay-shadow);
+ list-style: none;
+ margin: 0;
+ padding: 0;
+
+ li > * {
+ padding: var(--spacing-single);
+ display: grid;
+ grid-template-columns: var(--icon-size) auto;
+ gap: var(--spacing-single);
+ align-items: center;
+
+ border: none;
+ background: none;
+ font-family: inherit;
+ font-size: var(--font-normal-size) !important;
+ color: var(--clr-foreground) !important;
+ width: max-content !important;
+ text-decoration: none;
+ cursor: pointer;
+ }
+ li:not(:last-child) {
+ border-bottom: var(--border);
+ }
+}
diff --git a/static/style.css b/static/style.css
index 54128ac..8cd33b9 100644
--- a/static/style.css
+++ b/static/style.css
@@ -7,4 +7,5 @@
@import url(pills.css);
@import url(profile.css);
@import url(mobilepanes.css);
+@import url(popup-menu.css);
@import url(misc.css);
diff --git a/static/theme.css b/static/theme.css
index 161288f..f002669 100644
--- a/static/theme.css
+++ b/static/theme.css
@@ -46,6 +46,7 @@
--icon-size: 24px;
--avatar-size: 64px;
--avatar-big-size: 96px;
+ --overlay-shadow: 0 0 8px #0004;
accent-color: var(--clr-primary);
}
diff --git a/templates/actor_profile.php b/templates/actor_profile.php
index cfcc510..1e37f28 100644
--- a/templates/actor_profile.php
+++ b/templates/actor_profile.php
@@ -5,6 +5,7 @@ $user = UserAccount::findByCurrentSession();
?>
<div class="fullProfile">
<div class="basicInfo">
+
<?php if ($addLink ?? false): ?>
<a href="/@/<?= $actor->getFullHandle() ?>">
<?php endif; ?>
@@ -12,20 +13,28 @@ $user = UserAccount::findByCurrentSession();
<?php if ($addLink ?? false): ?>
</a>
<?php endif; ?>
+
<div>
<div class="displayName"><?= htmlspecialchars($actor->displayName) ?></div>
<div class="handle">@<?= $actor->getFullHandle() ?></div>
- <div class="joinedDate"><?= sprintf(__('user.profile.createdAt'), $actor->created->format('Y-m-d')) ?></div>
+ <div class="joinedDate"><?= sprintf(__('user.profile.createdAt'), $actor->created->format('Y-m-d')) ?>
+ </div>
</div>
+
<div class="profileMiniActions">
- <?php if ($user != null && $user->actor != $actor)
- call_template('actor_profile_block_button', ['actor' => $actor, 'user' => $user]); ?>
- <?php if (!$actor->isLocal): ?>
- <a class="material-symbols open-in-new" href="<?= $actor->homepage ?>"
- title="<?= __('user.profile.openRemote') ?>" target="_blank"></a>
- <?php endif; ?>
+ <?php call_template('menu_button', [], function () {
+ global $actor;
+ // i don't know why but `global $user` just straight up doesnt work?
+ $user = UserAccount::findByCurrentSession();
+ ?>
+ <?php if ($user != null && $user->actor != $actor): ?>
+ <li><?php call_template('actor_profile_block_button', ['user' => $user, 'actor' => $actor]) ?></li>
+ <?php endif; ?>
+ <?php }); ?>
</div>
+
</div>
+
<?php if (isset($actor->bio) && $actor->bio != '') { ?>
<pre class="bio"><?= htmlspecialchars($actor->bio) ?></pre>
<?php } else {
@@ -48,4 +57,5 @@ $user = UserAccount::findByCurrentSession();
</button>
</div>
<?php endif; ?>
+
</div> \ No newline at end of file
diff --git a/templates/actor_profile_block_button.php b/templates/actor_profile_block_button.php
index f82cc9a..1b71613 100644
--- a/templates/actor_profile_block_button.php
+++ b/templates/actor_profile_block_button.php
@@ -2,6 +2,8 @@
$blocks = $user->actor->blocks($actor);
$action = $blocks ? 'unblock' : 'block';
?>
-<button class="material-symbols <?= $blocks ? 'remove' : 'block' ?>" title="<?= __("user.profile.$action") ?>"
- hx-confirm="<?= __("user.profile.$action.confirm") ?>" hx-post="/fragment/actor/<?= $actor->id ?>/blockButton"
- hx-swap="outerHTML"></button> \ No newline at end of file
+<button hx-confirm="<?= __("user.profile.$action.confirm") ?>" hx-post="/fragment/actor/<?= $actor->id ?>/blockButton"
+ hx-swap="outerHTML">
+ <span class="material-symbols <?= $blocks ? 'remove' : 'block' ?>"></span>
+ <span><?= __("user.profile.$action") ?></span>
+</button> \ No newline at end of file
diff --git a/templates/infinite_scroll_trigger.php b/templates/infinite_scroll_trigger.php
index dce1343..3a19ebe 100644
--- a/templates/infinite_scroll_trigger.php
+++ b/templates/infinite_scroll_trigger.php
@@ -1,4 +1,4 @@
-<?php if ($isEnd): ?>
+<?php if ($isEnd ?? false): ?>
<div id="infiniteScroll" hx-swap-oob="true">
<?php call_template('placeholder_text', ['count' => 0]); ?>
</div>
diff --git a/templates/menu_button.php b/templates/menu_button.php
new file mode 100644
index 0000000..da47569
--- /dev/null
+++ b/templates/menu_button.php
@@ -0,0 +1,14 @@
+<div class="menuButton">
+ <button type="button" class="material-symbols more-horiz" _="
+ on click
+ set menu to next .popupMenu
+ toggle @hidden on menu
+ measure me then set pos to it
+ set menu.style.top to `${scrollY + pos.top}px`
+ set menu.style.right to `${(scrollX + innerWidth) - pos.right}px`
+ on click from elsewhere set @hidden of next .popupMenu to false
+ "></button>
+ <ul class="popupMenu" hidden>
+ <?php slot(); ?>
+ </ul>
+</div> \ No newline at end of file
diff --git a/templates/note.php b/templates/note.php
index e5c2942..74ce566 100644
--- a/templates/note.php
+++ b/templates/note.php
@@ -94,6 +94,23 @@ if ($user != null) {
'disabled' => $actionsDisabled
]);
?>
+ <div class="right">
+ <?php call_template('menu_button', [], function () {
+ global $note;
+ $user = UserAccount::findByCurrentSession();
+ ?>
+ <?php if ($note->author == $user?->actor || $user?->isAdmin): ?>
+ <li>
+ <button hx-delete="/fragment/note/<?= $note->id ?>"
+ hx-confirm="<?= __('note.action.delete.confirmation') ?>" hx-target="closest .note"
+ hx-swap="delete">
+ <span class="material-symbols delete-outline"></span>
+ <span><?= __('note.action.delete') ?></span>
+ </button>
+ </li>
+ <?php endif; ?>
+ <?php }); ?>
+ </div>
</div>
<?php call_template('reply_form', ['note' => $note]); ?>