pptx-react-viewer 1.0.9 → 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
@@ -4,7 +4,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
  import * as ReactDOM from 'react-dom/client';
5
5
  import { clsx } from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
7
- import { LuMessageSquare, LuEyeOff, LuShieldCheck, LuPanelLeftClose, LuPlus, LuStickyNote, LuChevronDown, LuChevronUp, LuClock, LuX, LuDownload, LuTrash2, LuCheck, LuLock, LuEye, LuFileText, LuType, LuLoader, LuShieldAlert, LuSignature, LuInfo, LuTriangleAlert, LuPrinter, LuPenTool, LuMinus, LuMonitorOff, LuMonitor, LuChevronLeft, LuChevronRight, LuPlay, LuPause, LuPanelLeft, LuZoomOut, LuZoomIn, LuUndo, LuRedo, LuSearch, LuPanelRight, LuSquare, LuImage, LuVideo, LuDatabase, LuLayers, LuCopy, LuClipboardPaste, LuPaintbrush, LuPalette, LuPencil, LuSpellCheck, LuGitCompare, LuList, LuPipette, LuCaseSensitive, LuReplace, LuTimer, LuMousePointer2, LuHighlighter, LuEraser, LuGripVertical, LuUpload, LuBold, LuItalic, LuUnderline, LuStrikethrough, LuListOrdered, LuIndentIncrease, LuIndentDecrease, LuLink, LuGrid2X2, LuCopyPlus, LuEllipsis, LuCircle, LuMoveRight, LuTriangle, LuDiamond, LuAlignLeft, LuAlignCenter, LuAlignRight, LuAlignJustify, LuSpline, LuSettings2, LuMove, LuSettings, LuRadio, LuCaptions, LuFolderOpen, LuArrowDown, LuArrowUp, LuArrowRight, LuArrowLeft, LuReply, LuRotateCw, LuScissors, LuBookmark } from 'react-icons/lu';
7
+ import { LuMessageSquare, LuEyeOff, LuSettings, LuX, LuShieldCheck, LuPlus, LuPanelLeftClose, LuStickyNote, LuMonitor, LuColumns2, LuPresentation, LuMinus, LuClock, LuDownload, LuTrash2, LuCheck, LuLock, LuEye, LuFileText, LuType, LuLoader, LuShieldAlert, LuSignature, LuInfo, LuTriangleAlert, LuPrinter, LuPenTool, LuWifi, LuWifiOff, LuUsers, LuCopy, LuMonitorOff, LuChevronLeft, LuChevronRight, LuPlay, LuPause, LuPanelLeft, LuUndo, LuRedo, LuSearch, LuShare2, LuPanelRight, LuFolderOpen, LuVideo, LuImage, LuClipboardPaste, LuScissors, LuPaintbrush, LuChevronDown, LuSquare, LuDatabase, LuLayers, LuAArrowUp, LuAArrowDown, LuRemoveFormatting, LuHighlighter, LuList, LuListOrdered, LuIndentDecrease, LuIndentIncrease, LuChevronUp, LuPalette, LuPencil, LuPaintBucket, LuSparkles, LuCast, LuCaptions, LuSpellCheck, LuGitCompare, LuPipette, LuCaseSensitive, LuReplace, LuTimer, LuMousePointer2, LuEraser, LuGripVertical, LuUpload, LuBold, LuItalic, LuUnderline, LuStrikethrough, LuLink, LuGrid2X2, LuCopyPlus, LuEllipsis, LuCircle, LuMoveRight, LuTriangle, LuDiamond, LuAlignLeft, LuAlignCenter, LuAlignRight, LuAlignJustify, LuSpline, LuSettings2, LuMove, LuRadio, LuArrowDown, LuArrowUp, LuArrowRight, LuArrowLeft, LuReply, LuRotateCw, LuBookmark } from 'react-icons/lu';
8
8
  import { hasShapeProperties, hasTextProperties, SWITCHABLE_LAYOUT_TYPES, isCalloutShape, getCalloutLeaderLineGeometry, buildCalloutLeaderLineSvgPath, getCalloutViewBoxBounds, isInkElement, getLinkedTextBoxSegments, isImageLikeElement, getSubstituteFontFamily, PptxHandler, EncryptedFileError, guidePxToEmu, guideEmuToPx, THEME_COLOR_SCHEME_KEYS, hslToRgb, PRESET_COLOR_MAP, elementActionToPptxAction, mergeShapes, SvgExporter, applyDrawingColorTransforms as applyDrawingColorTransforms$1, getPresetShapeClipPath, svgPathToPolygons, polygonsToSvgPath, EMU_PER_PX as EMU_PER_PX$1, chartDataChangeType, chartDataUpdatePoint, chartDataAddCategory, chartDataRemoveCategory, chartDataAddSeries, chartDataRemoveSeries, getOleObjectTypeLabel, pptxActionToElementAction, hasNonTrivialOverride, COLOR_MAP_ALIAS_KEYS, DEFAULT_COLOR_MAP, addSmartArtNodeAsChild, updateSmartArtNodeText, removeSmartArtNode, switchSmartArtLayout, applyThemeToData, THEME_PRESETS } from 'pptx-viewer-core';
9
9
  import { useTranslation } from 'react-i18next';
10
10
  import html2canvasPro from 'html2canvas-pro';
@@ -43302,7 +43302,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43302
43302
  return x2 === y && (0 !== x2 || 1 / x2 === 1 / y) || x2 !== x2 && y !== y;
43303
43303
  }
43304
43304
  function useSyncExternalStore$2(subscribe3, getSnapshot2) {
43305
- didWarnOld18Alpha || void 0 === React90.startTransition || (didWarnOld18Alpha = true, console.error(
43305
+ didWarnOld18Alpha || void 0 === React95.startTransition || (didWarnOld18Alpha = true, console.error(
43306
43306
  "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."
43307
43307
  ));
43308
43308
  var value = getSnapshot2();
@@ -43312,7 +43312,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43312
43312
  "The result of getSnapshot should be cached to avoid an infinite loop"
43313
43313
  ), didWarnUncachedGetSnapshot = true);
43314
43314
  }
43315
- cachedValue = useState80({
43315
+ cachedValue = useState84({
43316
43316
  inst: { value, getSnapshot: getSnapshot2 }
43317
43317
  });
43318
43318
  var inst = cachedValue[0].inst, forceUpdate = cachedValue[1];
@@ -43324,7 +43324,7 @@ var require_use_sync_external_store_shim_development = __commonJS({
43324
43324
  },
43325
43325
  [subscribe3, value, getSnapshot2]
43326
43326
  );
43327
- useEffect64(
43327
+ useEffect68(
43328
43328
  function() {
43329
43329
  checkIfSnapshotChanged(inst) && forceUpdate({ inst });
43330
43330
  return subscribe3(function() {
@@ -43350,8 +43350,8 @@ var require_use_sync_external_store_shim_development = __commonJS({
43350
43350
  return getSnapshot2();
43351
43351
  }
43352
43352
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
43353
- 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;
43354
- exports$1.useSyncExternalStore = void 0 !== React90.useSyncExternalStore ? React90.useSyncExternalStore : shim;
43353
+ 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;
43354
+ exports$1.useSyncExternalStore = void 0 !== React95.useSyncExternalStore ? React95.useSyncExternalStore : shim;
43355
43355
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
43356
43356
  })();
43357
43357
  }
@@ -43374,9 +43374,9 @@ var require_with_selector_development = __commonJS({
43374
43374
  return x2 === y && (0 !== x2 || 1 / x2 === 1 / y) || x2 !== x2 && y !== y;
43375
43375
  }
43376
43376
  "undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ && "function" === typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart && __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
43377
- 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;
43377
+ 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;
43378
43378
  exports$1.useSyncExternalStoreWithSelector = function(subscribe3, getSnapshot2, getServerSnapshot2, selector, isEqual) {
43379
- var instRef = useRef66(null);
43379
+ var instRef = useRef69(null);
43380
43380
  if (null === instRef.current) {
43381
43381
  var inst = { hasValue: false, value: null };
43382
43382
  instRef.current = inst;
@@ -43417,7 +43417,7 @@ var require_with_selector_development = __commonJS({
43417
43417
  [getSnapshot2, getServerSnapshot2, selector, isEqual]
43418
43418
  );
43419
43419
  var value = useSyncExternalStore3(subscribe3, instRef[0], instRef[1]);
43420
- useEffect64(
43420
+ useEffect68(
43421
43421
  function() {
43422
43422
  inst.hasValue = true;
43423
43423
  inst.value = value;
@@ -69705,6 +69705,7 @@ var UNGROUPED_SECTION_ID = "__ungrouped__";
69705
69705
 
69706
69706
  // src/viewer/constants/toolbar.ts
69707
69707
  var TOOLBAR_SECTIONS = [
69708
+ { id: "file", label: "File" },
69708
69709
  { id: "home", label: "Home" },
69709
69710
  { id: "insert", label: "Insert" },
69710
69711
  { id: "text", label: "Text" },
@@ -69712,8 +69713,11 @@ var TOOLBAR_SECTIONS = [
69712
69713
  { id: "arrange", label: "Arrange" },
69713
69714
  { id: "design", label: "Design" },
69714
69715
  { id: "transitions", label: "Transitions" },
69716
+ { id: "animations", label: "Animations" },
69717
+ { id: "slideShow", label: "Slide Show" },
69715
69718
  { id: "review", label: "Review" },
69716
- { id: "view", label: "View" }
69719
+ { id: "view", label: "View" },
69720
+ { id: "help", label: "Help" }
69717
69721
  ];
69718
69722
  var SHORTCUT_REFERENCE_ITEMS = [
69719
69723
  { action: "Undo", shortcut: "Ctrl/Cmd+Z" },
@@ -85751,6 +85755,7 @@ function AccessibilityPanel({
85751
85755
  reducedMotion,
85752
85756
  onToggleReducedMotion
85753
85757
  }) {
85758
+ const { t: t2 } = useTranslation();
85754
85759
  if (!isOpen) {
85755
85760
  return null;
85756
85761
  }
@@ -85758,31 +85763,27 @@ function AccessibilityPanel({
85758
85763
  "div",
85759
85764
  {
85760
85765
  role: "dialog",
85761
- "aria-label": "Accessibility Checker",
85766
+ "aria-label": t2("pptx.accessibility.title"),
85762
85767
  className: "absolute top-14 right-3 z-40 w-[min(28rem,calc(100%-1.5rem))] rounded border border-border bg-popover shadow-2xl",
85763
85768
  children: [
85764
85769
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-2", children: [
85765
- /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-wide text-foreground", children: "Accessibility Checker" }),
85770
+ /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-wide text-foreground", children: t2("pptx.accessibility.title") }),
85766
85771
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
85767
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
85768
- issues.length,
85769
- " issue",
85770
- issues.length !== 1 ? "s" : ""
85771
- ] }),
85772
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: t2("pptx.accessibility.issueCount", { count: issues.length }) }),
85772
85773
  /* @__PURE__ */ jsx(
85773
85774
  "button",
85774
85775
  {
85775
85776
  type: "button",
85776
85777
  onClick: onClose,
85777
- "aria-label": "Close accessibility panel",
85778
+ "aria-label": t2("pptx.accessibility.closePanel"),
85778
85779
  className: "rounded px-2 py-1 text-[11px] text-foreground hover:bg-muted hover:text-foreground",
85779
- children: "Close"
85780
+ children: t2("pptx.accessibility.close")
85780
85781
  }
85781
85782
  )
85782
85783
  ] })
85783
85784
  ] }),
85784
85785
  onToggleReducedMotion !== void 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-2", children: [
85785
- /* @__PURE__ */ jsx("label", { htmlFor: "reduced-motion-toggle", className: "text-xs text-foreground", children: "Reduce motion" }),
85786
+ /* @__PURE__ */ jsx("label", { htmlFor: "reduced-motion-toggle", className: "text-xs text-foreground", children: t2("pptx.accessibility.reduceMotion") }),
85786
85787
  /* @__PURE__ */ jsx(
85787
85788
  "button",
85788
85789
  {
@@ -85811,9 +85812,9 @@ function AccessibilityPanel({
85811
85812
  "div",
85812
85813
  {
85813
85814
  role: "list",
85814
- "aria-label": "Accessibility issues",
85815
+ "aria-label": t2("pptx.accessibility.issuesList"),
85815
85816
  className: "max-h-72 overflow-y-auto p-2 space-y-1",
85816
- children: issues.length === 0 ? /* @__PURE__ */ jsx("div", { role: "listitem", className: "text-center text-xs text-muted-foreground py-4", children: "No accessibility issues found." }) : issues.map((issue, idx) => /* @__PURE__ */ jsxs(
85817
+ children: issues.length === 0 ? /* @__PURE__ */ jsx("div", { role: "listitem", className: "text-center text-xs text-muted-foreground py-4", children: t2("pptx.accessibility.noIssues") }) : issues.map((issue, idx) => /* @__PURE__ */ jsxs(
85817
85818
  "div",
85818
85819
  {
85819
85820
  role: "listitem",
@@ -85825,7 +85826,7 @@ function AccessibilityPanel({
85825
85826
  children: [
85826
85827
  /* @__PURE__ */ jsx("span", { className: "shrink-0 mt-0.5", "aria-hidden": "true", children: issue.severity === "error" ? "\u25CF" : issue.severity === "warning" ? "\u25B2" : "\u2139" }),
85827
85828
  /* @__PURE__ */ jsxs("span", { children: [
85828
- issue.severity === "error" ? "Error: " : issue.severity === "warning" ? "Warning: " : "Info: ",
85829
+ issue.severity === "error" ? t2("pptx.accessibility.error") : issue.severity === "warning" ? t2("pptx.accessibility.warning") : t2("pptx.accessibility.info"),
85829
85830
  issue.message
85830
85831
  ] })
85831
85832
  ]
@@ -85877,22 +85878,32 @@ function ExportProgressModal({
85877
85878
  ) })
85878
85879
  ] }) });
85879
85880
  }
85880
- function formatAutosaveAge(timestamp) {
85881
+ function formatAutosaveAge(timestamp, t2) {
85881
85882
  const diff = Date.now() - timestamp;
85882
85883
  const minutes = Math.floor(diff / 6e4);
85883
85884
  if (minutes < 1) {
85884
- return "just now";
85885
+ return t2("pptx.autosave.justNow");
85885
85886
  }
85886
85887
  if (minutes === 1) {
85887
- return "1 min ago";
85888
+ return t2("pptx.autosave.oneMinAgo");
85888
85889
  }
85889
- return `${minutes} min ago`;
85890
+ return t2("pptx.autosave.minutesAgo", { count: minutes });
85890
85891
  }
85891
85892
  function StatusBar({
85892
85893
  slideCount,
85893
85894
  activeSlideIndex,
85894
85895
  isDirty,
85895
- autosaveStatus
85896
+ autosaveStatus,
85897
+ scale,
85898
+ onZoomIn,
85899
+ onZoomOut,
85900
+ onZoomToFit,
85901
+ isNotesExpanded,
85902
+ onToggleNotes,
85903
+ mode,
85904
+ onSetMode,
85905
+ onToggleSlideSorter,
85906
+ collaborationSlot
85896
85907
  }) {
85897
85908
  const { t: t2 } = useTranslation();
85898
85909
  let statusText;
@@ -85900,7 +85911,7 @@ function StatusBar({
85900
85911
  statusText = t2("pptx.autosave.saving");
85901
85912
  } else if (autosaveStatus?.state === "saved") {
85902
85913
  statusText = t2("pptx.autosave.saved", {
85903
- time: formatAutosaveAge(autosaveStatus.timestamp)
85914
+ time: formatAutosaveAge(autosaveStatus.timestamp, t2)
85904
85915
  });
85905
85916
  } else if (autosaveStatus?.state === "error") {
85906
85917
  statusText = t2("pptx.autosave.error");
@@ -85909,15 +85920,124 @@ function StatusBar({
85909
85920
  } else {
85910
85921
  statusText = t2("pptx.statusBar.allSaved");
85911
85922
  }
85912
- return /* @__PURE__ */ 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: [
85913
- /* @__PURE__ */ jsx("span", { children: slideCount > 0 ? `Slide ${Math.min(activeSlideIndex + 1, slideCount)} of ${slideCount}` : "No slides" }),
85923
+ const vb = "p-1 rounded-sm transition-colors hover:bg-accent/60 text-muted-foreground active:scale-95 active:opacity-80";
85924
+ return /* @__PURE__ */ 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: [
85925
+ /* @__PURE__ */ jsx("span", { className: "shrink-0", children: slideCount > 0 ? t2("pptx.statusBar.slideOf", {
85926
+ current: Math.min(activeSlideIndex + 1, slideCount),
85927
+ total: slideCount
85928
+ }) : t2("pptx.statusBar.noSlides") }),
85929
+ /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-border/40 mx-1 max-md:hidden" }),
85930
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 max-md:hidden text-[10px]", children: t2("pptx.statusBar.language") }),
85931
+ /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-border/60 mx-1 max-md:hidden" }),
85914
85932
  /* @__PURE__ */ jsx(
85915
85933
  "span",
85916
85934
  {
85917
- className: autosaveStatus?.state === "error" ? "text-red-400" : autosaveStatus?.state === "saving" ? "text-yellow-400" : "",
85935
+ className: cn(
85936
+ "shrink-0 max-md:hidden",
85937
+ autosaveStatus?.state === "error" ? "text-red-400" : autosaveStatus?.state === "saving" ? "text-yellow-400" : ""
85938
+ ),
85918
85939
  children: statusText
85919
85940
  }
85920
- )
85941
+ ),
85942
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
85943
+ onToggleNotes && /* @__PURE__ */ jsxs(
85944
+ "button",
85945
+ {
85946
+ type: "button",
85947
+ onClick: onToggleNotes,
85948
+ className: cn(
85949
+ vb,
85950
+ "flex items-center gap-1 text-[10px]",
85951
+ isNotesExpanded && "text-primary"
85952
+ ),
85953
+ title: t2("pptx.statusBar.toggleNotes"),
85954
+ "aria-label": t2("pptx.statusBar.toggleNotes"),
85955
+ children: [
85956
+ /* @__PURE__ */ jsx(LuStickyNote, { className: "w-3 h-3" }),
85957
+ /* @__PURE__ */ jsx("span", { className: "max-md:hidden", children: t2("pptx.notes.title") })
85958
+ ]
85959
+ }
85960
+ ),
85961
+ /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-border/60 mx-0.5" }),
85962
+ onSetMode && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
85963
+ /* @__PURE__ */ jsx(
85964
+ "button",
85965
+ {
85966
+ type: "button",
85967
+ onClick: () => onSetMode("edit"),
85968
+ className: cn(vb, mode === "edit" && "text-primary"),
85969
+ title: t2("pptx.statusBar.normalView"),
85970
+ "aria-label": t2("pptx.statusBar.normalView"),
85971
+ children: /* @__PURE__ */ jsx(LuMonitor, { className: "w-3.5 h-3.5" })
85972
+ }
85973
+ ),
85974
+ onToggleSlideSorter && /* @__PURE__ */ jsx(
85975
+ "button",
85976
+ {
85977
+ type: "button",
85978
+ onClick: onToggleSlideSorter,
85979
+ className: vb,
85980
+ title: t2("pptx.statusBar.slideSorter"),
85981
+ "aria-label": t2("pptx.statusBar.slideSorter"),
85982
+ children: /* @__PURE__ */ jsx(LuColumns2, { className: "w-3.5 h-3.5" })
85983
+ }
85984
+ ),
85985
+ /* @__PURE__ */ jsx(
85986
+ "button",
85987
+ {
85988
+ type: "button",
85989
+ onClick: () => onSetMode("present"),
85990
+ className: cn(vb, mode === "present" && "text-primary"),
85991
+ title: t2("pptx.statusBar.slideShow"),
85992
+ "aria-label": t2("pptx.statusBar.slideShow"),
85993
+ children: /* @__PURE__ */ jsx(LuPresentation, { className: "w-3.5 h-3.5" })
85994
+ }
85995
+ )
85996
+ ] }),
85997
+ collaborationSlot && /* @__PURE__ */ jsxs(Fragment, { children: [
85998
+ /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-border/40 mx-0.5" }),
85999
+ collaborationSlot
86000
+ ] }),
86001
+ scale !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
86002
+ /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-border/60 mx-0.5" }),
86003
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
86004
+ onZoomOut && /* @__PURE__ */ jsx(
86005
+ "button",
86006
+ {
86007
+ type: "button",
86008
+ onClick: onZoomOut,
86009
+ className: vb,
86010
+ title: t2("pptx.statusBar.zoomOut"),
86011
+ "aria-label": t2("pptx.statusBar.zoomOut"),
86012
+ children: /* @__PURE__ */ jsx(LuMinus, { className: "w-3 h-3" })
86013
+ }
86014
+ ),
86015
+ /* @__PURE__ */ jsxs(
86016
+ "button",
86017
+ {
86018
+ type: "button",
86019
+ onClick: onZoomToFit,
86020
+ 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",
86021
+ title: t2("pptx.statusBar.zoomToFit"),
86022
+ children: [
86023
+ Math.round((scale ?? 1) * 100),
86024
+ "%"
86025
+ ]
86026
+ }
86027
+ ),
86028
+ onZoomIn && /* @__PURE__ */ jsx(
86029
+ "button",
86030
+ {
86031
+ type: "button",
86032
+ onClick: onZoomIn,
86033
+ className: vb,
86034
+ title: t2("pptx.statusBar.zoomIn"),
86035
+ "aria-label": t2("pptx.statusBar.zoomIn"),
86036
+ children: /* @__PURE__ */ jsx(LuPlus, { className: "w-3 h-3" })
86037
+ }
86038
+ )
86039
+ ] })
86040
+ ] })
85921
86041
  ] });
85922
86042
  }
85923
86043
  function ResizeHandle({
@@ -90733,6 +90853,7 @@ function FindReplacePanel({
90733
90853
  onReplaceAll,
90734
90854
  onClose
90735
90855
  }) {
90856
+ const { t: t2 } = useTranslation();
90736
90857
  const searchInputRef = useRef(null);
90737
90858
  useEffect(() => {
90738
90859
  searchInputRef.current?.focus();
@@ -90765,12 +90886,12 @@ function FindReplacePanel({
90765
90886
  [onClose, onReplace]
90766
90887
  );
90767
90888
  const hasResults = findResults.length > 0;
90768
- const matchCountLabel = hasResults ? `${findResultIndex + 1} of ${findResults.length}` : findQuery.length > 0 ? "No matches" : "";
90889
+ const matchCountLabel = hasResults ? t2("pptx.findReplace.matchCount", { current: findResultIndex + 1, total: findResults.length }) : findQuery.length > 0 ? t2("pptx.findReplace.noMatches") : "";
90769
90890
  return /* @__PURE__ */ 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: [
90770
90891
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
90771
90892
  /* @__PURE__ */ jsxs("span", { className: "font-semibold text-sm inline-flex items-center gap-1.5", children: [
90772
90893
  /* @__PURE__ */ jsx(LuSearch, { className: ic }),
90773
- "Find & Replace"
90894
+ t2("pptx.findReplace.title")
90774
90895
  ] }),
90775
90896
  /* @__PURE__ */ jsx(
90776
90897
  "button",
@@ -90778,8 +90899,8 @@ function FindReplacePanel({
90778
90899
  type: "button",
90779
90900
  className: btnGhost,
90780
90901
  onClick: onClose,
90781
- title: "Close (Escape)",
90782
- "aria-label": "Close find and replace",
90902
+ title: t2("pptx.findReplace.closeEscape"),
90903
+ "aria-label": t2("pptx.findReplace.closeAriaLabel"),
90783
90904
  children: /* @__PURE__ */ jsx(LuX, { className: ic })
90784
90905
  }
90785
90906
  )
@@ -90794,9 +90915,9 @@ function FindReplacePanel({
90794
90915
  value: findQuery,
90795
90916
  onChange: (e2) => onSetFindQuery(e2.target.value),
90796
90917
  onKeyDown: handleSearchKeyDown,
90797
- placeholder: "Find\u2026",
90918
+ placeholder: t2("pptx.findReplace.findPlaceholder"),
90798
90919
  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",
90799
- "aria-label": "Search text"
90920
+ "aria-label": t2("pptx.findReplace.searchText")
90800
90921
  }
90801
90922
  ),
90802
90923
  /* @__PURE__ */ jsx(
@@ -90805,8 +90926,8 @@ function FindReplacePanel({
90805
90926
  type: "button",
90806
90927
  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"}`,
90807
90928
  onClick: () => onSetFindMatchCase(!findMatchCase),
90808
- title: "Match case",
90809
- "aria-label": "Toggle match case",
90929
+ title: t2("pptx.findReplace.matchCase"),
90930
+ "aria-label": t2("pptx.findReplace.toggleMatchCase"),
90810
90931
  "aria-pressed": findMatchCase,
90811
90932
  children: /* @__PURE__ */ jsx(LuCaseSensitive, { className: ic })
90812
90933
  }
@@ -90819,8 +90940,8 @@ function FindReplacePanel({
90819
90940
  className: btnGhost,
90820
90941
  onClick: () => onNavigateResult(-1),
90821
90942
  disabled: !hasResults,
90822
- title: "Previous match (Shift+Enter)",
90823
- "aria-label": "Previous match",
90943
+ title: t2("pptx.findReplace.previousMatch"),
90944
+ "aria-label": t2("pptx.findReplace.previousMatch"),
90824
90945
  children: /* @__PURE__ */ jsx(LuChevronUp, { className: ic })
90825
90946
  }
90826
90947
  ),
@@ -90831,8 +90952,8 @@ function FindReplacePanel({
90831
90952
  className: btnGhost,
90832
90953
  onClick: () => onNavigateResult(1),
90833
90954
  disabled: !hasResults,
90834
- title: "Next match (Enter)",
90835
- "aria-label": "Next match",
90955
+ title: t2("pptx.findReplace.nextMatch"),
90956
+ "aria-label": t2("pptx.findReplace.nextMatch"),
90836
90957
  children: /* @__PURE__ */ jsx(LuChevronDown, { className: ic })
90837
90958
  }
90838
90959
  )
@@ -90847,9 +90968,9 @@ function FindReplacePanel({
90847
90968
  value: replaceQuery,
90848
90969
  onChange: (e2) => onSetReplaceQuery(e2.target.value),
90849
90970
  onKeyDown: handleReplaceKeyDown,
90850
- placeholder: "Replace with\u2026",
90971
+ placeholder: t2("pptx.findReplace.replacePlaceholder"),
90851
90972
  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",
90852
- "aria-label": "Replacement text"
90973
+ "aria-label": t2("pptx.findReplace.replacementText")
90853
90974
  }
90854
90975
  )
90855
90976
  ] }) }),
@@ -90861,8 +90982,8 @@ function FindReplacePanel({
90861
90982
  className: btnAction,
90862
90983
  onClick: onReplace,
90863
90984
  disabled: !hasResults,
90864
- title: "Replace current match",
90865
- children: "Replace"
90985
+ title: t2("pptx.findReplace.replaceCurrent"),
90986
+ children: t2("pptx.findReplace.replace")
90866
90987
  }
90867
90988
  ),
90868
90989
  /* @__PURE__ */ jsx(
@@ -90872,8 +90993,8 @@ function FindReplacePanel({
90872
90993
  className: btnAction,
90873
90994
  onClick: onReplaceAll,
90874
90995
  disabled: !hasResults,
90875
- title: "Replace all matches",
90876
- children: "Replace All"
90996
+ title: t2("pptx.findReplace.replaceAllMatches"),
90997
+ children: t2("pptx.findReplace.replaceAll")
90877
90998
  }
90878
90999
  )
90879
91000
  ] })
@@ -91282,8 +91403,8 @@ function SlideItemInner({
91282
91403
  {
91283
91404
  ref: slideRef,
91284
91405
  className: cn(
91285
- "group relative cursor-pointer rounded-lg border-2 p-1 transition-all",
91286
- isActive ? "border-primary bg-primary/10" : "border-border bg-background/40 hover:border-muted-foreground",
91406
+ "group relative flex items-center gap-1 cursor-pointer py-0.5 px-1 transition-all",
91407
+ isActive && "bg-accent/40 before:absolute before:left-0 before:top-1 before:bottom-1 before:w-[3px] before:bg-primary before:rounded-r",
91287
91408
  isHidden && "opacity-50"
91288
91409
  ),
91289
91410
  draggable: canEdit,
@@ -91293,21 +91414,35 @@ function SlideItemInner({
91293
91414
  onDragOver,
91294
91415
  onDrop: (e2) => onDrop(e2, slideIndex),
91295
91416
  children: [
91296
- isHidden && /* @__PURE__ */ 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)]" }),
91297
- /* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded bg-white", children: [
91298
- /* @__PURE__ */ jsx(LazyThumbnail, { slide, canvasSize, previewHeight }),
91299
- (slide.comments?.length ?? 0) > 0 && /* @__PURE__ */ 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: [
91300
- /* @__PURE__ */ jsx(LuMessageSquare, { className: "w-2 h-2" }),
91301
- slide.comments?.length
91302
- ] })
91303
- ] }),
91304
- /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center justify-between px-1", children: [
91305
- /* @__PURE__ */ jsx("span", { className: cn("text-[10px]", isActive ? "text-primary" : "text-muted-foreground"), children: slideIndex + 1 }),
91306
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
91307
- rehearsalTimings && typeof rehearsalTimings[slideIndex] === "number" && /* @__PURE__ */ jsx("span", { className: "text-[9px] font-mono text-amber-400/80 tabular-nums", children: formatTimingMs(rehearsalTimings[slideIndex]) }),
91308
- isHidden && /* @__PURE__ */ jsx(LuEyeOff, { className: "w-3 h-3 text-muted-foreground" })
91309
- ] })
91310
- ] })
91417
+ /* @__PURE__ */ jsx(
91418
+ "span",
91419
+ {
91420
+ className: cn(
91421
+ "text-[10px] tabular-nums w-5 text-right shrink-0 select-none",
91422
+ isActive ? "text-primary font-medium" : "text-muted-foreground"
91423
+ ),
91424
+ children: slideIndex + 1
91425
+ }
91426
+ ),
91427
+ /* @__PURE__ */ jsxs(
91428
+ "div",
91429
+ {
91430
+ className: cn(
91431
+ "relative flex-1 overflow-hidden border transition-colors bg-white",
91432
+ isActive ? "border-primary/60" : "border-transparent group-hover:border-border/40"
91433
+ ),
91434
+ children: [
91435
+ isHidden && /* @__PURE__ */ 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)]" }),
91436
+ /* @__PURE__ */ jsx(LazyThumbnail, { slide, canvasSize, previewHeight }),
91437
+ (slide.comments?.length ?? 0) > 0 && /* @__PURE__ */ 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: [
91438
+ /* @__PURE__ */ jsx(LuMessageSquare, { className: "w-2 h-2" }),
91439
+ slide.comments?.length
91440
+ ] }),
91441
+ isHidden && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0.5 right-0.5 z-10", children: /* @__PURE__ */ jsx(LuEyeOff, { className: "w-3 h-3 text-muted-foreground" }) }),
91442
+ rehearsalTimings && typeof rehearsalTimings[slideIndex] === "number" && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0.5 left-0.5 z-10", children: /* @__PURE__ */ jsx("span", { className: "text-[8px] font-mono text-amber-400/80 tabular-nums bg-black/50 px-0.5 rounded", children: formatTimingMs(rehearsalTimings[slideIndex]) }) })
91443
+ ]
91444
+ }
91445
+ )
91311
91446
  ]
