pptx-react-viewer 1.0.10 → 1.0.11

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/README.md +68 -0
  2. package/dist/{PowerPointViewer-K2URyPlJ.d.mts → PowerPointViewer-gSKLhZDo.d.mts} +35 -1
  3. package/dist/{PowerPointViewer-K2URyPlJ.d.ts → PowerPointViewer-gSKLhZDo.d.ts} +35 -1
  4. package/dist/index.d.mts +2 -2
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +2532 -882
  7. package/dist/index.mjs +2533 -883
  8. package/dist/pptx-viewer.css +1 -1
  9. package/dist/viewer/index.d.mts +15 -3
  10. package/dist/viewer/index.d.ts +15 -3
  11. package/dist/viewer/index.js +2731 -985
  12. package/dist/viewer/index.mjs +2732 -987
  13. package/node_modules/emf-converter/package.json +1 -1
  14. package/node_modules/mtx-decompressor/package.json +1 -1
  15. package/node_modules/pptx-viewer-core/dist/{SvgExporter-DqcmwxFu.d.mts → SvgExporter-B4-1_Hjp.d.mts} +1 -1
  16. package/node_modules/pptx-viewer-core/dist/{SvgExporter-BZJguJbp.d.ts → SvgExporter-CPr1npgo.d.ts} +1 -1
  17. package/node_modules/pptx-viewer-core/dist/cli/index.d.mts +2 -2
  18. package/node_modules/pptx-viewer-core/dist/cli/index.d.ts +2 -2
  19. package/node_modules/pptx-viewer-core/dist/converter/index.d.mts +3 -3
  20. package/node_modules/pptx-viewer-core/dist/converter/index.d.ts +3 -3
  21. package/node_modules/pptx-viewer-core/dist/index.d.mts +5 -5
  22. package/node_modules/pptx-viewer-core/dist/index.d.ts +5 -5
  23. package/node_modules/pptx-viewer-core/dist/{presentation-Bo7cMMCe.d.mts → presentation-DgkIYhXo.d.mts} +6 -0
  24. package/node_modules/pptx-viewer-core/dist/{presentation-Bo7cMMCe.d.ts → presentation-DgkIYhXo.d.ts} +6 -0
  25. package/node_modules/pptx-viewer-core/dist/{text-operations-D0f1jred.d.ts → text-operations-B6U6XxWt.d.ts} +1 -1
  26. package/node_modules/pptx-viewer-core/dist/{text-operations-Bo-WG-Z8.d.mts → text-operations-dYKZp3zE.d.mts} +1 -1
  27. package/node_modules/pptx-viewer-core/package.json +1 -1
  28. package/package.json +4 -4
@@ -43328,7 +43328,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43328
43328
  return x2 === y && (0 !== x2 || 1 / x2 === 1 / y) || x2 !== x2 && y !== y;
43329
43329
  }
43330
43330
  function useSyncExternalStore$2(subscribe3, getSnapshot2) {
43331
- didWarnOld18Alpha || void 0 === React90.startTransition || (didWarnOld18Alpha = true, console.error(
43331
+ didWarnOld18Alpha || void 0 === React95.startTransition || (didWarnOld18Alpha = true, console.error(
43332
43332
  "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."
43333
43333
  ));
43334
43334
  var value = getSnapshot2();
@@ -43338,7 +43338,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43338
43338
  "The result of getSnapshot should be cached to avoid an infinite loop"
43339
43339
  ), didWarnUncachedGetSnapshot = true);
43340
43340
  }
43341
- cachedValue = useState80({
43341
+ cachedValue = useState84({
43342
43342
  inst: { value, getSnapshot: getSnapshot2 }
43343
43343
  });
43344
43344
  var inst = cachedValue[0].inst, forceUpdate = cachedValue[1];
@@ -43350,7 +43350,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43350
43350
  },
43351
43351
  [subscribe3, value, getSnapshot2]
43352
43352
  );
43353
- useEffect64(
43353
+ useEffect68(
43354
43354
  function() {
43355
43355
  checkIfSnapshotChanged(inst) && forceUpdate({ inst });
43356
43356
  return subscribe3(function() {
@@ -43376,8 +43376,8 @@ var require_use_sync_external_store_shim_development = __commonJS({
43376
43376
  return getSnapshot2();
43377
43377
  }
43378
43378
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
43379
- var React90 = __require("react"), objectIs = "function" === typeof Object.is ? Object.is : is2, useState80 = React90.useState, useEffect64 = React90.useEffect, useLayoutEffect7 = React90.useLayoutEffect, useDebugValue = React90.useDebugValue, didWarnOld18Alpha = false, didWarnUncachedGetSnapshot = false, shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
43380
- exports$1.useSyncExternalStore = void 0 !== React90.useSyncExternalStore ? React90.useSyncExternalStore : shim;
43379
+ var React95 = __require("react"), objectIs = "function" === typeof Object.is ? Object.is : is2, useState84 = React95.useState, useEffect68 = React95.useEffect, useLayoutEffect7 = React95.useLayoutEffect, useDebugValue = React95.useDebugValue, didWarnOld18Alpha = false, didWarnUncachedGetSnapshot = false, shim = "undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement ? useSyncExternalStore$1 : useSyncExternalStore$2;
43380
+ exports$1.useSyncExternalStore = void 0 !== React95.useSyncExternalStore ? React95.useSyncExternalStore : shim;
43381
43381
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
43382
43382
  })();
43383
43383
  }
@@ -43400,9 +43400,9 @@ var require_with_selector_development = __commonJS({
43400
43400
  return x2 === y && (0 !== x2 || 1 / x2 === 1 / y) || x2 !== x2 && y !== y;
43401
43401
  }
43402
43402
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
43403
- var React90 = __require("react"), shim = require_shim(), objectIs = "function" === typeof Object.is ? Object.is : is2, useSyncExternalStore3 = shim.useSyncExternalStore, useRef66 = React90.useRef, useEffect64 = React90.useEffect, useMemo42 = React90.useMemo, useDebugValue = React90.useDebugValue;
43403
+ var React95 = __require("react"), shim = require_shim(), objectIs = "function" === typeof Object.is ? Object.is : is2, useSyncExternalStore3 = shim.useSyncExternalStore, useRef69 = React95.useRef, useEffect68 = React95.useEffect, useMemo42 = React95.useMemo, useDebugValue = React95.useDebugValue;
43404
43404
  exports$1.useSyncExternalStoreWithSelector = function(subscribe3, getSnapshot2, getServerSnapshot2, selector, isEqual) {
43405
- var instRef = useRef66(null);
43405
+ var instRef = useRef69(null);
43406
43406
  if (null === instRef.current) {
43407
43407
  var inst = { hasValue: false, value: null };
43408
43408
  instRef.current = inst;
@@ -43443,7 +43443,7 @@ var require_with_selector_development = __commonJS({
43443
43443
  [getSnapshot2, getServerSnapshot2, selector, isEqual]
43444
43444
  );
43445
43445
  var value = useSyncExternalStore3(subscribe3, instRef[0], instRef[1]);
43446
- useEffect64(
43446
+ useEffect68(
43447
43447
  function() {
43448
43448
  inst.hasValue = true;
43449
43449
  inst.value = value;
@@ -69731,6 +69731,7 @@ var UNGROUPED_SECTION_ID = "__ungrouped__";
69731
69731
 
69732
69732
  // src/viewer/constants/toolbar.ts
69733
69733
  var TOOLBAR_SECTIONS = [
69734
+ { id: "file", label: "File" },
69734
69735
  { id: "home", label: "Home" },
69735
69736
  { id: "insert", label: "Insert" },
69736
69737
  { id: "text", label: "Text" },
@@ -69738,8 +69739,11 @@ var TOOLBAR_SECTIONS = [
69738
69739
  { id: "arrange", label: "Arrange" },
69739
69740
  { id: "design", label: "Design" },
69740
69741
  { id: "transitions", label: "Transitions" },
69742
+ { id: "animations", label: "Animations" },
69743
+ { id: "slideShow", label: "Slide Show" },
69741
69744
  { id: "review", label: "Review" },
69742
- { id: "view", label: "View" }
69745
+ { id: "view", label: "View" },
69746
+ { id: "help", label: "Help" }
69743
69747
  ];
69744
69748
  var SHORTCUT_REFERENCE_ITEMS = [
69745
69749
  { action: "Undo", shortcut: "Ctrl/Cmd+Z" },
@@ -85777,6 +85781,7 @@ function AccessibilityPanel({
85777
85781
  reducedMotion,
85778
85782
  onToggleReducedMotion
85779
85783
  }) {
85784
+ const { t: t2 } = reactI18next.useTranslation();
85780
85785
  if (!isOpen) {
85781
85786
  return null;
85782
85787
  }
@@ -85784,31 +85789,27 @@ function AccessibilityPanel({
85784
85789
  "div",
85785
85790
  {
85786
85791
  role: "dialog",
85787
- "aria-label": "Accessibility Checker",
85792
+ "aria-label": t2("pptx.accessibility.title"),
85788
85793
  className: "absolute top-14 right-3 z-40 w-[min(28rem,calc(100%-1.5rem))] rounded border border-border bg-popover shadow-2xl",
85789
85794
  children: [
85790
85795
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-2", children: [
85791
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs uppercase tracking-wide text-foreground", children: "Accessibility Checker" }),
85796
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs uppercase tracking-wide text-foreground", children: t2("pptx.accessibility.title") }),
85792
85797
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
85793
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
85794
- issues.length,
85795
- " issue",
85796
- issues.length !== 1 ? "s" : ""
85797
- ] }),
85798
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: t2("pptx.accessibility.issueCount", { count: issues.length }) }),
85798
85799
  /* @__PURE__ */ jsxRuntime.jsx(
85799
85800
  "button",
85800
85801
  {
85801
85802
  type: "button",
85802
85803
  onClick: onClose,
85803
- "aria-label": "Close accessibility panel",
85804
+ "aria-label": t2("pptx.accessibility.closePanel"),
85804
85805
  className: "rounded px-2 py-1 text-[11px] text-foreground hover:bg-muted hover:text-foreground",
85805
- children: "Close"
85806
+ children: t2("pptx.accessibility.close")
85806
85807
  }
85807
85808
  )
85808
85809
  ] })
85809
85810
  ] }),
85810
85811
  onToggleReducedMotion !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-2", children: [
85811
- /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "reduced-motion-toggle", className: "text-xs text-foreground", children: "Reduce motion" }),
85812
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "reduced-motion-toggle", className: "text-xs text-foreground", children: t2("pptx.accessibility.reduceMotion") }),
85812
85813
  /* @__PURE__ */ jsxRuntime.jsx(
85813
85814
  "button",
85814
85815
  {
@@ -85837,9 +85838,9 @@ function AccessibilityPanel({
85837
85838
  "div",
85838
85839
  {
85839
85840
  role: "list",
85840
- "aria-label": "Accessibility issues",
85841
+ "aria-label": t2("pptx.accessibility.issuesList"),
85841
85842
  className: "max-h-72 overflow-y-auto p-2 space-y-1",
85842
- children: issues.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listitem", className: "text-center text-xs text-muted-foreground py-4", children: "No accessibility issues found." }) : issues.map((issue, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
85843
+ children: issues.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { role: "listitem", className: "text-center text-xs text-muted-foreground py-4", children: t2("pptx.accessibility.noIssues") }) : issues.map((issue, idx) => /* @__PURE__ */ jsxRuntime.jsxs(
85843
85844
  "div",
85844
85845
  {
85845
85846
  role: "listitem",
@@ -85851,7 +85852,7 @@ function AccessibilityPanel({
85851
85852
  children: [
85852
85853
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 mt-0.5", "aria-hidden": "true", children: issue.severity === "error" ? "\u25CF" : issue.severity === "warning" ? "\u25B2" : "\u2139" }),
85853
85854
  /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
85854
- issue.severity === "error" ? "Error: " : issue.severity === "warning" ? "Warning: " : "Info: ",
85855
+ issue.severity === "error" ? t2("pptx.accessibility.error") : issue.severity === "warning" ? t2("pptx.accessibility.warning") : t2("pptx.accessibility.info"),
85855
85856
  issue.message
85856
85857
  ] })
85857
85858
  ]
@@ -85903,22 +85904,32 @@ function ExportProgressModal({
85903
85904
  ) })
85904
85905
  ] }) });
85905
85906
  }
85906
- function formatAutosaveAge(timestamp) {
85907
+ function formatAutosaveAge(timestamp, t2) {
85907
85908
  const diff = Date.now() - timestamp;
85908
85909
  const minutes = Math.floor(diff / 6e4);
85909
85910
  if (minutes < 1) {
85910
- return "just now";
85911
+ return t2("pptx.autosave.justNow");
85911
85912
  }
85912
85913
  if (minutes === 1) {
85913
- return "1 min ago";
85914
+ return t2("pptx.autosave.oneMinAgo");
85914
85915
  }
85915
- return `${minutes} min ago`;
85916
+ return t2("pptx.autosave.minutesAgo", { count: minutes });
85916
85917
  }
85917
85918
  function StatusBar({
85918
85919
  slideCount,
85919
85920
  activeSlideIndex,
85920
85921
  isDirty,
85921
- autosaveStatus
85922
+ autosaveStatus,
85923
+ scale,
85924
+ onZoomIn,
85925
+ onZoomOut,
85926
+ onZoomToFit,
85927
+ isNotesExpanded,
85928
+ onToggleNotes,
85929
+ mode,
85930
+ onSetMode,
85931
+ onToggleSlideSorter,
85932
+ collaborationSlot
85922
85933
  }) {
85923
85934
  const { t: t2 } = reactI18next.useTranslation();
85924
85935
  let statusText;
@@ -85926,7 +85937,7 @@ function StatusBar({
85926
85937
  statusText = t2("pptx.autosave.saving");
85927
85938
  } else if (autosaveStatus?.state === "saved") {
85928
85939
  statusText = t2("pptx.autosave.saved", {
85929
- time: formatAutosaveAge(autosaveStatus.timestamp)
85940
+ time: formatAutosaveAge(autosaveStatus.timestamp, t2)
85930
85941
  });
85931
85942
  } else if (autosaveStatus?.state === "error") {
85932
85943
  statusText = t2("pptx.autosave.error");
@@ -85935,15 +85946,124 @@ function StatusBar({
85935
85946
  } else {
85936
85947
  statusText = t2("pptx.statusBar.allSaved");
85937
85948
  }
85938
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-3 py-1 border-t border-border bg-background/50 text-[10px] text-muted-foreground flex items-center justify-between", children: [
85939
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: slideCount > 0 ? `Slide ${Math.min(activeSlideIndex + 1, slideCount)} of ${slideCount}` : "No slides" }),
85949
+ const vb = "p-1 rounded-sm transition-colors hover:bg-accent/60 text-muted-foreground active:scale-95 active:opacity-80";
85950
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full px-2 py-0.5 border-t border-border bg-secondary/50 text-[10px] text-muted-foreground flex items-center gap-1", children: [
85951
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0", children: slideCount > 0 ? t2("pptx.statusBar.slideOf", {
85952
+ current: Math.min(activeSlideIndex + 1, slideCount),
85953
+ total: slideCount
85954
+ }) : t2("pptx.statusBar.noSlides") }),
85955
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-3 bg-border/40 mx-1 max-md:hidden" }),
85956
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 max-md:hidden text-[10px]", children: t2("pptx.statusBar.language") }),
85957
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-3 bg-border/60 mx-1 max-md:hidden" }),
85940
85958
  /* @__PURE__ */ jsxRuntime.jsx(
85941
85959
  "span",
85942
85960
  {
85943
- className: autosaveStatus?.state === "error" ? "text-red-400" : autosaveStatus?.state === "saving" ? "text-yellow-400" : "",
85961
+ className: cn(
85962
+ "shrink-0 max-md:hidden",
85963
+ autosaveStatus?.state === "error" ? "text-red-400" : autosaveStatus?.state === "saving" ? "text-yellow-400" : ""
85964
+ ),
85944
85965
  children: statusText
85945
85966
  }
85946
- )
85967
+ ),
85968
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
85969
+ onToggleNotes && /* @__PURE__ */ jsxRuntime.jsxs(
85970
+ "button",
85971
+ {
85972
+ type: "button",
85973
+ onClick: onToggleNotes,
85974
+ className: cn(
85975
+ vb,
85976
+ "flex items-center gap-1 text-[10px]",
85977
+ isNotesExpanded && "text-primary"
85978
+ ),
85979
+ title: t2("pptx.statusBar.toggleNotes"),
85980
+ "aria-label": t2("pptx.statusBar.toggleNotes"),
85981
+ children: [
85982
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuStickyNote, { className: "w-3 h-3" }),
85983
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-md:hidden", children: t2("pptx.notes.title") })
85984
+ ]
85985
+ }
85986
+ ),
85987
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-3 bg-border/60 mx-0.5" }),
85988
+ onSetMode && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", children: [
85989
+ /* @__PURE__ */ jsxRuntime.jsx(
85990
+ "button",
85991
+ {
85992
+ type: "button",
85993
+ onClick: () => onSetMode("edit"),
85994
+ className: cn(vb, mode === "edit" && "text-primary"),
85995
+ title: t2("pptx.statusBar.normalView"),
85996
+ "aria-label": t2("pptx.statusBar.normalView"),
85997
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuMonitor, { className: "w-3.5 h-3.5" })
85998
+ }
85999
+ ),
86000
+ onToggleSlideSorter && /* @__PURE__ */ jsxRuntime.jsx(
86001
+ "button",
86002
+ {
86003
+ type: "button",
86004
+ onClick: onToggleSlideSorter,
86005
+ className: vb,
86006
+ title: t2("pptx.statusBar.slideSorter"),
86007
+ "aria-label": t2("pptx.statusBar.slideSorter"),
86008
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuColumns2, { className: "w-3.5 h-3.5" })
86009
+ }
86010
+ ),
86011
+ /* @__PURE__ */ jsxRuntime.jsx(
86012
+ "button",
86013
+ {
86014
+ type: "button",
86015
+ onClick: () => onSetMode("present"),
86016
+ className: cn(vb, mode === "present" && "text-primary"),
86017
+ title: t2("pptx.statusBar.slideShow"),
86018
+ "aria-label": t2("pptx.statusBar.slideShow"),
86019
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuPresentation, { className: "w-3.5 h-3.5" })
86020
+ }
86021
+ )
86022
+ ] }),
86023
+ collaborationSlot && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
86024
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-3 bg-border/40 mx-0.5" }),
86025
+ collaborationSlot
86026
+ ] }),
86027
+ scale !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
86028
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-3 bg-border/60 mx-0.5" }),
86029
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", children: [
86030
+ onZoomOut && /* @__PURE__ */ jsxRuntime.jsx(
86031
+ "button",
86032
+ {
86033
+ type: "button",
86034
+ onClick: onZoomOut,
86035
+ className: vb,
86036
+ title: t2("pptx.statusBar.zoomOut"),
86037
+ "aria-label": t2("pptx.statusBar.zoomOut"),
86038
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuMinus, { className: "w-3 h-3" })
86039
+ }
86040
+ ),
86041
+ /* @__PURE__ */ jsxRuntime.jsxs(
86042
+ "button",
86043
+ {
86044
+ type: "button",
86045
+ onClick: onZoomToFit,
86046
+ className: "px-1.5 py-0.5 rounded-sm hover:bg-accent/60 text-[10px] text-muted-foreground tabular-nums min-w-[3rem] text-center transition-colors",
86047
+ title: t2("pptx.statusBar.zoomToFit"),
86048
+ children: [
86049
+ Math.round((scale ?? 1) * 100),
86050
+ "%"
86051
+ ]
86052
+ }
86053
+ ),
86054
+ onZoomIn && /* @__PURE__ */ jsxRuntime.jsx(
86055
+ "button",
86056
+ {
86057
+ type: "button",
86058
+ onClick: onZoomIn,
86059
+ className: vb,
86060
+ title: t2("pptx.statusBar.zoomIn"),
86061
+ "aria-label": t2("pptx.statusBar.zoomIn"),
86062
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuPlus, { className: "w-3 h-3" })
86063
+ }
86064
+ )
86065
+ ] })
86066
+ ] })
85947
86067
  ] });
85948
86068
  }
85949
86069
  function ResizeHandle({
@@ -90759,6 +90879,7 @@ function FindReplacePanel({
90759
90879
  onReplaceAll,
90760
90880
  onClose
90761
90881
  }) {
90882
+ const { t: t2 } = reactI18next.useTranslation();
90762
90883
  const searchInputRef = React10.useRef(null);
90763
90884
  React10.useEffect(() => {
90764
90885
  searchInputRef.current?.focus();
@@ -90791,12 +90912,12 @@ function FindReplacePanel({
90791
90912
  [onClose, onReplace]
90792
90913
  );
90793
90914
  const hasResults = findResults.length > 0;
90794
- const matchCountLabel = hasResults ? `${findResultIndex + 1} of ${findResults.length}` : findQuery.length > 0 ? "No matches" : "";
90915
+ const matchCountLabel = hasResults ? t2("pptx.findReplace.matchCount", { current: findResultIndex + 1, total: findResults.length }) : findQuery.length > 0 ? t2("pptx.findReplace.noMatches") : "";
90795
90916
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 right-2 z-40 bg-popover border border-border rounded-lg shadow-lg p-3 text-xs text-foreground w-80 backdrop-blur", children: [
90796
90917
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
90797
90918
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-semibold text-sm inline-flex items-center gap-1.5", children: [
90798
90919
  /* @__PURE__ */ jsxRuntime.jsx(lu.LuSearch, { className: ic }),
90799
- "Find & Replace"
90920
+ t2("pptx.findReplace.title")
90800
90921
  ] }),
90801
90922
  /* @__PURE__ */ jsxRuntime.jsx(
90802
90923
  "button",
@@ -90804,8 +90925,8 @@ function FindReplacePanel({
90804
90925
  type: "button",
90805
90926
  className: btnGhost,
90806
90927
  onClick: onClose,
90807
- title: "Close (Escape)",
90808
- "aria-label": "Close find and replace",
90928
+ title: t2("pptx.findReplace.closeEscape"),
90929
+ "aria-label": t2("pptx.findReplace.closeAriaLabel"),
90809
90930
  children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuX, { className: ic })
90810
90931
  }
90811
90932
  )
@@ -90820,9 +90941,9 @@ function FindReplacePanel({
90820
90941
  value: findQuery,
90821
90942
  onChange: (e2) => onSetFindQuery(e2.target.value),
90822
90943
  onKeyDown: handleSearchKeyDown,
90823
- placeholder: "Find\u2026",
90944
+ placeholder: t2("pptx.findReplace.findPlaceholder"),
90824
90945
  className: "w-full bg-muted border border-border rounded px-2 py-1 pr-7 text-xs text-foreground placeholder-muted-foreground focus:border-primary focus:outline-none",
90825
- "aria-label": "Search text"
90946
+ "aria-label": t2("pptx.findReplace.searchText")
90826
90947
  }
90827
90948
  ),
90828
90949
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -90831,8 +90952,8 @@ function FindReplacePanel({
90831
90952
  type: "button",
90832
90953
  className: `absolute right-1 top-1/2 -translate-y-1/2 p-0.5 rounded transition-colors ${findMatchCase ? "bg-primary/80 text-white" : "text-muted-foreground hover:text-foreground hover:bg-accent"}`,
90833
90954
  onClick: () => onSetFindMatchCase(!findMatchCase),
90834
- title: "Match case",
90835
- "aria-label": "Toggle match case",
90955
+ title: t2("pptx.findReplace.matchCase"),
90956
+ "aria-label": t2("pptx.findReplace.toggleMatchCase"),
90836
90957
  "aria-pressed": findMatchCase,
90837
90958
  children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuCaseSensitive, { className: ic })
90838
90959
  }
@@ -90845,8 +90966,8 @@ function FindReplacePanel({
90845
90966
  className: btnGhost,
90846
90967
  onClick: () => onNavigateResult(-1),
90847
90968
  disabled: !hasResults,
90848
- title: "Previous match (Shift+Enter)",
90849
- "aria-label": "Previous match",
90969
+ title: t2("pptx.findReplace.previousMatch"),
90970
+ "aria-label": t2("pptx.findReplace.previousMatch"),
90850
90971
  children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronUp, { className: ic })
90851
90972
  }
90852
90973
  ),
@@ -90857,8 +90978,8 @@ function FindReplacePanel({
90857
90978
  className: btnGhost,
90858
90979
  onClick: () => onNavigateResult(1),
90859
90980
  disabled: !hasResults,
90860
- title: "Next match (Enter)",
90861
- "aria-label": "Next match",
90981
+ title: t2("pptx.findReplace.nextMatch"),
90982
+ "aria-label": t2("pptx.findReplace.nextMatch"),
90862
90983
  children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronDown, { className: ic })
90863
90984
  }
90864
90985
  )
@@ -90873,9 +90994,9 @@ function FindReplacePanel({
90873
90994
  value: replaceQuery,
90874
90995
  onChange: (e2) => onSetReplaceQuery(e2.target.value),
90875
90996
  onKeyDown: handleReplaceKeyDown,
90876
- placeholder: "Replace with\u2026",
90997
+ placeholder: t2("pptx.findReplace.replacePlaceholder"),
90877
90998
  className: "w-full bg-muted border border-border rounded pl-7 pr-2 py-1 text-xs text-foreground placeholder-muted-foreground focus:border-primary focus:outline-none",
90878
- "aria-label": "Replacement text"
90999
+ "aria-label": t2("pptx.findReplace.replacementText")
90879
91000
  }
90880
91001
  )
90881
91002
  ] }) }),
