更多内容请访问 rubyonrails.org:

1. 什么是 Action Text?

Action Text 简化了富文本内容的处理和显示。富文本内容是指包含粗体、斜体、颜色和超链接等格式元素的文本,提供了超越纯文本的视觉增强和结构化呈现。它允许我们创建富文本内容,将其存储在表中,然后将其附加到我们的任何模型。

Action Text 包含一个名为 Trix 的 WYSIWYG 编辑器,它在 Web 应用程序中用于为用户提供一个用户友好的界面来创建和编辑富文本内容。它处理从提供丰富功能(如文本格式化、添加链接或引用、嵌入图像等等)的一切。有关示例,请参阅 Trix 编辑器网站

Trix 编辑器生成的富文本内容保存在其自己的 RichText 模型中,该模型可以与应用程序中任何现有的 Active Record 模型关联。此外,任何嵌入的图像(或其他附件)都可以使用 Active Storage(作为依赖项添加)自动存储并与该 RichText 模型关联。当需要渲染内容时,Action Text 会首先对内容进行清理,以便可以安全地直接嵌入到页面的 HTML 中。

大多数 WYSIWYG 编辑器都是 HTML 的 contenteditableexecCommand API 的封装。这些 API 由微软设计,旨在支持 Internet Explorer 5.5 中网页的实时编辑。它们最终被其他浏览器逆向工程和复制。因此,这些 API 从未被完全规范或文档化,并且由于 WYSIWYG HTML 编辑器范围巨大,每个浏览器的实现都有其自身的一系列错误和怪癖。因此,JavaScript 开发者经常需要解决这些不一致性。

Trix 通过将 contenteditable 视为 I/O 设备来规避这些不一致性:当输入进入编辑器时,Trix 将该输入转换为其内部文档模型上的编辑操作,然后将该文档重新渲染回编辑器。这使得 Trix 能够完全控制每次按键后发生的事情,并避免使用 execCommand 及其带来的不一致性。

2. 安装

要安装 Action Text 并开始使用富文本内容,请运行

$ bin/rails action_text:install

它将执行以下操作:

  • 安装 trix@rails/actiontext 的 JavaScript 包,并将它们添加到 application.js
  • 添加 image_processing gem,用于分析和转换嵌入图像以及 Active Storage 的其他附件。有关更多信息,请参阅 Active Storage 概述指南。
  • 添加迁移,以创建存储富文本内容和附件的以下表:action_text_rich_textsactive_storage_blobsactive_storage_attachmentsactive_storage_variant_records
  • 创建 actiontext.css,其中包含所有 Trix 样式和覆盖。
  • 添加默认视图局部视图 _content.html_blob.html,分别用于渲染 Action Text 内容和 Active Storage 附件(即 blob)。

此后,执行迁移会将新的 action_text_*active_storage_* 表添加到您的应用程序中。

$ bin/rails db:migrate

当 Action Text 安装创建 action_text_rich_texts 表时,它使用多态关系,以便多个模型可以添加富文本属性。这是通过 record_typerecord_id 列完成的,它们分别存储模型的 ClassName 和记录的 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

无需向您的 Article 表添加 content 列。has_rich_text 将内容与已创建的 action_text_rich_texts 表关联,并将其链接回您的模型。您也可以选择将属性命名为不同于 content 的名称。

一旦您向模型添加了 has_rich_text 类方法,您就可以更新您的视图,以便该字段使用富文本编辑器 (Trix)。为此,请在该表单字段中使用 rich_textarea

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

这将显示一个 Trix 编辑器,它提供相应创建和更新富文本的功能。稍后我们将详细介绍 如何更新编辑器的样式

最后,为了确保您可以接受来自编辑器的更新,您需要在相关控制器中允许引用的属性作为参数

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

如果需要重命名使用 has_rich_text 的类,您还需要更新 action_text_rich_texts 表中相应行的多态类型列 record_type

由于 Action Text 依赖于多态关联,而多态关联又涉及在数据库中存储类名,因此将数据与 Ruby 代码中使用的类名保持同步至关重要。这种同步对于保持存储数据和代码库中类引用之间的一致性至关重要。

4. 渲染富文本内容

ActionText::RichText 的实例可以直接嵌入到页面中,因为它们的内容已经经过清理,可以安全地渲染。您可以按如下方式显示内容

<%= @article.content %>

