pptx-react-viewer 1.1.4 → 1.1.5

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.
Files changed (28) hide show
  1. package/dist/index.js +512 -96
  2. package/dist/index.mjs +512 -97
  3. package/dist/viewer/index.js +530 -102
  4. package/dist/viewer/index.mjs +530 -103
  5. package/node_modules/emf-converter/dist/index.d.mts +2 -2
  6. package/node_modules/emf-converter/dist/index.d.ts +2 -2
  7. package/node_modules/emf-converter/dist/index.js +91 -33
  8. package/node_modules/emf-converter/dist/index.mjs +91 -33
  9. package/node_modules/emf-converter/package.json +1 -1
  10. package/node_modules/mtx-decompressor/dist/index.js +39 -9
  11. package/node_modules/mtx-decompressor/dist/index.mjs +39 -9
  12. package/node_modules/mtx-decompressor/package.json +1 -1
  13. package/node_modules/pptx-viewer-core/dist/cli/index.js +0 -0
  14. package/node_modules/pptx-viewer-core/dist/cli/index.mjs +0 -0
  15. package/node_modules/pptx-viewer-core/dist/converter/index.js +0 -0
  16. package/node_modules/pptx-viewer-core/dist/converter/index.mjs +0 -0
  17. package/node_modules/pptx-viewer-core/dist/index.d.mts +95 -11
  18. package/node_modules/pptx-viewer-core/dist/index.d.ts +95 -11
  19. package/node_modules/pptx-viewer-core/dist/index.js +795 -257
  20. package/node_modules/pptx-viewer-core/dist/index.mjs +791 -258
  21. package/node_modules/pptx-viewer-core/dist/{signature-inspection-status-BcJSdOvb.d.mts → signature-inspection-status-BCUpfCQh.d.mts} +13 -2
  22. package/node_modules/pptx-viewer-core/dist/{signature-inspection-status-BcJSdOvb.d.ts → signature-inspection-status-BCUpfCQh.d.ts} +13 -2
  23. package/node_modules/pptx-viewer-core/dist/signature-node/index.d.mts +2 -2
  24. package/node_modules/pptx-viewer-core/dist/signature-node/index.d.ts +2 -2
  25. package/node_modules/pptx-viewer-core/dist/signature-node/index.js +17 -3
  26. package/node_modules/pptx-viewer-core/dist/signature-node/index.mjs +16 -4
  27. package/node_modules/pptx-viewer-core/package.json +1 -1
  28. package/package.json +6 -4
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ var clsx = require('clsx');
7
7
  var tailwindMerge = require('tailwind-merge');
8
8
  var lu = require('react-icons/lu');
9
9
  var pptxViewerCore = require('pptx-viewer-core');
10
+ var DOMPurify = require('dompurify');
10
11
  var reactI18next = require('react-i18next');
11
12
  var html2canvasPro = require('html2canvas-pro');
12
13
  var JSZip = require('jszip');
@@ -33,6 +34,7 @@ function _interopNamespace(e) {
33
34
 
34
35
  var React10__namespace = /*#__PURE__*/_interopNamespace(React10);
35
36
  var ReactDOM__namespace = /*#__PURE__*/_interopNamespace(ReactDOM);
37
+ var DOMPurify__default = /*#__PURE__*/_interopDefault(DOMPurify);
36
38
  var html2canvasPro__default = /*#__PURE__*/_interopDefault(html2canvasPro);
37
39
  var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
38
40
 
@@ -43596,7 +43598,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43596
43598
  return x2 === y && (0 !== x2 || 1 / x2 === 1 / y) || x2 !== x2 && y !== y;
43597
43599
  }
43598
43600
  function useSyncExternalStore$2(subscribe3, getSnapshot2) {
43599
- didWarnOld18Alpha || void 0 === React97.startTransition || (didWarnOld18Alpha = true, console.error(
43601
+ didWarnOld18Alpha || void 0 === React100.startTransition || (didWarnOld18Alpha = true, console.error(
43600
43602
  "You are using an outdated, pre-release alpha of React 18 that does not support useSyncExternalStore. The use-sync-external-store shim will not work correctly. Upgrade to a newer pre-release."
43601
43603
  ));
43602
43604
  var value = getSnapshot2();
@@ -43606,7 +43608,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43606
43608
  "The result of getSnapshot should be cached to avoid an infinite loop"
43607
43609
  ), didWarnUncachedGetSnapshot = true);
43608
43610
  }
43609
- cachedValue = useState85({
43611
+ cachedValue = useState86({
43610
43612
  inst: { value, getSnapshot: getSnapshot2 }
43611
43613
  });
43612
43614
  var inst = cachedValue[0].inst, forceUpdate = cachedValue[1];
@@ -43644,8 +43646,8 @@ var require_use_sync_external_store_shim_development = __commonJS({
43644
43646
  return getSnapshot2();
43645
43647
  }
43646
43648
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
43647
- var React97 = __require("react"), objectIs = "function" === typeof Object.is ? Object.is : is2, useState85 = React97.useState, useEffect72 = React97.useEffect, useLayoutEffect7 = React97.useLayoutEffect, useDebugValue = React97.useDebugValue, didWarnOld18Alpha = false, didWarnUncachedGetSnapshot = false, shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
43648
- exports$1.useSyncExternalStore = void 0 !== React97.useSyncExternalStore ? React97.useSyncExternalStore : shim;
43649
+ var React100 = __require("react"), objectIs = "function" === typeof Object.is ? Object.is : is2, useState86 = React100.useState, useEffect72 = React100.useEffect, useLayoutEffect7 = React100.useLayoutEffect, useDebugValue = React100.useDebugValue, didWarnOld18Alpha = false, didWarnUncachedGetSnapshot = false, shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
43650
+ exports$1.useSyncExternalStore = void 0 !== React100.useSyncExternalStore ? React100.useSyncExternalStore : shim;
43649
43651
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
43650
43652
  })();
43651
43653
  }
