feat(pptx): patch-mode saves + IndexedDB-backed source persistence#49
Open
karthikmudunuri wants to merge 2 commits into
Open
feat(pptx): patch-mode saves + IndexedDB-backed source persistence#49karthikmudunuri wants to merge 2 commits into
karthikmudunuri wants to merge 2 commits into
Conversation
Two changes that make edits feel like editing the real PowerPoint, not regenerating from a stripped model. 1. Patch-mode saves: edited elements get their source <p:sp> spliced in place instead of regenerated via pptxgenjs, so themed colors / brand fonts / gradient fills / custGeom / effects / autofit / body padding on the unchanged parts of the element survive verbatim. Covers text content edits (splice <p:txBody> preserving first paragraph's pPr and first run's rPr) and geometry edits (splice <a:xfrm> or <p:xfrm> for <p:graphicFrame>). pptxgenjs remains the fallback for unpatchable cases. Placeholder-inherited shapes (no explicit xfrm in source) are now registered too — patch-mode always splices geometry into the patched output for them. 2. IndexedDB-backed source persistence: parsePptx mirrors source bytes to IndexedDB keyed by deck.sourcePptxId; serializeDeck reads through in-memory cache → IndexedDB → non-enumerable attachment → explicit options.source. The chrome / EMF / slide-bg preservation pipeline now survives page reloads on its own — host apps that persist deck JSON to localStorage no longer need to re-attach source bytes. Also: Export topbar button falls back to a real .pptx download (via serializeDeck) instead of a .slidewise.json dump when the host doesn't register an onExport callback. Makes local round-trip testing trivial. Validated end-to-end on KBC-More_sample_slides.pptx: after parsePptx → structuredClone + spread → serializeDeck(deck) with no options.source, the saved zip retains all 2 masters, 50 layouts, and 3 themes versus the 1/1/1 the previous build produced. New regression tests in patch-mode.test.ts and slide10-bg.test.ts cover the theme-ref and geometry-patch paths.
4e621f3 to
255f2f7
Compare
Three things were dropping shapes in the editor's saved output after the patch-mode landing: 1. Self-closing <p:spPr/> bug. The geometry-injection regex `<p:spPr\b[^>]*>` matched both `<p:spPr>` AND `<p:spPr/>` (the `/` sits inside `[^>]*`), so for placeholder-only shapes whose source spPr was empty/self-closing, we emitted `<p:spPr/><a:xfrm…/>` — xfrm OUTSIDE the spPr container, invalid OOXML, and PowerPoint silently dropped the shape. Reorder the regex chain so the self-closing form is matched FIRST and converted to an open/close pair with xfrm INSIDE. 2. Add a final structural sanity check after any patch — count opening vs closing tags for p:sp / p:pic / p:graphicFrame / p:spPr / p:txBody / a:p / a:r / a:t / a:xfrm. If they don't balance, or the shape container count drifts from the source, fall back to pptxgenjs's lossy emitter instead of shipping broken XML. 3. Regression test edits every text element on eon-deck slide 10 (placeholder-only spPrs across the board) and asserts the saved slide has no `<p:spPr/><a:xfrm` pattern and that p:spPr opening / closing counts balance.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The user complaint
After 1.12.1 the user re-uploaded
KBC-More_sample_slides.pptx, edited a small text, exported, and the save dropped:Plus the edited text element lost its themed colors and brand fonts — pptxgenjs flattens
<a:schemeClr>to inline<a:srgbClr>and drops<a:latin>typeface refs.Two distinct root causes, two fixes.
1. Patch-mode saves (Univer-inspired)
When the user edits a text or drags a shape, the old path regenerates the entire
<p:sp>via pptxgenjs — which only knows a fraction of the OOXML schema, so themed colors, brand fonts, custGeom paths, gradient fills, body padding, effects, autofit hints all drop on the floor.tryPatchEditedElementnow splices the edit into the source XML in place:<p:txBody>, preserving the first paragraph's<a:pPr>(alignment, bullets, indent) and the first run's<a:rPr>(themed colors, fonts, bold/italic/underline/strike). Multi-line text becomes multi-paragraph; mixed-style runs still fall back to pptxgenjs.<a:xfrm>(or<p:xfrm>on<p:graphicFrame>) and keep everything else. Works for<p:sp>,<p:pic>,<p:cxnSp>,<p:graphicFrame>.pptxgenjs remains the fallback for unpatchable cases (newly added elements, color/font picker edits, mixed-style run restyling, shape kind changes). Architecture matches Univer's "edit the source doc tree, never round-trip through a lossy intermediate model."
2. IndexedDB-backed source persistence
Even with patch-mode, the chrome / EMF / slide-bg preservation pipeline still needs the source PPTX bytes at save time. PR #47 introduced an enumerable
Deck.sourcePptxIdplus a module-level cache so the bytes survivestructuredCloneand reducer spreads — but the cache was in-memory only, so a full page reload (host loads deck from localStorage, fresh module instance) emptied it.parsePptxnow mirrors source bytes to IndexedDB keyed bysourcePptxId.serializeDeck's source resolution walks: explicitoptions.source→ in-memory cache → IndexedDB → legacy non-enumerable attachment. The IDB layer is best-effort (private mode / quota exceeded gracefully no-op) and falls back to nothing in SSR / Node environments.Bonus: Export topbar now downloads
.pptxThe package's
<TopBar.Export>button used to download a.slidewise.jsondump when the host didn't registeronExport. It now callsserializeDeckdirectly and downloads a real.pptx, so hosts can verify the full edit → save round trip without wiringonExportat all (and so I can test the dev server locally with one click).Validation
End-to-end against
KBC-More_sample_slides.pptxwithparsePptx → structuredClone + spread → serializeDeck(deck)and nosourcepassed:New regression tests in
patch-mode.test.ts: edit text on eon-deck slide 10 column 2 → asserts the source<a:schemeClr val="accent1"/>fill AND the<a:schemeClr val="bg1"/>text color both survive verbatim. Geometry drag test asserts the same theme refs survive after an x-coordinate change.pnpm typecheckclean.pnpm --filter @textcortex/slidewise test→ 41/41 passing.Test plan
KBC-More_sample_slides.pptx→ edit a text → export → verify in PowerPoint that the 50 layouts and 3 themes are intactChangeset
Minor bump on
@textcortex/slidewise(1.12.1 → 1.13.0). New optionalDeck.sourcePptxIdfield is additive; patch-mode kicks in automatically, no API changes.Known not-yet-patched
runsarray which the patch path can't yet faithfully serialize. Follow-up: rebuild<p:txBody>fromel.runswhile preserving paragraph-level<a:pPr>.colorandfontFamilyto the patch field set is straightforward; will follow once the contentEditable run handling is sorted.