[PLUGIN][TreeNotes] Correct cache issues and iterate functionality
- Replies ordering now correct - Replies count added - Posting adds new replies to cache (when concerning replies cache is not empty) and increments replies count - Configuration to specify number of in-tree replies shown added - TreeNotes templates was moved from core to plugin - Button to read more replies was added
This commit is contained in:
parent
2f539d176d
commit
a9b34b75b6
|
@ -39,7 +39,10 @@
|
||||||
{% for conversation in notes %}
|
{% for conversation in notes %}
|
||||||
{% block current_note %}
|
{% block current_note %}
|
||||||
{% if conversation is instanceof('array') %}
|
{% if conversation is instanceof('array') %}
|
||||||
{% set args = { 'type': 'vanilla_full', 'note': conversation['note'], 'replies': conversation['replies'] | default, 'extra': { 'depth': 0 } } %}
|
{% set args = {
|
||||||
|
'type': 'vanilla_full',
|
||||||
|
'conversation': conversation
|
||||||
|
} %}
|
||||||
{{ NoteFactory.constructor(args) }}
|
{{ NoteFactory.constructor(args) }}
|
||||||
{# {% else %}
|
{# {% else %}
|
||||||
{% set args = { 'type': 'vanilla_full', 'note': conversation, 'extra': { 'depth': 0 } } %}
|
{% set args = { 'type': 'vanilla_full', 'note': conversation, 'extra': { 'depth': 0 } } %}
|
||||||
|
|
|
@ -24,6 +24,7 @@ declare(strict_types = 1);
|
||||||
namespace Component\Posting;
|
namespace Component\Posting;
|
||||||
|
|
||||||
use App\Core\ActorLocalRoles;
|
use App\Core\ActorLocalRoles;
|
||||||
|
use App\Core\Cache;
|
||||||
use App\Core\DB\DB;
|
use App\Core\DB\DB;
|
||||||
use App\Core\Event;
|
use App\Core\Event;
|
||||||
use App\Core\Form;
|
use App\Core\Form;
|
||||||
|
@ -332,6 +333,14 @@ class Posting extends Component
|
||||||
DB::persist($note);
|
DB::persist($note);
|
||||||
Conversation::assignLocalConversation($note, $reply_to_id);
|
Conversation::assignLocalConversation($note, $reply_to_id);
|
||||||
|
|
||||||
|
// Update replies cache
|
||||||
|
if (!\is_null($reply_to_id)) {
|
||||||
|
Cache::incr(Note::cacheKeys($reply_to_id)['replies-count']);
|
||||||
|
if (Cache::exists(Note::cacheKeys($reply_to_id)['replies'])) {
|
||||||
|
Cache::listPushRight(Note::cacheKeys($reply_to_id)['replies'], $note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Need file and note ids for the next step
|
// Need file and note ids for the next step
|
||||||
$note->setUrl(Router::url('note_view', ['id' => $note->getId()], Router::ABSOLUTE_URL));
|
$note->setUrl(Router::url('note_view', ['id' => $note->getId()], Router::ABSOLUTE_URL));
|
||||||
if (!empty($content)) {
|
if (!empty($content)) {
|
||||||
|
@ -373,7 +382,7 @@ class Posting extends Component
|
||||||
$activity,
|
$activity,
|
||||||
[
|
[
|
||||||
'note-attention' => $attention_ids,
|
'note-attention' => $attention_ids,
|
||||||
'object' => F\unique(F\flat_map($mentions, fn (array $m) => F\map($m['mentioned'] ?? [], fn (Actor $a) => $a->getId()))),
|
'object' => F\unique(F\flat_map($mentions, fn (array $m) => F\map($m['mentioned'] ?? [], fn (Actor $a) => $a->getId()))),
|
||||||
],
|
],
|
||||||
_m('{nickname} created a note {note_id}.', [
|
_m('{nickname} created a note {note_id}.', [
|
||||||
'{nickname}' => $actor->getNickname(),
|
'{nickname}' => $actor->getNickname(),
|
||||||
|
|
|
@ -265,6 +265,14 @@ class Note extends Model
|
||||||
// Assign conversation to this note
|
// Assign conversation to this note
|
||||||
Conversation::assignLocalConversation($obj, $reply_to);
|
Conversation::assignLocalConversation($obj, $reply_to);
|
||||||
|
|
||||||
|
// Update replies cache
|
||||||
|
if (!\is_null($reply_to)) {
|
||||||
|
Cache::incr(GSNote::cacheKeys($reply_to)['replies-count']);
|
||||||
|
if (Cache::exists(GSNote::cacheKeys($reply_to)['replies'])) {
|
||||||
|
Cache::listPushRight(GSNote::cacheKeys($reply_to)['replies'], $obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$object_mention_ids = [];
|
$object_mention_ids = [];
|
||||||
foreach ($type_note->get('tag') ?? [] as $ap_tag) {
|
foreach ($type_note->get('tag') ?? [] as $ap_tag) {
|
||||||
switch ($ap_tag->get('type')) {
|
switch ($ap_tag->get('type')) {
|
||||||
|
|
|
@ -21,8 +21,11 @@ declare(strict_types = 1);
|
||||||
|
|
||||||
namespace Plugin\TreeNotes;
|
namespace Plugin\TreeNotes;
|
||||||
|
|
||||||
|
use App\Core\Event;
|
||||||
use App\Core\Modules\Plugin;
|
use App\Core\Modules\Plugin;
|
||||||
use App\Entity\Note;
|
use App\Entity\Note;
|
||||||
|
use App\Util\Common;
|
||||||
|
use App\Util\Formatting;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
class TreeNotes extends Plugin
|
class TreeNotes extends Plugin
|
||||||
|
@ -43,30 +46,43 @@ class TreeNotes extends Plugin
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats general Feed view, allowing users to see a Note and its direct replies.
|
* Formats general Feed view, allowing users to see a Note and its direct replies.
|
||||||
* These replies are then, shown independently of parent note, making sure that every single Note is shown at least once to users.
|
* These replies are then, shown independently of parent note, making sure that every single Note is shown at least
|
||||||
|
* once to users.
|
||||||
*
|
*
|
||||||
* The list is transversed in reverse to prevent any parent Note from being processed twice. At the same time, this allows all direct replies to be rendered inside the same, respective, parent Note.
|
* The list is transversed in reverse to prevent any parent Note from being processed twice. At the same time,
|
||||||
|
* this allows all direct replies to be rendered inside the same, respective, parent Note.
|
||||||
* Moreover, this implies the Entity\Note::getReplies() query will only be performed once, for every Note.
|
* Moreover, this implies the Entity\Note::getReplies() query will only be performed once, for every Note.
|
||||||
*
|
*
|
||||||
* @param array $notes The Note list to be formatted, each element has two keys: 'note' (parent/current note), and 'replies' (array of notes in the same format)
|
* @param array $notes The Note list to be formatted, each element has two keys: 'note' (parent/current note),
|
||||||
|
* and 'replies' (array of notes in the same format)
|
||||||
*/
|
*/
|
||||||
private function feedFormatTree(array $notes): array
|
private function feedFormatTree(array $notes): array
|
||||||
{
|
{
|
||||||
$tree = [];
|
$tree = [];
|
||||||
$notes = array_reverse($notes);
|
$notes = array_reverse($notes);
|
||||||
|
$max_replies_to_show = Common::config('plugin_tree_notes', 'feed_replies');
|
||||||
foreach ($notes as $note) {
|
foreach ($notes as $note) {
|
||||||
if (!\is_null($children = $note->getReplies())) {
|
if (!\is_null($children = $note->getReplies(limit: $max_replies_to_show))) {
|
||||||
$notes = array_filter($notes, fn (Note $n) => !\in_array($n, $children));
|
$total_replies = $note->getRepliesCount();
|
||||||
|
$show_more = $total_replies > $max_replies_to_show;
|
||||||
|
$notes = array_filter($notes, fn (Note $n) => !\in_array($n, $children));
|
||||||
|
|
||||||
$tree[] = [
|
$tree[] = [
|
||||||
'note' => $note,
|
'note' => $note,
|
||||||
'replies' => array_map(
|
'replies' => array_map(
|
||||||
fn ($n) => ['note' => $n, 'replies' => []],
|
fn ($n) => [
|
||||||
|
'note' => $n,
|
||||||
|
'replies' => [],
|
||||||
|
'show_more' => ($n->getRepliesCount() > $max_replies_to_show),
|
||||||
|
'total_replies' => $n->getRepliesCount(),
|
||||||
|
],
|
||||||
$children,
|
$children,
|
||||||
),
|
),
|
||||||
|
'total_replies' => $total_replies,
|
||||||
|
'show_more' => $show_more,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$tree[] = ['note' => $note, 'replies' => []];
|
$tree[] = ['note' => $note, 'replies' => [], 'show_more' => false];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +115,24 @@ class TreeNotes extends Plugin
|
||||||
{
|
{
|
||||||
$children = array_filter($notes, fn (Note $note) => $note->getReplyTo() === $parent->getId());
|
$children = array_filter($notes, fn (Note $note) => $note->getReplyTo() === $parent->getId());
|
||||||
|
|
||||||
return ['note' => $parent, 'replies' => $this->conversationFormatTree($children, $notes)];
|
return [
|
||||||
|
'note' => $parent,
|
||||||
|
'replies' => $this->conversationFormatTree($children, $notes),
|
||||||
|
'show_more' => false, // It's always false, we're showing everyone
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onAppendNoteBlock(Request $request, array $conversation, array &$res): bool
|
||||||
|
{
|
||||||
|
if (\array_key_exists('replies', $conversation)) {
|
||||||
|
$res[] = Formatting::twigRenderFile(
|
||||||
|
'tree_notes/note_replies_block.html.twig',
|
||||||
|
[
|
||||||
|
'nickname' => $conversation['note']->getActorNickname(),
|
||||||
|
'conversation' => $conversation,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Event::next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
{% import '/cards/macros/note/factory.html.twig' as NoteFactory %}
|
||||||
|
<section class="note-replies" title="{{ 'Replies to ' | trans }}{{ nickname }}{{ '\'s note' | trans }}">
|
||||||
|
<div class="note-replies-start"></div>
|
||||||
|
<div class="u-in-reply-to replies">
|
||||||
|
{% for reply in conversation.replies %}
|
||||||
|
<span class="note-replies-indicator" role="presentation"></span>
|
||||||
|
{% set args = { 'type': 'vanilla_full', 'conversation': reply } %}
|
||||||
|
{{ NoteFactory.constructor(args) }}
|
||||||
|
{% endfor %}
|
||||||
|
{% if conversation.show_more %}
|
||||||
|
<a href="{{ conversation.note.getConversationUrl() }}">
|
||||||
|
{{ transchoice({
|
||||||
|
'1': 'Show an additional reply',
|
||||||
|
'other': 'Show # additional replies'
|
||||||
|
}, (conversation.total_replies - config('plugin_tree_notes', 'feed_replies'))) }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</section>
|
|
@ -279,6 +279,9 @@ parameters:
|
||||||
feeds:
|
feeds:
|
||||||
entries_per_page: 32
|
entries_per_page: 32
|
||||||
|
|
||||||
|
plugin_tree_notes:
|
||||||
|
feed_replies: 3
|
||||||
|
|
||||||
oauth2:
|
oauth2:
|
||||||
private_key: '%kernel.project_dir%/file/oauth/private.key'
|
private_key: '%kernel.project_dir%/file/oauth/private.key'
|
||||||
private_key_password: null
|
private_key_password: null
|
||||||
|
|
|
@ -269,6 +269,7 @@ class Note extends Entity
|
||||||
'links' => "note-links-{$note_id}",
|
'links' => "note-links-{$note_id}",
|
||||||
'tags' => "note-tags-{$note_id}",
|
'tags' => "note-tags-{$note_id}",
|
||||||
'replies' => "note-replies-{$note_id}",
|
'replies' => "note-replies-{$note_id}",
|
||||||
|
'replies-count' => "note-replies-count-{$note_id}",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,9 +406,24 @@ class Note extends Entity
|
||||||
/**
|
/**
|
||||||
* Returns all **known** replies made to this entity
|
* Returns all **known** replies made to this entity
|
||||||
*/
|
*/
|
||||||
public function getReplies(): array
|
public function getReplies(int $offset = 0, int $limit = null): array
|
||||||
{
|
{
|
||||||
return Cache::getList(self::cacheKeys($this->getId())['replies'], fn () => DB::findBy('note', ['reply_to' => $this->getId()], order_by: ['created' => 'DESC', 'id' => 'DESC']));
|
return Cache::getList(self::cacheKeys($this->getId())['replies'],
|
||||||
|
fn () => DB::findBy(self::class, [
|
||||||
|
'reply_to' => $this->getId()
|
||||||
|
],
|
||||||
|
order_by: ['created' => 'ASC', 'id' => 'ASC']),
|
||||||
|
left: $offset,
|
||||||
|
right: $limit
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRepliesCount(): int
|
||||||
|
{
|
||||||
|
return Cache::get(
|
||||||
|
self::cacheKeys($this->getId())['replies-count'],
|
||||||
|
fn() => DB::count(self::class, ['reply_to' => $this->getId()])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,23 +22,6 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock note_actions %}
|
{% endblock note_actions %}
|
||||||
|
|
||||||
{% block note_replies %}
|
|
||||||
{% import '/cards/macros/note/factory.html.twig' as NoteFactory %}
|
|
||||||
|
|
||||||
{% if replies is defined and replies is not empty %}
|
|
||||||
<section class="note-replies" title="{{ 'Replies to ' | trans }}{{ nickname }}{{ '\'s note' | trans }}">
|
|
||||||
<div class="note-replies-start"></div>
|
|
||||||
<div class="u-in-reply-to replies">
|
|
||||||
{% for conversation in replies %}
|
|
||||||
<span class="note-replies-indicator" role="presentation"></span>
|
|
||||||
{% set args = { 'type': 'vanilla_full', 'note': conversation['note'], 'replies': conversation['replies'] | default, 'extra': extra } %}
|
|
||||||
{{ NoteFactory.constructor(args) }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock note_replies %}
|
|
||||||
|
|
||||||
{% block note_attachments %}
|
{% block note_attachments %}
|
||||||
{% if hide_attachments is not defined %}
|
{% if hide_attachments is not defined %}
|
||||||
{% if note.getAttachments() is not empty %}
|
{% if note.getAttachments() is not empty %}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
|
|
||||||
{# args: { 'type': { 'vanilla_full' }, 'note': note, ?'replies': { note, ?replies }, ?'extra': { 'foo': bar } #}
|
{# args: { 'type': { 'vanilla_full' }, 'note': note, ?'replies': { note, ?replies }, ?'extra': { 'foo': bar } #}
|
||||||
{% macro vanilla_full(args) %}
|
{% macro vanilla_full(args) %}
|
||||||
{% set note = args.note %}
|
{% set note = args.conversation.note %}
|
||||||
{% if args.replies is defined %}{% set replies = args.replies %}{% else %}{% set replies = null %}{% endif %}
|
|
||||||
{% if args.extra is defined %}{% set extra = args.extra %}{% else %}{% set extra = null %}{% endif %}
|
|
||||||
|
|
||||||
{% set actor = note.getActor() %}
|
{% set actor = note.getActor() %}
|
||||||
{% set nickname = actor.getNickname() %}
|
{% set nickname = actor.getNickname() %}
|
||||||
|
@ -54,9 +51,10 @@
|
||||||
{{ block('note_complementary', 'cards/blocks/note.html.twig') }}
|
{{ block('note_complementary', 'cards/blocks/note.html.twig') }}
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
{% if replies is defined %}
|
{% set additional_blocks = handle_event('AppendNoteBlock', app.request, args.conversation) %}
|
||||||
{{ block('note_replies', 'cards/blocks/note.html.twig') }}
|
{% for block in additional_blocks %}
|
||||||
{% endif %}
|
{{ block | raw }}
|
||||||
|
{% endfor %}
|
||||||
{% endmacro vanilla_full %}
|
{% endmacro vanilla_full %}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user