Rails ActionText Images mit eigenem Partial rendern

Dear reader,
this article is in German. It's about how you can modify the functionality of ActionText of Ruby on Rails so that you have more control on attached images. There is no translation of this article but I'm sure DeepL or Google Translate or an LLM will help you understand it in your language. If you have questions please let me know - mail address is in the footer.

 

ActionText ist eine hilfreiche Erweiterung für Rails und bietet einen einfachen aber erweiterbaren WYSIWIG Editor auf Basis des TRIX Editors.

Wenn man Bildanhänge nutzt ist ActionText leider etwas begrenzt in der Ausgabe. Die Bilder werden bei der Ausgabe im Frontend nicht zugeschnitten und man hat wenig Einfluss auf die Ausgabe (z.B. HTML Wrapper, CSS-Klassen etc).
Ich wollte aber auf die Ausgabe der Bilder Einfluss nehmen. Ich habe mich mehrere Tage durch den Code von ActionText gewühlt um herauszufinden, wie ActionText überhaupt die Anhänge in Zusammenhang mit ActiveStorage ausgibt. Danach habe ich eine Möglichkeit in mein Webprojekt eingebaut Einfluss auf die Bildausgabe zu nehmen.

 

Funktionsweise von Bildern/Anhängen mit ActionText

Werden mit dem Editor Bilder in einen Text eingefügt, werden die Bilder hochgeladen und mit ActiveStorage als Blob abgelegt. Dann wird der Blob über eine SGID in einem AttachmentTag referenziert.
Das sieht ungefähr so aus:

<actiontext-rich-text-attachment sgid="eyJfcmFpbHMiOnsiZGF0YSI6ImdpZDovL2RpZWdydWVuZXdlbHQtcmFpbHMvQWN0aXZlU3RvcmFnZTo6QmxvYi8xODk2ND9leHBpcmVzX2luIiwicHVyIjoiYXR0YWNoYWJsZSJ9fQ==--1e0193f625e7b8e7b1853e53acf246d57bc518c8" content-type="image/jpeg" url="http://localhost:3000/data/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTg5NjQsInB1ciI6ImJsb2JfaWQifX0=--b9e29dbd0fac4faf8e5b8280ec78383e07b0f654/image.jpg" filename="image.jpg" filesize="99197" width="800" height="536" presentation="gallery" caption="ImageCaption">
</actiontext-rich-text-attachment>

Beim Rendern der angehangenen Bilder besteht keine Möglichkeit herauszufinden, in welchem Kontext man gerade ist um z.B. ein anderes Partial beim Rendern zu nutzen.

Um also die gewünschte Funktionalität zu erreichen müssten beim Rendern bekannt sein, welchem Model der ActionText zugeordnet ist.

Kontext für das Bild

Das Attachment Attribute muss also zusätzlich eine Referenz enthalten, welchem Model der Richtext zugeordnet ist.
Das kann man über einen Initializer (in config/initializers/<deininitializer.rb>) machen:

 

# Attachment Tag braucht ein weiteres Attribute dafür welchem Model der Richtext zugeordnet ist
ActionText::Attachment::ATTRIBUTES << "attachment-parent"
ActionText::TrixAttachment::ATTRIBUTES << "attachmentParent"

Rails.application.config.to_prepare do
  ActiveSupport.on_load(:active_storage_attachment) do
    module ActionText
      class Attachment
        # Attachment Pfade vor dem Rendern setzen
        def to_attachable_partial_path
          attachment_parent_sgid = node_attributes["attachment_parent"]
          @attachment_parent = GlobalID::Locator.locate_signed(attachment_parent_sgid) || nil
          # if attachment_parent has attachment_partial_path then use it otherswise fallback
          @attachment_parent&.to_attachable_partial_path || @attachable.to_attachable_partial_path
        end
      end
    end
  end
end

# der Sanitizer darf die neuen Attribute nicht rausschmeißen 
Rails.application.config.after_initialize do
    sani = Rails::HTML5::Sanitizer.safe_list_sanitizer

    ActionText::ContentHelper.allowed_tags = sani.allowed_tags +
                                             Set.new(%w[ picture source figure figcaption ]) +
                                             Set.new([ ActionText::Attachment.tag_name ])

    ActionText::ContentHelper.allowed_attributes = sani.allowed_attributes +
                                                   Set.new(%w[ srcset media data-lightbox ]) +
                                                   ActionText::Attachment::ATTRIBUTES.to_set
end

In dem entsprechenden Model muss man nun noch Methoden hinzufügen die lediglich den Pfad zum Partial zurück geben

class MyModel < ApplicationRecord  
	include ActionText::Attachable   
	has_rich_text :myrichtext
	
	def to_attachable_partial_path
    	"mymodel/image"
	end
end

Im View (also app/views/mymodel/_image.html.erb) muss der Code wie folgt angepasst werden:

<figure class="article-img">
  <%= link_to blob,
              class: 'richtext-image',
              data: { lightbox: true }   do %>
    <%= richtext_img(blob) %>
  <% end %>
</figure>

Und wir brauchen noch einen kleinen Helper der uns zum Beispiel ein picture tag baut und darin verschiedene Bildgrößen ausgibt, die z.B. je nach Bildschirmgröße angepasst ausgegeben werden.

module ImageHelper

  def richtext_img(img, params = {})
    image_size = {
      default_desktop: [300, 300],
      default_medium_mobile: [280, 280],
      default_small_mobile: [250, 250]
    }
    if img.present?

      tag.picture do
        concat(tag(:source,
          srcset: url_for(img.representation(resize_to_limit: image_size[:default_desktop])),
          media: "(min-width: 600px)"))

        concat(tag(:source,
          srcset: url_for(img.representation(resize_to_limit: image_size[:default_medium_mobile])),
          media: "(min-width: 450px)"))

        concat image_tag(img.variant(resize_to_limit: image_size [:default_small_mobile]), title: img.caption)
      end

    else
      render_no_image
    end
  end
  def render_no_image
    tag.figure(class: "no-image") do
      image_tag("placeholder.svg", width: "228", height: "239") +
        tag.figcaption("Leider kein Bild vorhanden")
    end
  end  
end

Das wars auch schon. Die Bilder können jetzt recht flexibel ausgegeben werden.

Wenn dir der Artikel geholfen hat oder du Fragen hast oder einen Fehler gefunden hast, schreib es mir ruhig (Mail steht im Footer).