How to custom render external links in Action Text?

How could Action Text be customized to render hyperlinks pointing to a different domain with target="blank" and be preceded by a special icon Screenshot 2021-06-24 at 09.14.27? Preferably without needing to build some custom attachment system, just processing all the hyperlinks present in the text at the moment of rendering.

I’ve found ActionText (like Trix before it) to be fairly resistant to these sorts of refinements, but what you are describing would be fairly trivial to do in your page with JavaScript.

Walter

1 Like

Actually I’ve managed to do it server-side as follows, don’t know if there’s a simpler way:

# config/initializers/action_text.rb
module ActionText
  Rails.application.reloader.to_prepare do
    ContentHelper.allowed_attributes += ["target", "rel"]
    unless ContentHelper.included_modules.include?(ContentHelperMonkeyPatch)
      ContentHelper.include(ContentHelperMonkeyPatch)
    end
    unless Content.included_modules.include?(ContentMonkeyPatch)
      Content.include(ContentMonkeyPatch)
    end
  end
end

# lib/action_text/content_helper_monkey_patch.rb
module ActionText
  module ContentHelperMonkeyPatch
    def custom_render_action_text(content)
      local_base_url = request.base_url
      self.prefix_partial_path_with_controller_namespace = false
      rendered = render_action_text_attachments(content).render_links(local_base_url)
      sanitize_action_text_content(rendered)
    end
  end
end

# lib/action_text/content_monkey_patch.rb
module ActionText
  module ContentMonkeyPatch
    def render_links(local_base_url)
      content = fragment.replace("a") do |node|
        unless node.attribute("href")&.value&.start_with?(local_base_url)
          node.set_attribute("target", "_blank")
          node.set_attribute("rel", "noreferrer noopener")
          node.set_attribute("class", "external")
        end
        node
      end
      self.class.new(content, canonicalize: false)
    end
  end
end
# app/views/action_text/content/_layout.html.erb
<div class="trix-content">
  <%= custom_render_action_text(content) %>
</div>
2 Likes

Thanks! That is really helpful.

Walter

NB: the view needs to be changed when migrating from Rails 6 to Rails 7 (pull request).

Before:

# app/views/action_text/content/_layout.html.erb
<div class="trix-content">
  <%= custom_render_action_text(content) %>
</div>

After:

# app/views/action_text/contents/_content.html.erb
<%= custom_render_action_text(content) %>