Allow group admins to moderate new members
Closes #881 Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -148,6 +148,11 @@ export default class GroupActivityItem extends mixins(ActivityMixin) {
|
||||
case Openness.INVITE_ONLY:
|
||||
details.push("The group can now only be joined with an invite.");
|
||||
break;
|
||||
case Openness.MODERATED:
|
||||
details.push(
|
||||
"The group can now be joined by anyone, but new members need to be approved by an administrator."
|
||||
);
|
||||
break;
|
||||
case Openness.OPEN:
|
||||
details.push("The group can now be joined by anyone.");
|
||||
break;
|
||||
|
||||
@@ -9,13 +9,7 @@
|
||||
:inline="true"
|
||||
slot="member"
|
||||
>
|
||||
<b>
|
||||
{{
|
||||
$t("@{username}", {
|
||||
username: usernameWithDomain(activity.object.actor),
|
||||
})
|
||||
}}</b
|
||||
></popover-actor-card
|
||||
<b> {{ displayName(activity.object.actor) }}</b></popover-actor-card
|
||||
>
|
||||
<b slot="member" v-else>{{
|
||||
subjectParams.member_actor_federated_username
|
||||
@@ -25,13 +19,7 @@
|
||||
:inline="true"
|
||||
slot="profile"
|
||||
>
|
||||
<b>
|
||||
{{
|
||||
$t("@{username}", {
|
||||
username: usernameWithDomain(activity.author),
|
||||
})
|
||||
}}</b
|
||||
></popover-actor-card
|
||||
<b> {{ displayName(activity.author) }}</b></popover-actor-card
|
||||
></i18n
|
||||
>
|
||||
<small class="has-text-grey-dark activity-date">{{
|
||||
@@ -41,7 +29,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { usernameWithDomain } from "@/types/actor";
|
||||
import { displayName } from "@/types/actor";
|
||||
import { ActivityMemberSubject, MemberRole } from "@/types/enums";
|
||||
import { Component } from "vue-property-decorator";
|
||||
import RouteName from "../../router/name";
|
||||
@@ -62,7 +50,7 @@ export const MEMBER_ROLE_VALUE: Record<string, number> = {
|
||||
},
|
||||
})
|
||||
export default class MemberActivityItem extends mixins(ActivityMixin) {
|
||||
usernameWithDomain = usernameWithDomain;
|
||||
displayName = displayName;
|
||||
RouteName = RouteName;
|
||||
ActivityMemberSubject = ActivityMemberSubject;
|
||||
|
||||
@@ -83,6 +71,14 @@ export default class MemberActivityItem extends mixins(ActivityMixin) {
|
||||
return "You added the member {member}.";
|
||||
}
|
||||
return "{profile} added the member {member}.";
|
||||
case ActivityMemberSubject.MEMBER_APPROVED:
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You approved {member}'s membership.";
|
||||
}
|
||||
if (this.isObjectMemberCurrentActor) {
|
||||
return "Your membership was approved by {profile}.";
|
||||
}
|
||||
return "{profile} approved {member}'s membership.";
|
||||
case ActivityMemberSubject.MEMBER_JOINED:
|
||||
return "{member} joined the group.";
|
||||
case ActivityMemberSubject.MEMBER_UPDATED:
|
||||
@@ -94,6 +90,12 @@ export default class MemberActivityItem extends mixins(ActivityMixin) {
|
||||
}
|
||||
return "{profile} updated the member {member}.";
|
||||
case ActivityMemberSubject.MEMBER_REMOVED:
|
||||
if (this.subjectParams.member_role === MemberRole.NOT_APPROVED) {
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You rejected {member}'s membership request.";
|
||||
}
|
||||
return "{profile} rejected {member}'s membership request.";
|
||||
}
|
||||
if (this.isAuthorCurrentActor) {
|
||||
return "You excluded member {member}.";
|
||||
}
|
||||
|
||||
@@ -1,57 +1,65 @@
|
||||
<template>
|
||||
<div class="media">
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<i18n
|
||||
tag="p"
|
||||
path="You have been invited by {invitedBy} to the following group:"
|
||||
>
|
||||
<b slot="invitedBy">{{ member.invitedBy.name }}</b>
|
||||
</i18n>
|
||||
</div>
|
||||
<div class="media subfield">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48" v-if="member.parent.avatar">
|
||||
<img class="is-rounded" :src="member.parent.avatar.url" alt="" />
|
||||
</figure>
|
||||
<b-icon v-else size="is-large" icon="account-group" />
|
||||
<div class="card">
|
||||
<div class="card-content media">
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<i18n
|
||||
tag="p"
|
||||
path="You have been invited by {invitedBy} to the following group:"
|
||||
>
|
||||
<b slot="invitedBy">{{ member.invitedBy.name }}</b>
|
||||
</i18n>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.GROUP,
|
||||
params: {
|
||||
preferredUsername: usernameWithDomain(member.parent),
|
||||
},
|
||||
}"
|
||||
>
|
||||
<h3>{{ member.parent.name }}</h3>
|
||||
<p class="is-6 has-text-grey">
|
||||
<span v-if="member.parent.domain">
|
||||
{{
|
||||
`@${member.parent.preferredUsername}@${member.parent.domain}`
|
||||
}}
|
||||
</span>
|
||||
<span v-else>{{
|
||||
`@${member.parent.preferredUsername}`
|
||||
}}</span>
|
||||
</p>
|
||||
</router-link>
|
||||
<div class="media subfield">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48" v-if="member.parent.avatar">
|
||||
<img class="is-rounded" :src="member.parent.avatar.url" alt="" />
|
||||
</figure>
|
||||
<b-icon v-else size="is-large" icon="account-group" />
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item mr-3">
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.GROUP,
|
||||
params: {
|
||||
preferredUsername: usernameWithDomain(member.parent),
|
||||
},
|
||||
}"
|
||||
>
|
||||
<h3 class="is-size-5">{{ member.parent.name }}</h3>
|
||||
<p class="is-size-7 has-text-grey-dark">
|
||||
<span v-if="member.parent.domain">
|
||||
{{
|
||||
`@${member.parent.preferredUsername}@${member.parent.domain}`
|
||||
}}
|
||||
</span>
|
||||
<span v-else>{{
|
||||
`@${member.parent.preferredUsername}`
|
||||
}}</span>
|
||||
</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<b-button type="is-success" @click="$emit('accept', member.id)">
|
||||
{{ $t("Accept") }}
|
||||
</b-button>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<b-button type="is-danger" @click="$emit('reject', member.id)">
|
||||
{{ $t("Decline") }}
|
||||
</b-button>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<b-button
|
||||
type="is-success"
|
||||
@click="$emit('accept', member.id)"
|
||||
>
|
||||
{{ $t("Accept") }}
|
||||
</b-button>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<b-button
|
||||
type="is-danger"
|
||||
@click="$emit('reject', member.id)"
|
||||
>
|
||||
{{ $t("Decline") }}
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -82,4 +90,7 @@ export default class InvitationCard extends Vue {
|
||||
background: lighten($primary, 40%);
|
||||
padding: 10px;
|
||||
}
|
||||
h3 {
|
||||
color: $violet-3;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<section v-if="invitations && invitations.length > 0">
|
||||
<section class="card my-3" v-if="invitations && invitations.length > 0">
|
||||
<InvitationCard
|
||||
v-for="member in invitations"
|
||||
:key="member.id"
|
||||
@@ -13,8 +13,9 @@
|
||||
import { ACCEPT_INVITATION, REJECT_INVITATION } from "@/graphql/member";
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import InvitationCard from "@/components/Group/InvitationCard.vue";
|
||||
import { LOGGED_USER_MEMBERSHIPS } from "@/graphql/actor";
|
||||
import { PERSON_STATUS_GROUP } from "@/graphql/actor";
|
||||
import { IMember } from "@/types/actor/member.model";
|
||||
import { IGroup, IPerson, usernameWithDomain } from "@/types/actor";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@@ -26,18 +27,25 @@ export default class Invitations extends Vue {
|
||||
|
||||
async acceptInvitation(id: string): Promise<void> {
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate<{ acceptInvitation: IMember }>(
|
||||
{
|
||||
mutation: ACCEPT_INVITATION,
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [{ query: LOGGED_USER_MEMBERSHIPS }],
|
||||
}
|
||||
);
|
||||
if (data) {
|
||||
this.$emit("accept-invitation", data.acceptInvitation);
|
||||
}
|
||||
await this.$apollo.mutate<{ acceptInvitation: IMember }>({
|
||||
mutation: ACCEPT_INVITATION,
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries({ data }) {
|
||||
const profile = data?.acceptInvitation?.actor as IPerson;
|
||||
const group = data?.acceptInvitation?.parent as IGroup;
|
||||
if (profile && group) {
|
||||
return [
|
||||
{
|
||||
query: PERSON_STATUS_GROUP,
|
||||
variables: { id: profile.id, group: usernameWithDomain(group) },
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
@@ -48,18 +56,25 @@ export default class Invitations extends Vue {
|
||||
|
||||
async rejectInvitation(id: string): Promise<void> {
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate<{ rejectInvitation: IMember }>(
|
||||
{
|
||||
mutation: REJECT_INVITATION,
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [{ query: LOGGED_USER_MEMBERSHIPS }],
|
||||
}
|
||||
);
|
||||
if (data) {
|
||||
this.$emit("reject-invitation", data.rejectInvitation);
|
||||
}
|
||||
await this.$apollo.mutate<{ rejectInvitation: IMember }>({
|
||||
mutation: REJECT_INVITATION,
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries({ data }) {
|
||||
const profile = data?.rejectInvitation?.actor as IPerson;
|
||||
const group = data?.rejectInvitation?.parent as IGroup;
|
||||
if (profile && group) {
|
||||
return [
|
||||
{
|
||||
query: PERSON_STATUS_GROUP,
|
||||
variables: { id: profile.id, group: usernameWithDomain(group) },
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
|
||||
@@ -37,9 +37,10 @@ export const ACCEPT_INVITATION = gql`
|
||||
export const REJECT_INVITATION = gql`
|
||||
mutation RejectInvitation($id: ID!) {
|
||||
rejectInvitation(id: $id) {
|
||||
id
|
||||
...MemberFragment
|
||||
}
|
||||
}
|
||||
${MEMBER_FRAGMENT}
|
||||
`;
|
||||
|
||||
export const GROUP_MEMBERS = gql`
|
||||
@@ -72,13 +73,22 @@ export const UPDATE_MEMBER = gql`
|
||||
`;
|
||||
|
||||
export const REMOVE_MEMBER = gql`
|
||||
mutation RemoveMember($groupId: ID!, $memberId: ID!) {
|
||||
removeMember(groupId: $groupId, memberId: $memberId) {
|
||||
mutation RemoveMember($memberId: ID!, $exclude: Boolean) {
|
||||
removeMember(memberId: $memberId, exclude: $exclude) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const APPROVE_MEMBER = gql`
|
||||
mutation ApproveMember($memberId: ID!) {
|
||||
approveMember(memberId: $memberId) {
|
||||
...MemberFragment
|
||||
}
|
||||
}
|
||||
${MEMBER_FRAGMENT}
|
||||
`;
|
||||
|
||||
export const JOIN_GROUP = gql`
|
||||
mutation JoinGroup($groupId: ID!) {
|
||||
joinGroup(groupId: $groupId) {
|
||||
|
||||
@@ -881,6 +881,7 @@
|
||||
"{member} was invited by {profile}.": "{member} was invited by {profile}.",
|
||||
"You added the member {member}.": "You added the member {member}.",
|
||||
"{profile} added the member {member}.": "{profile} added the member {member}.",
|
||||
"{member} joined the group.": "{member} joined the group.",
|
||||
"{member} rejected the invitation to join the group.": "{member} rejected the invitation to join the group.",
|
||||
"{member} accepted the invitation to join the group.": "{member} accepted the invitation to join the group.",
|
||||
"You excluded member {member}.": "You excluded member {member}.",
|
||||
@@ -1233,5 +1234,17 @@
|
||||
"Any type": "Any type",
|
||||
"In person": "In person",
|
||||
"In the past": "In the past",
|
||||
"Only registered users may fetch remote events from their URL.": "Only registered users may fetch remote events from their URL."
|
||||
"Only registered users may fetch remote events from their URL.": "Only registered users may fetch remote events from their URL.",
|
||||
"Moderate new members": "Moderate new members",
|
||||
"Anyone can request being a member, but an administrator needs to approve the membership.": "Anyone can request being a member, but an administrator needs to approve the membership.",
|
||||
"Cancel membership request": "Cancel membership request",
|
||||
"group's upcoming public events": "group's upcoming public events",
|
||||
"access to the group's private content as well": "access to the group's private content as well",
|
||||
"Following the group will allow you to be informed of the {group_upcoming_public_events}, whereas joining the group means you will {access_to_group_private_content_as_well}, including group discussions, group resources and members-only posts.": "Following the group will allow you to be informed of the {group_upcoming_public_events}, whereas joining the group means you will {access_to_group_private_content_as_well}, including group discussions, group resources and members-only posts.",
|
||||
"The group can now be joined by anyone, but new members need to be approved by an administrator.": "The group can now be joined by anyone, but new members need to be approved by an administrator.",
|
||||
"You approved {member}'s membership.": "You approved {member}'s membership.",
|
||||
"Your membership was approved by {profile}.": "Your membership was approved by {profile}.",
|
||||
"{profile} approved {member}'s membership.": "{profile} approved {member}'s membership.",
|
||||
"You rejected {member}'s membership request.": "You rejected {member}'s membership request.",
|
||||
"{profile} rejected {member}'s membership request.": "{profile} rejected {member}'s membership request."
|
||||
}
|
||||
|
||||
@@ -1077,7 +1077,7 @@
|
||||
"You excluded member {member}.": "Vous avez exclu le ou la membre {member}.",
|
||||
"You have been disconnected": "Vous avez été déconnecté⋅e",
|
||||
"You have been invited by {invitedBy} to the following group:": "Vous avez été invité par {invitedBy} à rejoindre le groupe suivant :",
|
||||
"You have been removed from this group's members.": "Vous avez été exclu des membres de ce groupe.",
|
||||
"You have been removed from this group's members.": "Vous avez été exclu⋅e des membres de ce groupe.",
|
||||
"You have cancelled your participation": "Vous avez annulé votre participation",
|
||||
"You have one event in {days} days.": "Vous n'avez pas d'événements dans {days} jours | Vous avez un événement dans {days} jours. | Vous avez {count} événements dans {days} jours",
|
||||
"You have one event today.": "Vous n'avez pas d'événement aujourd'hui | Vous avez un événement aujourd'hui. | Vous avez {count} événements aujourd'hui",
|
||||
@@ -1231,6 +1231,7 @@
|
||||
"{old_group_name} was renamed to {group}.": "{old_group_name} a été renommé en {group}.",
|
||||
"{profile} (by default)": "{profile} (par défault)",
|
||||
"{profile} added the member {member}.": "{profile} a ajouté le ou la membre {member}.",
|
||||
"{member} joined the group.": "{member} a rejoint le groupe.",
|
||||
"{profile} archived the discussion {discussion}.": "{profile} a archivé la discussion {discussion}.",
|
||||
"{profile} created the discussion {discussion}.": "{profile} a créé la discussion {discussion}.",
|
||||
"{profile} created the folder {resource}.": "{profile} a créé le dossier {resource}.",
|
||||
@@ -1337,5 +1338,17 @@
|
||||
"Any type": "N'importe quel type",
|
||||
"In person": "En personne",
|
||||
"In the past": "Dans le passé",
|
||||
"Only registered users may fetch remote events from their URL.": "Seul⋅es les utilisateur⋅ices enregistré⋅es peuvent récupérer des événements depuis leur URL."
|
||||
"Only registered users may fetch remote events from their URL.": "Seul⋅es les utilisateur⋅ices enregistré⋅es peuvent récupérer des événements depuis leur URL.",
|
||||
"Moderate new members": "Modérer les nouvelles et nouveaux membres",
|
||||
"Anyone can request being a member, but an administrator needs to approve the membership.": "N'importe qui peut demander à être membre, mais un⋅e administrateur⋅ice devra approuver leur adhésion.",
|
||||
"Cancel membership request": "Annuler la demande d'adhésion",
|
||||
"group's upcoming public events": "prochains événements publics du groupe",
|
||||
"access to the group's private content as well": "accédez également au contenu privé du groupe",
|
||||
"Following the group will allow you to be informed of the {group_upcoming_public_events}, whereas joining the group means you will {access_to_group_private_content_as_well}, including group discussions, group resources and members-only posts.": "Suivre le groupe vous permettra d'être informé⋅e des {group_upcoming_public_events}, alors que rejoindre le groupe signfie que vous {access_to_group_private_content_as_well}, y compris les discussion de groupe, les resources du groupe et les billets réservés au groupe.",
|
||||
"The group can now be joined by anyone, but new members need to be approved by an administrator.": "Le groupe peut maintenant être rejoint par n'importe qui, mais les nouvelles et nouveaux membres doivent être approuvées par un⋅e modérateur⋅ice.",
|
||||
"You approved {member}'s membership.": "Vous avez approuvé la demande d'adhésion de {member}.",
|
||||
"Your membership was approved by {profile}.": "Votre demande d'adhésion a été approuvée par {profile}.",
|
||||
"{profile} approved {member}'s membership.": "{profile} a approuvé la demande d'adhésion de {member}.",
|
||||
"You rejected {member}'s membership request.": "Vous avez rejeté la demande d'adhésion de {member}.",
|
||||
"{profile} rejected {member}'s membership request.": "{profile} a rejeté la demande d'adhésion de {member}."
|
||||
}
|
||||
|
||||
@@ -99,6 +99,10 @@ export default class GroupMixin extends Vue {
|
||||
]);
|
||||
}
|
||||
|
||||
get isCurrentActorAPendingGroupMember(): boolean {
|
||||
return this.hasCurrentActorThisRole([MemberRole.NOT_APPROVED]);
|
||||
}
|
||||
|
||||
hasCurrentActorThisRole(givenRole: string | string[]): boolean {
|
||||
const roles = Array.isArray(givenRole) ? givenRole : [givenRole];
|
||||
return (
|
||||
|
||||
@@ -23,29 +23,6 @@
|
||||
</ul>
|
||||
</nav>
|
||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||
<invitations
|
||||
v-if="isCurrentActorAnInvitedGroupMember"
|
||||
:invitations="[groupMember]"
|
||||
@acceptInvitation="acceptInvitation"
|
||||
@reject-invitation="rejectInvitation"
|
||||
/>
|
||||
<b-message v-if="isCurrentActorARejectedGroupMember" type="is-danger">
|
||||
{{ $t("You have been removed from this group's members.") }}
|
||||
</b-message>
|
||||
<b-message
|
||||
v-if="
|
||||
isCurrentActorAGroupMember &&
|
||||
isCurrentActorARecentMember &&
|
||||
isCurrentActorOnADifferentDomainThanGroup
|
||||
"
|
||||
type="is-info"
|
||||
>
|
||||
{{
|
||||
$t(
|
||||
"Since you are a new member, private content can take a few minutes to appear."
|
||||
)
|
||||
}}
|
||||
</b-message>
|
||||
<header class="block-container presentation" v-if="group">
|
||||
<div class="banner-container">
|
||||
<lazy-image-wrapper :picture="group.banner" />
|
||||
@@ -137,7 +114,7 @@
|
||||
<b-tooltip
|
||||
v-if="
|
||||
(!isCurrentActorAGroupMember || previewPublic) &&
|
||||
group.openness !== Openness.OPEN
|
||||
group.openness === Openness.INVITE_ONLY
|
||||
"
|
||||
:label="$t('This group is invite-only')"
|
||||
position="is-bottom"
|
||||
@@ -148,7 +125,9 @@
|
||||
>
|
||||
<b-button
|
||||
v-else-if="
|
||||
(!isCurrentActorAGroupMember || previewPublic) &&
|
||||
((!isCurrentActorAGroupMember &&
|
||||
!isCurrentActorAPendingGroupMember) ||
|
||||
previewPublic) &&
|
||||
currentActor.id
|
||||
"
|
||||
@click="joinGroup"
|
||||
@@ -157,6 +136,14 @@
|
||||
:disabled="previewPublic"
|
||||
>{{ $t("Join group") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
outlined
|
||||
v-else-if="isCurrentActorAPendingGroupMember"
|
||||
@click="leaveGroup"
|
||||
@keyup.enter="leaveGroup"
|
||||
type="is-primary"
|
||||
>{{ $t("Cancel membership request") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
tag="router-link"
|
||||
:to="{
|
||||
@@ -310,6 +297,49 @@
|
||||
</b-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<invitations
|
||||
v-if="isCurrentActorAnInvitedGroupMember"
|
||||
:invitations="[groupMember]"
|
||||
/>
|
||||
<b-message v-if="isCurrentActorARejectedGroupMember" type="is-danger">
|
||||
{{ $t("You have been removed from this group's members.") }}
|
||||
</b-message>
|
||||
<b-message
|
||||
v-if="
|
||||
isCurrentActorAGroupMember &&
|
||||
isCurrentActorARecentMember &&
|
||||
isCurrentActorOnADifferentDomainThanGroup
|
||||
"
|
||||
type="is-info"
|
||||
>
|
||||
{{
|
||||
$t(
|
||||
"Since you are a new member, private content can take a few minutes to appear."
|
||||
)
|
||||
}}
|
||||
</b-message>
|
||||
<b-message
|
||||
v-if="
|
||||
!isCurrentActorAGroupMember &&
|
||||
!isCurrentActorAPendingGroupMember &&
|
||||
!isCurrentActorPendingFollow &&
|
||||
!isCurrentActorFollowing
|
||||
"
|
||||
type="is-info"
|
||||
has-icon
|
||||
class="m-3"
|
||||
>
|
||||
<i18n
|
||||
path="Following the group will allow you to be informed of the {group_upcoming_public_events}, whereas joining the group means you will {access_to_group_private_content_as_well}, including group discussions, group resources and members-only posts."
|
||||
>
|
||||
<b slot="group_upcoming_public_events">{{
|
||||
$t("group's upcoming public events")
|
||||
}}</b>
|
||||
<b slot="access_to_group_private_content_as_well">{{
|
||||
$t("access to the group's private content as well")
|
||||
}}</b>
|
||||
</i18n>
|
||||
</b-message>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
@@ -893,31 +923,6 @@ export default class Group extends mixins(GroupMixin) {
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
const member = this.groupMember;
|
||||
member.role = MemberRole.MEMBER;
|
||||
this.person.memberships.elements.splice(index, 1, member);
|
||||
this.$apollo.queries.group.refetch();
|
||||
}
|
||||
}
|
||||
|
||||
rejectInvitation({ id: memberId }: { id: string }): void {
|
||||
const index = this.person.memberships.elements.findIndex(
|
||||
(membership) =>
|
||||
membership.role === MemberRole.INVITED && membership.id === memberId
|
||||
);
|
||||
if (index > -1) {
|
||||
this.person.memberships.elements.splice(index, 1);
|
||||
this.person.memberships.total -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
async reportGroup(content: string, forward: boolean): Promise<void> {
|
||||
this.isReportModalActive = false;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
||||
@@ -195,6 +195,20 @@
|
||||
</b-table-column>
|
||||
<b-table-column field="actions" :label="$t('Actions')" v-slot="props">
|
||||
<div class="buttons" v-if="props.row.actor.id !== currentActor.id">
|
||||
<b-button
|
||||
type="is-success"
|
||||
v-if="props.row.role === MemberRole.NOT_APPROVED"
|
||||
@click="approveMember(props.row)"
|
||||
icon-left="check"
|
||||
>{{ $t("Approve member") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
type="is-danger"
|
||||
v-if="props.row.role === MemberRole.NOT_APPROVED"
|
||||
@click="rejectMember(props.row)"
|
||||
icon-left="exit-to-app"
|
||||
>{{ $t("Reject member") }}</b-button
|
||||
>
|
||||
<b-button
|
||||
v-if="
|
||||
[MemberRole.MEMBER, MemberRole.MODERATOR].includes(
|
||||
@@ -217,7 +231,7 @@
|
||||
>
|
||||
<b-button
|
||||
v-if="props.row.role === MemberRole.MEMBER"
|
||||
@click="removeMember(props.row.id)"
|
||||
@click="removeMember(props.row)"
|
||||
type="is-danger"
|
||||
icon-left="exit-to-app"
|
||||
>{{ $t("Remove") }}</b-button
|
||||
@@ -250,8 +264,9 @@ import {
|
||||
GROUP_MEMBERS,
|
||||
REMOVE_MEMBER,
|
||||
UPDATE_MEMBER,
|
||||
APPROVE_MEMBER,
|
||||
} from "../../graphql/member";
|
||||
import { usernameWithDomain } from "../../types/actor";
|
||||
import { usernameWithDomain, displayName } from "../../types/actor";
|
||||
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
||||
|
||||
@Component({
|
||||
@@ -332,7 +347,7 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
this.$notifier.success(
|
||||
this.$t("{username} was invited to {group}", {
|
||||
username: this.newMemberUsername,
|
||||
group: this.group.name || usernameWithDomain(this.group),
|
||||
group: displayName(this.group),
|
||||
}) as string
|
||||
);
|
||||
this.newMemberUsername = "";
|
||||
@@ -375,7 +390,7 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
});
|
||||
}
|
||||
|
||||
async removeMember(memberId: string): Promise<void> {
|
||||
async removeMember(oldMember: IMember): Promise<void> {
|
||||
const { roles, MEMBERS_PER_PAGE, group, page } = this;
|
||||
const variables = {
|
||||
name: usernameWithDomain(group),
|
||||
@@ -388,7 +403,7 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
mutation: REMOVE_MEMBER,
|
||||
variables: {
|
||||
groupId: this.group.id,
|
||||
memberId,
|
||||
memberId: oldMember.id,
|
||||
},
|
||||
refetchQueries: [
|
||||
{
|
||||
@@ -397,12 +412,18 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
},
|
||||
],
|
||||
});
|
||||
this.$notifier.success(
|
||||
this.$t("The member was removed from the group {group}", {
|
||||
username: this.newMemberUsername,
|
||||
group: this.group.name || usernameWithDomain(this.group),
|
||||
}) as string
|
||||
);
|
||||
let message = this.$t("The member was removed from the group {group}", {
|
||||
group: displayName(this.group),
|
||||
}) as string;
|
||||
if (oldMember.role === MemberRole.NOT_APPROVED) {
|
||||
message = this.$t(
|
||||
"The membership request from {profile} was rejected",
|
||||
{
|
||||
group: displayName(oldMember.actor),
|
||||
}
|
||||
) as string;
|
||||
}
|
||||
this.$notifier.success(message);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
@@ -414,29 +435,49 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
promoteMember(member: IMember): void {
|
||||
if (!member.id) return;
|
||||
if (member.role === MemberRole.MODERATOR) {
|
||||
this.updateMember(member.id, MemberRole.ADMINISTRATOR);
|
||||
this.updateMember(member, MemberRole.ADMINISTRATOR);
|
||||
}
|
||||
if (member.role === MemberRole.MEMBER) {
|
||||
this.updateMember(member.id, MemberRole.MODERATOR);
|
||||
this.updateMember(member, MemberRole.MODERATOR);
|
||||
}
|
||||
}
|
||||
|
||||
demoteMember(member: IMember): void {
|
||||
if (!member.id) return;
|
||||
if (member.role === MemberRole.MODERATOR) {
|
||||
this.updateMember(member.id, MemberRole.MEMBER);
|
||||
this.updateMember(member, MemberRole.MEMBER);
|
||||
}
|
||||
if (member.role === MemberRole.ADMINISTRATOR) {
|
||||
this.updateMember(member.id, MemberRole.MODERATOR);
|
||||
this.updateMember(member, MemberRole.MODERATOR);
|
||||
}
|
||||
}
|
||||
|
||||
async updateMember(memberId: string, role: MemberRole): Promise<void> {
|
||||
async approveMember(member: IMember): Promise<void> {
|
||||
try {
|
||||
await this.$apollo.mutate<{ approveMember: IMember }>({
|
||||
mutation: APPROVE_MEMBER,
|
||||
variables: { memberId: member.id },
|
||||
});
|
||||
this.$notifier.success(this.$t("The member was approved") as string);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
this.$notifier.error(error.graphQLErrors[0].message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rejectMember(member: IMember): void {
|
||||
if (!member.id) return;
|
||||
this.removeMember(member);
|
||||
}
|
||||
|
||||
async updateMember(oldMember: IMember, role: MemberRole): Promise<void> {
|
||||
try {
|
||||
await this.$apollo.mutate<{ updateMember: IMember }>({
|
||||
mutation: UPDATE_MEMBER,
|
||||
variables: {
|
||||
memberId,
|
||||
memberId: oldMember.id,
|
||||
role,
|
||||
},
|
||||
refetchQueries: [
|
||||
@@ -455,8 +496,14 @@ export default class GroupMembers extends mixins(GroupMixin) {
|
||||
successMessage = "The member role was updated to administrator";
|
||||
break;
|
||||
case MemberRole.MEMBER:
|
||||
if (oldMember.role === MemberRole.NOT_APPROVED) {
|
||||
successMessage = "The member was approved";
|
||||
} else {
|
||||
successMessage = "The member role was updated to simple member";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
successMessage = "The member role was updated to simple member";
|
||||
successMessage = "The member role was updated";
|
||||
}
|
||||
this.$notifier.success(this.$t(successMessage) as string);
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -128,6 +128,19 @@
|
||||
}}</small>
|
||||
</b-radio>
|
||||
</div>
|
||||
<div class="field">
|
||||
<b-radio
|
||||
v-model="editableGroup.openness"
|
||||
name="groupOpenness"
|
||||
:native-value="Openness.MODERATED"
|
||||
>{{ $t("Moderate new members") }}<br />
|
||||
<small>{{
|
||||
$t(
|
||||
"Anyone can request being a member, but an administrator needs to approve the membership."
|
||||
)
|
||||
}}</small>
|
||||
</b-radio>
|
||||
</div>
|
||||
<div class="field">
|
||||
<b-radio
|
||||
v-model="editableGroup.openness"
|
||||
|
||||
Reference in New Issue
Block a user