mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #40308 from seanpdoyle/action-text-rendering-guides
Improve ActionText extensiblibility
This commit is contained in:
commit
f58b7250ae
16 changed files with 179 additions and 21 deletions
|
@ -1,3 +1,11 @@
|
|||
* Expose how we render the HTML _surrounding_ rich text content as an
|
||||
extensible `layouts/action_view/contents/_content.html.erb` template to
|
||||
encourage user-land customizations, while retaining private API control over how
|
||||
the rich text itself is rendered by `action_text/contents/_content.html.erb`
|
||||
partial.
|
||||
|
||||
*Sean Doyle*
|
||||
|
||||
* Add `with_all_rich_text` method to eager load all rich text associations on a model at once.
|
||||
|
||||
*Matt Swanson*, *DHH*
|
||||
|
|
|
@ -22,17 +22,31 @@ module ActionText
|
|||
content.render_attachments do |attachment|
|
||||
unless attachment.in?(content.gallery_attachments)
|
||||
attachment.node.tap do |node|
|
||||
node.inner_html = render(attachment, in_gallery: false).chomp
|
||||
node.inner_html = render_action_text_attachment attachment, locals: { in_gallery: false }
|
||||
end
|
||||
end
|
||||
end.render_attachment_galleries do |attachment_gallery|
|
||||
render(layout: attachment_gallery, object: attachment_gallery) do
|
||||
attachment_gallery.attachments.map do |attachment|
|
||||
attachment.node.inner_html = render(attachment, in_gallery: true).chomp
|
||||
attachment.node.inner_html = render_action_text_attachment attachment, locals: { in_gallery: true }
|
||||
attachment.to_html
|
||||
end.join.html_safe
|
||||
end.chomp
|
||||
end
|
||||
end
|
||||
|
||||
def render_action_text_attachment(attachment, locals: {}) #:nodoc:
|
||||
options = { locals: locals, object: attachment, partial: attachment }
|
||||
|
||||
if attachment.respond_to?(:to_attachable_partial_path)
|
||||
options[:partial] = attachment.to_attachable_partial_path
|
||||
end
|
||||
|
||||
if attachment.respond_to?(:model_name)
|
||||
options[:as] = attachment.model_name.element
|
||||
end
|
||||
|
||||
render(**options).chomp
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
<div class="trix-content">
|
||||
<%= render_action_text_content(content) %>
|
||||
</div>
|
|
@ -0,0 +1 @@
|
|||
<%= render_action_text_content(content) %>
|
|
@ -0,0 +1,3 @@
|
|||
<div class="trix-content">
|
||||
<%= yield -%>
|
||||
</div>
|
|
@ -71,6 +71,10 @@ module ActionText
|
|||
to_partial_path
|
||||
end
|
||||
|
||||
def to_attachable_partial_path
|
||||
to_partial_path
|
||||
end
|
||||
|
||||
def to_rich_text_attributes(attributes = {})
|
||||
attributes.dup.tap do |attrs|
|
||||
attrs[:sgid] = attachable_sgid
|
||||
|
|
|
@ -84,7 +84,11 @@ module ActionText
|
|||
end
|
||||
|
||||
def to_rendered_html_with_layout
|
||||
render partial: "action_text/content/layout", formats: :html, locals: { content: self }
|
||||
render layout: "action_text/contents/content", partial: to_partial_path, formats: :html, locals: { content: self }
|
||||
end
|
||||
|
||||
def to_partial_path
|
||||
"action_text/contents/content"
|
||||
end
|
||||
|
||||
def to_s
|
||||
|
|
|
@ -47,6 +47,9 @@ module ActionText
|
|||
|
||||
copy_file "#{GEM_ROOT}/app/views/active_storage/blobs/_blob.html.erb",
|
||||
"app/views/active_storage/blobs/_blob.html.erb"
|
||||
|
||||
copy_file "#{GEM_ROOT}/app/views/layouts/action_text/contents/_content.html.erb",
|
||||
"app/views/layouts/action_text/contents/_content.html.erb"
|
||||
end
|
||||
|
||||
def create_migrations
|
||||
|
|
|
@ -4,4 +4,8 @@ class Person < ApplicationRecord
|
|||
def to_trix_content_attachment_partial_path
|
||||
"people/trix_content_attachment"
|
||||
end
|
||||
|
||||
def to_attachable_partial_path
|
||||
"people/attachable"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<span class="mentioned-person"><%= person.name %></span>
|
|
@ -1,3 +1,8 @@
|
|||
hello_alice_message_content:
|
||||
record: hello_alice (Message)
|
||||
name: content
|
||||
body: <p>Hello, <%= ActionText::FixtureSet.attachment("people", :alice) %></p>
|
||||
|
||||
hello_world_review_content:
|
||||
record: hello_world (Review)
|
||||
name: content
|
||||
|
|
3
actiontext/test/fixtures/messages.yml
vendored
3
actiontext/test/fixtures/messages.yml
vendored
|
@ -1,2 +1,5 @@
|
|||
hello_alice:
|
||||
subject: "A message to Alice"
|
||||
|
||||
hello_world:
|
||||
subject: "A greeting"
|
||||
|
|
2
actiontext/test/fixtures/people.yml
vendored
Normal file
2
actiontext/test/fixtures/people.yml
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
alice:
|
||||
name: "Alice"
|
|
@ -31,4 +31,12 @@ class ActionText::ControllerRenderTest < ActionDispatch::IntegrationTest
|
|||
get admin_message_path(message)
|
||||
assert_select "#content-html .trix-content .attachment--jpg"
|
||||
end
|
||||
|
||||
test "resolves ActionText::Attachable based on their to_attachable_partial_path" do
|
||||
alice = people(:alice)
|
||||
|
||||
get messages_path
|
||||
|
||||
assert_select ".mentioned-person", text: alice.name
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ After reading this guide, you will know:
|
|||
|
||||
* How to configure Action Text.
|
||||
* How to handle rich text content.
|
||||
* How to style rich text content.
|
||||
* How to style rich text content and attachments.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
@ -70,9 +70,9 @@ After the installation is complete, a Rails app using Webpacker should have the
|
|||
@import "./actiontext.scss";
|
||||
```
|
||||
|
||||
## Examples
|
||||
## Creating Rich Text content
|
||||
|
||||
Adding a rich text field to an existing model:
|
||||
Add a rich text field to an existing model:
|
||||
|
||||
```ruby
|
||||
# app/models/message.rb
|
||||
|
@ -81,7 +81,7 @@ class Message < ApplicationRecord
|
|||
end
|
||||
```
|
||||
|
||||
Note that you don't need to add a `content` field to your `messages` table.
|
||||
**Note:** you don't need to add a `content` field to your `messages` table.
|
||||
|
||||
Then refer to this field in the form for the model:
|
||||
|
||||
|
@ -112,6 +112,112 @@ class MessagesController < ApplicationController
|
|||
end
|
||||
```
|
||||
|
||||
## Rendering Rich Text content
|
||||
|
||||
Action Text will sanitize and render rich content on your behalf.
|
||||
|
||||
By default, the Action Text editor and content is styled by the Trix defaults.
|
||||
|
||||
If you want to change these defaults, remove the `// require "actiontext.scss"`
|
||||
line from your `application.scss` to omit the [contents of that
|
||||
file](https://raw.githubusercontent.com/basecamp/trix/master/dist/trix.css).
|
||||
|
||||
By default, Action Text will render rich text content into an element that
|
||||
declares the `.trix-content` class:
|
||||
|
||||
```html+erb
|
||||
<%# app/views/layouts/action_text/contents/_content.html.erb %>
|
||||
<div class="trix-content">
|
||||
<%= yield %>
|
||||
</div>
|
||||
```
|
||||
|
||||
If you'd like to change the rich text's surrounding HTML with your own layout,
|
||||
declare your own `app/views/layouts/action_text/contents/_content.html.erb`
|
||||
template and call `yield` in place of the content.
|
||||
|
||||
You can also style the HTML used for embedded images and other attachments
|
||||
(known as blobs). On installation, Action Text will copy over a partial to
|
||||
`app/views/active_storage/blobs/_blob.html.erb`, which you can specialize.
|
||||
|
||||
### Rendering attachments
|
||||
|
||||
In addition to attachments uploaded through Active Storage, Action Text can
|
||||
embed anything that can be resolved by a [Signed
|
||||
GlobalID](https://github.com/rails/globalid#signed-global-ids).
|
||||
|
||||
Action Text renders embedded `<action-text-attachment>` elements by resolving
|
||||
their `sgid` attribute into an instance. Once resolved, that instance is passed
|
||||
along to
|
||||
[`render`](https://edgeapi.rubyonrails.org/classes/ActionView/Helpers/RenderingHelper.html#method-i-render).
|
||||
The resulting HTML is embedded as a descendant of the `<action-text-attachment>`
|
||||
element.
|
||||
|
||||
For example, consider a `User` model:
|
||||
|
||||
```ruby
|
||||
# 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…
|
||||
```
|
||||
|
||||
Next, consider some rich text content that embeds an `<action-text-attachment>`
|
||||
element that references the `User` instance's signed GlobalID:
|
||||
|
||||
```html
|
||||
<p>Hello, <action-text-attachment sgid="BAh7CEkiCG…"></action-text-content>.</p>
|
||||
```
|
||||
|
||||
Aciton Text resolves uses the "BAh7CEkiCG…" String to resolve the `User`
|
||||
instance. Next, consider the application's `users/user` partial:
|
||||
|
||||
```html+erb
|
||||
<%# app/views/users/_user.html.erb %>
|
||||
<span><%= image_tag user.avatar %> <%= user.name %></span>
|
||||
```
|
||||
|
||||
The resulting HTML rendered by Aciton Text would look something like:
|
||||
|
||||
```html
|
||||
<p>Hello, <action-text-attachment sgid="BAh7CEkiCG…"><span><img src="..."> Jane Doe</span></action-text-content>.</p>
|
||||
```
|
||||
|
||||
To render a different partial, define `User#to_attachable_partial_path`:
|
||||
|
||||
```ruby
|
||||
class User < ApplicationRecord
|
||||
def to_attachable_partial_path
|
||||
"users/attachable"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Then declare that partial. The `User` instance will be available as the `user`
|
||||
partial-local variable:
|
||||
|
||||
```html+erb
|
||||
<%# app/views/users/_attachable.html.erb %>
|
||||
<span><%= image_tag user.avatar %> <%= user.name %></span>
|
||||
```
|
||||
|
||||
To integrate with Aciton Text `<action-text-attachment>` element rendering, a
|
||||
class must:
|
||||
|
||||
* include the `ActionText::Attachable` module
|
||||
* implement `#to_sgid(**options)` (made available through the [`GlobalID::Identification` concern][global-id])
|
||||
* (optional) declare `#to_attachable_partial_path`
|
||||
|
||||
By default, all `ActiveRecord::Base` descendants mix-in
|
||||
[`GlobalID::Identification` concern][global-id], and are therefore
|
||||
`ActionText::Attachable` compatible.
|
||||
|
||||
[global-id]: https://github.com/rails/globalid#usage
|
||||
|
||||
## Avoid N+1 queries
|
||||
|
||||
If you wish to preload the dependent `ActionText::RichText` model, assuming your rich text field is named `content`, you can use the named scope:
|
||||
|
@ -121,17 +227,6 @@ Message.all.with_rich_text_content # Preload the body without attachments.
|
|||
Message.all.with_rich_text_content_and_embeds # Preload both body and attachments.
|
||||
```
|
||||
|
||||
## Custom styling
|
||||
|
||||
By default, the Action Text editor and content is styled by the Trix defaults.
|
||||
If you want to change these defaults, you'll want to remove
|
||||
the `app/assets/stylesheets/actiontext.scss` linker and base your stylings on
|
||||
the [contents of that file](https://raw.githubusercontent.com/basecamp/trix/master/dist/trix.css).
|
||||
|
||||
You can also style the HTML used for embedded images and other attachments (known as blobs).
|
||||
On installation, Action Text will copy over a partial to
|
||||
`app/views/active_storage/blobs/_blob.html.erb`, which you can specialize.
|
||||
|
||||
## API / Backend development
|
||||
|
||||
1. A backend API (for example, using JSON) needs a separate endpoint for uploading files that creates an `ActiveStorage::Blob` and returns its `attachable_sgid`:
|
||||
|
|
|
@ -60,6 +60,12 @@ class ActionText::Generators::InstallGeneratorTest < Rails::Generators::TestCase
|
|||
assert_file "app/views/active_storage/blobs/_blob.html.erb"
|
||||
end
|
||||
|
||||
test "creates Action Text content view layout" do
|
||||
run_generator_instance
|
||||
|
||||
assert_file "app/views/layouts/action_text/contents/_content.html.erb"
|
||||
end
|
||||
|
||||
test "creates migrations" do
|
||||
run_generator_instance
|
||||
|
||||
|
|
Loading…
Reference in a new issue