91312
91447
  }
91313
91448
  );
@@ -91428,7 +91563,7 @@ function SlidesPaneSidebar({
91428
91563
  onSlideContextMenu,
91429
91564
  onMoveSlide,
91430
91565
  onAddSlide,
91431
- onCollapse,
91566
+ onCollapse: _onCollapse,
91432
91567
  onAddSection,
91433
91568
  onRenameSection,
91434
91569
  onDeleteSection,
@@ -91633,50 +91768,23 @@ function SlidesPaneSidebar({
91633
91768
  {
91634
91769
  role: "navigation",
91635
91770
  "aria-label": "Slides",
91636
- className: "flex h-full flex-col border-r border-border bg-background/70 backdrop-blur-sm",
91771
+ className: "flex h-full flex-col border-r border-border bg-secondary/30",
91637
91772
  style: panelWidth ? { width: panelWidth, flexShrink: 0 } : void 0,
91638
91773
  children: [
91639
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-2", children: [
91640
- /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t2("pptx.sections.slides") }),
91641
- /* @__PURE__ */ jsx(
91642
- "button",
91643
- {
91644
- type: "button",
91645
- className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
91646
- title: t2("pptx.sections.collapsePane"),
91647
- onClick: onCollapse,
91648
- children: /* @__PURE__ */ jsx(LuPanelLeftClose, { className: "h-3.5 w-3.5" })
91649
- }
91650
- )
91651
- ] }),
91652
91774
  shouldVirtualize ? renderVirtualized() : renderNonVirtualized(),
91653
- /* @__PURE__ */ jsxs("div", { className: "border-t border-border/60 px-3 py-2 space-y-1", children: [
91654
- /* @__PURE__ */ jsxs(
91655
- "button",
91656
- {
91657
- type: "button",
91658
- 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",
91659
- disabled: !canEdit,
91660
- onClick: onAddSlide,
91661
- children: [
91662
- /* @__PURE__ */ jsx(LuPlus, { className: "h-3.5 w-3.5" }),
91663
- t2("pptx.sections.addSlide")
91664
- ]
91665
- }
91666
- ),
91667
- canEdit && onAddSection && /* @__PURE__ */ jsxs(
91668
- "button",
91669
- {
91670
- type: "button",
91671
- 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",
91672
- onClick: () => onAddSection(t2("pptx.sections.defaultName"), activeSlideIndex),
91673
- children: [
91674
- /* @__PURE__ */ jsx(LuPlus, { className: "h-3 w-3" }),
91675
- t2("pptx.sections.addSection")
91676
- ]
91677
- }
91678
- )
91679
- ] }),
91775
+ canEdit && /* @__PURE__ */ jsx("div", { className: "border-t border-border/60 px-2 py-1.5", children: /* @__PURE__ */ jsxs(
91776
+ "button",
91777
+ {
91778
+ type: "button",
91779
+ 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",
91780
+ disabled: !canEdit,
91781
+ onClick: onAddSlide,
91782
+ children: [
91783
+ /* @__PURE__ */ jsx(LuPlus, { className: "h-3 w-3" }),
91784
+ t2("pptx.sections.addSlide")
91785
+ ]
91786
+ }
91787
+ ) }),
91680
91788
  sectionContextMenu && /* @__PURE__ */ jsx(
91681
91789
  SectionContextMenu,
91682
91790
  {
@@ -94111,23 +94219,18 @@ var SlideNotesPanel = ({
94111
94219
  });
94112
94220
  const hasNotes = draft.trim().length > 0;
94113
94221
  const slideLabel = activeSlide ? t2("pptx.notes.slideN", { n: activeSlide.slideNumber }) : t2("pptx.notes.noSlide");
94114
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col border-t border-border/60 bg-background/80 select-none", children: [
94222
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col border-t border-border/60 bg-background select-none", children: [
94115
94223
  /* @__PURE__ */ jsxs(
94116
94224
  "button",
94117
94225
  {
94118
94226
  type: "button",
94119
94227
  onClick: onToggle,
94120
- 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",
94228
+ 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",
94121
94229
  "aria-expanded": isExpanded,
94122
94230
  "aria-controls": "slide-notes-content",
94123
94231
  children: [
94124
- /* @__PURE__ */ jsx(LuStickyNote, { className: "w-3.5 h-3.5 shrink-0" }),
94125
- /* @__PURE__ */ jsx("span", { className: "font-medium tracking-wide uppercase", children: t2("pptx.notes.title") }),
94126
- !isExpanded && hasNotes && /* @__PURE__ */ jsxs("span", { className: "ml-1 truncate max-w-[240px] text-muted-foreground font-normal normal-case", children: [
94127
- "- ",
94128
- draft.trim().split("\n")[0]
94129
- ] }),
94130
- /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0", children: isExpanded ? /* @__PURE__ */ jsx(LuChevronDown, { className: "w-3.5 h-3.5" }) : /* @__PURE__ */ jsx(LuChevronUp, { className: "w-3.5 h-3.5" }) })
94232
+ "Notes",
94233
+ !isExpanded && hasNotes && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/50 text-[10px]", children: "(has notes)" })
94131
94234
  ]
94132
94235
  }
94133
94236
  ),
@@ -96395,15 +96498,14 @@ function SelectionPane({
96395
96498
  }) })
96396
96499
  ] });
96397
96500
  }
96398
- var _b = "inline-flex items-center justify-center px-2.5 py-1.5 max-md:min-h-[44px] max-md:min-w-[44px]";
96501
+ 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";
96399
96502
  var gB = `${_b} border-r border-border hover:bg-accent disabled:opacity-40 disabled:cursor-not-allowed`;
96400
96503
  var gL = `${_b} hover:bg-accent disabled:opacity-40 disabled:cursor-not-allowed`;
96401
96504
  var grp = "inline-flex items-center rounded bg-muted text-xs overflow-hidden";
96402
- 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";
96403
- var sep = /* @__PURE__ */ jsx("div", { className: "w-px h-5 bg-border/60 mx-0.5 max-md:hidden" });
96505
+ 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";
96506
+ var sep = /* @__PURE__ */ jsx("div", { className: "w-px self-stretch bg-border/40 mx-1 max-md:hidden" });
96404
96507
  var ic2 = "w-4 h-4";
96405
96508
  var ics = "w-3.5 h-3.5";
