Comment fixes

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-05-17 11:36:28 +02:00
parent b5a5de5c0c
commit 679600f003
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
7 changed files with 88 additions and 113 deletions

View file

@ -60,11 +60,6 @@ export const typePolicies: TypePolicies = {
relatedEvents: pageLimitPagination<IEvent>(),
},
},
Comment: {
fields: {
replies: pageLimitPagination<IComment>(),
},
},
RootQueryType: {
fields: {
relayFollowers: paginatedLimitPagination<IFollower>(),

View file

@ -55,7 +55,7 @@
<span class="icons" v-if="!comment.deletedAt">
<button
v-if="comment.actor.id === currentActor.id"
@click="$emit('delete-comment', comment)"
@click="deleteComment"
>
<b-icon icon="delete" size="is-small" aria-hidden="true" />
<span class="visually-hidden">{{ $t("Delete") }}</span>
@ -183,7 +183,6 @@ import { CommentModeration } from "@/types/enums";
import { CommentModel, IComment } from "../../types/comment.model";
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
import { IPerson, usernameWithDomain } from "../../types/actor";
import { COMMENTS_THREADS, FETCH_THREAD_REPLIES } from "../../graphql/comment";
import { IEvent } from "../../types/event.model";
import ReportModal from "../Report/ReportModal.vue";
import { IReport } from "../../types/report.model";
@ -257,39 +256,15 @@ export default class Comment extends Vue {
this.$emit("create-comment", this.newComment);
this.newComment = new CommentModel();
this.replyTo = false;
this.showReplies = true;
}
async fetchReplies(): Promise<void> {
const parentId = this.comment.id;
const { data } = await this.$apollo.query<{ thread: IComment[] }>({
query: FETCH_THREAD_REPLIES,
variables: {
threadId: parentId,
},
});
if (!data) return;
const { thread } = data;
const eventData = this.$apollo.getClient().readQuery<{ event: IEvent }>({
query: COMMENTS_THREADS,
variables: {
eventUUID: this.event.uuid,
},
});
if (!eventData) return;
const { event } = eventData;
const { comments } = event;
const parentCommentIndex = comments.findIndex(
(oldComment: IComment) => oldComment.id === parentId
);
const parentComment = comments[parentCommentIndex];
if (!parentComment) return;
parentComment.replies = thread;
comments[parentCommentIndex] = parentComment;
event.comments = comments;
this.$apollo.getClient().writeQuery({
query: COMMENTS_THREADS,
data: { event },
});
deleteComment(): void {
this.$emit("delete-comment", this.comment);
this.showReplies = false;
}
fetchReplies(): void {
this.showReplies = true;
}

View file

@ -82,8 +82,7 @@ import { CommentModel, IComment } from "../../types/comment.model";
import {
CREATE_COMMENT_FROM_EVENT,
DELETE_COMMENT,
COMMENTS_THREADS,
FETCH_THREAD_REPLIES,
COMMENTS_THREADS_WITH_REPLIES,
} from "../../graphql/comment";
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
import { IPerson } from "../../types/actor";
@ -96,7 +95,7 @@ import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";
query: CURRENT_ACTOR_CLIENT,
},
comments: {
query: COMMENTS_THREADS,
query: COMMENTS_THREADS_WITH_REPLIES,
variables() {
return {
eventUUID: this.event.uuid,
@ -145,6 +144,7 @@ export default class CommentTree extends Vue {
}
async createCommentForEvent(comment: IComment): Promise<void> {
console.log("creating comment", comment);
this.emptyCommentError = ["", "<p></p>"].includes(comment.text);
if (this.emptyCommentError) return;
try {
@ -160,21 +160,19 @@ export default class CommentTree extends Vue {
},
update: (store: ApolloCache<InMemoryCache>, { data }: FetchResult) => {
if (data == null) return;
const newComment = data.createComment;
// comments are attached to the event, so we can pass it to replies later
newComment.event = this.event;
const newComment = { ...data.createComment, event: this.event };
// we load all existing threads
const commentThreadsData = store.readQuery<{ event: IEvent }>({
query: COMMENTS_THREADS,
query: COMMENTS_THREADS_WITH_REPLIES,
variables: {
eventUUID: this.event.uuid,
},
});
if (!commentThreadsData) return;
const { event } = commentThreadsData;
const { comments: oldComments } = event;
const oldComments = [...event.comments];
// if it's no a root comment, we first need to find
// existing replies and add the new reply to it
@ -185,44 +183,25 @@ export default class CommentTree extends Vue {
);
const parentComment = oldComments[parentCommentIndex];
let oldReplyList: IComment[] = [];
try {
const threadData = store.readQuery<{ thread: IComment[] }>({
query: FETCH_THREAD_REPLIES,
variables: {
threadId: parentComment.id,
},
});
if (!threadData) return;
oldReplyList = threadData.thread;
} catch (e) {
// This simply means there's no loaded replies yet
} finally {
oldReplyList.push(newComment);
// save the updated list of replies (with the one we've just added)
store.writeQuery({
query: FETCH_THREAD_REPLIES,
data: { thread: oldReplyList },
variables: {
threadId: parentComment.id,
},
});
// replace the root comment with has the updated list of replies in the thread list
parentComment.replies = oldReplyList;
event.comments.splice(parentCommentIndex, 1, parentComment);
}
oldComments.splice(parentCommentIndex, 1, {
...parentComment,
replies: [...parentComment.replies, newComment],
});
} else {
// otherwise it's simply a new thread and we add it to the list
oldComments.push(newComment);
}
// finally we save the thread list
event.comments = oldComments;
store.writeQuery({
query: COMMENTS_THREADS,
data: { event },
query: COMMENTS_THREADS_WITH_REPLIES,
data: {
event: {
...event,
comments: oldComments,
},
},
variables: {
eventUUID: this.event.uuid,
},
@ -255,58 +234,61 @@ export default class CommentTree extends Vue {
const deletedCommentId = data.deleteComment.id;
const commentsData = store.readQuery<{ event: IEvent }>({
query: COMMENTS_THREADS,
query: COMMENTS_THREADS_WITH_REPLIES,
variables: {
eventUUID: this.event.uuid,
},
});
if (!commentsData) return;
const { event } = commentsData;
const { comments: oldComments } = event;
let updatedComments: IComment[] = [...event.comments];
if (comment.originComment) {
// we have deleted a reply to a thread
const localData = store.readQuery<{ thread: IComment[] }>({
query: FETCH_THREAD_REPLIES,
variables: {
threadId: comment.originComment.id,
},
});
if (!localData) return;
const { thread: oldReplyList } = localData;
const replies = oldReplyList.filter(
(reply) => reply.id !== deletedCommentId
);
store.writeQuery({
query: FETCH_THREAD_REPLIES,
variables: {
threadId: comment.originComment.id,
},
data: { thread: replies },
});
const { originComment } = comment;
const parentCommentIndex = oldComments.findIndex(
const parentCommentIndex = updatedComments.findIndex(
(oldComment) => oldComment.id === originComment.id
);
const parentComment = oldComments[parentCommentIndex];
parentComment.replies = replies;
parentComment.totalReplies -= 1;
oldComments.splice(parentCommentIndex, 1, parentComment);
event.comments = oldComments;
const parentComment = updatedComments[parentCommentIndex];
const updatedReplies = parentComment.replies.map((reply) => {
if (reply.id === deletedCommentId) {
return {
...reply,
deletedAt: new Date().toString(),
};
}
return reply;
});
updatedComments.splice(parentCommentIndex, 1, {
...parentComment,
replies: updatedReplies,
totalReplies: parentComment.totalReplies - 1,
});
console.log("updatedComments", updatedComments);
} else {
// we have deleted a thread itself
event.comments = oldComments.filter(
(reply) => reply.id !== deletedCommentId
);
updatedComments = updatedComments.map((reply) => {
if (reply.id === deletedCommentId) {
return {
...reply,
deletedAt: new Date().toString(),
};
}
return reply;
});
}
store.writeQuery({
query: COMMENTS_THREADS,
query: COMMENTS_THREADS_WITH_REPLIES,
variables: {
eventUUID: this.event.uuid,
},
data: { event },
data: {
event: {
...event,
comments: updatedComments,
},
},
});
},
});

View file

@ -38,6 +38,12 @@ export const COMMENT_RECURSIVE_FRAGMENT = gql`
}
replies {
...CommentFields
inReplyToComment {
...CommentFields
}
originComment {
...CommentFields
}
replies {
...CommentFields
}
@ -68,6 +74,19 @@ export const COMMENTS_THREADS = gql`
${COMMENT_FIELDS_FRAGMENT}
`;
export const COMMENTS_THREADS_WITH_REPLIES = gql`
query($eventUUID: UUID!) {
event(uuid: $eventUUID) {
id
uuid
comments {
...CommentRecursive
}
}
}
${COMMENT_RECURSIVE_FRAGMENT}
`;
export const CREATE_COMMENT_FROM_EVENT = gql`
mutation CreateCommentFromEvent(
$eventId: ID!

View file

@ -5,7 +5,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
use Absinthe.Schema.Notation
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
import Absinthe.Resolution.Helpers, only: [dataloader: 1, dataloader: 2]
alias Mobilizon.{Actors, Addresses, Discussions}
alias Mobilizon.GraphQL.Resolvers.{Event, Media, Tag}
@ -94,7 +94,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
)
field(:comments, list_of(:comment), description: "The comments in reply to the event") do
resolve(dataloader(Discussions))
resolve(dataloader(Discussions, args: %{top_level: true}))
end
# field(:tracks, list_of(:track))

View file

@ -65,7 +65,7 @@ defmodule Mobilizon.Discussions.Comment do
belongs_to(:in_reply_to_comment, Comment, foreign_key: :in_reply_to_comment_id)
belongs_to(:origin_comment, Comment, foreign_key: :origin_comment_id)
belongs_to(:discussion, Discussion, type: :binary_id)
has_many(:replies, Comment, foreign_key: :in_reply_to_comment_id)
has_many(:replies, Comment, foreign_key: :origin_comment_id)
many_to_many(:tags, Tag, join_through: "comments_tags", on_replace: :delete)
has_many(:mentions, Mention)
many_to_many(:media, Media, join_through: "comments_medias", on_replace: :delete)

View file

@ -72,7 +72,7 @@ defmodule Mobilizon.Discussions do
Read: https://hexdocs.pm/absinthe/ecto.html#dataloader
"""
@spec query(atom(), map()) :: Ecto.Queryable.t()
def query(Comment, _params) do
def query(Comment, %{top_level: true}) do
Comment
|> join(:left, [c], r in Comment, on: r.origin_comment_id == c.id)
|> where([c, _], is_nil(c.in_reply_to_comment_id))
@ -83,6 +83,10 @@ defmodule Mobilizon.Discussions do
|> select([c, r], %{c | total_replies: count(r.id)})
end
def query(Comment, _) do
order_by(Comment, [c], asc: :published_at)
end
def query(queryable, _) do
queryable
end