Skip to content

Commit 1912016

Browse files
committed
fix(context-menu): preserve whitespace and selection on paste
Two fixes for context menu paste: 1. Add `preserveWhitespace: true` to `handleHtmlPaste` — Chromium wraps plain-text clipboard content in HTML, routing it through the HTML paste path which strips leading/trailing whitespace by default. 2. Use the selection saved at menu-open time (`context.selectionStart/End`) instead of reading from the live PM state — the right-click handler may have collapsed a range selection before the paste action runs.
1 parent f875666 commit 1912016

2 files changed

Lines changed: 7 additions & 12 deletions

File tree

packages/super-editor/src/components/context-menu/menuItems.js

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -300,19 +300,14 @@ export function getItems(context, customItems = [], includeDefaultItems = true)
300300
label: TEXTS.paste,
301301
icon: ICONS.paste,
302302
isDefault: true,
303-
action: async (editor) => {
303+
action: async (editor, context) => {
304304
const { view } = editor ?? {};
305305
if (!view) return;
306-
// Save the current selection before focusing. When the context menu
307-
// is open, its hidden search input holds focus, so the PM editor's
308-
// contenteditable is blurred. A raw `view.dom.focus()` would restart
309-
// ProseMirror's DOMObserver which reads the stale browser selection
310-
// (collapsed at the document start) and overwrites the PM state.
311-
// Using `view.focus()` (ProseMirror-aware) prevents this by writing
312-
// the PM selection to the DOM before restarting the observer. We also
313-
// save/restore as a safety net against async drift during clipboard reads.
314-
const savedFrom = view.state.selection.from;
315-
const savedTo = view.state.selection.to;
306+
// Use the selection captured when the context menu opened — the
307+
// right-click handler may have collapsed a range selection via
308+
// moveCursorToMouseEvent before this action runs.
309+
const savedFrom = context?.selectionStart ?? view.state.selection.from;
310+
const savedTo = context?.selectionEnd ?? view.state.selection.to;
316311
view.focus();
317312
const { html, text } = await readClipboardRaw();
318313
// Restore selection after the async gap — ProseMirror's DOMObserver

packages/super-editor/src/core/InputRule.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ export function handleHtmlPaste(html, editor, source) {
348348
cleanedHtml.dataset.superdocImport = 'true';
349349
}
350350

351-
let doc = PMDOMParser.fromSchema(editor.schema).parse(cleanedHtml);
351+
let doc = PMDOMParser.fromSchema(editor.schema).parse(cleanedHtml, { preserveWhitespace: true });
352352
doc = mergeAdjacentTableFragments(doc);
353353

354354
doc = wrapTextsInRuns(doc);

0 commit comments

Comments
 (0)