diff options
| author | winter | 2025-01-14 18:13:57 +0000 |
|---|---|---|
| committer | winter | 2025-01-14 18:13:57 +0000 |
| commit | 25b748f04911262826d0a0bc9609e03ee47c3232 (patch) | |
| tree | 35dc82bd0cb1798913f726fb6bb74be97e5b83ba | |
| parent | 2082bfe9a378a90b33d4ab5c05dbb19a3457bef6 (diff) | |
add interaction counters
| -rw-r--r-- | Digitigrade/Model.php | 16 | ||||
| -rw-r--r-- | Digitigrade/Model/Note.php | 14 | ||||
| -rw-r--r-- | routes/note_fragment.php | 11 | ||||
| -rw-r--r-- | static/note.css | 27 | ||||
| -rw-r--r-- | templates/interaction_button.php | 11 | ||||
| -rw-r--r-- | templates/note.php | 8 | ||||
| -rw-r--r-- | templates/reply_button.php | 10 |
7 files changed, 78 insertions, 19 deletions
diff --git a/Digitigrade/Model.php b/Digitigrade/Model.php index 4a206a6..be664cb 100644 --- a/Digitigrade/Model.php +++ b/Digitigrade/Model.php @@ -183,6 +183,22 @@ abstract class Model { return static::findAllWhere('1 = 1', []); } + public static function countWhere(string $whereClause, array $parameters): int { + $classNameParts = explode('\\', static::class); + $className = $classNameParts[count($classNameParts) - 1]; + $tableName = self::mangleName($className); + + $query = "SELECT count(*) FROM $tableName WHERE $whereClause"; + $stmt = Db::getInstance()->getPdo()->prepare($query); + $stmt->execute($parameters); + return $stmt->fetchColumn(0); + } + + public static function count(): int { + // FIXME + return static::countWhere('1 = 1', []); + } + public static function find(int $id): ?static { return static::findWhere('id = ?', [$id]); } diff --git a/Digitigrade/Model/Note.php b/Digitigrade/Model/Note.php index a4891a3..cf23e80 100644 --- a/Digitigrade/Model/Note.php +++ b/Digitigrade/Model/Note.php @@ -123,10 +123,18 @@ class Note extends PushableModel implements TimelineIncludeable { return Interaction::findAllWithTarget($this); } + public function countInteractionsOfKind(InteractionKind $kind): int { + return Interaction::countWhere('target = ? AND kind = ? AND deleted = false', [$this->id, $kind->value]); + } + public function getInteractionsWithAuthor(Actor $author): array { return Interaction::findAllWhere('author = ? AND target = ? AND deleted = false', [$author->id, $this->id]); } + public function countReplies(): int { + return self::countWhere('in_reply_to = ? AND deleted = false', [$this->id]); + } + protected function getRelevantServers(): array { $recipientActors = match ($this->privacy->scope) { NotePrivacyScope::NONE => [], @@ -134,6 +142,12 @@ class Note extends PushableModel implements TimelineIncludeable { default => $this->author->findFollowers() }; $recipientActors = array_merge($recipientActors, $this->mentions, $this->privacy->alsoVisibleTo); + // reply-to and thread apex author should be covered by mentions but in case they're not: + if (isset($this->inReplyTo)) + $recipientActors[] = $this->inReplyTo->author; + if (isset($this->threadApex)) + $recipientActors[] = $this->threadApex->author; + $recipientActors = array_unique($recipientActors); $instances = array_map(function (Actor $a) { return $a->findHomeInstance(); diff --git a/routes/note_fragment.php b/routes/note_fragment.php index 53fd7af..a992a61 100644 --- a/routes/note_fragment.php +++ b/routes/note_fragment.php @@ -20,8 +20,8 @@ Router::getInstance()->mount('/fragment/note/:id/:action', function (array $args if ($interaction == null) { // we are adding a new interaction - $newint = Interaction::create($user->actor, InteractionKind::from($action), $note); - $newint->publish(); + $interaction = Interaction::create($user->actor, InteractionKind::from($action), $note); + $interaction->publish(); $active = true; } else { // we are removing the existing one @@ -29,7 +29,12 @@ Router::getInstance()->mount('/fragment/note/:id/:action', function (array $args $active = false; } - render_template('interaction_button', ['action' => $args['action'], 'active' => $active, 'note' => $note]); + render_template('interaction_button', [ + 'action' => $args['action'], + 'active' => $active, + 'note' => $note, + 'count' => $note->countInteractionsOfKind($interaction->kind) + ]); }); Router::getInstance()->mount('/fragment/note/:id/reply', function (array $args) { diff --git a/static/note.css b/static/note.css index 7795882..fde4c62 100644 --- a/static/note.css +++ b/static/note.css @@ -94,15 +94,10 @@ article.note { gap: 16px; width: max-content; - button { - width: 24px; - font-size: 24px; + .interactButtonContainer { + display: flex; + align-items: center; color: #3b005e44; - cursor: pointer; - - &:disabled { - opacity: 0.5; - } &.like.active { color: #8c22d8; @@ -117,5 +112,21 @@ article.note { color: #0761b6; } } + + label { + margin-left: 4px; + font-weight: bold; + } + + button { + width: 24px; + font-size: 24px; + cursor: pointer; + color: inherit; + + &:disabled { + opacity: 0.5; + } + } } } diff --git a/templates/interaction_button.php b/templates/interaction_button.php index 9115435..edd1a25 100644 --- a/templates/interaction_button.php +++ b/templates/interaction_button.php @@ -10,6 +10,13 @@ $icon = match ($action) { 'dislike' => 'heart_broken', 'reshare' => $active ? 'repeat_on' : 'repeat' }; +$id = "interactButton-$action-$note->id"; ?> -<button title="<?= $tooltip ?>" class="icon material-symbols-outlined <?= $action . ($active ? ' active' : '') ?>" - hx-post="<?= "/fragment/note/$note->id/$action" ?>" hx-swap="outerHTML" hx-disabled-elt="this"><?= $icon ?></button>
\ No newline at end of file +<div class="interactButtonContainer <?= $action . ($active ? ' active' : '') ?>"> + <button title="<?= $tooltip ?>" class="icon material-symbols-outlined <?= $active ? 'active' : '' ?>" + hx-post="<?= "/fragment/note/$note->id/$action" ?>" hx-swap="outerHTML" + hx-target="closest .interactButtonContainer" hx-disabled-elt="this" id="<?= $id ?>"> + <?= $icon ?> + </button> + <label for="<?= $id ?>" class="interactionCount"><?= $count ?? '' ?></label> +</div>
\ No newline at end of file diff --git a/templates/note.php b/templates/note.php index 1fb03a9..0a19475 100644 --- a/templates/note.php +++ b/templates/note.php @@ -9,7 +9,7 @@ if ($user != null) { $interKinds = array_map(fn($inter) => $inter->kind, $interactions); } ?> -<article class="note <?= $selected ? 'selected' : '' ?>"> +<article class="note <?= ($selected ?? false) ? 'selected' : '' ?>"> <a href="/@/<?= $note->author->getFullHandle() ?>"> <?php call_template('actor_avatar', ['actor' => $note->author]); ?> </a> @@ -41,13 +41,15 @@ if ($user != null) { </div> <div class="buttons"> <?php foreach (['like', 'dislike', 'reshare'] as $action) { + $kind = InteractionKind::from($action); call_template('interaction_button', [ 'action' => $action, 'note' => $note, - 'active' => in_array(InteractionKind::from($action), $interKinds) + 'active' => in_array($kind, $interKinds), + 'count' => $note->countInteractionsOfKind($kind) ]); } - call_template('reply_button', ['note' => $note]); + call_template('reply_button', ['note' => $note, 'count' => $note->countReplies()]); ?> </div> <?php call_template('reply_form', ['note' => $note]); ?> diff --git a/templates/reply_button.php b/templates/reply_button.php index 1b9736b..aeaeabd 100644 --- a/templates/reply_button.php +++ b/templates/reply_button.php @@ -2,6 +2,10 @@ use Digitigrade\Model\Note; $icon = count(Note::findAllInThread($note, 2)) > 1 ? 'reply_all' : 'reply'; -?> -<button title="<?= __('note.action.reply') ?>" class="icon material-symbols-outlined reply" - _="on click send toggle to [me, next .noteReplyForm] on toggle toggle .active"><?= $icon ?></button>
\ No newline at end of file +$id = "interactButton-reply-$note->id" + ?> +<div class="interactButtonContainer reply"> + <button title="<?= __('note.action.reply') ?>" class="icon material-symbols-outlined reply" id="<?= $id ?>" + _="on click send toggle to [me, next .noteReplyForm] on toggle toggle .active on closest <div/>"><?= $icon ?></button> + <label for="<?= $id ?>"><?= $count ?? '' ?></label> +</div>
\ No newline at end of file |