ActionText::RichText#to_s 安全地将 RichText 转换为 HTML 字符串。另一方面,ActionText::RichText#to_plain_text 返回一个非 HTML 安全的字符串,不应在浏览器中不经额外清理而渲染。您可以在 ActionText::RichText 文档中了解有关 Action Text 清理过程的更多信息。

如果 content 字段中包含附加资源,则除非您安装了 Active Storage 的必要依赖项,否则它可能无法正确显示。

5. 自定义富文本内容编辑器 (Trix)

有时您可能希望更新编辑器的呈现方式以满足您的样式要求,本节将指导您如何操作。

5.1. 删除或添加 Trix 样式

默认情况下,Action Text 会在一个带有 .trix-content 类的元素内部渲染富文本内容。这在 app/views/layouts/action_text/contents/_content.html.erb 中设置。带有此类的元素然后由 trix 样式表进行样式化。

如果您想更新任何 trix 样式,您可以将自定义样式添加到 app/assets/stylesheets/actiontext.css,其中包含 Trix 的完整样式集和 Action Text 所需的覆盖。

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

要自定义嵌入图像和其他附件(称为 blob)渲染的 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 上传的附件以及链接到已签名 GlobalID 的附件。

6.1. Active Storage

在富文本编辑器中上传图像时,它使用 Action Text,而 Action Text 又使用 Active Storage。然而,Active Storage 具有一些 Rails 未提供的依赖项。要使用内置预览器,您必须安装这些库。

这些库中有一部分(但不是全部)是必需的,它们取决于您预期在编辑器中上传的类型。用户在使用 Action Text 和 Active Storage 时遇到的一个常见错误是图像在编辑器中无法正确渲染。这通常是由于未安装 libvips 依赖项造成的。

6.1.1. 附件直接上传 JavaScript 事件

Action Text 在文件附件生命周期期间分发 Active Storage 直接上传事件

除了典型的 event.detail 属性之外,Action Text 还分发带有 event.detail.attachment 属性的事件。

事件名称 事件目标 事件数据 (event.detail) 描述
direct-upload:start <input> {id, file} 直接上传正在开始。
direct-upload:progress <input> {id, file, progress} 存储文件的请求正在进行中。
direct-upload:error <input> {id, file, error} 发生错误。除非此事件被取消,否则将显示 alert
direct-upload:end <input> {id, file} 直接上传已结束。

通过 Active Storage 直接上传由 Action Text 上传的文件可能永远不会嵌入到富文本内容中。请考虑定期清除未附加的上传

6.2. 签名的 GlobalID

除了通过 Active Storage 上传的附件,Action Text 还可以嵌入任何可以通过 签名的 GlobalID 解析的内容。

Global ID 是一个应用程序范围的 URI,它唯一标识一个模型实例:gid://YourApp/Some::Model/id。当您需要一个单一标识符来引用不同类的对象时,这很有帮助。

使用此方法时,Action Text 要求附件具有签名的全局 ID(sgid)。默认情况下,Rails 应用程序中的所有 Active Record 模型都混入 GlobalID::Identification 模块,因此它们可以通过签名的全局 ID 解析,并且因此与 ActionText::Attachable 兼容。

Action Text 在保存时引用您插入的 HTML,以便以后可以使用最新内容重新渲染。这使得您可以引用模型并在这些记录更改时始终显示当前内容。

当您渲染内容时,Action Text 将从全局 ID 加载模型,然后使用默认局部路径渲染它。

Action Text 附件可能看起来像这样

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

Action Text 通过将其元素的 sgid 属性解析为实例来渲染嵌入的 <action-text-attachment> 元素。一旦解析,该实例将传递给渲染助手。结果,HTML 作为 <action-text-attachment> 元素的后代嵌入。

要作为附件在 Action Text <action-text-attachment> 元素中渲染,我们必须包含 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> 元素并引用 User 实例已签名 GlobalID 的富文本内容

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

因此,Action Text 渲染的最终 HTML 将看起来像这样

<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 实例将作为用户局部变量提供

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

6.5. 为未解析的实例或缺失的 action-text-attachment 渲染局部视图

如果 Action Text 无法解析 User 实例(例如,如果记录已被删除),则将渲染一个默认的 fallback 局部视图。

要渲染一个不同的缺失附件局部视图,请定义一个类级别的 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 # Preload the body without attachments.
Article.all.with_rich_text_content_and_embeds # Preload both body and attachments.


回到顶部