Allow members-restricted posts to be viewable by instance moderators

But add a warning message on top of the post

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-08-05 11:01:40 +02:00
parent c4bd26c120
commit f3a05929d9
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
9 changed files with 62 additions and 32 deletions

View file

@ -1067,5 +1067,10 @@
"View less": "View less", "View less": "View less",
"View more": "View more", "View more": "View more",
"Breadcrumbs": "Breadcrumbs", "Breadcrumbs": "Breadcrumbs",
"Other actions": "Other actions" "Other actions": "Other actions",
"Only group moderators can create, edit and delete events.": "Only group moderators can create, edit and delete events.",
"+ Create a post": "+ Create a post",
"Edited {relative_time} ago": "Edited {relative_time} ago",
"Members-only post": "Members-only post",
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator."
} }

View file

@ -1158,5 +1158,10 @@
"View less": "Voir moins", "View less": "Voir moins",
"View more": "Voir plus", "View more": "Voir plus",
"Breadcrumbs": "Fil d'Ariane", "Breadcrumbs": "Fil d'Ariane",
"Other actions": "Autres actions" "Other actions": "Autres actions",
"Only group moderators can create, edit and delete events.": "Seule⋅s les modérateur⋅ices de groupe peuvent créer, éditer et supprimer des événements.",
"+ Create a post": "+ Créer un billet",
"Edited {relative_time} ago": "Édité il y a {relative_time}",
"Members-only post": "Billet reservé aux membres",
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator.": "Ce billet est accessible uniquement aux membres. Vous y avez accès à des fins de modération car vous êtes modérateur⋅ice de l'instance."
} }

View file

