Skip to content

Notes: Support inline rich text (bold, italic, link, code) - #78242

Merged
adamsilverstein merged 141 commits into
trunkfrom
add/notes-rich-text
Jul 13, 2026
Merged

adamsilverstein merged 141 commits into
trunkfrom
add/notes-rich-text

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented May 13, 2026 •

Copy link
Copy Markdown
Member

What?

Adds a minimal, safe rich text input to Notes, replacing the plain textarea with the RichTextControl assembly from @wordpress/dataviews (introduced in #78471, now merged) - restricted to bold, italic, link, and code formatting. Keyboard shortcuts (⌘B / ⌘I / ⌘K) work; no visible toolbar. Surrounding text with backticks (`) adds code formatting.

Closes #73413.

Preps code for @ mentions - #79604

Test in Playground: https://playground.wordpress.net/?gutenberg-pr=78242

Built on #78471 (merged)

This PR builds on #78471, which landed a standalone rich text form field in two layers: a presentational ContentEditableControl shell in @wordpress/components (private API), and a RichTextControl assembly in @wordpress/dataviews that wires @wordpress/rich-text (useRichText, FormatEdit, keyboard-shortcut / input-event listeners) into that shell as the DataForm richtext control. This PR exposes the assembly through @wordpress/dataviews private APIs and makes the notes sidebar its second consumer.

Why a standalone control?

This input was originally prototyped against RichText from @wordpress/block-editor, then against an earlier RichTextControl exposed through the block-editor private API (#75275). Both coupled a tiny inline field to block-editor machinery it doesn't need - the selection store, the format-toolbar slot fill, block-attribute persistence - which leaked focus/selection bugs into the note form (see the workaround history on this branch).

The control that landed in #78471 wraps useRichText directly and exposes the small value / onChange surface a standalone field actually needs. Because it skips block-editor selection coupling, focusing the note input no longer shifts the canvas block selection, which let this PR delete the clientId-snapshot workaround the earlier stacking required.

Why?

Per discussion in #73413 with @jasmussen and @Mamaduka, Notes needed a lightweight inline rich-text input - useful for emphasizing labels, linking to references, and quoting identifiers - but without taking up sidebar space with a toolbar. This PR ships the smallest viable formatting set (bold/italic/link/code) so reviewers can express feedback more clearly without bloating the UI.

How?

@wordpress/dataviews - packages/dataviews/src/private-apis.ts (new)

  • Exposes the richtext control's RichTextControl assembly as a private API (via lock/unlock) so other core packages can render a standalone rich text form field without a DataForm.

Client - packages/editor/src/components/collab-sidebar/note-form.js

  • Renders RichTextControl (unlocked from @wordpress/dataviews) with allowedFormats = ['core/bold','core/italic','core/link','core/code'] and hideLabelFromVision for the accessible label.
  • The submit-disabled check strips HTML before measuring length, so an empty contenteditable (which may contain stray markup) still disables the button.
  • Submit is async and failure-aware: the note actions resolve with the saved record on success and undefined on failure (they surface their own error notice), so the draft is only cleared once the save actually succeeded - and only if the user hasn't typed more in the meantime.
  • ⌘+Enter / Ctrl+Enter submit and Escape cancel move up to the form element (the bubbled keyDown still reaches them); format-edit keyboard shortcuts (⌘B/⌘I/⌘K) are wired up inside the control.
  • Takes an opt-in focusOnMount prop: the new-note form passes it so the caret lands in the field immediately; the reply form omits it so selecting a thread doesn't pull focus away from thread keyboard navigation.

Add-note form - packages/editor/src/components/collab-sidebar/add-note.js

  • Keeps two blur guards needed by any contenteditable field: re-check ambiguous relatedTarget = null blurs on the next frame (rich-text re-renders briefly drop focus to the body while typing), and keep the form open while a format popover (e.g. the ⌘K link UI, which portals out of the form) holds focus.
  • No longer snapshots the block clientId: the decoupled control doesn't move the canvas selection, so AddNote reads the selected block directly like before.

Thread - packages/editor/src/components/collab-sidebar/note-thread.js

  • Mirrors the format-popover blur carve-out so the ⌘K link UI doesn't close an open thread, and targets the reply field via [role="textbox"] now that it's no longer a textarea.

Server - no changes needed. The default comment kses allowlist already permits the note formats (strong, em, a, code) for users without unfiltered_html, and users with unfiltered_html are trusted with comment HTML everywhere else in core, so note content goes through the standard comment filter chain unchanged.

Output - already renders HTML via RawHTML and the excerpt path already calls stripHTML, so no display-side changes were needed.

Testing instructions

Test in Playground: https://playground.wordpress.net/?gutenberg-pr=78242

  1. npm run wp-env start && npm start.
  2. Open a post with notes enabled, open the collab sidebar, start a new note.
  3. Type and apply formatting:
    • ⌘B for bold, ⌘I for italic, ⌘K for a link. For inline code, wrap text in backticks (`code`) or press ⌃⌥X (Ctrl+Option+X on Mac, Shift+Alt+X on Windows/Linux).
    • Submit. Confirm the rendered note shows formatting in the sidebar list and in the floating thread on the canvas.
  4. Reload - formatting persists (round-trips through REST and the database).
  5. Keyboard-only walkthrough: tab into the input, type, ⌘B, ⌘+Enter to submit, Escape to cancel a reply.

Automated tests

  • JS unit: npm run test:unit -- packages/editor/src/components/collab-sidebar and npm run test:unit -- packages/dataviews/src/components/dataform-controls (the control's own tests landed with DataViews: Add a richtext control backed by a private RichTextControl shell in @wordpress/components #78471 and cover the format/shortcut/input-rule behavior this PR relies on).
  • E2E: npm run test:e2e -- test/e2e/specs/editor/various/block-notes.spec.js and test/e2e/specs/editor/collaboration/collaboration-notes.spec.ts. Now that the standalone control holds focus and applies formats correctly, the rich-text flows (bold/code keyboard shortcuts, the Cmd+K link UI) and the focus-on-open of a new note are re-enabled and pass locally (29 passed). Six reply/thread focus flows stay deferred behind test.fixme: the reply form deliberately does not focus on mount so selecting a thread doesn't pull focus away from thread keyboard navigation, and wiring focus into the reply/skip-link paths is follow-up work rather than part of this restack.

Out of scope (deferred per issue discussion)

  • Image upload / embedding in notes (issue open question Add/local build #2).
  • Full inline markdown autocorrect (**bold**, etc.) - only the backtick-wrapping input rule for code ships today; keyboard shortcuts cover the rest of the primary UX.
  • Emoji picker UI - native OS emoji input already works inside RichTextControl.
  • Email-notification rendering of rich content (issue open question Move blocks #4).
  • Interaction with future "suggestions" / "view modes" (issue open question Introduce the ability to reorder blocks #5).

Screenshots/ Screencast

rich.text.notes.mp4
Notes rich text input

Types of changes

New feature.

Checklist

  • My code is tested.
  • My code follows the WordPress Coding Standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in JavaScript files (e.g. imports). (N/A - no RN code touched.)

@github-actions

github-actions Bot commented May 13, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: talldan <talldanwp@git.wordpress.org>
Co-authored-by: adamsilverstein <adamsilverstein@git.wordpress.org>
Co-authored-by: peterwilsoncc <peterwilsoncc@git.wordpress.org>
Co-authored-by: scruffian <scruffian@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: jasmussen <joen@git.wordpress.org>
Co-authored-by: jeffpaul <jeffpaul@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions github-actions Bot added the [Package] Editor /packages/editor label May 13, 2026
@github-actions

github-actions Bot commented May 13, 2026 •

Copy link
Copy Markdown

Size Change: -79 B (0%)

Total Size: 7.71 MB

📦 View Changed
Filename Size Change
build/scripts/editor/index.min.js 495 kB +167 B (+0.03%)
build/styles/editor/style-rtl.css 30.9 kB -101 B (-0.33%)
build/styles/editor/style-rtl.min.css 26.2 kB -24 B (-0.09%)
build/styles/editor/style.css 30.9 kB -99 B (-0.32%)
build/styles/editor/style.min.css 26.2 kB -22 B (-0.08%)

compressed-size-action

@adamsilverstein adamsilverstein added the [Feature] Notes Phase 3 of the Gutenberg roadmap around block commenting label May 13, 2026
@adamsilverstein adamsilverstein self-assigned this May 13, 2026
@adamsilverstein adamsilverstein added the [Type] Enhancement A suggestion for improvement. label May 13, 2026
@jasmussen

Copy link
Copy Markdown
Contributor

Fast work, I love it! Based on the description it sounds great. I'm curious to test ⌘K on selected text, I assume that still shows the URL dialog?

I wasn't able to tell though: in an empty post with no sidebar open and no notes sidebar becasue there are no comments yet, I'm unable to focus the input field. It could be my env, I tested on RC3, but not sure.

test

@Mamaduka

Copy link
Copy Markdown
Member

Sorry, don't have time to do a proper review now.

A technical note: we shouldn't use RichText from the block-editor package; it's meant to be used inside blocks and includes a lot of logic we don't need. A minimal implementation for this case should be similar to DataForms. See related discussion - #73180.

@adamsilverstein

Copy link
Copy Markdown
Member Author

I wasn't able to tell though: in an empty post with no sidebar open and no notes sidebar becasue there are no comments yet, I'm unable to focus the input field. It could be my env, I tested on RC3, but not sure.

Hmmm, that sounds like a bug. Checking.

A technical note: we shouldn't use RichText from the block-editor package; it's meant to be used inside blocks and includes a lot of logic we don't need. A minimal implementation for this case should be similar to DataForms. See related discussion - #73180.

Ok, thanks for the tip @Mamaduka and reference. I guess that means this ticket is blocked by #75275, unless you think there is a different approach we should use in the meantime?

@adamsilverstein
adamsilverstein requested a review from ellatrix as a code owner May 14, 2026 14:26
adamsilverstein added a commit that referenced this pull request May 14, 2026
Per review feedback on #78242, replace the block-editor RichText
component with RichTextControl introduced in #75275. RichText is
intended for use inside blocks and pulls in editor state (selection
store, format toolbar wiring, etc.) that the note form does not need.
RichTextControl wraps useRichText directly and exposes the smaller
surface the note form actually uses, which mirrors the approach
DataForms takes for its rich-text field.

The form keeps the same allowed-formats list (bold, italic, link,
code), the same keyboard shortcuts (Cmd+B / Cmd+I / Cmd+K), and the
same empty-state submit guard. The Cmd+Enter / Escape handling moves
up to the form element because RichTextControl does not expose an
onKeyDown prop; the events still reach it via bubbling from the
contenteditable.

Note styles are rescoped to the form-level container (the input now
carries the .block-editor-rich-text-control class) and the explicit
VisuallyHidden label is replaced by RichTextControl's built-in
hideLabelFromVision option.

Refs #75275
Refs #73413
@adamsilverstein
adamsilverstein changed the base branch from trunk to move/rich-text-to-fields-package May 14, 2026 14:26
NoteForm reused its visually hidden accessibility label as the visible
placeholder, so the reply field showed the full 'Reply to note N by
author' description. Trunk renders these fields with a hidden label and
no placeholder; restore that behavior and drop the now-unused
placeholder color override.
The richtext control now portals its format popovers (e.g. the Cmd+K
link UI) to the body so overflow ancestors cannot clip them. Focus
moving into one therefore leaves the thread's DOM, and the thread's
blur handler deselected the note, unmounting the reply form and the
popover with it. Carve out focus landing in a popover, mirroring the
new-note form in add-note.js.

Add e2e coverage for the link popover in the reply form (opens, stays
mounted, lies fully within the viewport) and for the note fields not
rendering their hidden labels as placeholders.
@adamsilverstein

adamsilverstein commented Jul 9, 2026 •

Copy link
Copy Markdown
Member Author

Both confirmed and fixed, @Mamaduka — one was specific to this PR, one general to #78471:

  1. Label shown as placeholder — specific to this PR: NoteForm reused its visually hidden label as the placeholder. Removed in bfce4ca, restoring trunk parity (hidden label, no placeholder).
  2. Clipped/mispositioned link popover — general to DataViews: Add a richtext control backed by a private RichTextControl shell in @wordpress/components #78471: the richtext control rendered its Popover.Slot inline, so the note card's overflow clipped the popover and the floating thread's transform offset it. Fixed upstream in d1beb6bd8f5 by portaling the slot to the body (where popovers land by default, while keeping the slot inside the field's SlotFillProvider so the focus containment still works), and merged into this branch. The thread's blur handler needed a matching carve-out so focus moving into the popover no longer collapses the note (53e5b86).

Both flows are now covered by e2e tests in block-notes.spec.js: the reply-form link popover must open, stay mounted, and lie fully within the viewport, and the note fields must not render their hidden labels as placeholders.

image

adamsilverstein and others added 8 commits July 9, 2026 14:52
…ol-package

# Conflicts:
#	packages/rich-text/CHANGELOG.md
Co-authored-by: Lena Morita <lena@jaguchi.com>
The @wordpress/ui direction is no stable public class names on
components, so styling hooks must not leak from the component itself.
The validated wrapper now applies its own class and targets the inner
editable via [role="textbox"] for the error treatment, per review
suggestion on #78471.
The description/aria-live changes to ControlWithError affect every
delegate-based validated control, so they need broader consideration
and testing (see #76741). Revert ControlWithError to trunk and skip the
description-association test, matching how the other delegate-based
controls document the same pre-existing limitation.
The base PR removed the stable class per review — components expose no
public styling hooks. Scope the note form's editable tweaks by role
instead.
@adamsilverstein

adamsilverstein commented Jul 10, 2026 •

Copy link
Copy Markdown
Member Author

I noticed a bug where placeholders are showing the note ID, investigating...

image

The placeholder-leak fix shipped without asserting the aria-placeholder
or the accessible names, so the hidden label could silently reappear as
a visible placeholder on divergent branches. Assert both fields expose
no placeholder and keep their visually hidden labels as the accessible
name.
The @example tag removal was applied through the GitHub UI, which
skips the local docs regeneration hook; the code snippet now flows
into the generated readme as intended.
Trunk commit 2b6da42 (#80104) landed with a formatting error that
fails Lint JavaScript on any branch containing it; trunk CI is red on
the same check. Format the file so this PR's CI can pass; trunk's own
fix will merge cleanly.
@jeffpaul

Copy link
Copy Markdown
Member

Tested and the expected keyboard shortcuts and backticks work as expected, though I hadn't read the PR description at first and was surprised to see now simple richtext editing options in the notes textarea so we'll want to ensure we include in user education / marketing that we note its keyboard shortcuts that allow these.

@adamsilverstein

Copy link
Copy Markdown
Member Author

I noticed a bug where placeholders are showing the note ID, investigating...

I fixed this bug and restored the current core behavior of no placeholders.

@adamsilverstein

adamsilverstein commented Jul 10, 2026 •

Copy link
Copy Markdown
Member Author

Tested and the expected keyboard shortcuts and backticks work as expected, though I hadn't read the PR description at first and was surprised to see now simple richtext editing options in the notes textarea so we'll want to ensure we include in user education / marketing that we note its keyboard shortcuts that allow these.

Thanks for testing! Hopefully for most people these shortcuts have become second nature and they will discover them naturally.

We will have the same issue when we add support for user @ mentions. I noticed Google docs hints users with a placeholder:

add note

image

and on reply
image

@jeffpaul

Copy link
Copy Markdown
Member

oh that's a nice UI improvement from Google, perhaps something to consider in the @mentions PR though could also be a follow-up improvement after landing the @mention work for this release as more imperative?

@adamsilverstein

Copy link
Copy Markdown
Member Author

oh that's a nice UI improvement from Google, perhaps something to consider in the @mentions PR though could also be a follow-up improvement after landing the @mention work for this release as more imperative?

We can add it in #79604

@adamsilverstein

Copy link
Copy Markdown
Member Author

Now that #78471 has merged, I have cleaned up this PR, based on trunk and updated the description to match the current setup.

This is ready for review again @Mamaduka!

@Mamaduka Mamaduka left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for bringing this over the line, @adamsilverstein! The feature tests well for me ✅

Pushed two minor cleanup commits:

I think this is good to merge. Anything we missed can be fixed during the beta/RC cycle.

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

Labels

[Feature] Notes Phase 3 of the Gutenberg roadmap around block commenting [Package] DataViews /packages/dataviews [Package] Editor /packages/editor [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for rich text in Notes (emoji, basic formatting, images)

7 participants