From b0e8a32d2aab9c816dc3750391e57b9f0d8d7d9a Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Wed, 2 Sep 2020 17:42:17 +0200 Subject: [PATCH] Improvements to group page Signed-off-by: Thomas Citharel --- js/src/components/Account/ActorCard.vue | 2 +- js/src/components/Editor.vue | 39 ++++++++---- js/src/components/Event/EventListViewCard.vue | 6 +- .../components/Event/EventMinimalistCard.vue | 41 ++++++++++++- js/src/graphql/group.ts | 36 ++++++++++- js/src/views/Event/GroupEvents.vue | 33 +++++++--- js/src/views/Group/Group.vue | 49 +++++++++------ js/src/views/Group/GroupSettings.vue | 16 ++--- lib/federation/activity_pub/types/actors.ex | 29 ++++++--- lib/graphql/resolvers/group.ex | 37 ++++++++++-- lib/graphql/schema/actors/group.ex | 4 ++ lib/mobilizon/actors/actor.ex | 4 +- lib/mobilizon/events/events.ex | 60 ++++++++++++++++++- lib/service/export/feed.ex | 2 +- lib/service/export/icalendar.ex | 2 +- lib/web/controllers/feed_controller.ex | 2 +- lib/web/views/json_ld/object_view.ex | 18 +++--- test/support/factory.ex | 3 +- test/web/controllers/feed_controller_test.exs | 2 +- 19 files changed, 298 insertions(+), 87 deletions(-) diff --git a/js/src/components/Account/ActorCard.vue b/js/src/components/Account/ActorCard.vue index b5fbe5ee9..4f910e534 100644 --- a/js/src/components/Account/ActorCard.vue +++ b/js/src/components/Account/ActorCard.vue @@ -13,7 +13,7 @@ {{ actor.name || `@${usernameWithDomain(actor)}` }}

@{{ usernameWithDomain(actor) }}

-

{{ actor.summary }}

