[TWIG][Cards] Note template fully refactored. Template has now macros for different views for the card.

[PLUGINS][Repeat] WIP: Action added.
[PLUGINS][Favourite] Changes to accomodate note card template refactoring.
[CSS] Fixed textarea from being resized horizontally.
This commit is contained in:
Eliseu Amaro 2021-10-27 20:39:34 +01:00
parent d47f125894
commit 3388e0e8f1
Signed by: eliseuamaro
GPG Key ID: 96DA09D4B97BC2D5
11 changed files with 347 additions and 121 deletions

View File

@ -51,7 +51,7 @@ class Favourite extends NoteHandlerPlugin
*/
public function onAddNoteActions(Request $request, Note $note, array &$actions): bool
{
if (($user = Common::user()) === null) {
if (is_null($user = Common::user())) {
return Event::next;
}

View File

@ -1,4 +1,5 @@
{% extends 'stdgrid.html.twig' %}
{% import "/cards/note/view.html.twig" as noteView %}
{% block title %}{{ 'Favourite ' | trans }}{{ note.getActorNickname() }}{{ '\'s note.' | trans }}{% endblock %}
@ -11,7 +12,7 @@
{{ parent() }}
<div class="page">
<div class="main">
{% include '/cards/note/view.html.twig' with {'note': note, 'note_actions_show': false} only %}
{{ noteView.macro_note_minimal(note) }}
{{ form(add_favourite) }}
</div>
</div>

View File

@ -1,4 +1,5 @@
{% extends 'stdgrid.html.twig' %}
{% import "/cards/note/view.html.twig" as noteView %}
{% block title %}{{ 'Remove favourite from ' | trans }}{{ note.getActorNickname() }}{{ '\'s note.' | trans }}{% endblock %}
@ -11,7 +12,7 @@
{{ parent() }}
<div class="page">
<div class="main">
{% include '/cards/note/view.html.twig' with {'note': note, 'note_actions_show': false} only %}
{{ noteView.macro_note_minimal(note) }}
{{ form(remove_favourite) }}
</div>
</div>

View File

@ -0,0 +1,145 @@
<?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\Controller;
use App\Core\Controller;
use App\Core\DB\DB;
use App\Core\Event;
use App\Core\Form;
use App\Core\Router\Router;
use App\Entity\Note;
use App\Util\Common;
use App\Util\Exception\InvalidFormException;
use App\Util\Exception\NoLoggedInUser;
use App\Util\Exception\NoSuchNoteException;
use App\Util\Exception\RedirectException;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use function App\Core\I18n\_m;
class Repeat extends Controller
{
/**
* @throws RedirectException
* @throws NoSuchNoteException
* @throws InvalidFormException
* @throws \App\Util\Exception\ServerException
* @throws NoLoggedInUser
*/
public function repeatAddNote(Request $request, int $id): bool|array
{
$user = Common::ensureLoggedIn();
$opts = ['actor_id' => $user->getId(), 'repeat_of' => $id];
$note_already_repeated = DB::count('note', $opts) >= 1;
if (is_null($note_already_repeated)) {
throw new NoSuchNoteException();
}
$note = Note::getWithPK(['id' => $id]);
$form_add_to_repeat = Form::create([
['add_repeat', SubmitType::class,
[
'label' => _m('Repeat note!'),
'attr' => [
'title' => _m('Repeat this note!')
],
],
],
]);
$form_add_to_repeat->handleRequest($request);
if ($form_add_to_repeat->isSubmitted()) {
if (!is_null($note)) {
DB::persist(Note::create([
'actor_id' => $user->getId(),
'repeat_of' => $note->getId(),
'content' => $note->getContent(),
'content_type' => $note->getContentType(),
'rendered' => $note->getRendered(),
'is_local' => true,
]));
DB::flush();
}
if (array_key_exists('from', $get_params = $this->params())) {
# TODO anchor on element id
throw new RedirectException($get_params['from']);
}
}
return [
'_template' => 'repeat/add_to_repeats.html.twig',
'note' => $note,
'add_repeat' => $form_add_to_repeat->createView(),
];
}
/**
* @throws RedirectException
* @throws NoSuchNoteException
* @throws InvalidFormException
* @throws \App\Util\Exception\ServerException
* @throws NoLoggedInUser
*/
public function repeatRemoveNote(Request $request, int $id): array
{
$user = Common::ensureLoggedIn();
$opts = ['note_id' => $id, 'actor_id' => $user->getId()];
$remove_repeat_note = DB::find('note', $opts);
if (is_null($remove_repeat_note)) {
throw new NoSuchNoteException();
}
$form_remove_repeat = Form::create([
['remove_repeat', SubmitType::class,
[
'label' => _m('Remove repeat'),
'attr' => [
'title' => _m('Remove note from repeats.')
],
],
],
]);
$form_remove_repeat->handleRequest($request);
if ($form_remove_repeat->isSubmitted()) {
if ($remove_repeat_note) {
DB::remove($remove_repeat_note);
DB::flush();
}
if (array_key_exists('from', $get_params = $this->params())) {
# TODO anchor on element id
throw new RedirectException($get_params['from']);
}
}
return [
'_template' => 'repeat/remove_from_repeats.html.twig',
'note' => $remove_repeat_note,
'remove_repeat' => $form_remove_repeat->createView(),
];
}
}

View File

@ -23,77 +23,66 @@ namespace Plugin\Repeat;
use App\Core\DB\DB;
use App\Core\Event;
use App\Core\Form;
use function App\Core\I18n\_m;
use App\Core\Router\RouteLoader;
use App\Core\Router\Router;
use App\Util\Exception\InvalidFormException;
use App\Util\Exception\NoSuchNoteException;
use App\Core\Modules\NoteHandlerPlugin;
use App\Entity\Note;
use App\Util\Common;
use App\Util\Exception\RedirectException;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
class Repeat extends NoteHandlerPlugin
{
// TODO: Refactoring to link instead of a form
/**
* HTML rendering event that adds the repeat form as a note
* action, if a user is logged in
*
* @throws InvalidFormException
* @throws NoSuchNoteException
* @throws RedirectException
*
* @return bool Event hook
*/
/* public function onAddNoteActions(Request $request, Note $note, array &$actions)
public function onAddNoteActions(Request $request, Note $note, array &$actions): bool
{
if (($user = Common::user()) === null) {
if (is_null($user = Common::user())) {
return Event::next;
}
$opts = ['actor_id' => $user->getId(), 'repeat_of' => $note->getId()];
$is_set = DB::count('note', $opts) == 1;
$form_repeat = Form::create([
['submit_repeat', SubmitType::class,
[
'label' => ' ',
'attr' => [
'class' => ($is_set ? 'note-actions-set' : 'note-actions-unset') . ' button-container repeat-button-container',
'title' => $is_set ? _m('Note already repeated!') : _m('Repeat this note!'),
],
],
],
['note_id', HiddenType::class, ['data' => $note->getId()]],
["repeat-{$note->getId()}", HiddenType::class, ['data' => $is_set ? '1' : '0']],
]);
// If note is repeat, "is_repeated" is 1
$opts = ['actor_id' => $user->getId(), 'repeat_of' => $note->getId()];
$is_repeated = DB::count('note', $opts) >= 1;
// Handle form
$ret = self::noteActionHandle(
$request,
$form_repeat,
$note,
"repeat-{$note->getId()}",
function ($note, $data, $user) {
if ($data["repeat-{$note->getId()}"] === '0') {
DB::persist(Note::create([
'actor_id' => $user->getId(),
'repeat_of' => $note->getId(),
'content' => $note->getContent(),
'is_local' => true,
]));
} else {
DB::remove(DB::findOneBy('note', ['actor_id' => $user->getId(), 'repeat_of' => $note->getId()]));
}
DB::flush();
// Generating URL for repeat action route
$args = ['id' => $note->getId()];
$type = Router::ABSOLUTE_PATH;
$repeat_action_url = $is_repeated ?
Router::url('repeat_remove', $args, $type) :
Router::url('repeat_add', $args, $type);
// Prevent accidental refreshes from resubmitting the form
throw new RedirectException();
// Concatenating get parameter to redirect the user to where he came from
$repeat_action_url .= '?from=' . substr($request->getQueryString(), 2);
return Event::stop;
},
);
$extra_classes = $is_repeated ? "note-actions-set" : "note-actions-unset";
$repeat_action = [
"url" => $repeat_action_url,
"classes" => "button-container repeat-button-container $extra_classes",
"id" => "repeat-button-container-" . $note->getId()
];
if ($ret !== null) {
return $ret;
}
$actions[] = $form_repeat->createView();
$actions[] = $repeat_action;
return Event::next;
}*/
}
public function onAddRoute(RouteLoader $r): bool
{
// Add/remove note to/from repeats
$r->connect(id: 'repeat_add', uri_path: '/object/note/{id<\d+>}/repeat', target: [Controller\Repeat::class, 'repeatAddNote']);
$r->connect(id: 'repeat_remove', uri_path: '/object/note/{id<\d+>}/unrepeat', target: [Controller\Repeat::class, 'repeatRemoveNote']);
return Event::next;
}
}

View File

@ -0,0 +1,19 @@
{% extends 'stdgrid.html.twig' %}
{% import "/cards/note/view.html.twig" as noteView %}
{% block title %}{{ 'Repeat ' | trans }}{{ note.getActorNickname() }}{{ '\'s note.' | trans }}{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('assets/default_theme/css/pages/feeds.css') }}" type="text/css">
{% endblock stylesheets %}
{% block body %}
{{ parent() }}
<div class="page">
<div class="main">
{{ noteView.macro_note_minimal(note) }}
{{ form(add_repeat) }}
</div>
</div>
{% endblock body %}

