diff --git a/lib/federation/activity_pub/types/conversation.ex b/lib/federation/activity_pub/types/conversation.ex
index b72cef022..6bb8c7918 100644
--- a/lib/federation/activity_pub/types/conversation.ex
+++ b/lib/federation/activity_pub/types/conversation.ex
@@ -147,6 +147,12 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Conversations do
(args |> Map.get(:mentions, []) |> prepare_mentions()) ++
ConverterUtils.fetch_mentions(mentions)
+ # Can't create a conversation with just ourselves
+ mentions =
+ Enum.filter(mentions, fn %{actor_id: actor_id} ->
+ to_string(actor_id) != to_string(args.actor_id)
+ end)
+
if Enum.empty?(mentions) do
{:error, :empty_participants}
else
diff --git a/lib/graphql/resolvers/conversation.ex b/lib/graphql/resolvers/conversation.ex
index 00f95de53..33c76c333 100644
--- a/lib/graphql/resolvers/conversation.ex
+++ b/lib/graphql/resolvers/conversation.ex
@@ -11,8 +11,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
alias Mobilizon.Storage.Page
alias Mobilizon.Users.User
alias Mobilizon.Web.Endpoint
- # alias Mobilizon.Users.User
import Mobilizon.Web.Gettext, only: [dgettext: 2]
+ require Logger
@spec find_conversations_for_event(Event.t(), map(), Absinthe.Resolution.t()) ::
{:ok, Page.t(ConversationView.t())} | {:error, :unauthenticated}
@@ -157,9 +157,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
{:ok, conversation_to_view(conversation, conversation_participant_actor)}
{:error, :empty_participants} ->
- {:error, dgettext("errors", "Conversation needs to mention at least one participant")}
+ {:error,
+ dgettext(
+ "errors",
+ "Conversation needs to mention at least one participant that's not yourself"
+ )}
end
else
+ Logger.debug(
+ "Actor #{current_actor.id} is not authorized to reply to conversation #{inspect(Map.get(args, :conversation_id))}"
+ )
+
{:error, :unauthorized}
end
end
@@ -259,7 +267,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Conversation do
%Conversation{participants: participants} ->
participant_ids = Enum.map(participants, fn participant -> to_string(participant.id) end)
- current_actor_id in participant_ids or
+ to_string(current_actor_id) in participant_ids or
Enum.any?(participant_ids, fn participant_id ->
Actors.is_member?(current_actor_id, participant_id) and
attributed_to_id == participant_id
diff --git a/lib/graphql/resolvers/participant.ex b/lib/graphql/resolvers/participant.ex
index 520ecb149..23e739873 100644
--- a/lib/graphql/resolvers/participant.ex
+++ b/lib/graphql/resolvers/participant.ex
@@ -2,8 +2,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
@moduledoc """
Handles the participation-related GraphQL calls.
"""
- # alias Mobilizon.Conversations.ConversationParticipant
- alias Mobilizon.{Actors, Config, Crypto, Events}
+ alias Mobilizon.{Actors, Config, Conversations, Crypto, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.Conversations.{Conversation, ConversationView}
alias Mobilizon.Events.{Event, Participant}
@@ -386,6 +385,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
{:member, false} ->
{:error, :unauthorized}
+ {:error, :empty_participants} ->
+ {:error,
+ dgettext(
+ "errors",
+ "There are no participants matching the audience you've selected."
+ )}
+
{:error, err} ->
{:error, err}
end
@@ -394,11 +400,19 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
def send_private_messages_to_participants(_parent, _args, _resolution),
do: {:error, :unauthorized}
- defp conversation_to_view(%Conversation{} = conversation, %Actor{} = actor) do
+ defp conversation_to_view(
+ %Conversation{id: conversation_id} = conversation,
+ %Actor{id: actor_id} = actor
+ ) do
value =
conversation
|> Map.from_struct()
|> Map.put(:actor, actor)
+ |> Map.put(:unread, false)
+ |> Map.put(
+ :conversation_participant_id,
+ Conversations.get_participant_by_conversation_and_actor(conversation_id, actor_id).id
+ )
struct(ConversationView, value)
end
diff --git a/lib/mobilizon/discussions/comment.ex b/lib/mobilizon/discussions/comment.ex
index 0bd20f245..151f45218 100644
--- a/lib/mobilizon/discussions/comment.ex
+++ b/lib/mobilizon/discussions/comment.ex
@@ -77,7 +77,7 @@ defmodule Mobilizon.Discussions.Comment do
belongs_to(:conversation, Conversation)
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)
+ has_many(:mentions, Mention, on_replace: :delete)
many_to_many(:media, Media, join_through: "comments_medias", on_replace: :delete)
timestamps(type: :utc_datetime)
diff --git a/lib/service/activity/conversation.ex b/lib/service/activity/conversation.ex
index f9dfe9738..3f31f1db0 100644
--- a/lib/service/activity/conversation.ex
+++ b/lib/service/activity/conversation.ex
@@ -79,11 +79,16 @@ defmodule Mobilizon.Service.Activity.Conversation do
defp send_participant_notifications(_, _, _, _), do: {:ok, :skipped}
defp event_subject_params(%Conversation{
- event: %Event{id: conversation_event_id, title: conversation_event_title}
+ event: %Event{
+ id: conversation_event_id,
+ title: conversation_event_title,
+ uuid: conversation_event_uuid
+ }
}),
do: %{
conversation_event_id: conversation_event_id,
- conversation_event_title: conversation_event_title
+ conversation_event_title: conversation_event_title,
+ conversation_event_uuid: conversation_event_uuid
}
defp event_subject_params(_), do: %{}
diff --git a/lib/service/formatter/html.ex b/lib/service/formatter/html.ex
index 5544eeded..67948d030 100644
--- a/lib/service/formatter/html.ex
+++ b/lib/service/formatter/html.ex
@@ -14,6 +14,8 @@ defmodule Mobilizon.Service.Formatter.HTML do
def filter_tags(html), do: Sanitizer.scrub(html, DefaultScrubbler)
+ defdelegate basic_html(html), to: FastSanitize
+
@spec strip_tags(String.t()) :: String.t() | no_return()
def strip_tags(html) do
case FastSanitize.strip_tags(html) do
@@ -39,5 +41,17 @@ defmodule Mobilizon.Service.Formatter.HTML do
def strip_tags_and_insert_spaces(html), do: html
+ @spec html_to_text(String.t()) :: String.t()
+ def html_to_text(html) do
+ html
+ |> String.replace(~r/
/, "\\g{1}- ", global: true)
+ |> String.replace(
+ ~r/<\/?\s?br>|<\/\s?p>|<\/\s?li>|<\/\s?div>|<\/\s?h.>/,
+ "\\g{1}\n\r",
+ global: true
+ )
+ |> strip_tags()
+ end
+
def filter_tags_for_oembed(html), do: Sanitizer.scrub(html, OEmbed)
end
diff --git a/lib/service/formatter/text.ex b/lib/service/formatter/text.ex
new file mode 100644
index 000000000..140502f16
--- /dev/null
+++ b/lib/service/formatter/text.ex
@@ -0,0 +1,37 @@
+defmodule Mobilizon.Service.Formatter.Text do
+ @moduledoc """
+ Helps to format text blocks
+
+ Inspired from https://elixirforum.com/t/is-there-are-text-wrapping-library-for-elixir/21733/4
+ Using the Knuth-Plass Line Wrapping Algorithm https://www.students.cs.ubc.ca/~cs-490/2015W2/lectures/Knuth.pdf
+ """
+
+ def quote_paragraph(string, max_line_length) do
+ paragraph(string, max_line_length, "> ")
+ end
+
+ def paragraph(string, max_line_length, prefix \\ "") do
+ string
+ |> String.split("\n\n", trim: true)
+ |> Enum.map(&subparagraph(&1, max_line_length, prefix))
+ |> Enum.join("\n#{prefix}\n")
+ end
+
+ defp subparagraph(string, max_line_length, prefix) do
+ [word | rest] = String.split(string, ~r/\s+/, trim: true)
+
+ lines_assemble(rest, max_line_length - String.length(prefix), String.length(word), word, [])
+ |> Enum.map(&"#{prefix}#{&1}")
+ |> Enum.join("\n")
+ end
+
+ defp lines_assemble([], _, _, line, acc), do: [line | acc] |> Enum.reverse()
+
+ defp lines_assemble([word | rest], max, line_length, line, acc) do
+ if line_length + 1 + String.length(word) > max do
+ lines_assemble(rest, max, String.length(word), word, [line | acc])
+ else
+ lines_assemble(rest, max, line_length + 1 + String.length(word), line <> " " <> word, acc)
+ end
+ end
+end
diff --git a/lib/service/workers/legacy_notifier_builder.ex b/lib/service/workers/legacy_notifier_builder.ex
index 4c0f776c7..a00e19260 100644
--- a/lib/service/workers/legacy_notifier_builder.ex
+++ b/lib/service/workers/legacy_notifier_builder.ex
@@ -22,6 +22,13 @@ defmodule Mobilizon.Service.Workers.LegacyNotifierBuilder do
notify_anonymous_participants(get_in(args, ["subject_params", "event_id"]), activity)
end
+ if args["subject"] == "conversation_created" do
+ notify_anonymous_participants(
+ get_in(args, ["subject_params", "conversation_event_id"]),
+ activity
+ )
+ end
+
args
|> users_to_notify(author_id: args["author_id"], group_id: Map.get(args, "group_id"))
|> Enum.each(¬ify_user(&1, activity))
diff --git a/lib/web/email/activity.ex b/lib/web/email/activity.ex
index 4f3f1a9f1..686360e0f 100644
--- a/lib/web/email/activity.ex
+++ b/lib/web/email/activity.ex
@@ -10,6 +10,7 @@ defmodule Mobilizon.Web.Email.Activity do
alias Mobilizon.Actors.Actor
alias Mobilizon.Config
alias Mobilizon.Web.Email
+ require Logger
@spec direct_activity(String.t(), list(), Keyword.t()) :: Swoosh.Email.t()
def direct_activity(
@@ -39,6 +40,36 @@ defmodule Mobilizon.Web.Email.Activity do
end
@spec anonymous_activity(String.t(), Activity.t(), Keyword.t()) :: Swoosh.Email.t()
+ def anonymous_activity(
+ email,
+ %Activity{subject_params: subject_params, type: :conversation} = activity,
+ options
+ ) do
+ locale = Keyword.get(options, :locale, "en")
+
+ subject =
+ dgettext(
+ "activity",
+ "Informations about your event %{event}",
+ event: subject_params["conversation_event_title"]
+ )
+
+ conversation = Mobilizon.Conversations.get_conversation(activity.object_id)
+
+ Logger.debug("Going to send anonymous activity of type #{activity.type} to #{email}")
+
+ [to: email, subject: subject]
+ |> Email.base_email()
+ |> render_body(:email_anonymous_activity, %{
+ subject: subject,
+ activity: activity,
+ locale: locale,
+ extra: %{
+ "conversation" => conversation
+ }
+ })
+ end
+
def anonymous_activity(email, %Activity{subject_params: subject_params} = activity, options) do
locale = Keyword.get(options, :locale, "en")
@@ -49,6 +80,8 @@ defmodule Mobilizon.Web.Email.Activity do
event: subject_params["event_title"]
)
+ Logger.debug("Going to send anonymous activity of type #{activity.type} to #{email}")
+
[to: email, subject: subject]
|> Email.base_email()
|> render_body(:email_anonymous_activity, %{
diff --git a/lib/web/templates/email/email_anonymous_activity.html.heex b/lib/web/templates/email/email_anonymous_activity.html.heex
index 4e0d9421e..b08a0652f 100644
--- a/lib/web/templates/email/email_anonymous_activity.html.heex
+++ b/lib/web/templates/email/email_anonymous_activity.html.heex
@@ -35,61 +35,164 @@
-
-
-
-
-
-
-
- <%= dgettext(
- "activity",
- "%{profile} has posted an announcement under event %{event}.",
- %{
- profile: "#{escape_html(display_name_and_username(@activity.author))}",
- event:
- " URI.decode()}\">
+ <%= case @activity.type do %>
+ <% :comment -> %>
+
+
+
+
+
- |
-
-
-
-
+ target="_blank"
+ style="font-size: 20px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; padding: 15px 25px; border-radius: 2px; border: 1px solid #3C376E; display: inline-block;"
+ >
+ <%= gettext("Visit event page") %>
+
+ |
+
+
+ |
+
+
+ |
+
+
+ <% :conversation -> %>
+
+
+
+
+
+
+
+ <%= dgettext(
+ "activity",
+ "%{profile} has posted a private announcement about event %{event}.",
+ %{
+ profile:
+ "#{escape_html(display_name_and_username(@activity.author))}",
+ event:
+ " URI.decode()}\">
+ #{escape_html(@activity.subject_params["conversation_event_title"])}
+ "
+ }
+ )
+ |> raw %>
+ <%= dgettext(
+ "activity",
+ "It might give details on how to join the event, so make sure to read it appropriately."
+ ) %>
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+ <%= @extra["conversation"].last_comment.text
+ |> sanitize_to_basic_html()
+ |> raw() %>
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+ <%= dgettext(
+ "activity",
+ "This information is sent privately to you as a person who registered for this event. Share the informations above with other people with caution."
+ ) %>
+ |
+
+
+ |
+
+
+
+
+ |
+
+
+ <% end %>
|