Merge branch 'bugfix/emoji-size' into 'develop'

Bugfix: emoji size in profile

Closes #269

See merge request pleroma/pleroma!792
This commit is contained in:
lambda 2019-05-03 17:26:39 +00:00
commit 4948a73e37
16 changed files with 139 additions and 32 deletions

View file

@ -113,9 +113,7 @@ def emojify(text, emoji, strip \\ false) do
html = html =
if not strip do if not strip do
"<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{ "<img class='emoji' alt='#{emoji}' title='#{emoji}' src='#{MediaProxy.url(file)}' />"
MediaProxy.url(file)
}' />"
else else
"" ""
end end
@ -130,12 +128,23 @@ def demojify(text) do
def demojify(text, nil), do: text def demojify(text, nil), do: text
@doc "Outputs a list of the emoji-shortcodes in a text"
def get_emoji(text) when is_binary(text) do def get_emoji(text) when is_binary(text) do
Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end) Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end)
end end
def get_emoji(_), do: [] def get_emoji(_), do: []
@doc "Outputs a list of the emoji-Maps in a text"
def get_emoji_map(text) when is_binary(text) do
get_emoji(text)
|> Enum.reduce(%{}, fn {name, file, _group}, acc ->
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
end)
end
def get_emoji_map(_), do: []
def html_escape({text, mentions, hashtags}, type) do def html_escape({text, mentions, hashtags}, type) do
{html_escape(text, type), mentions, hashtags} {html_escape(text, type), mentions, hashtags}
end end

View file

@ -151,6 +151,7 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
Meta.allow_tag_with_these_attributes("img", [ Meta.allow_tag_with_these_attributes("img", [
"width", "width",
"height", "height",
"class",
"title", "title",
"alt" "alt"
]) ])
@ -221,6 +222,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes("img", [ Meta.allow_tag_with_these_attributes("img", [
"width", "width",
"height", "height",
"class",
"title", "title",
"alt" "alt"
]) ])

View file

@ -11,7 +11,6 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Bookmark alias Pleroma.Bookmark
alias Pleroma.Formatter
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Registration alias Pleroma.Registration
@ -1331,18 +1330,15 @@ def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
end end
end end
def parse_bio(bio, user \\ %User{info: %{source_data: %{}}}) def parse_bio(bio) when is_binary(bio) and bio != "" do
def parse_bio(nil, _user), do: "" bio
def parse_bio(bio, _user) when bio == "", do: bio |> CommonUtils.format_input("text/plain", mentions_format: :full)
|> elem(0)
end
def parse_bio(bio, user) do def parse_bio(_), do: ""
emoji =
(user.info.source_data["tag"] || [])
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
{String.trim(name, ":"), url}
end)
def parse_bio(bio, user) when is_binary(bio) and bio != "" do
# TODO: get profile URLs other than user.ap_id # TODO: get profile URLs other than user.ap_id
profile_urls = [user.ap_id] profile_urls = [user.ap_id]
@ -1352,9 +1348,10 @@ def parse_bio(bio, user) do
rel: &RelMe.maybe_put_rel_me(&1, profile_urls) rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
) )
|> elem(0) |> elem(0)
|> Formatter.emojify(emoji)
end end
def parse_bio(_, _), do: ""
def tag(user_identifiers, tags) when is_list(user_identifiers) do def tag(user_identifiers, tags) when is_list(user_identifiers) do
Repo.transaction(fn -> Repo.transaction(fn ->
for user_identifier <- user_identifiers, do: tag(user_identifier, tags) for user_identifier <- user_identifiers, do: tag(user_identifier, tags)

View file

@ -41,6 +41,7 @@ defmodule Pleroma.User.Info do
field(:hide_favorites, :boolean, default: true) field(:hide_favorites, :boolean, default: true)
field(:pinned_activities, {:array, :string}, default: []) field(:pinned_activities, {:array, :string}, default: [])
field(:flavour, :string, default: nil) field(:flavour, :string, default: nil)
field(:emoji, {:array, :map}, default: [])
field(:notification_settings, :map, field(:notification_settings, :map,
default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true} default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true}

View file

@ -856,10 +856,16 @@ def add_mention_tags(object) do
|> Map.put("tag", tags ++ mentions) |> Map.put("tag", tags ++ mentions)
end end
def add_emoji_tags(%User{info: %{"emoji" => _emoji} = user_info} = object) do
user_info = add_emoji_tags(user_info)
object
|> Map.put(:info, user_info)
end
# TODO: we should probably send mtime instead of unix epoch time for updated # TODO: we should probably send mtime instead of unix epoch time for updated
def add_emoji_tags(object) do def add_emoji_tags(%{"emoji" => emoji} = object) do
tags = object["tag"] || [] tags = object["tag"] || []
emoji = object["emoji"] || []
out = out =
emoji emoji
@ -877,6 +883,10 @@ def add_emoji_tags(object) do
|> Map.put("tag", tags ++ out) |> Map.put("tag", tags ++ out)
end end
def add_emoji_tags(object) do
object
end
def set_conversation(object) do def set_conversation(object) do
Map.put(object, "conversation", object["context"]) Map.put(object, "conversation", object["context"])
end end

View file

@ -69,6 +69,11 @@ def render("user.json", %{user: user}) do
endpoints = render("endpoints.json", %{user: user}) endpoints = render("endpoints.json", %{user: user})
user_tags =
user
|> Transmogrifier.add_emoji_tags()
|> Map.get("tag", [])
%{ %{
"id" => user.ap_id, "id" => user.ap_id,
"type" => "Person", "type" => "Person",
@ -87,7 +92,7 @@ def render("user.json", %{user: user}) do
"publicKeyPem" => public_key "publicKeyPem" => public_key
}, },
"endpoints" => endpoints, "endpoints" => endpoints,
"tag" => user.info.source_data["tag"] || [] "tag" => (user.info.source_data["tag"] || []) ++ user_tags
} }
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user)) |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user)) |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))

