From 5214a74c0393d39e3358d8dd17537703b78ea1c1 Mon Sep 17 00:00:00 2001 From: Jorge Manrubia Date: Sun, 1 Jun 2025 21:51:26 +0200 Subject: [PATCH] Autolink emails and URLs at rendering time --- app/helpers/html_helper.rb | 46 +++++++++++++++++++ .../action_text/contents/_content.html.erb | 2 +- test/helpers/html_helper_test.rb | 35 ++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 app/helpers/html_helper.rb create mode 100644 test/helpers/html_helper_test.rb diff --git a/app/helpers/html_helper.rb b/app/helpers/html_helper.rb new file mode 100644 index 000000000..b989adf12 --- /dev/null +++ b/app/helpers/html_helper.rb @@ -0,0 +1,46 @@ +module HtmlHelper + def format_html(html) + fragment = Nokogiri::HTML.fragment(html) + + auto_link(fragment) + + fragment.to_html.html_safe + end + + private + EXCLUDED_ELEMENTS = %w[ a figcaption pre code ] + EMAIL_REGEXP = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/ + URL_REGEXP = URI::DEFAULT_PARSER.make_regexp(%w[http https]) + + def auto_link(fragment) + fragment.traverse do |node| + next unless auto_linkable_node?(node) + + content = node.text + linked_content = content.dup + + auto_link_urls(linked_content) + auto_link_emails(linked_content) + + if linked_content != content + node.replace(Nokogiri::HTML.fragment(linked_content)) + end + end + end + + def auto_linkable_node?(node) + node.text? && node.ancestors.none? { |ancestor| EXCLUDED_ELEMENTS.include?(ancestor.name) } + end + + def auto_link_urls(linked_content) + linked_content.gsub!(URL_REGEXP) do |match| + %(#{match}) + end + end + + def auto_link_emails(text) + text.gsub!(EMAIL_REGEXP) do |match| + %(#{match}) + end + end +end diff --git a/app/views/layouts/action_text/contents/_content.html.erb b/app/views/layouts/action_text/contents/_content.html.erb index 0dec72775..b264733d7 100644 --- a/app/views/layouts/action_text/contents/_content.html.erb +++ b/app/views/layouts/action_text/contents/_content.html.erb @@ -1,3 +1,3 @@
- <%= yield -%> + <%= format_html yield -%>
diff --git a/test/helpers/html_helper_test.rb b/test/helpers/html_helper_test.rb new file mode 100644 index 000000000..818ffa57b --- /dev/null +++ b/test/helpers/html_helper_test.rb @@ -0,0 +1,35 @@ +require "test_helper" + +class HtmlHelperTest < ActionView::TestCase + test "converts URLs into anchor tags" do + assert_equal_html \ + %(

Check this: https://example.com

), + format_html("

Check this: https://example.com

") + end + + test "respect existing links" do + assert_equal_html \ + %(

Check this: https://example.com

), + format_html("

Check this: https://example.com

") + end + + test "converts email addresses into mailto links" do + assert_equal_html \ + %(

Contact us at support@example.com

), + format_html("

Contact us at support@example.com

") + end + + test "respect existing linked emails" do + assert_equal_html \ + %(

Contact us at support@example.com

), + format_html(%(

Contact us at support@example.com

)) + end + + test "don't autolink content in excluded elements" do + %w[ figcaption pre code ].each do |element| + assert_equal_html \ + "<#{element}>Check this: https://example.com", + format_html("<#{element}>Check this: https://example.com") + end + end +end