From 7a8c552342d68e4ce4c1aa41fd016c477f5db8ba Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Sun, 16 Aug 2026 07:29:06 +0200 Subject: [PATCH] fix: a page break survives beside other paragraph formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit applyPageBreaks required its sentinel directly after the opening tag, but a custom paragraph's serialized text starts with the FSZ/STY/PBX payloads, so the break was silently dropped. It also minted PB# names, colliding with applyParagraphBoxes' PB# — the importer then resolved both paragraphs to one definition, painting the break paragraph with a stranger's box props. Now the sentinel matches anywhere in the block and the pass mints PBK# instead. --- src/lib/export/odt.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lib/export/odt.ts b/src/lib/export/odt.ts index b26778d..2149c20 100644 --- a/src/lib/export/odt.ts +++ b/src/lib/export/odt.ts @@ -1581,7 +1581,9 @@ function applyPageBreaks(odtBytes: Uint8Array): Uint8Array { const breakStyleFor = (source: string): string => { const existing = nameBySource.get(source); if (existing) return existing; - const name = `PB${++counter}`; + // PBK, not PB: applyParagraphBoxes mints PB names, and a duplicate style:name + // makes the importer resolve both paragraphs to whichever definition it finds. + const name = `PBK${++counter}`; nameBySource.set(source, name); const def = source ? findAutoStyle(content, source) : null; minted.push(def @@ -1591,15 +1593,18 @@ function applyPageBreaks(odtBytes: Uint8Array): Uint8Array { return name; }; + // The sentinel sits anywhere in the block: a custom paragraph's serialized text + // starts with the FSZ/STY/PBX payloads (their passes run later), then the PGB. content = content.replace( - new RegExp(`]*)>${PGB}`, 'g'), - (_m, tag: string, attrs: string) => { + new RegExp(`]*)>([\\s\\S]*?)`, 'g'), + (m, tag: string, attrs: string, inner: string) => { + if (!inner.includes(PGB)) return m; const sm = /text:style-name="([^"]*)"/.exec(attrs); const name = breakStyleFor(sm ? sm[1] : ''); const newAttrs = sm ? attrs.replace(/text:style-name="[^"]*"/, `text:style-name="${name}"`) : ` text:style-name="${name}"${attrs}`; - return ``; + return `${inner.split(PGB).join('')}`; }, ); // Drop any sentinels not consumed above (defensive — never legitimate text).