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
@@ -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, useMemo42 = 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, useMemo42 = 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) {
@@ -74089,6 +74091,13 @@ function hasDistinctScriptFonts(fonts) {
74089
74091
  }
74090
74092
  return Boolean(fonts.eastAsia) && fonts.eastAsia !== base || Boolean(fonts.complexScript) && fonts.complexScript !== base || Boolean(fonts.symbol) && fonts.symbol !== base;
74091
74093
  }
74094
+ function sanitizeMathMl(markup) {
74095
+ const purify = DOMPurify__default.default;
74096
+ if (typeof purify.sanitize !== "function") {
74097
+ return markup;
74098
+ }
74099
+ return purify.sanitize(markup, { USE_PROFILES: { mathMl: true, svg: true } });
74100
+ }
74092
74101
  function renderScriptAwareText(text2, needsScriptFonts, scriptFonts, baseFontFamily, keyPrefix) {
74093
74102
  if (!needsScriptFonts || !text2) {
74094
74103
  return text2;
@@ -74179,14 +74188,15 @@ function renderSegmentContent(elementId, segmentIndex, textValue, lines, needsSc
74179
74188
  }
74180
74189
  function renderEquationSegment(elementId, segmentIndex, equationXml, equationNumber) {
74181
74190
  const mathml = convertOmmlToMathMl(equationXml);
74182
- const equationContent = mathml ? /* @__PURE__ */ jsxRuntime.jsx(
74191
+ const safeMathml = mathml ? sanitizeMathMl(mathml) : "";
74192
+ const equationContent = safeMathml ? /* @__PURE__ */ jsxRuntime.jsx(
74183
74193
  "span",
74184
74194
  {
74185
74195
  className: "inline-block align-middle",
74186
74196
  style: {
74187
74197
  fontFamily: '"Cambria Math", "STIX Two Math", serif'
74188
74198
  },
74189
- dangerouslySetInnerHTML: { __html: mathml }
74199
+ dangerouslySetInnerHTML: { __html: safeMathml }
74190
74200
  }
74191
74201
  ) : /* @__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" });
74192
74202
  if (equationNumber) {
@@ -86543,7 +86553,7 @@ function ResizeHandle({
86543
86553
  }
86544
86554
  );
86545
86555
  }
86546
- function SlideThumbnail({
86556
+ function SlideThumbnailImpl({
86547
86557
  slide,
86548
86558
  templateElements,
86549
86559
  canvasSize
@@ -86781,6 +86791,37 @@ function ThumbnailTable({
86781
86791
  }
86782
86792
  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" });
86783
86793
  }
86794
+ function arePropsEqual(prev, next) {
86795
+ if (prev.slide.id !== next.slide.id) {
86796
+ return false;
86797
+ }
86798
+ if (prev.slide.isDirty !== next.slide.isDirty) {
86799
+ return false;
86800
+ }
86801
+ if (prev.slide.hidden !== next.slide.hidden) {
86802
+ return false;
86803
+ }
86804
+ if (prev.slide.elements !== next.slide.elements) {
86805
+ return false;
86806
+ }
86807
+ if (prev.slide.backgroundColor !== next.slide.backgroundColor) {
86808
+ return false;
86809
+ }
86810
+ if (prev.slide.backgroundImage !== next.slide.backgroundImage) {
86811
+ return false;
86812
+ }
86813
+ if (prev.slide.backgroundGradient !== next.slide.backgroundGradient) {
86814
+ return false;
86815
+ }
86816
+ if (prev.templateElements !== next.templateElements) {
86817
+ return false;
86818
+ }
86819
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
86820
+ return false;
86821
+ }
86822
+ return true;
86823
+ }
86824
+ var SlideThumbnail = React10__namespace.default.memo(SlideThumbnailImpl, arePropsEqual);
86784
86825
  function ContextMenu({
86785
86826
  contextMenuState,
86786
86827
  mode,
@@ -90514,7 +90555,7 @@ function BendingProcessRenderer({
90514
90555
  }
90515
90556
  );
90516
90557
  }
90517
- function SmartArtRenderer({
90558
+ function SmartArtRendererImpl({
90518
90559
  element: element2,
90519
90560
  className = ""
90520
90561
  }) {
@@ -90625,6 +90666,30 @@ function renderLayout(layoutType, element2, nodes, palette, style) {
90625
90666
  }
90626
90667
  return /* @__PURE__ */ jsxRuntime.jsx(ListRenderer, { element: element2, nodes, palette, style });
90627
90668
  }
90669
+ function arePropsEqual2(prev, next) {
90670
+ if (prev.className !== next.className) {
90671
+ return false;
90672
+ }
90673
+ if (prev.element.id !== next.element.id) {
90674
+ return false;
90675
+ }
90676
+ if (prev.element.type !== next.element.type) {
90677
+ return false;
90678
+ }
90679
+ if (prev.element.width !== next.element.width || prev.element.height !== next.element.height) {
90680
+ return false;
90681
+ }
90682
+ if (prev.element.x !== next.element.x || prev.element.y !== next.element.y) {
90683
+ return false;
90684
+ }
90685
+ const prevData = prev.element.type === "smartArt" ? prev.element.smartArtData : void 0;
90686
+ const nextData = next.element.type === "smartArt" ? next.element.smartArtData : void 0;
90687
+ if (prevData !== nextData) {
90688
+ return false;
90689
+ }
90690
+ return true;
90691
+ }
90692
+ var SmartArtRenderer = React10__namespace.default.memo(SmartArtRendererImpl, arePropsEqual2);
90628
90693
  function ZoomElementRenderer({
90629
90694
  element: element2,
90630
90695
  slides,
@@ -93663,7 +93728,7 @@ function SectionBlock({
93663
93728
  )
93664
93729
  ] });
93665
93730
  }
93666
- function SlideCard({
93731
+ function SlideCardImpl({
93667
93732
  slide,
93668
93733
  index,
93669
93734
  isActive,
@@ -93717,6 +93782,49 @@ function SlideCard({
93717
93782
  }
93718
93783
  );
93719
93784
  }
93785
+ function arePropsEqual3(prev, next) {
93786
+ if (prev.slide.id !== next.slide.id) {
93787
+ return false;
93788
+ }
93789
+ if (prev.slide.isDirty !== next.slide.isDirty) {
93790
+ return false;
93791
+ }
93792
+ if (prev.slide.hidden !== next.slide.hidden) {
93793
+ return false;
93794
+ }
93795
+ if (prev.slide.elements !== next.slide.elements) {
93796
+ return false;
93797
+ }
93798
+ if (prev.index !== next.index) {
93799
+ return false;
93800
+ }
93801
+ if (prev.isActive !== next.isActive) {
93802
+ return false;
93803
+ }
93804
+ if (prev.isDragTarget !== next.isDragTarget) {
93805
+ return false;
93806
+ }
93807
+ if (prev.isSelected !== next.isSelected) {
93808
+ return false;
93809
+ }
93810
+ if (prev.selectedCount !== next.selectedCount) {
93811
+ return false;
93812
+ }
93813
+ if (prev.selectionOrder !== next.selectionOrder) {
93814
+ return false;
93815
+ }
93816
+ if (prev.canEdit !== next.canEdit) {
93817
+ return false;
93818
+ }
93819
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
93820
+ return false;
93821
+ }
93822
+ 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) {
93823
+ return false;
93824
+ }
93825
+ return true;
93826
+ }
93827
+ var SlideCard = React10__namespace.default.memo(SlideCardImpl, arePropsEqual3);
93720
93828
  function SorterContextMenu({
93721
93829
  x: x2,
93722
93830
  y,
@@ -94510,6 +94618,14 @@ function getCurrentParagraphIndex(editorEl, segments) {
94510
94618
  }
94511
94619
  return paraIdx;
94512
94620
  }
94621
+ var CSS_VALUE_SAFE = /^[a-zA-Z0-9 _,.\-+#'%/]{1,100}$/;
94622
+ function isCssValueSafe(value) {
94623
+ if (value === void 0 || value === null) {
94624
+ return false;
94625
+ }
94626
+ const str = String(value);
94627
+ return str.length > 0 && CSS_VALUE_SAFE.test(str);
94628
+ }
94513
94629
  function deriveStyleFromElement(element2, inheritedStyle) {
94514
94630
  const style = { ...inheritedStyle };
94515
94631
  const tagName = element2.tagName.toLowerCase();
@@ -94642,17 +94758,17 @@ function segmentsToEditorHtml(segments) {
94642
94758
  if (segment.style.strikethrough) {
94643
94759
  inlineStyles.push("text-decoration:line-through");
94644
94760
  }
94645
- if (segment.style.color) {
94761
+ if (segment.style.color && isCssValueSafe(segment.style.color)) {
94646
94762
  inlineStyles.push(`color:${segment.style.color}`);
94647
94763
  }
94648
- if (segment.style.fontSize) {
94649
- inlineStyles.push(`font-size:${segment.style.fontSize}pt`);
94764
+ if (segment.style.fontSize && Number.isFinite(Number(segment.style.fontSize))) {
94765
+ inlineStyles.push(`font-size:${Number(segment.style.fontSize)}pt`);
94650
94766
  }
94651
- if (segment.style.fontFamily) {
94767
+ if (segment.style.fontFamily && isCssValueSafe(segment.style.fontFamily)) {
94652
94768
  inlineStyles.push(`font-family:${segment.style.fontFamily}`);
94653
94769
  }
94654
94770
  const text2 = escapeHtml(segment.text);
94655
- if (segment.style.hyperlink) {
94771
+ if (segment.style.hyperlink && isUrlSafe(segment.style.hyperlink)) {
94656
94772
  const href = escapeHtml(segment.style.hyperlink);
94657
94773
  return `<a href="${href}" style="color:#4a9eff;text-decoration:underline;cursor:pointer" data-hyperlink="${href}">${text2}</a>`;
94658
94774
  }
@@ -94735,7 +94851,8 @@ function renderRichNotesSegments(segments) {
94735
94851
  if (segment.style.fontFamily) {
94736
94852
  style.fontFamily = segment.style.fontFamily;
94737
94853
  }
94738
- if (segment.style.hyperlink) {
94854
+ if (segment.style.hyperlink && isUrlSafe(segment.style.hyperlink)) {
94855
+ const safeHref = segment.style.hyperlink;
94739
94856
  style.color = "#4a9eff";
94740
94857
  style.textDecoration = "underline";
94741
94858
  style.cursor = "pointer";
@@ -94743,11 +94860,11 @@ function renderRichNotesSegments(segments) {
94743
94860
  /* @__PURE__ */ jsxRuntime.jsx(
94744
94861
  "a",
94745
94862
  {
94746
- href: segment.style.hyperlink,
94863
+ href: safeHref,
94747
94864
  style,
94748
94865
  onClick: (e2) => {
94749
94866
  e2.preventDefault();
94750
- window.open(segment.style.hyperlink, "_blank");
94867
+ safeOpenUrl(safeHref);
94751
94868
  },
94752
94869
  children: segment.text
94753
94870
  },
@@ -95299,7 +95416,7 @@ function useSlideNotes({
95299
95416
  const href = target.getAttribute("data-hyperlink") || target.getAttribute("href");
95300
95417
  if (href && (e2.ctrlKey || e2.metaKey)) {
95301
95418
  e2.preventDefault();
95302
- window.open(href, "_blank");
95419
+ safeOpenUrl(href);
95303
95420
  }
95304
95421
  }, []);
95305
95422
  return {
@@ -96972,7 +97089,7 @@ function renderNotesSegments(segments) {
96972
97089
  return React10__namespace.default.createElement("span", { key: `seg-${index}`, style }, segment.text);
96973
97090
  });
96974
97091
  }
96975
- function ScaledSlidePreview({
97092
+ function ScaledSlidePreviewImpl({
96976
97093
  slide,
96977
97094
  templateElements,
96978
97095
  canvasSize,
@@ -97119,6 +97236,40 @@ function ScaledSlidePreview({
97119
97236
  }
97120
97237
  );
97121
97238
  }
97239
+ function arePropsEqual4(prev, next) {
97240
+ if (prev.slide.id !== next.slide.id) {
97241
+ return false;
97242
+ }
97243
+ if (prev.slide.isDirty !== next.slide.isDirty) {
97244
+ return false;
97245
+ }
97246
+ if (prev.slide.hidden !== next.slide.hidden) {
97247
+ return false;
97248
+ }
97249
+ if (prev.slide.elements !== next.slide.elements) {
97250
+ return false;
97251
+ }
97252
+ if (prev.slide.backgroundColor !== next.slide.backgroundColor) {
97253
+ return false;
97254
+ }
97255
+ if (prev.slide.backgroundImage !== next.slide.backgroundImage) {
97256
+ return false;
97257
+ }
97258
+ if (prev.slide.backgroundGradient !== next.slide.backgroundGradient) {
97259
+ return false;
97260
+ }
97261
+ if (prev.templateElements !== next.templateElements) {
97262
+ return false;
97263
+ }
97264
+ if (prev.canvasSize.width !== next.canvasSize.width || prev.canvasSize.height !== next.canvasSize.height) {
97265
+ return false;
97266
+ }
97267
+ if (prev.className !== next.className) {
97268
+ return false;
97269
+ }
97270
+ return true;
97271
+ }
97272
+ var ScaledSlidePreview = React10__namespace.default.memo(ScaledSlidePreviewImpl, arePropsEqual4);
97122
97273
  function PresenterView({
97123
97274
  slides,
97124
97275
  currentSlideIndex,
@@ -111422,6 +111573,13 @@ function convertOmmlToLatex(omml) {
111422
111573
  }
111423
111574
  return ommlChildrenToLatex(oMath);
111424
111575
  }
111576
+ function sanitizeMathMl2(markup) {
111577
+ const purify = DOMPurify__default.default;
111578
+ if (typeof purify.sanitize !== "function") {
111579
+ return markup;
111580
+ }
111581
+ return purify.sanitize(markup, { USE_PROFILES: { mathMl: true, svg: true } });
111582
+ }
111425
111583
  var TEMPLATES = [
111426
111584
  {
111427
111585
  label: "Fraction",
@@ -111487,7 +111645,8 @@ var TEMPLATES = [
111487
111645
  var TEMPLATE_MATHML = TEMPLATES.map((tmpl) => {
111488
111646
  try {
111489
111647
  const tmplOmml = convertLatexToOmml(tmpl.latex);
111490
- return convertOmmlToMathMl(tmplOmml);
111648
+ const raw = convertOmmlToMathMl(tmplOmml);
111649
+ return raw ? sanitizeMathMl2(raw) : "";
111491
111650
  } catch {
111492
111651
  return "";
111493
111652
  }
@@ -111499,7 +111658,7 @@ function MathMlPreview({ mathml }) {
111499
111658
  return;
111500
111659
  }
111501
111660
  if (mathml) {
111502
- containerRef.current.innerHTML = mathml;
111661
+ containerRef.current.innerHTML = sanitizeMathMl2(mathml);
111503
111662
  } else {
111504
111663
  containerRef.current.innerHTML = "";
111505
111664
  }
@@ -111527,6 +111686,7 @@ function EquationEditorDialog({
111527
111686
  return convertOmmlToLatex(existingOmml);
111528
111687
  }, [existingOmml]);
111529
111688
  const [latex, setLatex] = React10.useState(initialLatex);
111689
+ const deferredLatex = React10.useDeferredValue(latex);
111530
111690
  const textareaRef = React10.useRef(null);
111531
111691
  React10.useEffect(() => {
111532
111692
  if (isOpen) {
@@ -111535,17 +111695,17 @@ function EquationEditorDialog({
111535
111695
  }
111536
111696
  }, [isOpen, initialLatex]);
111537
111697
  const { mathml, omml } = React10.useMemo(() => {
111538
- if (!latex.trim()) {
111698
+ if (!deferredLatex.trim()) {
111539
111699
  return { mathml: "", omml: {} };
111540
111700
  }
111541
111701
  try {
111542
- const ommlObj = convertLatexToOmml(latex);
111702
+ const ommlObj = convertLatexToOmml(deferredLatex);
111543
111703
  const mathmlStr = convertOmmlToMathMl(ommlObj);
111544
111704
  return { mathml: mathmlStr, omml: ommlObj };
111545
111705
  } catch {
111546
111706
  return { mathml: "", omml: {} };
111547
111707
  }
111548
- }, [latex]);
111708
+ }, [deferredLatex]);
111549
111709
  const handleInsert = React10.useCallback(() => {
111550
111710
  if (!latex.trim()) {
111551
111711
  return;
@@ -114309,6 +114469,7 @@ function useEditorHistory(input) {
114309
114469
  const historyFutureRef = React10.useRef([]);
114310
114470
  const lastHistorySnapshotRef = React10.useRef(null);
114311
114471
  const lastHistorySerializedRef = React10.useRef("");
114472
+ const lastCheapHashRef = React10.useRef("");
114312
114473
  const isApplyingHistoryRef = React10.useRef(false);
114313
114474
  const unlockHistoryTimerRef = React10.useRef(null);
114314
114475
  const [canUndo, setCanUndo] = React10.useState(false);
@@ -114450,15 +114611,21 @@ function useEditorHistory(input) {
114450
114611
  if (hasActivePointerInteraction()) {
114451
114612
  return;
114452
114613
  }
114614
+ const cheapHash = `${slides.length}|${activeSlideIndex}|${canvasSize.width}x${canvasSize.height}|${slides.map((s) => `${s.id}:${s.elements.length}`).join("/")}`;
114615
+ if (cheapHash === lastCheapHashRef.current) {
114616
+ return;
114617
+ }
114453
114618
  const snapshot2 = buildHistorySnapshot();
114454
114619
  const serialized = JSON.stringify(snapshot2);
114455
114620
  if (serialized === lastHistorySerializedRef.current) {
114621
+ lastCheapHashRef.current = cheapHash;
114456
114622
  return;
114457
114623
  }
114458
114624
  const previousSnapshot = lastHistorySnapshotRef.current;
114459
114625
  if (!previousSnapshot) {
114460
114626
  lastHistorySnapshotRef.current = cloneHistorySnapshot(snapshot2);
114461
114627
  lastHistorySerializedRef.current = serialized;
114628
+ lastCheapHashRef.current = cheapHash;
114462
114629
  updateHistoryAvailability();
114463
114630
  return;
114464
114631
  }
@@ -114469,13 +114636,18 @@ function useEditorHistory(input) {
114469
114636
  historyFutureRef.current = [];
114470
114637
  lastHistorySnapshotRef.current = cloneHistorySnapshot(snapshot2);
114471
114638
  lastHistorySerializedRef.current = serialized;
114639
+ lastCheapHashRef.current = cheapHash;
114472
114640
  updateHistoryAvailability();
114473
114641
  }, [
114642
+ activeSlideIndex,
114474
114643
  buildHistorySnapshot,
114644
+ canvasSize.height,
114645
+ canvasSize.width,
114475
114646
  error2,
114476
114647
  hasActivePointerInteraction,
114477
114648
  loading2,
114478
114649
  pointerCommitNonce,
114650
+ slides,
114479
114651
  updateHistoryAvailability
114480
114652
  ]);
114481
114653
  return {
@@ -118168,6 +118340,7 @@ var DB_NAME2 = "pptx-viewer-audience";
118168
118340
  var DB_VERSION2 = 1;
118169
118341
  var STORE_NAME2 = "content";
118170
118342
  var CONTENT_KEY = "pptx-bytes";
118343
+ var MAX_CONTENT_AGE_MS = 5 * 60 * 1e3;
118171
118344
  function openDb() {
118172
118345
  return new Promise((resolve2, reject) => {
118173
118346
  const request = indexedDB.open(DB_NAME2, DB_VERSION2);
@@ -118187,7 +118360,8 @@ async function storeAudienceContent(content) {
118187
118360
  const tx = db.transaction(STORE_NAME2, "readwrite");
118188
118361
  const store = tx.objectStore(STORE_NAME2);
118189
118362
  const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
118190
- store.put(bytes, CONTENT_KEY);
118363
+ const record = { bytes, createdAt: Date.now() };
118364
+ store.put(record, CONTENT_KEY);
118191
118365
  tx.oncomplete = () => {
118192
118366
  db.close();
118193
118367
  resolve2();
@@ -118208,13 +118382,24 @@ async function loadAudienceContent() {
118208
118382
  request.onsuccess = () => {
118209
118383
  db.close();
118210
118384
  const result = request.result;
118211
- if (result instanceof Uint8Array) {
118212
- resolve2(result);
118213
- } else if (result instanceof ArrayBuffer) {
118214
- resolve2(new Uint8Array(result));
118215
- } else {
118216
- resolve2(null);
118385
+ if (result && typeof result === "object" && "bytes" in result && "createdAt" in result) {
118386
+ const record = result;
118387
+ const age = Date.now() - record.createdAt;
118388
+ if (age > MAX_CONTENT_AGE_MS) {
118389
+ resolve2(null);
118390
+ return;
118391
+ }
118392
+ const raw = record.bytes;
118393
+ if (raw instanceof Uint8Array) {
118394
+ resolve2(raw);
118395
+ } else if (raw instanceof ArrayBuffer) {
118396
+ resolve2(new Uint8Array(raw));
118397
+ } else {
118398
+ resolve2(null);
118399
+ }
118400
+ return;
118217
118401
  }
118402
+ resolve2(null);
118218
118403
  };
118219
118404
  request.onerror = () => {
118220
118405
  db.close();
@@ -118247,14 +118432,34 @@ async function clearAudienceContent() {
118247
118432
  var PRESENTER_CHANNEL_NAME = "pptx-viewer-presenter";
118248
118433
  var AUDIENCE_HASH = "#pptx-audience";
118249
118434
  var PRESENTER_MSG_ORIGIN = "pptx-viewer-presenter";
118435
+ var AUDIENCE_NONCE_KEY = "nonce";
118436
+ function generateSessionId() {
118437
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
118438
+ return crypto.randomUUID();
118439
+ }
118440
+ return `s${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
118441
+ }
118442
+ function parseAudienceNonce() {
118443
+ const hash = window.location.hash;
118444
+ if (!hash.startsWith(AUDIENCE_HASH)) {
118445
+ return null;
118446
+ }
118447
+ const trailing = hash.slice(AUDIENCE_HASH.length);
118448
+ if (!trailing) {
118449
+ return null;
118450
+ }
118451
+ const params2 = new URLSearchParams(trailing.replace(/^[&;?]/, ""));
118452
+ return params2.get(AUDIENCE_NONCE_KEY);
118453
+ }
118250
118454
  function isAudienceTab() {
118251
- return window.location.hash === AUDIENCE_HASH;
118455
+ return window.location.hash.startsWith(AUDIENCE_HASH);
118252
118456
  }
118253
118457
  function usePresenterWindow(input) {
118254
118458
  const { currentSlideIndex, isPresenterMode, content } = input;
118255
118459
  const audienceWindowRef = React10.useRef(null);
118256
118460
  const channelRef = React10.useRef(null);
118257
118461
  const pollTimerRef = React10.useRef(null);
118462
+ const sessionIdRef = React10.useRef("");
118258
118463
  const getChannel2 = React10.useCallback(() => {
118259
118464
  if (!channelRef.current) {
118260
118465
  channelRef.current = new BroadcastChannel(PRESENTER_CHANNEL_NAME);
@@ -118266,10 +118471,14 @@ function usePresenterWindow(input) {
118266
118471
  }, []);
118267
118472
  const syncSlideToAudience = React10.useCallback(
118268
118473
  (slideIndex) => {
118474
+ if (!sessionIdRef.current) {
118475
+ return;
118476
+ }
118269
118477
  const msg = {
118270
118478
  origin: PRESENTER_MSG_ORIGIN,
118271
118479
  type: "presenter-slide-change",
118272
- slideIndex
118480
+ slideIndex,
118481
+ sessionId: sessionIdRef.current
118273
118482
  };
118274
118483
  try {
118275
118484
  getChannel2().postMessage(msg);
@@ -118279,13 +118488,16 @@ function usePresenterWindow(input) {
118279
118488
  [getChannel2]
118280
118489
  );
118281
118490
  const closeAudienceWindow = React10.useCallback(() => {
118282
- try {
118283
- const exitMsg = {
118284
- origin: PRESENTER_MSG_ORIGIN,
118285
- type: "presenter-exit"
118286
- };
118287
- getChannel2().postMessage(exitMsg);
118288
- } catch {
118491
+ if (sessionIdRef.current) {
118492
+ try {
118493
+ const exitMsg = {
118494
+ origin: PRESENTER_MSG_ORIGIN,
118495
+ type: "presenter-exit",
118496
+ sessionId: sessionIdRef.current
118497
+ };
118498
+ getChannel2().postMessage(exitMsg);
118499
+ } catch {
118500
+ }
118289
118501
  }
118290
118502
  const win = audienceWindowRef.current;
118291
118503
  if (win && !win.closed) {
@@ -118295,6 +118507,7 @@ function usePresenterWindow(input) {
118295
118507
  }
118296
118508
  }
118297
118509
  audienceWindowRef.current = null;
118510
+ sessionIdRef.current = "";
118298
118511
  if (pollTimerRef.current !== null) {
118299
118512
  clearInterval(pollTimerRef.current);
118300
118513
  pollTimerRef.current = null;
@@ -118305,20 +118518,53 @@ function usePresenterWindow(input) {
118305
118518
  if (isAudienceWindowOpen()) {
118306
118519
  closeAudienceWindow();
118307
118520
  }
118308
- if (content) {
118309
- void storeAudienceContent(content);
118310
- }
118311
- const url = new URL(window.location.href);
118312
- url.hash = AUDIENCE_HASH;
118313
- const win = window.open(url.toString(), "_blank");
118314
- if (!win) {
118521
+ const blankWin = window.open("about:blank", "_blank");
118522
+ if (!blankWin) {
118315
118523
  return false;
118316
118524
  }
118317
- audienceWindowRef.current = win;
118525
+ audienceWindowRef.current = blankWin;
118526
+ const sessionId = generateSessionId();
118527
+ sessionIdRef.current = sessionId;
118528
+ const audienceUrl = new URL(window.location.href);
118529
+ const params2 = new URLSearchParams();
118530
+ params2.set(AUDIENCE_NONCE_KEY, sessionId);
118531
+ audienceUrl.hash = `${AUDIENCE_HASH}&${params2.toString()}`;
118532
+ const navigateOrClose = (ok) => {
118533
+ const win = audienceWindowRef.current;
118534
+ if (!win || win.closed) {
118535
+ return;
118536
+ }
118537
+ if (!ok) {
118538
+ try {
118539
+ win.close();
118540
+ } catch {
118541
+ }
118542
+ audienceWindowRef.current = null;
118543
+ sessionIdRef.current = "";
118544
+ return;
118545
+ }
118546
+ try {
118547
+ win.location.replace(audienceUrl.toString());
118548
+ } catch {
118549
+ try {
118550
+ win.close();
118551
+ } catch {
118552
+ }
118553
+ audienceWindowRef.current = null;
118554
+ sessionIdRef.current = "";
118555
+ }
118556
+ };
118557
+ if (content) {
118558
+ void storeAudienceContent(content).then(() => navigateOrClose(true)).catch(() => navigateOrClose(false));
118559
+ } else {
118560
+ navigateOrClose(true);
118561
+ }
118318
118562
  window.setTimeout(() => syncSlideToAudience(currentSlideIndex), 1500);
118319
118563
  pollTimerRef.current = setInterval(() => {
118320
- if (win.closed) {
118564
+ const win = audienceWindowRef.current;
118565
+ if (!win || win.closed) {
118321
118566
  audienceWindowRef.current = null;
118567
+ sessionIdRef.current = "";
118322
118568
  if (pollTimerRef.current !== null) {
118323
118569
  clearInterval(pollTimerRef.current);
118324
118570
  pollTimerRef.current = null;
@@ -118346,6 +118592,13 @@ function usePresenterWindow(input) {
118346
118592
  closeAudienceWindow();
118347
118593
  }
118348
118594
  }, [isPresenterMode, closeAudienceWindow]);
118595
+ React10.useEffect(() => {
118596
+ const handleBeforeUnload = () => {
118597
+ void clearAudienceContent();
118598
+ };
118599
+ window.addEventListener("beforeunload", handleBeforeUnload);
118600
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
118601
+ }, []);
118349
118602
  return {
118350
118603
  openAudienceWindow,
118351
118604
  closeAudienceWindow,
@@ -118383,6 +118636,7 @@ function useAudienceMode(input) {
118383
118636
  if (!isAudienceTab()) {
118384
118637
  return;
118385
118638
  }
118639
+ const expectedSessionId = parseAudienceNonce();
118386
118640
  let channel;
118387
118641
  try {
118388
118642
  channel = new BroadcastChannel(PRESENTER_CHANNEL_NAME);
@@ -118394,6 +118648,9 @@ function useAudienceMode(input) {
118394
118648
  if (!data || data.origin !== PRESENTER_MSG_ORIGIN) {
118395
118649
  return;
118396
118650
  }
118651
+ if (expectedSessionId && data.sessionId !== expectedSessionId) {
118652
+ return;
118653
+ }
118397
118654
  if (data.type === "presenter-slide-change") {
118398
118655
  onSetActiveSlideIndex(data.slideIndex);
118399
118656
  }
@@ -119327,6 +119584,12 @@ function useTouchGestures(input) {
119327
119584
  callbacksRef.current = callbacks;
119328
119585
  const scaleRef = React10.useRef(currentScale);
119329
119586
  scaleRef.current = currentScale;
119587
+ const [targetVersion, setTargetVersion] = React10.useState(0);
119588
+ const lastTargetRef = React10.useRef(null);
119589
+ if (targetRef.current !== lastTargetRef.current) {
119590
+ lastTargetRef.current = targetRef.current;
119591
+ queueMicrotask(() => setTargetVersion((v) => v + 1));
119592
+ }
119330
119593
  React10.useEffect(() => {
119331
119594
  const el = targetRef.current;
119332
119595
  if (!el || !enabled) {
@@ -119415,7 +119678,7 @@ function useTouchGestures(input) {
119415
119678
  el.removeEventListener("touchcancel", handleTouchCancel);
119416
119679
  cancelLongPress();
119417
119680
  };
119418
- }, [targetRef, enabled]);
119681
+ }, [targetRef, enabled, targetVersion]);
119419
119682
  }
119420
119683
 
119421
119684
  // src/viewer/utils/dom-helpers.ts
@@ -119436,11 +119699,31 @@ function safeConfirm(message) {
119436
119699
  return false;
119437
119700
  }
119438
119701
  }
119702
+ function sanitizeDownloadFilename(input) {
119703
+ if (typeof input !== "string" || input.trim().length === 0) {
119704
+ return "presentation.pptx";
119705
+ }
119706
+ let cleaned = input.replace(/[\x00-\x1f\x7f"\\/:*?<>|]/g, "_").replace(/\.\./g, "__").replace(/^\.+/, "").trim();
119707
+ if (cleaned.length === 0) {
119708
+ return "presentation.pptx";
119709
+ }
119710
+ if (cleaned.length > 200) {
119711
+ const dot = cleaned.lastIndexOf(".");
119712
+ if (dot > 0 && cleaned.length - dot <= 16) {
119713
+ const ext = cleaned.slice(dot);
119714
+ cleaned = cleaned.slice(0, 200 - ext.length) + ext;
119715
+ } else {
119716
+ cleaned = cleaned.slice(0, 200);
119717
+ }
119718
+ }
119719
+ return cleaned;
119720
+ }
119439
119721
  function downloadBlob(blob, filename) {
119722
+ const safeName = sanitizeDownloadFilename(filename);
119440
119723
  const url = URL.createObjectURL(blob);
119441
119724
  const a2 = document.createElement("a");
119442
119725
  a2.href = url;
119443
- a2.download = filename;
119726
+ a2.download = safeName;
119444
119727
  document.body.appendChild(a2);
119445
119728
  a2.click();
119446
119729
  setTimeout(() => {
@@ -119871,27 +120154,82 @@ function openAutosaveDb2() {
119871
120154
  req.onerror = () => reject(req.error);
119872
120155
  });
119873
120156
  }
119874
- async function saveToIndexedDb(filePath, data) {
120157
+ async function deleteOldestAutosaveEntry() {
119875
120158
  const db = await openAutosaveDb2();
119876
- return new Promise((resolve2, reject) => {
119877
- const tx = db.transaction(STORE_NAME3, "readwrite");
119878
- const store = tx.objectStore(STORE_NAME3);
119879
- store.put({
119880
- key: filePath,
119881
- data,
119882
- timestamp: Date.now(),
119883
- size: data.byteLength
119884
- });
119885
- tx.oncomplete = () => {
119886
- db.close();
119887
- resolve2(true);
119888
- };
119889
- tx.onerror = () => {
119890
- db.close();
119891
- reject(tx.error);
119892
- };
120159
+ return new Promise((resolve2) => {
120160
+ try {
120161
+ const tx = db.transaction(STORE_NAME3, "readwrite");
120162
+ const store = tx.objectStore(STORE_NAME3);
120163
+ let oldestKey = null;
120164
+ let oldestTimestamp = Infinity;
120165
+ const cursorReq = store.openCursor();
120166
+ cursorReq.onsuccess = () => {
120167
+ const cursor = cursorReq.result;
120168
+ if (cursor) {
120169
+ const value = cursor.value;
120170
+ if (typeof value.timestamp === "number" && value.timestamp < oldestTimestamp) {
120171
+ oldestTimestamp = value.timestamp;
120172
+ oldestKey = cursor.primaryKey;
120173
+ }
120174
+ cursor.continue();
120175
+ } else if (oldestKey !== null) {
120176
+ store.delete(oldestKey);
120177
+ }
120178
+ };
120179
+ tx.oncomplete = () => {
120180
+ db.close();
120181
+ resolve2(oldestKey !== null);
120182
+ };
120183
+ tx.onerror = () => {
120184
+ db.close();
120185
+ resolve2(false);
120186
+ };
120187
+ } catch {
120188
+ try {
120189
+ db.close();
120190
+ } catch {
120191
+ }
120192
+ resolve2(false);
120193
+ }
119893
120194
  });
119894
120195
  }
120196
+ function putAutosaveRecord(filePath, data) {
120197
+ return openAutosaveDb2().then(
120198
+ (db) => new Promise((resolve2, reject) => {
120199
+ const tx = db.transaction(STORE_NAME3, "readwrite");
120200
+ const store = tx.objectStore(STORE_NAME3);
120201
+ store.put({
120202
+ key: filePath,
120203
+ data,
120204
+ timestamp: Date.now(),
120205
+ size: data.byteLength
120206
+ });
120207
+ tx.oncomplete = () => {
120208
+ db.close();
120209
+ resolve2(true);
120210
+ };
120211
+ tx.onerror = () => {
120212
+ db.close();
120213
+ reject(tx.error);
120214
+ };
120215
+ })
120216
+ );
120217
+ }
120218
+ async function saveToIndexedDb(filePath, data) {
120219
+ try {
120220
+ return await putAutosaveRecord(filePath, data);
120221
+ } catch (err) {
120222
+ const errName = err instanceof Error || err instanceof DOMException ? err.name : "";
120223
+ if (errName !== "QuotaExceededError") {
120224
+ throw err;
120225
+ }
120226
+ const deleted = await deleteOldestAutosaveEntry();
120227
+ if (!deleted) {
120228
+ throw err;
120229
+ }
120230
+ return putAutosaveRecord(filePath, data);
120231
+ }
120232
+ }
119895
120233
  function useAutosave(input) {
119896
120234
  const {
119897
120235
  isDirty,
@@ -120008,6 +120346,25 @@ function collectReferencedFontFamilies(slides) {
120008
120346
  }
120009
120347
  return families;
120010
120348
  }
120349
+ var FONT_NAME_UNSAFE_CHARS = /["\\\n\r;}<>]/;
120350
+ var FONT_FORMAT_ALLOWED = /* @__PURE__ */ new Set([
120351
+ "truetype",
120352
+ "opentype",
120353
+ "woff",
120354
+ "woff2",
120355
+ "svg",
120356
+ "embedded-opentype"
120357
+ ]);
120358
+ var FONT_DATA_URL_PATTERN = /^data:font\/[a-z0-9+.-]+(?:;charset=[a-z0-9-]+)?;base64,[A-Za-z0-9+/=]+$/i;
120359
+ function isFontDataUrlSafe(url) {
120360
+ if (typeof url !== "string" || url.length === 0) {
120361
+ return false;
120362
+ }
120363
+ if (url.startsWith("blob:")) {
120364
+ return true;
120365
+ }
120366
+ return FONT_DATA_URL_PATTERN.test(url);
120367
+ }
120011
120368
  function useFontInjection({ embeddedFonts, slides }) {
120012
120369
  React10.useEffect(() => {
120013
120370
  if (!embeddedFonts.length) {
@@ -120015,17 +120372,28 @@ function useFontInjection({ embeddedFonts, slides }) {
120015
120372
  }
120016
120373
  const styleEl = document.createElement("style");
120017
120374
  styleEl.id = EMBEDDED_FONTS_STYLE_ID;
120018
- const cssRules = embeddedFonts.map((font) => {
120375
+ const cssRules = embeddedFonts.flatMap((font) => {
120376
+ if (typeof font.name !== "string" || font.name.length === 0 || FONT_NAME_UNSAFE_CHARS.test(font.name)) {
120377
+ return [];
120378
+ }
120379
+ if (!isFontDataUrlSafe(font.dataUrl)) {
120380
+ return [];
120381
+ }
120382
+ const fontFormat = font.format ?? "truetype";
120383
+ if (!FONT_FORMAT_ALLOWED.has(fontFormat)) {
120384
+ return [];
120385
+ }
120019
120386
  const fontWeight = font.bold ? "700" : "400";
120020
120387
  const fontStyleCss = font.italic ? "italic" : "normal";
120021
- const fontFormat = font.format ?? "truetype";
120022
- return `@font-face {
120388
+ return [
120389
+ `@font-face {
120023
120390
  font-family: "${font.name}";
120024
120391
  src: url("${font.dataUrl}") format("${fontFormat}");
120025
120392
  font-weight: ${fontWeight};
120026
120393
  font-style: ${fontStyleCss};
120027
120394
  font-display: swap;
120028
- }`;
120395
+ }`
120396
+ ];
120029
120397
  }).join("\n");
120030
120398
  styleEl.textContent = cssRules;
120031
120399
  document.head.appendChild(styleEl);
@@ -120054,7 +120422,7 @@ function useFontInjection({ embeddedFonts, slides }) {
120054
120422
  const linkEl = document.createElement("link");
120055
120423
  linkEl.id = GOOGLE_FONTS_LINK_ID;
120056
120424
  linkEl.rel = "stylesheet";
120057
- linkEl.href = `https://fonts.googleapis.com/css2?${googleFamilies.map((f) => `family=${GOOGLE_FONTS_AVAILABLE[f]}`).join("&")}&display=swap`;
120425
+ linkEl.href = `https://fonts.googleapis.com/css2?${googleFamilies.map((f) => `family=${encodeURIComponent(GOOGLE_FONTS_AVAILABLE[f])}`).join("&")}&display=swap`;
120058
120426
  document.head.appendChild(linkEl);
120059
120427
  return () => {
120060
120428
  const existing = document.getElementById(GOOGLE_FONTS_LINK_ID);
@@ -120070,13 +120438,18 @@ function useFontInjection({ embeddedFonts, slides }) {
120070
120438
  }
120071
120439
  const styleEl = document.createElement("style");
120072
120440
  styleEl.id = SYMBOL_FONTS_STYLE_ID;
120073
- const rules = neededSymbolFonts.map(
120074
- (font) => `@font-face {
120441
+ const rules = neededSymbolFonts.flatMap((font) => {
120442
+ if (typeof font !== "string" || FONT_NAME_UNSAFE_CHARS.test(font)) {
120443
+ return [];
120444
+ }
120445
+ return [
120446
+ `@font-face {
120075
120447
  font-family: "${font}";
120076
120448
  src: local("${font}"), local("${font} Regular");
120077
120449
  font-display: swap;
120078
120450
  }`
120079
- ).join("\n");
120451
+ ];
120452
+ }).join("\n");
120080
120453
  styleEl.textContent = rules;
120081
120454
  document.head.appendChild(styleEl);
120082
120455
  return () => {
@@ -120219,16 +120592,17 @@ function useLoadContent({
120219
120592
  `[pptx] Large file detected (${fileSizeMB.toFixed(1)} MB). Loading may use significant memory.`
120220
120593
  );
120221
120594
  }
120222
- if (handlerRef.current) {
120223
- handlerRef.current.dispose();
120224
- handlerRef.current = null;
120225
- }
120595
+ const previousHandler = handlerRef.current;
120226
120596
  const handler = new pptxViewerCore.PptxHandler();
120227
120597
  const parsed = await handler.load(buffer);
120228
120598
  if (cancelled || token !== renderTokenRef.current) {
120229
120599
  handler.dispose();
120230
120600
  return;
120231
120601
  }
120602
+ if (previousHandler) {
120603
+ previousHandler.dispose();
120604
+ }
120605
+ handlerRef.current = null;
120232
120606
  const mediaElements = [];
120233
120607
  for (const slide of parsed.slides) {
120234
120608
  collectMediaElements(slide.elements, mediaElements);
@@ -120273,6 +120647,7 @@ function useLoadContent({
120273
120647
  })
120274
120648
  );
120275
120649
  const { paths: imagePaths, refs: imageRefs } = collectImagePaths(parsed.slides);
120650
+ let nextSlides = parsed.slides;
120276
120651
  if (imagePaths.size > 0) {
120277
120652
  const resolvedMap = /* @__PURE__ */ new Map();
120278
120653
  await Promise.all(
@@ -120286,15 +120661,47 @@ function useLoadContent({
120286
120661
  }
120287
120662
  })
120288
120663
  );
120664
+ const elementPatches = /* @__PURE__ */ new Map();
120289
120665
  for (const ref of imageRefs) {
120290
120666
  const url = resolvedMap.get(ref.path);
120291
- if (url) {
120292
- ref.element[ref.field] = url;
120667
+ if (!url) {
120668
+ continue;
120293
120669
  }
120670
+ const id2 = ref.element.id;
120671
+ const existing = elementPatches.get(id2) ?? {};
120672
+ existing[ref.field] = url;
120673
+ elementPatches.set(id2, existing);
120674
+ }
120675
+ if (elementPatches.size > 0) {
120676
+ const patchElements = (elements) => {
120677
+ let mutated = false;
120678
+ const next = elements.map((el) => {
120679
+ let updated = el;
120680
+ const patch = elementPatches.get(el.id);
120681
+ if (patch) {
120682
+ updated = { ...el, ...patch };
120683
+ }
120684
+ if (updated.type === "group" && updated.children?.length) {
120685
+ const newChildren = patchElements(updated.children);
120686
+ if (newChildren !== updated.children) {
120687
+ updated = { ...updated, children: newChildren };
120688
+ }
120689
+ }
120690
+ if (updated !== el) {
120691
+ mutated = true;
120692
+ }
120693
+ return updated;
120694
+ });
120695
+ return mutated ? next : elements;
120696
+ };
120697
+ nextSlides = parsed.slides.map((s) => {
120698
+ const newElements = patchElements(s.elements);
120699
+ return newElements === s.elements ? s : { ...s, elements: newElements };
120700
+ });
120294
120701
  }
120295
120702
  }
120296
120703
  handlerRef.current = handler;
120297
- setSlides(parsed.slides);
120704
+ setSlides(nextSlides);
120298
120705
  setTemplateElementsBySlideId({});
120299
120706
  setCanvasSize({
120300
120707
  width: parsed.width ?? DEFAULT_CANVAS_WIDTH,
@@ -121468,7 +121875,19 @@ function injectFontFaces(svg, fontFaces) {
121468
121875
  if (!fontFaces.length) {
121469
121876
  return svg;
121470
121877
  }
121471
- const styleBlock = `<style type="text/css">${fontFaces.map((f) => f.css).join("\n")}</style>`;
121878
+ const safeFontFaces = fontFaces.filter((f) => {
121879
+ if (f.css.toLowerCase().includes("</style")) {
121880
+ console.warn(
121881
+ `[export-svg] Dropping @font-face entry for "${f.family}" containing "</style" \u2014 would break out of the <style> block.`
121882
+ );
121883
+ return false;
121884
+ }
121885
+ return true;
121886
+ });
121887
+ if (!safeFontFaces.length) {
121888
+ return svg;
121889
+ }
121890
+ const styleBlock = `<style type="text/css">${safeFontFaces.map((f) => f.css).join("\n")}</style>`;
121472
121891
  if (svg.includes("<defs>")) {
121473
121892
  return svg.replace("<defs>", `<defs>${styleBlock}`);
121474
121893
  }
@@ -121794,7 +122213,7 @@ function useExportHandlers(input) {
121794
122213
  activeSlideIndexForGuides,
121795
122214
  modalControls
121796
122215
  });
121797
- const handleExportPng = async () => {
122216
+ const handleExportPng = React10.useCallback(async () => {
121798
122217
  const stageEl = canvasStageRef.current;
121799
122218
  if (!stageEl) {
121800
122219
  return;
@@ -121806,8 +122225,8 @@ function useExportHandlers(input) {
121806
122225
  } catch (err) {
121807
122226
  console.error("[PowerPointViewer] PNG export failed:", err);
121808
122227
  }
121809
- };
121810
- const handleExportPdf = async () => {
122228
+ }, [canvasStageRef, activeSlideIndex, activeSlide?.backgroundColor]);
122229
+ const handleExportPdf = React10.useCallback(async () => {
121811
122230
  if (!canvasStageRef.current) {
121812
122231
  return;
121813
122232
  }
@@ -121848,8 +122267,8 @@ function useExportHandlers(input) {
121848
122267
  exportAbortRef.current = null;
121849
122268
  setExportModalOpen(false);
121850
122269
  }
121851
- };
121852
- const handleExportNotesPdf = async () => {
122270
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
122271
+ const handleExportNotesPdf = React10.useCallback(async () => {
121853
122272
  if (!canvasStageRef.current) {
121854
122273
  return;
121855
122274
  }
@@ -121892,8 +122311,8 @@ function useExportHandlers(input) {
121892
122311
  exportAbortRef.current = null;
121893
122312
  setExportModalOpen(false);
121894
122313
  }
121895
- };
121896
- const handleCopySlideAsImage = async () => {
122314
+ }, [canvasStageRef, slides, setActiveSlideIndex, activeSlideIndex]);
122315
+ const handleCopySlideAsImage = React10.useCallback(async () => {
121897
122316
  const stageEl = canvasStageRef.current;
121898
122317
  if (!stageEl) {
121899
122318
  return;
@@ -121905,8 +122324,8 @@ function useExportHandlers(input) {
121905
122324
  } catch (err) {
121906
122325
  console.error("[PowerPointViewer] Copy slide as image failed:", err);
121907
122326
  }
121908
- };
121909
- const handleExportVideo = async () => {
122327
+ }, [canvasStageRef, activeSlide?.backgroundColor]);
122328
+ const handleExportVideo = React10.useCallback(async () => {
121910
122329
  if (!canvasStageRef.current) {
121911
122330
  return;
121912
122331
  }
@@ -121948,8 +122367,8 @@ function useExportHandlers(input) {
121948
122367
  exportAbortRef.current = null;
121949
122368
  setExportModalOpen(false);
121950
122369
  }
121951
- };
121952
- const handleExportGif = async () => {
122370
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
122371
+ const handleExportGif = React10.useCallback(async () => {
121953
122372
  if (!canvasStageRef.current) {
121954
122373
  return;
121955
122374
  }
@@ -121987,7 +122406,7 @@ function useExportHandlers(input) {
121987
122406
  exportAbortRef.current = null;
121988
122407
  setExportModalOpen(false);
121989
122408
  }
121990
- };
122409
+ }, [canvasStageRef, slides.length, setActiveSlideIndex, activeSlideIndex]);
121991
122410
  const handleCancelExport = React10.useCallback(() => {
121992
122411
  exportAbortRef.current?.abort();
121993
122412
  exportAbortRef.current = null;
@@ -122013,6 +122432,15 @@ function useExportHandlers(input) {
122013
122432
  exportStatusMessage
122014
122433
  };
122015
122434
  }
122435
+ function escapeHtmlAttr(value) {
122436
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
122437
+ }
122438
+ function safeDataImageSrc(src) {
122439
+ if (typeof src !== "string" || !src.startsWith("data:image/")) {
122440
+ return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgAAIAAAUAAen63NgAAAAASUVORK5CYII=";
122441
+ }
122442
+ return escapeHtmlAttr(src);
122443
+ }
122016
122444
  function openPrintWindow(title, bodyHtml, orientation, colorFilter, frameSlides) {
122017
122445
  const printWindow = window.open("", "_blank", "noopener,noreferrer");
122018
122446
  if (!printWindow) {
@@ -122195,7 +122623,7 @@ function usePrintHandlers(input) {
122195
122623
  const slideImages = slideIndices.map((idx) => allImages[idx]).filter(Boolean);
122196
122624
  if (settings.printWhat === "slides") {
122197
122625
  const bodyHtml = slideImages.map(
122198
- (img, i3) => `<section class="page slide-page"><img class="slide-img" src="${img}" alt="Slide ${slideIndices[i3] + 1}" /></section>`
122626
+ (img, i3) => `<section class="page slide-page"><img class="slide-img" src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3] + 1}" /></section>`
122199
122627
  ).join("");
122200
122628
  openPrintWindow(
122201
122629
  "Slides",
@@ -122211,7 +122639,7 @@ function usePrintHandlers(input) {
122211
122639
  const idx = slideIndices[i3];
122212
122640
  const notes = slides[idx]?.notes?.trim() || "";
122213
122641
  return `<section class="page notes-page">
122214
- <img class="notes-slide" src="${img}" alt="Slide ${idx + 1}" />
122642
+ <img class="notes-slide" src="${safeDataImageSrc(img)}" alt="Slide ${idx + 1}" />
122215
122643
  <div class="notes-text">${escapeHtml2(notes)}</div>
122216
122644
  </section>`;
122217
122645
  }).join("");
@@ -122243,14 +122671,14 @@ function usePrintHandlers(input) {
122243
122671
  if (isThreePerPage) {
122244
122672
  const rows = Array.from({ length: spp }, (_, cellIndex) => {
122245
122673
  const img = pageImgs[cellIndex];
122246
- const slideCell = img ? `<div class="handout-cell"><img src="${img}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122674
+ const slideCell = img ? `<div class="handout-cell"><img src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122247
122675
  return `<div class="handout-row-3">${slideCell}${buildNoteLines()}</div>`;
122248
122676
  }).join("");
122249
122677
  pages.push(`<section class="page"><div class="handout-grid-3">${rows}</div></section>`);
122250
122678
  } else {
122251
122679
  const cells = Array.from({ length: spp }, (_, cellIndex) => {
122252
122680
  const img = pageImgs[cellIndex];
122253
- return img ? `<div class="handout-cell"><img src="${img}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122681
+ return img ? `<div class="handout-cell"><img src="${safeDataImageSrc(img)}" alt="Slide ${slideIndices[i3 + cellIndex] + 1}" /></div>` : `<div class="handout-cell"></div>`;
122254
122682
  }).join("");
122255
122683
  pages.push(
122256
122684
  `<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>`