aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorwinter2025-01-14 18:13:57 +0000
committerwinter2025-01-14 18:13:57 +0000
commit25b748f04911262826d0a0bc9609e03ee47c3232 (patch)
tree35dc82bd0cb1798913f726fb6bb74be97e5b83ba
parent2082bfe9a378a90b33d4ab5c05dbb19a3457bef6 (diff)
add interaction counters
-rw-r--r--Digitigrade/Model.php16
-rw-r--r--Digitigrade/Model/Note.php14
-rw-r--r--routes/note_fragment.php11
-rw-r--r--static/note.css27
-rw-r--r--templates/interaction_button.php11
-rw-r--r--templates/note.php8
-rw-r--r--templates/reply_button.php10
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