+
diff --git a/js/src/components/Editor.vue b/js/src/components/Editor.vue index d6a077998..68982a291 100644 --- a/js/src/components/Editor.vue +++ b/js/src/components/Editor.vue @@ -40,6 +40,7 @@ - - - @@ -229,26 +240,30 @@ export default class EditorComponent extends Vue { filteredActors: IActor[] = []; - suggestionRange!: object | null; + suggestionRange!: Record | null; navigatedActorIndex = 0; popup!: Instance[] | null; - get isDescriptionMode() { - return this.mode === "description"; + get isDescriptionMode(): boolean { + return this.mode === "description" || this.isBasicMode; } - get isCommentMode() { + get isCommentMode(): boolean { return this.mode === "comment"; } - get hasResults() { - return this.filteredActors.length; + get hasResults(): boolean { + return this.filteredActors.length > 0; } - get showSuggestions() { - return this.query || this.hasResults; + get showSuggestions(): boolean { + return (this.query || this.hasResults) as boolean; + } + + get isBasicMode(): boolean { + return this.mode === "basic"; } // eslint-disable-next-line @@ -258,7 +273,7 @@ export default class EditorComponent extends Vue { observer!: MutationObserver | null; - mounted() { + mounted(): void { this.editor = new Editor({ extensions: [ new Blockquote(), diff --git a/js/src/components/Event/EventListViewCard.vue b/js/src/components/Event/EventListViewCard.vue index 8e68e2f20..aed279d58 100644 --- a/js/src/components/Event/EventListViewCard.vue +++ b/js/src/components/Event/EventListViewCard.vue @@ -16,7 +16,7 @@ - {{ $t("Organized by {name}", { name: event.organizerActor.displayName() }) }} + {{ $t("Organized by {name}", { name: usernameWithDomain(event.organizerActor) }) }} @@ -53,7 +53,7 @@ import { ParticipantRole, EventVisibility, IEventCardOptions, IEvent } from "@/types/event.model"; import { Component, Prop } from "vue-property-decorator"; import DateCalendarIcon from "@/components/Event/DateCalendarIcon.vue"; -import { IPerson } from "@/types/actor"; +import { IPerson, usernameWithDomain } from "@/types/actor"; import { mixins } from "vue-class-component"; import ActorMixin from "@/mixins/actor"; import { CURRENT_ACTOR_CLIENT } from "@/graphql/actor"; @@ -96,6 +96,8 @@ export default class EventListViewCard extends mixins(ActorMixin, EventMixin) { EventVisibility = EventVisibility; RouteName = RouteName; + + usernameWithDomain = usernameWithDomain; } diff --git a/js/src/components/Event/EventMinimalistCard.vue b/js/src/components/Event/EventMinimalistCard.vue index ae3a312b2..7ede0d852 100644 --- a/js/src/components/Event/EventMinimalistCard.vue +++ b/js/src/components/Event/EventMinimalistCard.vue @@ -9,7 +9,46 @@

{{ event.physicalAddress.description }}

-

3 demandes de participation à traiter

+

+ + {{ + $tc( + "{available}/{capacity} available places", + event.options.maximumAttendeeCapacity - event.participantStats.participant, + { + available: + event.options.maximumAttendeeCapacity - event.participantStats.participant, + capacity: event.options.maximumAttendeeCapacity, + } + ) + }} + + + {{ + $tc("{count} participants", event.participantStats.participant, { + count: event.participantStats.participant, + }) + }} + + + + {{ + $tc("{count} requests waiting", event.participantStats.notApproved, { + count: event.participantStats.notApproved, + }) + }} + + +

diff --git a/js/src/graphql/group.ts b/js/src/graphql/group.ts index 1dddfe9dd..a87f30127 100644 --- a/js/src/graphql/group.ts +++ b/js/src/graphql/group.ts @@ -78,12 +78,30 @@ export const GROUP_FIELDS_FRAGMENTS = gql` banner { url } - organizedEvents { + organizedEvents( + afterDatetime: $afterDateTime + beforeDatetime: $beforeDateTime + page: $organisedEventsPage + limit: $organisedEventslimit + ) { elements { id uuid title beginsOn + options { + maximumAttendeeCapacity + } + participantStats { + participant + notApproved + } + organizerActor { + id + preferredUsername + name + domain + } } total } @@ -154,7 +172,13 @@ export const GROUP_FIELDS_FRAGMENTS = gql` `; export const FETCH_GROUP = gql` - query($name: String!) { + query( + $name: String! + $afterDateTime: DateTime + $beforeDateTime: DateTime + $organisedEventsPage: Int + $organisedEventslimit: Int + ) { group(preferredUsername: $name) { ...GroupFullFields } @@ -166,7 +190,13 @@ export const FETCH_GROUP = gql` `; export const GET_GROUP = gql` - query($id: ID!) { + query( + $id: ID! + $afterDateTime: DateTime + $beforeDateTime: DateTime + $organisedEventsPage: Int + $organisedEventslimit: Int + ) { getGroup(id: $id) { ...GroupFullFields } diff --git a/js/src/views/Event/GroupEvents.vue b/js/src/views/Event/GroupEvents.vue index 0d63a32a7..e7edd6e23 100644 --- a/js/src/views/Event/GroupEvents.vue +++ b/js/src/views/Event/GroupEvents.vue @@ -34,20 +34,25 @@ }}

-
+
- {{ $t("Past events") }} + {{ showPassedEvents ? $t("Past events") : $t("Upcoming events") }} + {{ $t("Past events") }} - + + + {{ $t("No events found") }} +
- - {{ $t("No events found") }} -
@@ -55,6 +60,8 @@ import { Component, Vue } from "vue-property-decorator"; import { FETCH_GROUP } from "@/graphql/group"; import RouteName from "@/router/name"; +import Subtitle from "@/components/Utils/Subtitle.vue"; +import EventListViewCard from "@/components/Event/EventListViewCard.vue"; import { IGroup, usernameWithDomain } from "../../types/actor"; @Component({ @@ -64,10 +71,16 @@ import { IGroup, usernameWithDomain } from "../../types/actor"; variables() { return { name: this.$route.params.preferredUsername, + beforeDateTime: this.showPassedEvents ? new Date() : null, + afterDateTime: this.showPassedEvents ? null : new Date(), }; }, }, }, + components: { + Subtitle, + EventListViewCard, + }, }) export default class GroupEvents extends Vue { group!: IGroup; @@ -75,5 +88,7 @@ export default class GroupEvents extends Vue { usernameWithDomain = usernameWithDomain; RouteName = RouteName; + + showPassedEvents = false; } diff --git a/js/src/views/Group/Group.vue b/js/src/views/Group/Group.vue index db93fa014..70e3a739b 100644 --- a/js/src/views/Group/Group.vue +++ b/js/src/views/Group/Group.vue @@ -302,6 +302,10 @@ {{ $t("No group found") }}
+
+ {{ $t("About") }} +
+
{{ $t("Upcoming events") }}
@@ -318,16 +322,12 @@
{{ $t("Latest posts") }} -
- - {{ post.title }} - +
+ +
+
+

{{ $t("No posts yet") }}

- {{ $t("No public posts") }}
@@ -369,6 +369,7 @@ import FolderItem from "@/components/Resource/FolderItem.vue"; import { Address } from "@/types/address.model"; import Invitations from "@/components/Group/Invitations.vue"; import addMinutes from "date-fns/addMinutes"; +import { Route } from "vue-router"; import GroupSection from "../../components/Group/GroupSection.vue"; import RouteName from "../../router/name"; @@ -413,11 +414,13 @@ import RouteName from "../../router/name"; metaInfo() { return { // if no subcomponents specify a metaInfo.title, this title will be used + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore title: this.groupTitle, // all titles will be injected into this template titleTemplate: "%s | Mobilizon", meta: [ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore { name: "description", content: this.groupSummary }, ], @@ -442,14 +445,14 @@ export default class Group extends Vue { showMap = false; @Watch("currentActor") - watchCurrentActor(currentActor: IActor, oldActor: IActor) { + watchCurrentActor(currentActor: IActor, oldActor: IActor): void { if (currentActor.id && oldActor && currentActor.id !== oldActor.id) { this.$apollo.queries.group.refetch(); } } - async leaveGroup() { - const { data } = await this.$apollo.mutate({ + async leaveGroup(): Promise { + await this.$apollo.mutate({ mutation: LEAVE_GROUP, variables: { groupId: this.group.id, @@ -458,9 +461,10 @@ export default class Group extends Vue { return this.$router.push({ name: RouteName.MY_GROUPS }); } - acceptInvitation() { + acceptInvitation(): void { if (this.groupMember) { const index = this.person.memberships.elements.findIndex( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore ({ id }: IMember) => id === this.groupMember.id ); @@ -471,12 +475,12 @@ export default class Group extends Vue { } } - get groupTitle() { + get groupTitle(): undefined | string { if (!this.group) return undefined; return this.group.preferredUsername; } - get groupSummary() { + get groupSummary(): undefined | string { if (!this.group) return undefined; return this.group.summary; } @@ -486,8 +490,8 @@ export default class Group extends Vue { return this.person.memberships.elements.find(({ parent: { id } }) => id === this.group.id); } - get groupMemberships() { - if (!this.person || !this.person.id) return undefined; + get groupMemberships(): (string | undefined)[] { + if (!this.person || !this.person.id) return []; return this.person.memberships.elements .filter( (membership: IMember) => @@ -499,7 +503,7 @@ export default class Group extends Vue { } get isCurrentActorAGroupMember(): boolean { - return this.groupMemberships != undefined && this.groupMemberships.includes(this.group.id); + return this.groupMemberships !== undefined && this.groupMemberships.includes(this.group.id); } get isCurrentActorARejectedGroupMember(): boolean { @@ -532,7 +536,8 @@ export default class Group extends Vue { } /** - * New members, if on a different server, can take a while to refresh the group and fetch all private data + * New members, if on a different server, + * can take a while to refresh the group and fetch all private data */ get isCurrentActorARecentMember(): boolean { return ( @@ -673,5 +678,11 @@ div.container { } } } + + .public-container { + section { + margin-top: 2rem; + } + } } diff --git a/js/src/views/Group/GroupSettings.vue b/js/src/views/Group/GroupSettings.vue index 084557e43..0ad4ea423 100644 --- a/js/src/views/Group/GroupSettings.vue +++ b/js/src/views/Group/GroupSettings.vue @@ -37,7 +37,7 @@ -

{{ $t("Group visibility") }}

@@ -105,12 +105,12 @@