1 什么是 Action Text?
Action Text 简化了富文本内容的处理和显示。富文本内容是指包含格式化元素(如粗体、斜体、颜色和超链接)的文本,它提供了一种超越纯文本的视觉增强和结构化呈现。它允许我们创建富文本内容,将其存储在一个表中,然后将其附加到我们的任何模型中。
Action Text 包含一个名为 Trix 的 所见即所得 编辑器,它在 Web 应用程序中使用,为用户提供了一个友好的界面来创建和编辑富文本内容。它处理从提供丰富功能(如文本格式化、添加链接或引用、嵌入图像)到更多更多功能的一切。有关示例,请参见 Trix 编辑器网站。
Trix 编辑器生成的富文本内容保存在自己的 RichText 模型中,该模型可以与应用程序中的任何现有 Active Record 模型相关联。此外,任何嵌入的图像(或其他附件)都可以使用 Active Storage(作为依赖项添加)自动存储并与该 RichText 模型相关联。在渲染内容时,Action Text 会先对内容进行清理,然后将其安全地嵌入页面的 HTML 中。
大多数所见即所得编辑器都是围绕 HTML 的 contenteditable
和 execCommand
API 的包装器。这些 API 由微软设计,用于支持 Internet Explorer 5.5 中网页的实时编辑。它们最终被其他浏览器反向工程并复制。因此,这些 API 从未完全指定或记录,并且由于所见即所得 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_texts
、active_storage_blobs
、active_storage_attachments
、active_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_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 记录在序列化 body
属性中保存 Trix 编辑器产生的内容。它还保存了对嵌入文件的引用,这些文件是使用 Active Storage 存储的。然后,该记录与希望拥有富文本内容的 Active Record 模型相关联。关联是通过将 has_rich_text
类方法放在要添加富文本的模型中来完成的。
# app/models/article.rb
class Article < ApplicationRecord
has_rich_text :content
end
无需将 content
列添加到 Article 表中。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 上传的附件以及链接到已签名全局 ID 的附件。
6.1 Active Storage
在富文本编辑器中上传图像时,它使用 Action Text,而 Action Text 又使用 Active Storage。但是,Active Storage 有一些依赖项,这些依赖项不是 Rails 提供的。要使用内置的预览器,您必须安装这些库。
并非所有这些库都是必需的,它们取决于您在编辑器中预期的上传类型。用户在使用 Action Text 和 Active Storage 时遇到的一个常见错误是图像在编辑器中无法正确渲染。这通常是由于未安装 libvips
依赖项造成的。
6.1.1 附件直接上传 JavaScript 事件
事件名称 | 事件目标 | 事件数据 (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} |
直接上传已结束。 |
6.2 已签名全局 ID
除了通过 Active Storage 上传的附件之外,Action Text 还可以嵌入任何可以由 已签名全局 ID 解析的内容。
全局 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 自动包含支持。
上面的代码将返回我们的标识符以唯一标识一个模型实例。
接下来,考虑一些嵌入引用 User 实例的已签名全局 ID 的 <action-text-attachment>
元素的富文本内容
<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 实例将作为 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),该 API 需要一个单独的端点来上传文件。该端点将需要创建 ActiveStorage::Blob
并返回其 attachable_sgid
{
"attachable_sgid": "BAh7CEkiCG…"
}
此后,您可以使用 <action-text-attachment>
标签在您的前端代码中使用 attachable_sgid
并将其插入富文本内容中
<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.