@@ -90887,8 +91008,8 @@ function FindReplacePanel({
90887
91008
  className: btnAction,
90888
91009
  onClick: onReplace,
90889
91010
  disabled: !hasResults,
90890
- title: "Replace current match",
90891
- children: "Replace"
91011
+ title: t2("pptx.findReplace.replaceCurrent"),
91012
+ children: t2("pptx.findReplace.replace")
90892
91013
  }
90893
91014
  ),
90894
91015
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -90898,8 +91019,8 @@ function FindReplacePanel({
90898
91019
  className: btnAction,
90899
91020
  onClick: onReplaceAll,
90900
91021
  disabled: !hasResults,
90901
- title: "Replace all matches",
90902
- children: "Replace All"
91022
+ title: t2("pptx.findReplace.replaceAllMatches"),
91023
+ children: t2("pptx.findReplace.replaceAll")
90903
91024
  }
90904
91025
  )
90905
91026
  ] })
@@ -91308,8 +91429,8 @@ function SlideItemInner({
91308
91429
  {
91309
91430
  ref: slideRef,
91310
91431
  className: cn(
91311
- "group relative cursor-pointer rounded-lg border-2 p-1 transition-all",
91312
- isActive ? "border-primary bg-primary/10" : "border-border bg-background/40 hover:border-muted-foreground",
91432
+ "group relative flex items-center gap-1 cursor-pointer py-0.5 px-1 transition-all",
91433
+ isActive && "bg-accent/40 before:absolute before:left-0 before:top-1 before:bottom-1 before:w-[3px] before:bg-primary before:rounded-r",
91313
91434
  isHidden && "opacity-50"
91314
91435
  ),
91315
91436
  draggable: canEdit,
@@ -91319,21 +91440,35 @@ function SlideItemInner({
91319
91440
  onDragOver,
91320
91441
  onDrop: (e2) => onDrop(e2, slideIndex),
91321
91442
  children: [
91322
- isHidden && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-lg pointer-events-none bg-[repeating-linear-gradient(135deg,transparent,transparent_4px,rgba(255,255,255,0.04)_4px,rgba(255,255,255,0.04)_8px)]" }),
91323
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative overflow-hidden rounded bg-white", children: [
91324
- /* @__PURE__ */ jsxRuntime.jsx(LazyThumbnail, { slide, canvasSize, previewHeight }),
91325
- (slide.comments?.length ?? 0) > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-0.5 right-0.5 flex items-center gap-0.5 rounded bg-amber-500/90 px-1 py-0.5 text-[8px] font-medium text-white leading-none", children: [
91326
- /* @__PURE__ */ jsxRuntime.jsx(lu.LuMessageSquare, { className: "w-2 h-2" }),
91327
- slide.comments?.length
91328
- ] })
91329
- ] }),
91330
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex items-center justify-between px-1", children: [
91331
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-[10px]", isActive ? "text-primary" : "text-muted-foreground"), children: slideIndex + 1 }),
91332
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
91333
- rehearsalTimings && typeof rehearsalTimings[slideIndex] === "number" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] font-mono text-amber-400/80 tabular-nums", children: formatTimingMs(rehearsalTimings[slideIndex]) }),
91334
- isHidden && /* @__PURE__ */ jsxRuntime.jsx(lu.LuEyeOff, { className: "w-3 h-3 text-muted-foreground" })
91335
- ] })
91336
- ] })
91443
+ /* @__PURE__ */ jsxRuntime.jsx(
91444
+ "span",
91445
+ {
91446
+ className: cn(
91447
+ "text-[10px] tabular-nums w-5 text-right shrink-0 select-none",
91448
+ isActive ? "text-primary font-medium" : "text-muted-foreground"
91449
+ ),
91450
+ children: slideIndex + 1
91451
+ }
91452
+ ),
91453
+ /* @__PURE__ */ jsxRuntime.jsxs(
91454
+ "div",
91455
+ {
91456
+ className: cn(
91457
+ "relative flex-1 overflow-hidden border transition-colors bg-white",
91458
+ isActive ? "border-primary/60" : "border-transparent group-hover:border-border/40"
91459
+ ),
91460
+ children: [
91461
+ isHidden && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 pointer-events-none z-10 bg-[repeating-linear-gradient(135deg,transparent,transparent_4px,rgba(255,255,255,0.04)_4px,rgba(255,255,255,0.04)_8px)]" }),
91462
+ /* @__PURE__ */ jsxRuntime.jsx(LazyThumbnail, { slide, canvasSize, previewHeight }),
91463
+ (slide.comments?.length ?? 0) > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-0.5 right-0.5 flex items-center gap-0.5 rounded bg-amber-500/90 px-1 py-0.5 text-[8px] font-medium text-white leading-none z-10", children: [
91464
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuMessageSquare, { className: "w-2 h-2" }),
91465
+ slide.comments?.length
91466
+ ] }),
91467
+ isHidden && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-0.5 right-0.5 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuEyeOff, { className: "w-3 h-3 text-muted-foreground" }) }),
91468
+ rehearsalTimings && typeof rehearsalTimings[slideIndex] === "number" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-0.5 left-0.5 z-10", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[8px] font-mono text-amber-400/80 tabular-nums bg-black/50 px-0.5 rounded", children: formatTimingMs(rehearsalTimings[slideIndex]) }) })
91469
+ ]
91470
+ }
91471
+ )
91337
91472
  ]
91338
91473
  }
91339
91474
  );
@@ -91454,7 +91589,7 @@ function SlidesPaneSidebar({
91454
91589
  onSlideContextMenu,
91455
91590
  onMoveSlide,
91456
91591
  onAddSlide,
91457
- onCollapse,
91592
+ onCollapse: _onCollapse,
91458
91593
  onAddSection,
91459
91594
  onRenameSection,
91460
91595
  onDeleteSection,
@@ -91659,50 +91794,23 @@ function SlidesPaneSidebar({
91659
91794
  {
91660
91795
  role: "navigation",
91661
91796
  "aria-label": "Slides",
91662
- className: "flex h-full flex-col border-r border-border bg-background/70 backdrop-blur-sm",
91797
+ className: "flex h-full flex-col border-r border-border bg-secondary/30",
91663
91798
  style: panelWidth ? { width: panelWidth, flexShrink: 0 } : void 0,
91664
91799
  children: [
91665
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-3 py-2", children: [
91666
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t2("pptx.sections.slides") }),
91667
- /* @__PURE__ */ jsxRuntime.jsx(
91668
- "button",
91669
- {
91670
- type: "button",
91671
- className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
91672
- title: t2("pptx.sections.collapsePane"),
91673
- onClick: onCollapse,
91674
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuPanelLeftClose, { className: "h-3.5 w-3.5" })
91675
- }
91676
- )
91677
- ] }),
91678
91800
  shouldVirtualize ? renderVirtualized() : renderNonVirtualized(),
91679
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-border/60 px-3 py-2 space-y-1", children: [
91680
- /* @__PURE__ */ jsxRuntime.jsxs(
91681
- "button",
91682
- {
91683
- type: "button",
91684
- className: "flex w-full items-center justify-center gap-1 rounded bg-muted/80 px-2 py-1.5 text-xs text-foreground hover:bg-accent disabled:cursor-not-allowed disabled:opacity-40",
91685
- disabled: !canEdit,
91686
- onClick: onAddSlide,
91687
- children: [
91688
- /* @__PURE__ */ jsxRuntime.jsx(lu.LuPlus, { className: "h-3.5 w-3.5" }),
91689
- t2("pptx.sections.addSlide")
91690
- ]
91691
- }
91692
- ),
91693
- canEdit && onAddSection && /* @__PURE__ */ jsxRuntime.jsxs(
91694
- "button",
91695
- {
91696
- type: "button",
91697
- className: "flex w-full items-center justify-center gap-1 rounded bg-muted/50 px-2 py-1 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground",
91698
- onClick: () => onAddSection(t2("pptx.sections.defaultName"), activeSlideIndex),
91699
- children: [
91700
- /* @__PURE__ */ jsxRuntime.jsx(lu.LuPlus, { className: "h-3 w-3" }),
91701
- t2("pptx.sections.addSection")
91702
- ]
91703
- }
91704
- )
91705
- ] }),
91801
+ canEdit && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border/60 px-2 py-1.5", children: /* @__PURE__ */ jsxRuntime.jsxs(
91802
+ "button",
91803
+ {
91804
+ type: "button",
91805
+ className: "flex w-full items-center justify-center gap-1 rounded-sm px-2 py-1 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground transition-colors disabled:cursor-not-allowed disabled:opacity-40",
91806
+ disabled: !canEdit,
91807
+ onClick: onAddSlide,
91808
+ children: [
91809
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuPlus, { className: "h-3 w-3" }),
91810
+ t2("pptx.sections.addSlide")
91811
+ ]
91812
+ }
91813
+ ) }),
91706
91814
  sectionContextMenu && /* @__PURE__ */ jsxRuntime.jsx(
91707
91815
  SectionContextMenu,
91708
91816
  {
@@ -94137,23 +94245,18 @@ var SlideNotesPanel = ({
94137
94245
  });
94138
94246
  const hasNotes = draft.trim().length > 0;
94139
94247
  const slideLabel = activeSlide ? t2("pptx.notes.slideN", { n: activeSlide.slideNumber }) : t2("pptx.notes.noSlide");
94140
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col border-t border-border/60 bg-background/80 select-none", children: [
94248
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col border-t border-border/60 bg-background select-none", children: [
94141
94249
  /* @__PURE__ */ jsxRuntime.jsxs(
94142
94250
  "button",
94143
94251
  {
94144
94252
  type: "button",
94145
94253
  onClick: onToggle,
94146
- className: "flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/60 transition-colors w-full text-left shrink-0",
94254
+ className: "flex items-center gap-1.5 px-3 py-1 text-[11px] text-muted-foreground hover:text-foreground hover:bg-accent/30 transition-colors w-full text-left shrink-0",
94147
94255
  "aria-expanded": isExpanded,
94148
94256
  "aria-controls": "slide-notes-content",
94149
94257
  children: [
94150
- /* @__PURE__ */ jsxRuntime.jsx(lu.LuStickyNote, { className: "w-3.5 h-3.5 shrink-0" }),
94151
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium tracking-wide uppercase", children: t2("pptx.notes.title") }),
94152
- !isExpanded && hasNotes && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-1 truncate max-w-[240px] text-muted-foreground font-normal normal-case", children: [
94153
- "- ",
94154
- draft.trim().split("\n")[0]
94155
- ] }),
94156
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto shrink-0", children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronDown, { className: "w-3.5 h-3.5" }) : /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronUp, { className: "w-3.5 h-3.5" }) })
94258
+ "Notes",
94259
+ !isExpanded && hasNotes && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground/50 text-[10px]", children: "(has notes)" })
94157
94260
  ]
94158
94261
  }
94159
94262
  ),
@@ -96421,15 +96524,14 @@ function SelectionPane({
96421
96524
  }) })
96422
96525
  ] });
96423
96526
  }
96424
- var _b = "inline-flex items-center justify-center px-2.5 py-1.5 max-md:min-h-[44px] max-md:min-w-[44px]";
96527
+ var _b = "inline-flex items-center justify-center px-2.5 py-1.5 max-md:min-h-[44px] max-md:min-w-[44px] active:scale-95 active:opacity-80";
96425
96528
  var gB = `${_b} border-r border-border hover:bg-accent disabled:opacity-40 disabled:cursor-not-allowed`;
96426
96529
  var gL = `${_b} hover:bg-accent disabled:opacity-40 disabled:cursor-not-allowed`;
96427
96530
  var grp = "inline-flex items-center rounded bg-muted text-xs overflow-hidden";
96428
- var pill = "inline-flex items-center gap-1.5 px-2.5 py-1.5 max-md:min-h-[44px] rounded bg-muted hover:bg-accent text-xs transition-colors";
96429
- var sep = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-5 bg-border/60 mx-0.5 max-md:hidden" });
96531
+ var pill = "inline-flex items-center gap-1.5 px-2.5 py-1.5 max-md:min-h-[44px] rounded bg-muted hover:bg-accent text-xs transition-colors active:scale-95 active:opacity-80";
96532
+ var sep = /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px self-stretch bg-border/40 mx-1 max-md:hidden" });
96430
96533
  var ic2 = "w-4 h-4";
96431
96534
  var ics = "w-3.5 h-3.5";
96432
- var MODES = ["edit", "preview", "present"];
96433
96535
  var ALIGN_BTNS = [
96434
96536
  { k: "left", el: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAlignLeft, { className: ic2 }) },
96435
96537
  { k: "center", el: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAlignCenter, { className: ic2 }) },
@@ -96548,7 +96650,128 @@ var ATXT = [
96548
96650
  { i: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAlignRight, { className: ic2 }), t: "Align right" },
96549
96651
  { i: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAlignJustify, { className: ic2 }), t: "Justify" }
96550
96652
  ];
96653
+ var ANIMATION_PRESETS = [
96654
+ {
96655
+ group: "Entrance",
96656
+ items: [
96657
+ { value: "appear", label: "Appear" },
96658
+ { value: "fadeIn", label: "Fade In" },
96659
+ { value: "flyIn", label: "Fly In" }
96660
+ ]
96661
+ },
96662
+ {
96663
+ group: "Emphasis",
96664
+ items: [
96665
+ { value: "pulse", label: "Pulse" },
96666
+ { value: "spin", label: "Spin" }
96667
+ ]
96668
+ },
96669
+ {
96670
+ group: "Exit",
96671
+ items: [
96672
+ { value: "disappear", label: "Disappear" },
96673
+ { value: "fadeOut", label: "Fade Out" }
96674
+ ]
96675
+ }
96676
+ ];
96677
+ function AnimationsSection(p3) {
96678
+ const { t: t2 } = reactI18next.useTranslation();
96679
+ const [previewActive, setPreviewActive] = React10.useState(false);
96680
+ const hasElement = p3.selectedElement !== null;
96681
+ const disabled = !p3.canEdit || !hasElement;
96682
+ const handlePreview = () => {
96683
+ if (disabled) {
96684
+ return;
96685
+ }
96686
+ setPreviewActive(true);
96687
+ setTimeout(() => setPreviewActive(false), 1200);
96688
+ };
96689
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
96690
+ /* @__PURE__ */ jsxRuntime.jsxs(
96691
+ "button",
96692
+ {
96693
+ type: "button",
96694
+ onClick: handlePreview,
96695
+ disabled,
96696
+ className: cn(
96697
+ pill,
96698
+ previewActive ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96699
+ ),
96700
+ title: t2("pptx.animations.previewTooltip"),
96701
+ children: [
96702
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuPlay, { className: ic2 }),
96703
+ t2("pptx.animations.preview")
96704
+ ]
96705
+ }
96706
+ ),
96707
+ sep,
96708
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
96709
+ /* @__PURE__ */ jsxRuntime.jsxs(
96710
+ "button",
96711
+ {
96712
+ type: "button",
96713
+ disabled,
96714
+ className: pill,
96715
+ title: t2("pptx.animations.addTooltip"),
96716
+ children: [
96717
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuSparkles, { className: ic2 }),
96718
+ t2("pptx.animations.addAnimation"),
96719
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronDown, { className: "w-3 h-3" })
96720
+ ]
96721
+ }
96722
+ ),
96723
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:flex flex-col w-44 pt-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl py-1", children: ANIMATION_PRESETS.map((group) => /* @__PURE__ */ jsxRuntime.jsxs(React10__namespace.default.Fragment, { children: [
96724
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pt-1.5 pb-0.5 text-[10px] font-semibold text-muted-foreground uppercase tracking-wider", children: t2(`pptx.animations.group.${group.group.toLowerCase()}`) }),
96725
+ group.items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
96726
+ "button",
96727
+ {
96728
+ type: "button",
96729
+ disabled,
96730
+ className: "flex items-center gap-2 w-full px-3 py-1.5 text-xs text-foreground hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
96731
+ title: t2("pptx.animations.applyAnimation", {
96732
+ name: t2(`pptx.animations.preset.${item.value}`)
96733
+ }),
96734
+ children: t2(`pptx.animations.preset.${item.value}`)
96735
+ },
96736
+ item.value
96737
+ ))
96738
+ ] }, group.group)) }) })
96739
+ ] }),
96740
+ sep,
96741
+ /* @__PURE__ */ jsxRuntime.jsxs(
96742
+ "button",
96743
+ {
96744
+ type: "button",
96745
+ disabled,
96746
+ className: pill,
96747
+ title: t2("pptx.animations.removeTooltip"),
96748
+ children: [
96749
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuTrash2, { className: ic2 }),
96750
+ t2("pptx.animations.remove")
96751
+ ]
96752
+ }
96753
+ ),
96754
+ sep,
96755
+ /* @__PURE__ */ jsxRuntime.jsxs(
96756
+ "button",
96757
+ {
96758
+ type: "button",
96759
+ onClick: p3.onToggleInspector,
96760
+ className: cn(
96761
+ pill,
96762
+ p3.isInspectorPaneOpen ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96763
+ ),
96764
+ title: t2("pptx.animations.openPanelTooltip"),
96765
+ children: [
96766
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuPanelRight, { className: ic2 }),
96767
+ t2("pptx.animations.animationPanel")
96768
+ ]
96769
+ }
96770
+ )
96771
+ ] });
96772
+ }
96551
96773
  function ArrangeSection(p3) {
96774
+ const { t: t2 } = reactI18next.useTranslation();
96552
96775
  const hasSel = Boolean(p3.selectedElement);
96553
96776
  const canMut = hasSel && p3.canEdit;
96554
96777
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -96559,21 +96782,21 @@ function ArrangeSection(p3) {
96559
96782
  onClick: () => p3.onAlignElements(a2.k),
96560
96783
  disabled: !canMut,
96561
96784
  className: i3 < arr.length - 1 ? gB : gL,
96562
- title: `Align ${a2.k}`,
96785
+ title: t2("pptx.arrange.align", { direction: a2.k }),
96563
96786
  children: a2.el
96564
96787
  },
96565
96788
  a2.k
96566
96789
  )) }),
96567
96790
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
96568
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: p3.onCopy, disabled: !hasSel, className: gB, title: "Copy", children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuCopy, { className: ic2 }) }),
96569
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: p3.onCut, disabled: !canMut, className: gB, title: "Cut", children: "Cut" }),
96791
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: p3.onCopy, disabled: !hasSel, className: gB, title: t2("pptx.arrange.copy"), children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuCopy, { className: ic2 }) }),
96792
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: p3.onCut, disabled: !canMut, className: gB, title: t2("pptx.arrange.cut"), children: t2("pptx.arrange.cut") }),
96570
96793
  /* @__PURE__ */ jsxRuntime.jsx(
96571
96794
  "button",
96572
96795
  {
96573
96796
  onClick: p3.onPaste,
96574
96797
  disabled: !p3.clipboardPayload || !p3.canEdit,
96575
96798
  className: gL,
96576
- title: "Paste",
96799
+ title: t2("pptx.arrange.paste"),
96577
96800
  children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuClipboardPaste, { className: ic2 })
96578
96801
  }
96579
96802
  )
@@ -96588,10 +96811,10 @@ function ArrangeSection(p3) {
96588
96811
  pill,
96589
96812
  p3.formatPainterActive ? "bg-amber-600 hover:bg-amber-500 text-amber-50" : ""
96590
96813
  ),
96591
- title: "Format Painter",
96814
+ title: t2("pptx.arrange.formatPainter"),
96592
96815
  children: [
96593
96816
  /* @__PURE__ */ jsxRuntime.jsx(lu.LuPaintbrush, { className: ic2 }),
96594
- "Format"
96817
+ t2("pptx.arrange.format")
96595
96818
  ]
96596
96819
  }
96597
96820
  ),
@@ -96603,8 +96826,8 @@ function ArrangeSection(p3) {
96603
96826
  onClick: () => p3.onFlip("horizontal"),
96604
96827
  disabled: !canMut,
96605
96828
  className: gB,
96606
- title: "Flip horizontally",
96607
- children: "Flip H"
96829
+ title: t2("pptx.arrange.flipHorizontally"),
96830
+ children: t2("pptx.arrange.flipH")
96608
96831
  }
96609
96832
  ),
96610
96833
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -96614,8 +96837,8 @@ function ArrangeSection(p3) {
96614
96837
  onClick: () => p3.onFlip("vertical"),
96615
96838
  disabled: !canMut,
96616
96839
  className: gL,
96617
- title: "Flip vertically",
96618
- children: "Flip V"
96840
+ title: t2("pptx.arrange.flipVertically"),
96841
+ children: t2("pptx.arrange.flipV")
96619
96842
  }
96620
96843
  )
96621
96844
  ] }),
@@ -96626,7 +96849,7 @@ function ArrangeSection(p3) {
96626
96849
  onClick: () => p3.onMoveLayer("backward"),
96627
96850
  disabled: !canMut,
96628
96851
  className: gB,
96629
- title: "Send backward",
96852
+ title: t2("pptx.arrange.sendBackward"),
96630
96853
  children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronDown, { className: ic2 })
96631
96854
  }
96632
96855
  ),
@@ -96636,7 +96859,7 @@ function ArrangeSection(p3) {
96636
96859
  onClick: () => p3.onMoveLayer("forward"),
96637
96860
  disabled: !canMut,
96638
96861
  className: gB,
96639
- title: "Bring forward",
96862
+ title: t2("pptx.arrange.bringForward"),
96640
96863
  children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronUp, { className: ic2 })
96641
96864
  }
96642
96865
  ),
@@ -96646,8 +96869,8 @@ function ArrangeSection(p3) {
96646
96869
  onClick: () => p3.onMoveLayerToEdge("back"),
96647
96870
  disabled: !canMut,
96648
96871
  className: gB,
96649
- title: "Send to back",
96650
- children: "Back"
96872
+ title: t2("pptx.arrange.sendToBack"),
96873
+ children: t2("pptx.arrange.back")
96651
96874
  }
96652
96875
  ),
96653
96876
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -96656,25 +96879,34 @@ function ArrangeSection(p3) {
96656
96879
  onClick: () => p3.onMoveLayerToEdge("front"),
96657
96880
  disabled: !canMut,
96658
96881
  className: gL,
96659
- title: "Bring to front",
96660
- children: "Front"
96882
+ title: t2("pptx.arrange.bringToFront"),
96883
+ children: t2("pptx.arrange.front")
96661
96884
  }
96662
96885
  )
96663
96886
  ] }),
96664
- /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onDuplicate, disabled: !canMut, className: pill, title: "Duplicate", children: [
96665
- /* @__PURE__ */ jsxRuntime.jsx(lu.LuCopy, { className: ic2 }),
96666
- "Duplicate"
96667
- ] }),
96887
+ /* @__PURE__ */ jsxRuntime.jsxs(
96888
+ "button",
96889
+ {
96890
+ onClick: p3.onDuplicate,
96891
+ disabled: !canMut,
96892
+ className: pill,
96893
+ title: t2("pptx.arrange.duplicate"),
96894
+ children: [
96895
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCopy, { className: ic2 }),
96896
+ t2("pptx.arrange.duplicate")
96897
+ ]
96898
+ }
96899
+ ),
96668
96900
  /* @__PURE__ */ jsxRuntime.jsxs(
96669
96901
  "button",
96670
96902
  {
96671
96903
  onClick: p3.onDelete,
96672
96904
  disabled: !canMut,
96673
96905
  className: "inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded bg-red-700/80 hover:bg-red-600 disabled:opacity-40 disabled:cursor-not-allowed text-xs transition-colors",
96674
- title: "Delete",
96906
+ title: t2("pptx.arrange.delete"),
96675
96907
  children: [
96676
96908
  /* @__PURE__ */ jsxRuntime.jsx(lu.LuTrash2, { className: ic2 }),
96677
- "Delete"
96909
+ t2("pptx.arrange.delete")
96678
96910
  ]
96679
96911
  }
96680
96912
  )
@@ -96713,12 +96945,91 @@ function DesignSection(p3) {
96713
96945
  "Edit Theme"
96714
96946
  ]
96715
96947
  }
96948
+ ),
96949
+ sep,
96950
+ p3.onOpenDocumentProperties && /* @__PURE__ */ jsxRuntime.jsxs(
96951
+ "button",
96952
+ {
96953
+ onClick: p3.onOpenDocumentProperties,
96954
+ className: pill,
96955
+ title: "Change slide dimensions (16:9, 4:3, custom)",
96956
+ children: [
96957
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuMonitor, { className: ics }),
96958
+ "Slide Size"
96959
+ ]
96960
+ }
96961
+ ),
96962
+ p3.onToggleInspector && /* @__PURE__ */ jsxRuntime.jsxs(
96963
+ "button",
96964
+ {
96965
+ onClick: p3.onToggleInspector,
96966
+ className: cn(
96967
+ pill,
96968
+ p3.isInspectorPaneOpen ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96969
+ ),
96970
+ title: "Open inspector to edit slide background",
96971
+ children: [
96972
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuPaintBucket, { className: ics }),
96973
+ "Format Background"
96974
+ ]
96975
+ }
96716
96976
  )
96717
96977
  ] });
96718
96978
  }
