Validate attachment href attributes using DOMPurify.isValidAttribute()
before rendering them as anchor tags.
ref: https://hackerone.com/reports/3455133
The changes proposed to
`action_text-trix/app/assets/javascripts/trix.js` were generated by
executing the following:
```sh
yarn build
```
To reduce the risk of future commits' outputs being excluded from the
commits that introduce them, this commit introduces some `git` commands
to the `.github/workflows/ci.yml` file to fail CI builds when `yarn
build` creates changes that are not already checked into the git commit.
The commands are lifted directly from the [hotwired/turbo-rails][]
version of this file.
[hotwired/turbo-rails]: https://github.com/hotwired/turbo-rails/blob/v2.0.20/.github/workflows/ci.yml#L48-L51
== What?
This commit introduces an `action_text-trix/test/dummy` directory
generated from `rails plugin new`. It serves as a Dummy Rails
application to consume the `action_text-trix` gem in its test suite.
== Why?
Prior to this commit, compliance with downstream gems like `rails/rails`
involved cloning `rails/rails` and execute *its* test suite. This
provides a tremendous amount of value to ensure that changes in this
repository don't have unintended downstream effects.
However, the gem itself does not have any gem-local test coverage. This
can be fairly limiting, since `rails/rails` is more likely to change at
a much slower pace than the gem.
Similarly, there are long-term goals to extract Trix's Action Text
compatibility out of `rails/rails` completely. With a test suite of its
own, the gem can start to roll those changes out in a
backwards-compatible way.
== How?
In addition to the `test/dummy` directory, this commit introduces
rudimentary browser-level coverage in a
`test/system/action_text_test.rb` file. It drives an Action
Text-compatible Trix instance to create rich text content for a
`Message` model.
In the future, this suite will serve as a testbed for new features, and
potentially will serve as the testing grounds for backwards
compatibility as Action Text becomes more editor-agnostic.
Extend the `PreviewableAttachmentView` to assign
[HTMLImageElement.alt][] based on the `Attachment` instance's `"alt"`
attribute.
This enables applications to set the [preview image's alt text][4.8.4.4]
while editing inside the `<trix-editor>` element. For example, an
application can modify a `ManagedAttachment` instance through a
`trix-attachment-add` event listener:
```js
addEventListener("trix-attachment-add", ({ attachment }) => {
attachment.setAttributes({ alt: `Attached file ${attachment.file.name}` })
})
```
[HTMLImageElement.alt]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/alt
[4.8.4.4]: https://html.spec.whatwg.org/multipage/images.html#alt
Revert [c4b9d5b][]
Requiring mouse events to trigger `<button>` element event listeners
poses accessibility issues for keyboard users.
The context in the original commit ([c4b9d5b][]) is fairly light, and
there doesn't appear to be a link to a GitHub pull request with more
information. The provided context is:
> Prevents flickering of the placeholder text when clicking block
> formatting buttons on an empty document.
Since the commit is from 2014, there is hope that the underlying flicker
might have been resolved through the natural course of browser
improvements and device enhancements.
The benefit of responding to keyboard events when the `<button>` element
has focus outweighs the potential downsides.
[c4b9d5b]: https://github.com/basecamp/trix/commit/c4b9d5b5f18bc41b6cedcc0ffbadf33db2ab240e
Related to [#1154][]
First, document the existing Attachment previewing process, including
_which_ content types are supported out of the box.
Next, resolve some `ManagedAttachment` to `Attachment` proxying issues.
The `ManagedAttachment` class is what gets dispatched as part of
`trix-attachment-add` events. It does not inherit from `Attachment`, but
instead proxies method calls and property access. Prior to this commit,
there were some proxy definition gaps.
For example, the `ManagedAttachment` [declares a proxy for the
`setAttribute` method][setAttribute]. Unfortunately, an
`Attachment.setAttribute` method did not exist prior to these changes.
This commit remedies that.
Next, this commit adds proxy definitions for `Attachment.setPreviewURL`
and `Attachment.getPreviewURL` so that event handlers can customize the
value from the event-level `ManagedAttachment` instance, without deeply
reaching into the `Attachment` instance (via
`event.attachment.attachment`).
[#1154]: https://github.com/basecamp/trix/issues/1154
[setAttribute]: https://github.com/basecamp/trix/blob/5db0ea49180de97f27b0becf47440690a1eaa39c/src/trix/models/managed_attachment.js#L22
Define properties for accessing all `<trix-editor>` elements that
declare a relationship with a `<trix-toolbar>` element through a
`[toolbar]`-`[id]` attribute relationship.
Since multiple `<trix-editor>` elements can reference a `<trix-toolbar>`
element by name, this commit introduces both an `.editorElements` and
`.editorElement` properties.
The `.editorElement` property returns the first item in
`.editorElements`, if there are any.
Removes the JavaScript and CSS file `FileUtils.cp` calls from
`action_text-trix/Rakefile` so that the files can be generated by the
existing tooling (`rollup.config.js` for JavaScript, `bin/sass-build`
for CSS).
With this change, the project is free to continue to utilize those tools
to manage the files that will be included in the Gem, without needing to
span the Ruby-NodeJS boundary.
Prior to this commit, Trix editors used an associated input element to
initially populate their content.
This commit proposes to extend the behavior so that when an associated
input element is absent, Trix will safely sanitize then load any HTML
content inside a `<trix-editor>…</trix-editor>` tag.
```html
<form …>
<trix-editor>Editor content goes here</trix-editor>
/form>
```
When a `<trix-editor>` element initially connects with both HTML content
*and* an associated input element, Trix will *always* disregard the HTML
content and +> load its initial content from the associated input
element.
Additional considerations
---
This should not break Action Text compatibility, since Rails renders
`<trix-editor>` elements through the [rich_textarea_tag][], which
renders initial content into an `<input type="hidden">`. This change
enables the migration toward rendering content directly *into* the
`<trix-editor>` element as HTML content.
[rich_textarea_tag]: https://edgeapi.rubyonrails.org/classes/ActionText/TagHelper.html#method-i-rich_textarea_tag
When changes are made to the `package.json` file, the CI cache is busted
and the file is re-evaluated by the containers. This causes
[CI failures][] like the following:
```
No VM guests are running outdated hypervisor (qemu) binaries on this host.
yarn install v1.22.22
[1/4] Resolving packages...
[2/4] Fetching packages...
error @eslint/js@9.24.0: The engine "node" is incompatible with this module. Expected version "^18.18.0 || ^20.9.0 || >=21.1.0". Got "16.20.2"
error Found incompatible module.
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
```
To resolve this issue, change the `.node-version` file to specify
`18.18.0` (the minimum version mentioned explicitly by the failure
output). There are more recent releases available, but this PR only aims
to resolve the current issues.
[CI failure]: https://github.com/basecamp/trix/actions/runs/18040917809/job/51339260902?pr=1256#step:8:184
Prior to this change, Trix's rendering process was managed internally,
and was not open for extension or customization. While it's critical
that Trix manages its own content (through parsing HTML, scrubbing
attributes and sanitizing content, reifying attachments, etc.), the
"rendering" of that content amounts to the replacement of a collection
of DOM nodes.
Advanced use cases (like integration with "morph" style rendering) can
benefit from customizing the process of rendering Trix's content.
This commit introduces a `trix-before-render` event to with a `render`
property to configure an individual `<trix-editor>` element's rendering
process. By default, the event's `render` property maintains the
existing "replace" behavior. When overridden, the function expects two
arguments: a `<trix-editor>` element along with with a
[DocumentFragment][] instance.
```js
document.addEventListener("trix-before-render", (event) => {
const defaultRender = event.render
event.render = function(editorElement, documentFragment) {
// modify the documentFragment…
customize(documentFragment)
// render it with the default rendering function
defaultRender(editorElement, documentFragment)
}
})
```
[DocumentFragment]: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
The successful migration to utilize [Element Internals][] stopped short
of removing Trix's dependency on an associated `<input type="hidden">`
element to store its state. Prior to integration with
`ElementInternals`, Trix relied on the `<input>` for the sake of having
its value serialized into a form submission. That is no longer
necessary.
When Trix is configured to be compatible with `ElementInternals`, it is
also capable of functioning without an `<input type="hidden">` element.
To configure a `<trix-editor>` element to skip creating its `<input
type="hidden">`, set the element's `willCreateInput = false`:
```js
addEventListener("before-trix-initialize", (event) => {
const trixEditor = event.target
trixEditor.willCreateInput = false
})
```
Trix will *always* use an associated `<input type="hidden">` element
when the `[input]` attribute is set, regardless of its `willCreateInput`
property. To migrate to `<input>`-free support, render the
`<trix-editor>` without the `[input]` attribute.
In the absence of an `<input type="hidden">` element, the
`<trix-editor>` element's value will not be included in `<form>` element
submissions unless it is rendered with a `[name]` attribute. Set the
`[name]` attribute to the same value that the `<input type="hidden">`
element would have.
Additional considerations
---
Action Text integration should remain unchanged. At the time of this
submission, Rails automatically renders an `<input type="hidden">`
element that is paired with the `<trix-editor>` element through its
`[input]` attribute.
[Element Internals]: https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals