diff --git a/js/src/graphql/actor.ts b/js/src/graphql/actor.ts index 54f616d94..43f691a64 100644 --- a/js/src/graphql/actor.ts +++ b/js/src/graphql/actor.ts @@ -433,7 +433,7 @@ export const PERSON_MEMBERSHIPS = gql` } `; -export const PERSON_MEMBERSHIP_GROUP = gql` +export const PERSON_STATUS_GROUP = gql` query PersonMembershipGroup($id: ID!, $group: String!) { person(id: $id) { id @@ -461,6 +461,35 @@ export const PERSON_MEMBERSHIP_GROUP = gql` updatedAt } } + follows(group: $group) { + total + elements { + id + notify + target_actor { + id + preferredUsername + name + domain + avatar { + id + url + } + } + actor { + id + preferredUsername + name + domain + avatar { + id + url + } + } + insertedAt + updatedAt + } + } } } `; diff --git a/js/src/graphql/followers.ts b/js/src/graphql/followers.ts index 48f4f0abc..cc43afe09 100644 --- a/js/src/graphql/followers.ts +++ b/js/src/graphql/followers.ts @@ -47,3 +47,28 @@ export const UPDATE_FOLLOWER = gql` } } `; + +export const FOLLOW_GROUP = gql` + mutation FollowGroup($groupId: ID!, $notify: Boolean) { + followGroup(groupId: $groupId, notify: $notify) { + id + } + } +`; + +export const UNFOLLOW_GROUP = gql` + mutation UnfollowGroup($groupId: ID!) { + unfollowGroup(groupId: $groupId) { + id + } + } +`; + +export const UPDATE_GROUP_FOLLOW = gql` + mutation UpdateGroupFollow($followId: ID!, $notify: Boolean) { + updateGroupFollow(followId: $followId, notify: $notify) { + id + notify + } + } +`; diff --git a/js/src/mixins/group.ts b/js/src/mixins/group.ts index 9b320e6a6..d1b43578e 100644 --- a/js/src/mixins/group.ts +++ b/js/src/mixins/group.ts @@ -1,11 +1,17 @@ import { CURRENT_ACTOR_CLIENT, GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED, - PERSON_MEMBERSHIP_GROUP, + PERSON_STATUS_GROUP, } from "@/graphql/actor"; import { FETCH_GROUP } from "@/graphql/group"; import RouteName from "@/router/name"; -import { IActor, IGroup, IPerson, usernameWithDomain } from "@/types/actor"; +import { + IActor, + IFollower, + IGroup, + IPerson, + usernameWithDomain, +} from "@/types/actor"; import { MemberRole } from "@/types/enums"; import { Component, Vue } from "vue-property-decorator"; @@ -31,7 +37,7 @@ const now = new Date(); }, }, person: { - query: PERSON_MEMBERSHIP_GROUP, + query: PERSON_STATUS_GROUP, fetchPolicy: "cache-and-network", variables() { return { @@ -100,6 +106,21 @@ export default class GroupMixin extends Vue { ); } + get isCurrentActorFollowing(): boolean { + return this.currentActorFollow !== null; + } + + get isCurrentActorFollowingNotify(): boolean { + return this.currentActorFollow?.notify === true; + } + + get currentActorFollow(): IFollower | null { + if (this.person?.follows?.total > 0) { + return this.person?.follows?.elements[0]; + } + return null; + } + handleErrors(errors: any[]): void { if ( errors.some((error) => error.status_code === 404) || diff --git a/js/src/types/actor/follower.model.ts b/js/src/types/actor/follower.model.ts index 9c96b7ec0..5d0a9acc0 100644 --- a/js/src/types/actor/follower.model.ts +++ b/js/src/types/actor/follower.model.ts @@ -5,4 +5,5 @@ export interface IFollower { actor: IActor; targetActor: IActor; approved: boolean; + notify?: boolean; } diff --git a/js/src/types/actor/index.ts b/js/src/types/actor/index.ts index ca57d3a40..5ecc51b53 100644 --- a/js/src/types/actor/index.ts +++ b/js/src/types/actor/index.ts @@ -1,3 +1,4 @@ export * from "./actor.model"; export * from "./group.model"; export * from "./person.model"; +export * from "./follower.model"; diff --git a/js/src/types/actor/person.model.ts b/js/src/types/actor/person.model.ts index 699f8a72d..dc8451a42 100644 --- a/js/src/types/actor/person.model.ts +++ b/js/src/types/actor/person.model.ts @@ -6,12 +6,14 @@ import type { Paginate } from "../paginate"; import type { IParticipant } from "../participant.model"; import type { IMember } from "./member.model"; import type { IFeedToken } from "../feedtoken.model"; +import { IFollower } from "./follower.model"; export interface IPerson extends IActor { feedTokens: IFeedToken[]; goingToEvents: IEvent[]; participations: Paginate; memberships: Paginate; + follows: Paginate; user?: ICurrentUser; } @@ -31,6 +33,7 @@ export class Person extends Actor implements IPerson { this.patch(hash); } + follows!: Paginate; patch(hash: IPerson | Record): void { Object.assign(this, hash); diff --git a/js/src/views/Discussions/DiscussionsList.vue b/js/src/views/Discussions/DiscussionsList.vue index a49c901c8..22b06a54a 100644 --- a/js/src/views/Discussions/DiscussionsList.vue +++ b/js/src/views/Discussions/DiscussionsList.vue @@ -89,7 +89,7 @@ import { MemberRole } from "@/types/enums"; import { CURRENT_ACTOR_CLIENT, GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED, - PERSON_MEMBERSHIP_GROUP, + PERSON_STATUS_GROUP, } from "@/graphql/actor"; import { IMember } from "@/types/actor/member.model"; import EmptyContent from "@/components/Utils/EmptyContent.vue"; @@ -116,7 +116,7 @@ const DISCUSSIONS_PER_PAGE = 10; }, }, person: { - query: PERSON_MEMBERSHIP_GROUP, + query: PERSON_STATUS_GROUP, fetchPolicy: "cache-and-network", variables() { return { diff --git a/js/src/views/Event/Edit.vue b/js/src/views/Event/Edit.vue index 7662c41a6..ddf96e3a8 100644 --- a/js/src/views/Event/Edit.vue +++ b/js/src/views/Event/Edit.vue @@ -607,7 +607,7 @@ import { IDENTITIES, LOGGED_USER_DRAFTS, LOGGED_USER_PARTICIPATIONS, - PERSON_MEMBERSHIP_GROUP, + PERSON_STATUS_GROUP, } from "../../graphql/actor"; import { displayNameAndUsername, @@ -669,7 +669,7 @@ const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10; }, }, person: { - query: PERSON_MEMBERSHIP_GROUP, + query: PERSON_STATUS_GROUP, fetchPolicy: "cache-and-network", variables() { return { diff --git a/js/src/views/Event/Event.vue b/js/src/views/Event/Event.vue index 48991bdad..609223fbe 100755 --- a/js/src/views/Event/Event.vue +++ b/js/src/views/Event/Event.vue @@ -496,10 +496,7 @@ import { FETCH_EVENT, JOIN_EVENT, } from "../../graphql/event"; -import { - CURRENT_ACTOR_CLIENT, - PERSON_MEMBERSHIP_GROUP, -} from "../../graphql/actor"; +import { CURRENT_ACTOR_CLIENT, PERSON_STATUS_GROUP } from "../../graphql/actor"; import { EventModel, IEvent } from "../../types/event.model"; import { IActor, IPerson, Person, usernameWithDomain } from "../../types/actor"; import { GRAPHQL_API_ENDPOINT } from "../../api/_entrypoint"; @@ -623,7 +620,7 @@ import { IUser } from "@/types/current-user.model"; }, }, person: { - query: PERSON_MEMBERSHIP_GROUP, + query: PERSON_STATUS_GROUP, fetchPolicy: "cache-and-network", variables() { return { diff --git a/js/src/views/Group/Group.vue b/js/src/views/Group/Group.vue index 2cde993c2..0e41a6d6f 100644 --- a/js/src/views/Group/Group.vue +++ b/js/src/views/Group/Group.vue @@ -164,6 +164,54 @@ type="is-primary" >{{ $t("Join group") }} + + {{ + $t("Follow 1") + }} + {{ $t("Follow 2") }} + {{ $t("Follow 3") }}{{ $t("Unfollow") }} + { + try { + const [group, currentActorId] = [ + usernameWithDomain(this.group), + this.currentActor.id, + ]; + await this.$apollo.mutate({ + mutation: FOLLOW_GROUP, + variables: { + groupId: this.group.id, + }, + refetchQueries: [ + { + query: PERSON_STATUS_GROUP, + variables: { + id: currentActorId, + group, + }, + }, + ], + }); + } catch (error: any) { + if (error.graphQLErrors && error.graphQLErrors.length > 0) { + this.$notifier.error(error.graphQLErrors[0].message); + } + } + } + + async unFollowGroup(): Promise { + console.debug("unfollow group"); + try { + const [group, currentActorId] = [ + usernameWithDomain(this.group), + this.currentActor.id, + ]; + await this.$apollo.mutate({ + mutation: UNFOLLOW_GROUP, + variables: { + groupId: this.group.id, + }, + refetchQueries: [ + { + query: PERSON_STATUS_GROUP, + variables: { + id: currentActorId, + group, + }, + }, + ], + }); + } catch (error: any) { + if (error.graphQLErrors && error.graphQLErrors.length > 0) { + this.$notifier.error(error.graphQLErrors[0].message); + } + } + } + + async toggleFollowNotify(): Promise { + await this.$apollo.mutate({ + mutation: UPDATE_GROUP_FOLLOW, + variables: { + followId: this.currentActorFollow?.id, + notify: !this.isCurrentActorFollowingNotify, + }, + }); + } + acceptInvitation(): void { if (this.groupMember) { const index = this.person.memberships.elements.findIndex( diff --git a/js/src/views/Posts/Edit.vue b/js/src/views/Posts/Edit.vue index 91bf702b9..642bcb5c9 100644 --- a/js/src/views/Posts/Edit.vue +++ b/js/src/views/Posts/Edit.vue @@ -181,7 +181,7 @@ import TagInput from "../../components/Event/TagInput.vue"; import RouteName from "../../router/name"; import Subtitle from "../../components/Utils/Subtitle.vue"; import PictureUpload from "../../components/PictureUpload.vue"; -import { PERSON_MEMBERSHIP_GROUP } from "@/graphql/actor"; +import { PERSON_STATUS_GROUP } from "@/graphql/actor"; import { FETCH_GROUP } from "@/graphql/group"; @Component({ @@ -211,7 +211,7 @@ import { FETCH_GROUP } from "@/graphql/group"; }, }, person: { - query: PERSON_MEMBERSHIP_GROUP, + query: PERSON_STATUS_GROUP, fetchPolicy: "cache-and-network", variables() { return { diff --git a/js/src/views/Posts/Post.vue b/js/src/views/Posts/Post.vue index e315c76de..5e8166314 100644 --- a/js/src/views/Posts/Post.vue +++ b/js/src/views/Posts/Post.vue @@ -120,7 +120,7 @@ import { IMember } from "@/types/actor/member.model"; import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS, - PERSON_MEMBERSHIP_GROUP, + PERSON_STATUS_GROUP, } from "../../graphql/actor"; import { FETCH_POST } from "../../graphql/post"; import { IPost } from "../../types/post.model"; @@ -166,7 +166,7 @@ import { ICurrentUser } from "@/types/current-user.model"; }, }, person: { - query: PERSON_MEMBERSHIP_GROUP, + query: PERSON_STATUS_GROUP, fetchPolicy: "cache-and-network", variables() { return {