Обзор Action Text

Это руководство предоставляет вам все, что нужно, чтобы начать обрабатывать содержимое обогащенного текста.

После прочтения этого руководства, вы узнаете:

  • Что такое Action Text, как его установить и настроить.
  • Как создать, отрендерить, стилизовать и доработать содержимое обогащенного текста.
  • Как обработать вложения.

1. Что такое Action Text?

Action Text упрощает обработку и отображение содержимого обогащенного текста. Под содержимым обогащенного текста понимается текст, который включает элементы форматирования, такие как жирный шрифт, курсив, цвета и гиперссылки, обеспечивая визуально улучшенное и структурированное представление по сравнению с простым текстом. Он позволяет нам создавать содержимое обогащенного текста, сохранять его в таблице, а затем прикреплять к любым моделям.

Action Text включает в себя редактор WYSIWYG под названием Trix, который используется в веб-приложениях для предоставления пользователям удобного интерфейса для создания и редактирования содержимого обогащенного текста. Он позволяет форматировать текст, добавлять ссылки или цитаты, вставлять изображения и многое другое. Пример работы редактора Trix можно посмотреть здесь.

Содержимое обогащенного текста, созданное редактором Trix, сохраняется в отдельной модели RichText, которую можно связать с любой существующей моделью Active Record в приложении. Кроме того, любые встроенные изображения (или другие вложения) могут быть автоматически сохранены с помощью Active Storage (которое добавляется как зависимость) и связаны с этой моделью RichText. Когда приходит время отображать содержимое, Action Text обрабатывает его, сначала очищая, чтобы его можно было безопасно встроить непосредственно в HTML страницы.

Большинство редакторов WYSIWYG представляют собой обертки вокруг API HTML contenteditable и execCommand. Эти API были разработаны Microsoft для поддержки редактирования веб-страниц в реальном времени в Internet Explorer 5.5. Впоследствии они были случайно обратно разработаны и скопированы другими браузерами. Следовательно, эти API никогда не были полностью специфицированы или документированы, а поскольку HTML редакторы WYSIWYG имеют огромный объем возможностей, в реализации для каждого браузера есть свой набор ошибок и странностей. Таким образом, JavaScript-разработчики часто оставлены наедине разбираться с этими неудобствами.

Trix обходит эти несоответствия, рассматривая contenteditable как устройство ввода-вывода: когда в редактор поступает ввод, Trix преобразует его в операцию редактирования внутренней модели документа, а затем повторно отображает этот документ в редакторе. Это дает Trix полный контроль над тем, что происходит после каждого нажатия клавиши, и позволяет избежать использования execCommand и связанных с ним несоответствий.

2. Установка

Чтобы установить Action Text и начать работать с содержимым обогащенного текста, запустите:

$ bin/rails action_text:install

Это выполнит следующие действия:

  • Установит JavaScript-пакеты для trix и @rails/actiontext и добавит их в application.js.
  • Добавит gem image_processing для анализа и преобразования встроенных изображений и других вложений с помощью Active Storage. За подробностями обратитесь к руководству Обзор Active Storage.
  • Создаст миграции для создания следующих таблиц, хранящих содержимое обогащенного текста и вложения: action_text_rich_texts, active_storage_blobs active_storage_attachments, active_storage_variant_records.
  • Создаст actiontext.css и импортирует его в application.css. Таблица стилей Trix также будет включена в файл application.css.
  • Добавит стандартные партиалы _content.html и _blob.html для отображения содержимого Action Text и вложений Active Storage (также называемых blob) соответственно.

После этого, запуск миграций добавит в ваше приложение новые таблицы action_text_* и active_storage_*.

$ bin/rails db:migrate

Когда установка Action Text создает таблицу action_text_rich_texts, она использует полиморфную связь, позволяющую нескольким моделям добавлять атрибуты обогащенного текста. Это реализуется через столбцы record_type и record_id, которые хранят соответственно имя класса модели и ID записи.

С использованием полиморфных связей модель может принадлежать более чем одной модели на единственной связи. Подробности смотрите в руководстве по связям Active Record.

