commit: e5abcd66b1913f8a5655d209510981f766b64ccf
parent ae032126cdcda07dcf8eebb50de3190536cc3fc4
Author: Egor <egor@kislitsyn.com>
Date: Sat, 23 Feb 2019 12:51:38 +0000
Improve hashtags and `rel` attribute
Diffstat:
3 files changed, 81 insertions(+), 22 deletions(-)
diff --git a/lib/auto_linker/builder.ex b/lib/auto_linker/builder.ex
@@ -7,11 +7,13 @@ defmodule AutoLinker.Builder do
Create a link.
"""
def create_link(url, opts) do
+ url = add_scheme(url)
+
[]
|> build_attrs(url, opts, :rel)
|> build_attrs(url, opts, :target)
|> build_attrs(url, opts, :class)
- |> build_attrs(url, opts, :scheme)
+ |> build_attrs(url, opts, :href)
|> format_url(url, opts)
end
@@ -23,6 +25,13 @@ defmodule AutoLinker.Builder do
|> format_markdown(text, opts)
end
+ defp build_attrs(attrs, uri, %{rel: get_rel}, :rel) when is_function(get_rel, 1) do
+ case get_rel.(uri) do
+ nil -> attrs
+ rel -> [{:rel, rel} | attrs]
+ end
+ end
+
defp build_attrs(attrs, _, opts, :rel) do
if rel = Map.get(opts, :rel, "noopener noreferrer"), do: [{:rel, rel} | attrs], else: attrs
end
@@ -35,12 +44,14 @@ defmodule AutoLinker.Builder do
if cls = Map.get(opts, :class, "auto-linker"), do: [{:class, cls} | attrs], else: attrs
end
- defp build_attrs(attrs, url, _opts, :scheme) do
- if String.starts_with?(url, ["http://", "https://"]),
- do: [{:href, url} | attrs],
- else: [{:href, "http://" <> url} | attrs]
+ defp build_attrs(attrs, url, _opts, :href) do
+ [{:href, url} | attrs]
end
+ defp add_scheme("http://" <> _ = url), do: url
+ defp add_scheme("https://" <> _ = url), do: url
+ defp add_scheme(url), do: "http://" <> url
+
defp format_url(attrs, url, opts) do
url =
url
@@ -117,10 +128,11 @@ defmodule AutoLinker.Builder do
url = mention_prefix <> name
- [href: url]
+ []
|> build_attrs(url, opts, :rel)
|> build_attrs(url, opts, :target)
|> build_attrs(url, opts, :class)
+ |> build_attrs(url, opts, :href)
|> format_mention(name, opts)
end
@@ -129,43 +141,48 @@ defmodule AutoLinker.Builder do
url = hashtag_prefix <> tag
- [href: url]
+ []
|> build_attrs(url, opts, :rel)
|> build_attrs(url, opts, :target)
|> build_attrs(url, opts, :class)
+ |> build_attrs(url, opts, :href)
|> format_hashtag(tag, opts)
end
def create_email_link(email, opts) do
[]
|> build_attrs(email, opts, :class)
+ |> build_attrs("mailto:#{email}", opts, :href)
|> format_email(email, opts)
end
def create_extra_link(uri, opts) do
[]
|> build_attrs(uri, opts, :class)
+ |> build_attrs(uri, opts, :rel)
+ |> build_attrs(uri, opts, :target)
+ |> build_attrs(uri, opts, :href)
|> format_extra(uri, opts)
end
def format_mention(attrs, name, _opts) do
attrs = format_attrs(attrs)
- "<a #{attrs}>@" <> name <> "</a>"
+ "<a #{attrs}>@#{name}</a>"
end
def format_hashtag(attrs, tag, _opts) do
attrs = format_attrs(attrs)
- "<a #{attrs}>#" <> tag <> "</a>"
+ "<a #{attrs}>##{tag}</a>"
end
def format_email(attrs, email, _opts) do
attrs = format_attrs(attrs)
- ~s(<a href="mailto:#{email}" #{attrs}>#{email}</a>)
+ ~s(<a #{attrs}>#{email}</a>)
end
def format_extra(attrs, uri, _opts) do
- attrs = format_attributes(attrs)
- ~s(<a href="#{uri}"#{attrs}>#{uri}</a>)
+ attrs = format_attrs(attrs)
+ ~s(<a #{attrs}>#{uri}</a>)
end
defp format_attributes(attrs) do
diff --git a/lib/auto_linker/parser.ex b/lib/auto_linker/parser.ex
@@ -25,7 +25,6 @@ defmodule AutoLinker.Parser do
~s{, work <a href="#" class="phone-number" data-phone="5555555555">(555) 555-5555</a>}
"""
- # @invalid_url ~r/\.\.+/
@invalid_url ~r/(\.\.+)|(^(\d+\.){1,2}\d+$)/
@match_url ~r{^[\w\.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+$}
@@ -45,7 +44,7 @@ defmodule AutoLinker.Parser do
# https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
@match_email ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/u
- @match_hashtag ~r/^(?<tag>\#\w+)/u
+ @match_hashtag ~r/^(?<tag>\#[[:word:]_]*[[:alpha:]_·][[:word:]_·]*)/u
@prefix_extra [
"magnet:?",
diff --git a/test/auto_linker_test.exs b/test/auto_linker_test.exs
@@ -33,7 +33,7 @@ defmodule AutoLinkerTest do
"hello google.com https://ddg.com 888 888-8888 user@email.com [google.com](http://google.com) irc:///mIRC"
expected =
- "hello <a href=\"http://google.com\">google.com</a> <a href=\"https://ddg.com\">ddg.com</a> <a href=\"#\" class=\"phone-number\" data-phone=\"8888888888\">888 888-8888</a> <a href=\"mailto:user@email.com\" >user@email.com</a> <a href='http://google.com'>google.com</a> <a href=\"irc:///mIRC\">irc:///mIRC</a>"
+ "hello <a href=\"http://google.com\">google.com</a> <a href=\"https://ddg.com\">ddg.com</a> <a href=\"#\" class=\"phone-number\" data-phone=\"8888888888\">888 888-8888</a> <a href=\"mailto:user@email.com\">user@email.com</a> <a href='http://google.com'>google.com</a> <a href=\"irc:///mIRC\">irc:///mIRC</a>"
assert AutoLinker.link(text,
phone: true,
@@ -47,6 +47,22 @@ defmodule AutoLinkerTest do
) == expected
end
+ test "rel as function" do
+ text = "google.com"
+
+ expected = "<a href=\"http://google.com\" rel=\"com\">google.com</a>"
+
+ custom_rel = fn url ->
+ url |> String.split(".") |> List.last()
+ end
+
+ assert AutoLinker.link(text,
+ class: false,
+ new_window: false,
+ rel: custom_rel
+ ) == expected
+ end
+
describe "custom handlers" do
test "mentions handler" do
text = "hello @user, @valid_user and @invalid_user"
@@ -106,7 +122,7 @@ defmodule AutoLinkerTest do
end
expected =
- "Hello again, <span class='h-card'><a href='#/user/user'>@<span>@user</span></a></span>.<script></script>\nThis is on another :moominmamma: line. <a class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"/tag/2hu\">#2hu</a> <a class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"/tag/epic\">#epic</a> <a class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"/tag/phantasmagoric\">#phantasmagoric</a>"
+ "Hello again, <span class='h-card'><a href='#/user/user'>@<span>@user</span></a></span>.<script></script>\nThis is on another :moominmamma: line. <a href=\"/tag/2hu\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">#2hu</a> <a href=\"/tag/epic\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">#epic</a> <a href=\"/tag/phantasmagoric\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">#phantasmagoric</a>"
assert AutoLinker.link(text,
mention: true,
@@ -120,7 +136,7 @@ defmodule AutoLinkerTest do
describe "mentions" do
test "simple mentions" do
expected =
- ~s{hello <a class="auto-linker" target="_blank" rel="noopener noreferrer" href="https://example.com/user/user">@user</a> and <a class="auto-linker" target="_blank" rel="noopener noreferrer" href="https://example.com/user/anotherUser">@anotherUser</a>.}
+ ~s{hello <a href="https://example.com/user/user" class="auto-linker" target="_blank" rel="noopener noreferrer">@user</a> and <a href="https://example.com/user/anotherUser" class="auto-linker" target="_blank" rel="noopener noreferrer">@anotherUser</a>.}
assert AutoLinker.link("hello @user and @anotherUser.",
mention: true,
@@ -132,7 +148,7 @@ defmodule AutoLinkerTest do
text = "hey @user@example.com"
expected =
- "hey <a class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://example.com/user/user@example.com\">@user@example.com</a>"
+ "hey <a href=\"https://example.com/user/user@example.com\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">@user@example.com</a>"
assert AutoLinker.link(text,
mention: true,
@@ -144,7 +160,7 @@ defmodule AutoLinkerTest do
describe "hashtag links" do
test "hashtag" do
expected =
- " one <a class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://example.com/tag/2two\">#2two</a> three <a class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://example.com/tag/four\">#four</a>."
+ " one <a href=\"https://example.com/tag/2two\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">#2two</a> three <a href=\"https://example.com/tag/four\" class=\"auto-linker\" target=\"_blank\" rel=\"noopener noreferrer\">#four</a>."
assert AutoLinker.link(" one #2two three #four.",
hashtag: true,
@@ -152,6 +168,33 @@ defmodule AutoLinkerTest do
) == expected
end
+ test "must have non-numbers" do
+ expected = "<a href=\"/t/1ok\">#1ok</a> #42 #7"
+
+ assert AutoLinker.link("#1ok #42 #7",
+ hashtag: true,
+ hashtag_prefix: "/t/",
+ class: false,
+ rel: false,
+ new_window: false
+ ) == expected
+ end
+
+ test "support French" do
+ text = "#administrateur·rice·s #ingénieur·e·s"
+
+ expected =
+ "<a href=\"/t/administrateur·rice·s\">#administrateur·rice·s</a> <a href=\"/t/ingénieur·e·s\">#ingénieur·e·s</a>"
+
+ assert AutoLinker.link(text,
+ hashtag: true,
+ hashtag_prefix: "/t/",
+ class: false,
+ rel: false,
+ new_window: false
+ ) == expected
+ end
+
test "do not turn urls with hashes into hashtags" do
text = "google.com#test #test google.com/#test #tag"
@@ -262,7 +305,7 @@ defmodule AutoLinkerTest do
expected =
"<a href=\"xmpp:user@example.com\" class=\"auto-linker\">xmpp:user@example.com</a>"
- assert AutoLinker.link(text, extra: true) == expected
+ assert AutoLinker.link(text, extra: true, new_window: false, rel: false) == expected
end
test "email" do
@@ -278,7 +321,7 @@ defmodule AutoLinkerTest do
expected =
"<a href=\"magnet:?xt=urn:btih:a4104a9d2f5615601c429fe8bab8177c47c05c84&dn=ubuntu-18.04.1.0-live-server-amd64.iso&tr=http%3A%2F%2Ftorrent.ubuntu.com%3A6969%2Fannounce&tr=http%3A%2F%2Fipv6.torrent.ubuntu.com%3A6969%2Fannounce\" class=\"auto-linker\">magnet:?xt=urn:btih:a4104a9d2f5615601c429fe8bab8177c47c05c84&dn=ubuntu-18.04.1.0-live-server-amd64.iso&tr=http%3A%2F%2Ftorrent.ubuntu.com%3A6969%2Fannounce&tr=http%3A%2F%2Fipv6.torrent.ubuntu.com%3A6969%2Fannounce</a>"
- assert AutoLinker.link(text, extra: true) == expected
+ assert AutoLinker.link(text, extra: true, new_window: false, rel: false) == expected
end
test "dweb" do
@@ -288,7 +331,7 @@ defmodule AutoLinkerTest do
expected =
"<a href=\"dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt\" class=\"auto-linker\">dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt</a>"
- assert AutoLinker.link(text, extra: true) == expected
+ assert AutoLinker.link(text, extra: true, new_window: false, rel: false) == expected
end
end