96979
+ var TRANSITION_PRESETS = [
96980
+ { value: "none", label: "None" },
96981
+ { value: "fade", label: "Fade" },
96982
+ { value: "push", label: "Push" },
96983
+ { value: "wipe", label: "Wipe" },
96984
+ { value: "split", label: "Split" },
96985
+ { value: "reveal", label: "Reveal" },
96986
+ { value: "cut", label: "Cut" },
96987
+ { value: "cover", label: "Cover" },
96988
+ { value: "uncover", label: "Uncover" }
96989
+ ];
96719
96990
  function TransitionsSection(p3) {
96991
+ const [selected, setSelected] = React10__namespace.default.useState("none");
96992
+ const [duration, setDuration] = React10__namespace.default.useState("00.50");
96720
96993
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
96721
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground px-2", children: "Configure transitions in the Inspector panel (Slide tab)." }),
96994
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: pill, title: "Preview transition", children: [
96995
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuPlay, { className: ics }),
96996
+ "Preview"
96997
+ ] }),
96998
+ sep,
96999
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex items-center gap-0.5 overflow-x-auto max-w-[420px]", children: TRANSITION_PRESETS.map((t2) => /* @__PURE__ */ jsxRuntime.jsx(
97000
+ "button",
97001
+ {
97002
+ type: "button",
97003
+ onClick: () => setSelected(t2.value),
97004
+ className: cn(
97005
+ "flex-shrink-0 px-2 py-1 max-md:min-h-[44px] rounded border text-[11px] leading-tight transition-colors",
97006
+ selected === t2.value ? "border-primary bg-primary/10 text-primary font-medium" : "border-border bg-muted hover:bg-accent text-foreground"
97007
+ ),
97008
+ title: `${t2.label} transition`,
97009
+ children: t2.label
97010
+ },
97011
+ t2.value
97012
+ )) }),
97013
+ sep,
97014
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "inline-flex items-center gap-1.5 text-xs text-muted-foreground", children: [
97015
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-nowrap", children: "Duration:" }),
97016
+ /* @__PURE__ */ jsxRuntime.jsx(
97017
+ "input",
97018
+ {
97019
+ type: "text",
97020
+ value: duration,
97021
+ onChange: (e2) => setDuration(e2.target.value),
97022
+ className: "w-14 px-1.5 py-1 rounded border border-border bg-muted text-xs text-foreground text-center",
97023
+ title: "Transition duration in seconds"
97024
+ }
97025
+ )
97026
+ ] }),
97027
+ sep,
97028
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: pill, title: "Apply transition to all slides", children: [
97029
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCopy, { className: ics }),
97030
+ "Apply to All"
97031
+ ] }),
97032
+ sep,
96722
97033
  /* @__PURE__ */ jsxRuntime.jsxs(
96723
97034
  "button",
96724
97035
  {
@@ -96728,7 +97039,7 @@ function TransitionsSection(p3) {
96728
97039
  pill,
96729
97040
  p3.isInspectorPaneOpen ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96730
97041
  ),
96731
- title: "Open Inspector to edit transitions",
97042
+ title: "Open Inspector for full transition options",
96732
97043
  children: [
96733
97044
  /* @__PURE__ */ jsxRuntime.jsx(lu.LuPanelRight, { className: ic2 }),
96734
97045
  "Inspector"
@@ -96839,6 +97150,321 @@ function DrawSection(p3) {
96839
97150
  ] })
96840
97151
  ] });
96841
97152
  }
97153
+ function FileSection(p3) {
97154
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
97155
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onSaveAsPpsx, className: pill, title: "Save as Slide Show (.ppsx)", children: [
97156
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuPlay, { className: ic2 }),
97157
+ "Save .ppsx"
97158
+ ] }),
97159
+ p3.hasMacros && /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onSaveAsPptm, className: pill, title: "Save as Macro-Enabled (.pptm)", children: [
97160
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuFileText, { className: ic2 }),
97161
+ "Save .pptm"
97162
+ ] }),
97163
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onPackageForSharing, className: pill, title: "Package for Sharing", children: [
97164
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuFolderOpen, { className: ic2 }),
97165
+ "Package"
97166
+ ] }),
97167
+ sep,
97168
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onExportPng, className: pill, title: "Export as PNG", children: [
97169
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuDownload, { className: ic2 }),
97170
+ "PNG"
97171
+ ] }),
97172
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onExportPdf, className: pill, title: "Export as PDF", children: [
97173
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuFileText, { className: ic2 }),
97174
+ "PDF"
97175
+ ] }),
97176
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onExportVideo, className: pill, title: "Export as Video", children: [
97177
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuVideo, { className: ic2 }),
97178
+ "Video"
97179
+ ] }),
97180
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onExportGif, className: pill, title: "Export as GIF", children: [
97181
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuImage, { className: ic2 }),
97182
+ "GIF"
97183
+ ] }),
97184
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onCopySlideAsImage, className: pill, title: "Copy Slide as Image", children: [
97185
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCopy, { className: ic2 }),
97186
+ "Copy Image"
97187
+ ] }),
97188
+ sep,
97189
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onPrint, className: pill, title: "Print", children: [
97190
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuPrinter, { className: ic2 }),
97191
+ "Print"
97192
+ ] }),
97193
+ sep,
97194
+ p3.onOpenDocumentProperties && /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onOpenDocumentProperties, className: pill, title: "Document Properties", children: [
97195
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuInfo, { className: ic2 }),
97196
+ "Properties"
97197
+ ] }),
97198
+ p3.onOpenPasswordProtection && /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onOpenPasswordProtection, className: pill, title: "Protect Presentation", children: [
97199
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuLock, { className: ic2 }),
97200
+ "Protect"
97201
+ ] }),
97202
+ p3.onOpenFontEmbedding && /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onOpenFontEmbedding, className: pill, title: "Embed Fonts", children: [
97203
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuType, { className: ic2 }),
97204
+ "Fonts"
97205
+ ] }),
97206
+ p3.onOpenDigitalSignatures && /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onOpenDigitalSignatures, className: pill, title: "Digital Signatures", children: [
97207
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuShieldAlert, { className: ic2 }),
97208
+ "Signatures"
97209
+ ] })
97210
+ ] });
97211
+ }
97212
+ function extractFontInfo(element2) {
97213
+ const defaults = { fontFamily: "Segoe UI", fontSize: "24" };
97214
+ if (!element2) {
97215
+ return defaults;
97216
+ }
97217
+ if (!pptxViewerCore.hasTextProperties(element2)) {
97218
+ return defaults;
97219
+ }
97220
+ const segStyle = element2.textSegments?.[0]?.style;
97221
+ const textStyle = element2.textStyle;
97222
+ const fontFamily = segStyle?.fontFamily ?? textStyle?.fontFamily ?? defaults.fontFamily;
97223
+ const fontSize = segStyle?.fontSize ?? textStyle?.fontSize;
97224
+ return {
97225
+ fontFamily,
97226
+ fontSize: fontSize !== void 0 && fontSize !== null ? String(fontSize) : defaults.fontSize
97227
+ };
97228
+ }
97229
+ var COMMON_FONTS = [
97230
+ "Arial",
97231
+ "Calibri",
97232
+ "Cambria",
97233
+ "Comic Sans MS",
97234
+ "Courier New",
97235
+ "Georgia",
97236
+ "Helvetica",
97237
+ "Impact",
97238
+ "Segoe UI",
97239
+ "Tahoma",
97240
+ "Times New Roman",
97241
+ "Trebuchet MS",
97242
+ "Verdana"
97243
+ ];
97244
+ var COMMON_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 40, 44, 48, 54, 60, 72, 96];
97245
+ function HomeSection(p3) {
97246
+ const [layoutMenuOpen, setLayoutMenuOpen] = React10.useState(false);
97247
+ const [fontMenuOpen, setFontMenuOpen] = React10.useState(false);
97248
+ const [sizeMenuOpen, setSizeMenuOpen] = React10.useState(false);
97249
+ const [copiedFeedback, setCopiedFeedback] = React10.useState(false);
97250
+ const [cutFeedback, setCutFeedback] = React10.useState(false);
97251
+ const layoutMenuRef = React10.useRef(null);
97252
+ const fontMenuRef = React10.useRef(null);
97253
+ const sizeMenuRef = React10.useRef(null);
97254
+ const { fontFamily, fontSize } = extractFontInfo(p3.selectedElement);
97255
+ const handleNewSlide = React10.useCallback(() => {
97256
+ if (p3.layoutOptions.length > 0) {
97257
+ p3.onInsertSlideFromLayout(p3.layoutOptions[0].path);
97258
+ }
97259
+ }, [p3]);
97260
+ React10.useEffect(() => {
97261
+ if (!layoutMenuOpen) {
97262
+ return;
97263
+ }
97264
+ const handler = (e2) => {
97265
+ if (layoutMenuRef.current && !layoutMenuRef.current.contains(e2.target)) {
97266
+ setLayoutMenuOpen(false);
97267
+ }
97268
+ };
97269
+ document.addEventListener("mousedown", handler);
97270
+ return () => document.removeEventListener("mousedown", handler);
97271
+ }, [layoutMenuOpen]);
97272
+ React10.useEffect(() => {
97273
+ if (!fontMenuOpen) {
97274
+ return;
97275
+ }
97276
+ const handler = (e2) => {
97277
+ if (fontMenuRef.current && !fontMenuRef.current.contains(e2.target)) {
97278
+ setFontMenuOpen(false);
97279
+ }
97280
+ };
97281
+ document.addEventListener("mousedown", handler);
97282
+ return () => document.removeEventListener("mousedown", handler);
97283
+ }, [fontMenuOpen]);
97284
+ React10.useEffect(() => {
97285
+ if (!sizeMenuOpen) {
97286
+ return;
97287
+ }
97288
+ const handler = (e2) => {
97289
+ if (sizeMenuRef.current && !sizeMenuRef.current.contains(e2.target)) {
97290
+ setSizeMenuOpen(false);
97291
+ }
97292
+ };
97293
+ document.addEventListener("mousedown", handler);
97294
+ return () => document.removeEventListener("mousedown", handler);
97295
+ }, [sizeMenuOpen]);
97296
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
97297
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97298
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
97299
+ /* @__PURE__ */ jsxRuntime.jsx(
97300
+ "button",
97301
+ {
97302
+ type: "button",
97303
+ onClick: p3.onPaste,
97304
+ disabled: !p3.clipboardPayload || !p3.canEdit,
97305
+ className: gB,
97306
+ title: "Paste",
97307
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuClipboardPaste, { className: ic2 })
97308
+ }
97309
+ ),
97310
+ /* @__PURE__ */ jsxRuntime.jsx(
97311
+ "button",
97312
+ {
97313
+ type: "button",
97314
+ onClick: () => {
97315
+ p3.onCut();
97316
+ setCutFeedback(true);
97317
+ setTimeout(() => setCutFeedback(false), 600);
97318
+ },
97319
+ disabled: !p3.canEdit,
97320
+ className: cn(gB, cutFeedback && "bg-green-600/20 text-green-400"),
97321
+ title: "Cut",
97322
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuScissors, { className: ic2 })
97323
+ }
97324
+ ),
97325
+ /* @__PURE__ */ jsxRuntime.jsx(
97326
+ "button",
97327
+ {
97328
+ type: "button",
97329
+ onClick: () => {
97330
+ p3.onCopy();
97331
+ setCopiedFeedback(true);
97332
+ setTimeout(() => setCopiedFeedback(false), 600);
97333
+ },
97334
+ className: cn(gB, copiedFeedback && "bg-green-600/20 text-green-400"),
97335
+ title: "Copy",
97336
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuCopy, { className: ic2 })
97337
+ }
97338
+ ),
97339
+ p3.onToggleFormatPainter && /* @__PURE__ */ jsxRuntime.jsx(
97340
+ "button",
97341
+ {
97342
+ type: "button",
97343
+ onClick: p3.onToggleFormatPainter,
97344
+ disabled: !p3.canEdit,
97345
+ className: cn(
97346
+ gL,
97347
+ p3.formatPainterActive ? "bg-amber-600 hover:bg-amber-500 text-amber-50" : ""
97348
+ ),
97349
+ title: "Format Painter",
97350
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuPaintbrush, { className: ic2 })
97351
+ }
97352
+ )
97353
+ ] }),
97354
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Clipboard" })
97355
+ ] }),
97356
+ sep,
97357
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97358
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative inline-flex items-center", ref: layoutMenuRef, children: [
97359
+ /* @__PURE__ */ jsxRuntime.jsxs(
97360
+ "button",
97361
+ {
97362
+ type: "button",
97363
+ onClick: handleNewSlide,
97364
+ disabled: !p3.canEdit || p3.layoutOptions.length === 0,
97365
+ className: cn(
97366
+ pill,
97367
+ "whitespace-nowrap",
97368
+ p3.layoutOptions.length > 0 ? "rounded-r-none" : ""
97369
+ ),
97370
+ title: "New Slide",
97371
+ children: [
97372
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuPlus, { className: ic2 }),
97373
+ "New Slide"
97374
+ ]
97375
+ }
97376
+ ),
97377
+ p3.layoutOptions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
97378
+ "button",
97379
+ {
97380
+ type: "button",
97381
+ disabled: !p3.canEdit,
97382
+ className: "inline-flex items-center justify-center self-stretch px-1 rounded-r bg-muted hover:bg-accent text-xs transition-colors border-l border-border/40 active:scale-95 active:opacity-80",
97383
+ title: "Choose layout",
97384
+ onClick: () => setLayoutMenuOpen((v) => !v),
97385
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronDown, { className: "w-3 h-3" })
97386
+ }
97387
+ ),
97388
+ layoutMenuOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 top-full z-50 flex flex-col w-48 pt-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl py-1 max-h-60 overflow-y-auto", children: p3.layoutOptions.map((lo) => /* @__PURE__ */ jsxRuntime.jsx(
97389
+ "button",
97390
+ {
97391
+ type: "button",
97392
+ className: "flex items-center gap-2 w-full px-3 py-1.5 text-xs text-foreground hover:bg-muted transition-colors",
97393
+ onClick: () => {
97394
+ p3.onInsertSlideFromLayout(lo.path);
97395
+ setLayoutMenuOpen(false);
97396
+ },
97397
+ children: lo.name
97398
+ },
97399
+ lo.path
97400
+ )) }) })
97401
+ ] }),
97402
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Slides" })
97403
+ ] }),
97404
+ sep,
97405
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97406
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
97407
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", ref: fontMenuRef, children: [
97408
+ /* @__PURE__ */ jsxRuntime.jsxs(
97409
+ "button",
97410
+ {
97411
+ type: "button",
97412
+ onClick: () => setFontMenuOpen((v) => !v),
97413
+ className: "inline-flex items-center justify-between px-2 py-1 rounded-sm border border-border/60 bg-background/60 text-[11px] text-foreground min-w-[120px] truncate hover:bg-accent/40 transition-colors cursor-pointer",
97414
+ children: [
97415
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: fontFamily }),
97416
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronDown, { className: "w-3 h-3 ml-1 shrink-0 text-muted-foreground" })
97417
+ ]
97418
+ }
97419
+ ),
97420
+ fontMenuOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 top-full z-50 flex flex-col w-48 pt-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl py-1 max-h-60 overflow-y-auto", children: COMMON_FONTS.map((f) => /* @__PURE__ */ jsxRuntime.jsx(
97421
+ "button",
97422
+ {
97423
+ type: "button",
97424
+ className: "flex items-center gap-2 w-full px-3 py-1.5 text-xs text-foreground hover:bg-muted transition-colors",
97425
+ style: { fontFamily: f },
97426
+ onClick: () => {
97427
+ p3.onUpdateTextStyle?.({ fontFamily: f });
97428
+ setFontMenuOpen(false);
97429
+ },
97430
+ children: f
97431
+ },
97432
+ f
97433
+ )) }) })
97434
+ ] }),
97435
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", ref: sizeMenuRef, children: [
97436
+ /* @__PURE__ */ jsxRuntime.jsxs(
97437
+ "button",
97438
+ {
97439
+ type: "button",
97440
+ onClick: () => setSizeMenuOpen((v) => !v),
97441
+ className: "inline-flex items-center justify-between px-2 py-1 rounded-sm border border-border/60 bg-background/60 text-[11px] text-foreground min-w-[50px] text-center hover:bg-accent/40 transition-colors cursor-pointer",
97442
+ children: [
97443
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: fontSize }),
97444
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuChevronDown, { className: "w-3 h-3 ml-1 shrink-0 text-muted-foreground" })
97445
+ ]
97446
+ }
97447
+ ),
97448
+ sizeMenuOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 top-full z-50 flex flex-col w-48 pt-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl py-1 max-h-60 overflow-y-auto", children: COMMON_SIZES.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
97449
+ "button",
97450
+ {
97451
+ type: "button",
97452
+ className: "flex items-center gap-2 w-full px-3 py-1.5 text-xs text-foreground hover:bg-muted transition-colors",
97453
+ onClick: () => {
97454
+ p3.onUpdateTextStyle?.({ fontSize: s });
97455
+ setSizeMenuOpen(false);
97456
+ },
97457
+ children: s
97458
+ },
97459
+ s
97460
+ )) }) })
97461
+ ] })
97462
+ ] }),
97463
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Font" })
97464
+ ] }),
97465
+ sep
97466
+ ] });
97467
+ }
96842
97468
  function InsertSection(p3) {
96843
97469
  const { t: t2 } = reactI18next.useTranslation();
96844
97470
  const { canEdit } = p3;
@@ -97197,6 +97823,60 @@ function InsertSection(p3) {
97197
97823
  )
97198
97824
  ] });
97199
97825
  }
