feat: initial support for multi-column; lists and media fixes(Copy/paste project) #2528
feat: initial support for multi-column; lists and media fixes(Copy/paste project) #2528VladaHarbour wants to merge 4 commits intomainfrom
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 547bdff47c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
packages/super-editor/src/core/helpers/superdocClipboardSlice.js
Outdated
Show resolved
Hide resolved
3e811a3 to
499b262
Compare
There was a problem hiding this comment.
@VladaHarbour nice work, Codex bot comments from earlier are all handled.
handleCutEvent:preventDefaultbefore the try block means Cmd+X silently fails if anything throws — see inline fixstripWordListConditionalPrefixes: missing[endif]eats all content after it — see inline warningcreateListIdAllocatoris copy-pasted between two files, bodySectPr helpers are duplicated — see inline suggestions- missing round-trip tests for
embedSliceInHtml/extractSliceFromHtml/stripSliceFromHtmlandshouldPreserveSlicePastedListRendering - no Playwright tests for copy-paste yet — fine as follow-up
|
|
||
| const { from, to } = view.state.selection; | ||
| if (from === to) return false; | ||
|
|
There was a problem hiding this comment.
if anything inside the try block fails, preventDefault already blocked the browser's cut — so the user presses Cmd+X and nothing happens (no clipboard data, no deletion). moving preventDefault to after the clipboard writes means the browser's own cut kicks in as a fallback.
| try { | |
| const slice = view.state.doc.slice(from, to); | |
| const fragment = slice.content; | |
| const sliceJson = JSON.stringify(slice.toJSON()); | |
| clipboardData.setData(SUPERDOC_SLICE_MIME, sliceJson); | |
| const mediaJson = collectReferencedImageMediaForClipboard(sliceJson, editor); | |
| if (mediaJson) { | |
| clipboardData.setData(SUPERDOC_MEDIA_MIME, mediaJson); | |
| } | |
| const div = document.createElement('div'); | |
| const serializer = PmDOMSerializer.fromSchema(view.state.schema); | |
| div.appendChild(serializer.serializeFragment(fragment)); | |
| annotateFragmentDomWithClipboardData(div, fragment, editor); | |
| const html = unflattenListsInHtml(div.innerHTML); | |
| const bodySectPr = view.state.doc.attrs?.bodySectPr; | |
| const bodySectPrJson = bodySectPr && bodySectPrShouldEmbed(bodySectPr) ? JSON.stringify(bodySectPr) : ''; | |
| clipboardData.setData('text/html', embedSliceInHtml(html, sliceJson, bodySectPrJson)); | |
| clipboardData.setData('text/plain', fragment.textBetween(0, fragment.size, '\n\n')); | |
| event.preventDefault(); | |
| view.dispatch(view.state.tr.deleteSelection().scrollIntoView()); | |
| } catch (error) { | |
| console.warn('Failed to handle cut:', error); | |
| } |
There was a problem hiding this comment.
moved preventDefault inside try/catch
| const current = node.childNodes[i]; | ||
| if (current?.nodeType === Node.COMMENT_NODE && current.nodeValue?.includes('[if !supportLists]')) { | ||
| let j = i + 1; | ||
| while (j < node.childNodes.length) { |
There was a problem hiding this comment.
if the closing [endif] comment is missing (broken Word HTML), this loop removes every node after it until there's nothing left. a warning would make this easier to debug:
| while (j < node.childNodes.length) { | |
| while (j < node.childNodes.length) { | |
| const next = node.childNodes[j]; | |
| if (next?.nodeType === Node.COMMENT_NODE && next.nodeValue?.includes('[endif]')) { | |
| node.removeChild(next); | |
| break; | |
| } | |
| node.removeChild(next); | |
| } | |
| if (j >= node.childNodes.length) { | |
| console.warn('[SuperDoc] stripWordListConditionalPrefixes: missing [endif], some content may have been removed'); | |
| } |
There was a problem hiding this comment.
created more advanced handling for this corner case + test
| } | ||
|
|
||
| /** Apply pasted body `sectPr` when target has single-column layout. */ | ||
| function tryApplyEmbeddedBodySectPr(editor, view, bodySectPr) { |
There was a problem hiding this comment.
tryApplyEmbeddedBodySectPr and applyEmbeddedBodySectPrToTransaction do the same checks and the same work — only difference is who owns the transaction. could be one shared function:
function applyBodySectPrToTr(editor, tr, bodySectPr, currentDoc) {
if (!bodySectPr || typeof bodySectPr !== 'object') return false;
const incomingCols = getSectPrColumns(bodySectPr);
if (!incomingCols?.count || incomingCols.count <= 1) return false;
const currentCols = getSectPrColumns(currentDoc.attrs?.bodySectPr);
if (currentCols?.count > 1) return false;
const clone = JSON.parse(JSON.stringify(bodySectPr));
tr.setDocAttribute('bodySectPr', clone);
if (editor?.converter) editor.converter.bodySectPr = clone;
return true;
}worth doing?
| if (paragraphMeta.length === 0) { | ||
| return fragment; | ||
| } | ||
|
|
There was a problem hiding this comment.
createListIdAllocator is identical to the one in html-helpers.js:53 — moving it to the shared numbering helpers would keep them from drifting apart.
| return doc.body.innerHTML; | ||
| } | ||
|
|
||
| function createListIdAllocator(editor) { |
There was a problem hiding this comment.
same createListIdAllocator as InputRule.js:790 — see comment there.
a5d7065 to
d410051
Compare
This PR introduces fixes to support copy/paste experience within Superdoc:
(We have hyperlinks and table support in current stable version)