diff --git a/lib/service/formatter/formatter.ex b/lib/service/formatter/formatter.ex index cf8c9526a..bba56b741 100644 --- a/lib/service/formatter/formatter.ex +++ b/lib/service/formatter/formatter.ex @@ -66,7 +66,15 @@ defmodule Mobilizon.Service.Formatter do def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do tag = String.downcase(tag) url = "#{Endpoint.url()}/tag/#{tag}" - link = "" + + link = + Tag.content_tag(:a, tag_text, + class: "hashtag", + "data-tag": tag, + href: url, + rel: "tag ugc" + ) + |> Phoenix.HTML.safe_to_string() {link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}} end @@ -81,7 +89,8 @@ defmodule Mobilizon.Service.Formatter do options = linkify_opts() ++ options acc = %{mentions: MapSet.new(), tags: MapSet.new()} - {text, %{mentions: mentions, tags: tags}} = Linkify.link_map(text, acc, options) + {text, %{mentions: mentions}} = Linkify.link_map(text, acc, options) + {text, tags} = extract_tags(text) {text, MapSet.to_list(mentions), MapSet.to_list(tags)} end @@ -135,10 +144,45 @@ defmodule Mobilizon.Service.Formatter do defp linkify_opts do Mobilizon.Config.get(__MODULE__) ++ [ - hashtag: true, - hashtag_handler: &__MODULE__.hashtag_handler/4, + hashtag: false, mention: true, mention_handler: &__MODULE__.mention_handler/4 ] end + + @match_hashtag ~r/(?:^|[^\p{L}\p{M}\p{Nd}\)])(?\#[[:word:]_]*[[:alpha:]_·][[:word:]_·\p{M}]*)/u + + @spec extract_tags(String.t()) :: {String.t(), MapSet.t()} + def extract_tags(text) do + matches = + @match_hashtag + |> Regex.scan(text, capture: [:tag]) + |> Enum.map(&hd/1) + |> Enum.map(&{&1, tag_text_strip(&1)}) + |> MapSet.new() + + text = + @match_hashtag + |> Regex.replace(text, &generate_tag_link/2) + |> String.trim() + + {text, matches} + end + + @spec generate_tag_link(String.t(), String.t()) :: String.t() + defp generate_tag_link(_, tag_text) do + tag = tag_text_strip(tag_text) + url = "#{Endpoint.url()}/tag/#{tag}" + + Tag.content_tag(:a, tag_text, + class: "hashtag", + "data-tag": tag, + href: url, + rel: "tag ugc" + ) + |> Phoenix.HTML.safe_to_string() + |> (&" #{&1}").() + end + + defp tag_text_strip(tag), do: tag |> String.trim("#") |> String.downcase() end diff --git a/test/service/formatter/formatter_test.exs b/test/service/formatter/formatter_test.exs index 79e12429a..3dd7ba167 100644 --- a/test/service/formatter/formatter_test.exs +++ b/test/service/formatter/formatter_test.exs @@ -13,7 +13,7 @@ defmodule Mobilizon.Service.FormatterTest do text = "I love #cofe and #2hu" expected_text = - "I love and " + "I love #cofe and #2hu" assert {^expected_text, [], _tags} = Formatter.linkify(text) end @@ -22,7 +22,7 @@ defmodule Mobilizon.Service.FormatterTest do text = "#fact_3: pleroma does what mastodon't" expected_text = - ": pleroma does what mastodon't" + "#fact_3: pleroma does what mastodon't" assert {^expected_text, [], _tags} = Formatter.linkify(text) end @@ -174,6 +174,12 @@ defmodule Mobilizon.Service.FormatterTest do assert {_text, [], ^expected_tags} = Formatter.linkify(text) end + + test "parses tags in HTML" do + text = "

Hello #there

" + + assert {_text, [], [{"#there", "there"}]} = Formatter.linkify(text) + end end test "it can parse mentions and return the relevant users" do