97826
+ function SlideShowSection(p3) {
97827
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
97828
+ /* @__PURE__ */ jsxRuntime.jsxs(
97829
+ "button",
97830
+ {
97831
+ onClick: () => p3.onSetMode("present"),
97832
+ className: pill,
97833
+ title: "Start slide show from beginning",
97834
+ children: [
97835
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuPlay, { className: ic2 }),
97836
+ "From Beginning"
97837
+ ]
97838
+ }
97839
+ ),
97840
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onPresent, className: pill, title: "Start slide show from current slide", children: [
97841
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuPlay, { className: ic2 }),
97842
+ "From Current Slide"
97843
+ ] }),
97844
+ sep,
97845
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onEnterPresenterView, className: pill, title: "Presenter view", children: [
97846
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuMonitor, { className: ic2 }),
97847
+ "Presenter View"
97848
+ ] }),
97849
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onEnterRehearsalMode, className: pill, title: "Rehearse timings", children: [
97850
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuClock, { className: ic2 }),
97851
+ "Rehearse Timings"
97852
+ ] }),
97853
+ sep,
97854
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onOpenSetUpSlideShow, className: pill, title: "Set up slide show", children: [
97855
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuSettings, { className: ic2 }),
97856
+ "Set Up Slide Show"
97857
+ ] }),
97858
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: p3.onOpenBroadcastDialog, className: pill, title: "Broadcast slide show", children: [
97859
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCast, { className: ic2 }),
97860
+ "Broadcast"
97861
+ ] }),
97862
+ sep,
97863
+ /* @__PURE__ */ jsxRuntime.jsxs(
97864
+ "button",
97865
+ {
97866
+ onClick: p3.onToggleSubtitles,
97867
+ className: cn(
97868
+ pill,
97869
+ p3.showSubtitles ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
97870
+ ),
97871
+ title: "Toggle subtitles",
97872
+ children: [
97873
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCaptions, { className: ic2 }),
97874
+ "Subtitles"
97875
+ ]
97876
+ }
97877
+ )
97878
+ ] });
97879
+ }
97200
97880
  var FONT_COLOR_PRESETS = [
97201
97881
  "#000000",
97202
97882
  "#ffffff",
@@ -97209,6 +97889,18 @@ var FONT_COLOR_PRESETS = [
97209
97889
  "#ff69b4",
97210
97890
  "#808080"
97211
97891
  ];
97892
+ var HIGHLIGHT_COLOR_PRESETS = [
97893
+ "#ffff00",
97894
+ "#00ff00",
97895
+ "#00ffff",
97896
+ "#ff00ff",
97897
+ "#0000ff",
97898
+ "#ff0000",
97899
+ "#000080",
97900
+ "#008080",
97901
+ "#008000",
97902
+ "#800080"
97903
+ ];
97212
97904
  function TextSection(p3) {
97213
97905
  const hasSel = Boolean(p3.selectedElement);
97214
97906
  const canMut = hasSel && p3.canEdit;
@@ -97216,7 +97908,9 @@ function TextSection(p3) {
97216
97908
  const isTable = hasSel && p3.selectedElement?.type === "table";
97217
97909
  const canFormat = isTextEl || isTable;
97218
97910
  const currentColor = isTextEl && p3.selectedElement && pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textSegments?.[0]?.style?.color ?? p3.selectedElement.textStyle?.color ?? "#000000" : "#000000";
97911
+ const currentHighlight = isTextEl && p3.selectedElement && pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textSegments?.[0]?.style?.highlightColor ?? p3.selectedElement.textStyle?.highlightColor ?? "#ffff00" : "#ffff00";
97219
97912
  const colorInputRef = React10.useRef(null);
97913
+ const highlightInputRef = React10.useRef(null);
97220
97914
  const handleColorChange = React10.useCallback(
97221
97915
  (color) => {
97222
97916
  if (!canFormat) {
@@ -97226,138 +97920,875 @@ function TextSection(p3) {
97226
97920
  },
97227
97921
  [canFormat, p3]
97228
97922
  );
97923
+ const handleHighlightChange = React10.useCallback(
97924
+ (highlightColor) => {
97925
+ if (!canFormat) {
97926
+ return;
97927
+ }
97928
+ p3.onUpdateTextStyle({ highlightColor });
97929
+ },
97930
+ [canFormat, p3]
97931
+ );
97229
97932
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
97230
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: grp, children: FMT.map((b2, i3, a2) => {
97231
- const handleClick = () => {
97232
- if (!canFormat || !p3.selectedElement) {
97933
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97934
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
97935
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: grp, children: FMT.map((b2, i3, a2) => {
97936
+ const handleClick = () => {
97937
+ if (!canFormat || !p3.selectedElement) {
97938
+ return;
97939
+ }
97940
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97941
+ switch (b2.t) {
97942
+ case "Bold":
97943
+ p3.onUpdateTextStyle({ bold: !ts?.bold });
97944
+ break;
97945
+ case "Italic":
97946
+ p3.onUpdateTextStyle({ italic: !ts?.italic });
97947
+ break;
97948
+ case "Underline":
97949
+ p3.onUpdateTextStyle({
97950
+ underline: !ts?.underline
97951
+ });
97952
+ break;
97953
+ case "Strikethrough":
97954
+ p3.onUpdateTextStyle({
97955
+ strikethrough: !ts?.strikethrough
97956
+ });
97957
+ break;
97958
+ }
97959
+ };
97960
+ return /* @__PURE__ */ jsxRuntime.jsx(
97961
+ "button",
97962
+ {
97963
+ type: "button",
97964
+ disabled: !canMut,
97965
+ onMouseDown: (e2) => e2.preventDefault(),
97966
+ onClick: handleClick,
97967
+ className: i3 < a2.length - 1 ? gB : gL,
97968
+ title: b2.t,
97969
+ children: b2.i
97970
+ },
97971
+ b2.t
97972
+ );
97973
+ }) }),
97974
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
97975
+ /* @__PURE__ */ jsxRuntime.jsx(
97976
+ "button",
97977
+ {
97978
+ type: "button",
97979
+ disabled: !canMut,
97980
+ onMouseDown: (e2) => e2.preventDefault(),
97981
+ onClick: () => {
97982
+ if (!canFormat || !p3.selectedElement) {
97983
+ return;
97984
+ }
97985
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97986
+ const current = ts?.fontSize ?? 18;
97987
+ p3.onUpdateTextStyle({ fontSize: current + 2 });
97988
+ },
97989
+ className: gB,
97990
+ title: "Increase Font Size",
97991
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAArrowUp, { className: ic2 })
97992
+ }
97993
+ ),
97994
+ /* @__PURE__ */ jsxRuntime.jsx(
97995
+ "button",
97996
+ {
97997
+ type: "button",
97998
+ disabled: !canMut,
97999
+ onMouseDown: (e2) => e2.preventDefault(),
98000
+ onClick: () => {
98001
+ if (!canFormat || !p3.selectedElement) {
98002
+ return;
98003
+ }
98004
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98005
+ const current = ts?.fontSize ?? 18;
98006
+ p3.onUpdateTextStyle({ fontSize: Math.max(1, current - 2) });
98007
+ },
98008
+ className: gB,
98009
+ title: "Decrease Font Size",
98010
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAArrowDown, { className: ic2 })
98011
+ }
98012
+ ),
98013
+ /* @__PURE__ */ jsxRuntime.jsx(
98014
+ "button",
98015
+ {
98016
+ type: "button",
98017
+ disabled: !canMut,
98018
+ onMouseDown: (e2) => e2.preventDefault(),
98019
+ onClick: () => {
98020
+ if (!canFormat) {
98021
+ return;
98022
+ }
98023
+ p3.onUpdateTextStyle({
98024
+ bold: false,
98025
+ italic: false,
98026
+ underline: false,
98027
+ strikethrough: false,
98028
+ highlightColor: void 0
98029
+ });
98030
+ },
98031
+ className: gL,
98032
+ title: "Clear Formatting",
98033
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuRemoveFormatting, { className: ic2 })
98034
+ }
98035
+ )
98036
+ ] }),
98037
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
98038
+ /* @__PURE__ */ jsxRuntime.jsxs(
98039
+ "button",
98040
+ {
98041
+ type: "button",
98042
+ disabled: !canMut,
98043
+ onMouseDown: (e2) => e2.preventDefault(),
98044
+ className: pill,
98045
+ title: "Font Color",
98046
+ children: [
98047
+ /* @__PURE__ */ jsxRuntime.jsx(
98048
+ "svg",
98049
+ {
98050
+ className: ic2,
98051
+ viewBox: "0 0 24 24",
98052
+ fill: "none",
98053
+ stroke: "currentColor",
98054
+ strokeWidth: "2",
98055
+ strokeLinecap: "round",
98056
+ strokeLinejoin: "round",
98057
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 20h12M9.5 4h5L18 16H6L9.5 4z" })
98058
+ }
98059
+ ),
98060
+ /* @__PURE__ */ jsxRuntime.jsx(
98061
+ "div",
98062
+ {
98063
+ className: "w-4 h-1 rounded-sm -mt-0.5",
98064
+ style: { backgroundColor: currentColor }
98065
+ }
98066
+ )
98067
+ ]
98068
+ }
98069
+ ),
98070
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:block pt-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl p-2 w-36", children: [
98071
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: FONT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsxRuntime.jsx(
98072
+ "button",
98073
+ {
98074
+ type: "button",
98075
+ className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentColor?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
98076
+ style: { backgroundColor: c2 },
98077
+ onMouseDown: (e2) => e2.preventDefault(),
98078
+ onClick: () => handleColorChange(c2)
98079
+ },
98080
+ c2
98081
+ )) }),
98082
+ /* @__PURE__ */ jsxRuntime.jsx(
98083
+ "button",
98084
+ {
98085
+ type: "button",
98086
+ className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
98087
+ onMouseDown: (e2) => e2.preventDefault(),
98088
+ onClick: () => colorInputRef.current?.click(),
98089
+ children: "Custom colour..."
98090
+ }
98091
+ ),
98092
+ /* @__PURE__ */ jsxRuntime.jsx(
98093
+ "input",
98094
+ {
98095
+ ref: colorInputRef,
98096
+ type: "color",
98097
+ className: "sr-only",
98098
+ value: currentColor,
98099
+ onChange: (e2) => handleColorChange(e2.target.value)
98100
+ }
98101
+ )
98102
+ ] }) })
98103
+ ] }),
98104
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
98105
+ /* @__PURE__ */ jsxRuntime.jsxs(
98106
+ "button",
98107
+ {
98108
+ type: "button",
98109
+ disabled: !canMut,
98110
+ onMouseDown: (e2) => e2.preventDefault(),
98111
+ className: pill,
98112
+ title: "Text Highlight Color",
98113
+ children: [
98114
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuHighlighter, { className: ic2 }),
98115
+ /* @__PURE__ */ jsxRuntime.jsx(
98116
+ "div",
98117
+ {
98118
+ className: "w-4 h-1 rounded-sm -mt-0.5",
98119
+ style: { backgroundColor: currentHighlight }
98120
+ }
98121
+ )
98122
+ ]
98123
+ }
98124
+ ),
98125
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:block pt-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl p-2 w-36", children: [
98126
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: HIGHLIGHT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsxRuntime.jsx(
98127
+ "button",
98128
+ {
98129
+ type: "button",
98130
+ className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentHighlight?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
98131
+ style: { backgroundColor: c2 },
98132
+ onMouseDown: (e2) => e2.preventDefault(),
98133
+ onClick: () => handleHighlightChange(c2)
98134
+ },
98135
+ c2
98136
+ )) }),
98137
+ /* @__PURE__ */ jsxRuntime.jsx(
98138
+ "button",
98139
+ {
98140
+ type: "button",
98141
+ className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
98142
+ onMouseDown: (e2) => e2.preventDefault(),
98143
+ onClick: () => highlightInputRef.current?.click(),
98144
+ children: "Custom colour..."
98145
+ }
98146
+ ),
98147
+ /* @__PURE__ */ jsxRuntime.jsx(
98148
+ "input",
98149
+ {
98150
+ ref: highlightInputRef,
98151
+ type: "color",
98152
+ className: "sr-only",
98153
+ value: currentHighlight,
98154
+ onChange: (e2) => handleHighlightChange(e2.target.value)
98155
+ }
98156
+ )
98157
+ ] }) })
98158
+ ] })
98159
+ ] }),
98160
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Font" })
98161
+ ] }),
98162
+ sep,
98163
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
98164
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
98165
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
98166
+ /* @__PURE__ */ jsxRuntime.jsx(
98167
+ "button",
98168
+ {
98169
+ type: "button",
98170
+ disabled: !canMut,
98171
+ onMouseDown: (e2) => e2.preventDefault(),
98172
+ onClick: () => {
98173
+ if (!canFormat || !p3.selectedElement) {
98174
+ return;
98175
+ }
98176
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98177
+ p3.onUpdateTextStyle({
98178
+ listType: ts?.listType === "bullet" ? "none" : "bullet"
98179
+ });
98180
+ },
98181
+ className: gB,
98182
+ title: "Bullet List",
98183
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuList, { className: ic2 })
98184
+ }
98185
+ ),
98186
+ /* @__PURE__ */ jsxRuntime.jsx(
98187
+ "button",
98188
+ {
98189
+ type: "button",
98190
+ disabled: !canMut,
98191
+ onMouseDown: (e2) => e2.preventDefault(),
98192
+ onClick: () => {
98193
+ if (!canFormat || !p3.selectedElement) {
98194
+ return;
98195
+ }
98196
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98197
+ p3.onUpdateTextStyle({
98198
+ listType: ts?.listType === "numbered" ? "none" : "numbered"
98199
+ });
98200
+ },
98201
+ className: gL,
98202
+ title: "Numbered List",
98203
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuListOrdered, { className: ic2 })
98204
+ }
98205
+ )
98206
+ ] }),
98207
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
98208
+ /* @__PURE__ */ jsxRuntime.jsx(
98209
+ "button",
98210
+ {
98211
+ type: "button",
98212
+ disabled: !canMut,
98213
+ onMouseDown: (e2) => e2.preventDefault(),
98214
+ onClick: () => {
98215
+ if (!canFormat || !p3.selectedElement) {
98216
+ return;
98217
+ }
98218
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98219
+ const current = ts?.paragraphMarginLeft ?? 0;
98220
+ p3.onUpdateTextStyle({
98221
+ paragraphMarginLeft: Math.max(0, current - 24)
98222
+ });
98223
+ },
98224
+ className: gB,
98225
+ title: "Decrease Indent",
98226
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuIndentDecrease, { className: ic2 })
98227
+ }
98228
+ ),
98229
+ /* @__PURE__ */ jsxRuntime.jsx(
98230
+ "button",
98231
+ {
98232
+ type: "button",
98233
+ disabled: !canMut,
98234
+ onMouseDown: (e2) => e2.preventDefault(),
98235
+ onClick: () => {
98236
+ if (!canFormat || !p3.selectedElement) {
98237
+ return;
98238
+ }
98239
+ const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98240
+ const current = ts?.paragraphMarginLeft ?? 0;
98241
+ p3.onUpdateTextStyle({
98242
+ paragraphMarginLeft: current + 24
98243
+ });
98244
+ },
98245
+ className: gL,
98246
+ title: "Increase Indent",
98247
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuIndentIncrease, { className: ic2 })
98248
+ }
98249
+ )
98250
+ ] }),
98251
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: grp, children: ATXT.map((b2, i3, a2) => {
98252
+ const handleClick = () => {
98253
+ if (!canFormat) {
98254
+ return;
98255
+ }
98256
+ const alignMap = {
98257
+ "Align left": "left",
98258
+ "Align center": "center",
98259
+ "Align right": "right",
98260
+ Justify: "justify"
98261
+ };
98262
+ const align = alignMap[b2.t];
98263
+ if (align) {
98264
+ p3.onUpdateTextStyle({ align });
98265
+ }
98266
+ };
98267
+ return /* @__PURE__ */ jsxRuntime.jsx(
98268
+ "button",
98269
+ {
98270
+ type: "button",
98271
+ disabled: !canMut,
98272
+ onMouseDown: (e2) => e2.preventDefault(),
98273
+ onClick: handleClick,
98274
+ className: i3 < a2.length - 1 ? gB : gL,
98275
+ title: b2.t,
98276
+ children: b2.i
98277
+ },
98278
+ b2.t
98279
+ );
98280
+ }) })
98281
+ ] }),
98282
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Paragraph" })
98283
+ ] })
98284
+ ] });
98285
+ }
98286
+
98287
+ // src/viewer/hooks/collaboration/sanitize.ts
98288
+ var ROOM_ID_REGEX = /^[a-zA-Z0-9_-]{1,128}$/;
98289
+ function validateRoomId(roomId) {
98290
+ if (!ROOM_ID_REGEX.test(roomId)) {
98291
+ throw new Error(
98292
+ `Invalid collaboration room ID: "${roomId}". Must be 1-128 alphanumeric characters, hyphens, or underscores.`
98293
+ );
98294
+ }
98295
+ return roomId;
98296
+ }
98297
+ function sanitizeUserName(name) {
98298
+ if (typeof name !== "string") {
98299
+ return "Anonymous";
98300
+ }
98301
+ const stripped = name.replace(/<[^>]*>/g, "");
98302
+ const trimmed = stripped.trim().slice(0, 64);
98303
+ return trimmed || "Anonymous";
98304
+ }
98305
+ function clampCursorPosition(value, min2, max2) {
98306
+ if (typeof value !== "number" || !Number.isFinite(value)) {
98307
+ return 0;
98308
+ }
98309
+ const margin = 20;
98310
+ return Math.max(min2 - margin, Math.min(max2 + margin, value));
98311
+ }
98312
+ var HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
98313
+ function sanitizeColor(color, fallback = "#6366f1") {
98314
+ if (typeof color !== "string") {
98315
+ return fallback;
98316
+ }
98317
+ return HEX_COLOR_REGEX.test(color) ? color : fallback;
98318
+ }
98319
+ function sanitizeAvatarUrl(url) {
98320
+ if (typeof url !== "string") {
98321
+ return void 0;
98322
+ }
98323
+ try {
98324
+ const parsed = new URL(url);
98325
+ if (parsed.protocol === "https:" || parsed.protocol === "http:" || parsed.protocol === "data:") {
98326
+ return url;
98327
+ }
98328
+ } catch {
98329
+ }
98330
+ return void 0;
98331
+ }
98332
+ function sanitizeSlideIndex(value) {
98333
+ if (typeof value !== "number" || !Number.isFinite(value)) {
98334
+ return 0;
98335
+ }
98336
+ return Math.max(0, Math.floor(value));
98337
+ }
98338
+ function sanitizePresence(raw, canvasWidth, canvasHeight) {
98339
+ if (typeof raw.clientId !== "number") {
98340
+ return null;
98341
+ }
98342
+ return {
98343
+ clientId: raw.clientId,
98344
+ userName: sanitizeUserName(raw.userName),
98345
+ userAvatar: sanitizeAvatarUrl(raw.userAvatar),
98346
+ userColor: sanitizeColor(raw.userColor),
98347
+ activeSlideIndex: sanitizeSlideIndex(raw.activeSlideIndex),
98348
+ cursorX: clampCursorPosition(raw.cursorX, 0, canvasWidth),
98349
+ cursorY: clampCursorPosition(raw.cursorY, 0, canvasHeight),
98350
+ lastUpdated: typeof raw.lastUpdated === "string" ? raw.lastUpdated : (/* @__PURE__ */ new Date()).toISOString(),
98351
+ selectedElementId: typeof raw.selectedElementId === "string" ? raw.selectedElementId.slice(0, 128) : void 0
98352
+ };
98353
+ }
98354
+ var BROADCAST_THROTTLE_MS = 50;
98355
+ var STALE_PRESENCE_MS = 3e4;
98356
+ function usePresenceTracking({
98357
+ awareness,
98358
+ localClientId,
98359
+ userName,
98360
+ userColor,
98361
+ userAvatar,
98362
+ canvasWidth,
98363
+ canvasHeight
98364
+ }) {
98365
+ const [remoteUsers, setRemoteUsers] = React10.useState([]);
98366
+ const lastBroadcastRef = React10.useRef(0);
98367
+ const pendingBroadcastRef = React10.useRef(null);
98368
+ const latestLocalState = React10.useRef({});
98369
+ const broadcastPresence = React10.useCallback(
98370
+ (update2) => {
98371
+ if (!awareness) {
98372
+ return;
98373
+ }
98374
+ Object.assign(latestLocalState.current, update2);
98375
+ const now = Date.now();
98376
+ const elapsed = now - lastBroadcastRef.current;
98377
+ const flush = () => {
98378
+ const state2 = {
98379
+ ...latestLocalState.current,
98380
+ userName,
98381
+ userColor,
98382
+ userAvatar,
98383
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98384
+ };
98385
+ awareness.setLocalStateField("presence", state2);
98386
+ lastBroadcastRef.current = Date.now();
98387
+ };
98388
+ if (elapsed >= BROADCAST_THROTTLE_MS) {
98389
+ if (pendingBroadcastRef.current) {
98390
+ clearTimeout(pendingBroadcastRef.current);
98391
+ pendingBroadcastRef.current = null;
98392
+ }
98393
+ flush();
98394
+ } else if (!pendingBroadcastRef.current) {
98395
+ pendingBroadcastRef.current = setTimeout(() => {
98396
+ pendingBroadcastRef.current = null;
98397
+ flush();
98398
+ }, BROADCAST_THROTTLE_MS - elapsed);
98399
+ }
98400
+ },
98401
+ [awareness, userName, userColor, userAvatar]
98402
+ );
98403
+ React10.useEffect(() => {
98404
+ if (!awareness) {
98405
+ return;
98406
+ }
98407
+ awareness.setLocalStateField("presence", {
98408
+ userName,
98409
+ userColor,
98410
+ userAvatar,
98411
+ activeSlideIndex: 0,
98412
+ cursorX: 0,
98413
+ cursorY: 0,
98414
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98415
+ });
98416
+ }, [awareness, userName, userColor, userAvatar]);
98417
+ React10.useEffect(() => {
98418
+ if (!awareness || localClientId === null) {
98419
+ return;
98420
+ }
98421
+ const handleChange = () => {
98422
+ const now = Date.now();
98423
+ const states = awareness.getStates();
98424
+ const users = [];
98425
+ states.forEach((state2, cid) => {
98426
+ if (cid === localClientId) {
97233
98427
  return;
97234
98428
  }
97235
- const ts = pptxViewerCore.hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97236
- switch (b2.t) {
97237
- case "Bold":
97238
- p3.onUpdateTextStyle({ bold: !ts?.bold });
97239
- break;
97240
- case "Italic":
97241
- p3.onUpdateTextStyle({ italic: !ts?.italic });
97242
- break;
97243
- case "Underline":
97244
- p3.onUpdateTextStyle({
97245
- underline: !ts?.underline
97246
- });
97247
- break;
97248
- case "Strikethrough":
97249
- p3.onUpdateTextStyle({
97250
- strikethrough: !ts?.strikethrough
97251
- });
97252
- break;
98429
+ const raw = state2?.presence;
98430
+ if (!raw || typeof raw !== "object") {
98431
+ return;
97253
98432
  }
97254
- };
97255
- return /* @__PURE__ */ jsxRuntime.jsx(
97256
- "button",
98433
+ const sanitized = sanitizePresence({ ...raw, clientId: cid }, canvasWidth, canvasHeight);
98434
+ if (!sanitized) {
98435
+ return;
98436
+ }
98437
+ const updatedAt = new Date(sanitized.lastUpdated).getTime();
98438
+ if (Number.isNaN(updatedAt) || now - updatedAt > STALE_PRESENCE_MS) {
98439
+ return;
98440
+ }
98441
+ users.push(sanitized);
98442
+ });
98443
+ setRemoteUsers(users);
98444
+ };
98445
+ awareness.on("change", handleChange);
98446
+ awareness.on("update", handleChange);
98447
+ handleChange();
98448
+ return () => {
98449
+ awareness.off("change", handleChange);
98450
+ awareness.off("update", handleChange);
98451
+ };
98452
+ }, [awareness, localClientId, canvasWidth, canvasHeight]);
98453
+ React10.useEffect(() => {
98454
+ if (!awareness) {
98455
+ return;
98456
+ }
98457
+ const interval = setInterval(() => {
98458
+ awareness.setLocalStateField("presence", {
98459
+ ...latestLocalState.current,
98460
+ userName,
98461
+ userColor,
98462
+ userAvatar,
98463
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98464
+ });
98465
+ }, 1e4);
98466
+ return () => clearInterval(interval);
98467
+ }, [awareness, userName, userColor, userAvatar]);
98468
+ React10.useEffect(() => {
98469
+ return () => {
98470
+ if (pendingBroadcastRef.current) {
98471
+ clearTimeout(pendingBroadcastRef.current);
98472
+ }
98473
+ };
98474
+ }, []);
98475
+ return { remoteUsers, broadcastPresence };
98476
+ }
98477
+ function useYjsProvider({ config }) {
98478
+ const [status, setStatus] = React10.useState("disconnected");
98479
+ const [awareness, setAwareness] = React10.useState(null);
98480
+ const [doc2, setDoc] = React10.useState(null);
98481
+ const [clientId, setClientId] = React10.useState(null);
98482
+ const cleanupRef = React10.useRef(null);
98483
+ const init = React10.useCallback(async () => {
98484
+ const roomId = validateRoomId(config.roomId);
98485
+ setStatus("connecting");
98486
+ try {
98487
+ const [Y, { WebsocketProvider: WebsocketProvider2 }] = await Promise.all([Promise.resolve().then(() => (init_yjs(), yjs_exports)), Promise.resolve().then(() => (init_y_websocket(), y_websocket_exports))]);
98488
+ const yDoc = new Y.Doc();
98489
+ const provider = new WebsocketProvider2(
98490
+ config.serverUrl,
98491
+ roomId,
98492
+ yDoc,
98493
+ // eslint-disable-line @typescript-eslint/no-explicit-any
97257
98494
  {
97258
- type: "button",
97259
- disabled: !canMut,
97260
- onMouseDown: (e2) => e2.preventDefault(),
97261
- onClick: handleClick,
97262
- className: i3 < a2.length - 1 ? gB : gL,
97263
- title: b2.t,
97264
- children: b2.i
97265
- },
97266
- b2.t
98495
+ params: config.authToken ? { token: config.authToken } : void 0
98496
+ }
97267
98497
  );
97268
- }) }),
97269
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
97270
- /* @__PURE__ */ jsxRuntime.jsxs(
97271
- "button",
98498
+ const handleStatus = (event) => {
98499
+ if (event.status === "connected") {
98500
+ setStatus("connected");
98501
+ } else if (event.status === "disconnected") {
98502
+ setStatus("disconnected");
98503
+ }
98504
+ };
98505
+ provider.on("status", handleStatus);
98506
+ if (provider.wsconnected) {
98507
+ setStatus("connected");
98508
+ }
98509
+ setDoc(yDoc);
98510
+ setAwareness(provider.awareness);
98511
+ setClientId(provider.awareness.clientID);
98512
+ cleanupRef.current = () => {
98513
+ provider.off("status", handleStatus);
98514
+ provider.destroy();
98515
+ yDoc.destroy();
98516
+ setDoc(null);
98517
+ setAwareness(null);
98518
+ setClientId(null);
98519
+ setStatus("disconnected");
98520
+ };
98521
+ } catch (err) {
98522
+ console.warn(
98523
+ "[pptx-viewer] Collaboration packages not available:",
98524
+ err instanceof Error ? err.message : err
98525
+ );
98526
+ setStatus("error");
98527
+ }
98528
+ }, [config.roomId, config.serverUrl, config.authToken]);
98529
+ React10.useEffect(() => {
98530
+ init();
98531
+ return () => {
98532
+ cleanupRef.current?.();
98533
+ cleanupRef.current = null;
98534
+ };
98535
+ }, [init]);
98536
+ return { status, awareness, doc: doc2, clientId };
98537
+ }
98538
+
98539
+ // src/viewer/hooks/collaboration/useCollaborativeState.ts
98540
+ function useCollaborativeState({
98541
+ config,
98542
+ canvasWidth,
98543
+ canvasHeight
98544
+ }) {
98545
+ const userColor = sanitizeColor(config.userColor, "#6366f1");
98546
+ const { status, awareness, doc: doc2, clientId } = useYjsProvider({ config });
98547
+ const { remoteUsers, broadcastPresence } = usePresenceTracking({
98548
+ awareness,
98549
+ localClientId: clientId,
98550
+ userName: config.userName,
98551
+ userColor,
98552
+ userAvatar: config.userAvatar,
98553
+ canvasWidth,
98554
+ canvasHeight
98555
+ });
98556
+ const connectedCount = status === "connected" ? remoteUsers.length + 1 : remoteUsers.length;
98557
+ return {
98558
+ status,
98559
+ remoteUsers,
98560
+ broadcastPresence,
98561
+ connectedCount,
98562
+ config,
98563
+ doc: doc2
98564
+ };
98565
+ }
98566
+ var CollaborationContext = React10.createContext(null);
98567
+ function useCollaboration() {
98568
+ return React10.useContext(CollaborationContext);
98569
+ }
98570
+ function CollaborationProvider({
98571
+ config,
98572
+ canvasWidth,
98573
+ canvasHeight,
98574
+ children
98575
+ }) {
98576
+ const value = useCollaborativeState({
98577
+ config,
98578
+ canvasWidth,
98579
+ canvasHeight
98580
+ });
98581
+ return /* @__PURE__ */ jsxRuntime.jsx(CollaborationContext.Provider, { value, children });
98582
+ }
98583
+ function RemoteUserCursors({
98584
+ remoteUsers,
98585
+ activeSlideIndex,
98586
+ canvasWidth,
98587
+ canvasHeight
98588
+ }) {
98589
+ const visibleUsers = remoteUsers.filter((u2) => u2.activeSlideIndex === activeSlideIndex);
98590
+ if (visibleUsers.length === 0) {
98591
+ return null;
98592
+ }
98593
+ return /* @__PURE__ */ jsxRuntime.jsx(
98594
+ "svg",
98595
+ {
98596
+ "data-testid": "remote-user-cursors",
98597
+ className: "absolute inset-0 pointer-events-none",
98598
+ style: { zIndex: 9999 },
98599
+ width: canvasWidth,
98600
+ height: canvasHeight,
98601
+ viewBox: `0 0 ${canvasWidth} ${canvasHeight}`,
98602
+ "aria-hidden": "true",
98603
+ children: visibleUsers.map((user) => /* @__PURE__ */ jsxRuntime.jsxs(
98604
+ "g",
97272
98605
  {
97273
- type: "button",
97274
- disabled: !canMut,
97275
- onMouseDown: (e2) => e2.preventDefault(),
97276
- className: pill,
97277
- title: "Font color",
98606
+ transform: `translate(${user.cursorX}, ${user.cursorY})`,
98607
+ "data-testid": `remote-cursor-${user.clientId}`,
97278
98608
  children: [
97279
98609
  /* @__PURE__ */ jsxRuntime.jsx(
97280
- "svg",
98610
+ "path",
97281
98611
  {
97282
- className: ic2,
97283
- viewBox: "0 0 24 24",
97284
- fill: "none",
97285
- stroke: "currentColor",
97286
- strokeWidth: "2",
97287
- strokeLinecap: "round",
97288
- strokeLinejoin: "round",
97289
- children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 20h12M9.5 4h5L18 16H6L9.5 4z" })
98612
+ d: "M0 0 L0 16 L4.5 12.5 L8 20 L10.5 19 L7 11.5 L12 11 Z",
98613
+ fill: user.userColor,
98614
+ stroke: "#fff",
98615
+ strokeWidth: 1,
98616
+ opacity: 0.9
97290
98617
  }
97291
98618
  ),
97292
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-4 h-1 rounded-sm -mt-0.5", style: { backgroundColor: currentColor } })
98619
+ /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: "translate(14, 18)", children: [
98620
+ /* @__PURE__ */ jsxRuntime.jsx(
98621
+ "rect",
98622
+ {
98623
+ rx: 3,
98624
+ ry: 3,
98625
+ x: -2,
98626
+ y: -10,
98627
+ width: Math.min(user.userName.length * 7 + 8, 150),
98628
+ height: 16,
98629
+ fill: user.userColor,
98630
+ opacity: 0.85
98631
+ }
98632
+ ),
98633
+ /* @__PURE__ */ jsxRuntime.jsx(
98634
+ "text",
98635
+ {
98636
+ fill: "#fff",
98637
+ fontSize: 10,
98638
+ fontFamily: "system-ui, sans-serif",
98639
+ fontWeight: 500,
98640
+ dominantBaseline: "central",
98641
+ y: -2,
98642
+ x: 2,
98643
+ children: user.userName.length > 20 ? `${user.userName.slice(0, 18)}...` : user.userName
98644
+ }
98645
+ )
98646
+ ] })
97293
98647
  ]
98648
+ },
98649
+ user.clientId
98650
+ ))
98651
+ }
98652
+ );
98653
+ }
98654
+ function getInitials(name) {
98655
+ const parts = name.trim().split(/\s+/);
98656
+ if (parts.length >= 2) {
98657
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
98658
+ }
98659
+ return name.slice(0, 2).toUpperCase();
98660
+ }
98661
+ function AvatarCircle({
98662
+ name,
98663
+ color,
98664
+ avatar,
98665
+ isLocal
98666
+ }) {
98667
+ const { t: t2 } = reactI18next.useTranslation();
98668
+ const initials = getInitials(name);
98669
+ const title = isLocal ? t2("pptx.collaboration.youLabel", { name }) : name;
98670
+ return /* @__PURE__ */ jsxRuntime.jsx(
98671
+ "div",
98672
+ {
98673
+ className: "relative w-7 h-7 rounded-full flex items-center justify-center text-[10px] font-semibold text-white border-2 -ml-1 first:ml-0",
98674
+ style: {
98675
+ backgroundColor: color,
98676
+ borderColor: isLocal ? "#fff" : color
98677
+ },
98678
+ title,
98679
+ "aria-label": title,
98680
+ children: avatar ? /* @__PURE__ */ jsxRuntime.jsx(
98681
+ "img",
98682
+ {
98683
+ src: avatar,
98684
+ alt: "",
98685
+ className: "w-full h-full rounded-full object-cover",
98686
+ onError: (e2) => {
98687
+ e2.target.style.display = "none";
98688
+ }
97294
98689
  }
97295
- ),
97296
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:block pt-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl p-2 w-36", children: [
97297
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: FONT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsxRuntime.jsx(
97298
- "button",
98690
+ ) : initials
98691
+ }
98692
+ );
98693
+ }
98694
+ function UserAvatarBar({
98695
+ remoteUsers,
98696
+ localUserName,
98697
+ localUserColor,
98698
+ localUserAvatar,
98699
+ status,
98700
+ maxVisible = 5
98701
+ }) {
98702
+ const { t: t2 } = reactI18next.useTranslation();
98703
+ if (status === "disconnected" || status === "error") {
98704
+ return null;
98705
+ }
98706
+ const allUsers = [
98707
+ { name: localUserName, color: localUserColor, avatar: localUserAvatar, isLocal: true },
98708
+ ...remoteUsers.map((u2) => ({
98709
+ name: u2.userName,
98710
+ color: u2.userColor,
98711
+ avatar: u2.userAvatar,
98712
+ isLocal: false
98713
+ }))
98714
+ ];
98715
+ const visible = allUsers.slice(0, maxVisible);
98716
+ const overflow = allUsers.length - maxVisible;
98717
+ return /* @__PURE__ */ jsxRuntime.jsxs(
98718
+ "div",
98719
+ {
98720
+ "data-testid": "user-avatar-bar",
98721
+ className: "flex items-center px-2",
98722
+ "aria-label": t2("pptx.collaboration.usersConnected", { count: allUsers.length }),
98723
+ children: [
98724
+ visible.map((user, i3) => /* @__PURE__ */ jsxRuntime.jsx(
98725
+ AvatarCircle,
97299
98726
  {
97300
- type: "button",
97301
- className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentColor?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
97302
- style: { backgroundColor: c2 },
97303
- onMouseDown: (e2) => e2.preventDefault(),
97304
- onClick: () => handleColorChange(c2)
98727
+ name: user.name,
98728
+ color: user.color,
98729
+ avatar: user.avatar,
98730
+ isLocal: user.isLocal
97305
98731
  },
97306
- c2
97307
- )) }),
97308
- /* @__PURE__ */ jsxRuntime.jsx(
97309
- "button",
97310
- {
97311
- type: "button",
97312
- className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
97313
- onMouseDown: (e2) => e2.preventDefault(),
97314
- onClick: () => colorInputRef.current?.click(),
97315
- children: "Custom colour\u2026"
97316
- }
97317
- ),
97318
- /* @__PURE__ */ jsxRuntime.jsx(
97319
- "input",
98732
+ user.isLocal ? "local" : `remote-${i3}`
98733
+ )),
98734
+ overflow > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
98735
+ "div",
97320
98736
  {
97321
- ref: colorInputRef,
97322
- type: "color",
97323
- className: "sr-only",
97324
- value: currentColor,
97325
- onChange: (e2) => handleColorChange(e2.target.value)
98737
+ className: "w-7 h-7 rounded-full flex items-center justify-center text-[10px] font-semibold text-gray-300 bg-gray-700 border-2 border-gray-600 -ml-1",
98738
+ title: t2("pptx.collaboration.moreUsers", { count: overflow }),
98739
+ children: [
98740
+ "+",
98741
+ overflow
98742
+ ]
97326
98743
  }
97327
98744
  )
