@office-kit/pptx-dev 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -18
- package/dist/animation-player.js +1 -0
- package/dist/build-cpmt-r9Q.mjs +131 -0
- package/dist/build-cpmt-r9Q.mjs.map +1 -0
- package/dist/cli.mjs +1886 -120
- package/dist/cli.mjs.map +1 -1
- package/dist/editor.css +1 -0
- package/dist/editor.js +162 -0
- package/dist/index.d.mts +36 -1
- package/dist/index.mjs +1 -1
- package/dist/media-player.js +1 -0
- package/dist/src-CI73YbYS.mjs +1741 -0
- package/dist/src-CI73YbYS.mjs.map +1 -0
- package/dist/terminal-client.js +18 -18
- package/dist/worker.mjs +1 -65
- package/dist/worker.mjs.map +1 -1
- package/package.json +7 -6
- package/dist/src-YH6kAXbA.mjs +0 -380
- package/dist/src-YH6kAXbA.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -36,18 +36,95 @@ npm install -D @office-kit/pptx-dev@latest
|
|
|
36
36
|
npm run dev
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Open the local URL printed by the server.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
Open the local URL printed by the server. The default editor lets you edit text,
|
|
40
|
+
move and resize objects, and use the slide, insert and formatting tools directly
|
|
41
|
+
in the preview. Switch between English and Japanese from the editor header.
|
|
42
|
+
Committed edits save automatically; **Save** or Ctrl/Cmd+S saves immediately.
|
|
43
|
+
The browser keeps recovery copies of committed, unsaved changes. After reloading,
|
|
44
|
+
choose **Restore changes** or **Discard recovery copy**. Copies are separate for
|
|
45
|
+
each project and editing tab, and stay in this browser at the same preview URL
|
|
46
|
+
(including its port). A restored copy merges with later source changes like any
|
|
47
|
+
unsaved edit while the same server keeps running; after a server restart, a copy
|
|
48
|
+
of an older deck requires conflict resolution. Text still being typed must be
|
|
49
|
+
committed first.
|
|
50
|
+
|
|
51
|
+
Resize handles follow each object's rotation and keep the opposite corner or edge
|
|
52
|
+
fixed. Hold Shift while resizing to preserve the starting aspect ratio. Undo/Redo
|
|
53
|
+
restores the complete gesture; Escape cancels it. Multiple selected objects share
|
|
54
|
+
a rotation handle that turns the selection around its centre while preserving
|
|
55
|
+
spacing and relative angles. Hold Shift to rotate in 15-degree steps. The four
|
|
56
|
+
corners of a multiple selection resize all selected object positions and dimensions
|
|
57
|
+
proportionally, including rotated objects. Font sizes, line widths and other
|
|
58
|
+
appearance attributes keep their existing values.
|
|
59
|
+
|
|
60
|
+
The text input displays explicit character formatting while editing shapes and
|
|
61
|
+
table cells, and resolves inherited run styles for shapes. Text follows the canvas
|
|
62
|
+
zoom while saved font sizes remain unchanged. Ctrl/Cmd+Z and Ctrl/Cmd+Y undo and
|
|
63
|
+
redo pending text edits and continue through committed formatting changes without
|
|
64
|
+
leaving the text input. Ctrl/Cmd+Shift+Z also redoes edits. Escape cancels pending
|
|
65
|
+
text edits. Japanese composition is kept together
|
|
66
|
+
as one undo step. Paragraph alignment, line spacing, spacing before and after,
|
|
67
|
+
indentation and text direction are visible while editing. Bullets and numbered
|
|
68
|
+
lists show their markers without adding characters to the editable text; nested
|
|
69
|
+
numbered lists keep separate counters by level. Tab / Shift+Tab changes the
|
|
70
|
+
selected list paragraphs' levels; Ctrl/Cmd+[ / ] also changes paragraph levels,
|
|
71
|
+
including inside table cells. Tab in tables still moves to the next cell.
|
|
72
|
+
Picture bullets, custom bullet
|
|
73
|
+
font/size overrides, vertical text placement and inherited table character styles
|
|
74
|
+
still use the presentation renderer after committing the edit.
|
|
75
|
+
|
|
76
|
+
Selected text copied or cut within the editor keeps its character formatting
|
|
77
|
+
when pasted into another shape or table cell, including text still being edited.
|
|
78
|
+
Ctrl/Cmd+Shift+V pastes plain text (the browser may request clipboard access).
|
|
79
|
+
Copy also supplies HTML for other applications. Pasting external HTML imports
|
|
80
|
+
inline bold, italic, underline, strike, font, size, color, highlight and baseline
|
|
81
|
+
formatting when its text matches the plain-text clipboard. Unsupported markup
|
|
82
|
+
falls back to plain text; spreadsheet tables keep their cell-aware paste behavior.
|
|
83
|
+
Paragraph styles, hyperlinks, stylesheets and theme inheritance are not transferred.
|
|
84
|
+
|
|
85
|
+
The editor fills the window; the preview adds no header of its own. **Download**
|
|
86
|
+
(Quick Access Toolbar ⋯) exports the edited PPTX, and build progress and errors
|
|
87
|
+
appear in the editor's title bar. **View ▸ Reading View** (or the Reading View
|
|
88
|
+
button in the status bar) switches to the rendered viewer with thumbnails, zoom
|
|
89
|
+
and presentation mode; **Normal** in its status bar, or Esc, returns to the
|
|
90
|
+
editor. The Slide Show tab starts the presentation and presenter view. Presentation playback animates
|
|
91
|
+
fade, push, wipe, cover, uncover, and zoom using the saved speed and direction.
|
|
92
|
+
Other effects currently switch immediately. Reduced-motion preferences disable
|
|
93
|
+
these animations; automatic slide timing starts after each animation completes.
|
|
94
|
+
|
|
95
|
+
The viewer has a vertical thumbnail strip, a large slide canvas and a status bar
|
|
96
|
+
with the slide count, build status, Present, Presenter view, Download PPTX,
|
|
97
|
+
Agents, Normal and zoom. Click a thumbnail or use arrow keys, Page Up/Down,
|
|
98
|
+
Home/End to navigate; Fit/zoom and Present (Escape to exit) are viewing controls. Click an
|
|
99
|
+
object or drag an area for an AI instruction.
|
|
100
|
+
|
|
101
|
+
Canvas edits are saved beside the entry in `.office-kit/<entry-name>.editor.zip`;
|
|
102
|
+
for example, `.office-kit/deck.tsx.editor.zip`. Keep this file with your project:
|
|
103
|
+
it contains the edited presentation and the source build it was edited from.
|
|
104
|
+
Canvas edits do not rewrite TSX. CLI `build`, `buildDeck` and `exportDeck` use
|
|
105
|
+
the saved edited presentation, including after restarting the server.
|
|
106
|
+
|
|
107
|
+
Saving a slide file, `theme.ts` or `deck.tsx` rebuilds the source. Changes from
|
|
108
|
+
TSX merge with canvas edits, saved or not yet saved: shapes, slide settings,
|
|
109
|
+
media and links changed on only one side are combined, and the editor reloads
|
|
110
|
+
the merged presentation, which starts a new Undo history. The editor asks you
|
|
111
|
+
to choose **Keep my edits** or **Use source** only when the same item was
|
|
112
|
+
changed in both places, such as one shape edited on the canvas and in TSX, a
|
|
113
|
+
shape deleted on one side and changed on the other, or slides added, removed or
|
|
114
|
+
reordered on both sides. The message names each collision, for example
|
|
115
|
+
"Slide 2: Title 1 was changed both here and in the source." Keeping edits keeps
|
|
116
|
+
the entire edited deck without the TSX changes; using source discards the saved
|
|
117
|
+
canvas version. You can download the source version before choosing. CLI export
|
|
118
|
+
refuses an unresolved conflict so that it cannot silently export the wrong
|
|
119
|
+
version. Editor files from versions before merging was available cannot merge
|
|
120
|
+
until the conflict is resolved once.
|
|
121
|
+
|
|
122
|
+
Keep the server running throughout the edit/review loop. In Preview mode, saving
|
|
123
|
+
updates changed thumbnails and slides while preserving zoom, scroll and
|
|
124
|
+
presentation mode. Rapid source edits cancel obsolete evaluations; only the latest successful result
|
|
125
|
+
is published. Syntax and
|
|
126
|
+
runtime errors remain visible alongside the last successful preview; DSL errors
|
|
127
|
+
include the TSX element's source file and line number.
|
|
51
128
|
|
|
52
129
|
```sh
|
|
53
130
|
npm run check
|
|
@@ -72,7 +149,10 @@ slides. Update time depends on the deck size and any code it runs.
|
|
|
72
149
|
|
|
73
150
|
## Agents in the preview
|
|
74
151
|
|
|
75
|
-
|
|
152
|
+
**Agents** at the right end of the editor's tab row, beside Comments and Share,
|
|
153
|
+
opens the Agents task pane docked on the right (closed by default, like
|
|
154
|
+
PowerPoint's panes; × closes it). The pane follows the editor's light or dark
|
|
155
|
+
appearance, including the terminal colors. Each agent defaults to **Claude Code**. Click **Start** to open the locally
|
|
76
156
|
installed `claude` CLI in an interactive terminal. Install and sign in to Claude
|
|
77
157
|
Code first. Use a current version with HTTP `UserPromptSubmit` hook support.
|
|
78
158
|
The session runs in the deck entry's directory and uses your normal account,
|
|
@@ -131,22 +211,22 @@ each request. The dev server retains up to 100 messages until it exits.
|
|
|
131
211
|
sends a message. Model/settings menus in the embedded terminal are currently
|
|
132
212
|
specific to Claude Code.
|
|
133
213
|
|
|
134
|
-
Use the **Split right** or **Split down** icons in
|
|
214
|
+
Use the **Split right** or **Split down** icons in an agent's toolbar to run up to
|
|
135
215
|
four agents side by side or stacked. Each pane independently selects Claude Code
|
|
136
216
|
or Codex, retains its own conversation and receives the currently selected slide.
|
|
137
217
|
Drag the divider to adjust the split, or focus it and use the arrow keys. Layout
|
|
138
218
|
and Claude terminal sessions survive a browser reload. Splitting preserves running
|
|
139
|
-
sessions and unsent drafts. **Close** ends only that
|
|
219
|
+
sessions and unsent drafts. **Close agent** ends only that agent; closing the last
|
|
140
220
|
pane opens a fresh one. All agents edit the same project directory, so assign
|
|
141
221
|
separate slides or files when working simultaneously.
|
|
142
222
|
|
|
143
|
-
Drag the left edge of the
|
|
223
|
+
Drag the left edge of the Agents pane to adjust its width. The preview remembers the
|
|
144
224
|
width in this browser; double-click the edge to reset it. You can also focus the
|
|
145
225
|
edge and use the left/right arrow keys (Shift for larger steps), or Home/End for
|
|
146
226
|
the minimum/maximum width. Narrow screens keep the stacked layout.
|
|
147
227
|
|
|
148
|
-
|
|
149
|
-
moves below the
|
|
228
|
+
The pane stays open across reloads of the same tab. On narrow screens it floats
|
|
229
|
+
over the editor's right edge, and in the viewer it moves below the slide. Presentation mode hides it without ending the session.
|
|
150
230
|
Both providers save TSX directly, which triggers the usual automatic rebuild.
|
|
151
231
|
The server binds to loopback and requires same-origin JSON for terminal control;
|
|
152
232
|
the local context hook uses a separate per-session token.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var fe=["appear","disappear"],me=["fadeIn","appear","flyIn","zoomIn"],pe=["fadeOut","disappear","flyOut","zoomOut"],ye=["spin"],se={appear:"none",disappear:"none",fadeIn:"fade",fadeOut:"fade",flyIn:"fly",flyOut:"fly",zoomIn:"zoom",zoomOut:"zoom",spin:"spin"},ge=360,te=t=>t instanceof HTMLElement||t instanceof SVGElement?t:null,he=t=>t.effect===null?null:me.includes(t.effect)?"entrance":pe.includes(t.effect)?"exit":ye.includes(t.effect)?"emphasis":null,be=t=>t.durationMs!==null&&Number.isFinite(t.durationMs)&&t.durationMs>=0?t.durationMs:t.effect!==null&&fe.includes(t.effect)?0:null,Se=t=>{let m=[],S=[],r=(l,E)=>{S.push({step:l,reason:E})},p=null,c=0,i=0,a=!1;for(let l of t??[]){if(l.sequence!=="mainSeq"){r(l,"notInMainSeq");continue}let E=!a;if(a=!0,l.start==="unknown"){c=null,i=null,r(l,"notModelled");continue}l.start==="click"||E?(p={items:[],auto:E&&l.start!=="click"},m.push(p),c=0,i=0):l.start==="afterPrevious"&&(c=i,i=c);let h=l.playable?he(l):null,I=h===null?null:be(l),M=l.delayMs;if(h===null||M===null||c===null){r(l,h===null||M===null?"notModelled":"unknownTiming"),i=null;continue}let T=c+M;p?.items.push({step:l,kind:h,begin:T,duration:I}),i=I===null||i===null?null:Math.max(i,T+I)}return{stops:m,unsupported:S}},le=t=>`[data-pptx-shape-id="${t}"]`,oe=(t,m,S)=>{let r=m.target;if(r.kind!=="shape"&&r.kind!=="paragraphs")return[];if(!Number.isInteger(r.shapeId))return[];let p=String(r.shapeId);if(S.has(p))return[];let c=t.querySelectorAll(le(p)),i=c.length===1?te(c[0]):null;if(i===null)return[];if(r.kind!=="paragraphs")return[i];let a=[];for(let l of i.querySelectorAll("[data-pptx-paragraph]")){if(l.closest("[data-pptx-shape-id]")!==i)continue;let E=Number(l.getAttribute("data-pptx-paragraph"));if(E<r.firstParagraph||E>r.lastParagraph)continue;let h=te(l);h!==null&&a.push(h)}return a},re=()=>typeof performance=="object"?performance.now():Date.now(),Ee=(t,m)=>{let S=t.closest("svg");return S!==null?S.getBoundingClientRect():m instanceof Element?m.getBoundingClientRect():null},ve=t=>{if(t instanceof SVGGraphicsElement)return t.getScreenCTM();let m=t.closest("foreignObject");return m instanceof SVGGraphicsElement?m.getScreenCTM():null},Ae=(t,m,S)=>{let r=Ee(t,S);if(r===null||r.width<=0||r.height<=0)return null;let p=t.getBoundingClientRect(),c=m==="right"?r.right-p.left:m==="left"?r.left-p.right:0,i=m==="bottom"?r.bottom-p.top:m==="top"?r.top-p.bottom:0,a=ve(t);if(a===null)return{dx:c,dy:i};let l=a.a*a.d-a.b*a.c;return l===0?null:{dx:(a.d*c-a.c*i)/l,dy:(a.a*i-a.b*c)/l}},Ie=t=>t.step.effect!==null&&se[t.step.effect]==="fly",xe=(t,m,S)=>{let r=t.step.effect,p=t.kind==="entrance";switch(r===null?"none":se[r]??"none"){case"none":return null;case"fade":return{frames:p?[{opacity:0},{opacity:1}]:[{opacity:1},{opacity:0}],aboutOwnCentre:!1};case"zoom":{let c={scale:"0"},i={scale:"1"};return{frames:p?[c,i]:[i,c],aboutOwnCentre:!0}}case"spin":return{frames:[{rotate:"0deg"},{rotate:`${ge}deg`}],aboutOwnCentre:!0};case"fly":{let c=t.step.direction;if(c===null)return null;let i=Ae(m,c,S);if(i===null)return null;let a={translate:`${i.dx}px ${i.dy}px`},l={translate:"0px 0px"};return{frames:p?[a,l]:[l,a],aboutOwnCentre:!1}}}},Me=t=>{let m=()=>typeof t.root=="function"?t.root():t.root,S=Se(t.steps),r=S.stops,p=[...S.unsupported],c=()=>t.reducedMotion?.()===!0,i=t.onChange??(()=>{}),a=[],l=[],E=new Set,h=new Map,I=new Map,M=new Map,T=new Set,g=0,P=!1,O=null,k=0,R=new Set;for(let e of p)for(let n of e.step.targetShapeIds)R.add(String(n));let z=new Map;for(let e of r)for(let n of e.items){let s=String(n.step.target.shapeId);z.has(s)||z.set(s,m().querySelectorAll(le(s)).length)}for(let e of r)for(let n of e.items)(z.get(String(n.step.target.shapeId))??0)>1&&p.push({step:n.step,reason:"ambiguousTarget"});for(let[e,n]of z)n>1&&R.add(e);let ie=e=>{let n=new Set;for(let s of r){let f=new Map;for(let o of s.items){let u=o.duration===null?Number.POSITIVE_INFINITY:o.begin+o.duration;for(let y of oe(e,o.step,R)){let d={item:o,begin:o.begin,end:u},b=f.get(y);b===void 0?f.set(y,[d]):b.push(d)}}for(let o of f.values()){if(o.length<2)continue;o.sort((d,b)=>d.begin-b.begin);let u=Number.NEGATIVE_INFINITY;for(let d of o)d.begin<u&&n.add(d.item),u=Math.max(u,d.end);let y=Number.POSITIVE_INFINITY;for(let d=o.length-1;d>=0;d-=1){let b=o[d];b!==void 0&&(b.end>y&&n.add(b.item),y=Math.min(y,b.begin))}}}for(let s of r){let f=0;for(let o of s.items){if(o.step.valueAfterEnd==="unstated"&&n.has(o)){p.push({step:o.step,reason:"notModelled"});for(let u of o.step.targetShapeIds)R.add(String(u));continue}s.items[f]=o,f+=1}s.items.length=f}},C=()=>{for(let e of l)clearTimeout(e);l.length=0;for(let e of a)e.cancel();a.length=0;for(let e of I.values())for(let n of e)n.cancel();I.clear(),h.clear(),M.clear(),T.clear()},$=(e,n)=>{let s=e.indexOf(n);s>=0&&e.splice(s,1)},Y=()=>{P||(l.length===0&&a.length===0&&(O=null),i(x))},ae=e=>{E.add(e),e.style.transformBox="fill-box",e.style.transformOrigin="50% 50%"},U=e=>{e.style.transformBox="",e.style.transformOrigin=""},ue=e=>{E.add(e),e.style.visibility="",e.style.opacity="",U(e)},de=e=>{E.add(e),e.style.visibility="hidden",e.style.opacity="",U(e)},H=null,V=!0,w=new Map,B=new Map,K=0,L=e=>e.stopIndex<K||T.has(e.item),X=()=>{for(let e of w.values()){e.top=null;for(let s of e.gates)L(s)&&(e.top=s);let n=e.gates[0];e.firstPlayed=n!==void 0&&L(n)}},G=()=>{let e=m(),n=w.keys().next();if(!(e===H&&n.done!==!0&&n.value.isConnected&&V)){H=e,V=!0,ie(e),w=new Map,B=new Map,r.forEach((s,f)=>{for(let o of s.items){let u=[];for(let y of oe(e,o.step,R)){let d=w.get(y)??{list:[],gates:[],top:null,firstPlayed:!1};w.set(y,d);let b={item:o,stopIndex:f,order:0};d.list.push(b),o.kind!=="emphasis"&&d.gates.push(b);let A=xe(o,y,e);A===null&&Ie(o)&&(V=!1),u.push({el:y,plan:d,entry:b,movement:A})}B.set(o,u)}});for(let s of w.values()){let f=(o,u)=>o.stopIndex-u.stopIndex||o.item.begin-u.item.begin;s.list.sort(f),s.gates.sort(f),s.list.forEach((o,u)=>{o.order=u})}X()}},ce=e=>{let n=e.gates[0];return n===void 0?!0:n.item.kind==="entrance"&&!e.firstPlayed?!1:e.top===null||e.top.item.kind!=="exit"},J=(e,n)=>{h.has(e)||(ce(n)?ue(e):de(e))},F=e=>{T.clear(),K=e,G(),X();for(let[n,s]of w)J(n,s)},j=e=>{let{plan:n,entry:s}=e;s.item.kind!=="emphasis"&&(s===n.gates[0]&&(n.firstPlayed=!0),(n.top===null||s.order>n.top.order)&&(n.top=s)),J(e.el,n)},D=e=>{T.add(e),G();for(let n of B.get(e)??[])j(n)},Q=(e,n)=>{G();let s=B.get(e)??[];if(s.length===0){D(e);return}let f=s.filter(A=>A.movement!==null),o=e.duration,u=Math.max(0,n),y=o===null||o===0||u>=o,d=e.step.valueAfterEnd!=="removed";if(c()||f.length===0||y&&(!d||!f.some(A=>h.has(A.el)))){D(e);return}for(let A of s)A.movement===null&&j(A);let b=y?0:o;M.set(e,f.length);for(let A of f){let v=A.el,ee=A.movement;E.add(v),e.kind!=="emphasis"&&(v.style.visibility=""),ee.aboutOwnCentre&&ae(v),h.set(v,(h.get(v)??0)+1);let N=v.animate(ee.frames,{duration:b,fill:"both"});N.currentTime=y?b:u,a.push(N),N.finished.then(()=>{if(P)return;$(a,N);let _=(h.get(v)??1)-1;if(_<=0||!d){if(_<=0){h.delete(v);for(let q of I.get(v)??[])q.cancel();I.delete(v)}else h.set(v,_);N.cancel()}else{h.set(v,_);let q=I.get(v);q===void 0?I.set(v,[N]):q.push(N)}j(A);let ne=(M.get(e)??1)-1;M.set(e,ne),ne<=0&&D(e),Y()},()=>{})}},W=(e,n)=>{let s=r[e];if(s===void 0)return;let f=Math.max(0,n);O=re()-f;let o=[...s.items].sort((u,y)=>u.begin-y.begin);for(let u of o){let y=u.begin-f;if(c()||y<=0){Q(u,f-u.begin);continue}let d=setTimeout(()=>{$(l,d),Q(u,0),Y()},y);l.push(d)}},Z=e=>Math.max(0,Math.min(Number.isFinite(e)?Math.round(e):0,r.length)),x={get cursor(){return g},get stopCount(){return r.length},get pending(){return g<r.length},get running(){return l.length>0||a.length>0},get progress(){return{cursor:g,stops:r.length,elapsed:O===null?null:Math.round(re()-O),generation:k}},get unsupported(){return[...p]},reset(){k+=1,C(),O=null,g=0,F(0),i(x),r[0]?.auto===!0&&x.advance()},advance(){if(P||g>=r.length)return!1;k+=1,C(),F(g);let e=g;return g+=1,W(e,0),i(x),!0},back(){return P||g<=0?!1:(k+=1,C(),O=null,g-=1,F(g),i(x),!0)},jumpTo(e){P||(k+=1,C(),O=null,g=Z(e),F(g),i(x))},resume(e,n){P||(k+=1,C(),O=null,g=Z(e),g>0&&n!==null&&Number.isFinite(n)?(F(g-1),W(g-1,n)):F(g),i(x))},finish(){x.jumpTo(r.length)},dispose(){if(!P){P=!0,C();for(let e of E)e.style.visibility="",e.style.opacity="",U(e);E.clear()}}};return G(),x};export{Se as buildAnimationStops,Me as createAnimationPlayer};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { unzipSync } from "fflate";
|
|
5
|
+
import { compile, getShapeJsxSources } from "@office-kit/pptx-dsl";
|
|
6
|
+
import { findShapesWithMedia, getCustomShows, getGroupChildren, getShapeBounds, getShapeId, getShapeMedia, getShapeMediaPlayback, getSlideAnimations, getSlideNotes, getSlidePartName, getSlideShapes, getSlideShowProperties, getSlideSize, getSlideText, getSlideTransition, getSlides, isSlideHidden, loadPresentation, savePresentation, validatePresentation } from "@office-kit/pptx";
|
|
7
|
+
import { renderSlideToSvg } from "@office-kit/pptx-preview";
|
|
8
|
+
//#region src/preview-cache.ts
|
|
9
|
+
/** Reuse SVG only when both slide XML and every shared rendering resource match. */
|
|
10
|
+
function renderPreview(presentation, bytes, previous) {
|
|
11
|
+
const slides = getSlides(presentation);
|
|
12
|
+
const names = slides.map((slide) => getSlidePartName(slide).replace(/^\//, ""));
|
|
13
|
+
const slideParts = new Set(names);
|
|
14
|
+
const parts = unzipSync(bytes);
|
|
15
|
+
const hashes = new Map(Object.keys(parts).sort().map((name) => [name, createHash("sha256").update(parts[name]).digest("hex")]));
|
|
16
|
+
const shared = createHash("sha256").update(JSON.stringify([...hashes].filter(([name]) => !slideParts.has(name) && !name.startsWith("docProps/")))).digest("hex");
|
|
17
|
+
const cache = {
|
|
18
|
+
shared,
|
|
19
|
+
slides: {}
|
|
20
|
+
};
|
|
21
|
+
const rendered = slides.map((slide, index) => {
|
|
22
|
+
const name = names[index];
|
|
23
|
+
const hash = hashes.get(name);
|
|
24
|
+
const cached = previous?.shared === shared ? previous.slides[name] : void 0;
|
|
25
|
+
const result = cached?.hash === hash ? cached : {
|
|
26
|
+
hash,
|
|
27
|
+
svg: renderSlideToSvg(presentation, slide),
|
|
28
|
+
text: getSlideText(slide)
|
|
29
|
+
};
|
|
30
|
+
cache.slides[name] = result;
|
|
31
|
+
return result;
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
slides: rendered.map((slide) => slide.svg),
|
|
35
|
+
slideTexts: rendered.map((slide) => slide.text),
|
|
36
|
+
cache
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/media-manifest.ts
|
|
41
|
+
const dataUrl = (contentType, bytes) => `data:${contentType};base64,${Buffer.from(bytes).toString("base64")}`;
|
|
42
|
+
/**
|
|
43
|
+
* Collects media shapes without changing the rendered SVG. Embedded clips are
|
|
44
|
+
* made self-contained data URLs so the dev server does not need to expose raw
|
|
45
|
+
* OPC package parts; the presentation page can replace the matching preview
|
|
46
|
+
* shape with an HTML media element using the stable shape id.
|
|
47
|
+
*/
|
|
48
|
+
const getPreviewMedia = (presentation) => getSlides(presentation).flatMap((slide, slideIndex) => findShapesWithMedia(slide).flatMap((shape) => {
|
|
49
|
+
const media = getShapeMedia(shape);
|
|
50
|
+
const bounds = getShapeBounds(shape);
|
|
51
|
+
if (media === null || bounds === null) return [];
|
|
52
|
+
const embedded = media.kind !== "online";
|
|
53
|
+
return [{
|
|
54
|
+
slideIndex,
|
|
55
|
+
shapeId: getShapeId(shape),
|
|
56
|
+
kind: media.kind,
|
|
57
|
+
src: embedded ? dataUrl(media.contentType, media.bytes) : media.url,
|
|
58
|
+
...embedded ? { contentType: media.contentType } : {},
|
|
59
|
+
bounds,
|
|
60
|
+
playback: embedded ? getShapeMediaPlayback(shape) : null
|
|
61
|
+
}];
|
|
62
|
+
}));
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/build.ts
|
|
65
|
+
/** Evaluates trusted local TSX. This is code execution, not a sandbox. */
|
|
66
|
+
async function buildDeck(directory, dependencies, previous) {
|
|
67
|
+
const module = await import(pathToFileURL(join(directory, "deck.mjs")).href);
|
|
68
|
+
if (!module.default) throw new Error("The TSX file must default-export a Presentation.");
|
|
69
|
+
const presentation = await compile(module.default);
|
|
70
|
+
const errors = validatePresentation(presentation).filter((issue) => issue.severity === "error");
|
|
71
|
+
if (errors.length) throw new Error(`Invalid presentation: ${JSON.stringify(errors)}`);
|
|
72
|
+
const shapeSources = jsxSourcesBySlide(presentation);
|
|
73
|
+
const rendered = await renderDeck(await savePresentation(presentation), dependencies, previous);
|
|
74
|
+
return {
|
|
75
|
+
...rendered,
|
|
76
|
+
result: {
|
|
77
|
+
...rendered.result,
|
|
78
|
+
shapeSources
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function jsxSourcesBySlide(presentation) {
|
|
83
|
+
const result = {};
|
|
84
|
+
getSlides(presentation).forEach((slide, index) => {
|
|
85
|
+
const byId = {};
|
|
86
|
+
const visit = (shape) => {
|
|
87
|
+
const sources = getShapeJsxSources(shape);
|
|
88
|
+
if (sources) byId[getShapeId(shape)] = sources;
|
|
89
|
+
for (const child of getGroupChildren(shape)) visit(child);
|
|
90
|
+
};
|
|
91
|
+
for (const shape of getSlideShapes(slide)) visit(shape);
|
|
92
|
+
result[index] = byId;
|
|
93
|
+
});
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
async function renderDeck(bytes, dependencies, previous) {
|
|
97
|
+
const saved = await loadPresentation(bytes);
|
|
98
|
+
const diagnostics = validatePresentation(saved);
|
|
99
|
+
const errors = diagnostics.filter((issue) => issue.severity === "error");
|
|
100
|
+
if (errors.length) throw new Error(`Invalid presentation: ${JSON.stringify(errors)}`);
|
|
101
|
+
const size = getSlideSize(saved);
|
|
102
|
+
const { slides, slideTexts, cache } = renderPreview(saved, bytes, previous);
|
|
103
|
+
const deckSlides = getSlides(saved);
|
|
104
|
+
const slideIndices = new Map(deckSlides.map((slide, index) => [slide, index]));
|
|
105
|
+
return {
|
|
106
|
+
cache,
|
|
107
|
+
result: {
|
|
108
|
+
bytes,
|
|
109
|
+
aspectRatio: size ? size.width / size.height : 16 / 9,
|
|
110
|
+
slides,
|
|
111
|
+
dependencies,
|
|
112
|
+
slideTexts,
|
|
113
|
+
notes: getSlides(saved).map(getSlideNotes),
|
|
114
|
+
hiddenSlides: getSlides(saved).map(isSlideHidden),
|
|
115
|
+
transitions: getSlides(saved).map(getSlideTransition),
|
|
116
|
+
animations: getSlides(saved).map(getSlideAnimations),
|
|
117
|
+
showProperties: getSlideShowProperties(saved),
|
|
118
|
+
customShows: getCustomShows(saved).map((show) => ({
|
|
119
|
+
id: show.id,
|
|
120
|
+
name: show.name,
|
|
121
|
+
slideIndices: show.slides.map((slide) => slideIndices.get(slide))
|
|
122
|
+
})),
|
|
123
|
+
media: getPreviewMedia(saved),
|
|
124
|
+
diagnostics
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { renderDeck as n, buildDeck as t };
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=build-cpmt-r9Q.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-cpmt-r9Q.mjs","names":[],"sources":["../src/preview-cache.ts","../src/media-manifest.ts","../src/build.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { unzipSync } from 'fflate';\nimport { getSlidePartName, getSlides, getSlideText, type PresentationData } from '@office-kit/pptx';\nimport { renderSlideToSvg } from '@office-kit/pptx-preview';\n\nexport interface PreviewCache {\n shared: string;\n slides: Record<string, { hash: string; svg: string; text: string }>;\n}\n\n/** Reuse SVG only when both slide XML and every shared rendering resource match. */\nexport function renderPreview(\n presentation: PresentationData,\n bytes: Uint8Array,\n previous?: PreviewCache,\n) {\n const slides = getSlides(presentation);\n const names = slides.map((slide) => getSlidePartName(slide).replace(/^\\//, ''));\n const slideParts = new Set(names);\n const parts = unzipSync(bytes);\n const hashes = new Map(\n Object.keys(parts)\n .sort()\n .map((name) => [name, createHash('sha256').update(parts[name]!).digest('hex')]),\n );\n // Relationships, themes, layouts, charts and media all invalidate the cache.\n // Document metadata changes on each save but cannot affect slide rendering.\n const shared = createHash('sha256')\n .update(\n JSON.stringify(\n [...hashes].filter(([name]) => !slideParts.has(name) && !name.startsWith('docProps/')),\n ),\n )\n .digest('hex');\n const cache: PreviewCache = { shared, slides: {} };\n const rendered = slides.map((slide, index) => {\n const name = names[index]!;\n const hash = hashes.get(name)!;\n const cached = previous?.shared === shared ? previous.slides[name] : undefined;\n const result =\n cached?.hash === hash\n ? cached\n : { hash, svg: renderSlideToSvg(presentation, slide), text: getSlideText(slide) };\n cache.slides[name] = result;\n return result;\n });\n return {\n slides: rendered.map((slide) => slide.svg),\n slideTexts: rendered.map((slide) => slide.text),\n cache,\n };\n}\n","import {\n findShapesWithMedia,\n getShapeBounds,\n getShapeId,\n getShapeMedia,\n getShapeMediaPlayback,\n type MediaPlayback,\n type ShapeBounds,\n type ShapeMedia,\n} from '@office-kit/pptx';\nimport { getSlides } from '@office-kit/pptx';\n\n/** Media information exposed to the dev preview for a single slide shape. */\nexport interface PreviewMedia {\n readonly slideIndex: number;\n readonly shapeId: number;\n readonly kind: ShapeMedia['kind'];\n /** A data URL for embedded media, or the external URL for online media. */\n readonly src: string;\n /** Present for embedded media and suitable for an HTML media element. */\n readonly contentType?: string;\n /** Original shape geometry in EMUs. */\n readonly bounds: ShapeBounds;\n readonly playback: MediaPlayback | null;\n}\n\nconst dataUrl = (contentType: string, bytes: Uint8Array): string =>\n `data:${contentType};base64,${Buffer.from(bytes).toString('base64')}`;\n\n/**\n * Collects media shapes without changing the rendered SVG. Embedded clips are\n * made self-contained data URLs so the dev server does not need to expose raw\n * OPC package parts; the presentation page can replace the matching preview\n * shape with an HTML media element using the stable shape id.\n */\nexport const getPreviewMedia = (presentation: Parameters<typeof getSlides>[0]): PreviewMedia[] =>\n getSlides(presentation).flatMap((slide, slideIndex) =>\n findShapesWithMedia(slide).flatMap((shape) => {\n const media = getShapeMedia(shape);\n const bounds = getShapeBounds(shape);\n if (media === null || bounds === null) return [];\n const embedded = media.kind !== 'online';\n return [\n {\n slideIndex,\n shapeId: getShapeId(shape),\n kind: media.kind,\n src: embedded ? dataUrl(media.contentType, media.bytes) : media.url,\n ...(embedded ? { contentType: media.contentType } : {}),\n bounds,\n playback: embedded ? getShapeMediaPlayback(shape) : null,\n },\n ];\n }),\n );\n","import { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { compile, getShapeJsxSources, type JsxSource, type Node } from '@office-kit/pptx-dsl';\nimport {\n getGroupChildren,\n getShapeId,\n getSlideShapes,\n type PresentationData,\n type SlideShapeData,\n getSlideAnimations,\n getCustomShows,\n getSlideShowProperties,\n getSlideSize,\n getSlides,\n getSlideNotes,\n isSlideHidden,\n getSlideTransition,\n loadPresentation,\n savePresentation,\n validatePresentation,\n} from '@office-kit/pptx';\nimport { renderPreview, type PreviewCache } from './preview-cache.ts';\nimport { getPreviewMedia, type PreviewMedia } from './media-manifest.ts';\n\nexport interface BuildResult {\n bytes: Uint8Array;\n slides: string[];\n slideTexts: string[];\n notes: (string | null)[];\n hiddenSlides: boolean[];\n transitions: ReturnType<typeof getSlideTransition>[];\n /** What each slide animates, in click order — the preview's player reads this. */\n animations: ReturnType<typeof getSlideAnimations>[];\n showProperties: ReturnType<typeof getSlideShowProperties>;\n customShows: { id: number; name: string; slideIndices: number[] }[];\n /** Media clips keyed by slide and shape id for HTML playback in preview mode. */\n media: PreviewMedia[];\n aspectRatio: number;\n dependencies: string[];\n /**\n * Slide index → shape id → the JSX elements being evaluated when the shape\n * was made, outermost first. Only decks built from TSX have it; a saved\n * editor deck has no source to point at.\n */\n shapeSources?: Record<number, Record<number, readonly JsxSource[]>>;\n diagnostics: ReturnType<typeof validatePresentation>;\n}\n/** Evaluates trusted local TSX. This is code execution, not a sandbox. */\nexport async function buildDeck(\n directory: string,\n dependencies: string[],\n previous?: PreviewCache,\n): Promise<{ result: BuildResult; cache: PreviewCache }> {\n const output = join(directory, 'deck.mjs');\n const module: { default?: Node } = await import(pathToFileURL(output).href);\n if (!module.default) throw new Error('The TSX file must default-export a Presentation.');\n const presentation = await compile(module.default);\n const diagnostics = validatePresentation(presentation);\n const errors = diagnostics.filter((issue) => issue.severity === 'error');\n if (errors.length) throw new Error(`Invalid presentation: ${JSON.stringify(errors)}`);\n const shapeSources = jsxSourcesBySlide(presentation);\n const bytes = await savePresentation(presentation);\n // Preview serialized output too, so persistence defects are visible during authoring.\n const rendered = await renderDeck(bytes, dependencies, previous);\n return { ...rendered, result: { ...rendered.result, shapeSources } };\n}\n\n// Shape ids survive saving, so sources read before `savePresentation` still\n// name the shapes of the saved deck the editor loads.\nfunction jsxSourcesBySlide(\n presentation: PresentationData,\n): Record<number, Record<number, readonly JsxSource[]>> {\n const result: Record<number, Record<number, readonly JsxSource[]>> = {};\n getSlides(presentation).forEach((slide, index) => {\n const byId: Record<number, readonly JsxSource[]> = {};\n const visit = (shape: SlideShapeData) => {\n const sources = getShapeJsxSources(shape);\n if (sources) byId[getShapeId(shape)] = sources;\n for (const child of getGroupChildren(shape)) visit(child);\n };\n for (const shape of getSlideShapes(slide)) visit(shape);\n result[index] = byId;\n });\n return result;\n}\n\nexport async function renderDeck(\n bytes: Uint8Array,\n dependencies: string[],\n previous?: PreviewCache,\n): Promise<{ result: BuildResult; cache: PreviewCache }> {\n const saved = await loadPresentation(bytes);\n const diagnostics = validatePresentation(saved);\n const errors = diagnostics.filter((issue) => issue.severity === 'error');\n if (errors.length) throw new Error(`Invalid presentation: ${JSON.stringify(errors)}`);\n const size = getSlideSize(saved);\n const { slides, slideTexts, cache } = renderPreview(saved, bytes, previous);\n const deckSlides = getSlides(saved);\n const slideIndices = new Map(deckSlides.map((slide, index) => [slide, index]));\n return {\n cache,\n result: {\n bytes,\n aspectRatio: size ? size.width / size.height : 16 / 9,\n slides,\n dependencies,\n slideTexts,\n notes: getSlides(saved).map(getSlideNotes),\n hiddenSlides: getSlides(saved).map(isSlideHidden),\n transitions: getSlides(saved).map(getSlideTransition),\n animations: getSlides(saved).map(getSlideAnimations),\n showProperties: getSlideShowProperties(saved),\n customShows: getCustomShows(saved).map((show) => ({\n id: show.id,\n name: show.name,\n slideIndices: show.slides.map((slide) => slideIndices.get(slide)!),\n })),\n media: getPreviewMedia(saved),\n diagnostics,\n },\n };\n}\n"],"mappings":"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"}
|