@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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: %{}
|
||||
|
||||
@@ -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/<li>/, "\\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
|
||||
|
||||
37
lib/service/formatter/text.ex
Normal file
37
lib/service/formatter/text.ex
Normal file
@@ -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
|
||||
@@ -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))
|
||||
|
||||
@@ -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, %{
|
||||
|
||||
@@ -35,61 +35,164 @@
|
||||
<tr>
|
||||
<td align="center" valign="top" width="600">
|
||||
<![endif]-->
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
|
||||
<!-- COPY -->
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="border-radius: 3px; text-align: left; padding: 10px 5% 0px 30px; color: #474467; font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: 400;line-height: 25px;"
|
||||
>
|
||||
<%= dgettext(
|
||||
"activity",
|
||||
"%{profile} has posted an announcement under event %{event}.",
|
||||
%{
|
||||
profile: "<b>#{escape_html(display_name_and_username(@activity.author))}</b>",
|
||||
event:
|
||||
"<a href=\"#{Routes.page_url(Mobilizon.Web.Endpoint,
|
||||
:event,
|
||||
@activity.subject_params["event_uuid"]) |> URI.decode()}\">
|
||||
<%= case @activity.type do %>
|
||||
<% :comment -> %>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
|
||||
<!-- COPY -->
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="border-radius: 3px; text-align: left; padding: 10px 5% 0px 30px; color: #474467; font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: 400;line-height: 25px;"
|
||||
>
|
||||
<%= dgettext(
|
||||
"activity",
|
||||
"%{profile} has posted a public announcement under event %{event}.",
|
||||
%{
|
||||
profile:
|
||||
"<b>#{escape_html(display_name_and_username(@activity.author))}</b>",
|
||||
event:
|
||||
"<a href=\"#{Routes.page_url(Mobilizon.Web.Endpoint,
|
||||
:event,
|
||||
@activity.subject_params["event_uuid"]) |> URI.decode()}\">
|
||||
#{escape_html(@activity.subject_params["event_title"])}
|
||||
</a>"
|
||||
}
|
||||
)
|
||||
|> raw %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="center" style="padding: 20px 30px 60px 30px;">
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 3px;" bgcolor="#3C376E">
|
||||
<a
|
||||
href={
|
||||
}
|
||||
)
|
||||
|> raw %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="center" style="padding: 20px 30px 60px 30px;">
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 3px;" bgcolor="#3C376E">
|
||||
<a
|
||||
href={
|
||||
"#{Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"])}"
|
||||
}
|
||||
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") %>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
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") %>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<% :conversation -> %>
|
||||
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 600px;">
|
||||
<!-- COPY -->
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="border-radius: 3px; text-align: left; padding: 10px 5% 0px 30px; color: #474467; font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: 400;line-height: 25px;"
|
||||
>
|
||||
<%= dgettext(
|
||||
"activity",
|
||||
"%{profile} has posted a private announcement about event %{event}.",
|
||||
%{
|
||||
profile:
|
||||
"<b>#{escape_html(display_name_and_username(@activity.author))}</b>",
|
||||
event:
|
||||
"<a href=\"#{Routes.page_url(Mobilizon.Web.Endpoint,
|
||||
:event,
|
||||
@activity.subject_params["conversation_event_uuid"]) |> URI.decode()}\">
|
||||
#{escape_html(@activity.subject_params["conversation_event_title"])}
|
||||
</a>"
|
||||
}
|
||||
)
|
||||
|> raw %>
|
||||
<%= dgettext(
|
||||
"activity",
|
||||
"It might give details on how to join the event, so make sure to read it appropriately."
|
||||
) %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="center" style="padding: 20px 30px 60px 30px;">
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<blockquote style="border-left-width: 0.25rem;border-left-color: #e2e8f0;border-left-style: solid;padding-left: 1em;margin: 0;text-align: start;color: #474467;font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: 400;line-height: 25px;">
|
||||
<%= @extra["conversation"].last_comment.text
|
||||
|> sanitize_to_basic_html()
|
||||
|> raw() %>
|
||||
</blockquote>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="border-radius: 3px; text-align: left; padding: 10px 5% 0px 30px; color: #474467; font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 16px; font-weight: 400;line-height: 25px;"
|
||||
>
|
||||
<%= 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."
|
||||
) %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="left">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td bgcolor="#ffffff" align="center" style="padding: 20px 30px 60px 30px;">
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 3px;" bgcolor="#3C376E">
|
||||
<a
|
||||
href={
|
||||
"#{Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["conversation_event_uuid"])}"
|
||||
}
|
||||
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") %>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<% end %>
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
<%= @subject %>
|
||||
|
||||
==
|
||||
|
||||
<%= dgettext("activity", "%{profile} has posted an announcement under event %{event}.",
|
||||
<%= case @activity.type do %>
|
||||
<% :comment -> %>
|
||||
<%= dgettext("activity", "%{profile} has posted a public announcement under event %{event}.",
|
||||
%{
|
||||
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
|
||||
event: @activity.subject_params["event_title"]
|
||||
}
|
||||
) %>
|
||||
<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %>
|
||||
<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["event_uuid"]) |> URI.decode() %>
|
||||
<% :conversation -> %>
|
||||
<%= dgettext("activity", "%{profile} has posted a private announcement about event %{event}.",
|
||||
%{
|
||||
profile: Mobilizon.Actors.Actor.display_name_and_username(@activity.author),
|
||||
event: @activity.subject_params["conversation_event_title"]
|
||||
}
|
||||
) %>
|
||||
<%= dgettext("activity", "It might give details on how to join the event, so make sure to read it appropriately.") %>
|
||||
|
||||
--
|
||||
|
||||
<%= @extra["conversation"].last_comment.text |> html_to_text() |> mail_quote() %>
|
||||
|
||||
--
|
||||
|
||||
<%= 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.") %>
|
||||
<%= Routes.page_url(Mobilizon.Web.Endpoint, :event, @activity.subject_params["conversation_event_uuid"]) |> URI.decode() %>
|
||||
<% end %>
|
||||
@@ -7,6 +7,7 @@ defmodule Mobilizon.Web.EmailView do
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Service.Address
|
||||
alias Mobilizon.Service.DateTime, as: DateTimeRenderer
|
||||
alias Mobilizon.Service.Formatter.{HTML, Text}
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
import Mobilizon.Web.Gettext
|
||||
import Mobilizon.Service.Metadata.Utils, only: [process_description: 1]
|
||||
@@ -35,6 +36,21 @@ defmodule Mobilizon.Web.EmailView do
|
||||
|> safe_to_string()
|
||||
end
|
||||
|
||||
@spec sanitize_to_basic_html(String.t()) :: String.t()
|
||||
def sanitize_to_basic_html(html) do
|
||||
case HTML.basic_html(html) do
|
||||
{:ok, html} -> html
|
||||
_ -> ""
|
||||
end
|
||||
end
|
||||
|
||||
defdelegate html_to_text(html), to: HTML
|
||||
|
||||
def mail_quote(text) do
|
||||
# https://www.emailonacid.com/blog/article/email-development/line-length-in-html-email/
|
||||
Text.quote_paragraph(text, 78)
|
||||
end
|
||||
|
||||
def escaped_display_name_and_username(actor) do
|
||||
actor
|
||||
|> display_name_and_username()
|
||||
|
||||
Reference in New Issue
Block a user