Skip to content

Add configurable marks support - #3

Open
shved270189 wants to merge 1 commit into
mainfrom
configurable-marks
Open

Add configurable marks support#3
shved270189 wants to merge 1 commit into
mainfrom
configurable-marks

Conversation

@shved270189

Copy link
Copy Markdown
Collaborator

Adds a marks option to enable/disable the inline marks (bold, italic, strikethrough, underline) individually, via Lexxy.configure or a marks element attribute. Part of the per-feature configurability effort (see basecamp#1097); follows the same default-preserving pattern as code, tables, and highlight.

What

marks is an allow-list. Default ["bold", "italic", "strikethrough", "underline"], so behavior is unchanged. Configurable via preset or attribute:

Lexxy.configure({ default: { marks: ["bold", "italic"] } })
<lexxy-editor marks='["bold", "italic"]'></lexxy-editor>
<lexxy-editor marks="bold italic"></lexxy-editor> <!-- whitespace-separated also accepted -->

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:

  • Commands / hotkeys: a high-priority FORMAT_TEXT_COMMAND interceptor swallows the format, covering the toolbar button, programmatic dispatch, and the native Cmd+B/I/U shortcuts.
  • Markdown: the mark's shortcut transformers are filtered out (a combined transformer like *** is dropped when either bold or italic is off); underline has no transformer.
  • Toolbar: the button is hidden via a data-disabled-marks attribute + CSS, mirroring the existing data-attachments mechanism.
  • Import: the mark's semantic tags are dropped from the HTML import conversions, so markup is reduced to plain text on load / setValue / paste (and excluded from the sanitizer allow-list).
  • Pasted Lexical clipboard data: a TextNode transform clears any disabled-mark format bit restored from serialized nodes, which bypass HTML import.

Testing

  • Playwright (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.
  • System (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 updated (docs/configuration.md).

Known trade-offs

  • Disabling a mark also drops co-located legacy Trix highlight color on the same element (inherent to the import-conversion strip; consistent with stripping now-disabled content on load).
  • The data-disabled-marks toolbar hiding applies to the default toolbar only — external/inline toolbars are the host's responsibility, same as data-attachments.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 marks configuration (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.

Comment thread src/elements/editor.js
Comment on lines +269 to +286
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))
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.
@shved270189
shved270189 marked this pull request as ready for review June 10, 2026 20:02
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.

2 participants