97328
- ] }) })
97329
- ] }),
97330
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: grp, children: ATXT.map((b2, i3, a2) => {
97331
- const handleClick = () => {
97332
- if (!canFormat) {
97333
- return;
97334
- }
97335
- const alignMap = {
97336
- "Align left": "left",
97337
- "Align center": "center",
97338
- "Align right": "right",
97339
- Justify: "justify"
97340
- };
97341
- const align = alignMap[b2.t];
97342
- if (align) {
97343
- p3.onUpdateTextStyle({ align });
97344
- }
97345
- };
97346
- return /* @__PURE__ */ jsxRuntime.jsx(
97347
- "button",
97348
- {
97349
- type: "button",
97350
- disabled: !canMut,
97351
- onMouseDown: (e2) => e2.preventDefault(),
97352
- onClick: handleClick,
97353
- className: i3 < a2.length - 1 ? gB : gL,
97354
- title: b2.t,
97355
- children: b2.i
97356
- },
97357
- b2.t
97358
- );
97359
- }) })
97360
- ] });
98745
+ ]
98746
+ }
98747
+ );
98748
+ }
98749
+ var STATUS_STYLES = {
98750
+ connected: {
98751
+ dot: "bg-green-400",
98752
+ text: "text-green-400",
98753
+ label: "Connected"
98754
+ },
98755
+ connecting: {
98756
+ dot: "bg-yellow-400 animate-pulse",
98757
+ text: "text-yellow-400",
98758
+ label: "Connecting..."
98759
+ },
98760
+ disconnected: {
98761
+ dot: "bg-gray-500",
98762
+ text: "text-gray-500",
98763
+ label: "Disconnected"
98764
+ },
98765
+ error: {
98766
+ dot: "bg-red-400",
98767
+ text: "text-red-400",
98768
+ label: "Connection error"
98769
+ }
98770
+ };
98771
+ function CollaborationStatusIndicator({
98772
+ status,
98773
+ connectedCount
98774
+ }) {
98775
+ const { t: t2 } = reactI18next.useTranslation();
98776
+ const style = STATUS_STYLES[status];
98777
+ return /* @__PURE__ */ jsxRuntime.jsxs(
98778
+ "div",
98779
+ {
98780
+ "data-testid": "collaboration-status",
98781
+ className: "flex items-center gap-1.5",
98782
+ "aria-label": t2("pptx.collaboration.statusAriaLabel", {
98783
+ status: t2(`pptx.collaboration.status.${status}`),
98784
+ count: connectedCount
98785
+ }),
98786
+ children: [
98787
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-block w-2 h-2 rounded-full ${style.dot}`, "aria-hidden": "true" }),
98788
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-[10px] ${style.text}`, children: status === "connected" ? t2("pptx.collaboration.userCount", { count: connectedCount }) : t2(`pptx.collaboration.status.${status}`) })
98789
+ ]
98790
+ }
98791
+ );
97361
98792
  }
97362
98793
  function CustomShowsControls({
97363
98794
  customShows,
@@ -97605,7 +99036,6 @@ function ModeSwitcher({
97605
99036
  mode,
97606
99037
  onSetMode,
97607
99038
  onCloseMasterView,
97608
- onToggleSlideSorter,
97609
99039
  onEnterPresenterView,
97610
99040
  onEnterRehearsalMode,
97611
99041
  onOpenSetUpSlideShow,
@@ -97614,64 +99044,33 @@ function ModeSwitcher({
97614
99044
  showSubtitles
97615
99045
  }) {
97616
99046
  if (mode === "master") {
97617
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center gap-2", children: [
97618
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center px-2 py-1 rounded bg-amber-600/90 text-[11px] text-amber-50", children: "Slide Master View" }),
99047
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center gap-1.5", children: [
99048
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center px-2 py-0.5 rounded-sm bg-amber-600/90 text-[10px] text-amber-50", children: "Master View" }),
97619
99049
  /* @__PURE__ */ jsxRuntime.jsx(
97620
99050
  "button",
97621
99051
  {
97622
99052
  type: "button",
97623
99053
  onClick: onCloseMasterView,
97624
- className: "px-2.5 py-1 rounded bg-muted hover:bg-accent text-[11px] text-foreground transition-colors",
99054
+ className: "px-2 py-0.5 rounded-sm hover:bg-accent text-[10px] text-foreground transition-colors",
97625
99055
  title: "Close master view",
97626
- children: "Close Master View"
99056
+ children: "Close"
97627
99057
  }
97628
99058
  )
97629
99059
  ] });
97630
99060
  }
97631
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center rounded bg-muted text-[11px] overflow-hidden", children: [
97632
- MODES.map(
97633
- (m2) => m2 === "present" ? /* @__PURE__ */ jsxRuntime.jsx(
97634
- PresentDropdown,
97635
- {
97636
- isActive: mode === m2,
97637
- onPresent: () => onSetMode(m2),
97638
- onPresenterView: onEnterPresenterView,
97639
- onRehearse: onEnterRehearsalMode,
97640
- onSetUpSlideShow: onOpenSetUpSlideShow,
97641
- onBroadcast: onOpenBroadcastDialog,
97642
- onToggleSubtitles,
97643
- showSubtitles
97644
- },
97645
- m2
97646
- ) : /* @__PURE__ */ jsxRuntime.jsxs(
97647
- "button",
97648
- {
97649
- type: "button",
97650
- onClick: () => onSetMode(m2),
97651
- className: cn(
97652
- "px-2 py-1 transition-colors border-l border-border first:border-l-0",
97653
- mode === m2 ? "bg-primary text-primary-foreground" : "hover:bg-accent text-foreground"
97654
- ),
97655
- title: `${m2[0].toUpperCase()}${m2.slice(1)} mode`,
97656
- children: [
97657
- m2[0].toUpperCase(),
97658
- m2.slice(1)
97659
- ]
97660
- },
97661
- m2
97662
- )
97663
- ),
97664
- /* @__PURE__ */ jsxRuntime.jsx(
97665
- "button",
97666
- {
97667
- type: "button",
97668
- onClick: onToggleSlideSorter,
97669
- className: "px-2 py-1 border-l border-border hover:bg-accent text-foreground transition-colors",
97670
- title: "Slide sorter",
97671
- children: "Sorter"
97672
- }
97673
- )
97674
- ] });
99061
+ return /* @__PURE__ */ jsxRuntime.jsx(
99062
+ PresentDropdown,
99063
+ {
99064
+ isActive: mode === "present",
99065
+ onPresent: () => onSetMode("present"),
99066
+ onPresenterView: onEnterPresenterView,
99067
+ onRehearse: onEnterRehearsalMode,
99068
+ onSetUpSlideShow: onOpenSetUpSlideShow,
99069
+ onBroadcast: onOpenBroadcastDialog,
99070
+ onToggleSubtitles,
99071
+ showSubtitles
99072
+ }
99073
+ );
97675
99074
  }
97676
99075
  function OverflowMenu(p3) {
97677
99076
  const ovAct = (k2) => {
@@ -97739,14 +99138,12 @@ function OverflowMenu(p3) {
97739
99138
  ] });
97740
99139
  }
97741
99140
  function ToolbarPrimaryRow(p3) {
99141
+ const { t: t2 } = reactI18next.useTranslation();
97742
99142
  const {
97743
99143
  mode,
97744
99144
  canEdit,
97745
- isNarrowViewport,
97746
99145
  isSidebarCollapsed,
97747
99146
  isInspectorPaneOpen,
97748
- isCompactToolbarOpen,
97749
- scale,
97750
99147
  canUndo,
97751
99148
  canRedo,
97752
99149
  undoLabel,
@@ -97754,26 +99151,21 @@ function ToolbarPrimaryRow(p3) {
97754
99151
  findReplaceOpen,
97755
99152
  onToggleSidebar,
97756
99153
  onToggleInspector,
97757
- onToggleCompactToolbar,
97758
- onZoomIn,
97759
- onZoomOut,
97760
- onZoomToFit,
97761
99154
  onUndo,
97762
99155
  onRedo,
97763
99156
  onToggleFindReplace
97764
99157
  } = p3;
97765
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 max-md:gap-0.5 flex-wrap", children: [
99158
+ const collab = useCollaboration();
99159
+ const qab = "p-1 max-md:p-2 max-md:min-h-[40px] max-md:min-w-[40px] rounded-sm transition-colors hover:bg-accent/60 disabled:opacity-40 disabled:cursor-not-allowed active:scale-90 active:opacity-70";
99160
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5 max-md:gap-0 px-1.5 py-0.5 max-md:px-1", children: [
97766
99161
  mode !== "present" && /* @__PURE__ */ jsxRuntime.jsx(
97767
99162
  "button",
97768
99163
  {
97769
99164
  type: "button",
97770
99165
  onClick: onToggleSidebar,
97771
- className: cn(
97772
- "p-1.5 max-md:p-2.5 max-md:min-h-[44px] max-md:min-w-[44px] rounded transition-colors",
97773
- !isSidebarCollapsed ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
97774
- ),
97775
- title: "Toggle slides panel",
97776
- "aria-label": "Toggle slides panel",
99166
+ className: cn(qab, !isSidebarCollapsed ? "text-foreground" : "text-muted-foreground"),
99167
+ title: t2("pptx.toolbar.toggleSlidesPanel"),
99168
+ "aria-label": t2("pptx.toolbar.toggleSlidesPanel"),
97777
99169
  children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuPanelLeft, { className: ic2 })
97778
99170
  }
97779
99171
  ),
@@ -97781,77 +99173,84 @@ function ToolbarPrimaryRow(p3) {
97781
99173
  /* @__PURE__ */ jsxRuntime.jsx(
97782
99174
  "button",
97783
99175
  {
97784
- onClick: onZoomOut,
97785
- className: "p-1.5 max-md:p-2.5 max-md:min-h-[44px] max-md:min-w-[44px] rounded bg-muted hover:bg-accent transition-colors",
97786
- title: "Zoom out",
97787
- "aria-label": "Zoom out",
97788
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuZoomOut, { className: ics })
97789
- }
97790
- ),
97791
- /* @__PURE__ */ jsxRuntime.jsxs(
97792
- "button",
97793
- {
97794
- onClick: onZoomToFit,
97795
- className: "px-1.5 py-1 max-md:min-h-[44px] rounded bg-muted hover:bg-accent text-[11px] text-muted-foreground tabular-nums min-w-[3rem] text-center transition-colors",
97796
- title: "Zoom to fit",
97797
- children: [
97798
- Math.round(scale * 100),
97799
- "%"
97800
- ]
99176
+ type: "button",
99177
+ onClick: onUndo,
99178
+ disabled: !canEdit || !canUndo,
99179
+ className: cn(qab, "text-muted-foreground"),
99180
+ title: undoLabel ? t2("pptx.toolbar.undoAction", { action: undoLabel }) : t2("pptx.toolbar.undo"),
99181
+ "aria-label": t2("pptx.toolbar.undo"),
99182
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuUndo, { className: ics })
97801
99183
  }
97802
99184
  ),
97803
99185
  /* @__PURE__ */ jsxRuntime.jsx(
97804
99186
  "button",
97805
99187
  {
97806
- onClick: onZoomIn,
97807
- className: "p-1.5 max-md:p-2.5 max-md:min-h-[44px] max-md:min-w-[44px] rounded bg-muted hover:bg-accent transition-colors",
97808
- title: "Zoom in",
97809
- "aria-label": "Zoom in",
97810
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuZoomIn, { className: ics })
99188
+ type: "button",
99189
+ onClick: onRedo,
99190
+ disabled: !canEdit || !canRedo,
99191
+ className: cn(qab, "text-muted-foreground"),
99192
+ title: redoLabel ? t2("pptx.toolbar.redoAction", { action: redoLabel }) : t2("pptx.toolbar.redo"),
99193
+ "aria-label": t2("pptx.toolbar.redo"),
99194
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuRedo, { className: ics })
97811
99195
  }
97812
99196
  ),
97813
- sep,
97814
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: grp, children: [
97815
- /* @__PURE__ */ jsxRuntime.jsx(
97816
- "button",
97817
- {
97818
- type: "button",
97819
- onClick: onUndo,
97820
- disabled: !canEdit || !canUndo,
97821
- className: gB,
97822
- title: undoLabel ? `Undo: ${undoLabel}` : "Undo",
97823
- "aria-label": "Undo",
97824
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuUndo, { className: ics })
97825
- }
97826
- ),
97827
- /* @__PURE__ */ jsxRuntime.jsx(
97828
- "button",
97829
- {
97830
- type: "button",
97831
- onClick: onRedo,
97832
- disabled: !canEdit || !canRedo,
97833
- className: gL,
97834
- title: redoLabel ? `Redo: ${redoLabel}` : "Redo",
97835
- "aria-label": "Redo",
97836
- children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuRedo, { className: ics })
97837
- }
97838
- )
97839
- ] }),
97840
99197
  (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxRuntime.jsx(
97841
99198
  "button",
97842
99199
  {
97843
99200
  type: "button",
97844
99201
  onClick: onToggleFindReplace,
97845
99202
  className: cn(
97846
- "p-1.5 max-md:p-2.5 max-md:min-h-[44px] max-md:min-w-[44px] rounded transition-colors max-md:hidden",
97847
- findReplaceOpen ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
99203
+ qab,
99204
+ "max-md:hidden",
99205
+ findReplaceOpen ? "text-foreground" : "text-muted-foreground"
97848
99206
  ),
97849
- title: "Find & Replace",
97850
- "aria-label": "Find and Replace",
99207
+ title: t2("pptx.findReplace.title"),
99208
+ "aria-label": t2("pptx.findReplace.title"),
97851
99209
  children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuSearch, { className: ics })
97852
99210
  }
97853
99211
  ),
97854
99212
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-2 max-md:min-w-1" }),
99213
+ (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxRuntime.jsxs(
99214
+ "button",
99215
+ {
99216
+ type: "button",
99217
+ onClick: p3.onToggleComments,
99218
+ className: cn(
99219
+ qab,
99220
+ "max-md:hidden",
99221
+ p3.isCommentsPanelOpen ? "text-foreground" : "text-muted-foreground"
99222
+ ),
99223
+ title: t2("pptx.toolbar.comments"),
99224
+ "aria-label": t2("pptx.toolbar.comments"),
99225
+ children: [
99226
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuMessageSquare, { className: ics }),
99227
+ (p3.slideCommentCount ?? 0) > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute -top-0.5 -right-0.5 flex items-center justify-center w-3.5 h-3.5 rounded-full bg-primary text-[8px] text-primary-foreground leading-none", children: p3.slideCommentCount })
99228
+ ]
99229
+ }
99230
+ ),
99231
+ collab && (collab.status === "connected" || collab.status === "connecting") && collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center -space-x-1.5 mx-1", children: [
99232
+ collab.remoteUsers.slice(0, 4).map((user) => /* @__PURE__ */ jsxRuntime.jsx(
99233
+ "div",
99234
+ {
99235
+ className: "w-6 h-6 rounded-full border-2 border-background flex items-center justify-center text-[8px] font-semibold text-white shrink-0",
99236
+ style: { backgroundColor: user.userColor },
99237
+ title: user.userName,
99238
+ children: user.userAvatar ? /* @__PURE__ */ jsxRuntime.jsx(
99239
+ "img",
99240
+ {
99241
+ src: user.userAvatar,
99242
+ alt: user.userName,
99243
+ className: "w-full h-full rounded-full object-cover"
99244
+ }
99245
+ ) : user.userName.slice(0, 2).toUpperCase()
99246
+ },
99247
+ user.clientId
99248
+ )),
99249
+ collab.remoteUsers.length > 4 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-6 h-6 rounded-full border-2 border-background bg-muted flex items-center justify-center text-[8px] text-muted-foreground shrink-0", children: [
99250
+ "+",
99251
+ collab.remoteUsers.length - 4
99252
+ ] })
99253
+ ] }),
97855
99254
  /* @__PURE__ */ jsxRuntime.jsx(
97856
99255
  ModeSwitcher,
97857
99256
  {
@@ -97882,40 +99281,80 @@ function ToolbarPrimaryRow(p3) {
97882
99281
  }
97883
99282
  ),
97884
99283
  sep,
97885
- (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxRuntime.jsx(
99284
+ (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxRuntime.jsxs(
97886
99285
  "button",
97887
99286
  {
97888
99287
  type: "button",
97889
- onClick: onToggleInspector,
99288
+ onClick: p3.onOpenShareDialog ?? p3.onPackageForSharing,
97890
99289
  className: cn(
97891
- "p-1.5 max-md:p-2.5 max-md:min-h-[44px] max-md:min-w-[44px] rounded transition-colors",
97892
- isInspectorPaneOpen ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
99290
+ "relative inline-flex items-center gap-1 px-2.5 py-1 rounded-sm text-[11px] font-medium transition-colors",
99291
+ collab && collab.status === "connected" ? "bg-green-600 hover:bg-green-500 text-white" : "bg-primary hover:bg-primary/90 text-primary-foreground"
97893
99292
  ),
97894
- title: "Toggle inspector panel",
97895
- "aria-label": "Toggle inspector panel",
99293
+ title: collab && collab.status === "connected" ? t2("pptx.toolbar.sharingUsers", { count: collab.connectedCount }) : t2("pptx.toolbar.share"),
99294
+ "aria-label": t2("pptx.toolbar.share"),
99295
+ children: [
99296
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuShare2, { className: "w-3 h-3" }),
99297
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-md:hidden", children: collab && collab.status === "connected" ? t2("pptx.toolbar.sharingCount", { count: collab.connectedCount }) : t2("pptx.toolbar.share") })
99298
+ ]
99299
+ }
99300
+ ),
99301
+ (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxRuntime.jsx(
99302
+ "button",
99303
+ {
99304
+ type: "button",
99305
+ onClick: onToggleInspector,
99306
+ className: cn(qab, isInspectorPaneOpen ? "text-foreground" : "text-muted-foreground"),
99307
+ title: t2("pptx.toolbar.toggleInspector"),
99308
+ "aria-label": t2("pptx.toolbar.toggleInspector"),
97896
99309
  children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuPanelRight, { className: ic2 })
97897
99310
  }
97898
99311
  ),
97899
- !canEdit && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center px-2 py-1 rounded bg-amber-600/90 text-[11px] text-amber-50", children: "Read-only" }),
97900
- /* @__PURE__ */ jsxRuntime.jsx(OverflowMenu, { ...p3 }),
97901
- isNarrowViewport && (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxRuntime.jsx(
99312
+ /* @__PURE__ */ jsxRuntime.jsx(
97902
99313
  "button",
97903
99314
  {
97904
99315
  type: "button",
97905
- onClick: onToggleCompactToolbar,
97906
- className: cn(
97907
- "p-1.5 rounded text-[11px] transition-colors",
97908
- isCompactToolbarOpen ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
97909
- ),
97910
- title: "Toggle editing tools",
97911
- children: isCompactToolbarOpen ? "Less" : "Tools"
99316
+ onClick: p3.onOpenSettings ?? p3.onToggleShortcuts,
99317
+ className: cn(qab, "text-muted-foreground"),
99318
+ title: t2("pptx.toolbar.settingsShortcuts"),
99319
+ "aria-label": t2("pptx.toolbar.settings"),
99320
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuSettings, { className: ics })
97912
99321
  }
97913
- )
99322
+ ),
99323
+ !canEdit && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex items-center px-2 py-0.5 rounded-sm bg-amber-600/90 text-[10px] text-amber-50", children: t2("pptx.toolbar.readOnly") }),
99324
+ /* @__PURE__ */ jsxRuntime.jsx(OverflowMenu, { ...p3 })
97914
99325
  ] });
97915
99326
  }
97916
99327
  function ViewSection(p3) {
97917
99328
  const { t: t2 } = reactI18next.useTranslation();
97918
99329
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
99330
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
99331
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", children: [
99332
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: pill, title: "Normal view", children: "Normal" }),
99333
+ p3.onToggleSlideSorter ? /* @__PURE__ */ jsxRuntime.jsx("button", { className: pill, onClick: p3.onToggleSlideSorter, title: "Slide Sorter view", children: "Slide Sorter" }) : /* @__PURE__ */ jsxRuntime.jsx("button", { className: pill, title: "Slide Sorter view", children: "Slide Sorter" }),
99334
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: pill, title: "Reading View", children: "Reading View" })
99335
+ ] }),
99336
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Presentation Views" })
99337
+ ] }),
99338
+ sep,
99339
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
99340
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(
99341
+ "button",
99342
+ {
99343
+ onClick: p3.onEnterMasterView,
99344
+ disabled: !p3.canEdit,
99345
+ className: pill,
99346
+ title: "Edit slide masters and layouts",
99347
+ children: "Slide Master"
99348
+ }
99349
+ ) }),
99350
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Master Views" })
99351
+ ] }),
99352
+ sep,
99353
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
99354
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-0.5", children: p3.onZoomToFit && /* @__PURE__ */ jsxRuntime.jsx("button", { className: pill, onClick: p3.onZoomToFit, title: "Zoom to fit slide in window", children: "Zoom to Fit" }) }),
99355
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Zoom" })
99356
+ ] }),
99357
+ sep,
97919
99358
  /* @__PURE__ */ jsxRuntime.jsx(
97920
99359
  "button",
97921
99360
  {
@@ -98008,60 +99447,105 @@ function ViewSection(p3) {
98008
99447
  title: "Toggle spell check",
98009
99448
  children: "Spell"
98010
99449
  }
98011
- ),
98012
- sep,
98013
- /* @__PURE__ */ jsxRuntime.jsx(
98014
- "button",
98015
- {
98016
- onClick: p3.onEnterMasterView,
98017
- disabled: !p3.canEdit,
98018
- className: pill,
98019
- title: "Edit slide masters and layouts",
98020
- children: "Slide Master"
98021
- }
98022
99450
  )
98023
99451
  ] });
