Skip to content

[WC-3647] Combine seven rich text fixes - #2495

Open
samuelreichert wants to merge 7 commits into
mainfrom
fix/rich-text-combined-fixes
Open

samuelreichert wants to merge 7 commits into
mainfrom
fix/rich-text-combined-fixes

Conversation

@samuelreichert

Copy link
Copy Markdown
Contributor

Pull request type

Bug fix (non-breaking change which fixes an issue)


Description

This PR combines seven Rich Text bug fixes into one branch, based on the latest main, so they can be reviewed, tested and released together. Each fix stays a separate commit.

Included PRs (this PR supersedes them):

PR Fix
#2454 Keep text color, font family and font size in class style mode
#2456 Run On change on leave only after an edit
#2458 Discard the typed value when pressing Escape in a configuration dropdown
#2459 Ignore trailing line breaks in the character count
#2460 Build a valid Google Maps embed URL
#2465 Apply the "Enable spell checking" setting
#2466 Apply the "Default font family" and "Default font size" settings

1. Class style mode loses text color, font family and size (#2454)

With Style data format = Class, these marks are saved as <span data-text-color|data-font-family|data-font-size class="has-…"> with no style attribute. Tiptap's TextStyle mark only parses a <span> that has a style attribute, so the formatting was dropped when the content was loaded again.

A new TextStyleClass extension extends TextStyle and, in class mode only, adds a parse rule for spans with these data attributes. Inline mode is unchanged.

2. On change runs on every blur (#2456)

With On change type = When user leaves input field, On change ran on every blur, even without an edit. A dirty flag is now set by a real edit (onUpdate) and cleared on blur. External value syncs use emitUpdate: false, so they never set it. On leave still runs on every blur.

Out of scope: clicking a toolbar button also blurs the editor, so editing and then clicking the toolbar still runs On change.

3. Escape saves the typed value in configuration dropdowns (#2458)

Escape called clearDraft() and then blur() straight away, so onBlur committed the draft before the state update applied. A ref set around the Escape-triggered blur now makes onBlur skip the commit.

4. Character count includes trailing line breaks (#2459)

getCharCount now returns text.trimEnd().length, as described in the 4.12.0 changelog. Word count and HTML character count are unchanged.

5. Invalid Google Maps embed URL (#2460)

The template in videoUrlPattern.ts used msid=$2 instead of $3 and ended with a stray ". It is now maps.google.com/maps/ms?msid=$3&output=embed.

The test-only commits of #2460 are already on main, so only the fix commit is included here.

6. "Enable spell checking" has no effect (#2465)

The setting was lost in the move from Quill to Tiptap. EditorWrapper now passes spellCheck to Editor, which sets spellcheck on the editable area, as 4.x did. role: "textbox" is set explicitly in the same attributes, because useEditor replaces them on re-render. Existing snapshots only gain spellcheck="true".

7. "Default font family" and "Default font size" have no effect (#2466)

These settings were also lost in the move to Tiptap. As in 4.x, they do not apply a font to the content. They set the initial value of the font family and font size toolbar dropdowns when the selection has no font of its own.

  • Font family matches an option's value or name, ignoring case. Custom fonts work.
  • Font size normalizes a bare number such as 14 to 14px.
  • A default that matches no option, including unsafe values, is ignored.
  • With no default configured, the dropdowns keep the current placeholders ("Font Family" / "14px").

Notes on combining

  • The CHANGELOG entries are merged into one ### Fixed list under [Unreleased].
  • The snapshot for "renders with both word and character count" was dropped from the spell-check commit, because that test no longer exists on main.
  • EditorWrapper passes both spellCheck and the default font props to Editor.

All fixes were written test-first. In rich-text-web: 41 test suites and 640 tests pass, tsc --noEmit is clean, and lint has 0 errors. The lint warnings are the same as on main.

What should be covered while testing?

  1. Class style mode: set Style data format to Class. Apply a text color, a font family and a font size, then save and reload the page. The formatting is still there and the saved HTML has no inline style. Repeat with Inline: nothing changed.
  2. On change on leave: configure On change (type: when user leaves input field) to show a message. Tab in and out without typing: no message. Type, then leave: the message appears once. Enter and leave again without typing: no message.
  3. Escape in a configuration dropdown: open a table configuration dropdown and type a new value in a number field. Press Escape: the value goes back and the table is unchanged. Type a value and press Enter: it is applied.
  4. Character count: enable the status bar with Character count. Type abc and press Enter twice. The count stays at 3.
  5. Google Maps embed: paste a https://maps.google.com/maps/ms?…&msid=<id> link in the video dialog. The embed URL is well-formed.
  6. Spell checking: set Enable spell checking to Yes and type a misspelled word: the browser underlines it. Set it to No: misspellings are not underlined.
  7. Default font: set Default font family (for example Arial) and Default font size (for example 18px). Place the cursor in unformatted text: the dropdowns show Arial and 18px, and the text does not change. Apply another font to some text and place the cursor there: the dropdowns show that font. Clear both settings: the dropdowns show "Font Family" and 14px.

🤖 Generated with Claude Code

samuelreichert and others added 7 commits October 28, 2026 10:20
…yle mode

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@samuelreichert
samuelreichert requested a review from a team as a code owner October 28, 2026 09:23
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

AI Code Review

⚠️ Approved with suggestions — low-severity items only, safe to merge


What was reviewed

File Change
packages/pluggableWidgets/rich-text-web/CHANGELOG.md Seven new [Unreleased] entries (one per fix)
src/extensions/TextStyleClass.ts New extension: adds parse rule for class-mode spans without style attribute
src/extensions/__tests__/classFormat.spec.ts Round-trip and safety tests for class-format text marks
src/components/Editor.tsx spellCheck → editorProps.attributes; isDirtyRef dirty flag for onLeave; defaultFontFamily/defaultFontSize threading
src/components/EditorWrapper.tsx Guards defaultFontFamily/defaultFontSize on .status === "available" before passing to Editor
src/components/StatusBar.tsx getCharCount now returns text.trimEnd().length
src/components/__tests__/StatusBar.spec.tsx Adds trailing-whitespace exclusion test
src/components/toolbars/Toolbar.tsx Threads defaultFontFamily/defaultFontSize into getFilteredToolbarGroups
src/components/toolbars/ToolbarConfig.ts getFilteredToolbarGroups accepts defaultFonts; enhancedToolbarGroups wires defaults into dropdowns
src/components/toolbars/components/ConfigurationDropdown.tsx skipCommitOnBlurRef to fix Escape-then-blur race; buffered draft inputs
src/components/toolbars/helpers/fontHelpers.ts New DefaultFonts, findDefaultOptionValue, AddDefaultFontToDropdown
src/utils/videoUrlPattern.ts Fixes Google Maps capture group ($2 → $3); removes stray "
src/utils/__tests__/videoUrlPattern.spec.ts Adds Google Maps embed URL test case
src/__tests__/RichText.runtime.spec.tsx Adds onLeave dirty-flag tests
src/__tests__/RichText.spellCheck.spec.tsx New spec: spell-check attribute propagation
src/__tests__/RichText.defaultFont.spec.tsx New spec: default font family/size dropdown behaviour
src/__tests__/KeyboardNavigation.spec.tsx Adds ConfigurationDropdown Enter/Escape suite
src/__tests__/__snapshots__/RichText.spec.tsx.snap Snapshot gains spellcheck="true" and role="textbox"

Skipped (out of scope): dist/, pnpm-lock.yaml


Findings

⚠️ Low — Misleading comment in StatusBar.tsx

File: packages/pluggableWidgets/rich-text-web/src/components/StatusBar.tsx lines 38–40
Note: The comment says "providing built-in debouncing" to describe useMemo. useMemo is a cache that skips recomputation when inputs are unchanged — it does not delay execution the way a debounce does. The word "debouncing" would mislead a future reader into thinking the count update is deliberately deferred.
Fix: Replace the two-line comment with a one-liner or drop it entirely:

const metricValue = useMemo(() => {
    // Recompute only when content or metricType changes.
    switch (metricType) {

Positives

  • isDirtyRef design is airtight: setting dirty only in onUpdate, clearing only in onBlur after executing the action, and using emitUpdate: false on the external-value sync path correctly excludes all non-user-edit paths from the onLeave trigger. The four test cases (no-edit blur, one-edit then two blurs, onDataChange counterpart, canExecute=false) provide solid coverage of the state machine.
  • Escape-blur race is fixed correctly: the skipCommitOnBlurRef is set, blur() is called (which fires onBlur synchronously), and then immediately reset — relying on the well-established synchronous semantics of blur() in both browsers and jsdom. The test directly verifies the racing case.
  • TextStyleClass compose pattern is clean: calling this.parent?.() first and appending the class-mode rule, combined with consuming: false and the getAttrs guard against style-bearing spans, gives correct priority without interfering with inline-mode documents.
  • Security coverage in findDefaultOptionValue: the configured default is matched against known dropdown options (value or label) and is never applied to the DOM directly; an injection string like "arial; color: red" yields undefined, so the dropdown falls back to its placeholder. The test table covers this case explicitly.
  • CHANGELOG entries are user-facing: every entry describes observable behaviour (e.g., "the character count in the status bar included trailing empty lines") with no implementation detail leaking through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant