Comment fixes
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
b5a5de5c0c
commit
679600f003
@ -60,11 +60,6 @@ export const typePolicies: TypePolicies = {
|
|||||||
relatedEvents: pageLimitPagination<IEvent>(),
|
relatedEvents: pageLimitPagination<IEvent>(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Comment: {
|
|
||||||
fields: {
|
|
||||||
replies: pageLimitPagination<IComment>(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
RootQueryType: {
|
RootQueryType: {
|
||||||
fields: {
|
fields: {
|
||||||
relayFollowers: paginatedLimitPagination<IFollower>(),
|
relayFollowers: paginatedLimitPagination<IFollower>(),
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
<span class="icons" v-if="!comment.deletedAt">
|
<span class="icons" v-if="!comment.deletedAt">
|
||||||
<button
|
<button
|
||||||
v-if="comment.actor.id === currentActor.id"
|
v-if="comment.actor.id === currentActor.id"
|
||||||
@click="$emit('delete-comment', comment)"
|
@click="deleteComment"
|
||||||
>
|
>
|
||||||
<b-icon icon="delete" size="is-small" aria-hidden="true" />
|
<b-icon icon="delete" size="is-small" aria-hidden="true" />
|
||||||
<span class="visually-hidden">{{ $t("Delete") }}</span>
|
<span class="visually-hidden">{{ $t("Delete") }}</span>
|
||||||
@ -183,7 +183,6 @@ import { CommentModeration } from "@/types/enums";
|
|||||||
import { CommentModel, IComment } from "../../types/comment.model";
|
import { CommentModel, IComment } from "../../types/comment.model";
|
||||||
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
|
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
|
||||||
import { IPerson, usernameWithDomain } from "../../types/actor";
|
import { IPerson, usernameWithDomain } from "../../types/actor";
|
||||||
import { COMMENTS_THREADS, FETCH_THREAD_REPLIES } from "../../graphql/comment";
|
|
||||||
import { IEvent } from "../../types/event.model";
|
import { IEvent } from "../../types/event.model";
|
||||||
import ReportModal from "../Report/ReportModal.vue";
|
import ReportModal from "../Report/ReportModal.vue";
|
||||||
import { IReport } from "../../types/report.model";
|
import { IReport } from "../../types/report.model";
|
||||||
@ -257,39 +256,15 @@ export default class Comment extends Vue {
|
|||||||
this.$emit("create-comment", this.newComment);
|
this.$emit("create-comment", this.newComment);
|
||||||
this.newComment = new CommentModel();
|
this.newComment = new CommentModel();
|
||||||
this.replyTo = false;
|
this.replyTo = false;
|
||||||
|
this.showReplies = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchReplies(): Promise<void> {
|
deleteComment(): void {
|
||||||
const parentId = this.comment.id;
|
this.$emit("delete-comment", this.comment);
|
||||||
const { data } = await this.$apollo.query<{ thread: IComment[] }>({
|
this.showReplies = false;
|
||||||
query: FETCH_THREAD_REPLIES,
|
}
|
||||||
variables: {
|
|
||||||
threadId: parentId,
|
fetchReplies(): void {
|
||||||
},
|
|
||||||
});
|
|
||||||
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 },
|
|
||||||
});
|
|
||||||
this.showReplies = true;
|
this.showReplies = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,8 +82,7 @@ import { CommentModel, IComment } from "../../types/comment.model";
|
|||||||
import {
|
import {
|
||||||
CREATE_COMMENT_FROM_EVENT,
|
CREATE_COMMENT_FROM_EVENT,
|
||||||
DELETE_COMMENT,
|
DELETE_COMMENT,
|
||||||
COMMENTS_THREADS,
|
COMMENTS_THREADS_WITH_REPLIES,
|
||||||
FETCH_THREAD_REPLIES,
|
|
||||||
} from "../../graphql/comment";
|
} from "../../graphql/comment";
|
||||||
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
|
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
|
||||||
import { IPerson } from "../../types/actor";
|
import { IPerson } from "../../types/actor";
|
||||||
@ -96,7 +95,7 @@ import { ApolloCache, FetchResult, InMemoryCache } from "@apollo/client/core";
|
|||||||
query: CURRENT_ACTOR_CLIENT,
|
query: CURRENT_ACTOR_CLIENT,
|
||||||
},
|
},
|
||||||
comments: {
|
comments: {
|
||||||
query: COMMENTS_THREADS,
|
query: COMMENTS_THREADS_WITH_REPLIES,
|
||||||
variables() {
|
variables() {
|
||||||
return {
|
return {
|
||||||
eventUUID: this.event.uuid,
|
eventUUID: this.event.uuid,
|
||||||
@ -145,6 +144,7 @@ export default class CommentTree extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createCommentForEvent(comment: IComment): Promise<void> {
|
async createCommentForEvent(comment: IComment): Promise<void> {
|
||||||
|
console.log("creating comment", comment);
|
||||||
this.emptyCommentError = ["", "<p></p>"].includes(comment.text);
|
this.emptyCommentError = ["", "<p></p>"].includes(comment.text);
|
||||||
if (this.emptyCommentError) return;
|
if (this.emptyCommentError) return;
|
||||||
try {
|
try {
|
||||||
@ -160,21 +160,19 @@ export default class CommentTree extends Vue {
|
|||||||
},
|
},
|
||||||
update: (store: ApolloCache<InMemoryCache>, { data }: FetchResult) => {
|
update: (store: ApolloCache<InMemoryCache>, { data }: FetchResult) => {
|
||||||
if (data == null) return;
|
if (data == null) return;
|
||||||
const newComment = data.createComment;
|
|
||||||
|
|
||||||
// comments are attached to the event, so we can pass it to replies later
|
// 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
|
// we load all existing threads
|
||||||
const commentThreadsData = store.readQuery<{ event: IEvent }>({
|
const commentThreadsData = store.readQuery<{ event: IEvent }>({
|
||||||
query: COMMENTS_THREADS,
|
query: COMMENTS_THREADS_WITH_REPLIES,
|
||||||
variables: {
|
variables: {
|
||||||
eventUUID: this.event.uuid,
|
eventUUID: this.event.uuid,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!commentThreadsData) return;
|
if (!commentThreadsData) return;
|
||||||
const { event } = commentThreadsData;
|
const { event } = commentThreadsData;
|
||||||
const { comments: oldComments } = event;
|
const oldComments = [...event.comments];
|
||||||
|
|
||||||
// if it's no a root comment, we first need to find
|
// if it's no a root comment, we first need to find
|
||||||
// existing replies and add the new reply to it
|
// existing replies and add the new reply to it
|
||||||
@ -185,44 +183,25 @@ export default class CommentTree extends Vue {
|
|||||||
);
|
);
|
||||||
const parentComment = oldComments[parentCommentIndex];
|
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
|
// replace the root comment with has the updated list of replies in the thread list
|
||||||
parentComment.replies = oldReplyList;
|
oldComments.splice(parentCommentIndex, 1, {
|
||||||
event.comments.splice(parentCommentIndex, 1, parentComment);
|
...parentComment,
|
||||||
}
|
replies: [...parentComment.replies, newComment],
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// otherwise it's simply a new thread and we add it to the list
|
// otherwise it's simply a new thread and we add it to the list
|
||||||
oldComments.push(newComment);
|
oldComments.push(newComment);
|
||||||
}
|
}
|
||||||
|
|
||||||
// finally we save the thread list
|
// finally we save the thread list
|
||||||
event.comments = oldComments;
|
|
||||||
store.writeQuery({
|
store.writeQuery({
|
||||||
query: COMMENTS_THREADS,
|
query: COMMENTS_THREADS_WITH_REPLIES,
|
||||||
data: { event },
|
data: {
|
||||||
|
event: {
|
||||||
|
...event,
|
||||||
|
comments: oldComments,
|
||||||
|
},
|
||||||
|
},
|
||||||
variables: {
|
variables: {
|
||||||
eventUUID: this.event.uuid,
|
eventUUID: this.event.uuid,
|
||||||
},
|
},
|
||||||
@ -255,58 +234,61 @@ export default class CommentTree extends Vue {
|
|||||||
const deletedCommentId = data.deleteComment.id;
|
const deletedCommentId = data.deleteComment.id;
|
||||||
|
|
||||||
const commentsData = store.readQuery<{ event: IEvent }>({
|
const commentsData = store.readQuery<{ event: IEvent }>({
|
||||||
query: COMMENTS_THREADS,
|
query: COMMENTS_THREADS_WITH_REPLIES,
|
||||||
variables: {
|
variables: {
|
||||||
eventUUID: this.event.uuid,
|
eventUUID: this.event.uuid,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!commentsData) return;
|
if (!commentsData) return;
|
||||||
const { event } = commentsData;
|
const { event } = commentsData;
|
||||||
const { comments: oldComments } = event;
|
let updatedComments: IComment[] = [...event.comments];
|
||||||
|
|
||||||
if (comment.originComment) {
|
if (comment.originComment) {
|
||||||
// we have deleted a reply to a thread
|
// 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 { originComment } = comment;
|
||||||
|
|
||||||
const parentCommentIndex = oldComments.findIndex(
|
const parentCommentIndex = updatedComments.findIndex(
|
||||||
(oldComment) => oldComment.id === originComment.id
|
(oldComment) => oldComment.id === originComment.id
|
||||||
);
|
);
|
||||||
const parentComment = oldComments[parentCommentIndex];
|
const parentComment = updatedComments[parentCommentIndex];
|
||||||
parentComment.replies = replies;
|
const updatedReplies = parentComment.replies.map((reply) => {
|
||||||
parentComment.totalReplies -= 1;
|
if (reply.id === deletedCommentId) {
|
||||||
oldComments.splice(parentCommentIndex, 1, parentComment);
|
return {
|
||||||
event.comments = oldComments;
|
...reply,
|
||||||
|
deletedAt: new Date().toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return reply;
|
||||||
|
});
|
||||||
|
updatedComments.splice(parentCommentIndex, 1, {
|
||||||
|
...parentComment,
|
||||||
|
replies: updatedReplies,
|
||||||
|
totalReplies: parentComment.totalReplies - 1,
|
||||||
|
});
|
||||||
|
console.log("updatedComments", updatedComments);
|
||||||
} else {
|
} else {
|
||||||
// we have deleted a thread itself
|
// we have deleted a thread itself
|
||||||
event.comments = oldComments.filter(
|
updatedComments = updatedComments.map((reply) => {
|
||||||
(reply) => reply.id !== deletedCommentId
|
if (reply.id === deletedCommentId) {
|
||||||
);
|
return {
|
||||||
|
...reply,
|
||||||
|
deletedAt: new Date().toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return reply;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
store.writeQuery({
|
store.writeQuery({
|
||||||
query: COMMENTS_THREADS,
|
query: COMMENTS_THREADS_WITH_REPLIES,
|
||||||
variables: {
|
variables: {
|
||||||
eventUUID: this.event.uuid,
|
eventUUID: this.event.uuid,
|
||||||
},
|
},
|
||||||
data: { event },
|
data: {
|
||||||
|
event: {
|
||||||
|
...event,
|
||||||
|
comments: updatedComments,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -38,6 +38,12 @@ export const COMMENT_RECURSIVE_FRAGMENT = gql`
|
|||||||
}
|
}
|
||||||
replies {
|
replies {
|
||||||
...CommentFields
|
...CommentFields
|
||||||
|
inReplyToComment {
|
||||||
|
...CommentFields
|
||||||
|
}
|
||||||
|
originComment {
|
||||||
|
...CommentFields
|
||||||
|
}
|
||||||
replies {
|
replies {
|
||||||
...CommentFields
|
...CommentFields
|
||||||
}
|
}
|
||||||
@ -68,6 +74,19 @@ export const COMMENTS_THREADS = gql`
|
|||||||
${COMMENT_FIELDS_FRAGMENT}
|
${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`
|
export const CREATE_COMMENT_FROM_EVENT = gql`
|
||||||
mutation CreateCommentFromEvent(
|
mutation CreateCommentFromEvent(
|
||||||
$eventId: ID!
|
$eventId: ID!
|
||||||
|
@ -5,7 +5,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
|||||||
|
|
||||||
use Absinthe.Schema.Notation
|
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.{Actors, Addresses, Discussions}
|
||||||
alias Mobilizon.GraphQL.Resolvers.{Event, Media, Tag}
|
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
|
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
|
end
|
||||||
|
|
||||||
# field(:tracks, list_of(:track))
|
# field(:tracks, list_of(:track))
|
||||||
|
@ -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(:in_reply_to_comment, Comment, foreign_key: :in_reply_to_comment_id)
|
||||||
belongs_to(:origin_comment, Comment, foreign_key: :origin_comment_id)
|
belongs_to(:origin_comment, Comment, foreign_key: :origin_comment_id)
|
||||||
belongs_to(:discussion, Discussion, type: :binary_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)
|
many_to_many(:tags, Tag, join_through: "comments_tags", on_replace: :delete)
|
||||||
has_many(:mentions, Mention)
|
has_many(:mentions, Mention)
|
||||||
many_to_many(:media, Media, join_through: "comments_medias", on_replace: :delete)
|
many_to_many(:media, Media, join_through: "comments_medias", on_replace: :delete)
|
||||||
|
@ -72,7 +72,7 @@ defmodule Mobilizon.Discussions do
|
|||||||
Read: https://hexdocs.pm/absinthe/ecto.html#dataloader
|
Read: https://hexdocs.pm/absinthe/ecto.html#dataloader
|
||||||
"""
|
"""
|
||||||
@spec query(atom(), map()) :: Ecto.Queryable.t()
|
@spec query(atom(), map()) :: Ecto.Queryable.t()
|
||||||
def query(Comment, _params) do
|
def query(Comment, %{top_level: true}) do
|
||||||
Comment
|
Comment
|
||||||
|> join(:left, [c], r in Comment, on: r.origin_comment_id == c.id)
|
|> join(:left, [c], r in Comment, on: r.origin_comment_id == c.id)
|
||||||
|> where([c, _], is_nil(c.in_reply_to_comment_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)})
|
|> select([c, r], %{c | total_replies: count(r.id)})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def query(Comment, _) do
|
||||||
|
order_by(Comment, [c], asc: :published_at)
|
||||||
|
end
|
||||||
|
|
||||||
def query(queryable, _) do
|
def query(queryable, _) do
|
||||||
queryable
|
queryable
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user