Следовательно, если ваши модели, с содержимым Action Text, используют значения UUID в качестве идентификаторов, то все модели, использующие атрибуты Action Text, должны использовать значения UUID для своих уникальных идентификаторов. Сгенерированную миграцию для Action Text также необходимо обновить, указав type: :uuid в строчке ссылки на запись.

t.references :record, null: false, polymorphic: true, index: false, type: :uuid

3. Создание содержимого обогащенного текста

В этом разделе мы рассмотрим некоторые конфигурации, необходимые для создания обогащенного текста.

Запись RichText хранит содержимое, созданное редактором Trix, в сериализованном атрибуте body. Она также содержит все ссылки на встроенные файлы, которые хранятся с помощью Active Storage. Эта запись затем связывается с моделью Active Record, в которой желаете иметь содержимое обогащенного текста. Связь устанавливается путем размещения метода класса has_rich_text в модели, к которой вы хотите добавить обогащенный текст.

# app/models/article.rb
class Article < ApplicationRecord
  has_rich_text :content
end

Вам не нужно добавлять столбец content в таблицу Article. has_rich_text связывает content с таблицей action_text_rich_texts, которая уже была создана, и устанавливает связь с вашей моделью. Вы также можете выбрать именем атрибута что-то иное, чем content.

После добавления метода класса has_rich_text в модель можно обновить вью, чтобы использовать редактор обогащенного текста (Trix) для этого поля. Для этого используйте rich_text_area для поля формы.

<%# app/views/articles/_form.html.erb %>
<%= form_with model: article do |form| %>
  <div class="field">
    <%= form.label :content %>
    <%= form.rich_text_area :content %>
  </div>
<% end %>

Это отобразит редактор Trix, который предоставляет функции для создания и обновления вашего обогащенного текста. Позже мы подробно рассмотрим, как обновить стили для редактора.

Наконец, чтобы гарантировать возможность принимать обновления от редактора, вам необходимо разрешить использование ссылаемого атрибута в качестве параметра в соответствующем контроллере:

class ArticlesController < ApplicationController
  def create
    article = Article.create! params.require(:article).permit(:title, :content)
    redirect_to article
  end
end

Если вдруг вам нужно переименовать классы, использующие has_rich_text, вам также необходимо обновить столбец полиморфного типа record_type в таблице action_text_rich_texts для соответствующих строк.

Поскольку Action Text зависит от полиморфных связей, которые, в свою очередь, подразумевают хранение названий классов в базе данных, крайне важно синхронизировать эти данные с названиями классов, используемыми в вашем Ruby-коде. Эта синхронизация необходима для обеспечения соответствия между хранимыми данными и ссылками на классы в вашем коде.

4. Отображение содержимого обогащенного текста

Экземпляры ActionText::RichText можно напрямую встраивать на страницу, поскольку их содержимое уже очищено для безопасного отображения. Вы можете отобразить содержимое следующим образом:

<%= @article.content %>

ActionText::RichText#to_s безопасно преобразует RichText в HTML-строку. С другой стороны, ActionText::RichText#to_plain_text возвращает строку, не являющуюся безопасной для HTML и которую не следует отображать в браузерах. Подробнее о процессе очистки Action Text можно узнать в документации для класса ActionText::RichText.

Если в поле content есть прикрепленный ресурс, он может отображаться неправильно, если у вас не установлены необходимые зависимости для Active Storage.

5. Настройка редактора содержимого обогащенного текста (Trix)

Возможно, вам захочется изменить внешний вид редактора, чтобы он соответствовал вашим стилистическим требованиям, в этом разделе рассказывается, как это сделать.

5.1. Удаление или добавление стилей Trix

По умолчанию Action Text отобразит содержимое обогащенного текста внутри элемента с классом .trix-content. Это установлено в app/views/layouts/action_text/contents/_content.html.erb. Элементы с этим классом затем стилизуются с помощью таблицы стилей.

Если вы хотите изменить любые стили Trix, вы можете добавить свои собственные стили в файл app/assets/stylesheets/actiontext.css.

Однако, если вы хотите использовать свои собственные стили или стороннюю библиотеку вместо стандартных стилей trix, вы можете отключить trix в директивах препроцессора в файле app/assets/stylesheets/actiontext.css, удалив следующее:

= require trix

5.2. Настройка контейнера редактора

Чтобы настроить элемент HTML-контейнера, отображаемый вокруг содержимого обогащенного текста, отредактируйте файл макета app/views/layouts/action_text/contents/_content.html.erb, созданный установщиком:

<%# app/views/layouts/action_text/contents/_content.html.erb %>
<div class="trix-content">
  <%= yield %>
</div>

5.3. Настройка HTML для встроенных изображений и вложений

Чтобы изменить HTML, отображаемый для встроенных изображений и других вложений (известных как бинарные объекты), отредактируйте шаблон app/views/active_storage/blobs/_blob.html.erb, созданный установщиком:

<%# app/views/active_storage/blobs/_blob.html.erb %>
<figure class="attachment attachment--<%= blob.representable? ? "preview" : "file" %> attachment--<%= blob.filename.extension %>">
  <% if blob.representable? %>
    <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %>
  <% end %>

  <figcaption class="attachment__caption">
    <% if caption = blob.try(:caption) %>
      <%= caption %>
    <% else %>
      <span class="attachment__name"><%= blob.filename %></span>
      <span class="attachment__size"><%= number_to_human_size blob.byte_size %></span>
    <% end %>
  </figcaption>
</figure>

6. Вложения

В настоящее время Action Text поддерживает вложения, загруженные через Active Storage, а также вложения, связанные с Signed GlobalID.

6.1. Active Storage

Когда вы загружаете изображение через редактор обогащенного текста, он использует Action Text, который, в свою очередь, использует Active Storage. Однако, Active Storage имеет некоторые зависимости, которые не предоставляются Rails. Чтобы использовать встроенные функции предварительного просмотра, вам необходимо установить эти библиотеки.

Некоторые, но не все библиотеки обязательны, их необходимость зависит от того, какие типы файлов вы планируете загружать в редактор. Одной из распространенных ошибок, с которой сталкиваются пользователи при работе с Action Text и Active Storage, является неправильное отображение изображений в редакторе. Обычно это происходит из-за отсутствия зависимости libvips.

6.2. Signed GlobalID

В дополнение к вложениям, загруженным с помощью Active Storage, Action Text также может встроить все, что может быть найдено по Signed GlobalID.

Global ID - это универсальный URI для всего приложения, который уникально идентифицирует экземпляр модели: gid://YourApp/Some::Model/id. Это полезно, когда вам нужен единый идентификатор для ссылки на объекты разных классов.

При использовании этого метода Action Text требует, чтобы у вложений был подписанный глобальный идентификатор (sgid). По умолчанию, все модели Active Record в приложении Rails включают в себя поддержку GlobalID::Identification, поэтому их можно определить по подписанному глобальному идентификатору, что делает их совместимыми с ActionText::Attachable.

Action Text ссылается на HTML, который вы вставляете при сохранении, чтобы он мог позже перерисовать его с актуальным содержимым. Это позволяет ссылаться на модели и всегда отображать актуальное содержимое, даже если эти записи изменяются.

Action Text загрузит модель по глобальному идентификатору и затем отобразит ее с использованием пути по умолчанию, когда вы отрисовываете содержимое.

Пример вложения Action Text:

<action-text-attachment sgid="BAh7CEkiCG…"></action-text-attachment>

Action Text отрисовывает вложенные элементы <action-text-attachment>, преобразовывая их атрибут sgid элемента в экземпляр. Будучи найденным, этот экземпляр передается в хелпер отрисовки. В результате HTML вкладывается как потомок элемента <action-text-attachment>.

Для отображения в качестве вложения в элементе <action-text-attachment> Action Text, мы должны включить модуль ActionText::Attachable, который реализует #to_sgid(**options) (доступный через GlobalID::Identification).

