diff --git a/third_party/muya/src/assets/styles/exportStyle.css b/third_party/muya/src/assets/styles/exportStyle.css index e362724..187b8f1 100644 --- a/third_party/muya/src/assets/styles/exportStyle.css +++ b/third_party/muya/src/assets/styles/exportStyle.css @@ -89,6 +89,21 @@ white-space: pre-wrap; } +/* Render soft line breaks (Shift+Enter -> a bare `\n` inside a block) the way + the editor does (`.mu-content` is pre-wrap) instead of emitting a + non-standard `
`, so exported HTML stays CommonMark-conformant while still + showing the break (#3676). + + `li:not(:has(> p))` targets only tight list items, whose soft break is a + bare `\n` directly inside the `
  • `. Loose items wrap their content in + `

    ` (handled by the `p` rule); giving them `li` pre-wrap would expose + marked's pretty-printing newline between `

    ` and `
  • ` as a stray blank + line, so they are deliberately excluded. */ +.markdown-body p, +.markdown-body li:not(:has(> p)) { + white-space: pre-wrap; +} + .markdown-body table { display: table; } diff --git a/third_party/muya/src/state/__tests__/softBreakExportHtml.spec.ts b/third_party/muya/src/state/__tests__/softBreakExportHtml.spec.ts new file mode 100644 index 0000000..95db5e2 --- /dev/null +++ b/third_party/muya/src/state/__tests__/softBreakExportHtml.spec.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from 'vitest'; +import { getHighlightHtml } from '../../utils/marked'; + +const OPTS = { math: false, superSubScript: false, footnote: false, frontMatter: false }; + +describe('#3676: soft line breaks survive export as conformant newlines', () => { + it('keeps a paragraph soft break as a newline, never a
    ', () => { + const html = getHighlightHtml('line one\nline two', OPTS); + + expect(html).toContain('

    line one\nline two

    '); + expect(html).not.toMatch(//); + }); + + it('keeps a soft break inside a tight list item, never a
    ', () => { + const html = getHighlightHtml('- line A\n line B', OPTS); + + expect(html).toMatch(/
  • line A\nline B<\/li>/); + expect(html).not.toMatch(//); + }); + + it('leaves a real hard break as
    ', () => { + const html = getHighlightHtml('line one \nline two', OPTS); + + expect(html).toMatch(//); + }); +});