diff --git a/js/src/components/Activity/EventActivityItem.vue b/js/src/components/Activity/EventActivityItem.vue index f6a9eef6c..573ee11a7 100644 --- a/js/src/components/Activity/EventActivityItem.vue +++ b/js/src/components/Activity/EventActivityItem.vue @@ -35,7 +35,10 @@ </template> <script lang="ts"> import { usernameWithDomain } from "@/types/actor"; -import { ActivityEventSubject } from "@/types/enums"; +import { + ActivityEventCommentSubject, + ActivityEventSubject, +} from "@/types/enums"; import { mixins } from "vue-class-component"; import { Component } from "vue-property-decorator"; import RouteName from "../../router/name"; @@ -69,6 +72,17 @@ export default class EventActivityItem extends mixins(ActivityMixin) { return "You deleted the event {event}."; } return "The event {event} was deleted by {profile}."; + case ActivityEventCommentSubject.COMMENT_POSTED: + if (this.subjectParams.comment_reply_to) { + if (this.isAuthorCurrentActor) { + return "You replied to a comment on the event {event}."; + } + return "{profile} replied to a comment on the event {event}."; + } + if (this.isAuthorCurrentActor) { + return "You posted a comment on the event {event}."; + } + return "{profile} posted a comment on the event {event}."; default: return undefined; } @@ -77,6 +91,7 @@ export default class EventActivityItem extends mixins(ActivityMixin) { get iconColor(): string | undefined { switch (this.activity.subject) { case ActivityEventSubject.EVENT_CREATED: + case ActivityEventCommentSubject.COMMENT_POSTED: return "is-success"; case ActivityEventSubject.EVENT_UPDATED: return "is-grey"; diff --git a/js/src/components/Comment/Comment.vue b/js/src/components/Comment/Comment.vue index 8d86dc9b6..289552a95 100644 --- a/js/src/components/Comment/Comment.vue +++ b/js/src/components/Comment/Comment.vue @@ -97,7 +97,7 @@ <span style="cursor: pointer" class="level-item reply-btn" - @click="createReplyToComment(comment)" + @click="createReplyToComment()" > <span class="icon is-small"> <b-icon icon="reply" /> @@ -235,17 +235,13 @@ export default class Comment extends Vue { } } - async createReplyToComment(comment: IComment): Promise<void> { + async createReplyToComment(): Promise<void> { if (this.replyTo) { this.replyTo = false; this.newComment = new CommentModel(); return; } this.replyTo = true; - // this.newComment.inReplyToComment = comment; - await this.$nextTick(); - await this.$nextTick(); // For some reason commenteditor needs two $nextTick() to fully render - this.commentEditor.replyToComment(comment); } replyToComment(): void { @@ -303,7 +299,7 @@ export default class Comment extends Vue { get commentId(): string { if (this.comment.originComment) - return `#comment-${this.comment.originComment.uuid}:${this.comment.uuid}`; + return `#comment-${this.comment.originComment.uuid}-${this.comment.uuid}`; return `#comment-${this.comment.uuid}`; } diff --git a/js/src/graphql/group.ts b/js/src/graphql/group.ts index 5f5b27584..cae4c7fa3 100644 --- a/js/src/graphql/group.ts +++ b/js/src/graphql/group.ts @@ -393,6 +393,9 @@ export const GROUP_TIMELINE = gql` title slug } + ... on Comment { + id + } ... on Group { id preferredUsername diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json index fda891bd5..066791bcf 100644 --- a/js/src/i18n/en_US.json +++ b/js/src/i18n/en_US.json @@ -963,5 +963,9 @@ "Delete discussion": "Delete discussion", "All activities": "All activities", "From yourself": "From yourself", - "By others": "By others" + "By others": "By others", + "You posted a comment on the event {event}.": "You posted a comment on the event {event}.", + "{profile} posted a comment on the event {event}.": "{profile} posted a comment on the event {event}.", + "You replied to a comment on the event {event}.": "You replied to a comment on the event {event}.", + "{profile} replied to a comment on the event {event}.": "{profile} replied to a comment on the event {event}." } diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json index 4c5777190..7ffb0ae0d 100644 --- a/js/src/i18n/fr_FR.json +++ b/js/src/i18n/fr_FR.json @@ -1057,5 +1057,9 @@ "Delete discussion": "Supprimer la discussion", "All activities": "Toutes les activités", "From yourself": "De vous", - "By others": "Des autres" + "By others": "Des autres", + "You posted a comment on the event {event}.": "Vous avez posté un commentaire sur l'événement {event}.", + "{profile} posted a comment on the event {event}.": "{profile} a posté un commentaire sur l'événement {event}.", + "You replied to a comment on the event {event}.": "Vous avez répondu à un commentaire sur l'événement {event}.", + "{profile} replied to a comment on the event {event}.": "{profile} a répondu à un commentaire sur l'événement {event}." } diff --git a/js/src/types/activity.model.ts b/js/src/types/activity.model.ts index f36cba7fa..c5c218757 100644 --- a/js/src/types/activity.model.ts +++ b/js/src/types/activity.model.ts @@ -2,6 +2,7 @@ import { IActor, IGroup } from "./actor"; import { IMember } from "./actor/member.model"; import { ActivityDiscussionSubject, + ActivityEventCommentSubject, ActivityEventSubject, ActivityGroupSubject, ActivityMemberSubject, @@ -19,7 +20,8 @@ export type ActivitySubject = | ActivityMemberSubject | ActivityResourceSubject | ActivityDiscussionSubject - | ActivityGroupSubject; + | ActivityGroupSubject + | ActivityEventCommentSubject; export interface IActivity { id: string; diff --git a/js/src/types/enums.ts b/js/src/types/enums.ts index d7d6dbd09..d6a28ff5a 100644 --- a/js/src/types/enums.ts +++ b/js/src/types/enums.ts @@ -197,6 +197,10 @@ export enum ActivityEventSubject { EVENT_DELETED = "event_deleted", } +export enum ActivityEventCommentSubject { + COMMENT_POSTED = "comment_posted", +} + export enum ActivityPostSubject { POST_CREATED = "post_created", POST_UPDATED = "post_updated", diff --git a/js/src/views/Group/Timeline.vue b/js/src/views/Group/Timeline.vue index ff89edd43..f3b9ed051 100644 --- a/js/src/views/Group/Timeline.vue +++ b/js/src/views/Group/Timeline.vue @@ -168,7 +168,6 @@ import { IActivity } from "../../types/activity.model"; import Observer from "../../components/Utils/Observer.vue"; import SkeletonActivityItem from "../../components/Activity/SkeletonActivityItem.vue"; import RouteName from "../../router/name"; -import { Location } from "vue-router"; const PAGINATION_LIMIT = 25; const SKELETON_DAY_ITEMS = 2; diff --git a/lib/federation/activity_pub/types/comments.ex b/lib/federation/activity_pub/types/comments.ex index ee4c206b8..e7d1a3d3a 100644 --- a/lib/federation/activity_pub/types/comments.ex +++ b/lib/federation/activity_pub/types/comments.ex @@ -9,6 +9,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils alias Mobilizon.Federation.ActivityStream.Convertible alias Mobilizon.GraphQL.API.Utils, as: APIUtils + alias Mobilizon.Service.Activity.Comment, as: CommentActivity alias Mobilizon.Share alias Mobilizon.Tombstone alias Mobilizon.Web.Endpoint @@ -24,6 +25,10 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do :ok <- make_sure_event_allows_commenting(args), {:ok, %Comment{discussion_id: discussion_id} = comment} <- Discussions.create_comment(args), + {:ok, _} <- + CommentActivity.insert_activity(comment, + subject: "comment_posted" + ), :ok <- maybe_publish_graphql_subscription(discussion_id), comment_as_data <- Convertible.model_to_as(comment), audience <- diff --git a/lib/graphql/resolvers/activity.ex b/lib/graphql/resolvers/activity.ex index 9b7f589a6..c350c0b07 100644 --- a/lib/graphql/resolvers/activity.ex +++ b/lib/graphql/resolvers/activity.ex @@ -78,6 +78,10 @@ defmodule Mobilizon.GraphQL.Resolvers.Activity do Actors.get_actor(group_id) end + defp get_object(:comment, comment_id) do + Discussions.get_comment(comment_id) + end + @spec transform_params(map()) :: list() defp transform_params(params) do Enum.map(params, fn {key, value} -> %{key: key, value: transform_value(value)} end) diff --git a/lib/graphql/schema/activity.ex b/lib/graphql/schema/activity.ex index 7901c87d5..f43ec75f7 100644 --- a/lib/graphql/schema/activity.ex +++ b/lib/graphql/schema/activity.ex @@ -5,7 +5,7 @@ defmodule Mobilizon.GraphQL.Schema.ActivityType do use Absinthe.Schema.Notation alias Mobilizon.Actors.{Actor, Member} - alias Mobilizon.Discussions.Discussion + alias Mobilizon.Discussions.{Comment, Discussion} alias Mobilizon.Events.Event alias Mobilizon.Posts.Post alias Mobilizon.Resources.Resource @@ -51,6 +51,9 @@ defmodule Mobilizon.GraphQL.Schema.ActivityType do %Discussion{}, _ -> :discussion + %Comment{}, _ -> + :comment + %Actor{type: :Group}, _ -> :group diff --git a/lib/graphql/schema/discussions/comment.ex b/lib/graphql/schema/discussions/comment.ex index c22d1d021..814739d65 100644 --- a/lib/graphql/schema/discussions/comment.ex +++ b/lib/graphql/schema/discussions/comment.ex @@ -11,7 +11,7 @@ defmodule Mobilizon.GraphQL.Schema.Discussions.CommentType do @desc "A comment" object :comment do - interfaces([:action_log_object]) + interfaces([:action_log_object, :activity_object]) field(:id, :id, description: "Internal ID for this comment") field(:uuid, :uuid, description: "An UUID for this comment") field(:url, :string, description: "Comment URL") diff --git a/lib/mobilizon/activities/activities.ex b/lib/mobilizon/activities/activities.ex index 8c04a4e66..491a2f103 100644 --- a/lib/mobilizon/activities/activities.ex +++ b/lib/mobilizon/activities/activities.ex @@ -53,7 +53,7 @@ defmodule Mobilizon.Activities do @resource_activity_subjects ++ @member_activity_subjects ++ @settings_activity_subjects - @object_type ["event", "actor", "post", "discussion", "resource", "member", "group"] + @object_type ["event", "actor", "post", "discussion", "resource", "member", "group", "comment"] defenum(Type, @activity_types) defenum(Subject, @subjects) diff --git a/lib/service/activity/comment.ex b/lib/service/activity/comment.ex new file mode 100644 index 000000000..0b3ebcd31 --- /dev/null +++ b/lib/service/activity/comment.ex @@ -0,0 +1,51 @@ +defmodule Mobilizon.Service.Activity.Comment do + @moduledoc """ + Insert a comment activity + """ + alias Mobilizon.{Actors, Events} + alias Mobilizon.Actors.Actor + alias Mobilizon.Discussions.Comment + alias Mobilizon.Events.Event + alias Mobilizon.Service.Activity + alias Mobilizon.Service.Workers.ActivityBuilder + + @behaviour Activity + + @impl Activity + def insert_activity(comment, options \\ []) + + def insert_activity( + %Comment{ + actor_id: actor_id, + event_id: event_id, + in_reply_to_comment_id: in_reply_to_comment_id + } = comment, + options + ) + when not is_nil(actor_id) and not is_nil(event_id) do + with {:ok, %Event{attributed_to: %Actor{type: :Group} = group} = event} <- + Events.get_event_with_preload(event_id), + %Actor{id: actor_id} <- Actors.get_actor(actor_id), + subject <- Keyword.fetch!(options, :subject) do + ActivityBuilder.enqueue(:build_activity, %{ + "type" => "event", + "subject" => subject, + "subject_params" => %{ + event_title: event.title, + event_uuid: event.uuid, + comment_reply_to: !is_nil(in_reply_to_comment_id) + }, + "group_id" => group.id, + "author_id" => actor_id, + "object_type" => "comment", + "object_id" => to_string(comment.id), + "inserted_at" => DateTime.utc_now() + }) + else + # Event not from group + {:ok, %Event{}} -> {:ok, nil} + end + end + + def insert_activity(_, _), do: {:ok, nil} +end