-
Notifications
You must be signed in to change notification settings - Fork 94
fix: inherit run formatting when inserting inline structured content (SD-1421) #2501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ import { DOMParser as PMDOMParser } from 'prosemirror-model'; | |
| import { Extension } from '@core/Extension.js'; | ||
| import { htmlHandler } from '@core/InputRule.js'; | ||
| import { findParentNode } from '@helpers/findParentNode.js'; | ||
| import { getFormattingStateAtPos } from '@core/helpers/getMarksFromSelection.js'; | ||
| import { generateRandomSigned32BitIntStrId } from '@core/helpers/generateDocxRandomId.js'; | ||
| import { getStructuredContentTagsById } from './structuredContentHelpers/getStructuredContentTagsById.js'; | ||
| import { getStructuredContentByGroup } from './structuredContentHelpers/getStructuredContentByGroup.js'; | ||
|
|
@@ -134,6 +135,27 @@ export const StructuredContentCommands = Extension.create({ | |
| content = schema.text(' '); | ||
| } | ||
|
|
||
| // When content was not provided as structured JSON, wrap the text | ||
| // in a formatted run inside the SDT so it visually matches the | ||
| // surrounding text. The run-split logic below prevents an outer run | ||
| // from wrapping the SDT itself. | ||
| const runType = schema.nodes.run; | ||
| if (runType && !options.json && content.isText) { | ||
| const formattingState = getFormattingStateAtPos(state, from, editor); | ||
| const runProperties = formattingState.inlineRunProperties || null; | ||
|
|
||
| // Apply resolved marks so calculateInlineRunPropertiesPlugin can diff correctly | ||
| if (formattingState.resolvedMarks?.length) { | ||
| const mergedMarks = formattingState.resolvedMarks.reduce( | ||
| (set, mark) => mark.addToSet(set), | ||
| content.marks, | ||
| ); | ||
| content = content.mark(mergedMarks); | ||
| } | ||
|
|
||
| content = runType.create({ runProperties }, content); | ||
| } | ||
|
|
||
| // Handle group parameter: convert to JSON tag | ||
| let tag = options.attrs?.tag || 'inline_text_sdt'; | ||
| if (options.attrs?.group) { | ||
|
|
@@ -157,7 +179,37 @@ export const StructuredContentCommands = Extension.create({ | |
| from = to = insertPos; | ||
| } | ||
|
|
||
| tr.replaceWith(from, to, node); | ||
| // If the cursor is inside a run, split the run first so the SDT | ||
| // is inserted at paragraph level rather than becoming a child of the run. | ||
| const $from = state.doc.resolve(from); | ||
| if (runType && $from.parent.type === runType) { | ||
| const runDepth = $from.depth; | ||
| const runStart = $from.before(runDepth); | ||
| const runEnd = $from.after(runDepth); | ||
| const parentRun = $from.parent; | ||
| const startOffset = $from.parentOffset; | ||
|
|
||
| // When the user has a ranged selection, cut the right half from | ||
| // the end of the selection so the selected text is removed. | ||
| const $to = state.doc.resolve(to); | ||
| const endOffset = $to.parent === parentRun ? $to.parentOffset : startOffset; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the selection starts inside a Useful? React with 👍 / 👎. |
||
|
|
||
| const leftContent = parentRun.content.cut(0, startOffset); | ||
| const rightContent = parentRun.content.cut(endOffset); | ||
|
|
||
| const fragments = []; | ||
| if (leftContent.size > 0) { | ||
| fragments.push(runType.create(parentRun.attrs, leftContent, parentRun.marks)); | ||
| } | ||
| fragments.push(node); | ||
| if (rightContent.size > 0) { | ||
| fragments.push(runType.create(parentRun.attrs, rightContent, parentRun.marks)); | ||
| } | ||
|
|
||
| tr.replaceWith(runStart, runEnd, fragments); | ||
| } else { | ||
| tr.replaceWith(from, to, node); | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This now copies formatting with
getFormattingStateAtPos(state, from, editor), but on a collapsed selection that helper ignoresstate.storedMarksunless they are passed explicitly. If a user toggles bold/font size on an empty cursor and then inserts an inline field, the newstructuredContentwill inherit the surrounding run instead of the pending toolbar formatting, which is inconsistent with normal text insertion.Useful? React with 👍 / 👎.