Add configurable marks support - #3
Conversation
There was a problem hiding this comment.
Pull request overview
Adds per-editor configurability for inline marks (bold/italic/strikethrough/underline) via a new marks allow-list option, aligning with existing per-feature toggles (e.g. code, tables, highlight). Disabled marks are made inert across commands/hotkeys, Markdown shortcuts, toolbar UI, HTML import/sanitization, and Lexical clipboard paste paths.
Changes:
- Introduces
marksconfiguration (preset + element attribute) with default “all marks enabled”, plus parsing for JSON arrays and whitespace-separated strings. - Enforces disabled marks across the full pipeline: command interception, Markdown transformer filtering, HTML import conversion stripping + sanitizer allow-list, and TextNode transform to clear disabled format bits on Lexical clipboard paste.
- Adds browser/system test coverage, fixtures, CSS for toolbar hiding, and documentation updates.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/config/lexxy.js |
Adds default marks preset configuration. |
src/elements/editor.js |
Implements enabled/disabled marks parsing and enforcement across import/toolbar/attributes/markdown/clipboard paths. |
src/editor/marks.js |
Centralizes mark types, import tags, and Markdown transformer filtering helper. |
src/editor/command_dispatcher.js |
Adds high-priority interceptor to swallow FORMAT_TEXT_COMMAND for disabled marks. |
app/assets/stylesheets/lexxy-editor.css |
Hides disabled mark buttons via data-disabled-marks attribute selectors. |
docs/configuration.md |
Documents new marks option behavior and examples. |
test/browser/tests/formatting/marks_disabled.test.js |
Playwright coverage for UI/commands/markdown/import/lexical-clipboard behavior and parsing fallbacks. |
test/browser/fixtures/*.html |
Fixtures for limited/none/string/true/empty marks attribute cases. |
test/system/marks_disabled_test.rb |
System test for Action Text round-trip behavior with disabled marks. |
test/dummy/app/views/posts/_form.html.erb |
Plumbs marks query param into editor attribute for system testing. |
test/javascript/native/attributes_change.test.js |
Updates attributes-change expectations to include underline and per-mark enabled state. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| get enabledMarks() { | ||
| const configured = this.config.get("marks") | ||
|
|
||
| let list | ||
| if (Array.isArray(configured)) { | ||
| list = configured | ||
| } else if (typeof configured === "string" && configured.trim() !== "") { | ||
| list = configured.split(/\s+/) | ||
| } else { | ||
| return MARK_TYPES | ||
| } | ||
|
|
||
| return MARK_TYPES.filter((mark) => list.includes(mark)) | ||
| } | ||
|
|
||
| get disabledMarks() { | ||
| return MARK_TYPES.filter((mark) => !this.enabledMarks.includes(mark)) | ||
| } |
There was a problem hiding this comment.
Good catch. MARK_TYPES is now frozen at the module level so the fall-back return MARK_TYPES cannot be mutated by a host, and both enabledMarks and disabledMarks now return Object.freezed arrays, matching permittedAttachmentTypes. disabledMarks also computes enabledMarks once rather than per-iteration. Added a test asserting both getters return frozen arrays.
Make the inline marks (bold, italic, strikethrough, underline) configurable via Lexxy.configure or a `marks` element attribute, as an allow-list. The default enables all four, preserving current behavior. Accepts a JSON array (marks='["bold","italic"]') or a whitespace-separated string (marks="bold italic"); pass [] to disable every mark. A disabled mark is inert everywhere, not just hidden from the toolbar: its FORMAT_TEXT_COMMAND is swallowed by a high-priority interceptor (covering the toolbar button, programmatic dispatch, and the native Cmd+B/I/U shortcuts), its Markdown shortcut transformers are filtered out, its toolbar button is hidden via a data-disabled-marks attribute, and its semantic tags are dropped from the HTML import conversions so saved markup is reduced to plain text on load and paste. A TextNode transform additionally clears any disabled-mark format bit restored from pasted Lexical clipboard data.
c2e49b4 to
2ebff46
Compare
Adds a
marksoption to enable/disable the inline marks (bold, italic, strikethrough, underline) individually, viaLexxy.configureor amarkselement attribute. Part of the per-feature configurability effort (see basecamp#1097); follows the same default-preserving pattern ascode,tables, andhighlight.What
marksis an allow-list. Default["bold", "italic", "strikethrough", "underline"], so behavior is unchanged. Configurable via preset or attribute:Pass
[]to disable every mark. A non-list value (a boolean, or an empty/bare attribute) falls back to all marks enabled, so a misconfiguration never silently disables everything.How a disabled mark is disabled
Not just hidden from the toolbar — inert on every path:
FORMAT_TEXT_COMMANDinterceptor swallows the format, covering the toolbar button, programmatic dispatch, and the native Cmd+B/I/U shortcuts.***is dropped when either bold or italic is off); underline has no transformer.data-disabled-marksattribute + CSS, mirroring the existingdata-attachmentsmechanism.setValue/ paste (and excluded from the sanitizer allow-list).TextNodetransform clears any disabled-mark format bit restored from serialized nodes, which bypass HTML import.Testing
test/browser/tests/formatting/marks_disabled.test.js): toolbar visibility, command/hotkey inertness, Markdown shortcuts staying literal, import stripping, Lexical-clipboard paste, and the whitespace-string / fail-open (marks="true",marks="") parsing. Default behavior covered by a regression baseline.test/system/marks_disabled_test.rb): Action Text round-trip — a saved post's disabled marks are stripped when loaded into a limited editor and stay stripped through save / render / re-edit.docs/configuration.md).Known trade-offs
data-disabled-markstoolbar hiding applies to the default toolbar only — external/inline toolbars are the host's responsibility, same asdata-attachments.