Вы также можете (опционально) объявить #to_attachable_partial_path для отображения пользовательского пути к партиалу и #to_missing_attachable_partial_path для обработки отсутствующих записей.

Вот пример:

class Person < ApplicationRecord
  include ActionText::Attachable
end

person = Person.create! name: "Javan"
html = %Q(<action-text-attachment sgid="#{person.attachable_sgid}"></action-text-attachment>)
content = ActionText::Content.new(html)
content.attachables # => [person]

6.3. Отображение вложения Action Text

Стандартный способ отображения элемента <action-text-attachment> осуществляется через партиал пути по умолчанию.

Для наглядности рассмотрим модель User:

# app/models/user.rb
class User < ApplicationRecord
  has_one_attached :avatar
end

user = User.find(1)
user.to_global_id.to_s #=> gid://MyRailsApp/User/1
user.to_signed_global_id.to_s #=> BAh7CEkiCG…

Мы можем использовать GlobalID::Identification для любой модели с методом класса .find(id). Поддержка автоматически включена в Active Record.

Приведённый выше код вернет наш идентификатор для уникальной идентификации экземпляра модели.

Затем, рассмотрим некоторое содержимое обогащенного текста, в которое вложен элемент <action-text-attachment>, ссылающийся на подписанный GlobalID экземпляра User:

<p>Hello, <action-text-attachment sgid="BAh7CEkiCG…"></action-text-attachment>.</p>

Action Text использует строку "BAh7CEkiCG…", чтобы найти экземпляр User. Затем он отрисовывает его с использованием пути партиала по умолчанию для отрисовки содержимого.

В данном случае пути партиала по умолчанию - партиал users/user.

<%# app/views/users/_user.html.erb %>
<span><%= image_tag user.avatar %> <%= user.name %></span>

Следовательно, результирующий HTML, отрисованный Action Text, будет выглядеть наподобие:

<p>Hello, <action-text-attachment sgid="BAh7CEkiCG…"><span><img src="..."> Jane Doe</span></action-text-attachment>.</p>

6.4. Отображение другого партиала для action-text-attachment

Чтобы отобразить другой партиал для вложения, определите User#to_attachable_partial_path:

class User < ApplicationRecord
  def to_attachable_partial_path
    "users/attachable"
  end
end

Затем объявите этот партиал. Экземпляр User будет доступен как локальная для партиала переменная user:

<%# app/views/users/_attachable.html.erb %>
<span><%= image_tag user.avatar %> <%= user.name %></span>

6.5. Отображение партиала для несуществующего экземпляра или отсутствующего action-text-attachment

Если Action Text не может найти экземпляр User (например, если запись была удалена), будет отображен резервный партиал.

Для отрисовки другого партиала для отсутствующих вложений, определите метод на уровне класса to_missing_attachable_partial_path:

class User < ApplicationRecord
  def self.to_missing_attachable_partial_path
    "users/missing_attachable"
  end
end

Затем объявите этот партиал.

<%# app/views/users/missing_attachable.html.erb %>
<span>Deleted user</span>

6.6. Вложение через API

Если ваша архитектура не использует традиционную схему с серверным отображением Rails,то, возможно, вам понадобится отдельная точка доступа API (например, с использованием JSON) на стороне сервера для загрузки файлов. Эта точка доступа должна создавать объект ActiveStorage::Blob и возвращать его attachable_sgid.

{
  "attachable_sgid": "BAh7CEkiCG…"
}

Затем полученный attachable_sgid вы можете вставить в ваше содержимое обогащенного текста в коде фронтенда с помощью тега <action-text-attachment>:

<action-text-attachment sgid="BAh7CEkiCG…"></action-text-attachment>

7. Прочее

7.1. Избегание N+1 запроса

Если хотите предварительно загрузить зависимую модель ActionText::RichText, если допустить, что поле обогащенного текста названо content, можно использовать именованный скоуп:

Article.all.with_rich_text_content # Загружает тело без вложений.
Article.all.with_rich_text_content_and_embeds # Загружает тело и вложения.