98024
99452
  }
98025
99453
  function Toolbar(p3) {
98026
99454
  const { mode, isNarrowViewport, isCompactToolbarOpen, toolbarSection, onSetToolbarSection } = p3;
99455
+ const sFil = toolbarSection === "file";
98027
99456
  const sHome = toolbarSection === "home";
98028
- const sIns = sHome || toolbarSection === "insert";
99457
+ const sIns = toolbarSection === "insert";
98029
99458
  const sTxt = sHome || toolbarSection === "text";
98030
99459
  const sArr = toolbarSection === "arrange";
98031
99460
  const sDrw = toolbarSection === "draw";
98032
99461
  const sDes = toolbarSection === "design";
98033
99462
  const sTrn = toolbarSection === "transitions";
99463
+ const sAni = toolbarSection === "animations";
99464
+ const sSlw = toolbarSection === "slideShow";
98034
99465
  const sRev = toolbarSection === "review";
98035
99466
  const sViw = toolbarSection === "view";
99467
+ const sHlp = toolbarSection === "help";
99468
+ const showRibbon = mode === "edit" || mode === "master";
98036
99469
  return /* @__PURE__ */ jsxRuntime.jsxs(
98037
99470
  "div",
98038
99471
  {
98039
99472
  role: "toolbar",
98040
99473
  "aria-label": "Presentation toolbar",
98041
- className: "relative z-20 px-2 py-1.5 max-md:px-1 max-md:py-1 border-b border-border bg-background shadow-[0_4px_12px_rgba(0,0,0,0.3)] overflow-visible",
99474
+ className: "relative z-20 border-b border-border bg-secondary/50 overflow-visible",
98042
99475
  children: [
98043
99476
  /* @__PURE__ */ jsxRuntime.jsx(ToolbarPrimaryRow, { ...p3 }),
98044
- (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxRuntime.jsxs(
99477
+ showRibbon && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center border-b border-border/60 px-1 max-md:overflow-x-auto max-md:scrollbar-none", children: [
99478
+ TOOLBAR_SECTIONS.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
99479
+ "button",
99480
+ {
99481
+ type: "button",
99482
+ onClick: () => onSetToolbarSection(s.id),
99483
+ className: cn(
99484
+ "relative px-3.5 py-2 text-[12px] font-medium whitespace-nowrap transition-colors max-md:min-h-[36px] max-md:px-3",
99485
+ toolbarSection === s.id ? s.id === "file" ? "text-primary-foreground bg-primary/80 rounded-sm" : "text-foreground after:absolute after:-bottom-px after:left-0 after:right-0 after:h-[2.5px] after:bg-primary" : s.id === "file" ? "text-primary hover:bg-primary/15 rounded-sm" : "text-muted-foreground hover:text-foreground hover:bg-accent/30"
99486
+ ),
99487
+ children: s.label
99488
+ },
99489
+ s.id
99490
+ )),
99491
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
99492
+ isNarrowViewport && /* @__PURE__ */ jsxRuntime.jsx(
99493
+ "button",
99494
+ {
99495
+ type: "button",
99496
+ onClick: p3.onToggleCompactToolbar,
99497
+ className: cn(
99498
+ "px-2 py-1 rounded text-[11px] transition-colors mr-1",
99499
+ isCompactToolbarOpen ? "bg-primary/80 text-primary-foreground" : "text-muted-foreground hover:text-foreground"
99500
+ ),
99501
+ title: "Toggle ribbon",
99502
+ children: isCompactToolbarOpen ? "Collapse" : "Expand"
99503
+ }
99504
+ )
99505
+ ] }),
99506
+ showRibbon && /* @__PURE__ */ jsxRuntime.jsxs(
98045
99507
  "div",
98046
99508
  {
98047
99509
  className: cn(
98048
- "flex items-center gap-1.5 flex-wrap mt-1.5",
99510
+ "flex items-center gap-1.5 px-2 py-1 max-md:px-1 max-md:py-0.5 overflow-visible flex-nowrap",
98049
99511
  isNarrowViewport && !isCompactToolbarOpen && "hidden"
98050
99512
  ),
98051
99513
  children: [
98052
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "inline-flex items-center rounded-md border border-border/60 bg-muted/50 p-0.5 text-[11px] max-md:overflow-x-auto max-md:max-w-full max-md:scrollbar-none", children: TOOLBAR_SECTIONS.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
98053
- "button",
99514
+ sFil && /* @__PURE__ */ jsxRuntime.jsx(
99515
+ FileSection,
98054
99516
  {
98055
- type: "button",
98056
- onClick: () => onSetToolbarSection(s.id),
98057
- className: cn(
98058
- "rounded px-2 py-0.5 transition-colors whitespace-nowrap max-md:min-h-[36px] max-md:px-3",
98059
- toolbarSection === s.id ? "bg-primary text-primary-foreground" : "text-foreground hover:bg-accent"
98060
- ),
98061
- children: s.label
98062
- },
98063
- s.id
98064
- )) }),
99517
+ onExportPng: p3.onExportPng,
99518
+ onExportPdf: p3.onExportPdf,
99519
+ onExportVideo: p3.onExportVideo,
99520
+ onExportGif: p3.onExportGif,
99521
+ onPackageForSharing: p3.onPackageForSharing,
99522
+ onSaveAsPpsx: p3.onSaveAsPpsx,
99523
+ onSaveAsPptm: p3.onSaveAsPptm,
99524
+ hasMacros: p3.hasMacros,
99525
+ onCopySlideAsImage: p3.onCopySlideAsImage,
99526
+ onPrint: p3.onPrint,
99527
+ onOpenDocumentProperties: p3.onOpenDocumentProperties,
99528
+ onOpenPasswordProtection: p3.onOpenPasswordProtection,
99529
+ onOpenFontEmbedding: p3.onOpenFontEmbedding,
99530
+ onOpenDigitalSignatures: p3.onOpenDigitalSignatures
99531
+ }
99532
+ ),
99533
+ sHome && /* @__PURE__ */ jsxRuntime.jsx(
99534
+ HomeSection,
99535
+ {
99536
+ canEdit: p3.canEdit,
99537
+ clipboardPayload: p3.clipboardPayload,
99538
+ formatPainterActive: p3.formatPainterActive,
99539
+ onCopy: p3.onCopy,
99540
+ onCut: p3.onCut,
99541
+ onPaste: p3.onPaste,
99542
+ onToggleFormatPainter: p3.onToggleFormatPainter,
99543
+ layoutOptions: p3.layoutOptions,
99544
+ onInsertSlideFromLayout: p3.onInsertSlideFromLayout,
99545
+ selectedElement: p3.selectedElement,
99546
+ onUpdateTextStyle: p3.onUpdateTextStyle
99547
+ }
99548
+ ),
98065
99549
  sIns && /* @__PURE__ */ jsxRuntime.jsx(
98066
99550
  InsertSection,
98067
99551
  {
@@ -98124,7 +99608,10 @@ function Toolbar(p3) {
98124
99608
  onToggleThemeGallery: p3.onToggleThemeGallery,
98125
99609
  isThemeGalleryOpen: p3.isThemeGalleryOpen,
98126
99610
  onToggleThemeEditor: p3.onToggleThemeEditor,
98127
- isThemeEditorOpen: p3.isThemeEditorOpen
99611
+ isThemeEditorOpen: p3.isThemeEditorOpen,
99612
+ onOpenDocumentProperties: p3.onOpenDocumentProperties,
99613
+ onToggleInspector: p3.onToggleInspector,
99614
+ isInspectorPaneOpen: p3.isInspectorPaneOpen
98128
99615
  }
98129
99616
  ),
98130
99617
  sTrn && /* @__PURE__ */ jsxRuntime.jsx(
@@ -98134,6 +99621,33 @@ function Toolbar(p3) {
98134
99621
  onToggleInspector: p3.onToggleInspector
98135
99622
  }
98136
99623
  ),
99624
+ sAni && /* @__PURE__ */ jsxRuntime.jsx(
99625
+ AnimationsSection,
99626
+ {
99627
+ canEdit: p3.canEdit,
99628
+ selectedElement: p3.selectedElement,
99629
+ isInspectorPaneOpen: p3.isInspectorPaneOpen,
99630
+ onToggleInspector: p3.onToggleInspector
99631
+ }
99632
+ ),
99633
+ sSlw && /* @__PURE__ */ jsxRuntime.jsx(
99634
+ SlideShowSection,
99635
+ {
99636
+ onPresent: () => p3.onSetMode("present"),
99637
+ onEnterPresenterView: p3.onEnterPresenterView ?? (() => {
99638
+ }),
99639
+ onEnterRehearsalMode: p3.onEnterRehearsalMode ?? (() => {
99640
+ }),
99641
+ onOpenSetUpSlideShow: p3.onOpenSetUpSlideShow ?? (() => {
99642
+ }),
99643
+ onOpenBroadcastDialog: p3.onOpenBroadcastDialog ?? (() => {
99644
+ }),
99645
+ onToggleSubtitles: p3.onToggleSubtitles ?? (() => {
99646
+ }),
99647
+ showSubtitles: p3.showSubtitles ?? false,
99648
+ onSetMode: p3.onSetMode
99649
+ }
99650
+ ),
98137
99651
  sRev && /* @__PURE__ */ jsxRuntime.jsx(
98138
99652
  ReviewSection,
98139
99653
  {
@@ -98169,7 +99683,29 @@ function Toolbar(p3) {
98169
99683
  eyedropperActive: p3.eyedropperActive,
98170
99684
  onToggleEyedropper: p3.onToggleEyedropper
98171
99685
  }
98172
- )
99686
+ ),
99687
+ sHlp && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
99688
+ /* @__PURE__ */ jsxRuntime.jsx(
99689
+ "button",
99690
+ {
99691
+ type: "button",
99692
+ onClick: p3.onToggleShortcuts,
99693
+ className: pill,
99694
+ title: "Keyboard shortcuts",
99695
+ children: "Keyboard Shortcuts"
99696
+ }
99697
+ ),
99698
+ /* @__PURE__ */ jsxRuntime.jsx(
99699
+ "button",
99700
+ {
99701
+ type: "button",
99702
+ onClick: p3.onRunAccessibilityCheck,
99703
+ className: pill,
99704
+ title: "Accessibility check",
99705
+ children: "Accessibility"
99706
+ }
99707
+ )
99708
+ ] })
98173
99709
  ]
98174
99710
  }
98175
99711
  )
@@ -105590,6 +107126,333 @@ function BroadcastDialog({
105590
107126
  ] }) })
105591
107127
  ] });
105592
107128
  }
107129
+ function getInitials2(name) {
107130
+ const parts = name.trim().split(/\s+/);
107131
+ if (parts.length >= 2) {
107132
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
107133
+ }
107134
+ return name.slice(0, 2).toUpperCase();
107135
+ }
107136
+ function ShareDialog({
107137
+ open,
107138
+ onClose,
107139
+ activeCollaboration,
107140
+ onStartCollaboration,
107141
+ onStopCollaboration,
107142
+ preconfigured,
107143
+ defaultRoomId,
107144
+ defaultUserName,
107145
+ defaultServerUrl
107146
+ }) {
107147
+ const collab = useCollaboration();
107148
+ const isActive = collab !== null && collab.status !== "disconnected" && collab.status !== "error";
107149
+ const { t: t2 } = reactI18next.useTranslation();
107150
+ const [roomId, setRoomId] = React10.useState(defaultRoomId ?? "");
107151
+ const [userName, setUserName] = React10.useState(defaultUserName ?? "");
107152
+ const [serverUrl, setServerUrl] = React10.useState(defaultServerUrl ?? "");
107153
+ const [copied, setCopied] = React10.useState(false);
107154
+ const dialogRef = React10.useRef(null);
107155
+ React10.useEffect(() => {
107156
+ if (activeCollaboration) {
107157
+ setRoomId(activeCollaboration.roomId);
107158
+ setUserName(activeCollaboration.userName);
107159
+ setServerUrl(activeCollaboration.serverUrl);
107160
+ }
107161
+ }, [activeCollaboration]);
107162
+ React10.useEffect(() => {
107163
+ if (!open) {
107164
+ return;
107165
+ }
107166
+ function handleKeyDown(e2) {
107167
+ if (e2.key === "Escape") {
107168
+ onClose();
107169
+ }
107170
+ }
107171
+ document.addEventListener("keydown", handleKeyDown);
107172
+ return () => document.removeEventListener("keydown", handleKeyDown);
107173
+ }, [open, onClose]);
107174
+ React10.useEffect(() => {
107175
+ if (open && dialogRef.current) {
107176
+ dialogRef.current.focus();
107177
+ }
107178
+ }, [open]);
107179
+ const handleCopyRoomId = React10.useCallback(() => {
107180
+ const config = activeCollaboration ?? { roomId, serverUrl };
107181
+ const shareUrl = typeof window !== "undefined" ? `${window.location.origin}${window.location.pathname}?room=${encodeURIComponent(config.roomId)}&server=${encodeURIComponent(config.serverUrl)}` : config.roomId;
107182
+ void navigator.clipboard.writeText(shareUrl).then(() => {
107183
+ setCopied(true);
107184
+ setTimeout(() => setCopied(false), 2e3);
107185
+ return void 0;
107186
+ });
107187
+ }, [activeCollaboration, roomId, serverUrl]);
107188
+ const handleStartSharing = React10.useCallback(() => {
107189
+ if (!roomId.trim() || !userName.trim()) {
107190
+ return;
107191
+ }
107192
+ onStartCollaboration?.({
107193
+ roomId: roomId.trim(),
107194
+ serverUrl: serverUrl.trim(),
107195
+ userName: userName.trim()
107196
+ });
107197
+ }, [roomId, userName, serverUrl, onStartCollaboration]);
107198
+ const canStart = roomId.trim().length > 0 && userName.trim().length > 0 && serverUrl.trim().length > 0;
107199
+ if (!open) {
107200
+ return null;
107201
+ }
107202
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107203
+ /* @__PURE__ */ jsxRuntime.jsx(
107204
+ "button",
107205
+ {
107206
+ type: "button",
107207
+ className: "fixed inset-0 z-[200] bg-black/50",
107208
+ "aria-label": t2("pptx.share.closeDialog"),
107209
+ onClick: onClose
107210
+ }
107211
+ ),
107212
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[201] flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs(
107213
+ "div",
107214
+ {
107215
+ ref: dialogRef,
107216
+ role: "dialog",
107217
+ "aria-modal": "true",
107218
+ "aria-label": t2("pptx.share.title"),
107219
+ tabIndex: -1,
107220
+ className: "pointer-events-auto w-full max-w-md rounded-xl border border-border bg-popover text-foreground shadow-2xl outline-none",
107221
+ children: [
107222
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-b border-border", children: [
107223
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold text-foreground", children: isActive ? t2("pptx.share.collaborationActive") : t2("pptx.share.title") }),
107224
+ /* @__PURE__ */ jsxRuntime.jsx(
107225
+ "button",
107226
+ {
107227
+ type: "button",
107228
+ onClick: onClose,
107229
+ className: "text-muted-foreground hover:text-foreground text-lg leading-none",
107230
+ "aria-label": t2("pptx.share.close"),
107231
+ children: "\xD7"
107232
+ }
107233
+ )
107234
+ ] }),
107235
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-5 py-4", children: isActive ? /* @__PURE__ */ jsxRuntime.jsx(
107236
+ ActiveSessionView,
107237
+ {
107238
+ collab,
107239
+ activeCollaboration,
107240
+ copied,
107241
+ onCopyRoomId: handleCopyRoomId,
107242
+ onStopCollaboration
107243
+ }
107244
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
107245
+ StartSessionForm,
107246
+ {
107247
+ roomId,
107248
+ userName,
107249
+ serverUrl,
107250
+ onRoomIdChange: setRoomId,
107251
+ onUserNameChange: setUserName,
107252
+ onServerUrlChange: setServerUrl,
107253
+ preconfigured
107254
+ }
107255
+ ) }),
107256
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2 px-5 py-3 border-t border-border", children: [
107257
+ /* @__PURE__ */ jsxRuntime.jsx(
107258
+ "button",
107259
+ {
107260
+ type: "button",
107261
+ onClick: onClose,
107262
+ className: "px-3 py-1.5 rounded bg-muted hover:bg-accent text-[12px] text-foreground transition-colors",
107263
+ children: isActive ? t2("pptx.share.close") : t2("pptx.share.cancel")
107264
+ }
107265
+ ),
107266
+ !isActive && /* @__PURE__ */ jsxRuntime.jsx(
107267
+ "button",
107268
+ {
107269
+ type: "button",
107270
+ disabled: !canStart,
107271
+ onClick: handleStartSharing,
107272
+ className: "px-3 py-1.5 rounded bg-primary hover:bg-primary/90 text-[12px] text-primary-foreground transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
107273
+ children: t2("pptx.share.startSharing")
107274
+ }
107275
+ )
107276
+ ] })
107277
+ ]
107278
+ }
107279
+ ) })
107280
+ ] });
107281
+ }
107282
+ function StartSessionForm({
107283
+ roomId,
107284
+ userName,
107285
+ serverUrl,
107286
+ onRoomIdChange,
107287
+ onUserNameChange,
107288
+ onServerUrlChange,
107289
+ preconfigured
107290
+ }) {
107291
+ const { t: t2 } = reactI18next.useTranslation();
107292
+ const inputReadOnlyClass = preconfigured ? " opacity-70 cursor-not-allowed" : "";
107293
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
107294
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[13px] text-muted-foreground leading-relaxed", children: preconfigured ? t2("pptx.share.preconfiguredDescription") : t2("pptx.share.description") }),
107295
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107296
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "share-room-id", className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.sessionName") }),
107297
+ /* @__PURE__ */ jsxRuntime.jsx(
107298
+ "input",
107299
+ {
107300
+ id: "share-room-id",
107301
+ type: "text",
107302
+ value: roomId,
107303
+ onChange: (e2) => onRoomIdChange(e2.target.value),
107304
+ readOnly: preconfigured,
107305
+ placeholder: t2("pptx.share.sessionPlaceholder"),
107306
+ className: `w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary${inputReadOnlyClass}`
107307
+ }
107308
+ ),
107309
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("pptx.share.sessionHint") })
107310
+ ] }),
107311
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107312
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "share-user-name", className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.displayName") }),
107313
+ /* @__PURE__ */ jsxRuntime.jsx(
107314
+ "input",
107315
+ {
107316
+ id: "share-user-name",
107317
+ type: "text",
107318
+ value: userName,
107319
+ onChange: (e2) => onUserNameChange(e2.target.value),
107320
+ readOnly: preconfigured,
107321
+ placeholder: t2("pptx.share.namePlaceholder"),
107322
+ className: `w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary${inputReadOnlyClass}`
107323
+ }
107324
+ )
107325
+ ] }),
107326
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107327
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "share-server-url", className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.serverLabel") }),
107328
+ /* @__PURE__ */ jsxRuntime.jsx(
107329
+ "input",
107330
+ {
107331
+ id: "share-server-url",
107332
+ type: "text",
107333
+ value: serverUrl,
107334
+ onChange: (e2) => onServerUrlChange(e2.target.value),
107335
+ readOnly: preconfigured,
107336
+ placeholder: t2("pptx.share.serverPlaceholder"),
107337
+ className: `w-full px-3 py-1.5 rounded border border-border bg-background text-foreground text-[13px] placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary${inputReadOnlyClass}`
107338
+ }
107339
+ )
107340
+ ] }),
107341
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[11px] text-muted-foreground/70 leading-relaxed", children: [
107342
+ "Run",
107343
+ " ",
107344
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "px-1 py-0.5 rounded bg-muted text-[10px] font-mono", children: "bun run collab" }),
107345
+ " ",
107346
+ "to start the server. Others can join at",
107347
+ " ",
107348
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "px-1 py-0.5 rounded bg-muted text-[10px] font-mono", children: "?room=SESSION_NAME" })
107349
+ ] })
107350
+ ] });
107351
+ }
107352
+ function ActiveSessionView({
107353
+ collab,
107354
+ activeCollaboration,
107355
+ copied,
107356
+ onCopyRoomId,
107357
+ onStopCollaboration
107358
+ }) {
107359
+ const { t: t2 } = reactI18next.useTranslation();
107360
+ const statusColor = collab.status === "connected" ? "text-green-400" : collab.status === "connecting" ? "text-yellow-400" : "text-red-400";
107361
+ const statusIcon = collab.status === "connected" || collab.status === "connecting" ? /* @__PURE__ */ jsxRuntime.jsx(lu.LuWifi, { className: "w-4 h-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lu.LuWifiOff, { className: "w-4 h-4" });
107362
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
107363
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
107364
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: statusColor, children: statusIcon }),
107365
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[13px] font-medium text-foreground capitalize", children: collab.status }),
107366
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[12px] text-muted-foreground ml-auto flex items-center gap-1", children: [
107367
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuUsers, { className: "w-3.5 h-3.5" }),
107368
+ t2("pptx.collaboration.userCount", { count: collab.connectedCount })
107369
+ ] })
107370
+ ] }),
107371
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107372
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.shareLink") }),
107373
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
107374
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 px-3 py-1.5 rounded border border-border bg-background text-[11px] text-foreground select-all font-mono truncate", children: typeof window !== "undefined" ? `${window.location.origin}${window.location.pathname}?room=${encodeURIComponent(collab.config.roomId)}&server=${encodeURIComponent(collab.config.serverUrl)}` : collab.config.roomId }),
107375
+ /* @__PURE__ */ jsxRuntime.jsx(
107376
+ "button",
107377
+ {
107378
+ type: "button",
107379
+ onClick: onCopyRoomId,
107380
+ className: "flex items-center gap-1 px-2.5 py-1.5 rounded border border-border bg-muted hover:bg-accent text-[12px] text-foreground transition-colors shrink-0",
107381
+ title: t2("pptx.share.copyLink"),
107382
+ children: copied ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107383
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCheck, { className: "w-3.5 h-3.5 text-green-400" }),
107384
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("pptx.share.copied") })
107385
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107386
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuCopy, { className: "w-3.5 h-3.5" }),
107387
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("pptx.share.copyUrl") })
107388
+ ] })
107389
+ }
107390
+ )
107391
+ ] }),
107392
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("pptx.share.shareHint") })
107393
+ ] }),
107394
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 text-[11px] text-muted-foreground", children: [
107395
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
107396
+ t2("pptx.share.room"),
107397
+ " ",
107398
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "font-mono text-foreground", children: collab.config.roomId })
107399
+ ] }),
107400
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
107401
+ t2("pptx.share.server"),
107402
+ " ",
107403
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "font-mono text-foreground", children: collab.config.serverUrl })
107404
+ ] })
107405
+ ] }),
107406
+ collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
107407
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.connectedUsers") }),
107408
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded border border-border bg-background divide-y divide-border max-h-[140px] overflow-y-auto", children: [
107409
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
107410
+ /* @__PURE__ */ jsxRuntime.jsx(
107411
+ "div",
107412
+ {
107413
+ className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
107414
+ style: { backgroundColor: collab.config.userColor ?? "#6366f1" },
107415
+ children: getInitials2(activeCollaboration?.userName ?? collab.config.userName)
107416
+ }
107417
+ ),
107418
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[12px] text-foreground truncate", children: activeCollaboration?.userName ?? collab.config.userName }),
107419
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground ml-auto", children: t2("pptx.share.you") })
107420
+ ] }),
107421
+ collab.remoteUsers.map((user) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
107422
+ /* @__PURE__ */ jsxRuntime.jsx(
107423
+ "div",
107424
+ {
107425
+ className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
107426
+ style: { backgroundColor: user.userColor },
107427
+ children: user.userAvatar ? /* @__PURE__ */ jsxRuntime.jsx(
107428
+ "img",
107429
+ {
107430
+ src: user.userAvatar,
107431
+ alt: "",
107432
+ className: "w-full h-full rounded-full object-cover"
107433
+ }
107434
+ ) : getInitials2(user.userName)
107435
+ }
107436
+ ),
107437
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[12px] text-foreground truncate", children: user.userName }),
107438
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-muted-foreground ml-auto", children: [
107439
+ "Slide ",
107440
+ user.activeSlideIndex + 1
107441
+ ] })
107442
+ ] }, user.clientId))
107443
+ ] })
107444
+ ] }),
107445
+ onStopCollaboration && /* @__PURE__ */ jsxRuntime.jsx(
107446
+ "button",
107447
+ {
107448
+ type: "button",
107449
+ onClick: onStopCollaboration,
107450
+ className: "w-full px-3 py-2 rounded border border-red-500/30 bg-red-500/10 hover:bg-red-500/20 text-[12px] text-red-400 font-medium transition-colors",
107451
+ children: t2("pptx.share.stopSharing")
107452
+ }
107453
+ )
107454
+ ] });
107455
+ }
105593
107456
 
