diff --git a/components/Circle/Circle.php b/components/Circle/Circle.php index 2aa2f08dce..a76e2dfe0e 100644 --- a/components/Circle/Circle.php +++ b/components/Circle/Circle.php @@ -192,7 +192,7 @@ class Circle extends Component */ protected function shouldAddToRightPanel(Actor $user, $vars, Request $request): bool { - return \in_array($vars['path'], ['actor_view_nickname', 'actor_view_id', 'group_actor_view_nickname', 'group_actor_view_id']); + return \in_array($vars['path'], ['actor_view_nickname', 'actor_view_id']); } protected function getCollectionsBy(Actor $owner, ?array $vars = null, bool $ids_only = false): array diff --git a/components/Group/Controller/Group.php b/components/Group/Controller/Group.php index 2e55f44439..6b080ae3d4 100644 --- a/components/Group/Controller/Group.php +++ b/components/Group/Controller/Group.php @@ -32,7 +32,14 @@ use App\Core\UserRoles; use App\Entity as E; use App\Util\Common; use App\Util\Exception\ClientException; +use App\Util\Exception\NicknameEmptyException; +use App\Util\Exception\NicknameInvalidException; +use App\Util\Exception\NicknameNotAllowedException; +use App\Util\Exception\NicknameTakenException; +use App\Util\Exception\NicknameTooLongException; +use App\Util\Exception\NoLoggedInUser; use App\Util\Exception\RedirectException; +use App\Util\Exception\ServerException; use App\Util\Form\ActorForms; use App\Util\Nickname; use Component\Collection\Util\ActorControllerTrait; @@ -41,11 +48,22 @@ use Component\Group\Entity\GroupMember; use Component\Group\Entity\LocalGroup; use Component\Subscription\Entity\ActorSubscription; use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\HttpFoundation\Request; class Group extends FeedController { use ActorControllerTrait; + + /** + * View a group providing its id + * + * @param int $id The id of the group to be shown + * + * @throws ClientException + * + * @return array Containing both the template to be used and the group actor + */ public function groupViewId(Request $request, int $id) { return $this->handleActorById( @@ -58,14 +76,15 @@ class Group extends FeedController } /** - * View a group feed and give the option of creating it if it doesn't exist + * View a group feed by its nickname * - * @throws \App\Util\Exception\NicknameEmptyException - * @throws \App\Util\Exception\NicknameNotAllowedException - * @throws \App\Util\Exception\NicknameTakenException - * @throws \App\Util\Exception\NicknameTooLongException - * @throws \App\Util\Exception\ServerException - * @throws RedirectException + * @param string $nickname The group's nickname to be shown + * + * @throws NicknameEmptyException + * @throws NicknameNotAllowedException + * @throws NicknameTakenException + * @throws NicknameTooLongException + * @throws ServerException * * @return array */ @@ -76,74 +95,27 @@ class Group extends FeedController $actor = Common::actor(); $subscribe_form = null; - if (\is_null($group)) { - if (!\is_null($actor)) { - $create_form = Form::create([ - ['create', SubmitType::class, ['label' => _m('Create this group')]], - ]); - - $create_form->handleRequest($request); - if ($create_form->isSubmitted() && $create_form->isValid()) { - Log::info( - _m( - 'Actor id:{actor_id} nick:{actor_nick} created the group {nickname}', - ['{actor_id}' => $actor->getId(), 'actor_nick' => $actor->getNickname(), 'nickname' => $nickname], - ), - ); - - DB::persist($group = E\Actor::create([ - 'nickname' => $nickname, - 'type' => E\Actor::GROUP, - 'is_local' => true, - 'roles' => UserRoles::BOT, - ])); - DB::persist(LocalGroup::create([ - 'group_id' => $group->getId(), - 'nickname' => $nickname, - ])); - DB::persist(ActorSubscription::create([ - 'subscriber' => $group->getId(), - 'subscribed' => $group->getId(), - ])); - DB::persist(GroupMember::create([ - 'group_id' => $group->getId(), - 'actor_id' => $actor->getId(), - 'is_admin' => true, - ])); - DB::flush(); - Cache::delete(E\Actor::cacheKeys($actor->getId())['subscriber']); - Cache::delete(E\Actor::cacheKeys($actor->getId())['subscribed']); - throw new RedirectException(); - } - - return [ - '_template' => 'group/view.html.twig', - 'nickname' => $nickname, - 'create_form' => $create_form->createView(), - ]; - } - } else { - if (!\is_null($actor) - && \is_null(Cache::get( - ActorSubscription::cacheKeys($actor, $group)['subscribed'], - fn () => DB::findOneBy('subscription', [ - 'subscriber' => $actor->getId(), - 'subscribed' => $group->getId(), - ], return_null: true), - )) - ) { - $subscribe_form = Form::create([['subscribe', SubmitType::class, ['label' => _m('Subscribe to this group')]]]); - $subscribe_form->handleRequest($request); - if ($subscribe_form->isSubmitted() && $subscribe_form->isValid()) { - DB::persist(ActorSubscription::create([ - 'subscriber' => $actor->getId(), - 'subscribed' => $group->getId(), - ])); - DB::flush(); - Cache::delete(E\Actor::cacheKeys($group->getId())['subscriber']); - Cache::delete(E\Actor::cacheKeys($actor->getId())['subscribed']); - Cache::delete(ActorSubscription::cacheKeys($actor, $group)['subscribed']); - } + if (!\is_null($group) + && !\is_null($actor) + && \is_null(Cache::get( + ActorSubscription::cacheKeys($actor, $group)['subscribed'], + fn () => DB::findOneBy('actor_subscription', [ + 'subscriber_id' => $actor->getId(), + 'subscribed_id' => $group->getId(), + ], return_null: true), + )) + ) { + $subscribe_form = Form::create([['subscribe', SubmitType::class, ['label' => _m('Subscribe to this group')]]]); + $subscribe_form->handleRequest($request); + if ($subscribe_form->isSubmitted() && $subscribe_form->isValid()) { + DB::persist(ActorSubscription::create([ + 'subscriber_id' => $actor->getId(), + 'subscribed_id' => $group->getId(), + ])); + DB::flush(); + Cache::delete(E\Actor::cacheKeys($group->getId())['subscribers']); + Cache::delete(E\Actor::cacheKeys($actor->getId())['subscribed']); + Cache::delete(ActorSubscription::cacheKeys($actor, $group)['subscribed']); } } @@ -167,6 +139,83 @@ class Group extends FeedController ]; } + /** + * Page that allows an actor to create a new group + * + * @throws RedirectException + * @throws ServerException + * + * @return array + */ + public function groupCreate(Request $request) + { + if (\is_null($actor = Common::actor())) { + throw new RedirectException('security_login'); + } + + $create_form = Form::create([ + ['group_nickname', TextType::class, ['label' => _m('Group nickname')]], + ['group_create', SubmitType::class, ['label' => _m('Create this group!')]], + ]); + + $create_form->handleRequest($request); + if ($create_form->isSubmitted() && $create_form->isValid()) { + $data = $create_form->getData(); + $nickname = $data['group_nickname']; + + Log::info( + _m( + 'Actor id:{actor_id} nick:{actor_nick} created the group {nickname}', + ['{actor_id}' => $actor->getId(), 'actor_nick' => $actor->getNickname(), 'nickname' => $nickname], + ), + ); + + DB::persist($group = E\Actor::create([ + 'nickname' => $nickname, + 'type' => E\Actor::GROUP, + 'is_local' => true, + 'roles' => UserRoles::BOT, + ])); + DB::persist(LocalGroup::create([ + 'group_id' => $group->getId(), + 'nickname' => $nickname, + ])); + DB::persist(ActorSubscription::create([ + 'subscriber_id' => $group->getId(), + 'subscribed_id' => $group->getId(), + ])); + DB::persist(GroupMember::create([ + 'group_id' => $group->getId(), + 'actor_id' => $actor->getId(), + 'is_admin' => true, + ])); + DB::flush(); + Cache::delete(E\Actor::cacheKeys($actor->getId())['subscribers']); + Cache::delete(E\Actor::cacheKeys($actor->getId())['subscribed']); + + throw new RedirectException(); + } + + return [ + '_template' => 'group/create.html.twig', + 'create_form' => $create_form->createView(), + ]; + } + + /** + * Settings page for the group with the provided nickname, checks if the current actor can administrate given group + * + * @throws ClientException + * @throws NicknameEmptyException + * @throws NicknameInvalidException + * @throws NicknameNotAllowedException + * @throws NicknameTakenException + * @throws NicknameTooLongException + * @throws NoLoggedInUser + * @throws ServerException + * + * @return array + */ public function groupSettings(Request $request, string $nickname) { $group = LocalGroup::getActorByNickname($nickname); diff --git a/components/Group/Group.php b/components/Group/Group.php index 692635aebb..e22dee0141 100644 --- a/components/Group/Group.php +++ b/components/Group/Group.php @@ -39,6 +39,7 @@ class Group extends Component { public function onAddRoute(RouteLoader $r): bool { + $r->connect(id: 'group_create', uri_path: '/group/new', target: [C\Group::class, 'groupCreate']); $r->connect(id: 'group_actor_view_id', uri_path: '/group/{id<\d+>}', target: [C\Group::class, 'groupViewId']); $r->connect(id: 'group_actor_view_nickname', uri_path: '/!{nickname<' . Nickname::DISPLAY_FMT . '>}', target: [C\Group::class, 'groupViewNickname'], options: ['is_system_path' => false]); $r->connect(id: 'group_settings', uri_path: '/!{nickname<' . Nickname::DISPLAY_FMT . '>}/settings', target: [C\Group::class, 'groupSettings'], options: ['is_system_path' => false]); @@ -54,7 +55,7 @@ class Group extends Component $group = $vars['actor']; if (!\is_null($actor) && $group->isGroup() && $actor->canAdmin($group)) { $url = Router::url('group_settings', ['nickname' => $group->getNickname()]); - $res[] = HTML::html(['hr' => '', 'a' => ['attrs' => ['href' => $url, 'title' => _m('Edit group settings')], 'p' => _m('Group settings')]]); + $res[] = HTML::html(['a' => ['attrs' => ['href' => $url, 'title' => _m('Edit group settings'), 'class' => 'profile-extra-actions'], _m('Group settings')]]); } return Event::next; } diff --git a/components/Group/templates/group/create.html.twig b/components/Group/templates/group/create.html.twig new file mode 100644 index 0000000000..f52f0e1076 --- /dev/null +++ b/components/Group/templates/group/create.html.twig @@ -0,0 +1,5 @@ +{% extends 'stdgrid.html.twig' %} + +{% block body %} + {{ form(create_form) }} +{% endblock body %} \ No newline at end of file diff --git a/components/Group/templates/group/view.html.twig b/components/Group/templates/group/view.html.twig index ca2c9f89a0..89e31649be 100644 --- a/components/Group/templates/group/view.html.twig +++ b/components/Group/templates/group/view.html.twig @@ -10,41 +10,56 @@ {% endblock stylesheets %} {% block body %} - {% if subscribe_form is defined and subscribe_form is not null %} - {{ form(subscribe_form) }} - {% endif %} {% if actor is defined and actor is not null %} {% block profile_view %} {% include 'cards/profile/view.html.twig' with { 'actor': actor } only %} {% endblock profile_view %} -
-
- {% if notes is defined and notes is not empty %} - {% for conversation in notes %} - {% block current_note %} - {% if conversation is instanceof('array') %} - {{ noteView.macro_note(conversation['note'], conversation['replies']) }} - {% else %} - {{ noteView.macro_note(conversation) }} - {% endif %} -
- {% endblock current_note %} - {% endfor %} + {% if notes is defined %} +
+
+ {% if page_title is defined %} +

{{ page_title | trans }}

+ {% else %} +

{{ 'Notes' | trans }}

+ {% endif %} + +
+ + {% if notes is not empty %} + {# Backwards compatibility with hAtom 0.1 #} +
+ {% for conversation in notes %} + {% block current_note %} + {% if conversation is instanceof('array') %} + {{ noteView.macro_note(conversation['note'], conversation['replies']) }} + {% else %} + {{ noteView.macro_note(conversation) }} + {% endif %} +
+ {% endblock current_note %} + {% endfor %} +
{% else %} -

{% trans %}No notes here.{% endtrans %}

+
+ {% trans %}No notes yet...{% endtrans %} +
{% endif %} -
-
- - {% else %} -
-

{% trans with { '%group%': nickname } %}The group %group% doesn't exist.{% endtrans %}

- {% if create_form is defined and create_form is not null %} -

{% trans %}Would you like to create it?{% endtrans %}

- {{ form(create_form) }} - {% endif %} -
+ + {% endif %} {% endif %} {% endblock body %} diff --git a/public/assets/default_theme/css/widgets/sections.css b/public/assets/default_theme/css/widgets/sections.css index 16174f8e84..e9c4efff5a 100644 --- a/public/assets/default_theme/css/widgets/sections.css +++ b/public/assets/default_theme/css/widgets/sections.css @@ -16,6 +16,11 @@ .profile-info { display: flex; + flex-wrap: wrap; +} + +.profile-info .avatar { + flex: 0.5; } .profile-info-url { @@ -34,6 +39,10 @@ display: block; } +.profile-info section { + flex: 1; +} + .profile-stats { align-self: center; margin-left: auto; @@ -65,6 +74,17 @@ margin: 4px unset unset; } +.profile-extra-actions { + margin-top: var(--s); + margin-right: var(--s); + background: var(--gradient), var(--background-hard); + border: 2px solid var(--border); + border-radius: var(--s); + padding: 4px 8px; + font-weight: bold; + display: inline-block; +} + .button-container { border: none !important; mask-repeat: no-repeat !important; diff --git a/templates/cards/profile/view.html.twig b/templates/cards/profile/view.html.twig index 7f2559bf28..0871861cce 100644 --- a/templates/cards/profile/view.html.twig +++ b/templates/cards/profile/view.html.twig @@ -18,7 +18,7 @@ title="{% trans %} %actor_nickname%'s avatar{% endtrans %}" width="{{ actor_avatar_dimensions['width'] }}" height="{{ actor_avatar_dimensions['height'] }}"> -
+
@@ -39,15 +39,17 @@ {% endfor %} -
+
- + {{ 'Subscribed' | trans }} {{ actor.getSubscribedCount() }} - + {{ 'Subscribers' | trans }} {{ actor.getSubscribersCount() }}