Add an inline-only mode to the rich text editor - #6214
rtibblesbot wants to merge 4 commits into
Conversation
39ba7fd to
01db6df
Compare
AlexVelezLl
left a comment
There was a problem hiding this comment.
Thanks, this is working great. I was wrong when I raised the issue suggesting line-break support; let's drop it. inlineOnly will also mean just a one-line editor.
| // is marked afresh as a slice with no context. A paste with no blocks, like the | ||
| // toolbar's copy of a phrase, is marked too, so its edge spaces keep it apart from | ||
| // the words it lands beside. | ||
| export function unwrapPastedBlocks(doc) { |
There was a problem hiding this comment.
suggestion: Keeping a paste's edge spaces depends on data-pm-slice, which is prosemirror-view's internal clipboard marker, not a documented API. It's written in three places (here, handleCopy in useToolbarActions.js:180, and the clipboardSerializer override in useEditor.js), and its "0 0 []" format is parsed by private code in prosemirror-view's clipboard.ts. Together with trimSourceEdges / collapseWhitespace / collapseCodeWhitespace, that's most of this file's complexity, for behavior the issue doesn't ask for (it asks for blocks flattened to one inline run; edge-space fidelity on copy/paste isn't in the criteria).
The tests run against the real prosemirror-view, so a format change would fail CI rather than break silently. Still, would plain flattening (unwrap blocks, let ProseMirror collapse whitespace) be enough for gap choices, which are short phrases typed into a widget? If the edge spaces matter, a short comment linking the prosemirror-view source this relies on would help whoever upgrades it.
There was a problem hiding this comment.
Dropped edge-space preservation in 9ed37b5: plain flattening now, ProseMirror collapses the whitespace.
- Searched the branch for
data-pm-slice: 3 writers (this file,handleCopy, theclipboardSerializeroverride); all removed. trimSourceEdges,collapseWhitespace,collapseCodeWhitespaceand the edge-space tests are gone.- The one remaining use strips the attribute from an incoming paste, so ProseMirror collapses it like any other paste; it links the prosemirror-view source.
| // Inline-only fields (`qti-gap-text`) leave every block node out of the schema, | ||
| // so no command, shortcut or input rule has a block to create. |
There was a problem hiding this comment.
Please do not mention qti-gap-text here; it should know nothing about the QTI editor. This should be general.
There was a problem hiding this comment.
Removed in 9ed37b5. Searched TipTapEditor/ and docs/rich_text_editor.md for qti-gap-text/GapTextDType: 4 places (this comment, InlineDocument.js, the inlineOnly prop doc, docs/rich_text_editor.md) plus the useEditor.spec.js describe text and GAP_TEXT_ELEMENTS; all reworded as general inline-only content.
| // Touch keyboards insert their newline before ProseMirror offers Enter to the keymap; | ||
| // claiming it makes ProseMirror discard that newline. A line break takes its place, | ||
| // since touch keyboards have no Shift-Enter. | ||
| addKeyboardShortcuts() { | ||
| return { Enter: () => this.editor.commands.setHardBreak() }; | ||
| }, |
There was a problem hiding this comment.
I know the issue used to say that line breaks were accepted, but I have reconsidered it, and given that we cannot have gaps with multi-line inputs, we should not have multi-line distractors either. Could you please handle this and make inlineOnly enforce a single-line editor? I have updated the issue.
There was a problem hiding this comment.
Done in 9ed37b5: inlineOnly is single-line.
hardBreakis out of the schema; Enter, Shift-Enter and Mod-Enter are claimed and insert nothing (touch-keyboard newline included).- Stored and pasted
<br>become a space; a trailing<br>is dropped. - Searched for other line-break paths: the toolbar paste and
toInlineHTMLboth go through the sameflattenBlocks, covered by tests; no toolbar offers a line break. - Capture: Add an inline-only mode to the rich text editor #6214 (comment)
There was a problem hiding this comment.
Two more single-line gaps fixed in a8d2bcd:
<hr>between text welded the two runs (a<hr>bsavedab); it now becomes a space like<br>.- Searched
TipTapEditor/foraria-multiline: 1 place (the container), hard-codedtrue. It and the ProseMirror element now sayfalsein inline-only mode. - Dropped
dropTrailingBreak/isEmptyand 9 paste tests that passed with flattening disabled; ProseMirror already strips the edge whitespace they covered.
01db6df to
9ed37b5
Compare
|
Single-line ./inline-single-line.webm The description's Enter/line-break rows predate this change and no longer apply. inline-single-line.webm |
a8d2bcd to
85fa44b
Compare
- `initializeEditor(..., { inlineOnly })` leaves every block node and the line break out of the schema
- Enter, Shift-Enter and Mod-Enter insert nothing
- Pasted blocks and line breaks flatten to one space-separated inline run; plain-text lines join with spaces
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Stored block content loads flattened to inline and saves back inline - Dropped files are ignored in inline-only mode Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Pasted blocks and lines join with a space, as on a native paste Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Desktop and mobile bars drop lists, image, code block, alignment and the format dropdown Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
61ec7a1 to
85ee9db
Compare


Summary
inlineOnlyprop builds the editor on a schema with no block nodes, for inline-only QTI fields such as gap match choices.References
Closes #6193. Builds on #6151.
Reviewer guidance
content: 'inline*'; riskiest:focus('end'),selectAll, Math node view.useEditor.jscancels Android's keydown-less Enterbeforeinput.aria-multilineis"false"inline-only,"true"otherwise.qti-gap-text.QA steps
Setup:
pnpm devsetupadmin. Q1: Published Channel → Topic 1 → Sample Exercise → Questions → Edit on Question 1. Inline-only: addinlineOnlyto the choice<TipTapEditor>inshared/views/QTIEditor/interactions/choice/Editor.vue.Alpha **bold** mid x² end, desktop or phone: Enter, Shift-Enter or Ctrl+Enter anywhere leaves that text unchanged; Minimize Toolbar shows it on one line.Start **here** x² end: pasting rich HTML, a spreadsheet or multi-line text gives one single-spaced line, no image; one Ctrl+Z restoresStart **here** x² end.Evidence
Inline-only, typing
inline-typing-enter-variants-desktop.webm
Inline-only, paste
inline-toolbar-paste-and-undo.webm
Full editor copy, inline-only paste
s3-copy-full-paste-inline.webm
Inline-only, stored block content
s4-click-outside-and-minimize.webm
Full editor, choice item
More captures (39)
AI usage
Used Claude Code to plan and implement the change test-first and to capture the screenshots. Verified with the TipTapEditor and QTIEditor Jest suites, pre-commit, and an axe audit in a headless browser.
🤖 Generated with Claude Code
Deviations from the issue spec
Mod-e, backticks), as in the full editor.@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
🟡 Waiting for feedback
Last updated: 2026-10-02 05:35 UTC