View File

@ -0,0 +1,19 @@
{% extends 'stdgrid.html.twig' %}
{% import "/cards/note/view.html.twig" as noteView %}
{% block title %}{{ 'Remove repeat from ' | trans }}{{ note.getActorNickname() }}{{ '\'s note.' | trans }}{% endblock %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('assets/default_theme/css/pages/feeds.css') }}" type="text/css">
{% endblock stylesheets %}
{% block body %}
{{ parent() }}
<div class="page">
<div class="main">
{{ noteView.macro_note_minimal(note) }}
{{ form(remove_favourite) }}
</div>
</div>
{% endblock body %}

View File

@ -616,7 +616,8 @@ html {
border-radius: var(--smaller);
}
50% {
-webkit-border-radius: var(--smaller);
-webkit-border-rns
Enable/disabadius: var(--smaller);
border-radius: var(--smaller);
-webkit-box-shadow: inset 0 20px 40px #FFF;
box-shadow: inset 0 20px 40px #FFF;
@ -818,4 +819,4 @@ html {
border-radius: var(--smaller);
}
}
}
}

View File

@ -16,6 +16,10 @@
border: 2px solid transparent;
}
.section-panel-right textarea {
resize: vertical;
}
@media only screen and (min-width: 1281px) {
.panel-right-icon {
display: none;

View File

@ -1,74 +1,120 @@
{% set nickname = note.getActorNickname() %}
{% set fullname = note.getActorFullname() %}
{% set actor_url = note.getActor().getUrl() %}
{% if note_actions_show is not defined %} {% set note_actions_show = true %} {% endif %}
{% block note_actions %}
{% if app.user or note_actions_hide is defined %}
<div class="note-actions">
{% for current_action in get_note_actions(note) %}
<a class="{{ current_action["classes"] }}" href="{{ current_action["url"] }}"></a>
{% endfor %}
</div>
{% endif %}
{% endblock note_actions %}
<article class="h-entry hentry note">
{% block note_reply_to %}
{% set reply_to = note.getReplyToNickname()%}
{% if reply_to is not null %}
{% trans with {'%name%': reply_to} %} in reply to %name% {% endtrans %}
{% endif %}
{% endblock note_reply_to %}
{% block note_replies %}
{% if replies is defined and replies is not empty %}
<div class="u-in-reply-to replies" tabindex="0" title="{{ 'Begin replies to ' | trans }} {{ nickname }}'s note!">
{% for conversation in replies %}
{{ _self.macro_note(conversation['note'], conversation['replies']) }}
<hr tabindex="0" title="{{ 'End of reply' | trans }}">
{% endfor %}
</div>
{% endif %}
{% endblock note_replies %}
{% block note_attachments %}
{% if hide_attachments is not defined %}
{% if note.getAttachments() is not empty %}
<section class="note-attachments" tabindex="0" title="{{ 'Note attachments.' | trans }}">
{% for attachment in note.getAttachments() %}
{% include '/cards/attachments/view.html.twig' with {'attachment': attachment, 'note': note} only%}
{% endfor %}
</section>
{% endif %}
{% endif %}
{% endblock note_attachments %}
{% block note_links %}
{% if note.getLinks() is not empty %}
<div class="note-links" title="{{ 'Shared links.' | trans }}">
{% for link in note.getLinks() %}
{% for block in handle_event('ViewLink', {'link': link, 'note': note}) %}
{{ block | raw }}
{% endfor %}
{% endfor %}
</div>
{% endif %}
{% endblock note_links %}
{% block note_text %}
<div class="note-text" tabindex="0" title="{{ 'Note text content.' | trans }}">
{{ note.getRendered() | raw }}
</div>
{% endblock note_text %}
{% block note_author %}
{# 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 defined %}
{{ fullname }}
{% else %}
{{ nickname }}
{% endif %}
</strong>
<em class="note-author-nickname">{{ nickname }}</em>
</a>
{% endblock note_author %}
{% block note_sidebar %}
<aside class="note-sidebar">
<img class="u-logo avatar" src="{{ note.getActorAvatarUrl() }}" alt="{{ nickname }}'s avatar" width="32px" height="32px">
</aside>
{% endblock note_sidebar %}
<div class="note-wrapper">
{% macro macro_note(note, replies) %}
{% set nickname = note.getActorNickname() %}
{% set fullname = note.getActorFullname() %}
{% set actor_url = note.getActor().getUrl() %}
{# TODO: this should link to the note's user profile? #}
<div tabindex="0" title="{{ 'Begin a note by the user: ' | trans }} {{ nickname }}." class="note-info">
{# 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">{{ fullname }}</strong>
<em class="note-author-nickname">{{ nickname }}</em>
</a>
{% if app.user and note_actions_show %}
<div class="note-actions">
{% for current_action in get_note_actions(note) %}
<a class="{{ current_action["classes"] }}" href="{{ current_action["url"] }}"></a>
{% endfor %}
</div>
{% endif %}
{% set reply_to = note.getReplyToNickname() %}
{% if reply_to is not null and not skip_reply_to is defined %}
{% trans with {'%name%': reply_to} %} in reply to %name% {% endtrans %}
{% endif %}
<article class="h-entry hentry note">
{{ block('note_sidebar') }}
<div class="note-wrapper">
<div tabindex="0" title="{{ 'Begin a note by the user: ' | trans }} {{ nickname }}." class="note-info">
{{ block('note_author') }}
{{ block('note_reply_to') }}
{{ 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>
{% endmacro macro_note %}
<section tabindex="0" role="dialog" class="e-content entry-content note-content">
<div class="note-text" tabindex="0" title="{{ 'Note text content.' | trans }}">
{{ note.getRendered() | raw }}
{% macro macro_note_minimal(note) %}
{% set nickname = note.getActorNickname() %}
{% set fullname = note.getActorFullname() %}
{% set actor_url = note.getActor().getUrl() %}
<article class="h-entry hentry note">
{{ block('note_sidebar') }}
<div class="note-wrapper">
<div tabindex="0" title="{{ 'Begin a note by the user: ' | trans }} {{ nickname }}." class="note-info">
{{ block('note_author') }}
</div>
{% if hide_attachments is not defined %}
{% if note.getAttachments() is not empty %}
<section class="note-attachments" tabindex="0" title="{{ 'Note attachments.' | trans }}">
{% for attachment in note.getAttachments() %}
{% include '/cards/attachments/view.html.twig' with {'attachment': attachment, 'note': note} only%}
{% endfor %}
</section>
{% endif %}
{% endif %}
{% if note.getLinks() is not empty %}
<div class="note-links" title="{{ 'Shared links.' | trans }}">
{% for link in note.getLinks() %}
{% for block in handle_event('ViewLink', {'link': link, 'note': note}) %}
{{ block | raw }}
{% endfor %}
{% endfor %}
</div>
{% endif %}
</section>
{% if replies is defined and replies is not empty %}
<div class="u-in-reply-to replies" tabindex="0" title="{{ 'Begin replies to ' | trans }} {{ nickname }}'s note!">
{% for conversation in replies %}
{% include '/note/view.html.twig' with {'note': conversation['note'], 'skip_reply_to': true, 'have_user': have_user, 'replies': conversation['replies']} only %}
{% endfor %}
</div>
{% endif %}
{% if reply_to is not empty %}
<hr tabindex="0" title="{{ 'End of this reply' | trans }}">
{% endif %}
</div>
</article>
<section tabindex="0" role="dialog" class="e-content entry-content note-content">
{{ block('note_text') }}
{{ block('note_attachments') }}
{{ block('note_links') }}
</section>
</div>
</article>
{% endmacro macro_note_minimal %}

View File

@ -1,4 +1,5 @@
{% extends 'stdgrid.html.twig' %}
{% import "/cards/note/view.html.twig" as noteView %}
{% block title %}{% if page_title is defined %}{{ page_title | trans }}{% endif %}{% endblock %}
@ -13,7 +14,7 @@
<div class="h-feed hfeed notes">
{% if notes is defined and notes is not empty %}
{% for conversation in notes %}
{% include '/cards/note/view.html.twig' with {'note': conversation['note'], 'replies': conversation['replies']} only %}
{{ noteView.macro_note(conversation['note'], conversation['replies']) }}
<hr tabindex="0" title="{{ 'End of note and replies.' | trans }}">
{% endfor %}
{% else %}