@@ -43668,7 +43670,7 @@ var require_with_selector_development = __commonJS({
43668
43670
  return x2 === y && (0 !== x2 || 1 / x2 === 1 / y) || x2 !== x2 && y !== y;
43669
43671
  }
43670
43672
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
43671
- var React97 = __require("react"), shim = require_shim(), objectIs = "function" === typeof Object.is ? Object.is : is2, useSyncExternalStore3 = shim.useSyncExternalStore, useRef73 = React97.useRef, useEffect72 = React97.useEffect, useMemo41 = React97.useMemo, useDebugValue = React97.useDebugValue;
43673
+ var React100 = __require("react"), shim = require_shim(), objectIs = "function" === typeof Object.is ? Object.is : is2, useSyncExternalStore3 = shim.useSyncExternalStore, useRef73 = React100.useRef, useEffect72 = React100.useEffect, useMemo41 = React100.useMemo, useDebugValue = React100.useDebugValue;
43672
43674
  exports$1.useSyncExternalStoreWithSelector = function(subscribe3, getSnapshot2, getServerSnapshot2, selector, isEqual) {
43673
43675
  var instRef = useRef73(null);
43674
43676
  if (null === instRef.current) {
@@ -74100,6 +74102,13 @@ function hasDistinctScriptFonts(fonts) {
74100
74102
  }
74101
74103
  return Boolean(fonts.eastAsia) && fonts.eastAsia !== base || Boolean(fonts.complexScript) && fonts.complexScript !== base || Boolean(fonts.symbol) && fonts.symbol !== base;
74102
74104
  }
74105
+ function sanitizeMathMl(markup) {
74106
+ const purify = DOMPurify__default.default;
74107
+ if (typeof purify.sanitize !== "function") {
74108
+ return markup;
74109
+ }
74110
+ return purify.sanitize(markup, { USE_PROFILES: { mathMl: true, svg: true } });
74111
+ }
74103
74112
  function renderScriptAwareText(text2, needsScriptFonts, scriptFonts, baseFontFamily, keyPrefix) {
74104
74113
  if (!needsScriptFonts || !text2) {
74105
74114
  return text2;
@@ -74190,14 +74199,15 @@ function renderSegmentContent(elementId, segmentIndex, textValue, lines, needsSc
74190
74199
  }
74191
74200
  function renderEquationSegment(elementId, segmentIndex, equationXml, equationNumber) {
74192
74201
  const mathml = convertOmmlToMathMl(equationXml);
74193
- const equationContent = mathml ? /* @__PURE__ */ jsxRuntime.jsx(
74202
+ const safeMathml = mathml ? sanitizeMathMl(mathml) : "";
74203
+ const equationContent = safeMathml ? /* @__PURE__ */ jsxRuntime.jsx(
74194
74204
  "span",
74195
74205
  {
74196
74206
  className: "inline-block align-middle",
74197
74207
  style: {
74198
74208
  fontFamily: '"Cambria Math", "STIX Two Math", serif'
74199
74209
  },
74200
- dangerouslySetInnerHTML: { __html: mathml }
74210
+ dangerouslySetInnerHTML: { __html: safeMathml }
74201
74211
  }
74202
74212
  ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block px-1 py-0.5 rounded text-xs bg-gray-200/20 text-gray-400 italic", children: "Equation" });
74203
74213
  if (equationNumber) {
@@ -86554,7 +86564,7 @@ function ResizeHandle({
86554
86564
  }
86555
86565
  );
86556
86566
  }
86557
- function SlideThumbnail({
86567
+ function SlideThumbnailImpl({
86558
86568
  slide,
86559
86569
  templateElements,
86560
86570
  canvasSize
@@ -86792,6 +86802,37 @@ function ThumbnailTable({
86792
86802
  }
86793
86803
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-full flex items-center justify-center text-[10px] text-muted-foreground pointer-events-none", children: "Table" });
86794
86804
  }
86805
+ function arePropsEqual(prev, next) {
86806
+ if (prev.slide.id !== next.slide.id) {
86807
+ return false;
86808
+ }
86809
+ if (prev.slide.isDirty !== next.slide.isDirty) {
86810
+ return false;
86811
+ }
86812
+ if (prev.slide.hidden !== next.slide.hidden) {
86813
+ return false;
86814
+ }
86815
+ if (prev.slide.elements !== next.slide.elements) {
86816
+ return false;
86817
+ }
86818
+ if (prev.slide.backgroundColor !== next.slide.backgroundColor) {
86819
+ return false;
86820
+ }
86821
+ if (prev.slide.backgroundImage !== next.slide.backgroundImage) {
86822
+ return false;
86823
+ }
86824
+ if (prev.slide.backgroundGradient !== next.slide.backgroundGradient) {
86825
+ return false;
86826
+ }
86827
+ if (prev.templateElements !== next.templateElements) {
86828
+ return false;
86829
+ }
86830
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
86831
+ return false;
86832
+ }
86833
+ return true;
86834
+ }
86835
+ var SlideThumbnail = React10__namespace.default.memo(SlideThumbnailImpl, arePropsEqual);
86795
86836
  function ContextMenu({
86796
86837
  contextMenuState,
86797
86838
  mode,
@@ -90525,7 +90566,7 @@ function BendingProcessRenderer({
90525
90566
  }
90526
90567
  );
90527
90568
  }
90528
- function SmartArtRenderer({
90569
+ function SmartArtRendererImpl({
90529
90570
  element: element2,
90530
90571
  className = ""
90531
90572
  }) {
@@ -90636,6 +90677,30 @@ function renderLayout(layoutType, element2, nodes, palette, style) {
90636
90677
  }
90637
90678
  return /* @__PURE__ */ jsxRuntime.jsx(ListRenderer, { element: element2, nodes, palette, style });
90638
90679
  }
90680
+ function arePropsEqual2(prev, next) {
90681
+ if (prev.className !== next.className) {
90682
+ return false;
90683
+ }
90684
+ if (prev.element.id !== next.element.id) {
90685
+ return false;
90686
+ }
90687
+ if (prev.element.type !== next.element.type) {
90688
+ return false;
90689
+ }
90690
+ if (prev.element.width !== next.element.width || prev.element.height !== next.element.height) {
90691
+ return false;
90692
+ }
90693
+ if (prev.element.x !== next.element.x || prev.element.y !== next.element.y) {
90694
+ return false;
90695
+ }
90696
+ const prevData = prev.element.type === "smartArt" ? prev.element.smartArtData : void 0;
90697
+ const nextData = next.element.type === "smartArt" ? next.element.smartArtData : void 0;
90698
+ if (prevData !== nextData) {
90699
+ return false;
90700
+ }
90701
+ return true;
90702
+ }
90703
+ var SmartArtRenderer = React10__namespace.default.memo(SmartArtRendererImpl, arePropsEqual2);
90639
90704
  function ZoomElementRenderer({
90640
90705
  element: element2,
90641
90706
  slides,
@@ -93579,7 +93644,7 @@ function SectionBlock({
93579
93644
  )
93580
93645
  ] });
93581
93646
  }
93582
- function SlideCard({
93647
+ function SlideCardImpl({
93583
93648
  slide,
93584
93649
  index,
93585
93650
  isActive,
@@ -93633,6 +93698,49 @@ function SlideCard({
93633
93698
  }
93634
93699
  );
93635
93700
  }
93701
+ function arePropsEqual3(prev, next) {
93702
+ if (prev.slide.id !== next.slide.id) {
93703
+ return false;
93704
+ }
93705
+ if (prev.slide.isDirty !== next.slide.isDirty) {
93706
+ return false;
93707
+ }
93708
+ if (prev.slide.hidden !== next.slide.hidden) {
93709
+ return false;
93710
+ }
93711
+ if (prev.slide.elements !== next.slide.elements) {
93712
+ return false;
93713
+ }
93714
+ if (prev.index !== next.index) {
93715
+ return false;
93716
+ }
93717
+ if (prev.isActive !== next.isActive) {
93718
+ return false;
93719
+ }
93720
+ if (prev.isDragTarget !== next.isDragTarget) {
93721
+ return false;
93722
+ }
93723
+ if (prev.isSelected !== next.isSelected) {
93724
+ return false;
93725
+ }
93726
+ if (prev.selectedCount !== next.selectedCount) {
93727
+ return false;
93728
+ }
93729
+ if (prev.selectionOrder !== next.selectionOrder) {
93730
+ return false;
93731
+ }
93732
+ if (prev.canEdit !== next.canEdit) {
93733
+ return false;
93734
+ }
93735
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
93736
+ return false;
93737
+ }
93738
+ if (prev.onSlideClick !== next.onSlideClick || prev.onDoubleClick !== next.onDoubleClick || prev.onContextMenu !== next.onContextMenu || prev.onDragStart !== next.onDragStart || prev.onDragOver !== next.onDragOver || prev.onDragLeave !== next.onDragLeave || prev.onDrop !== next.onDrop) {
93739
+ return false;
93740
+ }
93741
+ return true;
93742
+ }
93743
+ var SlideCard = React10__namespace.default.memo(SlideCardImpl, arePropsEqual3);
93636
93744
  function SorterContextMenu({
93637
93745
  x: x2,
93638
93746
  y,
@@ -94426,6 +94534,14 @@ function getCurrentParagraphIndex(editorEl, segments) {
94426
94534
  }
94427
94535
  return paraIdx;
94428
94536
  }
94537
+ var CSS_VALUE_SAFE = /^[a-zA-Z0-9 _,.\-+#'%/]{1,100}$/;
94538
+ function isCssValueSafe(value) {
94539
+ if (value === void 0 || value === null) {
94540
+ return false;
94541
+ }
94542
+ const str = String(value);
94543
+ return str.length > 0 && CSS_VALUE_SAFE.test(str);
94544
+ }
94429
94545
  function deriveStyleFromElement(element2, inheritedStyle) {
94430
94546
  const style = { ...inheritedStyle };
94431
94547
  const tagName = element2.tagName.toLowerCase();
@@ -94558,17 +94674,17 @@ function segmentsToEditorHtml(segments) {
94558
94674
  if (segment.style.strikethrough) {
94559
94675
  inlineStyles.push("text-decoration:line-through");
94560
94676
  }
94561
- if (segment.style.color) {
94677
+ if (segment.style.color && isCssValueSafe(segment.style.color)) {
94562
94678
  inlineStyles.push(`color:${segment.style.color}`);
94563
94679
  }
94564
- if (segment.style.fontSize) {
94565
- inlineStyles.push(`font-size:${segment.style.fontSize}pt`);
94680
+ if (segment.style.fontSize && Number.isFinite(Number(segment.style.fontSize))) {
94681
+ inlineStyles.push(`font-size:${Number(segment.style.fontSize)}pt`);
94566
94682
  }
94567
- if (segment.style.fontFamily) {
94683
+ if (segment.style.fontFamily && isCssValueSafe(segment.style.fontFamily)) {
94568
94684
  inlineStyles.push(`font-family:${segment.style.fontFamily}`);
94569
94685
  }
94570
94686
  const text2 = escapeHtml(segment.text);
94571
- if (segment.style.hyperlink) {
94687
+ if (segment.style.hyperlink && isUrlSafe(segment.style.hyperlink)) {
94572
94688
  const href = escapeHtml(segment.style.hyperlink);
94573
94689
  return `<a href="${href}" style="color:#4a9eff;text-decoration:underline;cursor:pointer" data-hyperlink="${href}">${text2}</a>`;
94574
94690
  }
@@ -94651,7 +94767,8 @@ function renderRichNotesSegments(segments) {
94651
94767
  if (segment.style.fontFamily) {
94652
94768
  style.fontFamily = segment.style.fontFamily;
94653
94769
  }
94654
- if (segment.style.hyperlink) {
94770
+ if (segment.style.hyperlink && isUrlSafe(segment.style.hyperlink)) {
94771
+ const safeHref = segment.style.hyperlink;
94655
94772
  style.color = "#4a9eff";
94656
94773
  style.textDecoration = "underline";
94657
94774
  style.cursor = "pointer";
@@ -94659,11 +94776,11 @@ function renderRichNotesSegments(segments) {
94659
94776
  /* @__PURE__ */ jsxRuntime.jsx(
94660
94777
  "a",
94661
94778
  {
94662
- href: segment.style.hyperlink,
94779
+ href: safeHref,
94663
94780
  style,
94664
94781
  onClick: (e2) => {
94665
94782
  e2.preventDefault();
94666
- window.open(segment.style.hyperlink, "_blank");
94783
+ safeOpenUrl(safeHref);
94667
94784
  },
94668
94785
  children: segment.text
94669
94786
  },
@@ -95215,7 +95332,7 @@ function useSlideNotes({
95215
95332
  const href = target.getAttribute("data-hyperlink") || target.getAttribute("href");
95216
95333
  if (href && (e2.ctrlKey || e2.metaKey)) {
95217
95334
  e2.preventDefault();
95218
- window.open(href, "_blank");
95335
+ safeOpenUrl(href);
95219
95336
  }
95220
95337
  }, []);
95221
95338
  return {
@@ -96888,7 +97005,7 @@ function renderNotesSegments(segments) {
96888
97005
  return React10__namespace.default.createElement("span", { key: `seg-${index}`, style }, segment.text);
96889
97006
  });
96890
97007
  }
96891
- function ScaledSlidePreview({
97008
+ function ScaledSlidePreviewImpl({
96892
97009
  slide,
96893
97010
  templateElements,
96894
97011
  canvasSize,
@@ -97035,6 +97152,40 @@ function ScaledSlidePreview({
97035
97152
  }
97036
97153
  );
97037
97154
  }
97155
+ function arePropsEqual4(prev, next) {
97156
+ if (prev.slide.id !== next.slide.id) {
97157
+ return false;
97158
+ }
97159
+ if (prev.slide.isDirty !== next.slide.isDirty) {
97160
+ return false;
97161
+ }
97162
+ if (prev.slide.hidden !== next.slide.hidden) {
97163
+ return false;
97164
+ }
97165
+ if (prev.slide.elements !== next.slide.elements) {
97166
+ return false;
97167
+ }
97168
+ if (prev.slide.backgroundColor !== next.slide.backgroundColor) {
97169
+ return false;
97170
+ }
97171
+ if (prev.slide.backgroundImage !== next.slide.backgroundImage) {
97172
+ return false;
97173
+ }
97174
+ if (prev.slide.backgroundGradient !== next.slide.backgroundGradient) {
97175
+ return false;
97176
+ }
97177
+ if (prev.templateElements !== next.templateElements) {
97178
+ return false;
97179
+ }
97180
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
97181
+ return false;
97182
+ }
97183
+ if (prev.className !== next.className) {
97184
+ return false;
97185
+ }
97186
+ return true;
97187
+ }
97188
+ var ScaledSlidePreview = React10__namespace.default.memo(ScaledSlidePreviewImpl, arePropsEqual4);
97038
97189
  function PresenterView({
97039
97190
  slides,
97040
97191
  currentSlideIndex,
@@ -111338,6 +111489,13 @@ function convertOmmlToLatex(omml) {
111338
111489
  }
111339
111490
  return ommlChildrenToLatex(oMath);
111340
111491
  }
111492
+ function sanitizeMathMl2(markup) {
111493
+ const purify = DOMPurify__default.default;
111494
+ if (typeof purify.sanitize !== "function") {
111495
+ return markup;
111496
+ }
111497
+ return purify.sanitize(markup, { USE_PROFILES: { mathMl: true, svg: true } });
111498
+ }
111341
111499
  var TEMPLATES = [
111342
111500
  {
111343
111501
  label: "Fraction",
@@ -111403,7 +111561,8 @@ var TEMPLATES = [
111403
111561
  var TEMPLATE_MATHML = TEMPLATES.map((tmpl) => {
111404
111562
  try {
111405
111563
  const tmplOmml = convertLatexToOmml(tmpl.latex);
111406
- return convertOmmlToMathMl(tmplOmml);
111564
+ const raw = convertOmmlToMathMl(tmplOmml);
111565
+ return raw ? sanitizeMathMl2(raw) : "";
111407
111566
  } catch {
111408
111567
  return "";
111409
111568
  }
@@ -111415,7 +111574,7 @@ function MathMlPreview({ mathml }) {
111415
111574
  return;
111416
111575
  }
111417
111576
  if (mathml) {
111418
- containerRef.current.innerHTML = mathml;
111577
+ containerRef.current.innerHTML = sanitizeMathMl2(mathml);
111419
111578
  } else {
111420
111579
  containerRef.current.innerHTML = "";
111421
111580
  }
@@ -111443,6 +111602,7 @@ function EquationEditorDialog({
111443
111602
  return convertOmmlToLatex(existingOmml);
111444
111603
  }, [existingOmml]);
111445
111604
  const [latex, setLatex] = React10.useState(initialLatex);
111605
+ const deferredLatex = React10.useDeferredValue(latex);
111446
111606
  const textareaRef = React10.useRef(null);
111447
111607
  React10.useEffect(() => {
111448
111608
  if (isOpen) {
@@ -111451,17 +111611,17 @@ function EquationEditorDialog({
111451
111611
  }
111452
111612
  }, [isOpen, initialLatex]);
111453
111613
  const { mathml, omml } = React10.useMemo(() => {
111454
- if (!latex.trim()) {
111614
+ if (!deferredLatex.trim()) {
111455
111615
  return { mathml: "", omml: {} };
111456
111616
  }
111457
111617
  try {
111458
- const ommlObj = convertLatexToOmml(latex);
111618
+ const ommlObj = convertLatexToOmml(deferredLatex);
111459
111619
  const mathmlStr = convertOmmlToMathMl(ommlObj);
111460
111620
  return { mathml: mathmlStr, omml: ommlObj };
111461
111621
  } catch {
111462
111622
  return { mathml: "", omml: {} };
111463
111623
  }
111464
- }, [latex]);
111624
+ }, [deferredLatex]);
111465
111625
  const handleInsert = React10.useCallback(() => {
111466
111626
  if (!latex.trim()) {
111467
111627
  return;
@@ -114196,6 +114356,7 @@ function useEditorHistory(input) {
114196
114356
  const historyFutureRef = React10.useRef([]);
114197
114357
  const lastHistorySnapshotRef = React10.useRef(null);
114198
114358
  const lastHistorySerializedRef = React10.useRef("");
114359
+ const lastCheapHashRef = React10.useRef("");
114199
114360
  const isApplyingHistoryRef = React10.useRef(false);
114200
114361
  const unlockHistoryTimerRef = React10.useRef(null);
114201
114362
  const [canUndo, setCanUndo] = React10.useState(false);
@@ -114337,15 +114498,21 @@ function useEditorHistory(input) {
114337
114498
  if (hasActivePointerInteraction()) {
114338
114499
  return;
114339
114500
  }
114501
+ const cheapHash = `${slides.length}|${activeSlideIndex}|${canvasSize.width}x${canvasSize.height}|${slides.map((s) => `${s.id}:${s.elements.length}`).join("/")}`;
114502
+ if (cheapHash === lastCheapHashRef.current) {
114503
+ return;
114504
+ }
114340
114505
  const snapshot2 = buildHistorySnapshot();
114341
114506
  const serialized = JSON.stringify(snapshot2);
114342
114507
  if (serialized === lastHistorySerializedRef.current) {
114508
+ lastCheapHashRef.current = cheapHash;
114343
114509
  return;
114344
114510
  }
114345
114511
  const previousSnapshot = lastHistorySnapshotRef.current;
114346
114512
  if (!previousSnapshot) {
114347
114513
  lastHistorySnapshotRef.current = cloneHistorySnapshot(snapshot2);
114348
114514
  lastHistorySerializedRef.current = serialized;
114515
+ lastCheapHashRef.current = cheapHash;
114349
114516
  updateHistoryAvailability();
114350
114517
  return;
114351
114518
  }
@@ -114356,13 +114523,18 @@ function useEditorHistory(input) {
114356
114523
  historyFutureRef.current = [];
114357
114524
  lastHistorySnapshotRef.current = cloneHistorySnapshot(snapshot2);
114358
114525
  lastHistorySerializedRef.current = serialized;
114526
+ lastCheapHashRef.current = cheapHash;
114359
114527
  updateHistoryAvailability();
114360
114528
  }, [
114529
+ activeSlideIndex,
114361
114530
  buildHistorySnapshot,
114531
+ canvasSize.height,
114532
+ canvasSize.width,
114362
114533
  error2,
114363
114534
  hasActivePointerInteraction,
114364
114535
  loading2,
114365
114536
  pointerCommitNonce,
114537
+ slides,
114366
114538
  updateHistoryAvailability
114367
114539
  ]);
114368
114540
  return {
@@ -118074,7 +118246,8 @@ async function storeAudienceContent(content) {
118074
118246
  const tx = db.transaction(STORE_NAME2, "readwrite");
118075
118247
  const store = tx.objectStore(STORE_NAME2);
118076
118248
  const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
118077
- store.put(bytes, CONTENT_KEY);
118249
+ const record = { bytes, createdAt: Date.now() };
118250
+ store.put(record, CONTENT_KEY);
118078
118251
  tx.oncomplete = () => {
118079
118252
  db.close();
118080
118253
  resolve2();
@@ -118107,14 +118280,34 @@ async function clearAudienceContent() {
118107
118280
  var PRESENTER_CHANNEL_NAME = "pptx-viewer-presenter";
118108
118281
  var AUDIENCE_HASH = "#pptx-audience";
118109
118282
  var PRESENTER_MSG_ORIGIN = "pptx-viewer-presenter";
118283
+ var AUDIENCE_NONCE_KEY = "nonce";
118284
+ function generateSessionId() {
118285
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
118286
+ return crypto.randomUUID();
118287
+ }
118288
+ return `s${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
118289
+ }
118290
+ function parseAudienceNonce() {
118291
+ const hash = window.location.hash;
118292
+ if (!hash.startsWith(AUDIENCE_HASH)) {
118293
+ return null;
118294
+ }
118295
+ const trailing = hash.slice(AUDIENCE_HASH.length);
118296
+ if (!trailing) {
118297
+ return null;
118298
+ }
118299
+ const params2 = new URLSearchParams(trailing.replace(/^[&;?]/, ""));
118300
+ return params2.get(AUDIENCE_NONCE_KEY);
118301
+ }
118110
118302
  function isAudienceTab() {
118111
- return window.location.hash === AUDIENCE_HASH;
118303
+ return window.location.hash.startsWith(AUDIENCE_HASH);
118112
118304
  }
118113
118305
  function usePresenterWindow(input) {
118114
118306
  const { currentSlideIndex, isPresenterMode, content } = input;
118115
118307
  const audienceWindowRef = React10.useRef(null);
118116
118308
  const channelRef = React10.useRef(null);
118117
118309
  const pollTimerRef = React10.useRef(null);
118310
+ const sessionIdRef = React10.useRef("");
118118
118311
  const getChannel2 = React10.useCallback(() => {
118119
118312
  if (!channelRef.current) {
118120
118313
  channelRef.current = new BroadcastChannel(PRESENTER_CHANNEL_NAME);
@@ -118126,10 +118319,14 @@ function usePresenterWindow(input) {
118126
118319
  }, []);
118127
118320
  const syncSlideToAudience = React10.useCallback(
118128
118321
  (slideIndex) => {
118322
+ if (!sessionIdRef.current) {
118323
+ return;
118324
+ }
118129
118325
  const msg = {
118130
118326
  origin: PRESENTER_MSG_ORIGIN,
118131
118327
  type: "presenter-slide-change",
118132
- slideIndex
118328
+ slideIndex,
118329
+ sessionId: sessionIdRef.current
118133
118330
  };
118134
118331
  try {
118135
118332
  getChannel2().postMessage(msg);
@@ -118139,13 +118336,16 @@ function usePresenterWindow(input) {
118139
118336
  [getChannel2]
118140
118337
  );
118141
118338
  const closeAudienceWindow = React10.useCallback(() => {
118142
- try {
118143
- const exitMsg = {
118144
- origin: PRESENTER_MSG_ORIGIN,
118145
- type: "presenter-exit"
118146
- };
118147
- getChannel2().postMessage(exitMsg);
118148
- } catch {
118339
+ if (sessionIdRef.current) {
118340
+ try {
118341
+ const exitMsg = {
118342
+ origin: PRESENTER_MSG_ORIGIN,
118343
+ type: "presenter-exit",
118344
+ sessionId: sessionIdRef.current
118345
+ };
118346
+ getChannel2().postMessage(exitMsg);
118347
+ } catch {
118348
+ }
118149
118349
  }
118150
118350
  const win = audienceWindowRef.current;
118151
118351
  if (win && !win.closed) {
@@ -118155,6 +118355,7 @@ function usePresenterWindow(input) {
118155
118355
  }
118156
118356
  }
118157
118357
  audienceWindowRef.current = null;
118358
+ sessionIdRef.current = "";
118158
118359
  if (pollTimerRef.current !== null) {
118159
118360
  clearInterval(pollTimerRef.current);
118160
118361
  pollTimerRef.current = null;
@@ -118165,20 +118366,53 @@ function usePresenterWindow(input) {
118165
118366
  if (isAudienceWindowOpen()) {
118166
118367
  closeAudienceWindow();
118167
118368
  }
118168
- if (content) {
118169
- void storeAudienceContent(content);
118170
- }
118171
- const url = new URL(window.location.href);
118172
- url.hash = AUDIENCE_HASH;
118173
- const win = window.open(url.toString(), "_blank");
118174
- if (!win) {
118369
+ const blankWin = window.open("about:blank", "_blank");
118370
+ if (!blankWin) {
118175
118371
  return false;
118176
118372
  }
118177
- audienceWindowRef.current = win;
118373
+ audienceWindowRef.current = blankWin;
118374
+ const sessionId = generateSessionId();
118375
+ sessionIdRef.current = sessionId;
118376
+ const audienceUrl = new URL(window.location.href);
118377
+ const params2 = new URLSearchParams();
118378
+ params2.set(AUDIENCE_NONCE_KEY, sessionId);
118379
+ audienceUrl.hash = `${AUDIENCE_HASH}&${params2.toString()}`;
118380
+ const navigateOrClose = (ok) => {
118381
+ const win = audienceWindowRef.current;
118382
+ if (!win || win.closed) {
118383
+ return;
118384
+ }
118385
+ if (!ok) {
118386
+ try {
118387
+ win.close();
118388
+ } catch {
118389
+ }
118390
+ audienceWindowRef.current = null;
118391
+ sessionIdRef.current = "";
118392
+ return;
118393
+ }
118394
+ try {
118395
+ win.location.replace(audienceUrl.toString());
118396
+ } catch {
118397
+ try {
118398
+ win.close();
118399
+ } catch {
118400
+ }
118401
+ audienceWindowRef.current = null;
118402
+ sessionIdRef.current = "";
118403
+ }
118404
+ };
118405
+ if (content) {
118406
+ void storeAudienceContent(content).then(() => navigateOrClose(true)).catch(() => navigateOrClose(false));
118407
+ } else {
118408
+ navigateOrClose(true);
118409
+ }
118178
118410
  window.setTimeout(() => syncSlideToAudience(currentSlideIndex), 1500);
118179
118411
  pollTimerRef.current = setInterval(() => {
118180
- if (win.closed) {
118412
+ const win = audienceWindowRef.current;
118413
+ if (!win || win.closed) {
118181
118414
  audienceWindowRef.current = null;
118415
+ sessionIdRef.current = "";
118182
118416
  if (pollTimerRef.current !== null) {
118183
118417
  clearInterval(pollTimerRef.current);
118184
118418
  pollTimerRef.current = null;
@@ -118206,6 +118440,13 @@ function usePresenterWindow(input) {
118206
118440
  closeAudienceWindow();
118207
118441
  }
118208
118442
  }, [isPresenterMode, closeAudienceWindow]);
118443
+ React10.useEffect(() => {
118444
+ const handleBeforeUnload = () => {
118445
+ void clearAudienceContent();
118446
+ };
118447
+ window.addEventListener("beforeunload", handleBeforeUnload);
118448
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
118449
+ }, []);
118209
118450
  return {
118210
118451
  openAudienceWindow,
118211
118452
  closeAudienceWindow,
@@ -118243,6 +118484,7 @@ function useAudienceMode(input) {
118243
118484
  if (!isAudienceTab()) {
118244
118485
  return;
118245
118486
  }
118487
+ const expectedSessionId = parseAudienceNonce();
118246
118488
  let channel;
118247
118489
  try {
118248
118490
  channel = new BroadcastChannel(PRESENTER_CHANNEL_NAME);
@@ -118254,6 +118496,9 @@ function useAudienceMode(input) {
118254
118496
  if (!data || data.origin !== PRESENTER_MSG_ORIGIN) {
118255
118497
  return;
118256
118498
  }
118499
+ if (expectedSessionId && data.sessionId !== expectedSessionId) {
118500
+ return;
118501
+ }
118257
118502
  if (data.type === "presenter-slide-change") {
118258
118503
  onSetActiveSlideIndex(data.slideIndex);
118259
118504
  }
@@ -119187,6 +119432,12 @@ function useTouchGestures(input) {
119187
119432
  callbacksRef.current = callbacks;
119188
119433
  const scaleRef = React10.useRef(currentScale);
119189
119434
  scaleRef.current = currentScale;
119435
+ const [targetVersion, setTargetVersion] = React10.useState(0);
119436
+ const lastTargetRef = React10.useRef(null);
119437
+ if (targetRef.current !== lastTargetRef.current) {
119438
+ lastTargetRef.current = targetRef.current;
119439
+ queueMicrotask(() => setTargetVersion((v) => v + 1));
119440
+ }
119190
119441
  React10.useEffect(() => {
119191
119442
  const el = targetRef.current;
119192
119443
  if (!el || !enabled) {
@@ -119275,7 +119526,7 @@ function useTouchGestures(input) {
119275
119526
  el.removeEventListener("touchcancel", handleTouchCancel);
119276
119527
  cancelLongPress();
119277
119528
  };
119278
- }, [targetRef, enabled]);
119529
+ }, [targetRef, enabled, targetVersion]);
119279
119530
  }
119280
119531
 
119281
119532
  // src/viewer/utils/dom-helpers.ts
@@ -119296,11 +119547,31 @@ function safeConfirm(message) {
119296
119547
  return false;
119297
119548
  }
119298
119549
  }
119550
+ function sanitizeDownloadFilename(input) {
119551
+ if (typeof input !== "string" || input.trim().length === 0) {
119552
+ return "presentation.pptx";
119553
+ }
119554
+ let cleaned = input.replace(/[\x00-\x1f\x7f"\\/:*?<>|]/g, "_").replace(/\.\./g, "__").replace(/^\.+/, "").trim();
119555
+ if (cleaned.length === 0) {
119556
+ return "presentation.pptx";
119557
+ }
119558
+ if (cleaned.length > 200) {
119559
+ const dot = cleaned.lastIndexOf(".");
119560
+ if (dot > 0 && cleaned.length - dot <= 16) {
119561
+ const ext = cleaned.slice(dot);
119562
+ cleaned = cleaned.slice(0, 200 - ext.length) + ext;
119563
+ } else {
119564
+ cleaned = cleaned.slice(0, 200);
119565
+ }
119566
+ }
119567
+ return cleaned;
119568
+ }
119299
119569
  function downloadBlob(blob, filename) {
119570
+ const safeName = sanitizeDownloadFilename(filename);
119300
119571
  const url = URL.createObjectURL(blob);
119301
119572
  const a2 = document.createElement("a");
119302
119573
  a2.href = url;
119303
- a2.download = filename;
119574
+ a2.download = safeName;
119304
119575
  document.body.appendChild(a2);
119305
119576
  a2.click();
119306
119577
  setTimeout(() => {
@@ -119731,27 +120002,82 @@ function openAutosaveDb2() {
119731
120002
  req.onerror = () => reject(req.error);
119732
120003
  });
119733
120004
  }
119734
- async function saveToIndexedDb(filePath, data) {
120005
+ async function deleteOldestAutosaveEntry() {
119735
120006
  const db = await openAutosaveDb2();
119736
- return new Promise((resolve2, reject) => {
119737
- const tx = db.transaction(STORE_NAME3, "readwrite");
119738
- const store = tx.objectStore(STORE_NAME3);
119739
- store.put({
119740
- key: filePath,
119741
- data,
119742
- timestamp: Date.now(),
119743
- size: data.byteLength
119744
- });
119745
- tx.oncomplete = () => {
119746
- db.close();
119747
- resolve2(true);
119748
- };
119749
- tx.onerror = () => {
119750
- db.close();
119751
- reject(tx.error);
119752
- };
120007
+ return new Promise((resolve2) => {
120008
+ try {
120009
+ const tx = db.transaction(STORE_NAME3, "readwrite");
120010
+ const store = tx.objectStore(STORE_NAME3);
120011
+ let oldestKey = null;
120012
+ let oldestTimestamp = Infinity;
120013
+ const cursorReq = store.openCursor();
120014
+ cursorReq.onsuccess = () => {
120015
+ const cursor = cursorReq.result;
120016
+ if (cursor) {
120017
+ const value = cursor.value;
120018
+ if (typeof value.timestamp === "number" && value.timestamp < oldestTimestamp) {
120019
+ oldestTimestamp = value.timestamp;
120020
+ oldestKey = cursor.primaryKey;
120021
+ }
120022
+ cursor.continue();
120023
+ } else if (oldestKey !== null) {
120024
+ store.delete(oldestKey);
120025
+ }
120026
+ };
120027
+ tx.oncomplete = () => {
120028
+ db.close();
120029
+ resolve2(oldestKey !== null);
120030
+ };
120031
+ tx.onerror = () => {
120032
+ db.close();
120033
+ resolve2(false);
120034
+ };
120035
+ } catch {
120036
+ try {
120037
+ db.close();
120038
+ } catch {
120039
+ }
120040
+ resolve2(false);
120041
+ }
119753
120042
  });
119754
120043
  }
120044
+ function putAutosaveRecord(filePath, data) {
120045
+ return openAutosaveDb2().then(
120046
+ (db) => new Promise((resolve2, reject) => {
120047
+ const tx = db.transaction(STORE_NAME3, "readwrite");
120048
+ const store = tx.objectStore(STORE_NAME3);
120049
+ store.put({
120050
+ key: filePath,
120051
+ data,
120052
+ timestamp: Date.now(),
120053
+ size: data.byteLength
120054
+ });
120055
+ tx.oncomplete = () => {
120056
+ db.close();
120057
+ resolve2(true);
120058
+ };
120059
+ tx.onerror = () => {
120060
+ db.close();
120061
+ reject(tx.error);
120062
+ };
120063
+ })
120064
+ );
120065
+ }
120066
+ async function saveToIndexedDb(filePath, data) {
120067
+ try {
120068
+ return await putAutosaveRecord(filePath, data);
120069
+ } catch (err) {
120070
+ const errName = err instanceof Error || err instanceof DOMException ? err.name : "";
120071
+ if (errName !== "QuotaExceededError") {
120072
+ throw err;
120073
+ }
120074
+ const deleted = await deleteOldestAutosaveEntry();
120075
+ if (!deleted) {
120076
+ throw err;
120077
+ }
120078
+ return putAutosaveRecord(filePath, data);
120079
+ }
120080
+ }
119755
120081
  function useAutosave(input) {
119756
120082
  const {
119757
120083
  isDirty,
@@ -119868,6 +120194,25 @@ function collectReferencedFontFamilies(slides) {
119868
120194
  }
119869
120195
  return families;
119870
120196
  }
120197
+ var FONT_NAME_UNSAFE_CHARS = /["\\\n\r;}<>]/;
120198
+ var FONT_FORMAT_ALLOWED = /* @__PURE__ */ new Set([
120199
+ "truetype",
120200
+ "opentype",
120201
+ "woff",
120202
+ "woff2",
120203
+ "svg",
120204
+ "embedded-opentype"
120205
+ ]);
120206
+ var FONT_DATA_URL_PATTERN = /^data:font\/[a-z0-9+.-]+(?:;charset=[a-z0-9-]+)?;base64,[A-Za-z0-9+/=]+$/i;
120207
+ function isFontDataUrlSafe(url) {
120208
+ if (typeof url !== "string" || url.length === 0) {
120209
+ return false;
120210
+ }
120211
+ if (url.startsWith("blob:")) {
120212
+ return true;
120213
+ }
120214
+ return FONT_DATA_URL_PATTERN.test(url);
120215
+ }
119871
120216
  function useFontInjection({ embeddedFonts, slides }) {
119872
120217
  React10.useEffect(() => {
119873
120218
  if (!embeddedFonts.length) {
@@ -119875,17 +120220,28 @@ function useFontInjection({ embeddedFonts, slides }) {
119875
120220
  }
119876
120221
  const styleEl = document.createElement("style");
119877
120222
  styleEl.id = EMBEDDED_FONTS_STYLE_ID;
119878
- const cssRules = embeddedFonts.map((font) => {
120223
+ const cssRules = embeddedFonts.flatMap((font) => {
120224
+ if (typeof font.name !== "string" || font.name.length === 0 || FONT_NAME_UNSAFE_CHARS.test(font.name)) {
120225
+ return [];
120226
+ }
120227
+ if (!isFontDataUrlSafe(font.dataUrl)) {
120228
+ return [];
120229
+ }
120230
+ const fontFormat = font.format ?? "truetype";
120231
+ if (!FONT_FORMAT_ALLOWED.has(fontFormat)) {
120232
+ return [];
120233
+ }
119879
120234
  const fontWeight = font.bold ? "700" : "400";
119880
120235
  const fontStyleCss = font.italic ? "italic" : "normal";
119881
- const fontFormat = font.format ?? "truetype";
119882
- return `@font-face {
120236
+ return [
120237
+ `@font-face {
119883
120238
  font-family: "${font.name}";
119884
120239
  src: url("${font.dataUrl}") format("${fontFormat}");
119885
120240
  font-weight: ${fontWeight};
119886
120241
  font-style: ${fontStyleCss};
119887
120242
  font-display: swap;
119888
- }`;
120243
+ }`
120244
+ ];
119889
120245
  }).join("\n");
119890
120246
  styleEl.textContent = cssRules;
119891
120247
  document.head.appendChild(styleEl);
@@ -119914,7 +120270,7 @@ function useFontInjection({ embeddedFonts, slides }) {
119914
120270
  const linkEl = document.createElement("link");
119915
120271
  linkEl.id = GOOGLE_FONTS_LINK_ID;
119916
120272
  linkEl.rel = "stylesheet";
119917
- linkEl.href = `https://fonts.googleapis.com/css2?${googleFamilies.map((f) => `family=${GOOGLE_FONTS_AVAILABLE[f]}`).join("&")}&display=swap`;
120273
+ linkEl.href = `https://fonts.googleapis.com/css2?${googleFamilies.map((f) => `family=${encodeURIComponent(GOOGLE_FONTS_AVAILABLE[f])}`).join("&")}&display=swap`;
119918
120274
  document.head.appendChild(linkEl);
119919
120275
  return () => {
119920
120276
  const existing = document.getElementById(GOOGLE_FONTS_LINK_ID);
@@ -119930,13 +120286,18 @@ function useFontInjection({ embeddedFonts, slides }) {
119930
120286
  }
119931
120287
  const styleEl = document.createElement("style");
119932
120288
  styleEl.id = SYMBOL_FONTS_STYLE_ID;
119933
- const rules = neededSymbolFonts.map(
119934
- (font) => `@font-face {
120289
+ const rules = neededSymbolFonts.flatMap((font) => {
120290
+ if (typeof font !== "string" || FONT_NAME_UNSAFE_CHARS.test(font)) {
120291
+ return [];
120292
+ }
120293
+ return [
120294
+ `@font-face {
119935
120295
  font-family: "${font}";
119936
120296
  src: local("${font}"), local("${font} Regular");
119937
120297
  font-display: swap;
119938
120298
  }`
119939
- ).join("\n");
120299
+ ];
120300
+ }).join("\n");
119940
120301
  styleEl.textContent = rules;
119941
120302
  document.head.appendChild(styleEl);
119942
120303
  return () => {
@@ -120079,16 +120440,17 @@ function useLoadContent({
120079
120440
  `[pptx] Large file detected (${fileSizeMB.toFixed(1)} MB). Loading may use significant memory.`
120080
120441
  );
120081
120442
  }
120082
- if (handlerRef.current) {
120083
- handlerRef.current.dispose();
120084
- handlerRef.current = null;
120085
- }
120443
+ const previousHandler = handlerRef.current;
120086
120444
  const handler = new pptxViewerCore.PptxHandler();
120087
120445
  const parsed = await handler.load(buffer);
120088
120446
  if (cancelled || token !== renderTokenRef.current) {
120089
120447
  handler.dispose();
120090
120448
  return;
120091
120449
  }
120450
+ if (previousHandler) {
120451
+ previousHandler.dispose();
120452
+ }
120453
+ handlerRef.current = null;
120092
120454
  const mediaElements = [];
120093
120455
  for (const slide of parsed.slides) {
120094
120456
  collectMediaElements(slide.elements, mediaElements);
@@ -120133,6 +120495,7 @@ function useLoadContent({
120133
120495
  })
120134
120496
  );
120135
120497
  const { paths: imagePaths, refs: imageRefs } = collectImagePaths(parsed.slides);
120498
+ let nextSlides = parsed.slides;
120136
120499
  if (imagePaths.size > 0) {
120137
120500
  const resolvedMap = /* @__PURE__ */ new Map();
120138
120501
  await Promise.all(
@@ -120146,15 +120509,47 @@ function useLoadContent({
120146
120509
  }
120147
120510
  })
120148
120511
  );
120512
+ const elementPatches = /* @__PURE__ */ new Map();
120149
120513
  for (const ref of imageRefs) {
120150
120514
  const url = resolvedMap.get(ref.path);
120151
- if (url) {
120152
- ref.element[ref.field] = url;
120515
+ if (!url) {
120516
+ continue;
120153
120517
  }
120518
+ const id2 = ref.element.id;
120519
+ const existing = elementPatches.get(id2) ?? {};
120520
+ existing[ref.field] = url;
120521
+ elementPatches.set(id2, existing);
120522
+ }
120523
+ if (elementPatches.size > 0) {
120524
+ const patchElements = (elements) => {
120525
+ let mutated = false;
120526
+ const next = elements.map((el) => {
120527
+ let updated = el;
120528
+ const patch = elementPatches.get(el.id);
120529
+ if (patch) {
120530
+ updated = { ...el, ...patch };
120531
+ }
120532
+ if (updated.type === "group" && updated.children?.length) {
120533
+ const newChildren = patchElements(updated.children);
120534
+ if (newChildren !== updated.children) {
120535
+ updated = { ...updated, children: newChildren };
120536
+ }
120537
+ }
120538
+ if (updated !== el) {
120539
+ mutated = true;
120540
+ }
120541
+ return updated;
120542
+ });
120543
+ return mutated ? next : elements;
120544
+ };
120545
+ nextSlides = parsed.slides.map((s) => {
120546
+ const newElements = patchElements(s.elements);
120547
+ return newElements === s.elements ? s : { ...s, elements: newElements };
120548
+ });
120154
120549
  }
120155
120550
  }
120156
120551
  handlerRef.current = handler;
120157
- setSlides(parsed.slides);
120552
+ setSlides(nextSlides);
120158
120553
  setTemplateElementsBySlideId({});
120159
120554
  setCanvasSize({
120160
120555
  width: parsed.width ?? DEFAULT_CANVAS_WIDTH,
@@ -121328,7 +121723,19 @@ function injectFontFaces(svg, fontFaces) {
121328
121723
  if (!fontFaces.length) {
121329
121724
  return svg;
121330
121725
  }
121331
- const styleBlock = `<style type="text/css">${fontFaces.map((f) => f.css).join("\n")}</style>`;
121726
+ const safeFontFaces = fontFaces.filter((f) => {
121727
+ if (f.css.toLowerCase().includes("</style")) {
121728
+ console.warn(
121729
+ `[export-svg] Dropping @font-face entry for "${f.family}" containing "</style" \u2014 would break out of the <style> block.`
121730
+ );
121731
+ return false;
121732
+ }
121733
+ return true;
121734
+ });
121735
+ if (!safeFontFaces.length) {
121736
+ return svg;
121737
+ }
121738
+ const styleBlock = `<style type="text/css">${safeFontFaces.map((f) => f.css).join("\n")}</style>`;
121332
121739
  if (svg.includes("<defs>")) {
121333
121740
  return svg.replace("<defs>", `<defs>${styleBlock}`);
121334
121741
  }
@@ -121654,7 +122061,7 @@ function useExportHandlers(input) {
121654
122061
  activeSlideIndexForGuides,
121655
122062
  modalControls
121656
122063
  });
121657
- const handleExportPng = async () => {
122064
+ const handleExportPng = React10.useCallback(async () => {
121658
122065
  const stageEl = canvasStageRef.current;
121659
122066
  if (!stageEl) {
121660
122067
  return;
@@ -121666,8 +122073,8 @@ function useExportHandlers(input) {
121666
122073
  } catch (err) {
121667
122074
  console.error("[PowerPointViewer] PNG export failed:", err);
121668
122075
  }
121669
- };
121670
- const handleExportPdf = async () => {
122076
+ }, [canvasStageRef, activeSlideIndex, activeSlide?.backgroundColor]);
122077
+ const handleExportPdf = React10.useCallback(async () => {
121671
122078
  if (!canvasStageRef.current) {
121672
122079
  return;
121673
122080
  }
@@ -121708,8 +122115,8 @@ function useExportHandlers(input) {
121708
122115
  exportAbortRef.current = null;
121709
122116
  setExportModalOpen(false);
121710
122117
  }
121711
- };
121712
- const handleExportNotesPdf = async () => {
122118
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
122119
+ const handleExportNotesPdf = React10.useCallback(async () => {
121713
122120
  if (!canvasStageRef.current) {
121714
122121
  return;
121715
122122
  }
@@ -121752,8 +122159,8 @@ function useExportHandlers(input) {
121752
122159
  exportAbortRef.current = null;
121753
122160
  setExportModalOpen(false);
121754
122161
  }
121755
- };
121756
- const handleCopySlideAsImage = async () => {
122162
+ }, [canvasStageRef, slides, setActiveSlideIndex, activeSlideIndex]);
122163
+ const handleCopySlideAsImage = React10.useCallback(async () => {
121757
122164
  const stageEl = canvasStageRef.current;
121758
122165
  if (!stageEl) {
121759
122166
  return;
@@ -121765,8 +122172,8 @@ function useExportHandlers(input) {
121765
122172
  } catch (err) {
121766
122173
  console.error("[PowerPointViewer] Copy slide as image failed:", err);
121767
122174
  }
121768
- };
121769
- const handleExportVideo = async () => {
122175
+ }, [canvasStageRef, activeSlide?.backgroundColor]);
122176
+ const handleExportVideo = React10.useCallback(async () => {
121770
122177
  if (!canvasStageRef.current) {
121771
122178
  return;
121772
122179
  }
@@ -121808,8 +122215,8 @@ function useExportHandlers(input) {
121808
122215
  exportAbortRef.current = null;
121809
122216
  setExportModalOpen(false);
121810
122217
  }
121811
- };
121812
- const handleExportGif = async () => {
122218
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
122219
+ const handleExportGif = React10.useCallback(async () => {
121813
122220
  if (!canvasStageRef.current) {
121814
122221
  return;
121815
122222
  }
@@ -121847,7 +122254,7 @@ function useExportHandlers(input) {
121847
122254
  exportAbortRef.current = null;
121848
122255
  setExportModalOpen(false);
121849
122256
  }
121850
- };
122257
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
121851
122258
  const handleCancelExport = React10.useCallback(() => {
121852
122259
  exportAbortRef.current?.abort();
121853
122260
  exportAbortRef.current = null;
@@ -121873,6 +122280,15 @@ function useExportHandlers(input) {
121873
122280
  exportStatusMessage
121874
122281
  };
121875
122282
  }
122283
+ function escapeHtmlAttr(value) {
122284
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
122285
+ }
122286
+ function safeDataImageSrc(src) {
122287
+ if (typeof src !== "string" || !src.startsWith("data:image/")) {
122288
+ return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgAAIAAAUAAen63NgAAAAASUVORK5CYII=";
122289
+ }
122290
+ return escapeHtmlAttr(src);
122291
+ }
121876
122292
  function openPrintWindow(title, bodyHtml, orientation, colorFilter, frameSlides) {
121877
122293
  const printWindow = window.open("", "_blank", "noopener,noreferrer");
121878
122294
  if (!printWindow) {
@@ -122055,7 +122471,7 @@ function usePrintHandlers(input) {
122055
122471
  const slideImages = slideIndices.map((idx) => allImages[idx]).filter(Boolean);
122056
122472
  if (settings.printWhat === "slides") {
122057
122473
  const bodyHtml = slideImages.map(
122058
- (img, i3) => `<section class="page slide-page"><img class="slide-img" src="${img}" alt="Slide ${slideIndices[i3] + 1}" /></section>`
122474
+ (img, i3) => `<section class="page slide-page"><img class="slide-img" src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3] + 1}" /></section>`
122059
122475
  ).join("");
122060
122476
  openPrintWindow(
122061
122477
  "Slides",
@@ -122071,7 +122487,7 @@ function usePrintHandlers(input) {
122071
122487
  const idx = slideIndices[i3];
122072
122488
  const notes = slides[idx]?.notes?.trim() || "";
122073
122489
  return `<section class="page notes-page">
122074
- <img class="notes-slide" src="${img}" alt="Slide ${idx + 1}" />
122490
+ <img class="notes-slide" src="${safeDataImageSrc(img)}" alt="Slide ${idx + 1}" />
122075
122491
  <div class="notes-text">${escapeHtml2(notes)}</div>
122076
122492
  </section>`;
122077
122493
  }).join("");
@@ -122103,14 +122519,14 @@ function usePrintHandlers(input) {
122103
122519
  if (isThreePerPage) {
122104
122520
  const rows = Array.from({ length: spp }, (_, cellIndex) => {
122105
122521
  const img = pageImgs[cellIndex];
122106
- const slideCell = img ? `<div class="handout-cell"><img src="${img}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122522
+ const slideCell = img ? `<div class="handout-cell"><img src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122107
122523
  return `<div class="handout-row-3">${slideCell}${buildNoteLines()}</div>`;
122108
122524
  }).join("");
122109
122525
  pages.push(`<section class="page"><div class="handout-grid-3">${rows}</div></section>`);
122110
122526
  } else {
122111
122527
  const cells = Array.from({ length: spp }, (_, cellIndex) => {
122112
122528
  const img = pageImgs[cellIndex];
122113
- return img ? `<div class="handout-cell"><img src="${img}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122529
+ return img ? `<div class="handout-cell"><img src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122114
122530
  }).join("");
122115
122531
  pages.push(
122116
122532
  `<section class="page"><div class="handout-grid" style="grid-template-columns: repeat(${grid.columns}, minmax(0, 1fr)); grid-template-rows: repeat(${grid.rows}, minmax(0, 1fr));">${cells}</div></section>`