105594
107457
  // src/viewer/components/hyperlink-edit-types.ts
105595
107458
  var ACTION_VERB_MAP = {
@@ -107490,7 +109353,14 @@ function ViewerBottomPanels({
107490
109353
  onUpdateNotes,
107491
109354
  collaborationSlot,
107492
109355
  notesPanelHeight,
107493
- onResizeBottom
109356
+ onResizeBottom,
109357
+ scale,
109358
+ onZoomIn,
109359
+ onZoomOut,
109360
+ onZoomToFit,
109361
+ mode,
109362
+ onSetMode,
109363
+ onToggleSlideSorter
107494
109364
  }) {
107495
109365
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107496
109366
  onResizeBottom && !isSlideNotesCollapsed && /* @__PURE__ */ jsxRuntime.jsx(ResizeHandle, { direction: "vertical", onResize: onResizeBottom }),
@@ -107506,18 +109376,25 @@ function ViewerBottomPanels({
107506
109376
  panelHeight: notesPanelHeight
107507
109377
  }
107508
109378
  ),
107509
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
107510
- /* @__PURE__ */ jsxRuntime.jsx(
107511
- StatusBar,
107512
- {
107513
- slideCount,
107514
- activeSlideIndex,
107515
- isDirty,
107516
- autosaveStatus
107517
- }
107518
- ),
107519
- collaborationSlot
107520
- ] })
109379
+ /* @__PURE__ */ jsxRuntime.jsx(
109380
+ StatusBar,
109381
+ {
109382
+ slideCount,
109383
+ activeSlideIndex,
109384
+ isDirty,
109385
+ autosaveStatus,
109386
+ scale,
109387
+ onZoomIn,
109388
+ onZoomOut,
109389
+ onZoomToFit,
109390
+ isNotesExpanded: !isSlideNotesCollapsed,
109391
+ onToggleNotes,
109392
+ mode,
109393
+ onSetMode,
109394
+ onToggleSlideSorter,
109395
+ collaborationSlot
109396
+ }
109397
+ )
107521
109398
  ] });
107522
109399
  }
107523
109400
  function ViewerInspector({
@@ -107676,7 +109553,9 @@ function ViewerToolbarSection(props) {
107676
109553
  ops,
107677
109554
  onSetMode,
107678
109555
  onEnterPresenterView,
107679
- onEnterRehearsalMode
109556
+ onEnterRehearsalMode,
109557
+ onOpenSettings,
109558
+ onOpenShareDialog
107680
109559
  } = props;
107681
109560
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107682
109561
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -107755,12 +109634,14 @@ function ViewerToolbarSection(props) {
107755
109634
  onExportVideo: exportHandlers.handleExportVideo,
107756
109635
  onExportGif: exportHandlers.handleExportGif,
107757
109636
  onPackageForSharing: exportHandlers.handlePackageForSharing,
109637
+ onOpenShareDialog,
107758
109638
  onSaveAsPpsx: exportHandlers.handleSaveAsPpsx,
107759
109639
  onSaveAsPptm: exportHandlers.handleSaveAsPptm,
107760
109640
  hasMacros: s.hasMacros,
107761
109641
  onCopySlideAsImage: exportHandlers.handleCopySlideAsImage,
107762
109642
  onPrint: printHandlers.handlePrint,
107763
109643
  onToggleShortcuts: () => s.setIsShortcutHelpOpen((p3) => !p3),
109644
+ onOpenSettings,
107764
109645
  onRunAccessibilityCheck: dialogs.handleRunAccessibilityCheck,
107765
109646
  onToggleSlideSorter: () => s.setShowSlideSorter((p3) => !p3),
107766
109647
  onUpdateTextStyle: ops.updateSelectedTextStyle,
@@ -107790,7 +109671,12 @@ function ViewerToolbarSection(props) {
107790
109671
  onToggleThemeGallery: () => s.setIsThemeGalleryOpen((p3) => !p3),
107791
109672
  isThemeGalleryOpen: s.isThemeGalleryOpen,
107792
109673
  onCompare: propertyHandlers.handleCompare,
107793
- onToggleComments: () => s.setIsInspectorPaneOpen((p3) => !p3),
109674
+ onToggleComments: () => {
109675
+ s.setSidebarPanelMode("comments");
109676
+ if (!s.isInspectorPaneOpen) {
109677
+ s.setIsInspectorPaneOpen(true);
109678
+ }
109679
+ },
107794
109680
  isCommentsPanelOpen: s.isInspectorPaneOpen,
107795
109681
  slideCommentCount: activeSlide?.comments?.length ?? 0,
107796
109682
  formatPainterActive: s.formatPainterActive,
@@ -109537,7 +111423,7 @@ function ViewerCanvasArea(props) {
109537
111423
  const handleToolbarMouseLeave = React10.useCallback(() => {
109538
111424
  toolbarHoveringRef.current = false;
109539
111425
  }, []);
109540
- return /* @__PURE__ */ jsxRuntime.jsxs("main", { "aria-label": "Slide editor", className: "flex-1 min-w-0 relative flex flex-col", children: [
111426
+ return /* @__PURE__ */ jsxRuntime.jsxs("main", { "aria-label": "Slide editor", className: "flex-1 min-w-0 relative flex flex-col bg-background", children: [
109541
111427
  findReplace.findReplaceOpen && /* @__PURE__ */ jsxRuntime.jsx(
109542
111428
  FindReplacePanel,
109543
111429
  {
@@ -109907,7 +111793,7 @@ var PRESET_THEMES = [
109907
111793
  minorFont: "Gill Sans MT"
109908
111794
  }
109909
111795
  ];
109910
- var COMMON_FONTS = [
111796
+ var COMMON_FONTS2 = [
109911
111797
  "Arial",
109912
111798
  "Calibri",
109913
111799
  "Calibri Light",
@@ -110012,15 +111898,15 @@ function ThemeColorSchemeEditor({
110012
111898
  {
110013
111899
  className: THEME_EDITOR_INPUT,
110014
111900
  disabled: !canEdit,
110015
- value: COMMON_FONTS.includes(majorFont) ? majorFont : "__custom__",
111901
+ value: COMMON_FONTS2.includes(majorFont) ? majorFont : "__custom__",
110016
111902
  onChange: (e2) => {
110017
111903
  if (e2.target.value !== "__custom__") {
110018
111904
  onMajorFontChange(e2.target.value);
110019
111905
  }
110020
111906
  },
110021
111907
  children: [
110022
- COMMON_FONTS.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f, children: f }, f)),
110023
- !COMMON_FONTS.includes(majorFont) && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "__custom__", children: majorFont })
111908
+ COMMON_FONTS2.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f, children: f }, f)),
111909
+ !COMMON_FONTS2.includes(majorFont) && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "__custom__", children: majorFont })
110024
111910
  ]
110025
111911
  }
110026
111912
  )
@@ -110032,15 +111918,15 @@ function ThemeColorSchemeEditor({
110032
111918
  {
110033
111919
  className: THEME_EDITOR_INPUT,
110034
111920
  disabled: !canEdit,
110035
- value: COMMON_FONTS.includes(minorFont) ? minorFont : "__custom__",
111921
+ value: COMMON_FONTS2.includes(minorFont) ? minorFont : "__custom__",
110036
111922
  onChange: (e2) => {
110037
111923
  if (e2.target.value !== "__custom__") {
110038
111924
  onMinorFontChange(e2.target.value);
110039
111925
  }
110040
111926
  },
110041
111927
  children: [
110042
- COMMON_FONTS.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f, children: f }, f)),
110043
- !COMMON_FONTS.includes(minorFont) && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "__custom__", children: minorFont })
111928
+ COMMON_FONTS2.map((f) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: f, children: f }, f)),
111929
+ !COMMON_FONTS2.includes(minorFont) && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "__custom__", children: minorFont })
110044
111930
  ]
110045
111931
  }
110046
111932
  )
@@ -110906,486 +112792,334 @@ function ViewerMainContent(props) {
110906
112792
  activeSlideIndex,
110907
112793
  selectedElement,
110908
112794
  state: state2,
110909
- comments,
110910
- ops,
110911
- manipulation,
110912
- propertyHandlers,
110913
- themeHandlers,
110914
- history,
110915
- panelWidth: rightPanelWidth,
110916
- onResizeRight
110917
- }
110918
- )
110919
- ] });
110920
- }
110921
-
110922
- // src/viewer/hooks/collaboration/sanitize.ts
110923
- var ROOM_ID_REGEX = /^[a-zA-Z0-9_-]{1,128}$/;
110924
- function validateRoomId(roomId) {
110925
- if (!ROOM_ID_REGEX.test(roomId)) {
110926
- throw new Error(
110927
- `Invalid collaboration room ID: "${roomId}". Must be 1-128 alphanumeric characters, hyphens, or underscores.`
110928
- );
110929
- }
110930
- return roomId;
110931
- }
110932
- function sanitizeUserName(name) {
110933
- if (typeof name !== "string") {
110934
- return "Anonymous";
110935
- }
110936
- const stripped = name.replace(/<[^>]*>/g, "");
110937
- const trimmed = stripped.trim().slice(0, 64);
110938
- return trimmed || "Anonymous";
110939
- }
110940
- function clampCursorPosition(value, min2, max2) {
110941
- if (typeof value !== "number" || !Number.isFinite(value)) {
110942
- return 0;
110943
- }
110944
- const margin = 20;
110945
- return Math.max(min2 - margin, Math.min(max2 + margin, value));
110946
- }
110947
- var HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
110948
- function sanitizeColor(color, fallback = "#6366f1") {
110949
- if (typeof color !== "string") {
110950
- return fallback;
110951
- }
110952
- return HEX_COLOR_REGEX.test(color) ? color : fallback;
110953
- }
110954
- function sanitizeAvatarUrl(url) {
110955
- if (typeof url !== "string") {
110956
- return void 0;
110957
- }
110958
- try {
110959
- const parsed = new URL(url);
110960
- if (parsed.protocol === "https:" || parsed.protocol === "http:" || parsed.protocol === "data:") {
110961
- return url;
110962
- }
110963
- } catch {
110964
- }
110965
- return void 0;
110966
- }
110967
- function sanitizeSlideIndex(value) {
110968
- if (typeof value !== "number" || !Number.isFinite(value)) {
110969
- return 0;
110970
- }
110971
- return Math.max(0, Math.floor(value));
110972
- }
110973
- function sanitizePresence(raw, canvasWidth, canvasHeight) {
110974
- if (typeof raw.clientId !== "number") {
110975
- return null;
110976
- }
110977
- return {
110978
- clientId: raw.clientId,
110979
- userName: sanitizeUserName(raw.userName),
110980
- userAvatar: sanitizeAvatarUrl(raw.userAvatar),
110981
- userColor: sanitizeColor(raw.userColor),
110982
- activeSlideIndex: sanitizeSlideIndex(raw.activeSlideIndex),
110983
- cursorX: clampCursorPosition(raw.cursorX, 0, canvasWidth),
110984
- cursorY: clampCursorPosition(raw.cursorY, 0, canvasHeight),
110985
- lastUpdated: typeof raw.lastUpdated === "string" ? raw.lastUpdated : (/* @__PURE__ */ new Date()).toISOString(),
110986
- selectedElementId: typeof raw.selectedElementId === "string" ? raw.selectedElementId.slice(0, 128) : void 0
110987
- };
110988
- }
110989
- var BROADCAST_THROTTLE_MS = 50;
110990
- var STALE_PRESENCE_MS = 3e4;
110991
- function usePresenceTracking({
110992
- awareness,
110993
- localClientId,
110994
- userName,
110995
- userColor,
110996
- userAvatar,
110997
- canvasWidth,
110998
- canvasHeight
110999
- }) {
111000
- const [remoteUsers, setRemoteUsers] = React10.useState([]);
111001
- const lastBroadcastRef = React10.useRef(0);
111002
- const pendingBroadcastRef = React10.useRef(null);
111003
- const latestLocalState = React10.useRef({});
111004
- const broadcastPresence = React10.useCallback(
111005
- (update2) => {
111006
- if (!awareness) {
111007
- return;
111008
- }
111009
- Object.assign(latestLocalState.current, update2);
111010
- const now = Date.now();
111011
- const elapsed = now - lastBroadcastRef.current;
111012
- const flush = () => {
111013
- const state2 = {
111014
- ...latestLocalState.current,
111015
- userName,
111016
- userColor,
111017
- userAvatar,
111018
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
111019
- };
111020
- awareness.setLocalStateField("presence", state2);
111021
- lastBroadcastRef.current = Date.now();
111022
- };
111023
- if (elapsed >= BROADCAST_THROTTLE_MS) {
111024
- if (pendingBroadcastRef.current) {
111025
- clearTimeout(pendingBroadcastRef.current);
111026
- pendingBroadcastRef.current = null;
111027
- }
111028
- flush();
111029
- } else if (!pendingBroadcastRef.current) {
111030
- pendingBroadcastRef.current = setTimeout(() => {
111031
- pendingBroadcastRef.current = null;
111032
- flush();
111033
- }, BROADCAST_THROTTLE_MS - elapsed);
111034
- }
111035
- },
111036
- [awareness, userName, userColor, userAvatar]
111037
- );
111038
- React10.useEffect(() => {
111039
- if (!awareness || localClientId === null) {
111040
- return;
111041
- }
111042
- const handleChange = () => {
111043
- const now = Date.now();
111044
- const states = awareness.getStates();
111045
- const users = [];
111046
- states.forEach((state2, cid) => {
111047
- if (cid === localClientId) {
111048
- return;
111049
- }
111050
- const raw = state2?.presence;
111051
- if (!raw || typeof raw !== "object") {
111052
- return;
111053
- }
111054
- const sanitized = sanitizePresence({ ...raw, clientId: cid }, canvasWidth, canvasHeight);
111055
- if (!sanitized) {
111056
- return;
111057
- }
111058
- const updatedAt = new Date(sanitized.lastUpdated).getTime();
111059
- if (Number.isNaN(updatedAt) || now - updatedAt > STALE_PRESENCE_MS) {
111060
- return;
111061
- }
111062
- users.push(sanitized);
111063
- });
111064
- setRemoteUsers(users);
111065
- };
111066
- awareness.on("change", handleChange);
111067
- handleChange();
111068
- return () => {
111069
- awareness.off("change", handleChange);
111070
- };
111071
- }, [awareness, localClientId, canvasWidth, canvasHeight]);
111072
- React10.useEffect(() => {
111073
- return () => {
111074
- if (pendingBroadcastRef.current) {
111075
- clearTimeout(pendingBroadcastRef.current);
111076
- }
111077
- };
111078
- }, []);
111079
- return { remoteUsers, broadcastPresence };
111080
- }
111081
- function useYjsProvider({ config }) {
111082
- const [status, setStatus] = React10.useState("disconnected");
111083
- const [awareness, setAwareness] = React10.useState(null);
111084
- const [doc2, setDoc] = React10.useState(null);
111085
- const [clientId, setClientId] = React10.useState(null);
111086
- const cleanupRef = React10.useRef(null);
111087
- const init = React10.useCallback(async () => {
111088
- const roomId = validateRoomId(config.roomId);
111089
- setStatus("connecting");
111090
- try {
111091
- const [Y, { WebsocketProvider: WebsocketProvider2 }] = await Promise.all([Promise.resolve().then(() => (init_yjs(), yjs_exports)), Promise.resolve().then(() => (init_y_websocket(), y_websocket_exports))]);
111092
- const yDoc = new Y.Doc();
111093
- const provider = new WebsocketProvider2(
111094
- config.serverUrl,
111095
- roomId,
111096
- yDoc,
111097
- // eslint-disable-line @typescript-eslint/no-explicit-any
111098
- {
111099
- params: config.authToken ? { token: config.authToken } : void 0
111100
- }
111101
- );
111102
- const handleStatus = (event) => {
111103
- if (event.status === "connected") {
111104
- setStatus("connected");
111105
- } else if (event.status === "disconnected") {
111106
- setStatus("disconnected");
111107
- }
111108
- };
111109
- provider.on("status", handleStatus);
111110
- if (provider.wsconnected) {
111111
- setStatus("connected");
112795
+ comments,
112796
+ ops,
112797
+ manipulation,
112798
+ propertyHandlers,
112799
+ themeHandlers,
112800
+ history,
112801
+ panelWidth: rightPanelWidth,
112802
+ onResizeRight
111112
112803
  }
111113
- setDoc(yDoc);
111114
- setAwareness(provider.awareness);
111115
- setClientId(provider.awareness.clientID);
111116
- cleanupRef.current = () => {
111117
- provider.off("status", handleStatus);
111118
- provider.destroy();
111119
- yDoc.destroy();
111120
- setDoc(null);
111121
- setAwareness(null);
111122
- setClientId(null);
111123
- setStatus("disconnected");
111124
- };
111125
- } catch (err) {
111126
- console.warn(
111127
- "[pptx-viewer] Collaboration packages not available:",
111128
- err instanceof Error ? err.message : err
111129
- );
111130
- setStatus("error");
111131
- }
111132
- }, [config.roomId, config.serverUrl, config.authToken]);
111133
- React10.useEffect(() => {
111134
- init();
111135
- return () => {
111136
- cleanupRef.current?.();
111137
- cleanupRef.current = null;
111138
- };
111139
- }, [init]);
111140
- return { status, awareness, doc: doc2, clientId };
111141
- }
111142
-
111143
- // src/viewer/hooks/collaboration/useCollaborativeState.ts
111144
- function useCollaborativeState({
111145
- config,
111146
- canvasWidth,
111147
- canvasHeight
111148
- }) {
111149
- const userColor = sanitizeColor(config.userColor, "#6366f1");
111150
- const { status, awareness, clientId } = useYjsProvider({ config });
111151
- const { remoteUsers, broadcastPresence } = usePresenceTracking({
111152
- awareness,
111153
- localClientId: clientId,
111154
- userName: config.userName,
111155
- userColor,
111156
- userAvatar: config.userAvatar,
111157
- canvasWidth,
111158
- canvasHeight
111159
- });
111160
- const connectedCount = status === "connected" ? remoteUsers.length + 1 : remoteUsers.length;
111161
- return {
111162
- status,
111163
- remoteUsers,
111164
- broadcastPresence,
111165
- connectedCount,
111166
- config
111167
- };
111168
- }
111169
- var CollaborationContext = React10.createContext(null);
111170
- function useCollaboration() {
111171
- return React10.useContext(CollaborationContext);
112804
+ )
112805
+ ] });
111172
112806
  }
111173
- function CollaborationProvider({
111174
- config,
111175
- canvasWidth,
111176
- canvasHeight,
111177
- children
112807
+ function ToggleSwitch({
112808
+ label,
112809
+ enabled,
112810
+ onToggle
111178
112811
  }) {
111179
- const value = useCollaborativeState({
111180
- config,
111181
- canvasWidth,
111182
- canvasHeight
111183
- });
111184
- return /* @__PURE__ */ jsxRuntime.jsx(CollaborationContext.Provider, { value, children });
112812
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between py-2.5 px-3", children: [
112813
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-foreground", children: label }),
112814
+ /* @__PURE__ */ jsxRuntime.jsx(
112815
+ "button",
112816
+ {
112817
+ type: "button",
112818
+ onClick: onToggle,
112819
+ className: cn(
112820
+ "relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
112821
+ enabled ? "bg-primary" : "bg-muted-foreground/30"
112822
+ ),
112823
+ role: "switch",
112824
+ "aria-checked": enabled,
112825
+ "aria-label": label,
112826
+ children: /* @__PURE__ */ jsxRuntime.jsx(
112827
+ "span",
112828
+ {
112829
+ className: cn(
112830
+ "inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
112831
+ enabled ? "translate-x-[18px]" : "translate-x-[3px]"
112832
+ )
112833
+ }
112834
+ )
112835
+ }
112836
+ )
112837
+ ] });
111185
112838
  }
