Add front-end for managing group follow

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-10-25 13:18:13 +02:00
parent 244a349b7d
commit f8eda4aac5
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
12 changed files with 217 additions and 20 deletions

View file

@ -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!) { query PersonMembershipGroup($id: ID!, $group: String!) {
person(id: $id) { person(id: $id) {
id id
@ -461,6 +461,35 @@ export const PERSON_MEMBERSHIP_GROUP = gql`
updatedAt 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
}
}
} }
} }
`; `;

View file

@ -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
}
}
`;

View file

@ -1,11 +1,17 @@
import { import {
CURRENT_ACTOR_CLIENT, CURRENT_ACTOR_CLIENT,
GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED, GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED,
PERSON_MEMBERSHIP_GROUP, PERSON_STATUS_GROUP,
} from "@/graphql/actor"; } from "@/graphql/actor";
import { FETCH_GROUP } from "@/graphql/group"; import { FETCH_GROUP } from "@/graphql/group";
import RouteName from "@/router/name"; 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 { MemberRole } from "@/types/enums";
import { Component, Vue } from "vue-property-decorator"; import { Component, Vue } from "vue-property-decorator";
@ -31,7 +37,7 @@ const now = new Date();
}, },
}, },
person: { person: {
query: PERSON_MEMBERSHIP_GROUP, query: PERSON_STATUS_GROUP,
fetchPolicy: "cache-and-network", fetchPolicy: "cache-and-network",
variables() { variables() {
return { 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 { handleErrors(errors: any[]): void {
if ( if (
errors.some((error) => error.status_code === 404) || errors.some((error) => error.status_code === 404) ||

View file

@ -5,4 +5,5 @@ export interface IFollower {
actor: IActor; actor: IActor;
targetActor: IActor; targetActor: IActor;
approved: boolean; approved: boolean;
notify?: boolean;
} }

View file

@ -1,3 +1,4 @@
export * from "./actor.model"; export * from "./actor.model";
export * from "./group.model"; export * from "./group.model";
export * from "./person.model"; export * from "./person.model";
export * from "./follower.model";

View file

@ -6,12 +6,14 @@ import type { Paginate } from "../paginate";
import type { IParticipant } from "../participant.model"; import type { IParticipant } from "../participant.model";
import type { IMember } from "./member.model"; import type { IMember } from "./member.model";
import type { IFeedToken } from "../feedtoken.model"; import type { IFeedToken } from "../feedtoken.model";
import { IFollower } from "./follower.model";
export interface IPerson extends IActor { export interface IPerson extends IActor {
feedTokens: IFeedToken[]; feedTokens: IFeedToken[];
goingToEvents: IEvent[]; goingToEvents: IEvent[];
participations: Paginate<IParticipant>; participations: Paginate<IParticipant>;
memberships: Paginate<IMember>; memberships: Paginate<IMember>;
follows: Paginate<IFollower>;
user?: ICurrentUser; user?: ICurrentUser;
} }
@ -31,6 +33,7 @@ export class Person extends Actor implements IPerson {
this.patch(hash); this.patch(hash);
} }
follows!: Paginate<IFollower>;
patch(hash: IPerson | Record<string, unknown>): void { patch(hash: IPerson | Record<string, unknown>): void {
Object.assign(this, hash); Object.assign(this, hash);

View file

@ -89,7 +89,7 @@ import { MemberRole } from "@/types/enums";
import { import {
CURRENT_ACTOR_CLIENT, CURRENT_ACTOR_CLIENT,
GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED, GROUP_MEMBERSHIP_SUBSCRIPTION_CHANGED,
PERSON_MEMBERSHIP_GROUP, PERSON_STATUS_GROUP,
} from "@/graphql/actor"; } from "@/graphql/actor";
import { IMember } from "@/types/actor/member.model"; import { IMember } from "@/types/actor/member.model";
import EmptyContent from "@/components/Utils/EmptyContent.vue"; import EmptyContent from "@/components/Utils/EmptyContent.vue";
@ -116,7 +116,7 @@ const DISCUSSIONS_PER_PAGE = 10;
}, },
}, },
person: { person: {
query: PERSON_MEMBERSHIP_GROUP, query: PERSON_STATUS_GROUP,
fetchPolicy: "cache-and-network", fetchPolicy: "cache-and-network",
variables() { variables() {
return { return {

View file

@ -607,7 +607,7 @@ import {
IDENTITIES, IDENTITIES,
LOGGED_USER_DRAFTS, LOGGED_USER_DRAFTS,
LOGGED_USER_PARTICIPATIONS, LOGGED_USER_PARTICIPATIONS,
PERSON_MEMBERSHIP_GROUP, PERSON_STATUS_GROUP,
} from "../../graphql/actor"; } from "../../graphql/actor";
import { import {
displayNameAndUsername, displayNameAndUsername,
@ -669,7 +669,7 @@ const DEFAULT_LIMIT_NUMBER_OF_PLACES = 10;
}, },
}, },
person: { person: {
query: PERSON_MEMBERSHIP_GROUP, query: PERSON_STATUS_GROUP,
fetchPolicy: "cache-and-network", fetchPolicy: "cache-and-network",
variables() { variables() {
return { return {

View file

@ -496,10 +496,7 @@ import {
FETCH_EVENT, FETCH_EVENT,
JOIN_EVENT, JOIN_EVENT,
} from "../../graphql/event"; } from "../../graphql/event";
import { import { CURRENT_ACTOR_CLIENT, PERSON_STATUS_GROUP } from "../../graphql/actor";
CURRENT_ACTOR_CLIENT,
PERSON_MEMBERSHIP_GROUP,
} from "../../graphql/actor";
import { EventModel, IEvent } from "../../types/event.model"; import { EventModel, IEvent } from "../../types/event.model";
import { IActor, IPerson, Person, usernameWithDomain } from "../../types/actor"; import { IActor, IPerson, Person, usernameWithDomain } from "../../types/actor";
import { GRAPHQL_API_ENDPOINT } from "../../api/_entrypoint"; import { GRAPHQL_API_ENDPOINT } from "../../api/_entrypoint";
@ -623,7 +620,7 @@ import { IUser } from "@/types/current-user.model";
}, },
}, },
person: { person: {
query: PERSON_MEMBERSHIP_GROUP, query: PERSON_STATUS_GROUP,
fetchPolicy: "cache-and-network", fetchPolicy: "cache-and-network",
variables() { variables() {
return { return {

View file

@ -164,6 +164,54 @@
type="is-primary" type="is-primary"
>{{ $t("Join group") }}</b-button >{{ $t("Join group") }}</b-button
> >
<b-tooltip
v-if="
(!isCurrentActorFollowing || previewPublic) &&
group.openness !== Openness.OPEN
"
:label="$t('This group is invite-only')"
position="is-bottom"
>
<b-button disabled type="is-primary">{{
$t("Follow 1")
}}</b-button></b-tooltip
>
<b-button
v-else-if="
(!isCurrentActorFollowing || previewPublic) && currentActor.id
"
@click="followGroup"
type="is-primary"
:disabled="previewPublic"
>{{ $t("Follow 2") }}</b-button
>
<b-button
tag="router-link"
:to="{
name: RouteName.GROUP_FOLLOW,
params: { preferredUsername: usernameWithDomain(group) },
}"
v-else-if="!isCurrentActorFollowing || previewPublic"
:disabled="previewPublic"
type="is-primary"
>{{ $t("Follow 3") }}</b-button
><b-button
v-if="
isCurrentActorFollowing && !previewPublic && currentActor.id
"
type="is-primary"
@click="unFollowGroup"
>{{ $t("Unfollow") }}</b-button
>
<b-button
v-if="isCurrentActorFollowing"
@click="toggleFollowNotify"
:icon-left="
isCurrentActorFollowingNotify
? 'bell-outline'
: 'bell-off-outline'
"
></b-button>
<b-button <b-button
outlined outlined
icon-left="share" icon-left="share"
@ -586,7 +634,7 @@ import { IMember } from "@/types/actor/member.model";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import GroupSection from "../../components/Group/GroupSection.vue"; import GroupSection from "../../components/Group/GroupSection.vue";
import ReportModal from "../../components/Report/ReportModal.vue"; import ReportModal from "../../components/Report/ReportModal.vue";
import { PERSON_MEMBERSHIP_GROUP } from "@/graphql/actor"; import { PERSON_STATUS_GROUP } from "@/graphql/actor";
import { LEAVE_GROUP } from "@/graphql/group"; import { LEAVE_GROUP } from "@/graphql/group";
import LazyImageWrapper from "../../components/Image/LazyImageWrapper.vue"; import LazyImageWrapper from "../../components/Image/LazyImageWrapper.vue";
import EventMetadataBlock from "../../components/Event/EventMetadataBlock.vue"; import EventMetadataBlock from "../../components/Event/EventMetadataBlock.vue";
@ -594,6 +642,11 @@ import EmptyContent from "../../components/Utils/EmptyContent.vue";
import { Paginate } from "@/types/paginate"; import { Paginate } from "@/types/paginate";
import { IEvent } from "@/types/event.model"; import { IEvent } from "@/types/event.model";
import { IPost } from "@/types/post.model"; import { IPost } from "@/types/post.model";
import {
FOLLOW_GROUP,
UNFOLLOW_GROUP,
UPDATE_GROUP_FOLLOW,
} from "@/graphql/followers";
@Component({ @Component({
apollo: { apollo: {
@ -674,7 +727,7 @@ export default class Group extends mixins(GroupMixin) {
}, },
refetchQueries: [ refetchQueries: [
{ {
query: PERSON_MEMBERSHIP_GROUP, query: PERSON_STATUS_GROUP,
variables: { variables: {
id: currentActorId, id: currentActorId,
group, group,
@ -697,7 +750,7 @@ export default class Group extends mixins(GroupMixin) {
}, },
refetchQueries: [ refetchQueries: [
{ {
query: PERSON_MEMBERSHIP_GROUP, query: PERSON_STATUS_GROUP,
variables: { variables: {
id: currentActorId, id: currentActorId,
group, group,
@ -712,6 +765,73 @@ export default class Group extends mixins(GroupMixin) {
} }
} }
async followGroup(): Promise<void> {
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<void> {
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<void> {
await this.$apollo.mutate({
mutation: UPDATE_GROUP_FOLLOW,
variables: {
followId: this.currentActorFollow?.id,
notify: !this.isCurrentActorFollowingNotify,
},
});
}
acceptInvitation(): void { acceptInvitation(): void {
if (this.groupMember) { if (this.groupMember) {
const index = this.person.memberships.elements.findIndex( const index = this.person.memberships.elements.findIndex(

View file

@ -181,7 +181,7 @@ import TagInput from "../../components/Event/TagInput.vue";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import Subtitle from "../../components/Utils/Subtitle.vue"; import Subtitle from "../../components/Utils/Subtitle.vue";
import PictureUpload from "../../components/PictureUpload.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"; import { FETCH_GROUP } from "@/graphql/group";
@Component({ @Component({
@ -211,7 +211,7 @@ import { FETCH_GROUP } from "@/graphql/group";
}, },
}, },
person: { person: {
query: PERSON_MEMBERSHIP_GROUP, query: PERSON_STATUS_GROUP,
fetchPolicy: "cache-and-network", fetchPolicy: "cache-and-network",
variables() { variables() {
return { return {

View file

@ -120,7 +120,7 @@ import { IMember } from "@/types/actor/member.model";
import { import {
CURRENT_ACTOR_CLIENT, CURRENT_ACTOR_CLIENT,
PERSON_MEMBERSHIPS, PERSON_MEMBERSHIPS,
PERSON_MEMBERSHIP_GROUP, PERSON_STATUS_GROUP,
} from "../../graphql/actor"; } from "../../graphql/actor";
import { FETCH_POST } from "../../graphql/post"; import { FETCH_POST } from "../../graphql/post";
import { IPost } from "../../types/post.model"; import { IPost } from "../../types/post.model";
@ -166,7 +166,7 @@ import { ICurrentUser } from "@/types/current-user.model";
}, },
}, },
person: { person: {
query: PERSON_MEMBERSHIP_GROUP, query: PERSON_STATUS_GROUP,
fetchPolicy: "cache-and-network", fetchPolicy: "cache-and-network",
variables() { variables() {
return { return {