View file

@ -151,8 +151,8 @@ def post(user, %{"status" => status} = data) do
), ),
{to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility), {to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
context <- make_context(in_reply_to), context <- make_context(in_reply_to),
cw <- data["spoiler_text"], cw <- data["spoiler_text"] || "",
full_payload <- String.trim(status <> (data["spoiler_text"] || "")), full_payload <- String.trim(status <> cw),
length when length in 1..limit <- String.length(full_payload), length when length in 1..limit <- String.length(full_payload),
object <- object <-
make_note_data( make_note_data(
@ -170,10 +170,7 @@ def post(user, %{"status" => status} = data) do
Map.put( Map.put(
object, object,
"emoji", "emoji",
(Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"])) Formatter.get_emoji_map(full_payload)
|> Enum.reduce(%{}, fn {name, file, _}, acc ->
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
end)
) do ) do
res = res =
ActivityPub.create( ActivityPub.create(

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Bookmark alias Pleroma.Bookmark
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Filter alias Pleroma.Filter
alias Pleroma.Formatter
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Object.Fetcher alias Pleroma.Object.Fetcher
@ -86,7 +87,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
user_params = user_params =
%{} %{}
|> add_if_present(params, "display_name", :name) |> add_if_present(params, "display_name", :name)
|> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value)} end) |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
|> add_if_present(params, "avatar", :avatar, fn value -> |> add_if_present(params, "avatar", :avatar, fn value ->
with %Plug.Upload{} <- value, with %Plug.Upload{} <- value,
{:ok, object} <- ActivityPub.upload(value, type: :avatar) do {:ok, object} <- ActivityPub.upload(value, type: :avatar) do
@ -96,6 +97,12 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
end end
end) end)
emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
user_info_emojis =
((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
|> Enum.dedup()
info_params = info_params =
[:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role] [:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role]
|> Enum.reduce(%{}, fn key, acc -> |> Enum.reduce(%{}, fn key, acc ->
@ -112,6 +119,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
_ -> :error _ -> :error
end end
end) end)
|> Map.put(:emoji, user_info_emojis)
info_cng = User.Info.profile_update(user.info, info_params) info_cng = User.Info.profile_update(user.info, info_params)

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
alias Ecto.Changeset alias Ecto.Changeset
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Formatter
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
@ -653,7 +654,22 @@ defp build_info_cng(user, params) do
defp parse_profile_bio(user, params) do defp parse_profile_bio(user, params) do
if bio = params["description"] do if bio = params["description"] do
Map.put(params, "bio", User.parse_bio(bio, user)) emojis_text = (params["description"] || "") <> " " <> (params["name"] || "")
emojis =
((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
|> Enum.dedup()
user_info =
user.info
|> Map.put(
"emoji",
emojis
)
params
|> Map.put("bio", User.parse_bio(bio, user))
|> Map.put("info", user_info)
else else
params params
end end

View file

@ -67,6 +67,13 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do
{String.trim(name, ":"), url} {String.trim(name, ":"), url}
end) end)
emoji = Enum.dedup(emoji ++ user.info.emoji)
description_html =
(user.bio || "")
|> HTML.filter_tags(User.html_filter_policy(for_user))
|> Formatter.emojify(emoji)
# ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``. # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
# For example: [{"name": "Pronoun", "value": "she/her"}, …] # For example: [{"name": "Pronoun", "value": "she/her"}, …]
fields = fields =
@ -78,7 +85,7 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do
%{ %{
"created_at" => user.inserted_at |> Utils.format_naive_asctime(), "created_at" => user.inserted_at |> Utils.format_naive_asctime(),
"description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")), "description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
"description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)), "description_html" => description_html,
"favourites_count" => 0, "favourites_count" => 0,
"followers_count" => user_info[:follower_count], "followers_count" => user_info[:follower_count],
"following" => following, "following" => following,

View file

@ -248,7 +248,7 @@ test "it adds cool emoji" do
text = "I love :firefox:" text = "I love :firefox:"
expected_result = expected_result =
"I love <img height=\"32px\" width=\"32px\" alt=\"firefox\" title=\"firefox\" src=\"/emoji/Firefox.gif\" />" "I love <img class=\"emoji\" alt=\"firefox\" title=\"firefox\" src=\"/emoji/Firefox.gif\" />"
assert Formatter.emojify(text) == expected_result assert Formatter.emojify(text) == expected_result
end end
@ -263,7 +263,7 @@ test "it does not add XSS emoji" do
} }
expected_result = expected_result =
"I love <img height=\"32px\" width=\"32px\" alt=\"\" title=\"\" src=\"https://placehold.it/1x1\" />" "I love <img class=\"emoji\" alt=\"\" title=\"\" src=\"https://placehold.it/1x1\" />"
assert Formatter.emojify(text, custom_emoji) == expected_result assert Formatter.emojify(text, custom_emoji) == expected_result
end end

View file

@ -1103,7 +1103,7 @@ test "preserves hosts in user links text" do
expected_text = expected_text =
"A.k.a. <span class='h-card'><a data-user='#{remote_user.id}' class='u-url mention' href='#{ "A.k.a. <span class='h-card'><a data-user='#{remote_user.id}' class='u-url mention' href='#{
remote_user.ap_id remote_user.ap_id
}'>" <> "@<span>nick@domain.com</span></a></span>" }'>@<span>nick@domain.com</span></a></span>"
assert expected_text == User.parse_bio(bio, user) assert expected_text == User.parse_bio(bio, user)
end end

View file

@ -2351,6 +2351,33 @@ test "requires 'write' permission", %{conn: conn} do
end end
end end
end end
test "updates profile emojos", %{conn: conn} do
user = insert(:user)
note = "*sips :blank:*"
name = "I am :firefox:"
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{
"note" => note,
"display_name" => name
})
assert json_response(conn, 200)
conn =
conn
|> get("/api/v1/accounts/#{user.id}")
assert user = json_response(conn, 200)
assert user["note"] == note
assert user["display_name"] == name
assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"]
end
end end
test "get instance information", %{conn: conn} do test "get instance information", %{conn: conn} do

View file

@ -1611,6 +1611,34 @@ test "it unlocks an account", %{conn: conn} do
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end end
# Broken before the change to class="emoji" and non-<img/> in the DB
@tag :skip
test "it formats emojos", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"bio" => "I love our :moominmamma:"
})
assert response = json_response(conn, 200)
assert %{
"description" => "I love our :moominmamma:",
"description_html" =>
~s{I love our <img class="emoji" alt="moominmamma" title="moominmamma" src="} <>
_
} = response
conn =
conn
|> get("/api/users/show.json?user_id=#{user.nickname}")
assert response == json_response(conn, 200)
end
end end
defp valid_user(_context) do defp valid_user(_context) do

View file

@ -100,7 +100,7 @@ test "a create activity with a summary containing emoji" do
expected = ":firefox: meow" expected = ":firefox: meow"
expected_html = expected_html =
"<img height=\"32px\" width=\"32px\" alt=\"firefox\" title=\"firefox\" src=\"http://localhost:4001/emoji/Firefox.gif\" /> meow" "<img class=\"emoji\" alt=\"firefox\" title=\"firefox\" src=\"http://localhost:4001/emoji/Firefox.gif\" /> meow"
assert result["summary"] == expected assert result["summary"] == expected
assert result["summary_html"] == expected_html assert result["summary_html"] == expected_html

View file

@ -32,7 +32,7 @@ test "A user with an avatar object", %{user: user} do
test "A user with emoji in username" do test "A user with emoji in username" do
expected = expected =
"<img height=\"32px\" width=\"32px\" alt=\"karjalanpiirakka\" title=\"karjalanpiirakka\" src=\"/file.png\" /> man" "<img class=\"emoji\" alt=\"karjalanpiirakka\" title=\"karjalanpiirakka\" src=\"/file.png\" /> man"
user = user =
insert(:user, %{ insert(:user, %{