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).