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) {
|
||||
|
||||
Reference in New Issue
Block a user