Skip to content
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

Make Trix compatible with morphing #1227

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Make Trix compatible with morphing #1227

wants to merge 2 commits into from

Conversation

jorgemanrubia
Copy link
Member

Morphing the trix editor will leave it in an unusuable state because morphing will replace the bootstrapping elements without triggering the connection logic.

The solution is based on adding an attribute to flag the editor as initialized and detect when that attribute is modified.

See hotwired/turbo-rails#533 (comment)

Pending:

  • Tests

@jorgemanrubia
Copy link
Member Author

jorgemanrubia commented Mar 3, 2025

@seanpdoyle I would love your eyes on the approach 🙏

@jorgemanrubia jorgemanrubia marked this pull request as draft March 3, 2025 16:48
// Element callbacks

attributeChangedCallback(name, oldValue, newValue) {
if (name === "initialized" && oldValue && oldValue !== newValue) {
Copy link
Collaborator

@seanpdoyle seanpdoyle Mar 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens to an editor with the [initialized] attribute that is disconnected from the page, written to a cache (like Turbo's cache), then re-connected with the [initialized] attribute already encoded into the HTML?

Would the [initialized] attribute need to be removed in the custom element's disconnectedCallback()?

@@ -485,6 +497,8 @@ export default class TrixEditorElement extends HTMLElement {
}
this.editorController.registerSelectionManager()
this.#delegate.connectedCallback()

this.setAttribute("initialized", "true")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is to be treated as a boolean attribute, would Element.toggleAttribute with true as an argument be more appropriate?

Suggested change
this.setAttribute("initialized", "true")
this.toggleAttribute("initialized", true)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could even base the check simply on whether the attribute is present or not, and it could be blank.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tweaked it to use toogleAttribute and changed the check accordingly.

Comment on lines +512 to +513
this.disconnectedCallback()
this.connectedCallback()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this approach work with an existing <trix-toolbar> element? What about a <trix-editor> element with a [toolbar] attribute that references another element elsewhere in the page? Are the toolbars re-initialized as expected without attaching a duplicate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good points, I'll see how to make these cases work. You are right that it will lose bootstrapped toolbars. I'll make sure it works well with both scenarios (existing toolbar and bootstrapped).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some logic to (1) flag internal (bootstrapped) trix toolbars and (2) remove those when reconnecting. This works well in my test.s

@seanpdoyle
Copy link
Collaborator

I'd opened hotwired/turbo-rails#688 on the turbo-rails side to explore a different path toward support. I don't love it, and am skeptical that it's a complete solution. Having said that, sharing it here provides some additional context to the breadth of the problem.

@jorgemanrubia jorgemanrubia force-pushed the morphing branch 7 times, most recently from 989bf70 to 6ffd337 Compare March 5, 2025 14:31
@jorgemanrubia jorgemanrubia marked this pull request as ready for review March 5, 2025 14:34
@jorgemanrubia
Copy link
Member Author

I'd opened hotwired/turbo-rails#688 on the turbo-rails side to explore a different path toward support. I don't love it, and am skeptical that it's a complete solution. Having said that, sharing it here provides some additional context to the breadth of the problem.

Thanks for the exploration @seanpdoyle. I prefer trying to fix this as the Trix level and keep the turbo Rails gem ignorant about this, as much as possible.

@jorgemanrubia jorgemanrubia force-pushed the morphing branch 7 times, most recently from 6c6b54d to 98b4d0c Compare March 6, 2025 16:16
@@ -494,6 +507,18 @@ export default class TrixEditorElement extends HTMLElement {
this.#delegate.disconnectedCallback()
}

async reconnect() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the significance of defining this function as asynchronous?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it still function if omitted?

Suggested change
async reconnect() {
reconnect() {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, good catch. That's a remaining bit from some earlier explorations 👍

@@ -410,6 +412,7 @@ export default class TrixEditorElement extends HTMLElement {
} else if (this.parentNode) {
const toolbarId = `trix-toolbar-${this.trixId}`
this.setAttribute("toolbar", toolbarId)
this.toggleAttribute("internal-toolbar", true)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line toggles the [internal-toolbar] attribute on the the <trix-editor> (since this references that element).

However, the removeInternalToolbar() function defined below checks this.toolbarElement (the <trix-toolbar> if the <trix-editor> element is using an "internal" toolbar).

Should this line set the attribute on the toolbar, or should the check be switched from this.toolbarElement to this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Furthermore, is it important that the attribute be set? Could this same kind of internal state tracking be achieved by a JavaScript object property that isn't represented in the DOM?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh I think I messed that up when switching it to use toogleAttribute. I'll also make sure the tests catch this. Thanks 👍

Morphing the trix editor will leave it in an unusuable state because
morphing will replace the bootstrapping elements without triggering
the connection logic.

The solution is based on adding an attribute to flag the editor as
initialized and detect when that attribute is modified.

See hotwired/turbo-rails#533 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants