fix(super-editor): use fontFamily key for markdown code block monospace font#3820
fix(super-editor): use fontFamily key for markdown code block monospace font#3820smxmdrissel wants to merge 1 commit into
Conversation
…ce font
The markdown-to-ProseMirror converter's convertCodeBlock() set the
monospace run-property override under the key `rFonts`, but every
downstream consumer (the w:rFonts OOXML translator, which declares
sdNodeOrKeyName: 'fontFamily', and the style-engine's cascade
resolution) reads run font overrides under the key `fontFamily`.
Because the keys never matched, the Courier New override was silently
dropped before reaching the style-engine/DomPainter, so fenced
markdown code blocks rendered in the default body font instead of
monospace.
Also fixes the same key mismatch in the reverse direction:
proseMirrorToMdast.ts's isMonospaceRun() checked runProps.rFonts
instead of runProps.fontFamily, which would have prevented a
docx-imported monospace run from round-tripping back to markdown
inline-code spans on export. Note: proseMirrorToMdast.ts has no path
that emits an mdast fenced `code` block today, so even with this fix
a multi-line monospace run round-trips to a paragraph of inlineCode
spans joined by line breaks, not to a fenced code block. Genuine
fenced-block round-tripping is a separate, larger change and is not
addressed here.
Updated the corresponding unit test to exercise the corrected key.
Additionally, attach a `textStyle` mark ({ fontFamily: 'Courier New' })
to each code-block run alongside the direct runProperties.fontFamily
attrs. This is NOT a full mirror of the existing inlineCode conversion,
which sets only the mark. code-block JSON can be produced and consumed
without ever going through an editor.dispatch() (e.g. inspecting the
converter's raw JSON output directly), so the direct attrs are kept as
a fallback for that path; inlineCode runs are always dispatched through
a live Editor, so a mark alone suffices there. Once a code-block run is
dispatched, calculateInlineRunPropertiesPlugin's mark-based run-property
recalculation (which treats fontFamily as a mark-derived key) recomputes
runProperties.fontFamily from the mark via decodeRPrFromMarks anyway, so
the pre-dispatch direct attrs set here are provisional, not final --
without the mark, the directly-set fontFamily is simply dropped on the
very first editor.dispatch(tr) (e.g. the one triggered by the CLI's
'open --content-override' flow), since the plugin recomputes inline run
properties from marks and only preserves non-mark-derived existing keys.
Verified end-to-end via the CLI binary and the superdoc-docx demo
service's /convert endpoint.
Code Review by Qodo
Context used 1. Misses w:ascii monospace
|
| function isMonospaceRun(runProps: Record<string, unknown> | undefined): boolean { | ||
| if (!runProps) return false; | ||
| const rFonts = runProps.rFonts as Record<string, string> | undefined; | ||
| if (!rFonts) return false; | ||
| return rFonts.ascii === 'Courier New' || rFonts.hAnsi === 'Courier New'; | ||
| const fontFamily = runProps.fontFamily as Record<string, string> | undefined; | ||
| if (!fontFamily) return false; | ||
| return fontFamily.ascii === MARKDOWN_MONOSPACE_FONT || fontFamily.hAnsi === MARKDOWN_MONOSPACE_FONT; | ||
| } |
There was a problem hiding this comment.
1. Misses w:ascii monospace 🐞 Bug ≡ Correctness
isMonospaceRun() only checks runProperties.fontFamily.ascii/hAnsi, so runs whose fontFamily object uses OOXML-prefixed keys (e.g. w:ascii, w:hAnsi) won’t be recognized as monospace and won’t export to inlineCode. This undermines the intended “docx-imported monospace run” round-trip behavior when the prefixed key shape is present.
Agent Prompt
## Issue description
`isMonospaceRun()` only detects monospace when `runProps.fontFamily` contains unprefixed keys (`ascii`/`hAnsi`). Other parts of the codebase accept OOXML-prefixed keys (`w:ascii`, `w:hAnsi`, etc.), so monospace runs using that representation will not be converted to mdast `inlineCode` during markdown export.
## Issue Context
`getFontFamilyValue()` already normalizes between prefixed and unprefixed keys, and other code paths/tests demonstrate `runProperties.fontFamily` can carry `w:ascii`.
## Fix Focus Areas
- packages/super-editor/src/editors/v1/core/helpers/markdown/proseMirrorToMdast.ts[371-401]
- packages/super-editor/src/editors/v1/core/helpers/markdown/proseMirrorToMdast.test.ts[287-305]
## Implementation notes
- Update `isMonospaceRun()` to accept both key shapes, e.g.:
- `const ascii = ff.ascii ?? ff['w:ascii']`
- `const hAnsi = ff.hAnsi ?? ff['w:hAnsi']`
- (optionally also check `eastAsia`/`cs` equivalents)
- Consider also handling the case where `runProps.fontFamily` is a string (defensive).
- Add/extend a unit test where `runProperties.fontFamily` uses `{ 'w:ascii': 'Courier New', 'w:hAnsi': 'Courier New' }` and assert it exports to `inlineCode`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
Thanks again for digging into this and putting together such a thoughtful PR. Your diagnosis of the We have decided not to expand our Markdown support right now, so we will not be merging this. We shared more context in the related issue. This is a product-scope decision, not an issue with your work. We really appreciate the time and care you put into the contribution. |
What changed
Fixes a key mismatch that silently dropped the monospace font override on fenced markdown code blocks.
convertCodeBlock()in the markdown → ProseMirror converter set the monospace run-property override under the keyrFonts. Every downstream consumer — thew:rFontsOOXML translator (sdNodeOrKeyName: 'fontFamily') and the style-engine's cascade resolution — reads run font overrides under the keyfontFamily. Because the keys never matched, the Courier New override was dropped before reaching the style-engine/DomPainter, so fenced markdown code blocks rendered in the default body font instead of monospace.This also fixes the same key mismatch in the reverse direction:
proseMirrorToMdast.ts'sisMonospaceRun()checkedrunProps.rFontsinstead ofrunProps.fontFamily, which would have prevented a docx-imported monospace run from round-tripping back to markdown inline-code spans on export.Additionally, each code-block run now gets a
textStylemark ({ fontFamily: 'Courier New' }) alongside the directrunProperties.fontFamilyattrs. The direct attrs remain as a fallback for callers that inspect the converter's raw JSON output without dispatching through a liveEditor; once dispatched,calculateInlineRunPropertiesPlugin's mark-based recalculation recomputesrunProperties.fontFamilyfrom the mark anyway (mirroring howinlineCodealready works).A shared
MARKDOWN_MONOSPACE_FONTconstant now backs both conversion directions so they can't drift apart again.Known limitation (not addressed here):
proseMirrorToMdast.tshas no path that emits an mdast fencedcodeblock today, so a multi-line monospace run round-trips to a paragraph ofinlineCodespans joined by line breaks, not to a fenced code block. Genuine fenced-block round-tripping is a separate, larger change.Test plan
mdastToProseMirror.test.tscovering fenced code block conversion, asserting both thetextStylemark and the directrunProperties.fontFamilyattrs are set (and that the incorrectrFontskey is never used).proseMirrorToMdast.test.tsto exercise the correctedfontFamilykey inisMonospaceRun().calculateInlineRunPropertiesPlugin.test.jsto cover mark-based recalculation of code-block runs./convertendpoint.pnpm test(scoped to the touched test files) — 76/76 passing.Closes #3819