96406
- var MODES = ["edit", "preview", "present"];
96407
96509
  var ALIGN_BTNS = [
96408
96510
  { k: "left", el: /* @__PURE__ */ jsx(LuAlignLeft, { className: ic2 }) },
96409
96511
  { k: "center", el: /* @__PURE__ */ jsx(LuAlignCenter, { className: ic2 }) },
@@ -96522,7 +96624,128 @@ var ATXT = [
96522
96624
  { i: /* @__PURE__ */ jsx(LuAlignRight, { className: ic2 }), t: "Align right" },
96523
96625
  { i: /* @__PURE__ */ jsx(LuAlignJustify, { className: ic2 }), t: "Justify" }
96524
96626
  ];
96627
+ var ANIMATION_PRESETS = [
96628
+ {
96629
+ group: "Entrance",
96630
+ items: [
96631
+ { value: "appear", label: "Appear" },
96632
+ { value: "fadeIn", label: "Fade In" },
96633
+ { value: "flyIn", label: "Fly In" }
96634
+ ]
96635
+ },
96636
+ {
96637
+ group: "Emphasis",
96638
+ items: [
96639
+ { value: "pulse", label: "Pulse" },
96640
+ { value: "spin", label: "Spin" }
96641
+ ]
96642
+ },
96643
+ {
96644
+ group: "Exit",
96645
+ items: [
96646
+ { value: "disappear", label: "Disappear" },
96647
+ { value: "fadeOut", label: "Fade Out" }
96648
+ ]
96649
+ }
96650
+ ];
96651
+ function AnimationsSection(p3) {
96652
+ const { t: t2 } = useTranslation();
96653
+ const [previewActive, setPreviewActive] = useState(false);
96654
+ const hasElement = p3.selectedElement !== null;
96655
+ const disabled = !p3.canEdit || !hasElement;
96656
+ const handlePreview = () => {
96657
+ if (disabled) {
96658
+ return;
96659
+ }
96660
+ setPreviewActive(true);
96661
+ setTimeout(() => setPreviewActive(false), 1200);
96662
+ };
96663
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
96664
+ /* @__PURE__ */ jsxs(
96665
+ "button",
96666
+ {
96667
+ type: "button",
96668
+ onClick: handlePreview,
96669
+ disabled,
96670
+ className: cn(
96671
+ pill,
96672
+ previewActive ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96673
+ ),
96674
+ title: t2("pptx.animations.previewTooltip"),
96675
+ children: [
96676
+ /* @__PURE__ */ jsx(LuPlay, { className: ic2 }),
96677
+ t2("pptx.animations.preview")
96678
+ ]
96679
+ }
96680
+ ),
96681
+ sep,
96682
+ /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
96683
+ /* @__PURE__ */ jsxs(
96684
+ "button",
96685
+ {
96686
+ type: "button",
96687
+ disabled,
96688
+ className: pill,
96689
+ title: t2("pptx.animations.addTooltip"),
96690
+ children: [
96691
+ /* @__PURE__ */ jsx(LuSparkles, { className: ic2 }),
96692
+ t2("pptx.animations.addAnimation"),
96693
+ /* @__PURE__ */ jsx(LuChevronDown, { className: "w-3 h-3" })
96694
+ ]
96695
+ }
96696
+ ),
96697
+ /* @__PURE__ */ jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:flex flex-col w-44 pt-1", children: /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl py-1", children: ANIMATION_PRESETS.map((group) => /* @__PURE__ */ jsxs(React10__default.Fragment, { children: [
96698
+ /* @__PURE__ */ 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()}`) }),
96699
+ group.items.map((item) => /* @__PURE__ */ jsx(
96700
+ "button",
96701
+ {
96702
+ type: "button",
96703
+ disabled,
96704
+ 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",
96705
+ title: t2("pptx.animations.applyAnimation", {
96706
+ name: t2(`pptx.animations.preset.${item.value}`)
96707
+ }),
96708
+ children: t2(`pptx.animations.preset.${item.value}`)
96709
+ },
96710
+ item.value
96711
+ ))
96712
+ ] }, group.group)) }) })
96713
+ ] }),
96714
+ sep,
96715
+ /* @__PURE__ */ jsxs(
96716
+ "button",
96717
+ {
96718
+ type: "button",
96719
+ disabled,
96720
+ className: pill,
96721
+ title: t2("pptx.animations.removeTooltip"),
96722
+ children: [
96723
+ /* @__PURE__ */ jsx(LuTrash2, { className: ic2 }),
96724
+ t2("pptx.animations.remove")
96725
+ ]
96726
+ }
96727
+ ),
96728
+ sep,
96729
+ /* @__PURE__ */ jsxs(
96730
+ "button",
96731
+ {
96732
+ type: "button",
96733
+ onClick: p3.onToggleInspector,
96734
+ className: cn(
96735
+ pill,
96736
+ p3.isInspectorPaneOpen ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96737
+ ),
96738
+ title: t2("pptx.animations.openPanelTooltip"),
96739
+ children: [
96740
+ /* @__PURE__ */ jsx(LuPanelRight, { className: ic2 }),
96741
+ t2("pptx.animations.animationPanel")
96742
+ ]
96743
+ }
96744
+ )
96745
+ ] });
96746
+ }
96525
96747
  function ArrangeSection(p3) {
96748
+ const { t: t2 } = useTranslation();
96526
96749
  const hasSel = Boolean(p3.selectedElement);
96527
96750
  const canMut = hasSel && p3.canEdit;
96528
96751
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -96533,21 +96756,21 @@ function ArrangeSection(p3) {
96533
96756
  onClick: () => p3.onAlignElements(a2.k),
96534
96757
  disabled: !canMut,
96535
96758
  className: i3 < arr.length - 1 ? gB : gL,
96536
- title: `Align ${a2.k}`,
96759
+ title: t2("pptx.arrange.align", { direction: a2.k }),
96537
96760
  children: a2.el
96538
96761
  },
96539
96762
  a2.k
96540
96763
  )) }),
96541
96764
  /* @__PURE__ */ jsxs("div", { className: grp, children: [
96542
- /* @__PURE__ */ jsx("button", { onClick: p3.onCopy, disabled: !hasSel, className: gB, title: "Copy", children: /* @__PURE__ */ jsx(LuCopy, { className: ic2 }) }),
96543
- /* @__PURE__ */ jsx("button", { onClick: p3.onCut, disabled: !canMut, className: gB, title: "Cut", children: "Cut" }),
96765
+ /* @__PURE__ */ jsx("button", { onClick: p3.onCopy, disabled: !hasSel, className: gB, title: t2("pptx.arrange.copy"), children: /* @__PURE__ */ jsx(LuCopy, { className: ic2 }) }),
96766
+ /* @__PURE__ */ jsx("button", { onClick: p3.onCut, disabled: !canMut, className: gB, title: t2("pptx.arrange.cut"), children: t2("pptx.arrange.cut") }),
96544
96767
  /* @__PURE__ */ jsx(
96545
96768
  "button",
96546
96769
  {
96547
96770
  onClick: p3.onPaste,
96548
96771
  disabled: !p3.clipboardPayload || !p3.canEdit,
96549
96772
  className: gL,
96550
- title: "Paste",
96773
+ title: t2("pptx.arrange.paste"),
96551
96774
  children: /* @__PURE__ */ jsx(LuClipboardPaste, { className: ic2 })
96552
96775
  }
96553
96776
  )
@@ -96562,10 +96785,10 @@ function ArrangeSection(p3) {
96562
96785
  pill,
96563
96786
  p3.formatPainterActive ? "bg-amber-600 hover:bg-amber-500 text-amber-50" : ""
96564
96787
  ),
96565
- title: "Format Painter",
96788
+ title: t2("pptx.arrange.formatPainter"),
96566
96789
  children: [
96567
96790
  /* @__PURE__ */ jsx(LuPaintbrush, { className: ic2 }),
96568
- "Format"
96791
+ t2("pptx.arrange.format")
96569
96792
  ]
96570
96793
  }
96571
96794
  ),
@@ -96577,8 +96800,8 @@ function ArrangeSection(p3) {
96577
96800
  onClick: () => p3.onFlip("horizontal"),
96578
96801
  disabled: !canMut,
96579
96802
  className: gB,
96580
- title: "Flip horizontally",
96581
- children: "Flip H"
96803
+ title: t2("pptx.arrange.flipHorizontally"),
96804
+ children: t2("pptx.arrange.flipH")
96582
96805
  }
96583
96806
  ),
96584
96807
  /* @__PURE__ */ jsx(
@@ -96588,8 +96811,8 @@ function ArrangeSection(p3) {
96588
96811
  onClick: () => p3.onFlip("vertical"),
96589
96812
  disabled: !canMut,
96590
96813
  className: gL,
96591
- title: "Flip vertically",
96592
- children: "Flip V"
96814
+ title: t2("pptx.arrange.flipVertically"),
96815
+ children: t2("pptx.arrange.flipV")
96593
96816
  }
96594
96817
  )
96595
96818
  ] }),
@@ -96600,7 +96823,7 @@ function ArrangeSection(p3) {
96600
96823
  onClick: () => p3.onMoveLayer("backward"),
96601
96824
  disabled: !canMut,
96602
96825
  className: gB,
96603
- title: "Send backward",
96826
+ title: t2("pptx.arrange.sendBackward"),
96604
96827
  children: /* @__PURE__ */ jsx(LuChevronDown, { className: ic2 })
96605
96828
  }
96606
96829
  ),
@@ -96610,7 +96833,7 @@ function ArrangeSection(p3) {
96610
96833
  onClick: () => p3.onMoveLayer("forward"),
96611
96834
  disabled: !canMut,
96612
96835
  className: gB,
96613
- title: "Bring forward",
96836
+ title: t2("pptx.arrange.bringForward"),
96614
96837
  children: /* @__PURE__ */ jsx(LuChevronUp, { className: ic2 })
96615
96838
  }
96616
96839
  ),
@@ -96620,8 +96843,8 @@ function ArrangeSection(p3) {
96620
96843
  onClick: () => p3.onMoveLayerToEdge("back"),
96621
96844
  disabled: !canMut,
96622
96845
  className: gB,
96623
- title: "Send to back",
96624
- children: "Back"
96846
+ title: t2("pptx.arrange.sendToBack"),
96847
+ children: t2("pptx.arrange.back")
96625
96848
  }
96626
96849
  ),
96627
96850
  /* @__PURE__ */ jsx(
@@ -96630,25 +96853,34 @@ function ArrangeSection(p3) {
96630
96853
  onClick: () => p3.onMoveLayerToEdge("front"),
96631
96854
  disabled: !canMut,
96632
96855
  className: gL,
96633
- title: "Bring to front",
96634
- children: "Front"
96856
+ title: t2("pptx.arrange.bringToFront"),
96857
+ children: t2("pptx.arrange.front")
96635
96858
  }
96636
96859
  )
96637
96860
  ] }),
96638
- /* @__PURE__ */ jsxs("button", { onClick: p3.onDuplicate, disabled: !canMut, className: pill, title: "Duplicate", children: [
96639
- /* @__PURE__ */ jsx(LuCopy, { className: ic2 }),
96640
- "Duplicate"
96641
- ] }),
96861
+ /* @__PURE__ */ jsxs(
96862
+ "button",
96863
+ {
96864
+ onClick: p3.onDuplicate,
96865
+ disabled: !canMut,
96866
+ className: pill,
96867
+ title: t2("pptx.arrange.duplicate"),
96868
+ children: [
96869
+ /* @__PURE__ */ jsx(LuCopy, { className: ic2 }),
96870
+ t2("pptx.arrange.duplicate")
96871
+ ]
96872
+ }
96873
+ ),
96642
96874
  /* @__PURE__ */ jsxs(
96643
96875
  "button",
96644
96876
  {
96645
96877
  onClick: p3.onDelete,
96646
96878
  disabled: !canMut,
96647
96879
  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",
96648
- title: "Delete",
96880
+ title: t2("pptx.arrange.delete"),
96649
96881
  children: [
96650
96882
  /* @__PURE__ */ jsx(LuTrash2, { className: ic2 }),
96651
- "Delete"
96883
+ t2("pptx.arrange.delete")
96652
96884
  ]
96653
96885
  }
96654
96886
  )
@@ -96687,12 +96919,91 @@ function DesignSection(p3) {
96687
96919
  "Edit Theme"
96688
96920
  ]
96689
96921
  }
96922
+ ),
96923
+ sep,
96924
+ p3.onOpenDocumentProperties && /* @__PURE__ */ jsxs(
96925
+ "button",
96926
+ {
96927
+ onClick: p3.onOpenDocumentProperties,
96928
+ className: pill,
96929
+ title: "Change slide dimensions (16:9, 4:3, custom)",
96930
+ children: [
96931
+ /* @__PURE__ */ jsx(LuMonitor, { className: ics }),
96932
+ "Slide Size"
96933
+ ]
96934
+ }
96935
+ ),
96936
+ p3.onToggleInspector && /* @__PURE__ */ jsxs(
96937
+ "button",
96938
+ {
96939
+ onClick: p3.onToggleInspector,
96940
+ className: cn(
96941
+ pill,
96942
+ p3.isInspectorPaneOpen ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96943
+ ),
96944
+ title: "Open inspector to edit slide background",
96945
+ children: [
96946
+ /* @__PURE__ */ jsx(LuPaintBucket, { className: ics }),
96947
+ "Format Background"
96948
+ ]
96949
+ }
96690
96950
  )
96691
96951
  ] });
96692
96952
  }
96953
+ var TRANSITION_PRESETS = [
96954
+ { value: "none", label: "None" },
96955
+ { value: "fade", label: "Fade" },
96956
+ { value: "push", label: "Push" },
96957
+ { value: "wipe", label: "Wipe" },
96958
+ { value: "split", label: "Split" },
96959
+ { value: "reveal", label: "Reveal" },
96960
+ { value: "cut", label: "Cut" },
96961
+ { value: "cover", label: "Cover" },
96962
+ { value: "uncover", label: "Uncover" }
96963
+ ];
96693
96964
  function TransitionsSection(p3) {
96965
+ const [selected, setSelected] = React10__default.useState("none");
96966
+ const [duration, setDuration] = React10__default.useState("00.50");
96694
96967
  return /* @__PURE__ */ jsxs(Fragment, { children: [
96695
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground px-2", children: "Configure transitions in the Inspector panel (Slide tab)." }),
96968
+ /* @__PURE__ */ jsxs("button", { type: "button", className: pill, title: "Preview transition", children: [
96969
+ /* @__PURE__ */ jsx(LuPlay, { className: ics }),
96970
+ "Preview"
96971
+ ] }),
96972
+ sep,
96973
+ /* @__PURE__ */ jsx("div", { className: "inline-flex items-center gap-0.5 overflow-x-auto max-w-[420px]", children: TRANSITION_PRESETS.map((t2) => /* @__PURE__ */ jsx(
96974
+ "button",
96975
+ {
96976
+ type: "button",
96977
+ onClick: () => setSelected(t2.value),
96978
+ className: cn(
96979
+ "flex-shrink-0 px-2 py-1 max-md:min-h-[44px] rounded border text-[11px] leading-tight transition-colors",
96980
+ selected === t2.value ? "border-primary bg-primary/10 text-primary font-medium" : "border-border bg-muted hover:bg-accent text-foreground"
96981
+ ),
96982
+ title: `${t2.label} transition`,
96983
+ children: t2.label
96984
+ },
96985
+ t2.value
96986
+ )) }),
96987
+ sep,
96988
+ /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-1.5 text-xs text-muted-foreground", children: [
96989
+ /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: "Duration:" }),
96990
+ /* @__PURE__ */ jsx(
96991
+ "input",
96992
+ {
96993
+ type: "text",
96994
+ value: duration,
96995
+ onChange: (e2) => setDuration(e2.target.value),
96996
+ className: "w-14 px-1.5 py-1 rounded border border-border bg-muted text-xs text-foreground text-center",
96997
+ title: "Transition duration in seconds"
96998
+ }
96999
+ )
97000
+ ] }),
97001
+ sep,
97002
+ /* @__PURE__ */ jsxs("button", { type: "button", className: pill, title: "Apply transition to all slides", children: [
97003
+ /* @__PURE__ */ jsx(LuCopy, { className: ics }),
97004
+ "Apply to All"
97005
+ ] }),
97006
+ sep,
96696
97007
  /* @__PURE__ */ jsxs(
96697
97008
  "button",
96698
97009
  {
@@ -96702,7 +97013,7 @@ function TransitionsSection(p3) {
96702
97013
  pill,
96703
97014
  p3.isInspectorPaneOpen ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96704
97015
  ),
96705
- title: "Open Inspector to edit transitions",
97016
+ title: "Open Inspector for full transition options",
96706
97017
  children: [
96707
97018
  /* @__PURE__ */ jsx(LuPanelRight, { className: ic2 }),
96708
97019
  "Inspector"
@@ -96813,6 +97124,321 @@ function DrawSection(p3) {
96813
97124
  ] })
96814
97125
  ] });
96815
97126
  }
97127
+ function FileSection(p3) {
97128
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
97129
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onSaveAsPpsx, className: pill, title: "Save as Slide Show (.ppsx)", children: [
97130
+ /* @__PURE__ */ jsx(LuPlay, { className: ic2 }),
97131
+ "Save .ppsx"
97132
+ ] }),
97133
+ p3.hasMacros && /* @__PURE__ */ jsxs("button", { onClick: p3.onSaveAsPptm, className: pill, title: "Save as Macro-Enabled (.pptm)", children: [
97134
+ /* @__PURE__ */ jsx(LuFileText, { className: ic2 }),
97135
+ "Save .pptm"
97136
+ ] }),
97137
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onPackageForSharing, className: pill, title: "Package for Sharing", children: [
97138
+ /* @__PURE__ */ jsx(LuFolderOpen, { className: ic2 }),
97139
+ "Package"
97140
+ ] }),
97141
+ sep,
97142
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onExportPng, className: pill, title: "Export as PNG", children: [
97143
+ /* @__PURE__ */ jsx(LuDownload, { className: ic2 }),
97144
+ "PNG"
97145
+ ] }),
97146
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onExportPdf, className: pill, title: "Export as PDF", children: [
97147
+ /* @__PURE__ */ jsx(LuFileText, { className: ic2 }),
97148
+ "PDF"
97149
+ ] }),
97150
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onExportVideo, className: pill, title: "Export as Video", children: [
97151
+ /* @__PURE__ */ jsx(LuVideo, { className: ic2 }),
97152
+ "Video"
97153
+ ] }),
97154
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onExportGif, className: pill, title: "Export as GIF", children: [
97155
+ /* @__PURE__ */ jsx(LuImage, { className: ic2 }),
97156
+ "GIF"
97157
+ ] }),
97158
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onCopySlideAsImage, className: pill, title: "Copy Slide as Image", children: [
97159
+ /* @__PURE__ */ jsx(LuCopy, { className: ic2 }),
97160
+ "Copy Image"
97161
+ ] }),
97162
+ sep,
97163
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onPrint, className: pill, title: "Print", children: [
97164
+ /* @__PURE__ */ jsx(LuPrinter, { className: ic2 }),
97165
+ "Print"
97166
+ ] }),
97167
+ sep,
97168
+ p3.onOpenDocumentProperties && /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenDocumentProperties, className: pill, title: "Document Properties", children: [
97169
+ /* @__PURE__ */ jsx(LuInfo, { className: ic2 }),
97170
+ "Properties"
97171
+ ] }),
97172
+ p3.onOpenPasswordProtection && /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenPasswordProtection, className: pill, title: "Protect Presentation", children: [
97173
+ /* @__PURE__ */ jsx(LuLock, { className: ic2 }),
97174
+ "Protect"
97175
+ ] }),
97176
+ p3.onOpenFontEmbedding && /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenFontEmbedding, className: pill, title: "Embed Fonts", children: [
97177
+ /* @__PURE__ */ jsx(LuType, { className: ic2 }),
97178
+ "Fonts"
97179
+ ] }),
97180
+ p3.onOpenDigitalSignatures && /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenDigitalSignatures, className: pill, title: "Digital Signatures", children: [
97181
+ /* @__PURE__ */ jsx(LuShieldAlert, { className: ic2 }),
97182
+ "Signatures"
97183
+ ] })
97184
+ ] });
97185
+ }
97186
+ function extractFontInfo(element2) {
97187
+ const defaults = { fontFamily: "Segoe UI", fontSize: "24" };
97188
+ if (!element2) {
97189
+ return defaults;
97190
+ }
97191
+ if (!hasTextProperties(element2)) {
97192
+ return defaults;
97193
+ }
97194
+ const segStyle = element2.textSegments?.[0]?.style;
97195
+ const textStyle = element2.textStyle;
97196
+ const fontFamily = segStyle?.fontFamily ?? textStyle?.fontFamily ?? defaults.fontFamily;
97197
+ const fontSize = segStyle?.fontSize ?? textStyle?.fontSize;
97198
+ return {
97199
+ fontFamily,
97200
+ fontSize: fontSize !== void 0 && fontSize !== null ? String(fontSize) : defaults.fontSize
97201
+ };
97202
+ }
97203
+ var COMMON_FONTS = [
97204
+ "Arial",
97205
+ "Calibri",
97206
+ "Cambria",
97207
+ "Comic Sans MS",
97208
+ "Courier New",
97209
+ "Georgia",
97210
+ "Helvetica",
97211
+ "Impact",
97212
+ "Segoe UI",
97213
+ "Tahoma",
97214
+ "Times New Roman",
97215
+ "Trebuchet MS",
97216
+ "Verdana"
97217
+ ];
97218
+ var COMMON_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 40, 44, 48, 54, 60, 72, 96];
97219
+ function HomeSection(p3) {
97220
+ const [layoutMenuOpen, setLayoutMenuOpen] = useState(false);
97221
+ const [fontMenuOpen, setFontMenuOpen] = useState(false);
97222
+ const [sizeMenuOpen, setSizeMenuOpen] = useState(false);
97223
+ const [copiedFeedback, setCopiedFeedback] = useState(false);
97224
+ const [cutFeedback, setCutFeedback] = useState(false);
97225
+ const layoutMenuRef = useRef(null);
97226
+ const fontMenuRef = useRef(null);
97227
+ const sizeMenuRef = useRef(null);
97228
+ const { fontFamily, fontSize } = extractFontInfo(p3.selectedElement);
97229
+ const handleNewSlide = useCallback(() => {
97230
+ if (p3.layoutOptions.length > 0) {
97231
+ p3.onInsertSlideFromLayout(p3.layoutOptions[0].path);
97232
+ }
97233
+ }, [p3]);
97234
+ useEffect(() => {
97235
+ if (!layoutMenuOpen) {
97236
+ return;
97237
+ }
97238
+ const handler = (e2) => {
97239
+ if (layoutMenuRef.current && !layoutMenuRef.current.contains(e2.target)) {
97240
+ setLayoutMenuOpen(false);
97241
+ }
97242
+ };
97243
+ document.addEventListener("mousedown", handler);
97244
+ return () => document.removeEventListener("mousedown", handler);
97245
+ }, [layoutMenuOpen]);
97246
+ useEffect(() => {
97247
+ if (!fontMenuOpen) {
97248
+ return;
97249
+ }
97250
+ const handler = (e2) => {
97251
+ if (fontMenuRef.current && !fontMenuRef.current.contains(e2.target)) {
97252
+ setFontMenuOpen(false);
97253
+ }
97254
+ };
97255
+ document.addEventListener("mousedown", handler);
97256
+ return () => document.removeEventListener("mousedown", handler);
97257
+ }, [fontMenuOpen]);
97258
+ useEffect(() => {
97259
+ if (!sizeMenuOpen) {
97260
+ return;
97261
+ }
97262
+ const handler = (e2) => {
97263
+ if (sizeMenuRef.current && !sizeMenuRef.current.contains(e2.target)) {
97264
+ setSizeMenuOpen(false);
97265
+ }
97266
+ };
97267
+ document.addEventListener("mousedown", handler);
97268
+ return () => document.removeEventListener("mousedown", handler);
97269
+ }, [sizeMenuOpen]);
97270
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
97271
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97272
+ /* @__PURE__ */ jsxs("div", { className: grp, children: [
97273
+ /* @__PURE__ */ jsx(
97274
+ "button",
97275
+ {
97276
+ type: "button",
97277
+ onClick: p3.onPaste,
97278
+ disabled: !p3.clipboardPayload || !p3.canEdit,
97279
+ className: gB,
97280
+ title: "Paste",
97281
+ children: /* @__PURE__ */ jsx(LuClipboardPaste, { className: ic2 })
97282
+ }
97283
+ ),
97284
+ /* @__PURE__ */ jsx(
97285
+ "button",
97286
+ {
97287
+ type: "button",
97288
+ onClick: () => {
97289
+ p3.onCut();
97290
+ setCutFeedback(true);
97291
+ setTimeout(() => setCutFeedback(false), 600);
97292
+ },
97293
+ disabled: !p3.canEdit,
97294
+ className: cn(gB, cutFeedback && "bg-green-600/20 text-green-400"),
97295
+ title: "Cut",
97296
+ children: /* @__PURE__ */ jsx(LuScissors, { className: ic2 })
97297
+ }
97298
+ ),
97299
+ /* @__PURE__ */ jsx(
97300
+ "button",
97301
+ {
97302
+ type: "button",
97303
+ onClick: () => {
97304
+ p3.onCopy();
97305
+ setCopiedFeedback(true);
97306
+ setTimeout(() => setCopiedFeedback(false), 600);
97307
+ },
97308
+ className: cn(gB, copiedFeedback && "bg-green-600/20 text-green-400"),
97309
+ title: "Copy",
97310
+ children: /* @__PURE__ */ jsx(LuCopy, { className: ic2 })
97311
+ }
97312
+ ),
97313
+ p3.onToggleFormatPainter && /* @__PURE__ */ jsx(
97314
+ "button",
97315
+ {
97316
+ type: "button",
97317
+ onClick: p3.onToggleFormatPainter,
97318
+ disabled: !p3.canEdit,
97319
+ className: cn(
97320
+ gL,
97321
+ p3.formatPainterActive ? "bg-amber-600 hover:bg-amber-500 text-amber-50" : ""
97322
+ ),
97323
+ title: "Format Painter",
97324
+ children: /* @__PURE__ */ jsx(LuPaintbrush, { className: ic2 })
97325
+ }
97326
+ )
97327
+ ] }),
97328
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Clipboard" })
97329
+ ] }),
97330
+ sep,
97331
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97332
+ /* @__PURE__ */ jsxs("div", { className: "relative inline-flex items-center", ref: layoutMenuRef, children: [
97333
+ /* @__PURE__ */ jsxs(
97334
+ "button",
97335
+ {
97336
+ type: "button",
97337
+ onClick: handleNewSlide,
97338
+ disabled: !p3.canEdit || p3.layoutOptions.length === 0,
97339
+ className: cn(
97340
+ pill,
97341
+ "whitespace-nowrap",
97342
+ p3.layoutOptions.length > 0 ? "rounded-r-none" : ""
97343
+ ),
97344
+ title: "New Slide",
97345
+ children: [
97346
+ /* @__PURE__ */ jsx(LuPlus, { className: ic2 }),
97347
+ "New Slide"
97348
+ ]
97349
+ }
97350
+ ),
97351
+ p3.layoutOptions.length > 0 && /* @__PURE__ */ jsx(
97352
+ "button",
97353
+ {
97354
+ type: "button",
97355
+ disabled: !p3.canEdit,
97356
+ 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",
97357
+ title: "Choose layout",
97358
+ onClick: () => setLayoutMenuOpen((v) => !v),
97359
+ children: /* @__PURE__ */ jsx(LuChevronDown, { className: "w-3 h-3" })
97360
+ }
97361
+ ),
97362
+ layoutMenuOpen && /* @__PURE__ */ jsx("div", { className: "absolute left-0 top-full z-50 flex flex-col w-48 pt-1", children: /* @__PURE__ */ 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__ */ jsx(
97363
+ "button",
97364
+ {
97365
+ type: "button",
97366
+ className: "flex items-center gap-2 w-full px-3 py-1.5 text-xs text-foreground hover:bg-muted transition-colors",
97367
+ onClick: () => {
97368
+ p3.onInsertSlideFromLayout(lo.path);
97369
+ setLayoutMenuOpen(false);
97370
+ },
97371
+ children: lo.name
97372
+ },
97373
+ lo.path
97374
+ )) }) })
97375
+ ] }),
97376
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Slides" })
97377
+ ] }),
97378
+ sep,
97379
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97380
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
97381
+ /* @__PURE__ */ jsxs("div", { className: "relative", ref: fontMenuRef, children: [
97382
+ /* @__PURE__ */ jsxs(
97383
+ "button",
97384
+ {
97385
+ type: "button",
97386
+ onClick: () => setFontMenuOpen((v) => !v),
97387
+ 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",
97388
+ children: [
97389
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: fontFamily }),
97390
+ /* @__PURE__ */ jsx(LuChevronDown, { className: "w-3 h-3 ml-1 shrink-0 text-muted-foreground" })
97391
+ ]
97392
+ }
97393
+ ),
97394
+ fontMenuOpen && /* @__PURE__ */ jsx("div", { className: "absolute left-0 top-full z-50 flex flex-col w-48 pt-1", children: /* @__PURE__ */ 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__ */ jsx(
97395
+ "button",
97396
+ {
97397
+ type: "button",
97398
+ className: "flex items-center gap-2 w-full px-3 py-1.5 text-xs text-foreground hover:bg-muted transition-colors",
97399
+ style: { fontFamily: f },
97400
+ onClick: () => {
97401
+ p3.onUpdateTextStyle?.({ fontFamily: f });
97402
+ setFontMenuOpen(false);
97403
+ },
97404
+ children: f
97405
+ },
97406
+ f
97407
+ )) }) })
97408
+ ] }),
97409
+ /* @__PURE__ */ jsxs("div", { className: "relative", ref: sizeMenuRef, children: [
97410
+ /* @__PURE__ */ jsxs(
97411
+ "button",
97412
+ {
97413
+ type: "button",
97414
+ onClick: () => setSizeMenuOpen((v) => !v),
97415
+ 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",
97416
+ children: [
97417
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: fontSize }),
97418
+ /* @__PURE__ */ jsx(LuChevronDown, { className: "w-3 h-3 ml-1 shrink-0 text-muted-foreground" })
97419
+ ]
97420
+ }
97421
+ ),
97422
+ sizeMenuOpen && /* @__PURE__ */ jsx("div", { className: "absolute left-0 top-full z-50 flex flex-col w-48 pt-1", children: /* @__PURE__ */ 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__ */ jsx(
97423
+ "button",
97424
+ {
97425
+ type: "button",
97426
+ className: "flex items-center gap-2 w-full px-3 py-1.5 text-xs text-foreground hover:bg-muted transition-colors",
97427
+ onClick: () => {
97428
+ p3.onUpdateTextStyle?.({ fontSize: s });
97429
+ setSizeMenuOpen(false);
97430
+ },
97431
+ children: s
97432
+ },
97433
+ s
97434
+ )) }) })
97435
+ ] })
97436
+ ] }),
97437
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Font" })
97438
+ ] }),
97439
+ sep
97440
+ ] });
97441
+ }
96816
97442
  function InsertSection(p3) {
96817
97443
  const { t: t2 } = useTranslation();
96818
97444
  const { canEdit } = p3;
@@ -97171,6 +97797,60 @@ function InsertSection(p3) {
97171
97797
  )
97172
97798
  ] });
97173
97799
  }
97800
+ function SlideShowSection(p3) {
97801
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
97802
+ /* @__PURE__ */ jsxs(
97803
+ "button",
97804
+ {
97805
+ onClick: () => p3.onSetMode("present"),
97806
+ className: pill,
97807
+ title: "Start slide show from beginning",
97808
+ children: [
97809
+ /* @__PURE__ */ jsx(LuPlay, { className: ic2 }),
97810
+ "From Beginning"
97811
+ ]
97812
+ }
97813
+ ),
97814
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onPresent, className: pill, title: "Start slide show from current slide", children: [
97815
+ /* @__PURE__ */ jsx(LuPlay, { className: ic2 }),
97816
+ "From Current Slide"
97817
+ ] }),
97818
+ sep,
97819
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onEnterPresenterView, className: pill, title: "Presenter view", children: [
97820
+ /* @__PURE__ */ jsx(LuMonitor, { className: ic2 }),
97821
+ "Presenter View"
97822
+ ] }),
97823
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onEnterRehearsalMode, className: pill, title: "Rehearse timings", children: [
97824
+ /* @__PURE__ */ jsx(LuClock, { className: ic2 }),
97825
+ "Rehearse Timings"
97826
+ ] }),
97827
+ sep,
97828
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenSetUpSlideShow, className: pill, title: "Set up slide show", children: [
97829
+ /* @__PURE__ */ jsx(LuSettings, { className: ic2 }),
97830
+ "Set Up Slide Show"
97831
+ ] }),
97832
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenBroadcastDialog, className: pill, title: "Broadcast slide show", children: [
97833
+ /* @__PURE__ */ jsx(LuCast, { className: ic2 }),
97834
+ "Broadcast"
97835
+ ] }),
97836
+ sep,
97837
+ /* @__PURE__ */ jsxs(
97838
+ "button",
97839
+ {
97840
+ onClick: p3.onToggleSubtitles,
97841
+ className: cn(
97842
+ pill,
97843
+ p3.showSubtitles ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
97844
+ ),
97845
+ title: "Toggle subtitles",
97846
+ children: [
97847
+ /* @__PURE__ */ jsx(LuCaptions, { className: ic2 }),
97848
+ "Subtitles"
97849
+ ]
97850
+ }
97851
+ )
97852
+ ] });
97853
+ }
97174
97854
  var FONT_COLOR_PRESETS = [
97175
97855
  "#000000",
97176
97856
  "#ffffff",
@@ -97183,6 +97863,18 @@ var FONT_COLOR_PRESETS = [
97183
97863
  "#ff69b4",
97184
97864
  "#808080"
97185
97865
  ];
97866
+ var HIGHLIGHT_COLOR_PRESETS = [
97867
+ "#ffff00",
97868
+ "#00ff00",
97869
+ "#00ffff",
97870
+ "#ff00ff",
97871
+ "#0000ff",
97872
+ "#ff0000",
97873
+ "#000080",
97874
+ "#008080",
97875
+ "#008000",
97876
+ "#800080"
97877
+ ];
97186
97878
  function TextSection(p3) {
97187
97879
  const hasSel = Boolean(p3.selectedElement);
97188
97880
  const canMut = hasSel && p3.canEdit;
@@ -97190,7 +97882,9 @@ function TextSection(p3) {
97190
97882
  const isTable = hasSel && p3.selectedElement?.type === "table";
97191
97883
  const canFormat = isTextEl || isTable;
97192
97884
  const currentColor = isTextEl && p3.selectedElement && hasTextProperties(p3.selectedElement) ? p3.selectedElement.textSegments?.[0]?.style?.color ?? p3.selectedElement.textStyle?.color ?? "#000000" : "#000000";
97885
+ const currentHighlight = isTextEl && p3.selectedElement && hasTextProperties(p3.selectedElement) ? p3.selectedElement.textSegments?.[0]?.style?.highlightColor ?? p3.selectedElement.textStyle?.highlightColor ?? "#ffff00" : "#ffff00";
97193
97886
  const colorInputRef = useRef(null);
97887
+ const highlightInputRef = useRef(null);
97194
97888
  const handleColorChange = useCallback(
97195
97889
  (color) => {
97196
97890
  if (!canFormat) {
@@ -97200,138 +97894,875 @@ function TextSection(p3) {
97200
97894
  },
97201
97895
  [canFormat, p3]
97202
97896
  );
97897
+ const handleHighlightChange = useCallback(
97898
+ (highlightColor) => {
97899
+ if (!canFormat) {
97900
+ return;
97901
+ }
97902
+ p3.onUpdateTextStyle({ highlightColor });
97903
+ },
97904
+ [canFormat, p3]
97905
+ );
97203
97906
  return /* @__PURE__ */ jsxs(Fragment, { children: [
97204
- /* @__PURE__ */ jsx("div", { className: grp, children: FMT.map((b2, i3, a2) => {
97205
- const handleClick = () => {
97206
- if (!canFormat || !p3.selectedElement) {
97907
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97908
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
97909
+ /* @__PURE__ */ jsx("div", { className: grp, children: FMT.map((b2, i3, a2) => {
97910
+ const handleClick = () => {
97911
+ if (!canFormat || !p3.selectedElement) {
97912
+ return;
97913
+ }
97914
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97915
+ switch (b2.t) {
97916
+ case "Bold":
97917
+ p3.onUpdateTextStyle({ bold: !ts?.bold });
97918
+ break;
97919
+ case "Italic":
97920
+ p3.onUpdateTextStyle({ italic: !ts?.italic });
97921
+ break;
97922
+ case "Underline":
97923
+ p3.onUpdateTextStyle({
97924
+ underline: !ts?.underline
97925
+ });
97926
+ break;
97927
+ case "Strikethrough":
97928
+ p3.onUpdateTextStyle({
97929
+ strikethrough: !ts?.strikethrough
97930
+ });
97931
+ break;
97932
+ }
97933
+ };
97934
+ return /* @__PURE__ */ jsx(
97935
+ "button",
97936
+ {
97937
+ type: "button",
97938
+ disabled: !canMut,
97939
+ onMouseDown: (e2) => e2.preventDefault(),
97940
+ onClick: handleClick,
97941
+ className: i3 < a2.length - 1 ? gB : gL,
97942
+ title: b2.t,
97943
+ children: b2.i
97944
+ },
97945
+ b2.t
97946
+ );
97947
+ }) }),
97948
+ /* @__PURE__ */ jsxs("div", { className: grp, children: [
97949
+ /* @__PURE__ */ jsx(
97950
+ "button",
97951
+ {
97952
+ type: "button",
97953
+ disabled: !canMut,
97954
+ onMouseDown: (e2) => e2.preventDefault(),
97955
+ onClick: () => {
97956
+ if (!canFormat || !p3.selectedElement) {
97957
+ return;
97958
+ }
97959
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97960
+ const current = ts?.fontSize ?? 18;
97961
+ p3.onUpdateTextStyle({ fontSize: current + 2 });
97962
+ },
97963
+ className: gB,
97964
+ title: "Increase Font Size",
97965
+ children: /* @__PURE__ */ jsx(LuAArrowUp, { className: ic2 })
97966
+ }
97967
+ ),
97968
+ /* @__PURE__ */ jsx(
97969
+ "button",
97970
+ {
97971
+ type: "button",
97972
+ disabled: !canMut,
97973
+ onMouseDown: (e2) => e2.preventDefault(),
97974
+ onClick: () => {
97975
+ if (!canFormat || !p3.selectedElement) {
97976
+ return;
97977
+ }
97978
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97979
+ const current = ts?.fontSize ?? 18;
97980
+ p3.onUpdateTextStyle({ fontSize: Math.max(1, current - 2) });
97981
+ },
97982
+ className: gB,
97983
+ title: "Decrease Font Size",
97984
+ children: /* @__PURE__ */ jsx(LuAArrowDown, { className: ic2 })
97985
+ }
97986
+ ),
97987
+ /* @__PURE__ */ jsx(
97988
+ "button",
97989
+ {
97990
+ type: "button",
97991
+ disabled: !canMut,
97992
+ onMouseDown: (e2) => e2.preventDefault(),
97993
+ onClick: () => {
97994
+ if (!canFormat) {
97995
+ return;
97996
+ }
97997
+ p3.onUpdateTextStyle({
97998
+ bold: false,
97999
+ italic: false,
98000
+ underline: false,
98001
+ strikethrough: false,
98002
+ highlightColor: void 0
98003
+ });
98004
+ },
98005
+ className: gL,
98006
+ title: "Clear Formatting",
98007
+ children: /* @__PURE__ */ jsx(LuRemoveFormatting, { className: ic2 })
98008
+ }
98009
+ )
98010
+ ] }),
98011
+ /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
98012
+ /* @__PURE__ */ jsxs(
98013
+ "button",
98014
+ {
98015
+ type: "button",
98016
+ disabled: !canMut,
98017
+ onMouseDown: (e2) => e2.preventDefault(),
98018
+ className: pill,
98019
+ title: "Font Color",
98020
+ children: [
98021
+ /* @__PURE__ */ jsx(
98022
+ "svg",
98023
+ {
98024
+ className: ic2,
98025
+ viewBox: "0 0 24 24",
98026
+ fill: "none",
98027
+ stroke: "currentColor",
98028
+ strokeWidth: "2",
98029
+ strokeLinecap: "round",
98030
+ strokeLinejoin: "round",
98031
+ children: /* @__PURE__ */ jsx("path", { d: "M6 20h12M9.5 4h5L18 16H6L9.5 4z" })
98032
+ }
98033
+ ),
98034
+ /* @__PURE__ */ jsx(
98035
+ "div",
98036
+ {
98037
+ className: "w-4 h-1 rounded-sm -mt-0.5",
98038
+ style: { backgroundColor: currentColor }
98039
+ }
98040
+ )
98041
+ ]
98042
+ }
98043
+ ),
98044
+ /* @__PURE__ */ jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:block pt-1", children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl p-2 w-36", children: [
98045
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: FONT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsx(
98046
+ "button",
98047
+ {
98048
+ type: "button",
98049
+ className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentColor?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
98050
+ style: { backgroundColor: c2 },
98051
+ onMouseDown: (e2) => e2.preventDefault(),
98052
+ onClick: () => handleColorChange(c2)
98053
+ },
98054
+ c2
98055
+ )) }),
98056
+ /* @__PURE__ */ jsx(
98057
+ "button",
98058
+ {
98059
+ type: "button",
98060
+ className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
98061
+ onMouseDown: (e2) => e2.preventDefault(),
98062
+ onClick: () => colorInputRef.current?.click(),
98063
+ children: "Custom colour..."
98064
+ }
98065
+ ),
98066
+ /* @__PURE__ */ jsx(
98067
+ "input",
98068
+ {
98069
+ ref: colorInputRef,
98070
+ type: "color",
98071
+ className: "sr-only",
98072
+ value: currentColor,
98073
+ onChange: (e2) => handleColorChange(e2.target.value)
98074
+ }
98075
+ )
98076
+ ] }) })
98077
+ ] }),
98078
+ /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
98079
+ /* @__PURE__ */ jsxs(
98080
+ "button",
98081
+ {
98082
+ type: "button",
98083
+ disabled: !canMut,
98084
+ onMouseDown: (e2) => e2.preventDefault(),
98085
+ className: pill,
98086
+ title: "Text Highlight Color",
98087
+ children: [
98088
+ /* @__PURE__ */ jsx(LuHighlighter, { className: ic2 }),
98089
+ /* @__PURE__ */ jsx(
98090
+ "div",
98091
+ {
98092
+ className: "w-4 h-1 rounded-sm -mt-0.5",
98093
+ style: { backgroundColor: currentHighlight }
98094
+ }
98095
+ )
98096
+ ]
98097
+ }
98098
+ ),
98099
+ /* @__PURE__ */ jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:block pt-1", children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl p-2 w-36", children: [
98100
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: HIGHLIGHT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsx(
98101
+ "button",
98102
+ {
98103
+ type: "button",
98104
+ className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentHighlight?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
98105
+ style: { backgroundColor: c2 },
98106
+ onMouseDown: (e2) => e2.preventDefault(),
98107
+ onClick: () => handleHighlightChange(c2)
98108
+ },
98109
+ c2
98110
+ )) }),
98111
+ /* @__PURE__ */ jsx(
98112
+ "button",
98113
+ {
98114
+ type: "button",
98115
+ className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
98116
+ onMouseDown: (e2) => e2.preventDefault(),
98117
+ onClick: () => highlightInputRef.current?.click(),
98118
+ children: "Custom colour..."
98119
+ }
98120
+ ),
98121
+ /* @__PURE__ */ jsx(
98122
+ "input",
98123
+ {
98124
+ ref: highlightInputRef,
98125
+ type: "color",
98126
+ className: "sr-only",
98127
+ value: currentHighlight,
98128
+ onChange: (e2) => handleHighlightChange(e2.target.value)
98129
+ }
98130
+ )
98131
+ ] }) })
98132
+ ] })
98133
+ ] }),
98134
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Font" })
98135
+ ] }),
98136
+ sep,
98137
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
98138
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
98139
+ /* @__PURE__ */ jsxs("div", { className: grp, children: [
98140
+ /* @__PURE__ */ jsx(
98141
+ "button",
98142
+ {
98143
+ type: "button",
98144
+ disabled: !canMut,
98145
+ onMouseDown: (e2) => e2.preventDefault(),
98146
+ onClick: () => {
98147
+ if (!canFormat || !p3.selectedElement) {
98148
+ return;
98149
+ }
98150
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98151
+ p3.onUpdateTextStyle({
98152
+ listType: ts?.listType === "bullet" ? "none" : "bullet"
98153
+ });
98154
+ },
98155
+ className: gB,
98156
+ title: "Bullet List",
98157
+ children: /* @__PURE__ */ jsx(LuList, { className: ic2 })
98158
+ }
98159
+ ),
98160
+ /* @__PURE__ */ jsx(
98161
+ "button",
98162
+ {
98163
+ type: "button",
98164
+ disabled: !canMut,
98165
+ onMouseDown: (e2) => e2.preventDefault(),
98166
+ onClick: () => {
98167
+ if (!canFormat || !p3.selectedElement) {
98168
+ return;
98169
+ }
98170
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98171
+ p3.onUpdateTextStyle({
98172
+ listType: ts?.listType === "numbered" ? "none" : "numbered"
98173
+ });
98174
+ },
98175
+ className: gL,
98176
+ title: "Numbered List",
98177
+ children: /* @__PURE__ */ jsx(LuListOrdered, { className: ic2 })
98178
+ }
98179
+ )
98180
+ ] }),
98181
+ /* @__PURE__ */ jsxs("div", { className: grp, children: [
98182
+ /* @__PURE__ */ jsx(
98183
+ "button",
98184
+ {
98185
+ type: "button",
98186
+ disabled: !canMut,
98187
+ onMouseDown: (e2) => e2.preventDefault(),
98188
+ onClick: () => {
98189
+ if (!canFormat || !p3.selectedElement) {
98190
+ return;
98191
+ }
98192
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98193
+ const current = ts?.paragraphMarginLeft ?? 0;
98194
+ p3.onUpdateTextStyle({
98195
+ paragraphMarginLeft: Math.max(0, current - 24)
98196
+ });
98197
+ },
98198
+ className: gB,
98199
+ title: "Decrease Indent",
98200
+ children: /* @__PURE__ */ jsx(LuIndentDecrease, { className: ic2 })
98201
+ }
98202
+ ),
98203
+ /* @__PURE__ */ jsx(
98204
+ "button",
98205
+ {
98206
+ type: "button",
98207
+ disabled: !canMut,
98208
+ onMouseDown: (e2) => e2.preventDefault(),
98209
+ onClick: () => {
98210
+ if (!canFormat || !p3.selectedElement) {
98211
+ return;
98212
+ }
98213
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98214
+ const current = ts?.paragraphMarginLeft ?? 0;
98215
+ p3.onUpdateTextStyle({
98216
+ paragraphMarginLeft: current + 24
98217
+ });
98218
+ },
98219
+ className: gL,
98220
+ title: "Increase Indent",
98221
+ children: /* @__PURE__ */ jsx(LuIndentIncrease, { className: ic2 })
98222
+ }
98223
+ )
98224
+ ] }),
98225
+ /* @__PURE__ */ jsx("div", { className: grp, children: ATXT.map((b2, i3, a2) => {
98226
+ const handleClick = () => {
98227
+ if (!canFormat) {
98228
+ return;
98229
+ }
98230
+ const alignMap = {
98231
+ "Align left": "left",
98232
+ "Align center": "center",
98233
+ "Align right": "right",
98234
+ Justify: "justify"
98235
+ };
98236
+ const align = alignMap[b2.t];
98237
+ if (align) {
98238
+ p3.onUpdateTextStyle({ align });
98239
+ }
98240
+ };
98241
+ return /* @__PURE__ */ jsx(
98242
+ "button",
98243
+ {
98244
+ type: "button",
98245
+ disabled: !canMut,
98246
+ onMouseDown: (e2) => e2.preventDefault(),
98247
+ onClick: handleClick,
98248
+ className: i3 < a2.length - 1 ? gB : gL,
98249
+ title: b2.t,
98250
+ children: b2.i
98251
+ },
98252
+ b2.t
98253
+ );
98254
+ }) })
98255
+ ] }),
98256
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Paragraph" })
98257
+ ] })
98258
+ ] });
98259
+ }
98260
+
98261
+ // src/viewer/hooks/collaboration/sanitize.ts
98262
+ var ROOM_ID_REGEX = /^[a-zA-Z0-9_-]{1,128}$/;
98263
+ function validateRoomId(roomId) {
98264
+ if (!ROOM_ID_REGEX.test(roomId)) {
98265
+ throw new Error(
98266
+ `Invalid collaboration room ID: "${roomId}". Must be 1-128 alphanumeric characters, hyphens, or underscores.`
98267
+ );
98268
+ }
98269
+ return roomId;
98270
+ }
98271
+ function sanitizeUserName(name) {
98272
+ if (typeof name !== "string") {
98273
+ return "Anonymous";
98274
+ }
98275
+ const stripped = name.replace(/<[^>]*>/g, "");
98276
+ const trimmed = stripped.trim().slice(0, 64);
98277
+ return trimmed || "Anonymous";
98278
+ }
98279
+ function clampCursorPosition(value, min2, max2) {
98280
+ if (typeof value !== "number" || !Number.isFinite(value)) {
98281
+ return 0;
98282
+ }
98283
+ const margin = 20;
98284
+ return Math.max(min2 - margin, Math.min(max2 + margin, value));
98285
+ }
98286
+ var HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
98287
+ function sanitizeColor(color, fallback = "#6366f1") {
98288
+ if (typeof color !== "string") {
98289
+ return fallback;
98290
+ }
98291
+ return HEX_COLOR_REGEX.test(color) ? color : fallback;
98292
+ }
98293
+ function sanitizeAvatarUrl(url) {
98294
+ if (typeof url !== "string") {
98295
+ return void 0;
98296
+ }
98297
+ try {
98298
+ const parsed = new URL(url);
98299
+ if (parsed.protocol === "https:" || parsed.protocol === "http:" || parsed.protocol === "data:") {
98300
+ return url;
98301
+ }
98302
+ } catch {
98303
+ }
98304
+ return void 0;
98305
+ }
98306
+ function sanitizeSlideIndex(value) {
98307
+ if (typeof value !== "number" || !Number.isFinite(value)) {
98308
+ return 0;
98309
+ }
98310
+ return Math.max(0, Math.floor(value));
98311
+ }
98312
+ function sanitizePresence(raw, canvasWidth, canvasHeight) {
98313
+ if (typeof raw.clientId !== "number") {
98314
+ return null;
98315
+ }
98316
+ return {
98317
+ clientId: raw.clientId,
98318
+ userName: sanitizeUserName(raw.userName),
98319
+ userAvatar: sanitizeAvatarUrl(raw.userAvatar),
98320
+ userColor: sanitizeColor(raw.userColor),
98321
+ activeSlideIndex: sanitizeSlideIndex(raw.activeSlideIndex),
98322
+ cursorX: clampCursorPosition(raw.cursorX, 0, canvasWidth),
98323
+ cursorY: clampCursorPosition(raw.cursorY, 0, canvasHeight),
98324
+ lastUpdated: typeof raw.lastUpdated === "string" ? raw.lastUpdated : (/* @__PURE__ */ new Date()).toISOString(),
98325
+ selectedElementId: typeof raw.selectedElementId === "string" ? raw.selectedElementId.slice(0, 128) : void 0
98326
+ };
98327
+ }
98328
+ var BROADCAST_THROTTLE_MS = 50;
98329
+ var STALE_PRESENCE_MS = 3e4;
98330
+ function usePresenceTracking({
98331
+ awareness,
98332
+ localClientId,
98333
+ userName,
98334
+ userColor,
98335
+ userAvatar,
98336
+ canvasWidth,
98337
+ canvasHeight
98338
+ }) {
98339
+ const [remoteUsers, setRemoteUsers] = useState([]);
98340
+ const lastBroadcastRef = useRef(0);
98341
+ const pendingBroadcastRef = useRef(null);
98342
+ const latestLocalState = useRef({});
98343
+ const broadcastPresence = useCallback(
98344
+ (update2) => {
98345
+ if (!awareness) {
98346
+ return;
98347
+ }
98348
+ Object.assign(latestLocalState.current, update2);
98349
+ const now = Date.now();
98350
+ const elapsed = now - lastBroadcastRef.current;
98351
+ const flush = () => {
98352
+ const state2 = {
98353
+ ...latestLocalState.current,
98354
+ userName,
98355
+ userColor,
98356
+ userAvatar,
98357
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98358
+ };
98359
+ awareness.setLocalStateField("presence", state2);
98360
+ lastBroadcastRef.current = Date.now();
98361
+ };
98362
+ if (elapsed >= BROADCAST_THROTTLE_MS) {
98363
+ if (pendingBroadcastRef.current) {
98364
+ clearTimeout(pendingBroadcastRef.current);
98365
+ pendingBroadcastRef.current = null;
98366
+ }
98367
+ flush();
98368
+ } else if (!pendingBroadcastRef.current) {
98369
+ pendingBroadcastRef.current = setTimeout(() => {
98370
+ pendingBroadcastRef.current = null;
98371
+ flush();
98372
+ }, BROADCAST_THROTTLE_MS - elapsed);
98373
+ }
98374
+ },
98375
+ [awareness, userName, userColor, userAvatar]
98376
+ );
98377
+ useEffect(() => {
98378
+ if (!awareness) {
98379
+ return;
98380
+ }
98381
+ awareness.setLocalStateField("presence", {
98382
+ userName,
98383
+ userColor,
98384
+ userAvatar,
98385
+ activeSlideIndex: 0,
98386
+ cursorX: 0,
98387
+ cursorY: 0,
98388
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98389
+ });
98390
+ }, [awareness, userName, userColor, userAvatar]);
98391
+ useEffect(() => {
98392
+ if (!awareness || localClientId === null) {
98393
+ return;
98394
+ }
98395
+ const handleChange = () => {
98396
+ const now = Date.now();
98397
+ const states = awareness.getStates();
98398
+ const users = [];
98399
+ states.forEach((state2, cid) => {
98400
+ if (cid === localClientId) {
97207
98401
  return;
97208
98402
  }
97209
- const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97210
- switch (b2.t) {
97211
- case "Bold":
97212
- p3.onUpdateTextStyle({ bold: !ts?.bold });
97213
- break;
97214
- case "Italic":
97215
- p3.onUpdateTextStyle({ italic: !ts?.italic });
97216
- break;
97217
- case "Underline":
97218
- p3.onUpdateTextStyle({
97219
- underline: !ts?.underline
97220
- });
97221
- break;
97222
- case "Strikethrough":
97223
- p3.onUpdateTextStyle({
97224
- strikethrough: !ts?.strikethrough
97225
- });
97226
- break;
98403
+ const raw = state2?.presence;
98404
+ if (!raw || typeof raw !== "object") {
98405
+ return;
97227
98406
  }
97228
- };
97229
- return /* @__PURE__ */ jsx(
97230
- "button",
98407
+ const sanitized = sanitizePresence({ ...raw, clientId: cid }, canvasWidth, canvasHeight);
98408
+ if (!sanitized) {
98409
+ return;
98410
+ }
98411
+ const updatedAt = new Date(sanitized.lastUpdated).getTime();
98412
+ if (Number.isNaN(updatedAt) || now - updatedAt > STALE_PRESENCE_MS) {
98413
+ return;
98414
+ }
98415
+ users.push(sanitized);
98416
+ });
98417
+ setRemoteUsers(users);
98418
+ };
98419
+ awareness.on("change", handleChange);
98420
+ awareness.on("update", handleChange);
98421
+ handleChange();
98422
+ return () => {
98423
+ awareness.off("change", handleChange);
98424
+ awareness.off("update", handleChange);
98425
+ };
98426
+ }, [awareness, localClientId, canvasWidth, canvasHeight]);
98427
+ useEffect(() => {
98428
+ if (!awareness) {
98429
+ return;
98430
+ }
98431
+ const interval = setInterval(() => {
98432
+ awareness.setLocalStateField("presence", {
98433
+ ...latestLocalState.current,
98434
+ userName,
98435
+ userColor,
98436
+ userAvatar,
98437
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98438
+ });
98439
+ }, 1e4);
98440
+ return () => clearInterval(interval);
98441
+ }, [awareness, userName, userColor, userAvatar]);
98442
+ useEffect(() => {
98443
+ return () => {
98444
+ if (pendingBroadcastRef.current) {
98445
+ clearTimeout(pendingBroadcastRef.current);
98446
+ }
98447
+ };
98448
+ }, []);
98449
+ return { remoteUsers, broadcastPresence };
98450
+ }
98451
+ function useYjsProvider({ config }) {
98452
+ const [status, setStatus] = useState("disconnected");
98453
+ const [awareness, setAwareness] = useState(null);
98454
+ const [doc2, setDoc] = useState(null);
98455
+ const [clientId, setClientId] = useState(null);
98456
+ const cleanupRef = useRef(null);
98457
+ const init = useCallback(async () => {
98458
+ const roomId = validateRoomId(config.roomId);
98459
+ setStatus("connecting");
98460
+ try {
98461
+ const [Y, { WebsocketProvider: WebsocketProvider2 }] = await Promise.all([Promise.resolve().then(() => (init_yjs(), yjs_exports)), Promise.resolve().then(() => (init_y_websocket(), y_websocket_exports))]);
98462
+ const yDoc = new Y.Doc();
98463
+ const provider = new WebsocketProvider2(
98464
+ config.serverUrl,
98465
+ roomId,
98466
+ yDoc,
98467
+ // eslint-disable-line @typescript-eslint/no-explicit-any
97231
98468
  {
97232
- type: "button",
97233
- disabled: !canMut,
97234
- onMouseDown: (e2) => e2.preventDefault(),
97235
- onClick: handleClick,
97236
- className: i3 < a2.length - 1 ? gB : gL,
97237
- title: b2.t,
97238
- children: b2.i
97239
- },
97240
- b2.t
98469
+ params: config.authToken ? { token: config.authToken } : void 0
98470
+ }
97241
98471
  );
97242
- }) }),
97243
- /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
97244
- /* @__PURE__ */ jsxs(
97245
- "button",
98472
+ const handleStatus = (event) => {
98473
+ if (event.status === "connected") {
98474
+ setStatus("connected");
98475
+ } else if (event.status === "disconnected") {
98476
+ setStatus("disconnected");
98477
+ }
98478
+ };
98479
+ provider.on("status", handleStatus);
98480
+ if (provider.wsconnected) {
98481
+ setStatus("connected");
98482
+ }
98483
+ setDoc(yDoc);
98484
+ setAwareness(provider.awareness);
98485
+ setClientId(provider.awareness.clientID);
98486
+ cleanupRef.current = () => {
98487
+ provider.off("status", handleStatus);
98488
+ provider.destroy();
98489
+ yDoc.destroy();
98490
+ setDoc(null);
98491
+ setAwareness(null);
98492
+ setClientId(null);
98493
+ setStatus("disconnected");
98494
+ };
98495
+ } catch (err) {
98496
+ console.warn(
98497
+ "[pptx-viewer] Collaboration packages not available:",
98498
+ err instanceof Error ? err.message : err
98499
+ );
98500
+ setStatus("error");
98501
+ }
98502
+ }, [config.roomId, config.serverUrl, config.authToken]);
98503
+ useEffect(() => {
98504
+ init();
98505
+ return () => {
98506
+ cleanupRef.current?.();
98507
+ cleanupRef.current = null;
98508
+ };
98509
+ }, [init]);
98510
+ return { status, awareness, doc: doc2, clientId };
98511
+ }
98512
+
98513
+ // src/viewer/hooks/collaboration/useCollaborativeState.ts
98514
+ function useCollaborativeState({
98515
+ config,
98516
+ canvasWidth,
98517
+ canvasHeight
98518
+ }) {
98519
+ const userColor = sanitizeColor(config.userColor, "#6366f1");
98520
+ const { status, awareness, doc: doc2, clientId } = useYjsProvider({ config });
98521
+ const { remoteUsers, broadcastPresence } = usePresenceTracking({
98522
+ awareness,
98523
+ localClientId: clientId,
98524
+ userName: config.userName,
98525
+ userColor,
98526
+ userAvatar: config.userAvatar,
98527
+ canvasWidth,
98528
+ canvasHeight
98529
+ });
98530
+ const connectedCount = status === "connected" ? remoteUsers.length + 1 : remoteUsers.length;
98531
+ return {
98532
+ status,
98533
+ remoteUsers,
98534
+ broadcastPresence,
98535
+ connectedCount,
98536
+ config,
98537
+ doc: doc2
98538
+ };
98539
+ }
98540
+ var CollaborationContext = createContext(null);
98541
+ function useCollaboration() {
98542
+ return useContext(CollaborationContext);
98543
+ }
98544
+ function CollaborationProvider({
98545
+ config,
98546
+ canvasWidth,
98547
+ canvasHeight,
98548
+ children
98549
+ }) {
98550
+ const value = useCollaborativeState({
98551
+ config,
98552
+ canvasWidth,
98553
+ canvasHeight
98554
+ });
98555
+ return /* @__PURE__ */ jsx(CollaborationContext.Provider, { value, children });
98556
+ }
98557
+ function RemoteUserCursors({
98558
+ remoteUsers,
98559
+ activeSlideIndex,
98560
+ canvasWidth,
98561
+ canvasHeight
98562
+ }) {
98563
+ const visibleUsers = remoteUsers.filter((u2) => u2.activeSlideIndex === activeSlideIndex);
98564
+ if (visibleUsers.length === 0) {
98565
+ return null;
98566
+ }
98567
+ return /* @__PURE__ */ jsx(
98568
+ "svg",
98569
+ {
98570
+ "data-testid": "remote-user-cursors",
98571
+ className: "absolute inset-0 pointer-events-none",
98572
+ style: { zIndex: 9999 },
98573
+ width: canvasWidth,
98574
+ height: canvasHeight,
98575
+ viewBox: `0 0 ${canvasWidth} ${canvasHeight}`,
98576
+ "aria-hidden": "true",
98577
+ children: visibleUsers.map((user) => /* @__PURE__ */ jsxs(
98578
+ "g",
97246
98579
  {
97247
- type: "button",
97248
- disabled: !canMut,
97249
- onMouseDown: (e2) => e2.preventDefault(),
97250
- className: pill,
97251
- title: "Font color",
98580
+ transform: `translate(${user.cursorX}, ${user.cursorY})`,
98581
+ "data-testid": `remote-cursor-${user.clientId}`,
97252
98582
  children: [
97253
98583
  /* @__PURE__ */ jsx(
97254
- "svg",
98584
+ "path",
97255
98585
  {
97256
- className: ic2,
97257
- viewBox: "0 0 24 24",
97258
- fill: "none",
97259
- stroke: "currentColor",
97260
- strokeWidth: "2",
97261
- strokeLinecap: "round",
97262
- strokeLinejoin: "round",
97263
- children: /* @__PURE__ */ jsx("path", { d: "M6 20h12M9.5 4h5L18 16H6L9.5 4z" })
98586
+ d: "M0 0 L0 16 L4.5 12.5 L8 20 L10.5 19 L7 11.5 L12 11 Z",
98587
+ fill: user.userColor,
98588
+ stroke: "#fff",
98589
+ strokeWidth: 1,
98590
+ opacity: 0.9
97264
98591
  }
97265
98592
  ),
97266
- /* @__PURE__ */ jsx("div", { className: "w-4 h-1 rounded-sm -mt-0.5", style: { backgroundColor: currentColor } })
98593
+ /* @__PURE__ */ jsxs("g", { transform: "translate(14, 18)", children: [
98594
+ /* @__PURE__ */ jsx(
98595
+ "rect",
98596
+ {
98597
+ rx: 3,
98598
+ ry: 3,
98599
+ x: -2,
98600
+ y: -10,
98601
+ width: Math.min(user.userName.length * 7 + 8, 150),
98602
+ height: 16,
98603
+ fill: user.userColor,
98604
+ opacity: 0.85
98605
+ }
98606
+ ),
98607
+ /* @__PURE__ */ jsx(
98608
+ "text",
98609
+ {
98610
+ fill: "#fff",
98611
+ fontSize: 10,
98612
+ fontFamily: "system-ui, sans-serif",
98613
+ fontWeight: 500,
98614
+ dominantBaseline: "central",
98615
+ y: -2,
98616
+ x: 2,
98617
+ children: user.userName.length > 20 ? `${user.userName.slice(0, 18)}...` : user.userName
98618
+ }
98619
+ )
98620
+ ] })
97267
98621
  ]
98622
+ },
98623
+ user.clientId
98624
+ ))
98625
+ }
98626
+ );
98627
+ }
98628
+ function getInitials(name) {
98629
+ const parts = name.trim().split(/\s+/);
98630
+ if (parts.length >= 2) {
98631
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
98632
+ }
98633
+ return name.slice(0, 2).toUpperCase();
98634
+ }
98635
+ function AvatarCircle({
98636
+ name,
98637
+ color,
98638
+ avatar,
98639
+ isLocal
98640
+ }) {
98641
+ const { t: t2 } = useTranslation();
98642
+ const initials = getInitials(name);
98643
+ const title = isLocal ? t2("pptx.collaboration.youLabel", { name }) : name;
98644
+ return /* @__PURE__ */ jsx(
98645
+ "div",
98646
+ {
98647
+ 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",
98648
+ style: {
98649
+ backgroundColor: color,
98650
+ borderColor: isLocal ? "#fff" : color
98651
+ },
98652
+ title,
98653
+ "aria-label": title,
98654
+ children: avatar ? /* @__PURE__ */ jsx(
98655
+ "img",
98656
+ {
98657
+ src: avatar,
98658
+ alt: "",
98659
+ className: "w-full h-full rounded-full object-cover",
98660
+ onError: (e2) => {
98661
+ e2.target.style.display = "none";
98662
+ }
97268
98663
  }
97269
- ),
97270
- /* @__PURE__ */ jsx("div", { className: "absolute left-0 top-full z-50 hidden group-hover:block pt-1", children: /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border bg-popover backdrop-blur-lg shadow-2xl p-2 w-36", children: [
97271
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: FONT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsx(
97272
- "button",
98664
+ ) : initials
98665
+ }
98666
+ );
98667
+ }
98668
+ function UserAvatarBar({
98669
+ remoteUsers,
98670
+ localUserName,
98671
+ localUserColor,
98672
+ localUserAvatar,
98673
+ status,
98674
+ maxVisible = 5
98675
+ }) {
98676
+ const { t: t2 } = useTranslation();
98677
+ if (status === "disconnected" || status === "error") {
98678
+ return null;
98679
+ }
98680
+ const allUsers = [
98681
+ { name: localUserName, color: localUserColor, avatar: localUserAvatar, isLocal: true },
98682
+ ...remoteUsers.map((u2) => ({
98683
+ name: u2.userName,
98684
+ color: u2.userColor,
98685
+ avatar: u2.userAvatar,
98686
+ isLocal: false
98687
+ }))
98688
+ ];
98689
+ const visible = allUsers.slice(0, maxVisible);
98690
+ const overflow = allUsers.length - maxVisible;
98691
+ return /* @__PURE__ */ jsxs(
98692
+ "div",
98693
+ {
98694
+ "data-testid": "user-avatar-bar",
98695
+ className: "flex items-center px-2",
98696
+ "aria-label": t2("pptx.collaboration.usersConnected", { count: allUsers.length }),
98697
+ children: [
98698
+ visible.map((user, i3) => /* @__PURE__ */ jsx(
98699
+ AvatarCircle,
97273
98700
  {
97274
- type: "button",
97275
- className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentColor?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
97276
- style: { backgroundColor: c2 },
97277
- onMouseDown: (e2) => e2.preventDefault(),
97278
- onClick: () => handleColorChange(c2)
98701
+ name: user.name,
98702
+ color: user.color,
98703
+ avatar: user.avatar,
98704
+ isLocal: user.isLocal
97279
98705
  },
97280
- c2
97281
- )) }),
97282
- /* @__PURE__ */ jsx(
97283
- "button",
97284
- {
97285
- type: "button",
97286
- className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
97287
- onMouseDown: (e2) => e2.preventDefault(),
97288
- onClick: () => colorInputRef.current?.click(),
97289
- children: "Custom colour\u2026"
97290
- }
97291
- ),
97292
- /* @__PURE__ */ jsx(
97293
- "input",
98706
+ user.isLocal ? "local" : `remote-${i3}`
98707
+ )),
98708
+ overflow > 0 && /* @__PURE__ */ jsxs(
98709
+ "div",
97294
98710
  {
97295
- ref: colorInputRef,
97296
- type: "color",
97297
- className: "sr-only",
97298
- value: currentColor,
97299
- onChange: (e2) => handleColorChange(e2.target.value)
98711
+ 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",
98712
+ title: t2("pptx.collaboration.moreUsers", { count: overflow }),
98713
+ children: [
98714
+ "+",
98715
+ overflow
98716
+ ]
97300
98717
  }
97301
98718
  )
97302
- ] }) })
97303
- ] }),
97304
- /* @__PURE__ */ jsx("div", { className: grp, children: ATXT.map((b2, i3, a2) => {
97305
- const handleClick = () => {
97306
- if (!canFormat) {
97307
- return;
97308
- }
97309
- const alignMap = {
97310
- "Align left": "left",
97311
- "Align center": "center",
97312
- "Align right": "right",
97313
- Justify: "justify"
97314
- };
97315
- const align = alignMap[b2.t];
97316
- if (align) {
97317
- p3.onUpdateTextStyle({ align });
97318
- }
97319
- };
97320
- return /* @__PURE__ */ jsx(
97321
- "button",
97322
- {
97323
- type: "button",
97324
- disabled: !canMut,
97325
- onMouseDown: (e2) => e2.preventDefault(),
97326
- onClick: handleClick,
97327
- className: i3 < a2.length - 1 ? gB : gL,
97328
- title: b2.t,
97329
- children: b2.i
97330
- },
97331
- b2.t
97332
- );
97333
- }) })
97334
- ] });
98719
+ ]
98720
+ }
98721
+ );
98722
+ }
98723
+ var STATUS_STYLES = {
98724
+ connected: {
98725
+ dot: "bg-green-400",
98726
+ text: "text-green-400",
98727
+ label: "Connected"
98728
+ },
98729
+ connecting: {
98730
+ dot: "bg-yellow-400 animate-pulse",
98731
+ text: "text-yellow-400",
98732
+ label: "Connecting..."
98733
+ },
98734
+ disconnected: {
98735
+ dot: "bg-gray-500",
98736
+ text: "text-gray-500",
98737
+ label: "Disconnected"
98738
+ },
98739
+ error: {
98740
+ dot: "bg-red-400",
98741
+ text: "text-red-400",
98742
+ label: "Connection error"
98743
+ }
98744
+ };
98745
+ function CollaborationStatusIndicator({
98746
+ status,
98747
+ connectedCount
98748
+ }) {
98749
+ const { t: t2 } = useTranslation();
98750
+ const style = STATUS_STYLES[status];
98751
+ return /* @__PURE__ */ jsxs(
98752
+ "div",
98753
+ {
98754
+ "data-testid": "collaboration-status",
98755
+ className: "flex items-center gap-1.5",
98756
+ "aria-label": t2("pptx.collaboration.statusAriaLabel", {
98757
+ status: t2(`pptx.collaboration.status.${status}`),
98758
+ count: connectedCount
98759
+ }),
98760
+ children: [
98761
+ /* @__PURE__ */ jsx("span", { className: `inline-block w-2 h-2 rounded-full ${style.dot}`, "aria-hidden": "true" }),
98762
+ /* @__PURE__ */ jsx("span", { className: `text-[10px] ${style.text}`, children: status === "connected" ? t2("pptx.collaboration.userCount", { count: connectedCount }) : t2(`pptx.collaboration.status.${status}`) })
98763
+ ]
98764
+ }
98765
+ );
97335
98766
  }
97336
98767
  function CustomShowsControls({
97337
98768
  customShows,
@@ -97579,7 +99010,6 @@ function ModeSwitcher({
97579
99010
  mode,
97580
99011
  onSetMode,
97581
99012
  onCloseMasterView,
97582
- onToggleSlideSorter,
97583
99013
  onEnterPresenterView,
97584
99014
  onEnterRehearsalMode,
97585
99015
  onOpenSetUpSlideShow,
@@ -97588,64 +99018,33 @@ function ModeSwitcher({
97588
99018
  showSubtitles
97589
99019
  }) {
97590
99020
  if (mode === "master") {
97591
- return /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2", children: [
97592
- /* @__PURE__ */ 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" }),
99021
+ return /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-1.5", children: [
99022
+ /* @__PURE__ */ 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" }),
97593
99023
  /* @__PURE__ */ jsx(
97594
99024
  "button",
97595
99025
  {
97596
99026
  type: "button",
97597
99027
  onClick: onCloseMasterView,
97598
- className: "px-2.5 py-1 rounded bg-muted hover:bg-accent text-[11px] text-foreground transition-colors",
99028
+ className: "px-2 py-0.5 rounded-sm hover:bg-accent text-[10px] text-foreground transition-colors",
97599
99029
  title: "Close master view",
97600
- children: "Close Master View"
99030
+ children: "Close"
97601
99031
  }
97602
99032
  )
97603
99033
  ] });
97604
99034
  }
97605
- return /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center rounded bg-muted text-[11px] overflow-hidden", children: [
97606
- MODES.map(
97607
- (m2) => m2 === "present" ? /* @__PURE__ */ jsx(
97608
- PresentDropdown,
97609
- {
97610
- isActive: mode === m2,
97611
- onPresent: () => onSetMode(m2),
97612
- onPresenterView: onEnterPresenterView,
97613
- onRehearse: onEnterRehearsalMode,
97614
- onSetUpSlideShow: onOpenSetUpSlideShow,
97615
- onBroadcast: onOpenBroadcastDialog,
97616
- onToggleSubtitles,
97617
- showSubtitles
97618
- },
97619
- m2
97620
- ) : /* @__PURE__ */ jsxs(
97621
- "button",
97622
- {
97623
- type: "button",
97624
- onClick: () => onSetMode(m2),
97625
- className: cn(
97626
- "px-2 py-1 transition-colors border-l border-border first:border-l-0",
97627
- mode === m2 ? "bg-primary text-primary-foreground" : "hover:bg-accent text-foreground"
97628
- ),
97629
- title: `${m2[0].toUpperCase()}${m2.slice(1)} mode`,
97630
- children: [
97631
- m2[0].toUpperCase(),
97632
- m2.slice(1)
97633
- ]
97634
- },
97635
- m2
97636
- )
97637
- ),
97638
- /* @__PURE__ */ jsx(
97639
- "button",
97640
- {
97641
- type: "button",
97642
- onClick: onToggleSlideSorter,
97643
- className: "px-2 py-1 border-l border-border hover:bg-accent text-foreground transition-colors",
97644
- title: "Slide sorter",
97645
- children: "Sorter"
97646
- }
97647
- )
97648
- ] });
99035
+ return /* @__PURE__ */ jsx(
99036
+ PresentDropdown,
99037
+ {
99038
+ isActive: mode === "present",
99039
+ onPresent: () => onSetMode("present"),
99040
+ onPresenterView: onEnterPresenterView,
99041
+ onRehearse: onEnterRehearsalMode,
99042
+ onSetUpSlideShow: onOpenSetUpSlideShow,
99043
+ onBroadcast: onOpenBroadcastDialog,
99044
+ onToggleSubtitles,
99045
+ showSubtitles
99046
+ }
99047
+ );
97649
99048
  }
97650
99049
  function OverflowMenu(p3) {
97651
99050
  const ovAct = (k2) => {
@@ -97713,14 +99112,12 @@ function OverflowMenu(p3) {
97713
99112
  ] });
97714
99113
  }
97715
99114
  function ToolbarPrimaryRow(p3) {
99115
+ const { t: t2 } = useTranslation();
97716
99116
  const {
97717
99117
  mode,
97718
99118
  canEdit,
97719
- isNarrowViewport,
97720
99119
  isSidebarCollapsed,
97721
99120
  isInspectorPaneOpen,
97722
- isCompactToolbarOpen,
97723
- scale,
97724
99121
  canUndo,
97725
99122
  canRedo,
97726
99123
  undoLabel,
@@ -97728,26 +99125,21 @@ function ToolbarPrimaryRow(p3) {
97728
99125
  findReplaceOpen,
97729
99126
  onToggleSidebar,
97730
99127
  onToggleInspector,
97731
- onToggleCompactToolbar,
97732
- onZoomIn,
97733
- onZoomOut,
97734
- onZoomToFit,
97735
99128
  onUndo,
97736
99129
  onRedo,
97737
99130
  onToggleFindReplace
97738
99131
  } = p3;
97739
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 max-md:gap-0.5 flex-wrap", children: [
99132
+ const collab = useCollaboration();
99133
+ 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";
99134
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 max-md:gap-0 px-1.5 py-0.5 max-md:px-1", children: [
97740
99135
  mode !== "present" && /* @__PURE__ */ jsx(
97741
99136
  "button",
97742
99137
  {
97743
99138
  type: "button",
97744
99139
  onClick: onToggleSidebar,
97745
- className: cn(
97746
- "p-1.5 max-md:p-2.5 max-md:min-h-[44px] max-md:min-w-[44px] rounded transition-colors",
97747
- !isSidebarCollapsed ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
97748
- ),
97749
- title: "Toggle slides panel",
97750
- "aria-label": "Toggle slides panel",
99140
+ className: cn(qab, !isSidebarCollapsed ? "text-foreground" : "text-muted-foreground"),
99141
+ title: t2("pptx.toolbar.toggleSlidesPanel"),
99142
+ "aria-label": t2("pptx.toolbar.toggleSlidesPanel"),
97751
99143
  children: /* @__PURE__ */ jsx(LuPanelLeft, { className: ic2 })
97752
99144
  }
97753
99145
  ),
@@ -97755,77 +99147,84 @@ function ToolbarPrimaryRow(p3) {
97755
99147
  /* @__PURE__ */ jsx(
97756
99148
  "button",
97757
99149
  {
97758
- onClick: onZoomOut,
97759
- 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",
97760
- title: "Zoom out",
97761
- "aria-label": "Zoom out",
97762
- children: /* @__PURE__ */ jsx(LuZoomOut, { className: ics })
97763
- }
97764
- ),
97765
- /* @__PURE__ */ jsxs(
97766
- "button",
97767
- {
97768
- onClick: onZoomToFit,
97769
- 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",
97770
- title: "Zoom to fit",
97771
- children: [
97772
- Math.round(scale * 100),
97773
- "%"
97774
- ]
99150
+ type: "button",
99151
+ onClick: onUndo,
99152
+ disabled: !canEdit || !canUndo,
99153
+ className: cn(qab, "text-muted-foreground"),
99154
+ title: undoLabel ? t2("pptx.toolbar.undoAction", { action: undoLabel }) : t2("pptx.toolbar.undo"),
99155
+ "aria-label": t2("pptx.toolbar.undo"),
99156
+ children: /* @__PURE__ */ jsx(LuUndo, { className: ics })
97775
99157
  }
97776
99158
  ),
97777
99159
  /* @__PURE__ */ jsx(
97778
99160
  "button",
97779
99161
  {
97780
- onClick: onZoomIn,
97781
- 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",
97782
- title: "Zoom in",
97783
- "aria-label": "Zoom in",
97784
- children: /* @__PURE__ */ jsx(LuZoomIn, { className: ics })
99162
+ type: "button",
99163
+ onClick: onRedo,
99164
+ disabled: !canEdit || !canRedo,
99165
+ className: cn(qab, "text-muted-foreground"),
99166
+ title: redoLabel ? t2("pptx.toolbar.redoAction", { action: redoLabel }) : t2("pptx.toolbar.redo"),
99167
+ "aria-label": t2("pptx.toolbar.redo"),
99168
+ children: /* @__PURE__ */ jsx(LuRedo, { className: ics })
97785
99169
  }
97786
99170
  ),
97787
- sep,
97788
- /* @__PURE__ */ jsxs("div", { className: grp, children: [
97789
- /* @__PURE__ */ jsx(
97790
- "button",
97791
- {
97792
- type: "button",
97793
- onClick: onUndo,
97794
- disabled: !canEdit || !canUndo,
97795
- className: gB,
97796
- title: undoLabel ? `Undo: ${undoLabel}` : "Undo",
97797
- "aria-label": "Undo",
97798
- children: /* @__PURE__ */ jsx(LuUndo, { className: ics })
97799
- }
97800
- ),
97801
- /* @__PURE__ */ jsx(
97802
- "button",
97803
- {
97804
- type: "button",
97805
- onClick: onRedo,
97806
- disabled: !canEdit || !canRedo,
97807
- className: gL,
97808
- title: redoLabel ? `Redo: ${redoLabel}` : "Redo",
97809
- "aria-label": "Redo",
97810
- children: /* @__PURE__ */ jsx(LuRedo, { className: ics })
97811
- }
97812
- )
97813
- ] }),
97814
99171
  (mode === "edit" || mode === "master") && /* @__PURE__ */ jsx(
97815
99172
  "button",
97816
99173
  {
97817
99174
  type: "button",
97818
99175
  onClick: onToggleFindReplace,
97819
99176
  className: cn(
97820
- "p-1.5 max-md:p-2.5 max-md:min-h-[44px] max-md:min-w-[44px] rounded transition-colors max-md:hidden",
97821
- findReplaceOpen ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
99177
+ qab,
99178
+ "max-md:hidden",
99179
+ findReplaceOpen ? "text-foreground" : "text-muted-foreground"
97822
99180
  ),
97823
- title: "Find & Replace",
97824
- "aria-label": "Find and Replace",
99181
+ title: t2("pptx.findReplace.title"),
99182
+ "aria-label": t2("pptx.findReplace.title"),
97825
99183
  children: /* @__PURE__ */ jsx(LuSearch, { className: ics })
97826
99184
  }
97827
99185
  ),
97828
99186
  /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-2 max-md:min-w-1" }),
99187
+ (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxs(
99188
+ "button",
99189
+ {
99190
+ type: "button",
99191
+ onClick: p3.onToggleComments,
99192
+ className: cn(
99193
+ qab,
99194
+ "max-md:hidden",
99195
+ p3.isCommentsPanelOpen ? "text-foreground" : "text-muted-foreground"
99196
+ ),
99197
+ title: t2("pptx.toolbar.comments"),
99198
+ "aria-label": t2("pptx.toolbar.comments"),
99199
+ children: [
99200
+ /* @__PURE__ */ jsx(LuMessageSquare, { className: ics }),
99201
+ (p3.slideCommentCount ?? 0) > 0 && /* @__PURE__ */ 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 })
99202
+ ]
99203
+ }
99204
+ ),
99205
+ collab && (collab.status === "connected" || collab.status === "connecting") && collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center -space-x-1.5 mx-1", children: [
99206
+ collab.remoteUsers.slice(0, 4).map((user) => /* @__PURE__ */ jsx(
99207
+ "div",
99208
+ {
99209
+ className: "w-6 h-6 rounded-full border-2 border-background flex items-center justify-center text-[8px] font-semibold text-white shrink-0",
99210
+ style: { backgroundColor: user.userColor },
99211
+ title: user.userName,
99212
+ children: user.userAvatar ? /* @__PURE__ */ jsx(
99213
+ "img",
99214
+ {
99215
+ src: user.userAvatar,
99216
+ alt: user.userName,
99217
+ className: "w-full h-full rounded-full object-cover"
99218
+ }
99219
+ ) : user.userName.slice(0, 2).toUpperCase()
99220
+ },
99221
+ user.clientId
99222
+ )),
99223
+ collab.remoteUsers.length > 4 && /* @__PURE__ */ 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: [
99224
+ "+",
99225
+ collab.remoteUsers.length - 4
99226
+ ] })
99227
+ ] }),
97829
99228
  /* @__PURE__ */ jsx(
97830
99229
  ModeSwitcher,
97831
99230
  {
@@ -97856,40 +99255,80 @@ function ToolbarPrimaryRow(p3) {
97856
99255
  }
97857
99256
  ),
97858
99257
  sep,
97859
- (mode === "edit" || mode === "master") && /* @__PURE__ */ jsx(
99258
+ (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxs(
97860
99259
  "button",
97861
99260
  {
97862
99261
  type: "button",
97863
- onClick: onToggleInspector,
99262
+ onClick: p3.onOpenShareDialog ?? p3.onPackageForSharing,
97864
99263
  className: cn(
97865
- "p-1.5 max-md:p-2.5 max-md:min-h-[44px] max-md:min-w-[44px] rounded transition-colors",
97866
- isInspectorPaneOpen ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
99264
+ "relative inline-flex items-center gap-1 px-2.5 py-1 rounded-sm text-[11px] font-medium transition-colors",
99265
+ collab && collab.status === "connected" ? "bg-green-600 hover:bg-green-500 text-white" : "bg-primary hover:bg-primary/90 text-primary-foreground"
97867
99266
  ),
97868
- title: "Toggle inspector panel",
97869
- "aria-label": "Toggle inspector panel",
99267
+ title: collab && collab.status === "connected" ? t2("pptx.toolbar.sharingUsers", { count: collab.connectedCount }) : t2("pptx.toolbar.share"),
99268
+ "aria-label": t2("pptx.toolbar.share"),
99269
+ children: [
99270
+ /* @__PURE__ */ jsx(LuShare2, { className: "w-3 h-3" }),
99271
+ /* @__PURE__ */ jsx("span", { className: "max-md:hidden", children: collab && collab.status === "connected" ? t2("pptx.toolbar.sharingCount", { count: collab.connectedCount }) : t2("pptx.toolbar.share") })
99272
+ ]
99273
+ }
99274
+ ),
99275
+ (mode === "edit" || mode === "master") && /* @__PURE__ */ jsx(
99276
+ "button",
99277
+ {
99278
+ type: "button",
99279
+ onClick: onToggleInspector,
99280
+ className: cn(qab, isInspectorPaneOpen ? "text-foreground" : "text-muted-foreground"),
99281
+ title: t2("pptx.toolbar.toggleInspector"),
99282
+ "aria-label": t2("pptx.toolbar.toggleInspector"),
97870
99283
  children: /* @__PURE__ */ jsx(LuPanelRight, { className: ic2 })
97871
99284
  }
97872
99285
  ),
97873
- !canEdit && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center px-2 py-1 rounded bg-amber-600/90 text-[11px] text-amber-50", children: "Read-only" }),
97874
- /* @__PURE__ */ jsx(OverflowMenu, { ...p3 }),
97875
- isNarrowViewport && (mode === "edit" || mode === "master") && /* @__PURE__ */ jsx(
99286
+ /* @__PURE__ */ jsx(
97876
99287
  "button",
97877
99288
  {
97878
99289
  type: "button",
97879
- onClick: onToggleCompactToolbar,
97880
- className: cn(
97881
- "p-1.5 rounded text-[11px] transition-colors",
97882
- isCompactToolbarOpen ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
97883
- ),
97884
- title: "Toggle editing tools",
97885
- children: isCompactToolbarOpen ? "Less" : "Tools"
99290
+ onClick: p3.onOpenSettings ?? p3.onToggleShortcuts,
99291
+ className: cn(qab, "text-muted-foreground"),
99292
+ title: t2("pptx.toolbar.settingsShortcuts"),
99293
+ "aria-label": t2("pptx.toolbar.settings"),
99294
+ children: /* @__PURE__ */ jsx(LuSettings, { className: ics })
97886
99295
  }
97887
- )
99296
+ ),
99297
+ !canEdit && /* @__PURE__ */ 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") }),
99298
+ /* @__PURE__ */ jsx(OverflowMenu, { ...p3 })
97888
99299
  ] });
97889
99300
  }
97890
99301
  function ViewSection(p3) {
97891
99302
  const { t: t2 } = useTranslation();
97892
99303
  return /* @__PURE__ */ jsxs(Fragment, { children: [
99304
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
99305
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
99306
+ /* @__PURE__ */ jsx("button", { className: pill, title: "Normal view", children: "Normal" }),
99307
+ p3.onToggleSlideSorter ? /* @__PURE__ */ jsx("button", { className: pill, onClick: p3.onToggleSlideSorter, title: "Slide Sorter view", children: "Slide Sorter" }) : /* @__PURE__ */ jsx("button", { className: pill, title: "Slide Sorter view", children: "Slide Sorter" }),
99308
+ /* @__PURE__ */ jsx("button", { className: pill, title: "Reading View", children: "Reading View" })
99309
+ ] }),
99310
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Presentation Views" })
99311
+ ] }),
99312
+ sep,
99313
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
99314
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-0.5", children: /* @__PURE__ */ jsx(
99315
+ "button",
99316
+ {
99317
+ onClick: p3.onEnterMasterView,
99318
+ disabled: !p3.canEdit,
99319
+ className: pill,
99320
+ title: "Edit slide masters and layouts",
99321
+ children: "Slide Master"
99322
+ }
99323
+ ) }),
99324
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Master Views" })
99325
+ ] }),
99326
+ sep,
99327
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
99328
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-0.5", children: p3.onZoomToFit && /* @__PURE__ */ jsx("button", { className: pill, onClick: p3.onZoomToFit, title: "Zoom to fit slide in window", children: "Zoom to Fit" }) }),
99329
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Zoom" })
99330
+ ] }),
99331
+ sep,
97893
99332
  /* @__PURE__ */ jsx(
97894
99333
  "button",
97895
99334
  {
@@ -97982,60 +99421,105 @@ function ViewSection(p3) {
97982
99421
  title: "Toggle spell check",
97983
99422
  children: "Spell"
97984
99423
  }
97985
- ),
97986
- sep,
97987
- /* @__PURE__ */ jsx(
97988
- "button",
97989
- {
97990
- onClick: p3.onEnterMasterView,
97991
- disabled: !p3.canEdit,
97992
- className: pill,
97993
- title: "Edit slide masters and layouts",
97994
- children: "Slide Master"
97995
- }
97996
99424
  )
97997
99425
  ] });
97998
99426
  }
97999
99427
  function Toolbar(p3) {
98000
99428
  const { mode, isNarrowViewport, isCompactToolbarOpen, toolbarSection, onSetToolbarSection } = p3;
99429
+ const sFil = toolbarSection === "file";
98001
99430
  const sHome = toolbarSection === "home";
98002
- const sIns = sHome || toolbarSection === "insert";
99431
+ const sIns = toolbarSection === "insert";
98003
99432
  const sTxt = sHome || toolbarSection === "text";
98004
99433
  const sArr = toolbarSection === "arrange";
98005
99434
  const sDrw = toolbarSection === "draw";
98006
99435
  const sDes = toolbarSection === "design";
98007
99436
  const sTrn = toolbarSection === "transitions";
99437
+ const sAni = toolbarSection === "animations";
99438
+ const sSlw = toolbarSection === "slideShow";
98008
99439
  const sRev = toolbarSection === "review";
98009
99440
  const sViw = toolbarSection === "view";
99441
+ const sHlp = toolbarSection === "help";
99442
+ const showRibbon = mode === "edit" || mode === "master";
98010
99443
  return /* @__PURE__ */ jsxs(
98011
99444
  "div",
98012
99445
  {
98013
99446
  role: "toolbar",
98014
99447
  "aria-label": "Presentation toolbar",
98015
- 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",
99448
+ className: "relative z-20 border-b border-border bg-secondary/50 overflow-visible",
98016
99449
  children: [
98017
99450
  /* @__PURE__ */ jsx(ToolbarPrimaryRow, { ...p3 }),
98018
- (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxs(
99451
+ showRibbon && /* @__PURE__ */ jsxs("div", { className: "flex items-center border-b border-border/60 px-1 max-md:overflow-x-auto max-md:scrollbar-none", children: [
99452
+ TOOLBAR_SECTIONS.map((s) => /* @__PURE__ */ jsx(
99453
+ "button",
99454
+ {
99455
+ type: "button",
99456
+ onClick: () => onSetToolbarSection(s.id),
99457
+ className: cn(
99458
+ "relative px-3.5 py-2 text-[12px] font-medium whitespace-nowrap transition-colors max-md:min-h-[36px] max-md:px-3",
99459
+ 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"
99460
+ ),
99461
+ children: s.label
99462
+ },
99463
+ s.id
99464
+ )),
99465
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
99466
+ isNarrowViewport && /* @__PURE__ */ jsx(
99467
+ "button",
99468
+ {
99469
+ type: "button",
99470
+ onClick: p3.onToggleCompactToolbar,
99471
+ className: cn(
99472
+ "px-2 py-1 rounded text-[11px] transition-colors mr-1",
99473
+ isCompactToolbarOpen ? "bg-primary/80 text-primary-foreground" : "text-muted-foreground hover:text-foreground"
99474
+ ),
99475
+ title: "Toggle ribbon",
99476
+ children: isCompactToolbarOpen ? "Collapse" : "Expand"
99477
+ }
99478
+ )
99479
+ ] }),
99480
+ showRibbon && /* @__PURE__ */ jsxs(
98019
99481
  "div",
98020
99482
  {
98021
99483
  className: cn(
98022
- "flex items-center gap-1.5 flex-wrap mt-1.5",
99484
+ "flex items-center gap-1.5 px-2 py-1 max-md:px-1 max-md:py-0.5 overflow-visible flex-nowrap",
98023
99485
  isNarrowViewport && !isCompactToolbarOpen && "hidden"
98024
99486
  ),
98025
99487
  children: [
98026
- /* @__PURE__ */ 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__ */ jsx(
98027
- "button",
99488
+ sFil && /* @__PURE__ */ jsx(
99489
+ FileSection,
98028
99490
  {
98029
- type: "button",
98030
- onClick: () => onSetToolbarSection(s.id),
98031
- className: cn(
98032
- "rounded px-2 py-0.5 transition-colors whitespace-nowrap max-md:min-h-[36px] max-md:px-3",
98033
- toolbarSection === s.id ? "bg-primary text-primary-foreground" : "text-foreground hover:bg-accent"
98034
- ),
98035
- children: s.label
98036
- },
98037
- s.id
98038
- )) }),
99491
+ onExportPng: p3.onExportPng,
99492
+ onExportPdf: p3.onExportPdf,
99493
+ onExportVideo: p3.onExportVideo,
99494
+ onExportGif: p3.onExportGif,
99495
+ onPackageForSharing: p3.onPackageForSharing,
99496
+ onSaveAsPpsx: p3.onSaveAsPpsx,
99497
+ onSaveAsPptm: p3.onSaveAsPptm,
99498
+ hasMacros: p3.hasMacros,
99499
+ onCopySlideAsImage: p3.onCopySlideAsImage,
99500
+ onPrint: p3.onPrint,
99501
+ onOpenDocumentProperties: p3.onOpenDocumentProperties,
99502
+ onOpenPasswordProtection: p3.onOpenPasswordProtection,
99503
+ onOpenFontEmbedding: p3.onOpenFontEmbedding,
99504
+ onOpenDigitalSignatures: p3.onOpenDigitalSignatures
99505
+ }
99506
+ ),
99507
+ sHome && /* @__PURE__ */ jsx(
99508
+ HomeSection,
99509
+ {
99510
+ canEdit: p3.canEdit,
99511
+ clipboardPayload: p3.clipboardPayload,
99512
+ formatPainterActive: p3.formatPainterActive,
99513
+ onCopy: p3.onCopy,
99514
+ onCut: p3.onCut,
99515
+ onPaste: p3.onPaste,
99516
+ onToggleFormatPainter: p3.onToggleFormatPainter,
99517
+ layoutOptions: p3.layoutOptions,
99518
+ onInsertSlideFromLayout: p3.onInsertSlideFromLayout,
99519
+ selectedElement: p3.selectedElement,
99520
+ onUpdateTextStyle: p3.onUpdateTextStyle
99521
+ }
99522
+ ),
98039
99523
  sIns && /* @__PURE__ */ jsx(
98040
99524
  InsertSection,
98041
99525
  {
@@ -98098,7 +99582,10 @@ function Toolbar(p3) {
98098
99582
  onToggleThemeGallery: p3.onToggleThemeGallery,
98099
99583
  isThemeGalleryOpen: p3.isThemeGalleryOpen,
98100
99584
  onToggleThemeEditor: p3.onToggleThemeEditor,
98101
- isThemeEditorOpen: p3.isThemeEditorOpen
99585
+ isThemeEditorOpen: p3.isThemeEditorOpen,
99586
+ onOpenDocumentProperties: p3.onOpenDocumentProperties,
99587
+ onToggleInspector: p3.onToggleInspector,
99588
+ isInspectorPaneOpen: p3.isInspectorPaneOpen
98102
99589
  }
98103
99590
  ),
98104
99591
  sTrn && /* @__PURE__ */ jsx(
@@ -98108,6 +99595,33 @@ function Toolbar(p3) {
98108
99595
  onToggleInspector: p3.onToggleInspector
98109
99596
  }
98110
99597
  ),
99598
+ sAni && /* @__PURE__ */ jsx(
99599
+ AnimationsSection,
99600
+ {
99601
+ canEdit: p3.canEdit,
99602
+ selectedElement: p3.selectedElement,
99603
+ isInspectorPaneOpen: p3.isInspectorPaneOpen,
99604
+ onToggleInspector: p3.onToggleInspector
99605
+ }
99606
+ ),
99607
+ sSlw && /* @__PURE__ */ jsx(
99608
+ SlideShowSection,
99609
+ {
99610
+ onPresent: () => p3.onSetMode("present"),
99611
+ onEnterPresenterView: p3.onEnterPresenterView ?? (() => {
99612
+ }),
99613
+ onEnterRehearsalMode: p3.onEnterRehearsalMode ?? (() => {
99614
+ }),
99615
+ onOpenSetUpSlideShow: p3.onOpenSetUpSlideShow ?? (() => {
99616
+ }),
99617
+ onOpenBroadcastDialog: p3.onOpenBroadcastDialog ?? (() => {
99618
+ }),
99619
+ onToggleSubtitles: p3.onToggleSubtitles ?? (() => {
99620
+ }),
99621
+ showSubtitles: p3.showSubtitles ?? false,
99622
+ onSetMode: p3.onSetMode
99623
+ }
99624
+ ),
98111
99625
  sRev && /* @__PURE__ */ jsx(
98112
99626
  ReviewSection,
98113
99627
  {
@@ -98143,7 +99657,29 @@ function Toolbar(p3) {
98143
99657
  eyedropperActive: p3.eyedropperActive,
98144
99658
  onToggleEyedropper: p3.onToggleEyedropper
98145
99659
  }
98146
- )
99660
+ ),
99661
+ sHlp && /* @__PURE__ */ jsxs(Fragment, { children: [
99662
+ /* @__PURE__ */ jsx(
99663
+ "button",
99664
+ {
99665
+ type: "button",
99666
+ onClick: p3.onToggleShortcuts,
99667
+ className: pill,
99668
+ title: "Keyboard shortcuts",
99669
+ children: "Keyboard Shortcuts"
99670
+ }
99671
+ ),
99672
+ /* @__PURE__ */ jsx(
99673
+ "button",
99674
+ {
99675
+ type: "button",
99676
+ onClick: p3.onRunAccessibilityCheck,
99677
+ className: pill,
99678
+ title: "Accessibility check",
99679
+ children: "Accessibility"
99680
+ }
99681
+ )
99682
+ ] })
98147
99683
  ]
98148
99684
  }
98149
99685
  )
@@ -105564,6 +107100,333 @@ function BroadcastDialog({
105564
107100
  ] }) })
105565
107101
  ] });
105566
107102
  }
107103
+ function getInitials2(name) {
107104
+ const parts = name.trim().split(/\s+/);
107105
+ if (parts.length >= 2) {
107106
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
107107
+ }
107108
+ return name.slice(0, 2).toUpperCase();
107109
+ }
107110
+ function ShareDialog({
107111
+ open,
107112
+ onClose,
107113
+ activeCollaboration,
107114
+ onStartCollaboration,
107115
+ onStopCollaboration,
107116
+ preconfigured,
107117
+ defaultRoomId,
107118
+ defaultUserName,
107119
+ defaultServerUrl
107120
+ }) {
107121
+ const collab = useCollaboration();
107122
+ const isActive = collab !== null && collab.status !== "disconnected" && collab.status !== "error";
107123
+ const { t: t2 } = useTranslation();
107124
+ const [roomId, setRoomId] = useState(defaultRoomId ?? "");
107125
+ const [userName, setUserName] = useState(defaultUserName ?? "");
107126
+ const [serverUrl, setServerUrl] = useState(defaultServerUrl ?? "");
107127
+ const [copied, setCopied] = useState(false);
107128
+ const dialogRef = useRef(null);
107129
+ useEffect(() => {
107130
+ if (activeCollaboration) {
107131
+ setRoomId(activeCollaboration.roomId);
107132
+ setUserName(activeCollaboration.userName);
107133
+ setServerUrl(activeCollaboration.serverUrl);
107134
+ }
107135
+ }, [activeCollaboration]);
107136
+ useEffect(() => {
107137
+ if (!open) {
107138
+ return;
107139
+ }
107140
+ function handleKeyDown(e2) {
107141
+ if (e2.key === "Escape") {
107142
+ onClose();
107143
+ }
107144
+ }
107145
+ document.addEventListener("keydown", handleKeyDown);
107146
+ return () => document.removeEventListener("keydown", handleKeyDown);
107147
+ }, [open, onClose]);
107148
+ useEffect(() => {
107149
+ if (open && dialogRef.current) {
107150
+ dialogRef.current.focus();
107151
+ }
107152
+ }, [open]);
107153
+ const handleCopyRoomId = useCallback(() => {
107154
+ const config = activeCollaboration ?? { roomId, serverUrl };
107155
+ const shareUrl = typeof window !== "undefined" ? `${window.location.origin}${window.location.pathname}?room=${encodeURIComponent(config.roomId)}&server=${encodeURIComponent(config.serverUrl)}` : config.roomId;
107156
+ void navigator.clipboard.writeText(shareUrl).then(() => {
107157
+ setCopied(true);
107158
+ setTimeout(() => setCopied(false), 2e3);
107159
+ return void 0;
107160
+ });
107161
+ }, [activeCollaboration, roomId, serverUrl]);
107162
+ const handleStartSharing = useCallback(() => {
107163
+ if (!roomId.trim() || !userName.trim()) {
107164
+ return;
107165
+ }
107166
+ onStartCollaboration?.({
107167
+ roomId: roomId.trim(),
107168
+ serverUrl: serverUrl.trim(),
107169
+ userName: userName.trim()
107170
+ });
107171
+ }, [roomId, userName, serverUrl, onStartCollaboration]);
107172
+ const canStart = roomId.trim().length > 0 && userName.trim().length > 0 && serverUrl.trim().length > 0;
107173
+ if (!open) {
107174
+ return null;
107175
+ }
107176
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
107177
+ /* @__PURE__ */ jsx(
107178
+ "button",
107179
+ {
107180
+ type: "button",
107181
+ className: "fixed inset-0 z-[200] bg-black/50",
107182
+ "aria-label": t2("pptx.share.closeDialog"),
107183
+ onClick: onClose
107184
+ }
107185
+ ),
107186
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[201] flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs(
107187
+ "div",
107188
+ {
107189
+ ref: dialogRef,
107190
+ role: "dialog",
107191
+ "aria-modal": "true",
107192
+ "aria-label": t2("pptx.share.title"),
107193
+ tabIndex: -1,
107194
+ className: "pointer-events-auto w-full max-w-md rounded-xl border border-border bg-popover text-foreground shadow-2xl outline-none",
107195
+ children: [
107196
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-b border-border", children: [
107197
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: isActive ? t2("pptx.share.collaborationActive") : t2("pptx.share.title") }),
107198
+ /* @__PURE__ */ jsx(
107199
+ "button",
107200
+ {
107201
+ type: "button",
107202
+ onClick: onClose,
107203
+ className: "text-muted-foreground hover:text-foreground text-lg leading-none",
107204
+ "aria-label": t2("pptx.share.close"),
107205
+ children: "\xD7"
107206
+ }
107207
+ )
107208
+ ] }),
107209
+ /* @__PURE__ */ jsx("div", { className: "px-5 py-4", children: isActive ? /* @__PURE__ */ jsx(
107210
+ ActiveSessionView,
107211
+ {
107212
+ collab,
107213
+ activeCollaboration,
107214
+ copied,
107215
+ onCopyRoomId: handleCopyRoomId,
107216
+ onStopCollaboration
107217
+ }
107218
+ ) : /* @__PURE__ */ jsx(
107219
+ StartSessionForm,
107220
+ {
107221
+ roomId,
107222
+ userName,
107223
+ serverUrl,
107224
+ onRoomIdChange: setRoomId,
107225
+ onUserNameChange: setUserName,
107226
+ onServerUrlChange: setServerUrl,
107227
+ preconfigured
107228
+ }
107229
+ ) }),
107230
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-5 py-3 border-t border-border", children: [
107231
+ /* @__PURE__ */ jsx(
107232
+ "button",
107233
+ {
107234
+ type: "button",
107235
+ onClick: onClose,
107236
+ className: "px-3 py-1.5 rounded bg-muted hover:bg-accent text-[12px] text-foreground transition-colors",
107237
+ children: isActive ? t2("pptx.share.close") : t2("pptx.share.cancel")
107238
+ }
107239
+ ),
107240
+ !isActive && /* @__PURE__ */ jsx(
107241
+ "button",
107242
+ {
107243
+ type: "button",
107244
+ disabled: !canStart,
107245
+ onClick: handleStartSharing,
107246
+ 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",
107247
+ children: t2("pptx.share.startSharing")
107248
+ }
107249
+ )
107250
+ ] })
107251
+ ]
107252
+ }
107253
+ ) })
107254
+ ] });
107255
+ }
107256
+ function StartSessionForm({
107257
+ roomId,
107258
+ userName,
107259
+ serverUrl,
107260
+ onRoomIdChange,
107261
+ onUserNameChange,
107262
+ onServerUrlChange,
107263
+ preconfigured
107264
+ }) {
107265
+ const { t: t2 } = useTranslation();
107266
+ const inputReadOnlyClass = preconfigured ? " opacity-70 cursor-not-allowed" : "";
107267
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
107268
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] text-muted-foreground leading-relaxed", children: preconfigured ? t2("pptx.share.preconfiguredDescription") : t2("pptx.share.description") }),
107269
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
107270
+ /* @__PURE__ */ jsx("label", { htmlFor: "share-room-id", className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.sessionName") }),
107271
+ /* @__PURE__ */ jsx(
107272
+ "input",
107273
+ {
107274
+ id: "share-room-id",
107275
+ type: "text",
107276
+ value: roomId,
107277
+ onChange: (e2) => onRoomIdChange(e2.target.value),
107278
+ readOnly: preconfigured,
107279
+ placeholder: t2("pptx.share.sessionPlaceholder"),
107280
+ 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}`
107281
+ }
107282
+ ),
107283
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("pptx.share.sessionHint") })
107284
+ ] }),
107285
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
107286
+ /* @__PURE__ */ jsx("label", { htmlFor: "share-user-name", className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.displayName") }),
107287
+ /* @__PURE__ */ jsx(
107288
+ "input",
107289
+ {
107290
+ id: "share-user-name",
107291
+ type: "text",
107292
+ value: userName,
107293
+ onChange: (e2) => onUserNameChange(e2.target.value),
107294
+ readOnly: preconfigured,
107295
+ placeholder: t2("pptx.share.namePlaceholder"),
107296
+ 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}`
107297
+ }
107298
+ )
107299
+ ] }),
107300
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
107301
+ /* @__PURE__ */ jsx("label", { htmlFor: "share-server-url", className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.serverLabel") }),
107302
+ /* @__PURE__ */ jsx(
107303
+ "input",
107304
+ {
107305
+ id: "share-server-url",
107306
+ type: "text",
107307
+ value: serverUrl,
107308
+ onChange: (e2) => onServerUrlChange(e2.target.value),
107309
+ readOnly: preconfigured,
107310
+ placeholder: t2("pptx.share.serverPlaceholder"),
107311
+ 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}`
107312
+ }
107313
+ )
107314
+ ] }),
107315
+ /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-muted-foreground/70 leading-relaxed", children: [
107316
+ "Run",
107317
+ " ",
107318
+ /* @__PURE__ */ jsx("code", { className: "px-1 py-0.5 rounded bg-muted text-[10px] font-mono", children: "bun run collab" }),
107319
+ " ",
107320
+ "to start the server. Others can join at",
107321
+ " ",
107322
+ /* @__PURE__ */ jsx("code", { className: "px-1 py-0.5 rounded bg-muted text-[10px] font-mono", children: "?room=SESSION_NAME" })
107323
+ ] })
107324
+ ] });
107325
+ }
107326
+ function ActiveSessionView({
107327
+ collab,
107328
+ activeCollaboration,
107329
+ copied,
107330
+ onCopyRoomId,
107331
+ onStopCollaboration
107332
+ }) {
107333
+ const { t: t2 } = useTranslation();
107334
+ const statusColor = collab.status === "connected" ? "text-green-400" : collab.status === "connecting" ? "text-yellow-400" : "text-red-400";
107335
+ const statusIcon = collab.status === "connected" || collab.status === "connecting" ? /* @__PURE__ */ jsx(LuWifi, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(LuWifiOff, { className: "w-4 h-4" });
107336
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
107337
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
107338
+ /* @__PURE__ */ jsx("span", { className: statusColor, children: statusIcon }),
107339
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] font-medium text-foreground capitalize", children: collab.status }),
107340
+ /* @__PURE__ */ jsxs("span", { className: "text-[12px] text-muted-foreground ml-auto flex items-center gap-1", children: [
107341
+ /* @__PURE__ */ jsx(LuUsers, { className: "w-3.5 h-3.5" }),
107342
+ t2("pptx.collaboration.userCount", { count: collab.connectedCount })
107343
+ ] })
107344
+ ] }),
107345
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
107346
+ /* @__PURE__ */ jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.shareLink") }),
107347
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
107348
+ /* @__PURE__ */ 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 }),
107349
+ /* @__PURE__ */ jsx(
107350
+ "button",
107351
+ {
107352
+ type: "button",
107353
+ onClick: onCopyRoomId,
107354
+ 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",
107355
+ title: t2("pptx.share.copyLink"),
107356
+ children: copied ? /* @__PURE__ */ jsxs(Fragment, { children: [
107357
+ /* @__PURE__ */ jsx(LuCheck, { className: "w-3.5 h-3.5 text-green-400" }),
107358
+ /* @__PURE__ */ jsx("span", { children: t2("pptx.share.copied") })
107359
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
107360
+ /* @__PURE__ */ jsx(LuCopy, { className: "w-3.5 h-3.5" }),
107361
+ /* @__PURE__ */ jsx("span", { children: t2("pptx.share.copyUrl") })
107362
+ ] })
107363
+ }
107364
+ )
107365
+ ] }),
107366
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("pptx.share.shareHint") })
107367
+ ] }),
107368
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-[11px] text-muted-foreground", children: [
107369
+ /* @__PURE__ */ jsxs("span", { children: [
107370
+ t2("pptx.share.room"),
107371
+ " ",
107372
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: collab.config.roomId })
107373
+ ] }),
107374
+ /* @__PURE__ */ jsxs("span", { children: [
107375
+ t2("pptx.share.server"),
107376
+ " ",
107377
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: collab.config.serverUrl })
107378
+ ] })
107379
+ ] }),
107380
+ collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
107381
+ /* @__PURE__ */ jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.connectedUsers") }),
107382
+ /* @__PURE__ */ jsxs("div", { className: "rounded border border-border bg-background divide-y divide-border max-h-[140px] overflow-y-auto", children: [
107383
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
107384
+ /* @__PURE__ */ jsx(
107385
+ "div",
107386
+ {
107387
+ className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
107388
+ style: { backgroundColor: collab.config.userColor ?? "#6366f1" },
107389
+ children: getInitials2(activeCollaboration?.userName ?? collab.config.userName)
107390
+ }
107391
+ ),
107392
+ /* @__PURE__ */ jsx("span", { className: "text-[12px] text-foreground truncate", children: activeCollaboration?.userName ?? collab.config.userName }),
107393
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground ml-auto", children: t2("pptx.share.you") })
107394
+ ] }),
107395
+ collab.remoteUsers.map((user) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
107396
+ /* @__PURE__ */ jsx(
107397
+ "div",
107398
+ {
107399
+ className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
107400
+ style: { backgroundColor: user.userColor },
107401
+ children: user.userAvatar ? /* @__PURE__ */ jsx(
107402
+ "img",
107403
+ {
107404
+ src: user.userAvatar,
107405
+ alt: "",
107406
+ className: "w-full h-full rounded-full object-cover"
107407
+ }
107408
+ ) : getInitials2(user.userName)
107409
+ }
107410
+ ),
107411
+ /* @__PURE__ */ jsx("span", { className: "text-[12px] text-foreground truncate", children: user.userName }),
107412
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground ml-auto", children: [
107413
+ "Slide ",
107414
+ user.activeSlideIndex + 1
107415
+ ] })
107416
+ ] }, user.clientId))
107417
+ ] })
107418
+ ] }),
107419
+ onStopCollaboration && /* @__PURE__ */ jsx(
107420
+ "button",
107421
+ {
107422
+ type: "button",
107423
+ onClick: onStopCollaboration,
107424
+ 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",
107425
+ children: t2("pptx.share.stopSharing")
107426
+ }
107427
+ )
107428
+ ] });
107429
+ }
105567
107430
 
