-
Notifications
You must be signed in to change notification settings - Fork 22k
Extract ActionText::Editor base class and ActionText::TrixEditor adapter
#51238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
This change aims to both introduce the If that's too ambitious, risky, or the code review diff is too noisy, I'm happy to open a separate PR to deprecate the methods, then rebase this branch off that PR. |
ActionText::Editor to support WYSIWYG EditorsActionText::Editor to support WYSIWYG Editors
45ed3fb to
9c32f81
Compare
What would be a use-case for having this kind of configuration for ActionText? I can understand the case for ActiveStorage having multiple different "services" (mirroring, different regions, public vs private files, etc) but struggling to think of a case where a single Rails app would use multiple different text editor libraries. In my own codebase we have different "instances" of trix (for things like removing some formatting, changing styles, etc) but those are configured at the view-layer or with options passed to the underlying library. |
I think one potential use case is migration from one editor to another. For example, this diff's migration would backfill existing records to set For some period of time, the application could serve assets for both editors and use the For some editors, there might not be any significant difference. Since the WYSIWYG landscape is vast, supporting an open-ended migration felt safest.
This is another case I was imagining. Some consumer-facing pages might require real-time collaboration features, while other pages (back of house administrative pages, for example) might have different needs. While I agree with your point that using a consistent tool across the entire application would be the simplest approach, the spirit of this proposal is to provide flexibility when that isn't possible (for a variety of reasons, both in and out of teams' control). |
|
My gut reaction was that I was expecting this to be configured/designed more like |
This is an angle I hadn't considered. I think supporting has_rich_text :body # editor defaults to `ActionText::RichText.editor`
has_rich_text :body, editor: :trix
has_rich_text :body, editor: ActionText::RichText.editors[:trix]
has_rich_text :body, editor: ->(rich_text) { rich_text.method_to_determine_editor_during_migration } |
|
We migrated from Trix to TipTap over the course of 2023. Our app pre-dated ActionText but this type of PR could help other apps in a similar situation to ours:
|
|
@dhh shedding some light on this PR since you've mentioned working on a possible Markdown new editor for Rails and you were open to this idea at first. |
9c32f81 to
d9621a9
Compare
ActionText::Editor to support WYSIWYG EditorsActionText::TrixEditor
jorgemanrubia
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks amazing @seanpdoyle 👏
af66fc2 to
0c1332c
Compare
0c1332c to
0a89f39
Compare
| options[:data][:direct_upload_url] ||= main_app.rails_direct_uploads_url | ||
| options[:data][:blob_url_template] ||= main_app.rails_service_blob_url(":signed_id", ":filename") | ||
|
|
||
| editor_tag = content_tag("trix-editor", "", options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would leave this out of this PR to keep the size under control, but some thoughts about multi-editor support.
To support using multiple editors in the same app, we should be able to create thread-safe contexts where you can configure the editor. So that here, in the helpers you use in your app, we could support an editor: option that let you opt out the default. With that option, you could create contexts where ActionText::RichText.editor is resolved differently. So, some API like:
ActionText.with_editor :trix do
# ...
end
ActionText.with_editor :lexxy do
# ...
endThat way, within the helpers, we could set the right "editor context" to interpret the editor param. To do this, we would need to make the editor variable a thread one.
We need this because through the code we rely on ActionText::RichText.editor resolving to the configured editor, which is handy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this involve an implementation that leverages Object#with like:
module ActionText
singleton_class.delegate :editor, :editor=, to: "ActionText::RichText"
def self.with_editor(editor_name)
with editor: ActionText::RichText.editors.fetch(editor_name) do
yield
end
end
endI think the singleton_class.delegate bit is necessary since ActionText::RichText is an Active Record model, and Active Record overrides Object#with with ActiveRecord::QueryMethods.with for common-table expression support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jorgemanrubia I'd declared the RichText.editors and RichText.editor class attributes because I thought they read as self-descriptive (since they read aloud as "rich text editors" and "rich text editor").
Other than hooking into an ActiveSupport.on_load(:action_text_rich_text) { … } block in the engine, there isn't much benefit to declaring the attributes on RichText, rather than ActionText itself.
Is that decision worth reconsidering before merging?
0a89f39 to
c8f75e9
Compare
c8f75e9 to
28f148c
Compare
faa5f60 to
05c6e36
Compare
|
Hey @seanpdoyle some proposed changes here. It reverts the last WIP commit and introduce the proposed changes in another commit:
module ActionText
class Editor
def as_canonical(editable_content)
editable_content
end
def as_editable(canonical_content)
canonical_content
end
end
endThe tests passed for me in my branch. There are two tests I deleted when reverting the WIP, but I guess we want to keep those. Could you get your branch up to date with these changes? I will be happy to finally merge this. Thanks so much 🙏🙏. |
|
@jorgemanrubia I've pushed 8582d55 to restore the test coverage and to propose one final adjustment to the Editor interface. |
|
This current changeset combines two units of work:
@jorgemanrubia now that the Editor interface feels more settled, I wonder if it would be more productive to split out the deprecation unit ( We can keep this PR intact to serve as the pluggable editor unit of work ( What do you think? |
jorgemanrubia
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@seanpdoyle this looks great 👍👍. I think in a single unit is fine.
Thanks for the fantastic work here.
7615c1f to
9778d1d
Compare
ActionText::TrixEditorActionText::Editor base class and ActionText::TrixEditor adapter
Related to [rails/actiontext#41](rails/actiontext#41) The introduction of the `ActionText::TrixEditor` class enables the deprecation of a variety of class- and instance-level methods across the `ActionText` namespace. Action Text benefits from deprecating methods in order to reduce its public API. There are two categories of deprecations in this changeset: `module`/`class` deprecations and method deprecations. The `module` and `class` deprecations target modules that are internal and "private" in intent, but public in Ruby and not marked as `:nodoc:`. They're either particular to Trix, or their responsibilities have been given to `ActionText::Editor`. The method deprecations also target methods that are internal and "private" in intent, but `public` in Ruby and not marked as `:nodoc:`. In general, determining whether or not to deprecate a method hinged on whether its name or arguments mentioned `"trix"`, *or* if it lacked method-level documentation or comments of any kind. When possible, the previous code paths have been deprecated *and* re-implemented in terms of an `ActionText::TrixEditor` instance. Additions --- The main aim of this changeset is to provide a single, extensible entrypoint for third-party editors (that are not Trix) to integrate with Action Text. The initial responsibilities of the `ActionText::TrixEditor` have been determined by consolidating a variety of (class- and instance-) methods across a variety of classes to reduce the number of touchpoints for applications and engines that provide third-party editor integrations. The vast majority of its method definitions are direct copy-and-pastes from their original sources. Any method that previously mentioned `trix` in its name or argument list has replaced and generalized that occurrence with `editor`. Classes that inherit from `ActionText::Editor` can override two methods that accept and return `Fragment` instances: * `Editor#as_canonical` method accepts an editor-sourced `ActionText::Fragment` instance, transforms it, then returns an `ActionText::Fragment` instance to be stored. * `Editor#as_editable` method accepts a storage-sourced `ActionText::Fragment` instance, transforms it, then returns an `ActionText::Fragment` instance to be edited. The interface utilizes `Fragment` instances rather than `Content` or String instances. The `Fragment` class serves as an agnostic layer on top of HTML and Plain Text string manipulation through Nokogiri. It has a simple interface of transforms: * `find_all(selector)` * `update(&block)` * `replace(selector, &block)` Accepting **and** returning a transformation-ready `Fragment` enables a more consistent adapter interface. Changes --- *These changes aim to be considered implementation details, and intend to remain "private" from an API perspective.* Future commits can be made to expand upon and document the responsibilities of the adapter's interface.
9778d1d to
27fe49a
Compare
Related to rails/actiontext#41
The introduction of the
ActionText::TrixEditorclass enables thedeprecation of a variety of class- and instance-level methods across the
ActionTextnamespace. Action Text benefits from deprecating methods inorder to reduce its public API.
There are two categories of deprecations in this changeset:
module/classdeprecations and method deprecations.The
moduleandclassdeprecations target modules that are internaland "private" in intent, but public in Ruby and not marked as
:nodoc:.They're either particular to Trix, or their responsibilities have been
given to
ActionText::Editor.The method deprecations also target methods that are internal and
"private" in intent, but
publicin Ruby and not marked as:nodoc:.In general, determining whether or not to deprecate a method hinged on
whether its name or arguments mentioned
"trix", or if it lackedmethod-level documentation or comments of any kind.
When possible, the previous code paths have been deprecated and
re-implemented in terms of an
ActionText::TrixEditorinstance.Additions
The main aim of this changeset is to provide a single, extensible
entrypoint for third-party editors (that are not Trix) to integrate with
Action Text.
The initial responsibilities of the
ActionText::TrixEditorhave beendetermined by consolidating a variety of (class- and instance-) methods
across a variety of classes to reduce the number of touchpoints for
applications and engines that provide third-party editor integrations.
The vast majority of its method definitions are direct copy-and-pastes
from their original sources. Any method that previously mentioned
trixin its name or argument list has replaced and generalized that
occurrence with
editor.Changes
These changes aim to be considered implementation details, and intend
to remain "private" from an API perspective. Future commits can be made
to expand upon and document the responsibilities of the adapter's
interface.
Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]