Merge branch 'disable-updating-deleting-group-posts-and-discussions-for-non-moderators' into 'master'

Disable updating/deleting group posts and discussions for non-moderators

See merge request framasoft/mobilizon!633
This commit is contained in:
Thomas Citharel 2020-10-22 09:46:11 +02:00
commit 92d05daa9b
7 changed files with 39 additions and 18 deletions

View File

@ -33,7 +33,7 @@
$options.filters.formatDateTimeString(new Date(post.insertedAt), false) $options.filters.formatDateTimeString(new Date(post.insertedAt), false)
}}</small> }}</small>
<small class="has-text-grey" v-if="isCurrentActorMember">{{ <small class="has-text-grey" v-if="isCurrentActorMember">{{
$t("Created by {username}", { username: usernameWithDomain(post.author) }) $t("Created by {username}", { username: `@${usernameWithDomain(post.author)}` })
}}</small> }}</small>
</div> </div>
</div> </div>

View File

@ -158,6 +158,7 @@ export const DISCUSSION_COMMENT_CHANGED = gql`
actor { actor {
id id
preferredUsername preferredUsername
name
domain domain
avatar { avatar {
url url

View File

@ -44,10 +44,19 @@ export default class GroupMixin extends Vue {
person!: IPerson; person!: IPerson;
get isCurrentActorAGroupAdmin(): boolean { get isCurrentActorAGroupAdmin(): boolean {
return this.hasCurrentActorThisRole(MemberRole.ADMINISTRATOR);
}
get isCurrentActorAGroupModerator(): boolean {
return this.hasCurrentActorThisRole([MemberRole.MODERATOR, MemberRole.ADMINISTRATOR]);
}
hasCurrentActorThisRole(givenRole: string | string[]): boolean {
const roles = Array.isArray(givenRole) ? givenRole : [givenRole];
return ( return (
this.person && this.person &&
this.person.memberships.elements.some( this.person.memberships.elements.some(
({ parent: { id }, role }) => id === this.group.id && role === MemberRole.ADMINISTRATOR ({ parent: { id }, role }) => id === this.group.id && roles.includes(role)
) )
); );
} }

View File

@ -104,6 +104,7 @@ $hero-body-padding-medium: 6rem 1.5rem;
main > .container { main > .container {
background: $body-background-color; background: $body-background-color;
min-height: 70vh;
} }
$title-color: #3c376e; $title-color: #3c376e;

View File

@ -39,6 +39,7 @@
<h2 class="title" v-if="discussion.title && !editTitleMode"> <h2 class="title" v-if="discussion.title && !editTitleMode">
{{ discussion.title }} {{ discussion.title }}
<span <span
v-if="currentActor.id === discussion.creator.id || isCurrentActorAGroupModerator"
@click=" @click="
() => { () => {
newTitle = discussion.title; newTitle = discussion.title;
@ -100,7 +101,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator"; import { Component, Prop } from "vue-property-decorator";
import { mixins } from "vue-class-component";
import { import {
GET_DISCUSSION, GET_DISCUSSION,
REPLY_TO_DISCUSSION, REPLY_TO_DISCUSSION,
@ -113,6 +115,7 @@ import { usernameWithDomain } from "@/types/actor";
import DiscussionComment from "@/components/Discussion/DiscussionComment.vue"; import DiscussionComment from "@/components/Discussion/DiscussionComment.vue";
import { GraphQLError } from "graphql"; import { GraphQLError } from "graphql";
import { DELETE_COMMENT, UPDATE_COMMENT } from "@/graphql/comment"; import { DELETE_COMMENT, UPDATE_COMMENT } from "@/graphql/comment";
import GroupMixin from "@/mixins/group";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import { IComment } from "../../types/comment.model"; import { IComment } from "../../types/comment.model";
@ -170,7 +173,7 @@ import { IComment } from "../../types/comment.model";
}, },
metaInfo() { metaInfo() {
return { return {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
title: this.discussion.title, title: this.discussion.title,
// all titles will be injected into this template // all titles will be injected into this template
@ -178,7 +181,7 @@ import { IComment } from "../../types/comment.model";
}; };
}, },
}) })
export default class discussion extends Vue { export default class discussion extends mixins(GroupMixin) {
@Prop({ type: String, required: true }) slug!: string; @Prop({ type: String, required: true }) slug!: string;
discussion: IDiscussion = new Discussion(); discussion: IDiscussion = new Discussion();
@ -199,7 +202,7 @@ export default class discussion extends Vue {
usernameWithDomain = usernameWithDomain; usernameWithDomain = usernameWithDomain;
async reply() { async reply(): Promise<void> {
if (this.newComment === "") return; if (this.newComment === "") return;
await this.$apollo.mutate({ await this.$apollo.mutate({
@ -234,7 +237,7 @@ export default class discussion extends Vue {
this.newComment = ""; this.newComment = "";
} }
async updateComment(comment: IComment) { async updateComment(comment: IComment): Promise<void> {
const { data } = await this.$apollo.mutate<{ deleteComment: IComment }>({ const { data } = await this.$apollo.mutate<{ deleteComment: IComment }>({
mutation: UPDATE_COMMENT, mutation: UPDATE_COMMENT,
variables: { variables: {
@ -270,7 +273,7 @@ export default class discussion extends Vue {
}); });
} }
async deleteComment(comment: IComment) { async deleteComment(comment: IComment): Promise<void> {
const { data } = await this.$apollo.mutate<{ deleteComment: IComment }>({ const { data } = await this.$apollo.mutate<{ deleteComment: IComment }>({
mutation: DELETE_COMMENT, mutation: DELETE_COMMENT,
variables: { variables: {
@ -308,7 +311,7 @@ export default class discussion extends Vue {
}); });
} }
async loadMoreComments() { async loadMoreComments(): Promise<void> {
if (!this.hasMoreComments) return; if (!this.hasMoreComments) return;
this.page += 1; this.page += 1;
try { try {
@ -338,7 +341,7 @@ export default class discussion extends Vue {
} }
} }
async updateDiscussion() { async updateDiscussion(): Promise<void> {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: UPDATE_DISCUSSION, mutation: UPDATE_DISCUSSION,
variables: { variables: {
@ -368,7 +371,7 @@ export default class discussion extends Vue {
this.editTitleMode = false; this.editTitleMode = false;
} }
async deleteConversation() { async deleteConversation(): Promise<void> {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: DELETE_DISCUSSION, mutation: DELETE_DISCUSSION,
variables: { variables: {
@ -376,28 +379,28 @@ export default class discussion extends Vue {
}, },
}); });
if (this.discussion.actor) { if (this.discussion.actor) {
return this.$router.push({ this.$router.push({
name: RouteName.DISCUSSION_LIST, name: RouteName.DISCUSSION_LIST,
params: { preferredUsername: usernameWithDomain(this.discussion.actor) }, params: { preferredUsername: usernameWithDomain(this.discussion.actor) },
}); });
} }
} }
async handleErrors(errors: GraphQLError[]) { async handleErrors(errors: GraphQLError[]): Promise<void> {
if (errors[0].message.includes("No such discussion")) { if (errors[0].message.includes("No such discussion")) {
await this.$router.push({ name: RouteName.PAGE_NOT_FOUND }); await this.$router.push({ name: RouteName.PAGE_NOT_FOUND });
} }
} }
mounted() { mounted(): void {
window.addEventListener("scroll", this.handleScroll); window.addEventListener("scroll", this.handleScroll);
} }
destroyed() { destroyed(): void {
window.removeEventListener("scroll", this.handleScroll); window.removeEventListener("scroll", this.handleScroll);
} }
handleScroll() { handleScroll(): void {
const scrollTop = const scrollTop =
(document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop; (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;
const scrollHeight = const scrollHeight =

View File

@ -157,6 +157,10 @@ export default class PostList extends Vue {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container.section {
background: $white;
}
section { section {
div.intro, div.intro,
.post-list { .post-list {

View File

@ -21,6 +21,7 @@
<p class="buttons" v-if="isCurrentActorMember"> <p class="buttons" v-if="isCurrentActorMember">
<b-tag type="is-warning" size="is-medium" v-if="post.draft">{{ $t("Draft") }}</b-tag> <b-tag type="is-warning" size="is-medium" v-if="post.draft">{{ $t("Draft") }}</b-tag>
<router-link <router-link
v-if="currentActor.id === post.author.id || isCurrentActorAGroupModerator"
:to="{ name: RouteName.POST_EDIT, params: { slug: post.slug } }" :to="{ name: RouteName.POST_EDIT, params: { slug: post.slug } }"
tag="button" tag="button"
class="button is-text" class="button is-text"
@ -44,8 +45,10 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator"; import { Component, Prop } from "vue-property-decorator";
import { mixins } from "vue-class-component";
import Editor from "@/components/Editor.vue"; import Editor from "@/components/Editor.vue";
import GroupMixin from "@/mixins/group";
import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "../../graphql/actor"; import { CURRENT_ACTOR_CLIENT, PERSON_MEMBERSHIPS } from "../../graphql/actor";
import { FETCH_POST } from "../../graphql/post"; import { FETCH_POST } from "../../graphql/post";
@ -101,7 +104,7 @@ import Tag from "../../components/Tag.vue";
}; };
}, },
}) })
export default class Post extends Vue { export default class Post extends mixins(GroupMixin) {
@Prop({ required: true, type: String }) slug!: string; @Prop({ required: true, type: String }) slug!: string;
post!: IPost; post!: IPost;