[PLUGINS][Repeat] Added note_repeat entity, fixed visual discrepancies, and completed the expected functionality.

[ENTITY][Note] Removed repeat_off from table. It is now part of the Repeat plugin.
This commit is contained in:
Eliseu Amaro 2021-11-01 21:19:20 +00:00
parent 73e772e576
commit cf09b48e92
Signed by: eliseuamaro
GPG Key ID: 96DA09D4B97BC2D5
8 changed files with 210 additions and 30 deletions

View File

@ -34,6 +34,7 @@ use App\Util\Exception\InvalidFormException;
use App\Util\Exception\NoLoggedInUser; use App\Util\Exception\NoLoggedInUser;
use App\Util\Exception\NoSuchNoteException; use App\Util\Exception\NoSuchNoteException;
use App\Util\Exception\RedirectException; use App\Util\Exception\RedirectException;
use Plugin\Repeat\Entity\NoteRepeat;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use function App\Core\I18n\_m; use function App\Core\I18n\_m;
@ -52,7 +53,7 @@ class Repeat extends Controller
{ {
$user = Common::ensureLoggedIn(); $user = Common::ensureLoggedIn();
$opts = ['actor_id' => $user->getId(), 'repeat_of' => $id]; $opts = ['actor_id' => $user->getId(), 'repeat_of' => $id];
$note_already_repeated = DB::count('note', $opts) >= 1; $note_already_repeated = DB::count('note_repeat', $opts) >= 1;
if (is_null($note_already_repeated)) { if (is_null($note_already_repeated)) {
throw new NoSuchNoteException(); throw new NoSuchNoteException();
} }
@ -71,15 +72,38 @@ class Repeat extends Controller
$form_add_to_repeat->handleRequest($request); $form_add_to_repeat->handleRequest($request);
if ($form_add_to_repeat->isSubmitted()) { if ($form_add_to_repeat->isSubmitted()) {
if (!is_null($note)) { if (!is_null($note)) {
DB::persist(Note::create([ $actor_id = $user->getId();
'actor_id' => $user->getId(), $content = $note->getContent();
'repeat_of' => $note->getId(),
'content' => $note->getContent(), // Create a new note with the same content as the original
$repeat = Note::create([
'actor_id' => $actor_id,
'content' => $content,
'content_type' => $note->getContentType(), 'content_type' => $note->getContentType(),
'rendered' => $note->getRendered(), 'rendered' => $note->getRendered(),
'is_local' => true, 'is_local' => true,
])); ]);
DB::persist($repeat);
// Update DB
DB::flush();
// Find the id of the note we just created
$repeat_id = $repeat->getId();
$og_id = $note->getId();
// Add it to note_repeat table
if (!is_null($repeat_id)) {
DB::persist(NoteRepeat::create([
'id' => $repeat_id,
'actor_id' => $actor_id,
'repeat_of' => $og_id
]));
}
// Update DB one last time
DB::flush(); DB::flush();
} }
@ -106,7 +130,7 @@ class Repeat extends Controller
public function repeatRemoveNote(Request $request, int $id): array public function repeatRemoveNote(Request $request, int $id): array
{ {
$user = Common::ensureLoggedIn(); $user = Common::ensureLoggedIn();
$opts = ['note_id' => $id, 'actor_id' => $user->getId()]; $opts = ['id' => $id];
$remove_repeat_note = DB::find('note', $opts); $remove_repeat_note = DB::find('note', $opts);
if (is_null($remove_repeat_note)) { if (is_null($remove_repeat_note)) {
throw new NoSuchNoteException(); throw new NoSuchNoteException();

View File

@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
// {{{ License
// This file is part of GNU social - https://www.gnu.org/software/social
//
// GNU social is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GNU social is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
// }}}
namespace Plugin\Repeat\Entity;
use App\Core\Entity;
/**
* Entity for notices
*
* @category DB
* @package GNUsocial
*
* @author Eliseu Amaro <mail@eliseuama.ro>
* @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class NoteRepeat extends Entity
{
private int $id;
private int $actor_id;
private int $repeat_of;
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
public function getId(): int
{
return $this->id;
}
public function setActorId(int $actor_id): self
{
$this->actor_id = $actor_id;
return $this;
}
public function getActorId(): ?int
{
return $this->actor_id;
}
public function setRepeatOf(int $repeat_of): self
{
$this->repeat_of = $repeat_of;
return $this;
}
public function getRepeatOf(): int
{
return $this->repeat_of;
}
public static function schemaDef(): array
{
return [
'name' => 'note_repeat',
'fields' => [
'id' => ['type' => 'int', 'not null' => true, 'description' => 'The id of the repeat itself'],
'actor_id' => ['type' => 'int', 'not null' => true, 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'description' => 'Who made this repeat'],
'repeat_of' => ['type' => 'int', 'not null' => true, 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'description' => 'Note this is a repeat of'],
],
'primary key' => ['id'],
'foreign keys' => [
'note_repeat_of_id_fkey' => ['note', ['repeat_of' => 'id']],
],
];
}
}

View File

@ -25,11 +25,13 @@ use App\Core\DB\DB;
use App\Core\Event; use App\Core\Event;
use App\Core\Router\RouteLoader; use App\Core\Router\RouteLoader;
use App\Core\Router\Router; use App\Core\Router\Router;
use App\Util\Exception\DuplicateFoundException;
use App\Util\Exception\InvalidFormException; use App\Util\Exception\InvalidFormException;
use App\Util\Exception\NoSuchNoteException; use App\Util\Exception\NoSuchNoteException;
use App\Core\Modules\NoteHandlerPlugin; use App\Core\Modules\NoteHandlerPlugin;
use App\Entity\Note; use App\Entity\Note;
use App\Util\Common; use App\Util\Common;
use App\Util\Exception\NotFoundException;
use App\Util\Exception\RedirectException; use App\Util\Exception\RedirectException;
use App\Util\Formatting; use App\Util\Formatting;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -53,21 +55,30 @@ class Repeat extends NoteHandlerPlugin
return Event::next; return Event::next;
} }
// If note is repeat, "is_repeated" is 1 // If note is repeated, "is_repeated" is 1
$opts = ['actor_id' => $user->getId(), 'repeat_of' => $note->getId()]; $opts = ['repeat_of' => $note->getId()];
$is_repeated = DB::count('note', $opts) >= 1; try {
if (DB::findOneBy('note_repeat', $opts)) {
return Event::next;
}
} catch (DuplicateFoundException $e) {
} catch (NotFoundException $e) {
}
$is_repeat = DB::count('note_repeat', ['id' => $note->getId()]) >= 1;
// Generating URL for repeat action route // Generating URL for repeat action route
$args = ['id' => $note->getId()]; $args = ['id' => $note->getId()];
$type = Router::ABSOLUTE_PATH; $type = Router::ABSOLUTE_PATH;
$repeat_action_url = $is_repeated ? $repeat_action_url = $is_repeat ?
Router::url('repeat_remove', $args, $type) : Router::url('repeat_remove', $args, $type) :
Router::url('repeat_add', $args, $type); Router::url('repeat_add', $args, $type);
// Concatenating get parameter to redirect the user to where he came from // Concatenating get parameter to redirect the user to where he came from
$repeat_action_url .= '?from=' . substr($request->getQueryString(), 2); $repeat_action_url .= '?from=' . substr($request->getQueryString(), 2);
$extra_classes = $is_repeated ? "note-actions-set" : "note-actions-unset"; $extra_classes = $is_repeat ? "note-actions-set" : "note-actions-unset";
$repeat_action = [ $repeat_action = [
"url" => $repeat_action_url, "url" => $repeat_action_url,
"classes" => "button-container repeat-button-container $extra_classes", "classes" => "button-container repeat-button-container $extra_classes",
@ -89,6 +100,17 @@ class Repeat extends NoteHandlerPlugin
return Event::next; return Event::next;
} }
public function onGetAdditionalTemplateVars(array $vars, array &$result)
{
$note_id = $vars['note_id'];
$opts = ['id' => $note_id];
$is_repeat = DB::count('note_repeat', $opts) >= 1;
$result = ['is_repeat' => (bool)$is_repeat];
return Event::stop;
}
public function onAddRoute(RouteLoader $r): bool public function onAddRoute(RouteLoader $r): bool
{ {
// Add/remove note to/from repeats // Add/remove note to/from repeats

View File

@ -1,22 +1,54 @@
{% extends '/cards/note/view.html.twig' %} {% extends '/cards/note/view.html.twig' %}
{% block note_author_repeated %}
{# Microformat's h-card properties indicates a face icon is a "u-logo" #}
<a href="{{ actor_url }}" class="note-author u-url">
<strong class="note-author-fullname">
{% if fullname is not null %}
{{ fullname }}
{% else %}
{{ nickname }}
{% endif %}
</strong>
<em class="note-author-nickname">{{ nickname }} {{ "repeated the following note" | trans }}</em>
</a>
{% endblock note_author_repeated %}
{% macro macro_note(note, replies) %} {% macro macro_note(note, replies) %}
{% set nickname = note.getActorNickname() %} {% set nickname = note.getActorNickname() %}
{% set fullname = note.getActorFullname() %} {% set fullname = note.getActorFullname() %}
{% set actor_url = note.getActor().getUrl() %} {% set actor_url = note.getActor().getUrl() %}
{% set repeat_of = note.getRepeatOf() %} {% set additional_vars = handle_event('GetAdditionalTemplateVars', {'note_id': note.getId(), 'actor_id': note.getActorId()}) %}
<article class="h-entry hentry note"> {% if additional_vars['is_repeat'] %}
{{ block('note_sidebar') }} <article class="h-entry hentry note">
<div class="note-wrapper"> {{ block('note_sidebar') }}
<div tabindex="0" title="{{ 'Begin a note by the user: ' | trans }} {{ nickname }}." class="note-info"> <div class="note-wrapper">
{{ block('note_author') }} <div tabindex="0" title="{{ 'Begin a note by the user: ' | trans }} {{ nickname }}." class="note-info">
{{ block('note_actions') }} {{ block('note_author_repeated') }}
{{ block('note_actions') }}
</div>
<section tabindex="0" role="dialog" class="e-content entry-content note-content">
{{ _self.macro_note_minimal(note) }}
</section>
{{ block('note_replies') }}
</div> </div>
<section tabindex="0" role="dialog" class="e-content entry-content note-content"> </article>
{{ _self.macro_note_minimal(note) }} {% else %}
</section> <article class="h-entry hentry note">
{{ block('note_replies') }} {{ block('note_sidebar') }}
</div> <div class="note-wrapper">
</article> <div tabindex="0" title="{{ 'Begin a note by the user: ' | trans }} {{ nickname }}." class="note-info">
{{ block('note_author') }}
{{ block('note_actions') }}
</div>
<section tabindex="0" role="dialog" class="e-content entry-content note-content">
{{ block('note_text') }}
{{ block('note_attachments') }}
{{ block('note_links') }}
</section>
{{ block('note_replies') }}
</div>
</article>
{% endif %}
{% endmacro macro_note %} {% endmacro macro_note %}

View File

@ -13,7 +13,7 @@
<div class="page"> <div class="page">
<div class="main"> <div class="main">
{{ noteView.macro_note_minimal(note) }} {{ noteView.macro_note_minimal(note) }}
{{ form(remove_favourite) }} {{ form(remove_repeat) }}
</div> </div>
</div> </div>
{% endblock body %} {% endblock body %}

View File

@ -162,7 +162,7 @@ class Note extends Entity
return $this->conversation; return $this->conversation;
} }
public function setRepeatOf(?int $repeat_of): self /* public function setRepeatOf(?int $repeat_of): self
{ {
$this->repeat_of = $repeat_of; $this->repeat_of = $repeat_of;
return $this; return $this;
@ -171,7 +171,7 @@ class Note extends Entity
public function getRepeatOf(): ?int public function getRepeatOf(): ?int
{ {
return $this->repeat_of; return $this->repeat_of;
} }*/
public function setScope(int $scope): self public function setScope(int $scope): self
{ {
@ -349,7 +349,7 @@ class Note extends Entity
'is_local' => ['type' => 'bool', 'description' => 'was this note generated by a local actor'], 'is_local' => ['type' => 'bool', 'description' => 'was this note generated by a local actor'],
'source' => ['type' => 'varchar', 'foreign key' => true, 'length' => 32, 'target' => 'NoteSource.code', 'multiplicity' => 'many to one', 'description' => 'fkey to source of note, like "web", "im", or "clientname"'], 'source' => ['type' => 'varchar', 'foreign key' => true, 'length' => 32, 'target' => 'NoteSource.code', 'multiplicity' => 'many to one', 'description' => 'fkey to source of note, like "web", "im", or "clientname"'],
'conversation' => ['type' => 'int', 'foreign key' => true, 'target' => 'Conversation.id', 'multiplicity' => 'one to one', 'description' => 'the local conversation id'], 'conversation' => ['type' => 'int', 'foreign key' => true, 'target' => 'Conversation.id', 'multiplicity' => 'one to one', 'description' => 'the local conversation id'],
'repeat_of' => ['type' => 'int', 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'description' => 'note this is a repeat of'], // 'repeat_of' => ['type' => 'int', 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'description' => 'note this is a repeat of'],
'scope' => ['type' => 'int', 'not null' => true, 'default' => VisibilityScope::PUBLIC, 'description' => 'bit map for distribution scope; 0 = everywhere; 1 = this server only; 2 = addressees; 4 = groups; 8 = followers; 16 = messages; null = default'], 'scope' => ['type' => 'int', 'not null' => true, 'default' => VisibilityScope::PUBLIC, 'description' => 'bit map for distribution scope; 0 = everywhere; 1 = this server only; 2 = addressees; 4 = groups; 8 = followers; 16 = messages; null = default'],
'url' => ['type' => 'text', 'description' => 'Permalink to Note'], 'url' => ['type' => 'text', 'description' => 'Permalink to Note'],
'language' => ['type' => 'int', 'foreign key' => true, 'target' => 'Language.id', 'multiplicity' => 'one to many', 'description' => 'The language for this note'], 'language' => ['type' => 'int', 'foreign key' => true, 'target' => 'Language.id', 'multiplicity' => 'one to many', 'description' => 'The language for this note'],
@ -361,7 +361,7 @@ class Note extends Entity
'note_created_id_is_local_idx' => ['created', 'is_local'], 'note_created_id_is_local_idx' => ['created', 'is_local'],
'note_actor_created_idx' => ['actor_id', 'created'], 'note_actor_created_idx' => ['actor_id', 'created'],
'note_is_local_created_actor_idx' => ['is_local', 'created', 'actor_id'], 'note_is_local_created_actor_idx' => ['is_local', 'created', 'actor_id'],
'note_repeat_of_created_idx' => ['repeat_of', 'created'], // 'note_repeat_of_created_idx' => ['repeat_of', 'created'],
'note_conversation_created_idx' => ['conversation', 'created'], 'note_conversation_created_idx' => ['conversation', 'created'],
'note_reply_to_idx' => ['reply_to'], 'note_reply_to_idx' => ['reply_to'],
], ],

View File

@ -134,6 +134,15 @@ class Runtime implements RuntimeExtensionInterface, EventSubscriberInterface
return $result; return $result;
} }
public function getAdditionalTemplateVars(array $vars): array
{
$result = [];
if (Event::handle('GetAdditionalTemplateVars', [$vars, &$result]) !== Event::stop) {
return [];
}
return $result;
}
// ---------------------------------------------------------- // ----------------------------------------------------------
/** /**

View File

@ -1,4 +1,5 @@
{% extends 'stdgrid.html.twig' %} {% extends 'stdgrid.html.twig' %}
{% set override_import = handle_override_template_import('/network/feed.html.twig', '/cards/note/view.html.twig') %} {% set override_import = handle_override_template_import('/network/feed.html.twig', '/cards/note/view.html.twig') %}
{% import override_import as noteView %} {% import override_import as noteView %}