105568
107431
  // src/viewer/components/hyperlink-edit-types.ts
105569
107432
  var ACTION_VERB_MAP = {
@@ -107464,7 +109327,14 @@ function ViewerBottomPanels({
107464
109327
  onUpdateNotes,
107465
109328
  collaborationSlot,
107466
109329
  notesPanelHeight,
107467
- onResizeBottom
109330
+ onResizeBottom,
109331
+ scale,
109332
+ onZoomIn,
109333
+ onZoomOut,
109334
+ onZoomToFit,
109335
+ mode,
109336
+ onSetMode,
109337
+ onToggleSlideSorter
107468
109338
  }) {
107469
109339
  return /* @__PURE__ */ jsxs(Fragment, { children: [
107470
109340
  onResizeBottom && !isSlideNotesCollapsed && /* @__PURE__ */ jsx(ResizeHandle, { direction: "vertical", onResize: onResizeBottom }),
@@ -107480,18 +109350,25 @@ function ViewerBottomPanels({
107480
109350
  panelHeight: notesPanelHeight
107481
109351
  }
107482
109352
  ),
107483
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
107484
- /* @__PURE__ */ jsx(
107485
- StatusBar,
107486
- {
107487
- slideCount,
107488
- activeSlideIndex,
107489
- isDirty,
107490
- autosaveStatus
107491
- }
107492
- ),
107493
- collaborationSlot
107494
- ] })
109353
+ /* @__PURE__ */ jsx(
109354
+ StatusBar,
109355
+ {
109356
+ slideCount,
109357
+ activeSlideIndex,
109358
+ isDirty,
109359
+ autosaveStatus,
109360
+ scale,
109361
+ onZoomIn,
109362
+ onZoomOut,
109363
+ onZoomToFit,
109364
+ isNotesExpanded: !isSlideNotesCollapsed,
109365
+ onToggleNotes,
109366
+ mode,
109367
+ onSetMode,
109368
+ onToggleSlideSorter,
109369
+ collaborationSlot
109370
+ }
109371
+ )
107495
109372
  ] });