111186
- function RemoteUserCursors({
111187
- remoteUsers,
111188
- activeSlideIndex,
111189
- canvasWidth,
111190
- canvasHeight
112839
+ function SettingsDialog({
112840
+ isOpen,
112841
+ onClose,
112842
+ spellCheckEnabled = false,
112843
+ onSetSpellCheckEnabled,
112844
+ showGrid = false,
112845
+ onSetShowGrid,
112846
+ showRulers = false,
112847
+ onSetShowRulers,
112848
+ snapToGrid = false,
112849
+ onSetSnapToGrid,
112850
+ reducedMotion = false,
112851
+ onToggleReducedMotion
111191
112852
  }) {
111192
- const visibleUsers = remoteUsers.filter((u2) => u2.activeSlideIndex === activeSlideIndex);
111193
- if (visibleUsers.length === 0) {
111194
- return null;
111195
- }
111196
- return /* @__PURE__ */ jsxRuntime.jsx(
111197
- "svg",
111198
- {
111199
- "data-testid": "remote-user-cursors",
111200
- className: "absolute inset-0 pointer-events-none",
111201
- style: { zIndex: 9999 },
111202
- width: canvasWidth,
111203
- height: canvasHeight,
111204
- viewBox: `0 0 ${canvasWidth} ${canvasHeight}`,
111205
- "aria-hidden": "true",
111206
- children: visibleUsers.map((user) => /* @__PURE__ */ jsxRuntime.jsxs(
111207
- "g",
111208
- {
111209
- transform: `translate(${user.cursorX}, ${user.cursorY})`,
111210
- "data-testid": `remote-cursor-${user.clientId}`,
111211
- children: [
111212
- /* @__PURE__ */ jsxRuntime.jsx(
111213
- "path",
111214
- {
111215
- d: "M0 0 L0 16 L4.5 12.5 L8 20 L10.5 19 L7 11.5 L12 11 Z",
111216
- fill: user.userColor,
111217
- stroke: "#fff",
111218
- strokeWidth: 1,
111219
- opacity: 0.9
111220
- }
111221
- ),
111222
- /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: "translate(14, 18)", children: [
111223
- /* @__PURE__ */ jsxRuntime.jsx(
111224
- "rect",
111225
- {
111226
- rx: 3,
111227
- ry: 3,
111228
- x: -2,
111229
- y: -10,
111230
- width: Math.min(user.userName.length * 7 + 8, 150),
111231
- height: 16,
111232
- fill: user.userColor,
111233
- opacity: 0.85
111234
- }
111235
- ),
111236
- /* @__PURE__ */ jsxRuntime.jsx(
111237
- "text",
111238
- {
111239
- fill: "#fff",
111240
- fontSize: 10,
111241
- fontFamily: "system-ui, sans-serif",
111242
- fontWeight: 500,
111243
- dominantBaseline: "central",
111244
- y: -2,
111245
- x: 2,
111246
- children: user.userName.length > 20 ? `${user.userName.slice(0, 18)}...` : user.userName
111247
- }
111248
- )
111249
- ] })
111250
- ]
111251
- },
111252
- user.clientId
111253
- ))
111254
- }
112853
+ const [activeTab, setActiveTab] = React10.useState("general");
112854
+ const [autoSave, setAutoSave] = React10.useState(true);
112855
+ const { t: t2 } = reactI18next.useTranslation();
112856
+ const handleKeyDown = React10.useCallback(
112857
+ (e2) => {
112858
+ if (e2.key === "Escape") {
112859
+ onClose();
112860
+ }
112861
+ },
112862
+ [onClose]
111255
112863
  );
111256
- }
111257
- function getInitials(name) {
111258
- const parts = name.trim().split(/\s+/);
111259
- if (parts.length >= 2) {
111260
- return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
111261
- }
111262
- return name.slice(0, 2).toUpperCase();
111263
- }
111264
- function AvatarCircle({
111265
- name,
111266
- color,
111267
- avatar,
111268
- isLocal
111269
- }) {
111270
- const initials = getInitials(name);
111271
- const title = isLocal ? `${name} (you)` : name;
111272
- return /* @__PURE__ */ jsxRuntime.jsx(
111273
- "div",
111274
- {
111275
- className: "relative w-7 h-7 rounded-full flex items-center justify-center text-[10px] font-semibold text-white border-2 -ml-1 first:ml-0",
111276
- style: {
111277
- backgroundColor: color,
111278
- borderColor: isLocal ? "#fff" : color
111279
- },
111280
- title,
111281
- "aria-label": title,
111282
- children: avatar ? /* @__PURE__ */ jsxRuntime.jsx(
111283
- "img",
111284
- {
111285
- src: avatar,
111286
- alt: "",
111287
- className: "w-full h-full rounded-full object-cover",
111288
- onError: (e2) => {
111289
- e2.target.style.display = "none";
111290
- }
111291
- }
111292
- ) : initials
112864
+ React10.useEffect(() => {
112865
+ if (isOpen) {
112866
+ document.addEventListener("keydown", handleKeyDown);
112867
+ return () => document.removeEventListener("keydown", handleKeyDown);
111293
112868
  }
111294
- );
111295
- }
111296
- function UserAvatarBar({
111297
- remoteUsers,
111298
- localUserName,
111299
- localUserColor,
111300
- localUserAvatar,
111301
- status,
111302
- maxVisible = 5
111303
- }) {
111304
- if (status === "disconnected" || status === "error") {
112869
+ }, [isOpen, handleKeyDown]);
112870
+ if (!isOpen) {
111305
112871
  return null;
111306
112872
  }
111307
- const allUsers = [
111308
- { name: localUserName, color: localUserColor, avatar: localUserAvatar, isLocal: true },
111309
- ...remoteUsers.map((u2) => ({
111310
- name: u2.userName,
111311
- color: u2.userColor,
111312
- avatar: u2.userAvatar,
111313
- isLocal: false
111314
- }))
112873
+ const tabs = [
112874
+ { id: "general", label: t2("pptx.settings.general") },
112875
+ { id: "shortcuts", label: t2("pptx.settings.keyboardShortcuts") }
111315
112876
  ];
111316
- const visible = allUsers.slice(0, maxVisible);
111317
- const overflow = allUsers.length - maxVisible;
111318
- return /* @__PURE__ */ jsxRuntime.jsxs(
111319
- "div",
111320
- {
111321
- "data-testid": "user-avatar-bar",
111322
- className: "flex items-center px-2",
111323
- "aria-label": `${allUsers.length} user${allUsers.length !== 1 ? "s" : ""} connected`,
111324
- children: [
111325
- visible.map((user, i3) => /* @__PURE__ */ jsxRuntime.jsx(
111326
- AvatarCircle,
112877
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
112878
+ /* @__PURE__ */ jsxRuntime.jsx(
112879
+ "button",
112880
+ {
112881
+ type: "button",
112882
+ className: "fixed inset-0 z-50 bg-black/60",
112883
+ "aria-label": t2("pptx.settings.closeSettings"),
112884
+ onClick: onClose
112885
+ }
112886
+ ),
112887
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pointer-events-auto w-[min(32rem,calc(100%-2rem))] rounded-xl border border-border bg-popover backdrop-blur-xl shadow-2xl", children: [
112888
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b border-border/60", children: [
112889
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
112890
+ /* @__PURE__ */ jsxRuntime.jsx(lu.LuSettings, { className: "w-5 h-5 text-primary" }),
112891
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold text-foreground", children: t2("pptx.settings.title") })
112892
+ ] }),
112893
+ /* @__PURE__ */ jsxRuntime.jsx(
112894
+ "button",
111327
112895
  {
111328
- name: user.name,
111329
- color: user.color,
111330
- avatar: user.avatar,
111331
- isLocal: user.isLocal
111332
- },
111333
- user.isLocal ? "local" : `remote-${i3}`
111334
- )),
111335
- overflow > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
112896
+ type: "button",
112897
+ onClick: onClose,
112898
+ className: "p-1 rounded hover:bg-accent transition-colors",
112899
+ "aria-label": t2("pptx.settings.close"),
112900
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuX, { className: "w-4 h-4 text-muted-foreground" })
112901
+ }
112902
+ )
112903
+ ] }),
112904
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex border-b border-border/60 px-5", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsxs(
112905
+ "button",
112906
+ {
112907
+ type: "button",
112908
+ onClick: () => setActiveTab(tab.id),
112909
+ className: cn(
112910
+ "px-3 py-2 text-xs font-medium transition-colors relative",
112911
+ activeTab === tab.id ? "text-primary" : "text-muted-foreground hover:text-foreground"
112912
+ ),
112913
+ children: [
112914
+ tab.label,
112915
+ activeTab === tab.id && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute bottom-0 left-0 right-0 h-0.5 bg-primary rounded-full" })
112916
+ ]
112917
+ },
112918
+ tab.id
112919
+ )) }),
112920
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-5 py-4 max-h-[60vh] overflow-y-auto", children: [
112921
+ activeTab === "general" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
112922
+ /* @__PURE__ */ jsxRuntime.jsx(
112923
+ ToggleSwitch,
112924
+ {
112925
+ label: t2("pptx.settings.autoSave"),
112926
+ enabled: autoSave,
112927
+ onToggle: () => setAutoSave(!autoSave)
112928
+ }
112929
+ ),
112930
+ /* @__PURE__ */ jsxRuntime.jsx(
112931
+ ToggleSwitch,
112932
+ {
112933
+ label: t2("pptx.settings.spellCheck"),
112934
+ enabled: spellCheckEnabled,
112935
+ onToggle: () => onSetSpellCheckEnabled?.(!spellCheckEnabled)
112936
+ }
112937
+ ),
112938
+ /* @__PURE__ */ jsxRuntime.jsx(
112939
+ ToggleSwitch,
112940
+ {
112941
+ label: t2("pptx.settings.showGrid"),
112942
+ enabled: showGrid,
112943
+ onToggle: () => onSetShowGrid?.(!showGrid)
112944
+ }
112945
+ ),
112946
+ /* @__PURE__ */ jsxRuntime.jsx(
112947
+ ToggleSwitch,
112948
+ {
112949
+ label: t2("pptx.settings.showRulers"),
112950
+ enabled: showRulers,
112951
+ onToggle: () => onSetShowRulers?.(!showRulers)
112952
+ }
112953
+ ),
112954
+ /* @__PURE__ */ jsxRuntime.jsx(
112955
+ ToggleSwitch,
112956
+ {
112957
+ label: t2("pptx.settings.snapToGrid"),
112958
+ enabled: snapToGrid,
112959
+ onToggle: () => onSetSnapToGrid?.(!snapToGrid)
112960
+ }
112961
+ ),
112962
+ /* @__PURE__ */ jsxRuntime.jsx(
112963
+ ToggleSwitch,
112964
+ {
112965
+ label: t2("pptx.settings.reducedMotion"),
112966
+ enabled: reducedMotion,
112967
+ onToggle: () => onToggleReducedMotion?.()
112968
+ }
112969
+ )
112970
+ ] }),
112971
+ activeTab === "shortcuts" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-0.5", children: SHORTCUT_REFERENCE_ITEMS.map((shortcut, i3) => /* @__PURE__ */ jsxRuntime.jsxs(
111336
112972
  "div",
111337
112973
  {
111338
- className: "w-7 h-7 rounded-full flex items-center justify-center text-[10px] font-semibold text-gray-300 bg-gray-700 border-2 border-gray-600 -ml-1",
111339
- title: `${overflow} more user${overflow !== 1 ? "s" : ""}`,
112974
+ className: cn(
112975
+ "flex items-center justify-between gap-3 rounded px-3 py-2",
112976
+ i3 % 2 === 0 ? "bg-muted/60" : ""
112977
+ ),
111340
112978
  children: [
111341
- "+",
111342
- overflow
112979
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-foreground", children: shortcut.action }),
112980
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-[11px] text-muted-foreground whitespace-nowrap", children: shortcut.shortcut })
111343
112981
  ]
111344
- }
111345
- )
111346
- ]
112982
+ },
112983
+ shortcut.action
112984
+ )) })
112985
+ ] })
112986
+ ] }) })
112987
+ ] });
112988
+ }
112989
+ function useCollaborativeHistory({
112990
+ localClientId: _localClientId,
112991
+ handleUndo,
112992
+ handleRedo,
112993
+ canUndo,
112994
+ canRedo
112995
+ }) {
112996
+ const localChangeCountRef = React10.useRef(0);
112997
+ const wrappedUndo = React10.useCallback(() => {
112998
+ if (!canUndo) {
112999
+ return;
111347
113000
  }
111348
- );
113001
+ handleUndo();
113002
+ localChangeCountRef.current = Math.max(0, localChangeCountRef.current - 1);
113003
+ }, [handleUndo, canUndo]);
113004
+ const wrappedRedo = React10.useCallback(() => {
113005
+ if (!canRedo) {
113006
+ return;
113007
+ }
113008
+ handleRedo();
113009
+ localChangeCountRef.current += 1;
113010
+ }, [handleRedo, canRedo]);
113011
+ return {
113012
+ handleUndo: wrappedUndo,
113013
+ handleRedo: wrappedRedo,
113014
+ canUndo,
113015
+ canRedo
113016
+ };
111349
113017
  }
111350
- var STATUS_STYLES = {
111351
- connected: {
111352
- dot: "bg-green-400",
111353
- text: "text-green-400",
111354
- label: "Connected"
111355
- },
111356
- connecting: {
111357
- dot: "bg-yellow-400 animate-pulse",
111358
- text: "text-yellow-400",
111359
- label: "Connecting..."
111360
- },
111361
- disconnected: {
111362
- dot: "bg-gray-500",
111363
- text: "text-gray-500",
111364
- label: "Disconnected"
111365
- },
111366
- error: {
111367
- dot: "bg-red-400",
111368
- text: "text-red-400",
111369
- label: "Connection error"
111370
- }
111371
- };
111372
- function CollaborationStatusIndicator({
111373
- status,
111374
- connectedCount
113018
+ function useYjsDocumentSync({
113019
+ doc: doc2,
113020
+ slides,
113021
+ setSlides,
113022
+ isConnected
111375
113023
  }) {
111376
- const style = STATUS_STYLES[status];
111377
- return /* @__PURE__ */ jsxRuntime.jsxs(
111378
- "div",
111379
- {
111380
- "data-testid": "collaboration-status",
111381
- className: "flex items-center gap-1.5",
111382
- "aria-label": `Collaboration: ${style.label}. ${connectedCount} user${connectedCount !== 1 ? "s" : ""} connected.`,
111383
- children: [
111384
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `inline-block w-2 h-2 rounded-full ${style.dot}`, "aria-hidden": "true" }),
111385
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-[10px] ${style.text}`, children: status === "connected" ? `${connectedCount} user${connectedCount !== 1 ? "s" : ""}` : style.label })
111386
- ]
113024
+ const isApplyingRemoteRef = React10.useRef(false);
113025
+ const lastSyncedRef = React10.useRef("");
113026
+ const hasInitializedRef = React10.useRef(false);
113027
+ const getDocMap = React10.useCallback(() => {
113028
+ if (!doc2 || typeof doc2 !== "object") {
113029
+ return null;
111387
113030
  }
111388
- );
113031
+ const d = doc2;
113032
+ if (typeof d.getMap !== "function") {
113033
+ return null;
113034
+ }
113035
+ return d.getMap("slides-data");
113036
+ }, [doc2]);
113037
+ React10.useEffect(() => {
113038
+ if (!isConnected || !doc2) {
113039
+ return;
113040
+ }
113041
+ if (isApplyingRemoteRef.current) {
113042
+ return;
113043
+ }
113044
+ if (slides.length === 0) {
113045
+ return;
113046
+ }
113047
+ const map3 = getDocMap();
113048
+ if (!map3) {
113049
+ return;
113050
+ }
113051
+ const serialized = JSON.stringify(slides);
113052
+ if (serialized === lastSyncedRef.current) {
113053
+ return;
113054
+ }
113055
+ lastSyncedRef.current = serialized;
113056
+ const d = doc2;
113057
+ d.transact(() => {
113058
+ const currentCount = map3.get("count");
113059
+ if (currentCount && currentCount > slides.length) {
113060
+ for (let i3 = slides.length; i3 < currentCount; i3++) {
113061
+ map3.delete(`slide-${i3}`);
113062
+ }
113063
+ }
113064
+ map3.set("count", slides.length);
113065
+ for (let i3 = 0; i3 < slides.length; i3++) {
113066
+ const slideJson = JSON.stringify(slides[i3]);
113067
+ const existing = map3.get(`slide-${i3}`);
113068
+ if (existing !== slideJson) {
113069
+ map3.set(`slide-${i3}`, slideJson);
113070
+ }
113071
+ }
113072
+ });
113073
+ }, [doc2, slides, isConnected, getDocMap]);
113074
+ React10.useEffect(() => {
113075
+ if (!isConnected || !doc2) {
113076
+ return;
113077
+ }
113078
+ const map3 = getDocMap();
113079
+ if (!map3) {
113080
+ return;
113081
+ }
113082
+ const handleUpdate = () => {
113083
+ const count = map3.get("count");
113084
+ if (!count || count === 0) {
113085
+ return;
113086
+ }
113087
+ const remoteSlides = [];
113088
+ for (let i3 = 0; i3 < count; i3++) {
113089
+ const slideJson = map3.get(`slide-${i3}`);
113090
+ if (slideJson) {
113091
+ try {
113092
+ remoteSlides.push(JSON.parse(slideJson));
113093
+ } catch {
113094
+ }
113095
+ }
113096
+ }
113097
+ if (remoteSlides.length === 0) {
113098
+ return;
113099
+ }
113100
+ const serialized = JSON.stringify(remoteSlides);
113101
+ if (serialized === lastSyncedRef.current) {
113102
+ return;
113103
+ }
113104
+ lastSyncedRef.current = serialized;
113105
+ isApplyingRemoteRef.current = true;
113106
+ setSlides(remoteSlides);
113107
+ requestAnimationFrame(() => {
113108
+ isApplyingRemoteRef.current = false;
113109
+ });
113110
+ };
113111
+ map3.observe(handleUpdate);
113112
+ if (!hasInitializedRef.current) {
113113
+ hasInitializedRef.current = true;
113114
+ const count = map3.get("count");
113115
+ if (count && count > 0) {
113116
+ handleUpdate();
113117
+ }
113118
+ }
113119
+ return () => {
113120
+ map3.unobserve(handleUpdate);
113121
+ };
113122
+ }, [doc2, isConnected, getDocMap, setSlides]);
111389
113123
  }
111390
113124
  function computeGridSpacingPx(presentationGridSpacing) {
111391
113125
  if (presentationGridSpacing) {
@@ -121087,7 +122821,7 @@ function useViewerUIState() {
121087
122821
  const [isCompactToolbarOpen, setIsCompactToolbarOpen] = React10.useState(false);
121088
122822
  const [toolbarSection, setToolbarSection] = React10.useState("home");
121089
122823
  const [isSlidesPaneOpen, setIsSlidesPaneOpen] = React10.useState(true);
121090
- const [isInspectorPaneOpen, setIsInspectorPaneOpen] = React10.useState(true);
122824
+ const [isInspectorPaneOpen, setIsInspectorPaneOpen] = React10.useState(false);
121091
122825
  const [isSlideNotesCollapsed, setIsSlideNotesCollapsed] = React10.useState(true);
121092
122826
  const [isOverflowMenuOpen, setIsOverflowMenuOpen] = React10.useState(false);
121093
122827
  const [isSidebarCollapsed, setIsSidebarCollapsed] = React10.useState(false);
@@ -121370,13 +123104,18 @@ var PowerPointViewer = React10.forwardRef(
121370
123104
  onDirtyChange,
121371
123105
  onActiveSlideChange,
121372
123106
  theme,
121373
- collaboration
123107
+ collaboration,
123108
+ onStartCollaboration,
123109
+ onStopCollaboration,
123110
+ shareDefaults
121374
123111
  } = props;
121375
123112
  const themeStyle = useThemeStyle(theme);
121376
123113
  const [content, setContent] = React10.useState(incomingContent);
121377
123114
  React10.useEffect(() => {
121378
123115
  setContent(incomingContent);
121379
123116
  }, [incomingContent]);
123117
+ const [isSettingsOpen, setIsSettingsOpen] = React10.useState(false);
123118
+ const [isShareDialogOpen, setIsShareDialogOpen] = React10.useState(false);
121380
123119
  const { reducedMotion, toggleReducedMotion } = useReducedMotion();
121381
123120
  const state2 = useViewerState();
121382
123121
  const {
@@ -121577,36 +123316,35 @@ var PowerPointViewer = React10.forwardRef(
121577
123316
  ref: containerRef,
121578
123317
  tabIndex: 0,
121579
123318
  style: themeStyle,
123319
+ "data-pptx-viewer": "",
121580
123320
  className: "h-full w-full bg-background text-foreground flex flex-col relative overflow-hidden outline-none",
121581
123321
  children: [
121582
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-b from-purple-500/3 to-transparent z-0" }),
121583
- mode !== "present" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
121584
- /* @__PURE__ */ jsxRuntime.jsx(
121585
- ViewerToolbarSection,
121586
- {
121587
- mode,
121588
- canEdit,
121589
- state: state2,
121590
- selectedElement,
121591
- activeSlide,
121592
- zoom,
121593
- history,
121594
- findReplace: editorOps.findReplace,
121595
- manipulation: editorOps.manipulation,
121596
- insertHandlers: editorOps.insertHandlers,
121597
- exportHandlers,
121598
- printHandlers,
121599
- propertyHandlers,
121600
- dialogs,
121601
- slideOps: editorOps.slideOps,
121602
- ops: editorOps.ops,
121603
- onSetMode: handleSetMode,
121604
- onEnterPresenterView: handleEnterPresenterView,
121605
- onEnterRehearsalMode: handleEnterRehearsalMode
121606
- }
121607
- ),
121608
- collaboration && /* @__PURE__ */ jsxRuntime.jsx(CollaborationToolbarStrip, { collaboration })
121609
- ] }),
123322
+ mode !== "present" && /* @__PURE__ */ jsxRuntime.jsx(
123323
+ ViewerToolbarSection,
123324
+ {
123325
+ mode,
123326
+ canEdit,
123327
+ state: state2,
123328
+ selectedElement,
123329
+ activeSlide,
123330
+ zoom,
123331
+ history,
123332
+ findReplace: editorOps.findReplace,
123333
+ manipulation: editorOps.manipulation,
123334
+ insertHandlers: editorOps.insertHandlers,
123335
+ exportHandlers,
123336
+ printHandlers,
123337
+ propertyHandlers,
123338
+ dialogs,
123339
+ slideOps: editorOps.slideOps,
123340
+ ops: editorOps.ops,
123341
+ onSetMode: handleSetMode,
123342
+ onEnterPresenterView: handleEnterPresenterView,
123343
+ onEnterRehearsalMode: handleEnterRehearsalMode,
123344
+ onOpenSettings: () => setIsSettingsOpen(true),
123345
+ onOpenShareDialog: () => setIsShareDialogOpen(true)
123346
+ }
123347
+ ),
121610
123348
  /* @__PURE__ */ jsxRuntime.jsx(
121611
123349
  ViewerMainContent,
121612
123350
  {
@@ -121656,7 +123394,14 @@ var PowerPointViewer = React10.forwardRef(
121656
123394
  onUpdateNotes: propertyHandlers.handleUpdateNotes,
121657
123395
  collaborationSlot: collaboration ? /* @__PURE__ */ jsxRuntime.jsx(CollaborationStatusStrip, {}) : void 0,
121658
123396
  notesPanelHeight: isMobile ? void 0 : resizablePanels.bottomHeight,
121659
- onResizeBottom: isMobile ? void 0 : resizablePanels.onResizeBottom
123397
+ onResizeBottom: isMobile ? void 0 : resizablePanels.onResizeBottom,
123398
+ scale: zoom.scale,
123399
+ onZoomIn: zoom.handleZoomIn,
123400
+ onZoomOut: zoom.handleZoomOut,
123401
+ onZoomToFit: zoom.handleZoomToFit,
123402
+ mode,
123403
+ onSetMode: handleSetMode,
123404
+ onToggleSlideSorter: () => state2.setShowSlideSorter((p3) => !p3)
121660
123405
  }
121661
123406
  ),
121662
123407
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -121688,6 +123433,37 @@ var PowerPointViewer = React10.forwardRef(
121688
123433
  onDiscardAnnotations: handleDiscardAnnotations
121689
123434
  }
121690
123435
  ),
123436
+ /* @__PURE__ */ jsxRuntime.jsx(
123437
+ SettingsDialog,
123438
+ {
123439
+ isOpen: isSettingsOpen,
123440
+ onClose: () => setIsSettingsOpen(false),
123441
+ spellCheckEnabled: state2.spellCheckEnabled,
123442
+ onSetSpellCheckEnabled: state2.setSpellCheckEnabled,
123443
+ showGrid: state2.showGrid,
123444
+ onSetShowGrid: state2.setShowGrid,
123445
+ showRulers: state2.showRulers,
123446
+ onSetShowRulers: state2.setShowRulers,
123447
+ snapToGrid: state2.snapToGrid,
123448
+ onSetSnapToGrid: state2.setSnapToGrid,
123449
+ reducedMotion,
123450
+ onToggleReducedMotion: toggleReducedMotion
123451
+ }
123452
+ ),
123453
+ /* @__PURE__ */ jsxRuntime.jsx(
123454
+ ShareDialog,
123455
+ {
123456
+ open: isShareDialogOpen,
123457
+ onClose: () => setIsShareDialogOpen(false),
123458
+ activeCollaboration: collaboration,
123459
+ onStartCollaboration,
123460
+ onStopCollaboration,
123461
+ preconfigured: Boolean(collaboration),
123462
+ defaultRoomId: shareDefaults?.roomId,
123463
+ defaultUserName: shareDefaults?.userName,
123464
+ defaultServerUrl: shareDefaults?.serverUrl
123465
+ }
123466
+ ),
121691
123467
  /* @__PURE__ */ jsxRuntime.jsx(
121692
123468
  ViewerOverlays,
121693
123469
  {
@@ -121729,36 +123505,21 @@ var PowerPointViewer = React10.forwardRef(
121729
123505
  ]
121730
123506
  }
121731
123507
  );
121732
- return /* @__PURE__ */ jsxRuntime.jsx(ViewerThemeProvider, { theme, children: collaboration ? /* @__PURE__ */ jsxRuntime.jsx(
123508
+ return /* @__PURE__ */ jsxRuntime.jsx(ViewerThemeProvider, { theme, children: collaboration ? /* @__PURE__ */ jsxRuntime.jsxs(
121733
123509
  CollaborationProvider,
121734
123510
  {
121735
123511
  config: collaboration,
121736
123512
  canvasWidth: canvasSize.width,
121737
123513
  canvasHeight: canvasSize.height,
121738
- children: viewerContent
123514
+ children: [
123515
+ /* @__PURE__ */ jsxRuntime.jsx(CollaborationDocumentSync, { slides, setSlides: state2.setSlides }),
123516
+ viewerContent
123517
+ ]
121739
123518
  }
121740
123519
  ) : viewerContent });
121741
123520
  }
121742
123521
  );
121743
123522
  PowerPointViewer.displayName = "PowerPointViewer";
121744
- function CollaborationToolbarStrip({
121745
- collaboration
121746
- }) {
121747
- const collab = useCollaboration();
121748
- if (!collab) {
121749
- return null;
121750
- }
121751
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end px-2 py-0.5 border-b border-border bg-background/30 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(
121752
- UserAvatarBar,
121753
- {
121754
- remoteUsers: collab.remoteUsers,
121755
- localUserName: collaboration.userName,
121756
- localUserColor: collab.config.userColor ?? "#6366f1",
121757
- localUserAvatar: collaboration.userAvatar,
121758
- status: collab.status
121759
- }
121760
- ) });
121761
- }
121762
123523
  function CollaborationStatusStrip() {
121763
123524
  const collab = useCollaboration();
121764
123525
  if (!collab) {
@@ -121766,6 +123527,19 @@ function CollaborationStatusStrip() {
121766
123527
  }
121767
123528
  return /* @__PURE__ */ jsxRuntime.jsx(CollaborationStatusIndicator, { status: collab.status, connectedCount: collab.connectedCount });
121768
123529
  }
123530
+ function CollaborationDocumentSync({
123531
+ slides,
123532
+ setSlides
123533
+ }) {
123534
+ const collab = useCollaboration();
123535
+ useYjsDocumentSync({
123536
+ doc: collab?.doc ?? null,
123537
+ slides,
123538
+ setSlides,
123539
+ isConnected: collab?.status === "connected"
123540
+ });
123541
+ return null;
123542
+ }
121769
123543
  function colorSchemesMatch(a2, b2) {
121770
123544
  if (!a2) {
121771
123545
  return false;
@@ -121821,35 +123595,6 @@ function useThemeSwitching(input) {
121821
123595
  currentPreset
121822
123596
  };
121823
123597
  }
121824
- function useCollaborativeHistory({
121825
- localClientId: _localClientId,
121826
- handleUndo,
121827
- handleRedo,
121828
- canUndo,
121829
- canRedo
121830
- }) {
121831
- const localChangeCountRef = React10.useRef(0);
121832
- const wrappedUndo = React10.useCallback(() => {
121833
- if (!canUndo) {
121834
- return;
121835
- }
121836
- handleUndo();
121837
- localChangeCountRef.current = Math.max(0, localChangeCountRef.current - 1);
121838
- }, [handleUndo, canUndo]);
121839
- const wrappedRedo = React10.useCallback(() => {
121840
- if (!canRedo) {
121841
- return;
121842
- }
121843
- handleRedo();
121844
- localChangeCountRef.current += 1;
121845
- }, [handleRedo, canRedo]);
121846
- return {
121847
- handleUndo: wrappedUndo,
121848
- handleRedo: wrappedRedo,
121849
- canUndo,
121850
- canRedo
121851
- };
121852
- }
121853
123598
  /*! Bundled license information:
121854
123599
 
121855
123600
  three/build/three.core.js:
@@ -121940,6 +123685,7 @@ exports.clearAudienceContent = clearAudienceContent;
121940
123685
  exports.getAnimationInitialStyle = getAnimationInitialStyle;
121941
123686
  exports.isAudienceTab = isAudienceTab;
121942
123687
  exports.loadAudienceContent = loadAudienceContent;
123688
+ exports.storeAudienceContent = storeAudienceContent;
121943
123689
  exports.useCollaborativeHistory = useCollaborativeHistory;
121944
123690
  exports.useCollaborativeState = useCollaborativeState;
121945
123691
  exports.usePresenceTracking = usePresenceTracking;