@ -84,6 +84,14 @@ export default class GroupMixin extends Vue {
]); ]);
} }
get isCurrentActorAGroupMember(): boolean {
return this.hasCurrentActorThisRole([
MemberRole.MODERATOR,
MemberRole.ADMINISTRATOR,
MemberRole.MEMBER,
]);
}
hasCurrentActorThisRole(givenRole: string | string[]): boolean { hasCurrentActorThisRole(givenRole: string | string[]): boolean {
const roles = Array.isArray(givenRole) ? givenRole : [givenRole]; const roles = Array.isArray(givenRole) ? givenRole : [givenRole];
return ( return (

View file

@ -404,7 +404,7 @@
params: { preferredUsername: usernameWithDomain(group) }, params: { preferredUsername: usernameWithDomain(group) },
}" }"
class="button is-primary" class="button is-primary"
>{{ $t("+ Post a public message") }}</router-link >{{ $t("+ Create a post") }}</router-link
> >
</template> </template>
</group-section> </group-section>
@ -800,31 +800,11 @@ export default class Group extends mixins(GroupMixin) {
return undefined; return undefined;
} }
get groupMemberships(): (string | undefined)[] {
return this.person?.memberships?.elements
.filter(
(membership: IMember) =>
![
MemberRole.REJECTED,
MemberRole.NOT_APPROVED,
MemberRole.INVITED,
].includes(membership.role)
)
.map(({ parent: { id } }) => id);
}
@Watch("isCurrentActorAGroupMember") @Watch("isCurrentActorAGroupMember")
refetchGroupData(): void { refetchGroupData(): void {
this.$apollo.queries.group.refetch(); this.$apollo.queries.group.refetch();
} }
get isCurrentActorAGroupMember(): boolean {
return (
this.groupMemberships !== undefined &&
this.groupMemberships.includes(this.group.id)
);
}
get isCurrentActorARejectedGroupMember(): boolean { get isCurrentActorARejectedGroupMember(): boolean {
return ( return (
this.person && this.person &&

View file

@ -45,7 +45,7 @@
params: { preferredUsername: usernameWithDomain(group) }, params: { preferredUsername: usernameWithDomain(group) },
}" }"
class="button is-primary" class="button is-primary"
>{{ $t("+ Post a public message") }}</router-link >{{ $t("+ Create a post") }}</router-link
> >
</div> </div>
<div class="post-list"> <div class="post-list">

View file

@ -1,5 +1,5 @@
<template> <template>
<article class="container" v-if="post"> <article class="container post" v-if="post">
<header> <header>
<div class="banner-container"> <div class="banner-container">
<lazy-image-wrapper :picture="post.picture" /> <lazy-image-wrapper :picture="post.picture" />
@ -78,6 +78,24 @@
</div> </div>
</div> </div>
</header> </header>
<b-message
:title="$t('Members-only post')"
class="mx-4"
type="is-warning"
:closable="false"
v-if="
!$apollo.loading &&
isInstanceModerator &&
!isCurrentActorAGroupMember &&
post.visibility === PostVisibility.PRIVATE
"
>
{{
$t(
"This post is accessible only for members. You have access to it for moderation purposes only because you are an instance moderator."
)
}}
</b-message>
<section v-html="post.body" class="content" /> <section v-html="post.body" class="content" />
<section class="tags"> <section class="tags">
@ -96,7 +114,7 @@
import { Component, Prop } from "vue-property-decorator"; import { Component, Prop } from "vue-property-decorator";
import { mixins } from "vue-class-component"; import { mixins } from "vue-class-component";
import GroupMixin from "@/mixins/group"; import GroupMixin from "@/mixins/group";
import { PostVisibility } from "@/types/enums"; import { ICurrentUserRole, PostVisibility } from "@/types/enums";
import { IMember } from "@/types/actor/member.model"; import { IMember } from "@/types/actor/member.model";
import { import {
CURRENT_ACTOR_CLIENT, CURRENT_ACTOR_CLIENT,
@ -111,9 +129,12 @@ import Tag from "../../components/Tag.vue";
import LazyImageWrapper from "../../components/Image/LazyImageWrapper.vue"; import LazyImageWrapper from "../../components/Image/LazyImageWrapper.vue";
import ActorInline from "../../components/Account/ActorInline.vue"; import ActorInline from "../../components/Account/ActorInline.vue";
import { formatDistanceToNowStrict } from "date-fns"; import { formatDistanceToNowStrict } from "date-fns";
import { CURRENT_USER_CLIENT } from "@/graphql/user";
import { ICurrentUser } from "@/types/current-user.model";
@Component({ @Component({
apollo: { apollo: {
currentUser: CURRENT_USER_CLIENT,
currentActor: CURRENT_ACTOR_CLIENT, currentActor: CURRENT_ACTOR_CLIENT,
memberships: { memberships: {
query: PERSON_MEMBERSHIPS, query: PERSON_MEMBERSHIPS,
@ -187,6 +208,8 @@ export default class Post extends mixins(GroupMixin) {
RouteName = RouteName; RouteName = RouteName;
currentUser!: ICurrentUser;
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
formatDistanceToNowStrict = formatDistanceToNowStrict; formatDistanceToNowStrict = formatDistanceToNowStrict;
@ -205,10 +228,17 @@ export default class Post extends mixins(GroupMixin) {
.map(({ parent: { id } }) => id) .map(({ parent: { id } }) => id)
.includes(this.post.attributedTo.id); .includes(this.post.attributedTo.id);
} }
get isInstanceModerator(): boolean {
return [
ICurrentUserRole.ADMINISTRATOR,
ICurrentUserRole.MODERATOR,
].includes(this.currentUser.role);
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
article { article.post {
background: $white !important; background: $white !important;
header { header {
display: flex; display: flex;

View file

@ -16,7 +16,7 @@ const groupDomain = "remotedomain.net";
const groupUsername = `${groupPreferredUsername}@${groupDomain}`; const groupUsername = `${groupPreferredUsername}@${groupDomain}`;
const defaultSlotText = "A list of elements"; const defaultSlotText = "A list of elements";
const createSlotButtonText = "+ Post a public message"; const createSlotButtonText = "+ Create a post";
type Props = { type Props = {
title?: string; title?: string;

View file

@ -8,7 +8,7 @@ exports[`GroupSection renders group section with basic informations 1`] = `
<div class="main-slot"> <div class="main-slot">
<div>A list of elements</div> <div>A list of elements</div>
</div> </div>
<div class="create-slot"><a href="/@my_group@remotedomain.net/p/new" class="button is-primary">+ Post a public message</a></div> <div class="create-slot"><a href="/@my_group@remotedomain.net/p/new" class="button is-primary">+ Create a post</a></div>
</section> </section>
`; `;
@ -20,6 +20,6 @@ exports[`GroupSection renders public group section 1`] = `
<div class="main-slot"> <div class="main-slot">
<div>A list of elements</div> <div>A list of elements</div>
</div> </div>
<div class="create-slot"><a href="/@my_group@remotedomain.net/p/new" class="button is-primary">+ Post a public message</a></div> <div class="create-slot"><a href="/@my_group@remotedomain.net/p/new" class="button is-primary">+ Create a post</a></div>
</section> </section>
`; `;

View file

@ -65,7 +65,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
%{slug: slug}, %{slug: slug},
%{ %{
context: %{ context: %{
current_user: %User{} = user current_user: %User{role: user_role} = user
} }
} = _resolution } = _resolution
) do ) do
@ -73,7 +73,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
{:current_actor, Users.get_actor_for_user(user)}, {:current_actor, Users.get_actor_for_user(user)},
{:post, %Post{attributed_to: %Actor{}} = post} <- {:post, %Post{attributed_to: %Actor{}} = post} <-
{:post, Posts.get_post_by_slug_with_preloads(slug)}, {:post, Posts.get_post_by_slug_with_preloads(slug)},
{:member, true} <- {:member, Permission.can_access_group_object?(current_profile, post)} do {:member, true} <-
{:member,
Permission.can_access_group_object?(current_profile, post) or is_moderator(user_role)} do
{:ok, post} {:ok, post}
else else
{:member, false} -> get_post(parent, %{slug: slug}, nil) {:member, false} -> get_post(parent, %{slug: slug}, nil)