107496
109373
  }
107497
109374
  function ViewerInspector({
@@ -107650,7 +109527,9 @@ function ViewerToolbarSection(props) {
107650
109527
  ops,
107651
109528
  onSetMode,
107652
109529
  onEnterPresenterView,
107653
- onEnterRehearsalMode
109530
+ onEnterRehearsalMode,
109531
+ onOpenSettings,
109532
+ onOpenShareDialog
107654
109533
  } = props;
107655
109534
  return /* @__PURE__ */ jsxs(Fragment, { children: [
107656
109535
  /* @__PURE__ */ jsx(
@@ -107729,12 +109608,14 @@ function ViewerToolbarSection(props) {
107729
109608
  onExportVideo: exportHandlers.handleExportVideo,
107730
109609
  onExportGif: exportHandlers.handleExportGif,
107731
109610
  onPackageForSharing: exportHandlers.handlePackageForSharing,
109611
+ onOpenShareDialog,
107732
109612
  onSaveAsPpsx: exportHandlers.handleSaveAsPpsx,
107733
109613
  onSaveAsPptm: exportHandlers.handleSaveAsPptm,
107734
109614
  hasMacros: s.hasMacros,
107735
109615
  onCopySlideAsImage: exportHandlers.handleCopySlideAsImage,
107736
109616
  onPrint: printHandlers.handlePrint,
107737
109617
  onToggleShortcuts: () => s.setIsShortcutHelpOpen((p3) => !p3),
109618
+ onOpenSettings,
107738
109619
  onRunAccessibilityCheck: dialogs.handleRunAccessibilityCheck,
107739
109620
  onToggleSlideSorter: () => s.setShowSlideSorter((p3) => !p3),
107740
109621
  onUpdateTextStyle: ops.updateSelectedTextStyle,
@@ -107764,7 +109645,12 @@ function ViewerToolbarSection(props) {
107764
109645
  onToggleThemeGallery: () => s.setIsThemeGalleryOpen((p3) => !p3),
107765
109646
  isThemeGalleryOpen: s.isThemeGalleryOpen,
107766
109647
  onCompare: propertyHandlers.handleCompare,
107767
- onToggleComments: () => s.setIsInspectorPaneOpen((p3) => !p3),
109648
+ onToggleComments: () => {
109649
+ s.setSidebarPanelMode("comments");
109650
+ if (!s.isInspectorPaneOpen) {
109651
+ s.setIsInspectorPaneOpen(true);
109652
+ }
109653
+ },
107768
109654
  isCommentsPanelOpen: s.isInspectorPaneOpen,
107769
109655
  slideCommentCount: activeSlide?.comments?.length ?? 0,
107770
109656
  formatPainterActive: s.formatPainterActive,
@@ -109511,7 +111397,7 @@ function ViewerCanvasArea(props) {
109511
111397
  const handleToolbarMouseLeave = useCallback(() => {
109512
111398
  toolbarHoveringRef.current = false;
109513
111399
  }, []);
109514
- return /* @__PURE__ */ jsxs("main", { "aria-label": "Slide editor", className: "flex-1 min-w-0 relative flex flex-col", children: [
111400
+ return /* @__PURE__ */ jsxs("main", { "aria-label": "Slide editor", className: "flex-1 min-w-0 relative flex flex-col bg-background", children: [
109515
111401
  findReplace.findReplaceOpen && /* @__PURE__ */ jsx(
109516
111402
  FindReplacePanel,
109517
111403
  {
@@ -109881,7 +111767,7 @@ var PRESET_THEMES = [
109881
111767
  minorFont: "Gill Sans MT"
109882
111768
  }
109883
111769
  ];
109884
- var COMMON_FONTS = [
111770
+ var COMMON_FONTS2 = [
109885
111771
  "Arial",
109886
111772
  "Calibri",
109887
111773
  "Calibri Light",
@@ -109986,15 +111872,15 @@ function ThemeColorSchemeEditor({
109986
111872
  {
109987
111873
  className: THEME_EDITOR_INPUT,
109988
111874
  disabled: !canEdit,
109989
- value: COMMON_FONTS.includes(majorFont) ? majorFont : "__custom__",
111875
+ value: COMMON_FONTS2.includes(majorFont) ? majorFont : "__custom__",
109990
111876
  onChange: (e2) => {
109991
111877
  if (e2.target.value !== "__custom__") {
109992
111878
  onMajorFontChange(e2.target.value);
109993
111879
  }
109994
111880
  },
109995
111881
  children: [
109996
- COMMON_FONTS.map((f) => /* @__PURE__ */ jsx("option", { value: f, children: f }, f)),
109997
- !COMMON_FONTS.includes(majorFont) && /* @__PURE__ */ jsx("option", { value: "__custom__", children: majorFont })
111882
+ COMMON_FONTS2.map((f) => /* @__PURE__ */ jsx("option", { value: f, children: f }, f)),
111883
+ !COMMON_FONTS2.includes(majorFont) && /* @__PURE__ */ jsx("option", { value: "__custom__", children: majorFont })
109998
111884
  ]
109999
111885
  }
110000
111886
  )
@@ -110006,15 +111892,15 @@ function ThemeColorSchemeEditor({
110006
111892
  {
110007
111893
  className: THEME_EDITOR_INPUT,
110008
111894
  disabled: !canEdit,
110009
- value: COMMON_FONTS.includes(minorFont) ? minorFont : "__custom__",
111895
+ value: COMMON_FONTS2.includes(minorFont) ? minorFont : "__custom__",
110010
111896
  onChange: (e2) => {
110011
111897
  if (e2.target.value !== "__custom__") {
110012
111898
  onMinorFontChange(e2.target.value);
110013
111899
  }
110014
111900
  },
110015
111901
  children: [
110016
- COMMON_FONTS.map((f) => /* @__PURE__ */ jsx("option", { value: f, children: f }, f)),
110017
- !COMMON_FONTS.includes(minorFont) && /* @__PURE__ */ jsx("option", { value: "__custom__", children: minorFont })
111902
+ COMMON_FONTS2.map((f) => /* @__PURE__ */ jsx("option", { value: f, children: f }, f)),
111903
+ !COMMON_FONTS2.includes(minorFont) && /* @__PURE__ */ jsx("option", { value: "__custom__", children: minorFont })
110018
111904
  ]
110019
111905
  }
110020
111906
  )
@@ -110880,486 +112766,334 @@ function ViewerMainContent(props) {
110880
112766
  activeSlideIndex,
110881
112767
  selectedElement,
110882
112768
  state: state2,
110883
- comments,
110884
- ops,
110885
- manipulation,
110886
- propertyHandlers,
110887
- themeHandlers,
110888
- history,
110889
- panelWidth: rightPanelWidth,
110890
- onResizeRight
110891
- }
110892
- )
110893
- ] });
110894
- }
110895
-
110896
- // src/viewer/hooks/collaboration/sanitize.ts
110897
- var ROOM_ID_REGEX = /^[a-zA-Z0-9_-]{1,128}$/;
110898
- function validateRoomId(roomId) {
110899
- if (!ROOM_ID_REGEX.test(roomId)) {
110900
- throw new Error(
110901
- `Invalid collaboration room ID: "${roomId}". Must be 1-128 alphanumeric characters, hyphens, or underscores.`
110902
- );
110903
- }
110904
- return roomId;
110905
- }
110906
- function sanitizeUserName(name) {
110907
- if (typeof name !== "string") {
110908
- return "Anonymous";
110909
- }
110910
- const stripped = name.replace(/<[^>]*>/g, "");
110911
- const trimmed = stripped.trim().slice(0, 64);
110912
- return trimmed || "Anonymous";
110913
- }
110914
- function clampCursorPosition(value, min2, max2) {
110915
- if (typeof value !== "number" || !Number.isFinite(value)) {
110916
- return 0;
110917
- }
110918
- const margin = 20;
110919
- return Math.max(min2 - margin, Math.min(max2 + margin, value));
110920
- }
110921
- var HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
110922
- function sanitizeColor(color, fallback = "#6366f1") {
110923
- if (typeof color !== "string") {
110924
- return fallback;
110925
- }
110926
- return HEX_COLOR_REGEX.test(color) ? color : fallback;
110927
- }
110928
- function sanitizeAvatarUrl(url) {
110929
- if (typeof url !== "string") {
110930
- return void 0;
110931
- }
110932
- try {
110933
- const parsed = new URL(url);
110934
- if (parsed.protocol === "https:" || parsed.protocol === "http:" || parsed.protocol === "data:") {
110935
- return url;
110936
- }
110937
- } catch {
110938
- }
110939
- return void 0;
110940
- }
110941
- function sanitizeSlideIndex(value) {
110942
- if (typeof value !== "number" || !Number.isFinite(value)) {
110943
- return 0;
110944
- }
110945
- return Math.max(0, Math.floor(value));
110946
- }
110947
- function sanitizePresence(raw, canvasWidth, canvasHeight) {
110948
- if (typeof raw.clientId !== "number") {
110949
- return null;
110950
- }
110951
- return {
110952
- clientId: raw.clientId,
110953
- userName: sanitizeUserName(raw.userName),
110954
- userAvatar: sanitizeAvatarUrl(raw.userAvatar),
110955
- userColor: sanitizeColor(raw.userColor),
110956
- activeSlideIndex: sanitizeSlideIndex(raw.activeSlideIndex),
110957
- cursorX: clampCursorPosition(raw.cursorX, 0, canvasWidth),
110958
- cursorY: clampCursorPosition(raw.cursorY, 0, canvasHeight),
110959
- lastUpdated: typeof raw.lastUpdated === "string" ? raw.lastUpdated : (/* @__PURE__ */ new Date()).toISOString(),
110960
- selectedElementId: typeof raw.selectedElementId === "string" ? raw.selectedElementId.slice(0, 128) : void 0
110961
- };
110962
- }
110963
- var BROADCAST_THROTTLE_MS = 50;
110964
- var STALE_PRESENCE_MS = 3e4;
110965
- function usePresenceTracking({
110966
- awareness,
110967
- localClientId,
110968
- userName,
110969
- userColor,
110970
- userAvatar,
110971
- canvasWidth,
110972
- canvasHeight
110973
- }) {
110974
- const [remoteUsers, setRemoteUsers] = useState([]);
110975
- const lastBroadcastRef = useRef(0);
110976
- const pendingBroadcastRef = useRef(null);
110977
- const latestLocalState = useRef({});
110978
- const broadcastPresence = useCallback(
110979
- (update2) => {
110980
- if (!awareness) {
110981
- return;
110982
- }
110983
- Object.assign(latestLocalState.current, update2);
110984
- const now = Date.now();
110985
- const elapsed = now - lastBroadcastRef.current;
110986
- const flush = () => {
110987
- const state2 = {
110988
- ...latestLocalState.current,
110989
- userName,
110990
- userColor,
110991
- userAvatar,
110992
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
110993
- };
110994
- awareness.setLocalStateField("presence", state2);
110995
- lastBroadcastRef.current = Date.now();
110996
- };
110997
- if (elapsed >= BROADCAST_THROTTLE_MS) {
110998
- if (pendingBroadcastRef.current) {
110999
- clearTimeout(pendingBroadcastRef.current);
111000
- pendingBroadcastRef.current = null;
111001
- }
111002
- flush();
111003
- } else if (!pendingBroadcastRef.current) {
111004
- pendingBroadcastRef.current = setTimeout(() => {
111005
- pendingBroadcastRef.current = null;
111006
- flush();
111007
- }, BROADCAST_THROTTLE_MS - elapsed);
111008
- }
111009
- },
111010
- [awareness, userName, userColor, userAvatar]
111011
- );
111012
- useEffect(() => {
111013
- if (!awareness || localClientId === null) {
111014
- return;
111015
- }
111016
- const handleChange = () => {
111017
- const now = Date.now();
111018
- const states = awareness.getStates();
111019
- const users = [];
111020
- states.forEach((state2, cid) => {
111021
- if (cid === localClientId) {
111022
- return;
111023
- }
111024
- const raw = state2?.presence;
111025
- if (!raw || typeof raw !== "object") {
111026
- return;
111027
- }
111028
- const sanitized = sanitizePresence({ ...raw, clientId: cid }, canvasWidth, canvasHeight);
111029
- if (!sanitized) {
111030
- return;
111031
- }
111032
- const updatedAt = new Date(sanitized.lastUpdated).getTime();
111033
- if (Number.isNaN(updatedAt) || now - updatedAt > STALE_PRESENCE_MS) {
111034
- return;
111035
- }
111036
- users.push(sanitized);
111037
- });
111038
- setRemoteUsers(users);
111039
- };
111040
- awareness.on("change", handleChange);
111041
- handleChange();
111042
- return () => {
111043
- awareness.off("change", handleChange);
111044
- };
111045
- }, [awareness, localClientId, canvasWidth, canvasHeight]);
111046
- useEffect(() => {
111047
- return () => {
111048
- if (pendingBroadcastRef.current) {
111049
- clearTimeout(pendingBroadcastRef.current);
111050
- }
111051
- };
111052
- }, []);
111053
- return { remoteUsers, broadcastPresence };
111054
- }
111055
- function useYjsProvider({ config }) {
111056
- const [status, setStatus] = useState("disconnected");
111057
- const [awareness, setAwareness] = useState(null);
111058
- const [doc2, setDoc] = useState(null);
111059
- const [clientId, setClientId] = useState(null);
111060
- const cleanupRef = useRef(null);
111061
- const init = useCallback(async () => {
111062
- const roomId = validateRoomId(config.roomId);
111063
- setStatus("connecting");
111064
- try {
111065
- const [Y, { WebsocketProvider: WebsocketProvider2 }] = await Promise.all([Promise.resolve().then(() => (init_yjs(), yjs_exports)), Promise.resolve().then(() => (init_y_websocket(), y_websocket_exports))]);
111066
- const yDoc = new Y.Doc();
111067
- const provider = new WebsocketProvider2(
111068
- config.serverUrl,
111069
- roomId,
111070
- yDoc,
111071
- // eslint-disable-line @typescript-eslint/no-explicit-any
111072
- {
111073
- params: config.authToken ? { token: config.authToken } : void 0
111074
- }
111075
- );
111076
- const handleStatus = (event) => {
111077
- if (event.status === "connected") {
111078
- setStatus("connected");
111079
- } else if (event.status === "disconnected") {
111080
- setStatus("disconnected");
111081
- }
111082
- };
111083
- provider.on("status", handleStatus);
111084
- if (provider.wsconnected) {
111085
- setStatus("connected");
112769
+ comments,
112770
+ ops,
112771
+ manipulation,
112772
+ propertyHandlers,
112773
+ themeHandlers,
112774
+ history,
112775
+ panelWidth: rightPanelWidth,
112776
+ onResizeRight
111086
112777
  }
111087
- setDoc(yDoc);
111088
- setAwareness(provider.awareness);
111089
- setClientId(provider.awareness.clientID);
111090
- cleanupRef.current = () => {
111091
- provider.off("status", handleStatus);
111092
- provider.destroy();
111093
- yDoc.destroy();
111094
- setDoc(null);
111095
- setAwareness(null);
111096
- setClientId(null);
111097
- setStatus("disconnected");
111098
- };
111099
- } catch (err) {
111100
- console.warn(
111101
- "[pptx-viewer] Collaboration packages not available:",
111102
- err instanceof Error ? err.message : err
111103
- );
111104
- setStatus("error");
111105
- }
111106
- }, [config.roomId, config.serverUrl, config.authToken]);
111107
- useEffect(() => {
111108
- init();
111109
- return () => {
111110
- cleanupRef.current?.();
111111
- cleanupRef.current = null;
111112
- };
111113
- }, [init]);
111114
- return { status, awareness, doc: doc2, clientId };
111115
- }
111116
-
111117
- // src/viewer/hooks/collaboration/useCollaborativeState.ts
111118
- function useCollaborativeState({
111119
- config,
111120
- canvasWidth,
111121
- canvasHeight
111122
- }) {
111123
- const userColor = sanitizeColor(config.userColor, "#6366f1");
111124
- const { status, awareness, clientId } = useYjsProvider({ config });
111125
- const { remoteUsers, broadcastPresence } = usePresenceTracking({
111126
- awareness,
111127
- localClientId: clientId,
111128
- userName: config.userName,
111129
- userColor,
111130
- userAvatar: config.userAvatar,
111131
- canvasWidth,
111132
- canvasHeight
111133
- });
111134
- const connectedCount = status === "connected" ? remoteUsers.length + 1 : remoteUsers.length;
111135
- return {
111136
- status,
111137
- remoteUsers,
111138
- broadcastPresence,
111139
- connectedCount,
111140
- config
111141
- };
111142
- }
111143
- var CollaborationContext = createContext(null);
111144
- function useCollaboration() {
111145
- return useContext(CollaborationContext);
112778
+ )
112779
+ ] });
111146
112780
  }
111147
- function CollaborationProvider({
111148
- config,
111149
- canvasWidth,
111150
- canvasHeight,
111151
- children
112781
+ function ToggleSwitch({
112782
+ label,
112783
+ enabled,
112784
+ onToggle
111152
112785
  }) {
111153
- const value = useCollaborativeState({
111154
- config,
111155
- canvasWidth,
111156
- canvasHeight
111157
- });
111158
- return /* @__PURE__ */ jsx(CollaborationContext.Provider, { value, children });
112786
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-2.5 px-3", children: [
112787
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: label }),
112788
+ /* @__PURE__ */ jsx(
112789
+ "button",
112790
+ {
112791
+ type: "button",
112792
+ onClick: onToggle,
112793
+ className: cn(
112794
+ "relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
112795
+ enabled ? "bg-primary" : "bg-muted-foreground/30"
112796
+ ),
112797
+ role: "switch",
112798
+ "aria-checked": enabled,
112799
+ "aria-label": label,
112800
+ children: /* @__PURE__ */ jsx(
112801
+ "span",
112802
+ {
112803
+ className: cn(
112804
+ "inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
112805
+ enabled ? "translate-x-[18px]" : "translate-x-[3px]"
112806
+ )
112807
+ }
112808
+ )
112809
+ }
112810
+ )
112811
+ ] });
111159
112812
  }
111160
- function RemoteUserCursors({
111161
- remoteUsers,
111162
- activeSlideIndex,
111163
- canvasWidth,
111164
- canvasHeight
112813
+ function SettingsDialog({
112814
+ isOpen,
112815
+ onClose,
112816
+ spellCheckEnabled = false,
112817
+ onSetSpellCheckEnabled,
112818
+ showGrid = false,
112819
+ onSetShowGrid,
112820
+ showRulers = false,
112821
+ onSetShowRulers,
112822
+ snapToGrid = false,
112823
+ onSetSnapToGrid,
112824
+ reducedMotion = false,
112825
+ onToggleReducedMotion
111165
112826
  }) {
111166
- const visibleUsers = remoteUsers.filter((u2) => u2.activeSlideIndex === activeSlideIndex);
111167
- if (visibleUsers.length === 0) {
111168
- return null;
111169
- }
111170
- return /* @__PURE__ */ jsx(
111171
- "svg",
111172
- {
111173
- "data-testid": "remote-user-cursors",
111174
- className: "absolute inset-0 pointer-events-none",
111175
- style: { zIndex: 9999 },
111176
- width: canvasWidth,
111177
- height: canvasHeight,
111178
- viewBox: `0 0 ${canvasWidth} ${canvasHeight}`,
111179
- "aria-hidden": "true",
111180
- children: visibleUsers.map((user) => /* @__PURE__ */ jsxs(
111181
- "g",
111182
- {
111183
- transform: `translate(${user.cursorX}, ${user.cursorY})`,
111184
- "data-testid": `remote-cursor-${user.clientId}`,
111185
- children: [
111186
- /* @__PURE__ */ jsx(
111187
- "path",
111188
- {
111189
- d: "M0 0 L0 16 L4.5 12.5 L8 20 L10.5 19 L7 11.5 L12 11 Z",
111190
- fill: user.userColor,
111191
- stroke: "#fff",
111192
- strokeWidth: 1,
111193
- opacity: 0.9
111194
- }
111195
- ),
111196
- /* @__PURE__ */ jsxs("g", { transform: "translate(14, 18)", children: [
111197
- /* @__PURE__ */ jsx(
111198
- "rect",
111199
- {
111200
- rx: 3,
111201
- ry: 3,
111202
- x: -2,
111203
- y: -10,
111204
- width: Math.min(user.userName.length * 7 + 8, 150),
111205
- height: 16,
111206
- fill: user.userColor,
111207
- opacity: 0.85
111208
- }
111209
- ),
111210
- /* @__PURE__ */ jsx(
111211
- "text",
111212
- {
111213
- fill: "#fff",
111214
- fontSize: 10,
111215
- fontFamily: "system-ui, sans-serif",
111216
- fontWeight: 500,
111217
- dominantBaseline: "central",
111218
- y: -2,
111219
- x: 2,
111220
- children: user.userName.length > 20 ? `${user.userName.slice(0, 18)}...` : user.userName
111221
- }
111222
- )
111223
- ] })
111224
- ]
111225
- },
111226
- user.clientId
111227
- ))
111228
- }
112827
+ const [activeTab, setActiveTab] = useState("general");
112828
+ const [autoSave, setAutoSave] = useState(true);
112829
+ const { t: t2 } = useTranslation();
112830
+ const handleKeyDown = useCallback(
112831
+ (e2) => {
112832
+ if (e2.key === "Escape") {
112833
+ onClose();
112834
+ }
112835
+ },
112836
+ [onClose]
111229
112837
  );
111230
- }
111231
- function getInitials(name) {
111232
- const parts = name.trim().split(/\s+/);
111233
- if (parts.length >= 2) {
111234
- return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
111235
- }
111236
- return name.slice(0, 2).toUpperCase();
111237
- }
111238
- function AvatarCircle({
111239
- name,
111240
- color,
111241
- avatar,
111242
- isLocal
111243
- }) {
111244
- const initials = getInitials(name);
111245
- const title = isLocal ? `${name} (you)` : name;
111246
- return /* @__PURE__ */ jsx(
111247
- "div",
111248
- {
111249
- 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",
111250
- style: {
111251
- backgroundColor: color,
111252
- borderColor: isLocal ? "#fff" : color
111253
- },
111254
- title,
111255
- "aria-label": title,
111256
- children: avatar ? /* @__PURE__ */ jsx(
111257
- "img",
111258
- {
111259
- src: avatar,
111260
- alt: "",
111261
- className: "w-full h-full rounded-full object-cover",
111262
- onError: (e2) => {
111263
- e2.target.style.display = "none";
111264
- }
111265
- }
111266
- ) : initials
112838
+ useEffect(() => {
112839
+ if (isOpen) {
112840
+ document.addEventListener("keydown", handleKeyDown);
112841
+ return () => document.removeEventListener("keydown", handleKeyDown);
111267
112842
  }
111268
- );
111269
- }
111270
- function UserAvatarBar({
111271
- remoteUsers,
111272
- localUserName,
111273
- localUserColor,
111274
- localUserAvatar,
111275
- status,
111276
- maxVisible = 5
111277
- }) {
111278
- if (status === "disconnected" || status === "error") {
112843
+ }, [isOpen, handleKeyDown]);
112844
+ if (!isOpen) {
111279
112845
  return null;
111280
112846
  }
111281
- const allUsers = [
111282
- { name: localUserName, color: localUserColor, avatar: localUserAvatar, isLocal: true },
111283
- ...remoteUsers.map((u2) => ({
111284
- name: u2.userName,
111285
- color: u2.userColor,
111286
- avatar: u2.userAvatar,
111287
- isLocal: false
111288
- }))
112847
+ const tabs = [
112848
+ { id: "general", label: t2("pptx.settings.general") },
112849
+ { id: "shortcuts", label: t2("pptx.settings.keyboardShortcuts") }
111289
112850
  ];
111290
- const visible = allUsers.slice(0, maxVisible);
111291
- const overflow = allUsers.length - maxVisible;
111292
- return /* @__PURE__ */ jsxs(
111293
- "div",
111294
- {
111295
- "data-testid": "user-avatar-bar",
111296
- className: "flex items-center px-2",
111297
- "aria-label": `${allUsers.length} user${allUsers.length !== 1 ? "s" : ""} connected`,
111298
- children: [
111299
- visible.map((user, i3) => /* @__PURE__ */ jsx(
111300
- AvatarCircle,
112851
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
112852
+ /* @__PURE__ */ jsx(
112853
+ "button",
112854
+ {
112855
+ type: "button",
112856
+ className: "fixed inset-0 z-50 bg-black/60",
112857
+ "aria-label": t2("pptx.settings.closeSettings"),
112858
+ onClick: onClose
112859
+ }
112860
+ ),
112861
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ 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: [
112862
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b border-border/60", children: [
112863
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
112864
+ /* @__PURE__ */ jsx(LuSettings, { className: "w-5 h-5 text-primary" }),
112865
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: t2("pptx.settings.title") })
112866
+ ] }),
112867
+ /* @__PURE__ */ jsx(
112868
+ "button",
111301
112869
  {
111302
- name: user.name,
111303
- color: user.color,
111304
- avatar: user.avatar,
111305
- isLocal: user.isLocal
111306
- },
111307
- user.isLocal ? "local" : `remote-${i3}`
111308
- )),
111309
- overflow > 0 && /* @__PURE__ */ jsxs(
112870
+ type: "button",
112871
+ onClick: onClose,
112872
+ className: "p-1 rounded hover:bg-accent transition-colors",
112873
+ "aria-label": t2("pptx.settings.close"),
112874
+ children: /* @__PURE__ */ jsx(LuX, { className: "w-4 h-4 text-muted-foreground" })
112875
+ }
112876
+ )
112877
+ ] }),
112878
+ /* @__PURE__ */ jsx("div", { className: "flex border-b border-border/60 px-5", children: tabs.map((tab) => /* @__PURE__ */ jsxs(
112879
+ "button",
112880
+ {
112881
+ type: "button",
112882
+ onClick: () => setActiveTab(tab.id),
112883
+ className: cn(
112884
+ "px-3 py-2 text-xs font-medium transition-colors relative",
112885
+ activeTab === tab.id ? "text-primary" : "text-muted-foreground hover:text-foreground"
112886
+ ),
112887
+ children: [
112888
+ tab.label,
112889
+ activeTab === tab.id && /* @__PURE__ */ jsx("span", { className: "absolute bottom-0 left-0 right-0 h-0.5 bg-primary rounded-full" })
112890
+ ]
112891
+ },
112892
+ tab.id
112893
+ )) }),
112894
+ /* @__PURE__ */ jsxs("div", { className: "px-5 py-4 max-h-[60vh] overflow-y-auto", children: [
112895
+ activeTab === "general" && /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
112896
+ /* @__PURE__ */ jsx(
112897
+ ToggleSwitch,
112898
+ {
112899
+ label: t2("pptx.settings.autoSave"),
112900
+ enabled: autoSave,
112901
+ onToggle: () => setAutoSave(!autoSave)
112902
+ }
112903
+ ),
112904
+ /* @__PURE__ */ jsx(
112905
+ ToggleSwitch,
112906
+ {
112907
+ label: t2("pptx.settings.spellCheck"),
112908
+ enabled: spellCheckEnabled,
112909
+ onToggle: () => onSetSpellCheckEnabled?.(!spellCheckEnabled)
112910
+ }
112911
+ ),
112912
+ /* @__PURE__ */ jsx(
112913
+ ToggleSwitch,
112914
+ {
112915
+ label: t2("pptx.settings.showGrid"),
112916
+ enabled: showGrid,
112917
+ onToggle: () => onSetShowGrid?.(!showGrid)
112918
+ }
112919
+ ),
112920
+ /* @__PURE__ */ jsx(
112921
+ ToggleSwitch,
112922
+ {
112923
+ label: t2("pptx.settings.showRulers"),
112924
+ enabled: showRulers,
112925
+ onToggle: () => onSetShowRulers?.(!showRulers)
112926
+ }
112927
+ ),
112928
+ /* @__PURE__ */ jsx(
112929
+ ToggleSwitch,
112930
+ {
112931
+ label: t2("pptx.settings.snapToGrid"),
112932
+ enabled: snapToGrid,
112933
+ onToggle: () => onSetSnapToGrid?.(!snapToGrid)
112934
+ }
112935
+ ),
112936
+ /* @__PURE__ */ jsx(
112937
+ ToggleSwitch,
112938
+ {
112939
+ label: t2("pptx.settings.reducedMotion"),
112940
+ enabled: reducedMotion,
112941
+ onToggle: () => onToggleReducedMotion?.()
112942
+ }
112943
+ )
112944
+ ] }),
112945
+ activeTab === "shortcuts" && /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: SHORTCUT_REFERENCE_ITEMS.map((shortcut, i3) => /* @__PURE__ */ jsxs(
111310
112946
  "div",
111311
112947
  {
111312
- 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",
111313
- title: `${overflow} more user${overflow !== 1 ? "s" : ""}`,
112948
+ className: cn(
112949
+ "flex items-center justify-between gap-3 rounded px-3 py-2",
112950
+ i3 % 2 === 0 ? "bg-muted/60" : ""
112951
+ ),
111314
112952
  children: [
111315
- "+",
111316
- overflow
112953
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-foreground", children: shortcut.action }),
112954
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-muted-foreground whitespace-nowrap", children: shortcut.shortcut })
111317
112955
  ]
111318
- }
111319
- )
111320
- ]
112956
+ },
112957
+ shortcut.action
112958
+ )) })
112959
+ ] })
112960
+ ] }) })
112961
+ ] });
112962
+ }
112963
+ function useCollaborativeHistory({
112964
+ localClientId: _localClientId,
112965
+ handleUndo,
112966
+ handleRedo,
112967
+ canUndo,
112968
+ canRedo
112969
+ }) {
112970
+ const localChangeCountRef = useRef(0);
112971
+ const wrappedUndo = useCallback(() => {
112972
+ if (!canUndo) {
112973
+ return;
111321
112974
  }
111322
- );
112975
+ handleUndo();
112976
+ localChangeCountRef.current = Math.max(0, localChangeCountRef.current - 1);
112977
+ }, [handleUndo, canUndo]);
112978
+ const wrappedRedo = useCallback(() => {
112979
+ if (!canRedo) {
112980
+ return;
112981
+ }
112982
+ handleRedo();
112983
+ localChangeCountRef.current += 1;
112984
+ }, [handleRedo, canRedo]);
112985
+ return {
112986
+ handleUndo: wrappedUndo,
112987
+ handleRedo: wrappedRedo,
112988
+ canUndo,
112989
+ canRedo
112990
+ };
111323
112991
  }
111324
- var STATUS_STYLES = {
111325
- connected: {
111326
- dot: "bg-green-400",
111327
- text: "text-green-400",
111328
- label: "Connected"
111329
- },
111330
- connecting: {
111331
- dot: "bg-yellow-400 animate-pulse",
111332
- text: "text-yellow-400",
111333
- label: "Connecting..."
111334
- },
111335
- disconnected: {
111336
- dot: "bg-gray-500",
111337
- text: "text-gray-500",
111338
- label: "Disconnected"
111339
- },
111340
- error: {
111341
- dot: "bg-red-400",
111342
- text: "text-red-400",
111343
- label: "Connection error"
111344
- }
111345
- };
111346
- function CollaborationStatusIndicator({
111347
- status,
111348
- connectedCount
112992
+ function useYjsDocumentSync({
112993
+ doc: doc2,
112994
+ slides,
112995
+ setSlides,
112996
+ isConnected
111349
112997
  }) {
111350
- const style = STATUS_STYLES[status];
111351
- return /* @__PURE__ */ jsxs(
111352
- "div",
111353
- {
111354
- "data-testid": "collaboration-status",
111355
- className: "flex items-center gap-1.5",
111356
- "aria-label": `Collaboration: ${style.label}. ${connectedCount} user${connectedCount !== 1 ? "s" : ""} connected.`,
111357
- children: [
111358
- /* @__PURE__ */ jsx("span", { className: `inline-block w-2 h-2 rounded-full ${style.dot}`, "aria-hidden": "true" }),
111359
- /* @__PURE__ */ jsx("span", { className: `text-[10px] ${style.text}`, children: status === "connected" ? `${connectedCount} user${connectedCount !== 1 ? "s" : ""}` : style.label })
111360
- ]
112998
+ const isApplyingRemoteRef = useRef(false);
112999
+ const lastSyncedRef = useRef("");
113000
+ const hasInitializedRef = useRef(false);
113001
+ const getDocMap = useCallback(() => {
113002
+ if (!doc2 || typeof doc2 !== "object") {
113003
+ return null;
111361
113004
  }
111362
- );
113005
+ const d = doc2;
113006
+ if (typeof d.getMap !== "function") {
113007
+ return null;
113008
+ }
113009
+ return d.getMap("slides-data");
113010
+ }, [doc2]);
113011
+ useEffect(() => {
113012
+ if (!isConnected || !doc2) {
113013
+ return;
113014
+ }
113015
+ if (isApplyingRemoteRef.current) {
113016
+ return;
113017
+ }
113018
+ if (slides.length === 0) {
113019
+ return;
113020
+ }
113021
+ const map3 = getDocMap();
113022
+ if (!map3) {
113023
+ return;
113024
+ }
113025
+ const serialized = JSON.stringify(slides);
113026
+ if (serialized === lastSyncedRef.current) {
113027
+ return;
113028
+ }
113029
+ lastSyncedRef.current = serialized;
113030
+ const d = doc2;
113031
+ d.transact(() => {
113032
+ const currentCount = map3.get("count");
113033
+ if (currentCount && currentCount > slides.length) {
113034
+ for (let i3 = slides.length; i3 < currentCount; i3++) {
113035
+ map3.delete(`slide-${i3}`);
113036
+ }
113037
+ }
113038
+ map3.set("count", slides.length);
113039
+ for (let i3 = 0; i3 < slides.length; i3++) {
113040
+ const slideJson = JSON.stringify(slides[i3]);
113041
+ const existing = map3.get(`slide-${i3}`);
113042
+ if (existing !== slideJson) {
113043
+ map3.set(`slide-${i3}`, slideJson);
113044
+ }
113045
+ }
113046
+ });
113047
+ }, [doc2, slides, isConnected, getDocMap]);
113048
+ useEffect(() => {
113049
+ if (!isConnected || !doc2) {
113050
+ return;
113051
+ }
113052
+ const map3 = getDocMap();
113053
+ if (!map3) {
113054
+ return;
113055
+ }
113056
+ const handleUpdate = () => {
113057
+ const count = map3.get("count");
113058
+ if (!count || count === 0) {
113059
+ return;
113060
+ }
113061
+ const remoteSlides = [];
113062
+ for (let i3 = 0; i3 < count; i3++) {
113063
+ const slideJson = map3.get(`slide-${i3}`);
113064
+ if (slideJson) {
113065
+ try {
113066
+ remoteSlides.push(JSON.parse(slideJson));
113067
+ } catch {
113068
+ }
113069
+ }
113070
+ }
113071
+ if (remoteSlides.length === 0) {
113072
+ return;
113073
+ }
113074
+ const serialized = JSON.stringify(remoteSlides);
113075
+ if (serialized === lastSyncedRef.current) {
113076
+ return;
113077
+ }
113078
+ lastSyncedRef.current = serialized;
113079
+ isApplyingRemoteRef.current = true;
113080
+ setSlides(remoteSlides);
113081
+ requestAnimationFrame(() => {
113082
+ isApplyingRemoteRef.current = false;
113083
+ });
113084
+ };
113085
+ map3.observe(handleUpdate);
113086
+ if (!hasInitializedRef.current) {
113087
+ hasInitializedRef.current = true;
113088
+ const count = map3.get("count");
113089
+ if (count && count > 0) {
113090
+ handleUpdate();
113091
+ }
113092
+ }
113093
+ return () => {
113094
+ map3.unobserve(handleUpdate);
113095
+ };
113096
+ }, [doc2, isConnected, getDocMap, setSlides]);
111363
113097
  }
111364
113098
  function computeGridSpacingPx(presentationGridSpacing) {
111365
113099
  if (presentationGridSpacing) {
@@ -121061,7 +122795,7 @@ function useViewerUIState() {
121061
122795
  const [isCompactToolbarOpen, setIsCompactToolbarOpen] = useState(false);
121062
122796
  const [toolbarSection, setToolbarSection] = useState("home");
121063
122797
  const [isSlidesPaneOpen, setIsSlidesPaneOpen] = useState(true);
121064
- const [isInspectorPaneOpen, setIsInspectorPaneOpen] = useState(true);
122798
+ const [isInspectorPaneOpen, setIsInspectorPaneOpen] = useState(false);
121065
122799
  const [isSlideNotesCollapsed, setIsSlideNotesCollapsed] = useState(true);
121066
122800
  const [isOverflowMenuOpen, setIsOverflowMenuOpen] = useState(false);
121067
122801
  const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
@@ -121344,13 +123078,18 @@ var PowerPointViewer = forwardRef(
121344
123078
  onDirtyChange,
121345
123079
  onActiveSlideChange,
121346
123080
  theme,
121347
- collaboration
123081
+ collaboration,
123082
+ onStartCollaboration,
123083
+ onStopCollaboration,
123084
+ shareDefaults
121348
123085
  } = props;
121349
123086
  const themeStyle = useThemeStyle(theme);
121350
123087
  const [content, setContent] = useState(incomingContent);
121351
123088
  useEffect(() => {
121352
123089
  setContent(incomingContent);
121353
123090
  }, [incomingContent]);
123091
+ const [isSettingsOpen, setIsSettingsOpen] = useState(false);
123092
+ const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);
121354
123093
  const { reducedMotion, toggleReducedMotion } = useReducedMotion();
121355
123094
  const state2 = useViewerState();
121356
123095
  const {
@@ -121551,36 +123290,35 @@ var PowerPointViewer = forwardRef(
121551
123290
  ref: containerRef,
121552
123291
  tabIndex: 0,
121553
123292
  style: themeStyle,
123293
+ "data-pptx-viewer": "",
121554
123294
  className: "h-full w-full bg-background text-foreground flex flex-col relative overflow-hidden outline-none",
121555
123295
  children: [
121556
- /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-b from-purple-500/3 to-transparent z-0" }),
121557
- mode !== "present" && /* @__PURE__ */ jsxs(Fragment, { children: [
121558
- /* @__PURE__ */ jsx(
121559
- ViewerToolbarSection,
121560
- {
121561
- mode,
121562
- canEdit,
121563
- state: state2,
121564
- selectedElement,
121565
- activeSlide,
121566
- zoom,
121567
- history,
121568
- findReplace: editorOps.findReplace,
121569
- manipulation: editorOps.manipulation,
121570
- insertHandlers: editorOps.insertHandlers,
121571
- exportHandlers,
121572
- printHandlers,
121573
- propertyHandlers,
121574
- dialogs,
121575
- slideOps: editorOps.slideOps,
121576
- ops: editorOps.ops,
121577
- onSetMode: handleSetMode,
121578
- onEnterPresenterView: handleEnterPresenterView,
121579
- onEnterRehearsalMode: handleEnterRehearsalMode
121580
- }
121581
- ),
121582
- collaboration && /* @__PURE__ */ jsx(CollaborationToolbarStrip, { collaboration })
121583
- ] }),
123296
+ mode !== "present" && /* @__PURE__ */ jsx(
123297
+ ViewerToolbarSection,
123298
+ {
123299
+ mode,
123300
+ canEdit,
123301
+ state: state2,
123302
+ selectedElement,
123303
+ activeSlide,
123304
+ zoom,
123305
+ history,
123306
+ findReplace: editorOps.findReplace,
123307
+ manipulation: editorOps.manipulation,
123308
+ insertHandlers: editorOps.insertHandlers,
123309
+ exportHandlers,
123310
+ printHandlers,
123311
+ propertyHandlers,
123312
+ dialogs,
123313
+ slideOps: editorOps.slideOps,
123314
+ ops: editorOps.ops,
123315
+ onSetMode: handleSetMode,
123316
+ onEnterPresenterView: handleEnterPresenterView,
123317
+ onEnterRehearsalMode: handleEnterRehearsalMode,
123318
+ onOpenSettings: () => setIsSettingsOpen(true),
123319
+ onOpenShareDialog: () => setIsShareDialogOpen(true)
123320
+ }
123321
+ ),
121584
123322
  /* @__PURE__ */ jsx(
121585
123323
  ViewerMainContent,
121586
123324
  {
@@ -121630,7 +123368,14 @@ var PowerPointViewer = forwardRef(
121630
123368
  onUpdateNotes: propertyHandlers.handleUpdateNotes,
121631
123369
  collaborationSlot: collaboration ? /* @__PURE__ */ jsx(CollaborationStatusStrip, {}) : void 0,
121632
123370
  notesPanelHeight: isMobile ? void 0 : resizablePanels.bottomHeight,
121633
- onResizeBottom: isMobile ? void 0 : resizablePanels.onResizeBottom
123371
+ onResizeBottom: isMobile ? void 0 : resizablePanels.onResizeBottom,
123372
+ scale: zoom.scale,
123373
+ onZoomIn: zoom.handleZoomIn,
123374
+ onZoomOut: zoom.handleZoomOut,
123375
+ onZoomToFit: zoom.handleZoomToFit,
123376
+ mode,
123377
+ onSetMode: handleSetMode,
123378
+ onToggleSlideSorter: () => state2.setShowSlideSorter((p3) => !p3)
121634
123379
  }
121635
123380
  ),
121636
123381
  /* @__PURE__ */ jsx(
@@ -121662,6 +123407,37 @@ var PowerPointViewer = forwardRef(
121662
123407
  onDiscardAnnotations: handleDiscardAnnotations
121663
123408
  }
121664
123409
  ),
123410
+ /* @__PURE__ */ jsx(
123411
+ SettingsDialog,
123412
+ {
123413
+ isOpen: isSettingsOpen,
123414
+ onClose: () => setIsSettingsOpen(false),
123415
+ spellCheckEnabled: state2.spellCheckEnabled,
123416
+ onSetSpellCheckEnabled: state2.setSpellCheckEnabled,
123417
+ showGrid: state2.showGrid,
123418
+ onSetShowGrid: state2.setShowGrid,
123419
+ showRulers: state2.showRulers,
123420
+ onSetShowRulers: state2.setShowRulers,
123421
+ snapToGrid: state2.snapToGrid,
123422
+ onSetSnapToGrid: state2.setSnapToGrid,
123423
+ reducedMotion,
123424
+ onToggleReducedMotion: toggleReducedMotion
123425
+ }
123426
+ ),
123427
+ /* @__PURE__ */ jsx(
123428
+ ShareDialog,
123429
+ {
123430
+ open: isShareDialogOpen,
123431
+ onClose: () => setIsShareDialogOpen(false),
123432
+ activeCollaboration: collaboration,
123433
+ onStartCollaboration,
123434
+ onStopCollaboration,
123435
+ preconfigured: Boolean(collaboration),
123436
+ defaultRoomId: shareDefaults?.roomId,
123437
+ defaultUserName: shareDefaults?.userName,
123438
+ defaultServerUrl: shareDefaults?.serverUrl
123439
+ }
123440
+ ),
121665
123441
  /* @__PURE__ */ jsx(
121666
123442
  ViewerOverlays,
121667
123443
  {
@@ -121703,36 +123479,21 @@ var PowerPointViewer = forwardRef(
121703
123479
  ]
121704
123480
  }
121705
123481
  );
121706
- return /* @__PURE__ */ jsx(ViewerThemeProvider, { theme, children: collaboration ? /* @__PURE__ */ jsx(
123482
+ return /* @__PURE__ */ jsx(ViewerThemeProvider, { theme, children: collaboration ? /* @__PURE__ */ jsxs(
121707
123483
  CollaborationProvider,
121708
123484
  {
121709
123485
  config: collaboration,
121710
123486
  canvasWidth: canvasSize.width,
121711
123487
  canvasHeight: canvasSize.height,
121712
- children: viewerContent
123488
+ children: [
123489
+ /* @__PURE__ */ jsx(CollaborationDocumentSync, { slides, setSlides: state2.setSlides }),
123490
+ viewerContent
123491
+ ]
121713
123492
  }
121714
123493
  ) : viewerContent });
121715
123494
  }
121716
123495
  );
121717
123496
  PowerPointViewer.displayName = "PowerPointViewer";
121718
- function CollaborationToolbarStrip({
121719
- collaboration
121720
- }) {
121721
- const collab = useCollaboration();
121722
- if (!collab) {
121723
- return null;
121724
- }
121725
- return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end px-2 py-0.5 border-b border-border bg-background/30 z-10", children: /* @__PURE__ */ jsx(
121726
- UserAvatarBar,
121727
- {
121728
- remoteUsers: collab.remoteUsers,
121729
- localUserName: collaboration.userName,
121730
- localUserColor: collab.config.userColor ?? "#6366f1",
121731
- localUserAvatar: collaboration.userAvatar,
121732
- status: collab.status
121733
- }
121734
- ) });
121735
- }
121736
123497
  function CollaborationStatusStrip() {
121737
123498
  const collab = useCollaboration();
121738
123499
  if (!collab) {
@@ -121740,6 +123501,19 @@ function CollaborationStatusStrip() {
121740
123501
  }
121741
123502
  return /* @__PURE__ */ jsx(CollaborationStatusIndicator, { status: collab.status, connectedCount: collab.connectedCount });
121742
123503
  }
123504
+ function CollaborationDocumentSync({
123505
+ slides,
123506
+ setSlides
123507
+ }) {
123508
+ const collab = useCollaboration();
123509
+ useYjsDocumentSync({
123510
+ doc: collab?.doc ?? null,
123511
+ slides,
123512
+ setSlides,
123513
+ isConnected: collab?.status === "connected"
123514
+ });
123515
+ return null;
123516
+ }
121743
123517
  function colorSchemesMatch(a2, b2) {
121744
123518
  if (!a2) {
121745
123519
  return false;
@@ -121795,35 +123569,6 @@ function useThemeSwitching(input) {
121795
123569
  currentPreset
121796
123570
  };
121797
123571
  }
121798
- function useCollaborativeHistory({
121799
- localClientId: _localClientId,
121800
- handleUndo,
121801
- handleRedo,
121802
- canUndo,
121803
- canRedo
121804
- }) {
121805
- const localChangeCountRef = useRef(0);
121806
- const wrappedUndo = useCallback(() => {
121807
- if (!canUndo) {
121808
- return;
121809
- }
121810
- handleUndo();
121811
- localChangeCountRef.current = Math.max(0, localChangeCountRef.current - 1);
121812
- }, [handleUndo, canUndo]);
121813
- const wrappedRedo = useCallback(() => {
121814
- if (!canRedo) {
121815
- return;
121816
- }
121817
- handleRedo();
121818
- localChangeCountRef.current += 1;
121819
- }, [handleRedo, canRedo]);
121820
- return {
121821
- handleUndo: wrappedUndo,
121822
- handleRedo: wrappedRedo,
121823
- canUndo,
121824
- canRedo
121825
- };
121826
- }
121827
123572
  /*! Bundled license information:
121828
123573
 
121829
123574
  three/build/three.core.js:
@@ -121905,4 +123650,4 @@ camera-controls/dist/camera-controls.module.js:
121905
123650
  *)
121906
123651
  */
121907
123652
 
121908
- export { CollaborationProvider, CollaborationStatusIndicator, PowerPointViewer, RemoteUserCursors, UserAvatarBar, clearAudienceContent, getAnimationInitialStyle, isAudienceTab, loadAudienceContent, useCollaborativeHistory, useCollaborativeState, usePresenceTracking, useThemeSwitching, useYjsProvider };
123653
+ export { CollaborationProvider, CollaborationStatusIndicator, PowerPointViewer, RemoteUserCursors, UserAvatarBar, clearAudienceContent, getAnimationInitialStyle, isAudienceTab, loadAudienceContent, storeAudienceContent, useCollaborativeHistory, useCollaborativeState, usePresenceTracking, useThemeSwitching, useYjsProvider };