pptx-react-viewer 1.0.10 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +68 -0
  2. package/dist/{PowerPointViewer-K2URyPlJ.d.mts → PowerPointViewer-gSKLhZDo.d.mts} +35 -1
  3. package/dist/{PowerPointViewer-K2URyPlJ.d.ts → PowerPointViewer-gSKLhZDo.d.ts} +35 -1
  4. package/dist/index.d.mts +2 -2
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +2532 -882
  7. package/dist/index.mjs +2533 -883
  8. package/dist/pptx-viewer.css +1 -1
  9. package/dist/viewer/index.d.mts +15 -3
  10. package/dist/viewer/index.d.ts +15 -3
  11. package/dist/viewer/index.js +2731 -985
  12. package/dist/viewer/index.mjs +2732 -987
  13. package/node_modules/emf-converter/package.json +1 -1
  14. package/node_modules/mtx-decompressor/package.json +1 -1
  15. package/node_modules/pptx-viewer-core/dist/{SvgExporter-DqcmwxFu.d.mts → SvgExporter-B4-1_Hjp.d.mts} +1 -1
  16. package/node_modules/pptx-viewer-core/dist/{SvgExporter-BZJguJbp.d.ts → SvgExporter-CPr1npgo.d.ts} +1 -1
  17. package/node_modules/pptx-viewer-core/dist/cli/index.d.mts +2 -2
  18. package/node_modules/pptx-viewer-core/dist/cli/index.d.ts +2 -2
  19. package/node_modules/pptx-viewer-core/dist/converter/index.d.mts +3 -3
  20. package/node_modules/pptx-viewer-core/dist/converter/index.d.ts +3 -3
  21. package/node_modules/pptx-viewer-core/dist/index.d.mts +5 -5
  22. package/node_modules/pptx-viewer-core/dist/index.d.ts +5 -5
  23. package/node_modules/pptx-viewer-core/dist/{presentation-Bo7cMMCe.d.mts → presentation-DgkIYhXo.d.mts} +6 -0
  24. package/node_modules/pptx-viewer-core/dist/{presentation-Bo7cMMCe.d.ts → presentation-DgkIYhXo.d.ts} +6 -0
  25. package/node_modules/pptx-viewer-core/dist/{text-operations-D0f1jred.d.ts → text-operations-B6U6XxWt.d.ts} +1 -1
  26. package/node_modules/pptx-viewer-core/dist/{text-operations-Bo-WG-Z8.d.mts → text-operations-dYKZp3zE.d.mts} +1 -1
  27. package/node_modules/pptx-viewer-core/package.json +1 -1
  28. package/package.json +4 -4
package/dist/index.mjs CHANGED
@@ -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 } 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, useRef65 = React90.useRef, useEffect64 = React90.useEffect, useMemo41 = 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, useMemo41 = React95.useMemo, useDebugValue = React95.useDebugValue;
43378
43378
  exports$1.useSyncExternalStoreWithSelector = function(subscribe3, getSnapshot2, getServerSnapshot2, selector, isEqual) {
43379
- var instRef = useRef65(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;
@@ -69716,6 +69716,7 @@ var UNGROUPED_SECTION_ID = "__ungrouped__";
69716
69716
 
69717
69717
  // src/viewer/constants/toolbar.ts
69718
69718
  var TOOLBAR_SECTIONS = [
69719
+ { id: "file", label: "File" },
69719
69720
  { id: "home", label: "Home" },
69720
69721
  { id: "insert", label: "Insert" },
69721
69722
  { id: "text", label: "Text" },
@@ -69723,8 +69724,11 @@ var TOOLBAR_SECTIONS = [
69723
69724
  { id: "arrange", label: "Arrange" },
69724
69725
  { id: "design", label: "Design" },
69725
69726
  { id: "transitions", label: "Transitions" },
69727
+ { id: "animations", label: "Animations" },
69728
+ { id: "slideShow", label: "Slide Show" },
69726
69729
  { id: "review", label: "Review" },
69727
- { id: "view", label: "View" }
69730
+ { id: "view", label: "View" },
69731
+ { id: "help", label: "Help" }
69728
69732
  ];
69729
69733
  var SHORTCUT_REFERENCE_ITEMS = [
69730
69734
  { action: "Undo", shortcut: "Ctrl/Cmd+Z" },
@@ -85762,6 +85766,7 @@ function AccessibilityPanel({
85762
85766
  reducedMotion,
85763
85767
  onToggleReducedMotion
85764
85768
  }) {
85769
+ const { t: t2 } = useTranslation();
85765
85770
  if (!isOpen) {
85766
85771
  return null;
85767
85772
  }
@@ -85769,31 +85774,27 @@ function AccessibilityPanel({
85769
85774
  "div",
85770
85775
  {
85771
85776
  role: "dialog",
85772
- "aria-label": "Accessibility Checker",
85777
+ "aria-label": t2("pptx.accessibility.title"),
85773
85778
  className: "absolute top-14 right-3 z-40 w-[min(28rem,calc(100%-1.5rem))] rounded border border-border bg-popover shadow-2xl",
85774
85779
  children: [
85775
85780
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-2", children: [
85776
- /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-wide text-foreground", children: "Accessibility Checker" }),
85781
+ /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-wide text-foreground", children: t2("pptx.accessibility.title") }),
85777
85782
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
85778
- /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
85779
- issues.length,
85780
- " issue",
85781
- issues.length !== 1 ? "s" : ""
85782
- ] }),
85783
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: t2("pptx.accessibility.issueCount", { count: issues.length }) }),
85783
85784
  /* @__PURE__ */ jsx(
85784
85785
  "button",
85785
85786
  {
85786
85787
  type: "button",
85787
85788
  onClick: onClose,
85788
- "aria-label": "Close accessibility panel",
85789
+ "aria-label": t2("pptx.accessibility.closePanel"),
85789
85790
  className: "rounded px-2 py-1 text-[11px] text-foreground hover:bg-muted hover:text-foreground",
85790
- children: "Close"
85791
+ children: t2("pptx.accessibility.close")
85791
85792
  }
85792
85793
  )
85793
85794
  ] })
85794
85795
  ] }),
85795
85796
  onToggleReducedMotion !== void 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-border px-3 py-2", children: [
85796
- /* @__PURE__ */ jsx("label", { htmlFor: "reduced-motion-toggle", className: "text-xs text-foreground", children: "Reduce motion" }),
85797
+ /* @__PURE__ */ jsx("label", { htmlFor: "reduced-motion-toggle", className: "text-xs text-foreground", children: t2("pptx.accessibility.reduceMotion") }),
85797
85798
  /* @__PURE__ */ jsx(
85798
85799
  "button",
85799
85800
  {
@@ -85822,9 +85823,9 @@ function AccessibilityPanel({
85822
85823
  "div",
85823
85824
  {
85824
85825
  role: "list",
85825
- "aria-label": "Accessibility issues",
85826
+ "aria-label": t2("pptx.accessibility.issuesList"),
85826
85827
  className: "max-h-72 overflow-y-auto p-2 space-y-1",
85827
- 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(
85828
+ 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(
85828
85829
  "div",
85829
85830
  {
85830
85831
  role: "listitem",
@@ -85836,7 +85837,7 @@ function AccessibilityPanel({
85836
85837
  children: [
85837
85838
  /* @__PURE__ */ jsx("span", { className: "shrink-0 mt-0.5", "aria-hidden": "true", children: issue.severity === "error" ? "\u25CF" : issue.severity === "warning" ? "\u25B2" : "\u2139" }),
85838
85839
  /* @__PURE__ */ jsxs("span", { children: [
85839
- issue.severity === "error" ? "Error: " : issue.severity === "warning" ? "Warning: " : "Info: ",
85840
+ issue.severity === "error" ? t2("pptx.accessibility.error") : issue.severity === "warning" ? t2("pptx.accessibility.warning") : t2("pptx.accessibility.info"),
85840
85841
  issue.message
85841
85842
  ] })
85842
85843
  ]
@@ -85888,22 +85889,32 @@ function ExportProgressModal({
85888
85889
  ) })
85889
85890
  ] }) });
85890
85891
  }
85891
- function formatAutosaveAge(timestamp) {
85892
+ function formatAutosaveAge(timestamp, t2) {
85892
85893
  const diff = Date.now() - timestamp;
85893
85894
  const minutes = Math.floor(diff / 6e4);
85894
85895
  if (minutes < 1) {
85895
- return "just now";
85896
+ return t2("pptx.autosave.justNow");
85896
85897
  }
85897
85898
  if (minutes === 1) {
85898
- return "1 min ago";
85899
+ return t2("pptx.autosave.oneMinAgo");
85899
85900
  }
85900
- return `${minutes} min ago`;
85901
+ return t2("pptx.autosave.minutesAgo", { count: minutes });
85901
85902
  }
85902
85903
  function StatusBar({
85903
85904
  slideCount,
85904
85905
  activeSlideIndex,
85905
85906
  isDirty,
85906
- autosaveStatus
85907
+ autosaveStatus,
85908
+ scale,
85909
+ onZoomIn,
85910
+ onZoomOut,
85911
+ onZoomToFit,
85912
+ isNotesExpanded,
85913
+ onToggleNotes,
85914
+ mode,
85915
+ onSetMode,
85916
+ onToggleSlideSorter,
85917
+ collaborationSlot
85907
85918
  }) {
85908
85919
  const { t: t2 } = useTranslation();
85909
85920
  let statusText;
@@ -85911,7 +85922,7 @@ function StatusBar({
85911
85922
  statusText = t2("pptx.autosave.saving");
85912
85923
  } else if (autosaveStatus?.state === "saved") {
85913
85924
  statusText = t2("pptx.autosave.saved", {
85914
- time: formatAutosaveAge(autosaveStatus.timestamp)
85925
+ time: formatAutosaveAge(autosaveStatus.timestamp, t2)
85915
85926
  });
85916
85927
  } else if (autosaveStatus?.state === "error") {
85917
85928
  statusText = t2("pptx.autosave.error");
@@ -85920,15 +85931,124 @@ function StatusBar({
85920
85931
  } else {
85921
85932
  statusText = t2("pptx.statusBar.allSaved");
85922
85933
  }
85923
- 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: [
85924
- /* @__PURE__ */ jsx("span", { children: slideCount > 0 ? `Slide ${Math.min(activeSlideIndex + 1, slideCount)} of ${slideCount}` : "No slides" }),
85934
+ const vb = "p-1 rounded-sm transition-colors hover:bg-accent/60 text-muted-foreground active:scale-95 active:opacity-80";
85935
+ 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: [
85936
+ /* @__PURE__ */ jsx("span", { className: "shrink-0", children: slideCount > 0 ? t2("pptx.statusBar.slideOf", {
85937
+ current: Math.min(activeSlideIndex + 1, slideCount),
85938
+ total: slideCount
85939
+ }) : t2("pptx.statusBar.noSlides") }),
85940
+ /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-border/40 mx-1 max-md:hidden" }),
85941
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 max-md:hidden text-[10px]", children: t2("pptx.statusBar.language") }),
85942
+ /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-border/60 mx-1 max-md:hidden" }),
85925
85943
  /* @__PURE__ */ jsx(
85926
85944
  "span",
85927
85945
  {
85928
- className: autosaveStatus?.state === "error" ? "text-red-400" : autosaveStatus?.state === "saving" ? "text-yellow-400" : "",
85946
+ className: cn(
85947
+ "shrink-0 max-md:hidden",
85948
+ autosaveStatus?.state === "error" ? "text-red-400" : autosaveStatus?.state === "saving" ? "text-yellow-400" : ""
85949
+ ),
85929
85950
  children: statusText
85930
85951
  }
85931
- )
85952
+ ),
85953
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
85954
+ onToggleNotes && /* @__PURE__ */ jsxs(
85955
+ "button",
85956
+ {
85957
+ type: "button",
85958
+ onClick: onToggleNotes,
85959
+ className: cn(
85960
+ vb,
85961
+ "flex items-center gap-1 text-[10px]",
85962
+ isNotesExpanded && "text-primary"
85963
+ ),
85964
+ title: t2("pptx.statusBar.toggleNotes"),
85965
+ "aria-label": t2("pptx.statusBar.toggleNotes"),
85966
+ children: [
85967
+ /* @__PURE__ */ jsx(LuStickyNote, { className: "w-3 h-3" }),
85968
+ /* @__PURE__ */ jsx("span", { className: "max-md:hidden", children: t2("pptx.notes.title") })
85969
+ ]
85970
+ }
85971
+ ),
85972
+ /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-border/60 mx-0.5" }),
85973
+ onSetMode && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
85974
+ /* @__PURE__ */ jsx(
85975
+ "button",
85976
+ {
85977
+ type: "button",
85978
+ onClick: () => onSetMode("edit"),
85979
+ className: cn(vb, mode === "edit" && "text-primary"),
85980
+ title: t2("pptx.statusBar.normalView"),
85981
+ "aria-label": t2("pptx.statusBar.normalView"),
85982
+ children: /* @__PURE__ */ jsx(LuMonitor, { className: "w-3.5 h-3.5" })
85983
+ }
85984
+ ),
85985
+ onToggleSlideSorter && /* @__PURE__ */ jsx(
85986
+ "button",
85987
+ {
85988
+ type: "button",
85989
+ onClick: onToggleSlideSorter,
85990
+ className: vb,
85991
+ title: t2("pptx.statusBar.slideSorter"),
85992
+ "aria-label": t2("pptx.statusBar.slideSorter"),
85993
+ children: /* @__PURE__ */ jsx(LuColumns2, { className: "w-3.5 h-3.5" })
85994
+ }
85995
+ ),
85996
+ /* @__PURE__ */ jsx(
85997
+ "button",
85998
+ {
85999
+ type: "button",
86000
+ onClick: () => onSetMode("present"),
86001
+ className: cn(vb, mode === "present" && "text-primary"),
86002
+ title: t2("pptx.statusBar.slideShow"),
86003
+ "aria-label": t2("pptx.statusBar.slideShow"),
86004
+ children: /* @__PURE__ */ jsx(LuPresentation, { className: "w-3.5 h-3.5" })
86005
+ }
86006
+ )
86007
+ ] }),
86008
+ collaborationSlot && /* @__PURE__ */ jsxs(Fragment, { children: [
86009
+ /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-border/40 mx-0.5" }),
86010
+ collaborationSlot
86011
+ ] }),
86012
+ scale !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
86013
+ /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-border/60 mx-0.5" }),
86014
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
86015
+ onZoomOut && /* @__PURE__ */ jsx(
86016
+ "button",
86017
+ {
86018
+ type: "button",
86019
+ onClick: onZoomOut,
86020
+ className: vb,
86021
+ title: t2("pptx.statusBar.zoomOut"),
86022
+ "aria-label": t2("pptx.statusBar.zoomOut"),
86023
+ children: /* @__PURE__ */ jsx(LuMinus, { className: "w-3 h-3" })
86024
+ }
86025
+ ),
86026
+ /* @__PURE__ */ jsxs(
86027
+ "button",
86028
+ {
86029
+ type: "button",
86030
+ onClick: onZoomToFit,
86031
+ 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",
86032
+ title: t2("pptx.statusBar.zoomToFit"),
86033
+ children: [
86034
+ Math.round((scale ?? 1) * 100),
86035
+ "%"
86036
+ ]
86037
+ }
86038
+ ),
86039
+ onZoomIn && /* @__PURE__ */ jsx(
86040
+ "button",
86041
+ {
86042
+ type: "button",
86043
+ onClick: onZoomIn,
86044
+ className: vb,
86045
+ title: t2("pptx.statusBar.zoomIn"),
86046
+ "aria-label": t2("pptx.statusBar.zoomIn"),
86047
+ children: /* @__PURE__ */ jsx(LuPlus, { className: "w-3 h-3" })
86048
+ }
86049
+ )
86050
+ ] })
86051
+ ] })
85932
86052
  ] });
85933
86053
  }
85934
86054
  function ResizeHandle({
@@ -90744,6 +90864,7 @@ function FindReplacePanel({
90744
90864
  onReplaceAll,
90745
90865
  onClose
90746
90866
  }) {
90867
+ const { t: t2 } = useTranslation();
90747
90868
  const searchInputRef = useRef(null);
90748
90869
  useEffect(() => {
90749
90870
  searchInputRef.current?.focus();
@@ -90776,12 +90897,12 @@ function FindReplacePanel({
90776
90897
  [onClose, onReplace]
90777
90898
  );
90778
90899
  const hasResults = findResults.length > 0;
90779
- const matchCountLabel = hasResults ? `${findResultIndex + 1} of ${findResults.length}` : findQuery.length > 0 ? "No matches" : "";
90900
+ const matchCountLabel = hasResults ? t2("pptx.findReplace.matchCount", { current: findResultIndex + 1, total: findResults.length }) : findQuery.length > 0 ? t2("pptx.findReplace.noMatches") : "";
90780
90901
  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: [
90781
90902
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
90782
90903
  /* @__PURE__ */ jsxs("span", { className: "font-semibold text-sm inline-flex items-center gap-1.5", children: [
90783
90904
  /* @__PURE__ */ jsx(LuSearch, { className: ic }),
90784
- "Find & Replace"
90905
+ t2("pptx.findReplace.title")
90785
90906
  ] }),
90786
90907
  /* @__PURE__ */ jsx(
90787
90908
  "button",
@@ -90789,8 +90910,8 @@ function FindReplacePanel({
90789
90910
  type: "button",
90790
90911
  className: btnGhost,
90791
90912
  onClick: onClose,
90792
- title: "Close (Escape)",
90793
- "aria-label": "Close find and replace",
90913
+ title: t2("pptx.findReplace.closeEscape"),
90914
+ "aria-label": t2("pptx.findReplace.closeAriaLabel"),
90794
90915
  children: /* @__PURE__ */ jsx(LuX, { className: ic })
90795
90916
  }
90796
90917
  )
@@ -90805,9 +90926,9 @@ function FindReplacePanel({
90805
90926
  value: findQuery,
90806
90927
  onChange: (e2) => onSetFindQuery(e2.target.value),
90807
90928
  onKeyDown: handleSearchKeyDown,
90808
- placeholder: "Find\u2026",
90929
+ placeholder: t2("pptx.findReplace.findPlaceholder"),
90809
90930
  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",
90810
- "aria-label": "Search text"
90931
+ "aria-label": t2("pptx.findReplace.searchText")
90811
90932
  }
90812
90933
  ),
90813
90934
  /* @__PURE__ */ jsx(
@@ -90816,8 +90937,8 @@ function FindReplacePanel({
90816
90937
  type: "button",
90817
90938
  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"}`,
90818
90939
  onClick: () => onSetFindMatchCase(!findMatchCase),
90819
- title: "Match case",
90820
- "aria-label": "Toggle match case",
90940
+ title: t2("pptx.findReplace.matchCase"),
90941
+ "aria-label": t2("pptx.findReplace.toggleMatchCase"),
90821
90942
  "aria-pressed": findMatchCase,
90822
90943
  children: /* @__PURE__ */ jsx(LuCaseSensitive, { className: ic })
90823
90944
  }
@@ -90830,8 +90951,8 @@ function FindReplacePanel({
90830
90951
  className: btnGhost,
90831
90952
  onClick: () => onNavigateResult(-1),
90832
90953
  disabled: !hasResults,
90833
- title: "Previous match (Shift+Enter)",
90834
- "aria-label": "Previous match",
90954
+ title: t2("pptx.findReplace.previousMatch"),
90955
+ "aria-label": t2("pptx.findReplace.previousMatch"),
90835
90956
  children: /* @__PURE__ */ jsx(LuChevronUp, { className: ic })
90836
90957
  }
90837
90958
  ),
@@ -90842,8 +90963,8 @@ function FindReplacePanel({
90842
90963
  className: btnGhost,
90843
90964
  onClick: () => onNavigateResult(1),
90844
90965
  disabled: !hasResults,
90845
- title: "Next match (Enter)",
90846
- "aria-label": "Next match",
90966
+ title: t2("pptx.findReplace.nextMatch"),
90967
+ "aria-label": t2("pptx.findReplace.nextMatch"),
90847
90968
  children: /* @__PURE__ */ jsx(LuChevronDown, { className: ic })
90848
90969
  }
90849
90970
  )
@@ -90858,9 +90979,9 @@ function FindReplacePanel({
90858
90979
  value: replaceQuery,
90859
90980
  onChange: (e2) => onSetReplaceQuery(e2.target.value),
90860
90981
  onKeyDown: handleReplaceKeyDown,
90861
- placeholder: "Replace with\u2026",
90982
+ placeholder: t2("pptx.findReplace.replacePlaceholder"),
90862
90983
  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",
90863
- "aria-label": "Replacement text"
90984
+ "aria-label": t2("pptx.findReplace.replacementText")
90864
90985
  }
90865
90986
  )
90866
90987
  ] }) }),
@@ -90872,8 +90993,8 @@ function FindReplacePanel({
90872
90993
  className: btnAction,
90873
90994
  onClick: onReplace,
90874
90995
  disabled: !hasResults,
90875
- title: "Replace current match",
90876
- children: "Replace"
90996
+ title: t2("pptx.findReplace.replaceCurrent"),
90997
+ children: t2("pptx.findReplace.replace")
90877
90998
  }
90878
90999
  ),
90879
91000
  /* @__PURE__ */ jsx(
@@ -90883,8 +91004,8 @@ function FindReplacePanel({
90883
91004
  className: btnAction,
90884
91005
  onClick: onReplaceAll,
90885
91006
  disabled: !hasResults,
90886
- title: "Replace all matches",
90887
- children: "Replace All"
91007
+ title: t2("pptx.findReplace.replaceAllMatches"),
91008
+ children: t2("pptx.findReplace.replaceAll")
90888
91009
  }
90889
91010
  )
90890
91011
  ] })
@@ -91293,8 +91414,8 @@ function SlideItemInner({
91293
91414
  {
91294
91415
  ref: slideRef,
91295
91416
  className: cn(
91296
- "group relative cursor-pointer rounded-lg border-2 p-1 transition-all",
91297
- isActive ? "border-primary bg-primary/10" : "border-border bg-background/40 hover:border-muted-foreground",
91417
+ "group relative flex items-center gap-1 cursor-pointer py-0.5 px-1 transition-all",
91418
+ isActive && "bg-accent/40 before:absolute before:left-0 before:top-1 before:bottom-1 before:w-[3px] before:bg-primary before:rounded-r",
91298
91419
  isHidden && "opacity-50"
91299
91420
  ),
91300
91421
  draggable: canEdit,
@@ -91304,21 +91425,35 @@ function SlideItemInner({
91304
91425
  onDragOver,
91305
91426
  onDrop: (e2) => onDrop(e2, slideIndex),
91306
91427
  children: [
91307
- 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)]" }),
91308
- /* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded bg-white", children: [
91309
- /* @__PURE__ */ jsx(LazyThumbnail, { slide, canvasSize, previewHeight }),
91310
- (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: [
91311
- /* @__PURE__ */ jsx(LuMessageSquare, { className: "w-2 h-2" }),
91312
- slide.comments?.length
91313
- ] })
91314
- ] }),
91315
- /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center justify-between px-1", children: [
91316
- /* @__PURE__ */ jsx("span", { className: cn("text-[10px]", isActive ? "text-primary" : "text-muted-foreground"), children: slideIndex + 1 }),
91317
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
91318
- rehearsalTimings && typeof rehearsalTimings[slideIndex] === "number" && /* @__PURE__ */ jsx("span", { className: "text-[9px] font-mono text-amber-400/80 tabular-nums", children: formatTimingMs(rehearsalTimings[slideIndex]) }),
91319
- isHidden && /* @__PURE__ */ jsx(LuEyeOff, { className: "w-3 h-3 text-muted-foreground" })
91320
- ] })
91321
- ] })
91428
+ /* @__PURE__ */ jsx(
91429
+ "span",
91430
+ {
91431
+ className: cn(
91432
+ "text-[10px] tabular-nums w-5 text-right shrink-0 select-none",
91433
+ isActive ? "text-primary font-medium" : "text-muted-foreground"
91434
+ ),
91435
+ children: slideIndex + 1
91436
+ }
91437
+ ),
91438
+ /* @__PURE__ */ jsxs(
91439
+ "div",
91440
+ {
91441
+ className: cn(
91442
+ "relative flex-1 overflow-hidden border transition-colors bg-white",
91443
+ isActive ? "border-primary/60" : "border-transparent group-hover:border-border/40"
91444
+ ),
91445
+ children: [
91446
+ 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)]" }),
91447
+ /* @__PURE__ */ jsx(LazyThumbnail, { slide, canvasSize, previewHeight }),
91448
+ (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: [
91449
+ /* @__PURE__ */ jsx(LuMessageSquare, { className: "w-2 h-2" }),
91450
+ slide.comments?.length
91451
+ ] }),
91452
+ 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" }) }),
91453
+ 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]) }) })
91454
+ ]
91455
+ }
91456
+ )
91322
91457
  ]
91323
91458
  }
91324
91459
  );
@@ -91439,7 +91574,7 @@ function SlidesPaneSidebar({
91439
91574
  onSlideContextMenu,
91440
91575
  onMoveSlide,
91441
91576
  onAddSlide,
91442
- onCollapse,
91577
+ onCollapse: _onCollapse,
91443
91578
  onAddSection,
91444
91579
  onRenameSection,
91445
91580
  onDeleteSection,
@@ -91644,50 +91779,23 @@ function SlidesPaneSidebar({
91644
91779
  {
91645
91780
  role: "navigation",
91646
91781
  "aria-label": "Slides",
91647
- className: "flex h-full flex-col border-r border-border bg-background/70 backdrop-blur-sm",
91782
+ className: "flex h-full flex-col border-r border-border bg-secondary/30",
91648
91783
  style: panelWidth ? { width: panelWidth, flexShrink: 0 } : void 0,
91649
91784
  children: [
91650
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-2", children: [
91651
- /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: t2("pptx.sections.slides") }),
91652
- /* @__PURE__ */ jsx(
91653
- "button",
91654
- {
91655
- type: "button",
91656
- className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
91657
- title: t2("pptx.sections.collapsePane"),
91658
- onClick: onCollapse,
91659
- children: /* @__PURE__ */ jsx(LuPanelLeftClose, { className: "h-3.5 w-3.5" })
91660
- }
91661
- )
91662
- ] }),
91663
91785
  shouldVirtualize ? renderVirtualized() : renderNonVirtualized(),
91664
- /* @__PURE__ */ jsxs("div", { className: "border-t border-border/60 px-3 py-2 space-y-1", children: [
91665
- /* @__PURE__ */ jsxs(
91666
- "button",
91667
- {
91668
- type: "button",
91669
- 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",
91670
- disabled: !canEdit,
91671
- onClick: onAddSlide,
91672
- children: [
91673
- /* @__PURE__ */ jsx(LuPlus, { className: "h-3.5 w-3.5" }),
91674
- t2("pptx.sections.addSlide")
91675
- ]
91676
- }
91677
- ),
91678
- canEdit && onAddSection && /* @__PURE__ */ jsxs(
91679
- "button",
91680
- {
91681
- type: "button",
91682
- 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",
91683
- onClick: () => onAddSection(t2("pptx.sections.defaultName"), activeSlideIndex),
91684
- children: [
91685
- /* @__PURE__ */ jsx(LuPlus, { className: "h-3 w-3" }),
91686
- t2("pptx.sections.addSection")
91687
- ]
91688
- }
91689
- )
91690
- ] }),
91786
+ canEdit && /* @__PURE__ */ jsx("div", { className: "border-t border-border/60 px-2 py-1.5", children: /* @__PURE__ */ jsxs(
91787
+ "button",
91788
+ {
91789
+ type: "button",
91790
+ 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",
91791
+ disabled: !canEdit,
91792
+ onClick: onAddSlide,
91793
+ children: [
91794
+ /* @__PURE__ */ jsx(LuPlus, { className: "h-3 w-3" }),
91795
+ t2("pptx.sections.addSlide")
91796
+ ]
91797
+ }
91798
+ ) }),
91691
91799
  sectionContextMenu && /* @__PURE__ */ jsx(
91692
91800
  SectionContextMenu,
91693
91801
  {
@@ -94122,23 +94230,18 @@ var SlideNotesPanel = ({
94122
94230
  });
94123
94231
  const hasNotes = draft.trim().length > 0;
94124
94232
  const slideLabel = activeSlide ? t2("pptx.notes.slideN", { n: activeSlide.slideNumber }) : t2("pptx.notes.noSlide");
94125
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col border-t border-border/60 bg-background/80 select-none", children: [
94233
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col border-t border-border/60 bg-background select-none", children: [
94126
94234
  /* @__PURE__ */ jsxs(
94127
94235
  "button",
94128
94236
  {
94129
94237
  type: "button",
94130
94238
  onClick: onToggle,
94131
- 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",
94239
+ 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",
94132
94240
  "aria-expanded": isExpanded,
94133
94241
  "aria-controls": "slide-notes-content",
94134
94242
  children: [
94135
- /* @__PURE__ */ jsx(LuStickyNote, { className: "w-3.5 h-3.5 shrink-0" }),
94136
- /* @__PURE__ */ jsx("span", { className: "font-medium tracking-wide uppercase", children: t2("pptx.notes.title") }),
94137
- !isExpanded && hasNotes && /* @__PURE__ */ jsxs("span", { className: "ml-1 truncate max-w-[240px] text-muted-foreground font-normal normal-case", children: [
94138
- "- ",
94139
- draft.trim().split("\n")[0]
94140
- ] }),
94141
- /* @__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" }) })
94243
+ "Notes",
94244
+ !isExpanded && hasNotes && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/50 text-[10px]", children: "(has notes)" })
94142
94245
  ]
94143
94246
  }
94144
94247
  ),
@@ -96406,15 +96509,14 @@ function SelectionPane({
96406
96509
  }) })
96407
96510
  ] });
96408
96511
  }
96409
- var _b = "inline-flex items-center justify-center px-2.5 py-1.5 max-md:min-h-[44px] max-md:min-w-[44px]";
96512
+ 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";
96410
96513
  var gB = `${_b} border-r border-border hover:bg-accent disabled:opacity-40 disabled:cursor-not-allowed`;
96411
96514
  var gL = `${_b} hover:bg-accent disabled:opacity-40 disabled:cursor-not-allowed`;
96412
96515
  var grp = "inline-flex items-center rounded bg-muted text-xs overflow-hidden";
96413
- 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";
96414
- var sep = /* @__PURE__ */ jsx("div", { className: "w-px h-5 bg-border/60 mx-0.5 max-md:hidden" });
96516
+ 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";
96517
+ var sep = /* @__PURE__ */ jsx("div", { className: "w-px self-stretch bg-border/40 mx-1 max-md:hidden" });
96415
96518
  var ic2 = "w-4 h-4";
96416
96519
  var ics = "w-3.5 h-3.5";
96417
- var MODES = ["edit", "preview", "present"];
96418
96520
  var ALIGN_BTNS = [
96419
96521
  { k: "left", el: /* @__PURE__ */ jsx(LuAlignLeft, { className: ic2 }) },
96420
96522
  { k: "center", el: /* @__PURE__ */ jsx(LuAlignCenter, { className: ic2 }) },
@@ -96533,7 +96635,128 @@ var ATXT = [
96533
96635
  { i: /* @__PURE__ */ jsx(LuAlignRight, { className: ic2 }), t: "Align right" },
96534
96636
  { i: /* @__PURE__ */ jsx(LuAlignJustify, { className: ic2 }), t: "Justify" }
96535
96637
  ];
96638
+ var ANIMATION_PRESETS = [
96639
+ {
96640
+ group: "Entrance",
96641
+ items: [
96642
+ { value: "appear", label: "Appear" },
96643
+ { value: "fadeIn", label: "Fade In" },
96644
+ { value: "flyIn", label: "Fly In" }
96645
+ ]
96646
+ },
96647
+ {
96648
+ group: "Emphasis",
96649
+ items: [
96650
+ { value: "pulse", label: "Pulse" },
96651
+ { value: "spin", label: "Spin" }
96652
+ ]
96653
+ },
96654
+ {
96655
+ group: "Exit",
96656
+ items: [
96657
+ { value: "disappear", label: "Disappear" },
96658
+ { value: "fadeOut", label: "Fade Out" }
96659
+ ]
96660
+ }
96661
+ ];
96662
+ function AnimationsSection(p3) {
96663
+ const { t: t2 } = useTranslation();
96664
+ const [previewActive, setPreviewActive] = useState(false);
96665
+ const hasElement = p3.selectedElement !== null;
96666
+ const disabled = !p3.canEdit || !hasElement;
96667
+ const handlePreview = () => {
96668
+ if (disabled) {
96669
+ return;
96670
+ }
96671
+ setPreviewActive(true);
96672
+ setTimeout(() => setPreviewActive(false), 1200);
96673
+ };
96674
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
96675
+ /* @__PURE__ */ jsxs(
96676
+ "button",
96677
+ {
96678
+ type: "button",
96679
+ onClick: handlePreview,
96680
+ disabled,
96681
+ className: cn(
96682
+ pill,
96683
+ previewActive ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96684
+ ),
96685
+ title: t2("pptx.animations.previewTooltip"),
96686
+ children: [
96687
+ /* @__PURE__ */ jsx(LuPlay, { className: ic2 }),
96688
+ t2("pptx.animations.preview")
96689
+ ]
96690
+ }
96691
+ ),
96692
+ sep,
96693
+ /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
96694
+ /* @__PURE__ */ jsxs(
96695
+ "button",
96696
+ {
96697
+ type: "button",
96698
+ disabled,
96699
+ className: pill,
96700
+ title: t2("pptx.animations.addTooltip"),
96701
+ children: [
96702
+ /* @__PURE__ */ jsx(LuSparkles, { className: ic2 }),
96703
+ t2("pptx.animations.addAnimation"),
96704
+ /* @__PURE__ */ jsx(LuChevronDown, { className: "w-3 h-3" })
96705
+ ]
96706
+ }
96707
+ ),
96708
+ /* @__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: [
96709
+ /* @__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()}`) }),
96710
+ group.items.map((item) => /* @__PURE__ */ jsx(
96711
+ "button",
96712
+ {
96713
+ type: "button",
96714
+ disabled,
96715
+ 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",
96716
+ title: t2("pptx.animations.applyAnimation", {
96717
+ name: t2(`pptx.animations.preset.${item.value}`)
96718
+ }),
96719
+ children: t2(`pptx.animations.preset.${item.value}`)
96720
+ },
96721
+ item.value
96722
+ ))
96723
+ ] }, group.group)) }) })
96724
+ ] }),
96725
+ sep,
96726
+ /* @__PURE__ */ jsxs(
96727
+ "button",
96728
+ {
96729
+ type: "button",
96730
+ disabled,
96731
+ className: pill,
96732
+ title: t2("pptx.animations.removeTooltip"),
96733
+ children: [
96734
+ /* @__PURE__ */ jsx(LuTrash2, { className: ic2 }),
96735
+ t2("pptx.animations.remove")
96736
+ ]
96737
+ }
96738
+ ),
96739
+ sep,
96740
+ /* @__PURE__ */ jsxs(
96741
+ "button",
96742
+ {
96743
+ type: "button",
96744
+ onClick: p3.onToggleInspector,
96745
+ className: cn(
96746
+ pill,
96747
+ p3.isInspectorPaneOpen ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96748
+ ),
96749
+ title: t2("pptx.animations.openPanelTooltip"),
96750
+ children: [
96751
+ /* @__PURE__ */ jsx(LuPanelRight, { className: ic2 }),
96752
+ t2("pptx.animations.animationPanel")
96753
+ ]
96754
+ }
96755
+ )
96756
+ ] });
96757
+ }
96536
96758
  function ArrangeSection(p3) {
96759
+ const { t: t2 } = useTranslation();
96537
96760
  const hasSel = Boolean(p3.selectedElement);
96538
96761
  const canMut = hasSel && p3.canEdit;
96539
96762
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -96544,21 +96767,21 @@ function ArrangeSection(p3) {
96544
96767
  onClick: () => p3.onAlignElements(a2.k),
96545
96768
  disabled: !canMut,
96546
96769
  className: i3 < arr.length - 1 ? gB : gL,
96547
- title: `Align ${a2.k}`,
96770
+ title: t2("pptx.arrange.align", { direction: a2.k }),
96548
96771
  children: a2.el
96549
96772
  },
96550
96773
  a2.k
96551
96774
  )) }),
96552
96775
  /* @__PURE__ */ jsxs("div", { className: grp, children: [
96553
- /* @__PURE__ */ jsx("button", { onClick: p3.onCopy, disabled: !hasSel, className: gB, title: "Copy", children: /* @__PURE__ */ jsx(LuCopy, { className: ic2 }) }),
96554
- /* @__PURE__ */ jsx("button", { onClick: p3.onCut, disabled: !canMut, className: gB, title: "Cut", children: "Cut" }),
96776
+ /* @__PURE__ */ jsx("button", { onClick: p3.onCopy, disabled: !hasSel, className: gB, title: t2("pptx.arrange.copy"), children: /* @__PURE__ */ jsx(LuCopy, { className: ic2 }) }),
96777
+ /* @__PURE__ */ jsx("button", { onClick: p3.onCut, disabled: !canMut, className: gB, title: t2("pptx.arrange.cut"), children: t2("pptx.arrange.cut") }),
96555
96778
  /* @__PURE__ */ jsx(
96556
96779
  "button",
96557
96780
  {
96558
96781
  onClick: p3.onPaste,
96559
96782
  disabled: !p3.clipboardPayload || !p3.canEdit,
96560
96783
  className: gL,
96561
- title: "Paste",
96784
+ title: t2("pptx.arrange.paste"),
96562
96785
  children: /* @__PURE__ */ jsx(LuClipboardPaste, { className: ic2 })
96563
96786
  }
96564
96787
  )
@@ -96573,10 +96796,10 @@ function ArrangeSection(p3) {
96573
96796
  pill,
96574
96797
  p3.formatPainterActive ? "bg-amber-600 hover:bg-amber-500 text-amber-50" : ""
96575
96798
  ),
96576
- title: "Format Painter",
96799
+ title: t2("pptx.arrange.formatPainter"),
96577
96800
  children: [
96578
96801
  /* @__PURE__ */ jsx(LuPaintbrush, { className: ic2 }),
96579
- "Format"
96802
+ t2("pptx.arrange.format")
96580
96803
  ]
96581
96804
  }
96582
96805
  ),
@@ -96588,8 +96811,8 @@ function ArrangeSection(p3) {
96588
96811
  onClick: () => p3.onFlip("horizontal"),
96589
96812
  disabled: !canMut,
96590
96813
  className: gB,
96591
- title: "Flip horizontally",
96592
- children: "Flip H"
96814
+ title: t2("pptx.arrange.flipHorizontally"),
96815
+ children: t2("pptx.arrange.flipH")
96593
96816
  }
96594
96817
  ),
96595
96818
  /* @__PURE__ */ jsx(
@@ -96599,8 +96822,8 @@ function ArrangeSection(p3) {
96599
96822
  onClick: () => p3.onFlip("vertical"),
96600
96823
  disabled: !canMut,
96601
96824
  className: gL,
96602
- title: "Flip vertically",
96603
- children: "Flip V"
96825
+ title: t2("pptx.arrange.flipVertically"),
96826
+ children: t2("pptx.arrange.flipV")
96604
96827
  }
96605
96828
  )
96606
96829
  ] }),
@@ -96611,7 +96834,7 @@ function ArrangeSection(p3) {
96611
96834
  onClick: () => p3.onMoveLayer("backward"),
96612
96835
  disabled: !canMut,
96613
96836
  className: gB,
96614
- title: "Send backward",
96837
+ title: t2("pptx.arrange.sendBackward"),
96615
96838
  children: /* @__PURE__ */ jsx(LuChevronDown, { className: ic2 })
96616
96839
  }
96617
96840
  ),
@@ -96621,7 +96844,7 @@ function ArrangeSection(p3) {
96621
96844
  onClick: () => p3.onMoveLayer("forward"),
96622
96845
  disabled: !canMut,
96623
96846
  className: gB,
96624
- title: "Bring forward",
96847
+ title: t2("pptx.arrange.bringForward"),
96625
96848
  children: /* @__PURE__ */ jsx(LuChevronUp, { className: ic2 })
96626
96849
  }
96627
96850
  ),
@@ -96631,8 +96854,8 @@ function ArrangeSection(p3) {
96631
96854
  onClick: () => p3.onMoveLayerToEdge("back"),
96632
96855
  disabled: !canMut,
96633
96856
  className: gB,
96634
- title: "Send to back",
96635
- children: "Back"
96857
+ title: t2("pptx.arrange.sendToBack"),
96858
+ children: t2("pptx.arrange.back")
96636
96859
  }
96637
96860
  ),
96638
96861
  /* @__PURE__ */ jsx(
@@ -96641,25 +96864,34 @@ function ArrangeSection(p3) {
96641
96864
  onClick: () => p3.onMoveLayerToEdge("front"),
96642
96865
  disabled: !canMut,
96643
96866
  className: gL,
96644
- title: "Bring to front",
96645
- children: "Front"
96867
+ title: t2("pptx.arrange.bringToFront"),
96868
+ children: t2("pptx.arrange.front")
96646
96869
  }
96647
96870
  )
96648
96871
  ] }),
96649
- /* @__PURE__ */ jsxs("button", { onClick: p3.onDuplicate, disabled: !canMut, className: pill, title: "Duplicate", children: [
96650
- /* @__PURE__ */ jsx(LuCopy, { className: ic2 }),
96651
- "Duplicate"
96652
- ] }),
96872
+ /* @__PURE__ */ jsxs(
96873
+ "button",
96874
+ {
96875
+ onClick: p3.onDuplicate,
96876
+ disabled: !canMut,
96877
+ className: pill,
96878
+ title: t2("pptx.arrange.duplicate"),
96879
+ children: [
96880
+ /* @__PURE__ */ jsx(LuCopy, { className: ic2 }),
96881
+ t2("pptx.arrange.duplicate")
96882
+ ]
96883
+ }
96884
+ ),
96653
96885
  /* @__PURE__ */ jsxs(
96654
96886
  "button",
96655
96887
  {
96656
96888
  onClick: p3.onDelete,
96657
96889
  disabled: !canMut,
96658
96890
  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",
96659
- title: "Delete",
96891
+ title: t2("pptx.arrange.delete"),
96660
96892
  children: [
96661
96893
  /* @__PURE__ */ jsx(LuTrash2, { className: ic2 }),
96662
- "Delete"
96894
+ t2("pptx.arrange.delete")
96663
96895
  ]
96664
96896
  }
96665
96897
  )
@@ -96698,12 +96930,91 @@ function DesignSection(p3) {
96698
96930
  "Edit Theme"
96699
96931
  ]
96700
96932
  }
96933
+ ),
96934
+ sep,
96935
+ p3.onOpenDocumentProperties && /* @__PURE__ */ jsxs(
96936
+ "button",
96937
+ {
96938
+ onClick: p3.onOpenDocumentProperties,
96939
+ className: pill,
96940
+ title: "Change slide dimensions (16:9, 4:3, custom)",
96941
+ children: [
96942
+ /* @__PURE__ */ jsx(LuMonitor, { className: ics }),
96943
+ "Slide Size"
96944
+ ]
96945
+ }
96946
+ ),
96947
+ p3.onToggleInspector && /* @__PURE__ */ jsxs(
96948
+ "button",
96949
+ {
96950
+ onClick: p3.onToggleInspector,
96951
+ className: cn(
96952
+ pill,
96953
+ p3.isInspectorPaneOpen ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96954
+ ),
96955
+ title: "Open inspector to edit slide background",
96956
+ children: [
96957
+ /* @__PURE__ */ jsx(LuPaintBucket, { className: ics }),
96958
+ "Format Background"
96959
+ ]
96960
+ }
96701
96961
  )
96702
96962
  ] });
96703
96963
  }
96964
+ var TRANSITION_PRESETS = [
96965
+ { value: "none", label: "None" },
96966
+ { value: "fade", label: "Fade" },
96967
+ { value: "push", label: "Push" },
96968
+ { value: "wipe", label: "Wipe" },
96969
+ { value: "split", label: "Split" },
96970
+ { value: "reveal", label: "Reveal" },
96971
+ { value: "cut", label: "Cut" },
96972
+ { value: "cover", label: "Cover" },
96973
+ { value: "uncover", label: "Uncover" }
96974
+ ];
96704
96975
  function TransitionsSection(p3) {
96976
+ const [selected, setSelected] = React10__default.useState("none");
96977
+ const [duration, setDuration] = React10__default.useState("00.50");
96705
96978
  return /* @__PURE__ */ jsxs(Fragment, { children: [
96706
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground px-2", children: "Configure transitions in the Inspector panel (Slide tab)." }),
96979
+ /* @__PURE__ */ jsxs("button", { type: "button", className: pill, title: "Preview transition", children: [
96980
+ /* @__PURE__ */ jsx(LuPlay, { className: ics }),
96981
+ "Preview"
96982
+ ] }),
96983
+ sep,
96984
+ /* @__PURE__ */ jsx("div", { className: "inline-flex items-center gap-0.5 overflow-x-auto max-w-[420px]", children: TRANSITION_PRESETS.map((t2) => /* @__PURE__ */ jsx(
96985
+ "button",
96986
+ {
96987
+ type: "button",
96988
+ onClick: () => setSelected(t2.value),
96989
+ className: cn(
96990
+ "flex-shrink-0 px-2 py-1 max-md:min-h-[44px] rounded border text-[11px] leading-tight transition-colors",
96991
+ selected === t2.value ? "border-primary bg-primary/10 text-primary font-medium" : "border-border bg-muted hover:bg-accent text-foreground"
96992
+ ),
96993
+ title: `${t2.label} transition`,
96994
+ children: t2.label
96995
+ },
96996
+ t2.value
96997
+ )) }),
96998
+ sep,
96999
+ /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-1.5 text-xs text-muted-foreground", children: [
97000
+ /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: "Duration:" }),
97001
+ /* @__PURE__ */ jsx(
97002
+ "input",
97003
+ {
97004
+ type: "text",
97005
+ value: duration,
97006
+ onChange: (e2) => setDuration(e2.target.value),
97007
+ className: "w-14 px-1.5 py-1 rounded border border-border bg-muted text-xs text-foreground text-center",
97008
+ title: "Transition duration in seconds"
97009
+ }
97010
+ )
97011
+ ] }),
97012
+ sep,
97013
+ /* @__PURE__ */ jsxs("button", { type: "button", className: pill, title: "Apply transition to all slides", children: [
97014
+ /* @__PURE__ */ jsx(LuCopy, { className: ics }),
97015
+ "Apply to All"
97016
+ ] }),
97017
+ sep,
96707
97018
  /* @__PURE__ */ jsxs(
96708
97019
  "button",
96709
97020
  {
@@ -96713,7 +97024,7 @@ function TransitionsSection(p3) {
96713
97024
  pill,
96714
97025
  p3.isInspectorPaneOpen ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
96715
97026
  ),
96716
- title: "Open Inspector to edit transitions",
97027
+ title: "Open Inspector for full transition options",
96717
97028
  children: [
96718
97029
  /* @__PURE__ */ jsx(LuPanelRight, { className: ic2 }),
96719
97030
  "Inspector"
@@ -96824,6 +97135,321 @@ function DrawSection(p3) {
96824
97135
  ] })
96825
97136
  ] });
96826
97137
  }
97138
+ function FileSection(p3) {
97139
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
97140
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onSaveAsPpsx, className: pill, title: "Save as Slide Show (.ppsx)", children: [
97141
+ /* @__PURE__ */ jsx(LuPlay, { className: ic2 }),
97142
+ "Save .ppsx"
97143
+ ] }),
97144
+ p3.hasMacros && /* @__PURE__ */ jsxs("button", { onClick: p3.onSaveAsPptm, className: pill, title: "Save as Macro-Enabled (.pptm)", children: [
97145
+ /* @__PURE__ */ jsx(LuFileText, { className: ic2 }),
97146
+ "Save .pptm"
97147
+ ] }),
97148
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onPackageForSharing, className: pill, title: "Package for Sharing", children: [
97149
+ /* @__PURE__ */ jsx(LuFolderOpen, { className: ic2 }),
97150
+ "Package"
97151
+ ] }),
97152
+ sep,
97153
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onExportPng, className: pill, title: "Export as PNG", children: [
97154
+ /* @__PURE__ */ jsx(LuDownload, { className: ic2 }),
97155
+ "PNG"
97156
+ ] }),
97157
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onExportPdf, className: pill, title: "Export as PDF", children: [
97158
+ /* @__PURE__ */ jsx(LuFileText, { className: ic2 }),
97159
+ "PDF"
97160
+ ] }),
97161
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onExportVideo, className: pill, title: "Export as Video", children: [
97162
+ /* @__PURE__ */ jsx(LuVideo, { className: ic2 }),
97163
+ "Video"
97164
+ ] }),
97165
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onExportGif, className: pill, title: "Export as GIF", children: [
97166
+ /* @__PURE__ */ jsx(LuImage, { className: ic2 }),
97167
+ "GIF"
97168
+ ] }),
97169
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onCopySlideAsImage, className: pill, title: "Copy Slide as Image", children: [
97170
+ /* @__PURE__ */ jsx(LuCopy, { className: ic2 }),
97171
+ "Copy Image"
97172
+ ] }),
97173
+ sep,
97174
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onPrint, className: pill, title: "Print", children: [
97175
+ /* @__PURE__ */ jsx(LuPrinter, { className: ic2 }),
97176
+ "Print"
97177
+ ] }),
97178
+ sep,
97179
+ p3.onOpenDocumentProperties && /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenDocumentProperties, className: pill, title: "Document Properties", children: [
97180
+ /* @__PURE__ */ jsx(LuInfo, { className: ic2 }),
97181
+ "Properties"
97182
+ ] }),
97183
+ p3.onOpenPasswordProtection && /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenPasswordProtection, className: pill, title: "Protect Presentation", children: [
97184
+ /* @__PURE__ */ jsx(LuLock, { className: ic2 }),
97185
+ "Protect"
97186
+ ] }),
97187
+ p3.onOpenFontEmbedding && /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenFontEmbedding, className: pill, title: "Embed Fonts", children: [
97188
+ /* @__PURE__ */ jsx(LuType, { className: ic2 }),
97189
+ "Fonts"
97190
+ ] }),
97191
+ p3.onOpenDigitalSignatures && /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenDigitalSignatures, className: pill, title: "Digital Signatures", children: [
97192
+ /* @__PURE__ */ jsx(LuShieldAlert, { className: ic2 }),
97193
+ "Signatures"
97194
+ ] })
97195
+ ] });
97196
+ }
97197
+ function extractFontInfo(element2) {
97198
+ const defaults = { fontFamily: "Segoe UI", fontSize: "24" };
97199
+ if (!element2) {
97200
+ return defaults;
97201
+ }
97202
+ if (!hasTextProperties(element2)) {
97203
+ return defaults;
97204
+ }
97205
+ const segStyle = element2.textSegments?.[0]?.style;
97206
+ const textStyle = element2.textStyle;
97207
+ const fontFamily = segStyle?.fontFamily ?? textStyle?.fontFamily ?? defaults.fontFamily;
97208
+ const fontSize = segStyle?.fontSize ?? textStyle?.fontSize;
97209
+ return {
97210
+ fontFamily,
97211
+ fontSize: fontSize !== void 0 && fontSize !== null ? String(fontSize) : defaults.fontSize
97212
+ };
97213
+ }
97214
+ var COMMON_FONTS = [
97215
+ "Arial",
97216
+ "Calibri",
97217
+ "Cambria",
97218
+ "Comic Sans MS",
97219
+ "Courier New",
97220
+ "Georgia",
97221
+ "Helvetica",
97222
+ "Impact",
97223
+ "Segoe UI",
97224
+ "Tahoma",
97225
+ "Times New Roman",
97226
+ "Trebuchet MS",
97227
+ "Verdana"
97228
+ ];
97229
+ var COMMON_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 40, 44, 48, 54, 60, 72, 96];
97230
+ function HomeSection(p3) {
97231
+ const [layoutMenuOpen, setLayoutMenuOpen] = useState(false);
97232
+ const [fontMenuOpen, setFontMenuOpen] = useState(false);
97233
+ const [sizeMenuOpen, setSizeMenuOpen] = useState(false);
97234
+ const [copiedFeedback, setCopiedFeedback] = useState(false);
97235
+ const [cutFeedback, setCutFeedback] = useState(false);
97236
+ const layoutMenuRef = useRef(null);
97237
+ const fontMenuRef = useRef(null);
97238
+ const sizeMenuRef = useRef(null);
97239
+ const { fontFamily, fontSize } = extractFontInfo(p3.selectedElement);
97240
+ const handleNewSlide = useCallback(() => {
97241
+ if (p3.layoutOptions.length > 0) {
97242
+ p3.onInsertSlideFromLayout(p3.layoutOptions[0].path);
97243
+ }
97244
+ }, [p3]);
97245
+ useEffect(() => {
97246
+ if (!layoutMenuOpen) {
97247
+ return;
97248
+ }
97249
+ const handler = (e2) => {
97250
+ if (layoutMenuRef.current && !layoutMenuRef.current.contains(e2.target)) {
97251
+ setLayoutMenuOpen(false);
97252
+ }
97253
+ };
97254
+ document.addEventListener("mousedown", handler);
97255
+ return () => document.removeEventListener("mousedown", handler);
97256
+ }, [layoutMenuOpen]);
97257
+ useEffect(() => {
97258
+ if (!fontMenuOpen) {
97259
+ return;
97260
+ }
97261
+ const handler = (e2) => {
97262
+ if (fontMenuRef.current && !fontMenuRef.current.contains(e2.target)) {
97263
+ setFontMenuOpen(false);
97264
+ }
97265
+ };
97266
+ document.addEventListener("mousedown", handler);
97267
+ return () => document.removeEventListener("mousedown", handler);
97268
+ }, [fontMenuOpen]);
97269
+ useEffect(() => {
97270
+ if (!sizeMenuOpen) {
97271
+ return;
97272
+ }
97273
+ const handler = (e2) => {
97274
+ if (sizeMenuRef.current && !sizeMenuRef.current.contains(e2.target)) {
97275
+ setSizeMenuOpen(false);
97276
+ }
97277
+ };
97278
+ document.addEventListener("mousedown", handler);
97279
+ return () => document.removeEventListener("mousedown", handler);
97280
+ }, [sizeMenuOpen]);
97281
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
97282
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97283
+ /* @__PURE__ */ jsxs("div", { className: grp, children: [
97284
+ /* @__PURE__ */ jsx(
97285
+ "button",
97286
+ {
97287
+ type: "button",
97288
+ onClick: p3.onPaste,
97289
+ disabled: !p3.clipboardPayload || !p3.canEdit,
97290
+ className: gB,
97291
+ title: "Paste",
97292
+ children: /* @__PURE__ */ jsx(LuClipboardPaste, { className: ic2 })
97293
+ }
97294
+ ),
97295
+ /* @__PURE__ */ jsx(
97296
+ "button",
97297
+ {
97298
+ type: "button",
97299
+ onClick: () => {
97300
+ p3.onCut();
97301
+ setCutFeedback(true);
97302
+ setTimeout(() => setCutFeedback(false), 600);
97303
+ },
97304
+ disabled: !p3.canEdit,
97305
+ className: cn(gB, cutFeedback && "bg-green-600/20 text-green-400"),
97306
+ title: "Cut",
97307
+ children: /* @__PURE__ */ jsx(LuScissors, { className: ic2 })
97308
+ }
97309
+ ),
97310
+ /* @__PURE__ */ jsx(
97311
+ "button",
97312
+ {
97313
+ type: "button",
97314
+ onClick: () => {
97315
+ p3.onCopy();
97316
+ setCopiedFeedback(true);
97317
+ setTimeout(() => setCopiedFeedback(false), 600);
97318
+ },
97319
+ className: cn(gB, copiedFeedback && "bg-green-600/20 text-green-400"),
97320
+ title: "Copy",
97321
+ children: /* @__PURE__ */ jsx(LuCopy, { className: ic2 })
97322
+ }
97323
+ ),
97324
+ p3.onToggleFormatPainter && /* @__PURE__ */ jsx(
97325
+ "button",
97326
+ {
97327
+ type: "button",
97328
+ onClick: p3.onToggleFormatPainter,
97329
+ disabled: !p3.canEdit,
97330
+ className: cn(
97331
+ gL,
97332
+ p3.formatPainterActive ? "bg-amber-600 hover:bg-amber-500 text-amber-50" : ""
97333
+ ),
97334
+ title: "Format Painter",
97335
+ children: /* @__PURE__ */ jsx(LuPaintbrush, { className: ic2 })
97336
+ }
97337
+ )
97338
+ ] }),
97339
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Clipboard" })
97340
+ ] }),
97341
+ sep,
97342
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97343
+ /* @__PURE__ */ jsxs("div", { className: "relative inline-flex items-center", ref: layoutMenuRef, children: [
97344
+ /* @__PURE__ */ jsxs(
97345
+ "button",
97346
+ {
97347
+ type: "button",
97348
+ onClick: handleNewSlide,
97349
+ disabled: !p3.canEdit || p3.layoutOptions.length === 0,
97350
+ className: cn(
97351
+ pill,
97352
+ "whitespace-nowrap",
97353
+ p3.layoutOptions.length > 0 ? "rounded-r-none" : ""
97354
+ ),
97355
+ title: "New Slide",
97356
+ children: [
97357
+ /* @__PURE__ */ jsx(LuPlus, { className: ic2 }),
97358
+ "New Slide"
97359
+ ]
97360
+ }
97361
+ ),
97362
+ p3.layoutOptions.length > 0 && /* @__PURE__ */ jsx(
97363
+ "button",
97364
+ {
97365
+ type: "button",
97366
+ disabled: !p3.canEdit,
97367
+ 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",
97368
+ title: "Choose layout",
97369
+ onClick: () => setLayoutMenuOpen((v) => !v),
97370
+ children: /* @__PURE__ */ jsx(LuChevronDown, { className: "w-3 h-3" })
97371
+ }
97372
+ ),
97373
+ 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(
97374
+ "button",
97375
+ {
97376
+ type: "button",
97377
+ className: "flex items-center gap-2 w-full px-3 py-1.5 text-xs text-foreground hover:bg-muted transition-colors",
97378
+ onClick: () => {
97379
+ p3.onInsertSlideFromLayout(lo.path);
97380
+ setLayoutMenuOpen(false);
97381
+ },
97382
+ children: lo.name
97383
+ },
97384
+ lo.path
97385
+ )) }) })
97386
+ ] }),
97387
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Slides" })
97388
+ ] }),
97389
+ sep,
97390
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97391
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
97392
+ /* @__PURE__ */ jsxs("div", { className: "relative", ref: fontMenuRef, children: [
97393
+ /* @__PURE__ */ jsxs(
97394
+ "button",
97395
+ {
97396
+ type: "button",
97397
+ onClick: () => setFontMenuOpen((v) => !v),
97398
+ 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",
97399
+ children: [
97400
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: fontFamily }),
97401
+ /* @__PURE__ */ jsx(LuChevronDown, { className: "w-3 h-3 ml-1 shrink-0 text-muted-foreground" })
97402
+ ]
97403
+ }
97404
+ ),
97405
+ 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(
97406
+ "button",
97407
+ {
97408
+ type: "button",
97409
+ className: "flex items-center gap-2 w-full px-3 py-1.5 text-xs text-foreground hover:bg-muted transition-colors",
97410
+ style: { fontFamily: f },
97411
+ onClick: () => {
97412
+ p3.onUpdateTextStyle?.({ fontFamily: f });
97413
+ setFontMenuOpen(false);
97414
+ },
97415
+ children: f
97416
+ },
97417
+ f
97418
+ )) }) })
97419
+ ] }),
97420
+ /* @__PURE__ */ jsxs("div", { className: "relative", ref: sizeMenuRef, children: [
97421
+ /* @__PURE__ */ jsxs(
97422
+ "button",
97423
+ {
97424
+ type: "button",
97425
+ onClick: () => setSizeMenuOpen((v) => !v),
97426
+ 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",
97427
+ children: [
97428
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: fontSize }),
97429
+ /* @__PURE__ */ jsx(LuChevronDown, { className: "w-3 h-3 ml-1 shrink-0 text-muted-foreground" })
97430
+ ]
97431
+ }
97432
+ ),
97433
+ 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(
97434
+ "button",
97435
+ {
97436
+ type: "button",
97437
+ className: "flex items-center gap-2 w-full px-3 py-1.5 text-xs text-foreground hover:bg-muted transition-colors",
97438
+ onClick: () => {
97439
+ p3.onUpdateTextStyle?.({ fontSize: s });
97440
+ setSizeMenuOpen(false);
97441
+ },
97442
+ children: s
97443
+ },
97444
+ s
97445
+ )) }) })
97446
+ ] })
97447
+ ] }),
97448
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Font" })
97449
+ ] }),
97450
+ sep
97451
+ ] });
97452
+ }
96827
97453
  function InsertSection(p3) {
96828
97454
  const { t: t2 } = useTranslation();
96829
97455
  const { canEdit } = p3;
@@ -97182,6 +97808,60 @@ function InsertSection(p3) {
97182
97808
  )
97183
97809
  ] });
97184
97810
  }
97811
+ function SlideShowSection(p3) {
97812
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
97813
+ /* @__PURE__ */ jsxs(
97814
+ "button",
97815
+ {
97816
+ onClick: () => p3.onSetMode("present"),
97817
+ className: pill,
97818
+ title: "Start slide show from beginning",
97819
+ children: [
97820
+ /* @__PURE__ */ jsx(LuPlay, { className: ic2 }),
97821
+ "From Beginning"
97822
+ ]
97823
+ }
97824
+ ),
97825
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onPresent, className: pill, title: "Start slide show from current slide", children: [
97826
+ /* @__PURE__ */ jsx(LuPlay, { className: ic2 }),
97827
+ "From Current Slide"
97828
+ ] }),
97829
+ sep,
97830
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onEnterPresenterView, className: pill, title: "Presenter view", children: [
97831
+ /* @__PURE__ */ jsx(LuMonitor, { className: ic2 }),
97832
+ "Presenter View"
97833
+ ] }),
97834
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onEnterRehearsalMode, className: pill, title: "Rehearse timings", children: [
97835
+ /* @__PURE__ */ jsx(LuClock, { className: ic2 }),
97836
+ "Rehearse Timings"
97837
+ ] }),
97838
+ sep,
97839
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenSetUpSlideShow, className: pill, title: "Set up slide show", children: [
97840
+ /* @__PURE__ */ jsx(LuSettings, { className: ic2 }),
97841
+ "Set Up Slide Show"
97842
+ ] }),
97843
+ /* @__PURE__ */ jsxs("button", { onClick: p3.onOpenBroadcastDialog, className: pill, title: "Broadcast slide show", children: [
97844
+ /* @__PURE__ */ jsx(LuCast, { className: ic2 }),
97845
+ "Broadcast"
97846
+ ] }),
97847
+ sep,
97848
+ /* @__PURE__ */ jsxs(
97849
+ "button",
97850
+ {
97851
+ onClick: p3.onToggleSubtitles,
97852
+ className: cn(
97853
+ pill,
97854
+ p3.showSubtitles ? "bg-primary hover:bg-primary/80 text-primary-foreground" : ""
97855
+ ),
97856
+ title: "Toggle subtitles",
97857
+ children: [
97858
+ /* @__PURE__ */ jsx(LuCaptions, { className: ic2 }),
97859
+ "Subtitles"
97860
+ ]
97861
+ }
97862
+ )
97863
+ ] });
97864
+ }
97185
97865
  var FONT_COLOR_PRESETS = [
97186
97866
  "#000000",
97187
97867
  "#ffffff",
@@ -97194,6 +97874,18 @@ var FONT_COLOR_PRESETS = [
97194
97874
  "#ff69b4",
97195
97875
  "#808080"
97196
97876
  ];
97877
+ var HIGHLIGHT_COLOR_PRESETS = [
97878
+ "#ffff00",
97879
+ "#00ff00",
97880
+ "#00ffff",
97881
+ "#ff00ff",
97882
+ "#0000ff",
97883
+ "#ff0000",
97884
+ "#000080",
97885
+ "#008080",
97886
+ "#008000",
97887
+ "#800080"
97888
+ ];
97197
97889
  function TextSection(p3) {
97198
97890
  const hasSel = Boolean(p3.selectedElement);
97199
97891
  const canMut = hasSel && p3.canEdit;
@@ -97201,7 +97893,9 @@ function TextSection(p3) {
97201
97893
  const isTable = hasSel && p3.selectedElement?.type === "table";
97202
97894
  const canFormat = isTextEl || isTable;
97203
97895
  const currentColor = isTextEl && p3.selectedElement && hasTextProperties(p3.selectedElement) ? p3.selectedElement.textSegments?.[0]?.style?.color ?? p3.selectedElement.textStyle?.color ?? "#000000" : "#000000";
97896
+ const currentHighlight = isTextEl && p3.selectedElement && hasTextProperties(p3.selectedElement) ? p3.selectedElement.textSegments?.[0]?.style?.highlightColor ?? p3.selectedElement.textStyle?.highlightColor ?? "#ffff00" : "#ffff00";
97204
97897
  const colorInputRef = useRef(null);
97898
+ const highlightInputRef = useRef(null);
97205
97899
  const handleColorChange = useCallback(
97206
97900
  (color) => {
97207
97901
  if (!canFormat) {
@@ -97211,138 +97905,709 @@ function TextSection(p3) {
97211
97905
  },
97212
97906
  [canFormat, p3]
97213
97907
  );
97908
+ const handleHighlightChange = useCallback(
97909
+ (highlightColor) => {
97910
+ if (!canFormat) {
97911
+ return;
97912
+ }
97913
+ p3.onUpdateTextStyle({ highlightColor });
97914
+ },
97915
+ [canFormat, p3]
97916
+ );
97214
97917
  return /* @__PURE__ */ jsxs(Fragment, { children: [
97215
- /* @__PURE__ */ jsx("div", { className: grp, children: FMT.map((b2, i3, a2) => {
97216
- const handleClick = () => {
97217
- if (!canFormat || !p3.selectedElement) {
97218
- return;
97219
- }
97220
- const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97221
- switch (b2.t) {
97222
- case "Bold":
97223
- p3.onUpdateTextStyle({ bold: !ts?.bold });
97224
- break;
97225
- case "Italic":
97226
- p3.onUpdateTextStyle({ italic: !ts?.italic });
97227
- break;
97228
- case "Underline":
97229
- p3.onUpdateTextStyle({
97230
- underline: !ts?.underline
97231
- });
97232
- break;
97233
- case "Strikethrough":
97234
- p3.onUpdateTextStyle({
97235
- strikethrough: !ts?.strikethrough
97236
- });
97237
- break;
97238
- }
97239
- };
97240
- return /* @__PURE__ */ jsx(
97241
- "button",
97242
- {
97243
- type: "button",
97244
- disabled: !canMut,
97245
- onMouseDown: (e2) => e2.preventDefault(),
97246
- onClick: handleClick,
97247
- className: i3 < a2.length - 1 ? gB : gL,
97248
- title: b2.t,
97249
- children: b2.i
97250
- },
97251
- b2.t
97252
- );
97253
- }) }),
97254
- /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
97255
- /* @__PURE__ */ jsxs(
97256
- "button",
97257
- {
97258
- type: "button",
97259
- disabled: !canMut,
97260
- onMouseDown: (e2) => e2.preventDefault(),
97261
- className: pill,
97262
- title: "Font color",
97263
- children: [
97918
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
97919
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
97920
+ /* @__PURE__ */ jsx("div", { className: grp, children: FMT.map((b2, i3, a2) => {
97921
+ const handleClick = () => {
97922
+ if (!canFormat || !p3.selectedElement) {
97923
+ return;
97924
+ }
97925
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97926
+ switch (b2.t) {
97927
+ case "Bold":
97928
+ p3.onUpdateTextStyle({ bold: !ts?.bold });
97929
+ break;
97930
+ case "Italic":
97931
+ p3.onUpdateTextStyle({ italic: !ts?.italic });
97932
+ break;
97933
+ case "Underline":
97934
+ p3.onUpdateTextStyle({
97935
+ underline: !ts?.underline
97936
+ });
97937
+ break;
97938
+ case "Strikethrough":
97939
+ p3.onUpdateTextStyle({
97940
+ strikethrough: !ts?.strikethrough
97941
+ });
97942
+ break;
97943
+ }
97944
+ };
97945
+ return /* @__PURE__ */ jsx(
97946
+ "button",
97947
+ {
97948
+ type: "button",
97949
+ disabled: !canMut,
97950
+ onMouseDown: (e2) => e2.preventDefault(),
97951
+ onClick: handleClick,
97952
+ className: i3 < a2.length - 1 ? gB : gL,
97953
+ title: b2.t,
97954
+ children: b2.i
97955
+ },
97956
+ b2.t
97957
+ );
97958
+ }) }),
97959
+ /* @__PURE__ */ jsxs("div", { className: grp, children: [
97960
+ /* @__PURE__ */ jsx(
97961
+ "button",
97962
+ {
97963
+ type: "button",
97964
+ disabled: !canMut,
97965
+ onMouseDown: (e2) => e2.preventDefault(),
97966
+ onClick: () => {
97967
+ if (!canFormat || !p3.selectedElement) {
97968
+ return;
97969
+ }
97970
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97971
+ const current = ts?.fontSize ?? 18;
97972
+ p3.onUpdateTextStyle({ fontSize: current + 2 });
97973
+ },
97974
+ className: gB,
97975
+ title: "Increase Font Size",
97976
+ children: /* @__PURE__ */ jsx(LuAArrowUp, { className: ic2 })
97977
+ }
97978
+ ),
97979
+ /* @__PURE__ */ jsx(
97980
+ "button",
97981
+ {
97982
+ type: "button",
97983
+ disabled: !canMut,
97984
+ onMouseDown: (e2) => e2.preventDefault(),
97985
+ onClick: () => {
97986
+ if (!canFormat || !p3.selectedElement) {
97987
+ return;
97988
+ }
97989
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
97990
+ const current = ts?.fontSize ?? 18;
97991
+ p3.onUpdateTextStyle({ fontSize: Math.max(1, current - 2) });
97992
+ },
97993
+ className: gB,
97994
+ title: "Decrease Font Size",
97995
+ children: /* @__PURE__ */ jsx(LuAArrowDown, { className: ic2 })
97996
+ }
97997
+ ),
97998
+ /* @__PURE__ */ jsx(
97999
+ "button",
98000
+ {
98001
+ type: "button",
98002
+ disabled: !canMut,
98003
+ onMouseDown: (e2) => e2.preventDefault(),
98004
+ onClick: () => {
98005
+ if (!canFormat) {
98006
+ return;
98007
+ }
98008
+ p3.onUpdateTextStyle({
98009
+ bold: false,
98010
+ italic: false,
98011
+ underline: false,
98012
+ strikethrough: false,
98013
+ highlightColor: void 0
98014
+ });
98015
+ },
98016
+ className: gL,
98017
+ title: "Clear Formatting",
98018
+ children: /* @__PURE__ */ jsx(LuRemoveFormatting, { className: ic2 })
98019
+ }
98020
+ )
98021
+ ] }),
98022
+ /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
98023
+ /* @__PURE__ */ jsxs(
98024
+ "button",
98025
+ {
98026
+ type: "button",
98027
+ disabled: !canMut,
98028
+ onMouseDown: (e2) => e2.preventDefault(),
98029
+ className: pill,
98030
+ title: "Font Color",
98031
+ children: [
98032
+ /* @__PURE__ */ jsx(
98033
+ "svg",
98034
+ {
98035
+ className: ic2,
98036
+ viewBox: "0 0 24 24",
98037
+ fill: "none",
98038
+ stroke: "currentColor",
98039
+ strokeWidth: "2",
98040
+ strokeLinecap: "round",
98041
+ strokeLinejoin: "round",
98042
+ children: /* @__PURE__ */ jsx("path", { d: "M6 20h12M9.5 4h5L18 16H6L9.5 4z" })
98043
+ }
98044
+ ),
98045
+ /* @__PURE__ */ jsx(
98046
+ "div",
98047
+ {
98048
+ className: "w-4 h-1 rounded-sm -mt-0.5",
98049
+ style: { backgroundColor: currentColor }
98050
+ }
98051
+ )
98052
+ ]
98053
+ }
98054
+ ),
98055
+ /* @__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: [
98056
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: FONT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsx(
98057
+ "button",
98058
+ {
98059
+ type: "button",
98060
+ className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentColor?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
98061
+ style: { backgroundColor: c2 },
98062
+ onMouseDown: (e2) => e2.preventDefault(),
98063
+ onClick: () => handleColorChange(c2)
98064
+ },
98065
+ c2
98066
+ )) }),
97264
98067
  /* @__PURE__ */ jsx(
97265
- "svg",
98068
+ "button",
97266
98069
  {
97267
- className: ic2,
97268
- viewBox: "0 0 24 24",
97269
- fill: "none",
97270
- stroke: "currentColor",
97271
- strokeWidth: "2",
97272
- strokeLinecap: "round",
97273
- strokeLinejoin: "round",
97274
- children: /* @__PURE__ */ jsx("path", { d: "M6 20h12M9.5 4h5L18 16H6L9.5 4z" })
98070
+ type: "button",
98071
+ className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
98072
+ onMouseDown: (e2) => e2.preventDefault(),
98073
+ onClick: () => colorInputRef.current?.click(),
98074
+ children: "Custom colour..."
97275
98075
  }
97276
98076
  ),
97277
- /* @__PURE__ */ jsx("div", { className: "w-4 h-1 rounded-sm -mt-0.5", style: { backgroundColor: currentColor } })
97278
- ]
97279
- }
97280
- ),
97281
- /* @__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: [
97282
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: FONT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsx(
97283
- "button",
97284
- {
97285
- type: "button",
97286
- className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentColor?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
97287
- style: { backgroundColor: c2 },
97288
- onMouseDown: (e2) => e2.preventDefault(),
97289
- onClick: () => handleColorChange(c2)
97290
- },
97291
- c2
97292
- )) }),
97293
- /* @__PURE__ */ jsx(
97294
- "button",
97295
- {
97296
- type: "button",
97297
- className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
97298
- onMouseDown: (e2) => e2.preventDefault(),
97299
- onClick: () => colorInputRef.current?.click(),
97300
- children: "Custom colour\u2026"
97301
- }
97302
- ),
97303
- /* @__PURE__ */ jsx(
97304
- "input",
97305
- {
97306
- ref: colorInputRef,
97307
- type: "color",
97308
- className: "sr-only",
97309
- value: currentColor,
97310
- onChange: (e2) => handleColorChange(e2.target.value)
97311
- }
97312
- )
97313
- ] }) })
98077
+ /* @__PURE__ */ jsx(
98078
+ "input",
98079
+ {
98080
+ ref: colorInputRef,
98081
+ type: "color",
98082
+ className: "sr-only",
98083
+ value: currentColor,
98084
+ onChange: (e2) => handleColorChange(e2.target.value)
98085
+ }
98086
+ )
98087
+ ] }) })
98088
+ ] }),
98089
+ /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
98090
+ /* @__PURE__ */ jsxs(
98091
+ "button",
98092
+ {
98093
+ type: "button",
98094
+ disabled: !canMut,
98095
+ onMouseDown: (e2) => e2.preventDefault(),
98096
+ className: pill,
98097
+ title: "Text Highlight Color",
98098
+ children: [
98099
+ /* @__PURE__ */ jsx(LuHighlighter, { className: ic2 }),
98100
+ /* @__PURE__ */ jsx(
98101
+ "div",
98102
+ {
98103
+ className: "w-4 h-1 rounded-sm -mt-0.5",
98104
+ style: { backgroundColor: currentHighlight }
98105
+ }
98106
+ )
98107
+ ]
98108
+ }
98109
+ ),
98110
+ /* @__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: [
98111
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-5 gap-1.5 mb-2", children: HIGHLIGHT_COLOR_PRESETS.map((c2) => /* @__PURE__ */ jsx(
98112
+ "button",
98113
+ {
98114
+ type: "button",
98115
+ className: `w-5 h-5 rounded-full border transition-transform hover:scale-125 ${currentHighlight?.toLowerCase() === c2 ? "border-primary ring-1 ring-primary" : "border-border"}`,
98116
+ style: { backgroundColor: c2 },
98117
+ onMouseDown: (e2) => e2.preventDefault(),
98118
+ onClick: () => handleHighlightChange(c2)
98119
+ },
98120
+ c2
98121
+ )) }),
98122
+ /* @__PURE__ */ jsx(
98123
+ "button",
98124
+ {
98125
+ type: "button",
98126
+ className: "w-full text-[10px] text-muted-foreground hover:text-foreground py-1 transition-colors",
98127
+ onMouseDown: (e2) => e2.preventDefault(),
98128
+ onClick: () => highlightInputRef.current?.click(),
98129
+ children: "Custom colour..."
98130
+ }
98131
+ ),
98132
+ /* @__PURE__ */ jsx(
98133
+ "input",
98134
+ {
98135
+ ref: highlightInputRef,
98136
+ type: "color",
98137
+ className: "sr-only",
98138
+ value: currentHighlight,
98139
+ onChange: (e2) => handleHighlightChange(e2.target.value)
98140
+ }
98141
+ )
98142
+ ] }) })
98143
+ ] })
98144
+ ] }),
98145
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Font" })
97314
98146
  ] }),
97315
- /* @__PURE__ */ jsx("div", { className: grp, children: ATXT.map((b2, i3, a2) => {
97316
- const handleClick = () => {
97317
- if (!canFormat) {
98147
+ sep,
98148
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
98149
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
98150
+ /* @__PURE__ */ jsxs("div", { className: grp, children: [
98151
+ /* @__PURE__ */ jsx(
98152
+ "button",
98153
+ {
98154
+ type: "button",
98155
+ disabled: !canMut,
98156
+ onMouseDown: (e2) => e2.preventDefault(),
98157
+ onClick: () => {
98158
+ if (!canFormat || !p3.selectedElement) {
98159
+ return;
98160
+ }
98161
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98162
+ p3.onUpdateTextStyle({
98163
+ listType: ts?.listType === "bullet" ? "none" : "bullet"
98164
+ });
98165
+ },
98166
+ className: gB,
98167
+ title: "Bullet List",
98168
+ children: /* @__PURE__ */ jsx(LuList, { className: ic2 })
98169
+ }
98170
+ ),
98171
+ /* @__PURE__ */ jsx(
98172
+ "button",
98173
+ {
98174
+ type: "button",
98175
+ disabled: !canMut,
98176
+ onMouseDown: (e2) => e2.preventDefault(),
98177
+ onClick: () => {
98178
+ if (!canFormat || !p3.selectedElement) {
98179
+ return;
98180
+ }
98181
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98182
+ p3.onUpdateTextStyle({
98183
+ listType: ts?.listType === "numbered" ? "none" : "numbered"
98184
+ });
98185
+ },
98186
+ className: gL,
98187
+ title: "Numbered List",
98188
+ children: /* @__PURE__ */ jsx(LuListOrdered, { className: ic2 })
98189
+ }
98190
+ )
98191
+ ] }),
98192
+ /* @__PURE__ */ jsxs("div", { className: grp, children: [
98193
+ /* @__PURE__ */ jsx(
98194
+ "button",
98195
+ {
98196
+ type: "button",
98197
+ disabled: !canMut,
98198
+ onMouseDown: (e2) => e2.preventDefault(),
98199
+ onClick: () => {
98200
+ if (!canFormat || !p3.selectedElement) {
98201
+ return;
98202
+ }
98203
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98204
+ const current = ts?.paragraphMarginLeft ?? 0;
98205
+ p3.onUpdateTextStyle({
98206
+ paragraphMarginLeft: Math.max(0, current - 24)
98207
+ });
98208
+ },
98209
+ className: gB,
98210
+ title: "Decrease Indent",
98211
+ children: /* @__PURE__ */ jsx(LuIndentDecrease, { className: ic2 })
98212
+ }
98213
+ ),
98214
+ /* @__PURE__ */ jsx(
98215
+ "button",
98216
+ {
98217
+ type: "button",
98218
+ disabled: !canMut,
98219
+ onMouseDown: (e2) => e2.preventDefault(),
98220
+ onClick: () => {
98221
+ if (!canFormat || !p3.selectedElement) {
98222
+ return;
98223
+ }
98224
+ const ts = hasTextProperties(p3.selectedElement) ? p3.selectedElement.textStyle : void 0;
98225
+ const current = ts?.paragraphMarginLeft ?? 0;
98226
+ p3.onUpdateTextStyle({
98227
+ paragraphMarginLeft: current + 24
98228
+ });
98229
+ },
98230
+ className: gL,
98231
+ title: "Increase Indent",
98232
+ children: /* @__PURE__ */ jsx(LuIndentIncrease, { className: ic2 })
98233
+ }
98234
+ )
98235
+ ] }),
98236
+ /* @__PURE__ */ jsx("div", { className: grp, children: ATXT.map((b2, i3, a2) => {
98237
+ const handleClick = () => {
98238
+ if (!canFormat) {
98239
+ return;
98240
+ }
98241
+ const alignMap = {
98242
+ "Align left": "left",
98243
+ "Align center": "center",
98244
+ "Align right": "right",
98245
+ Justify: "justify"
98246
+ };
98247
+ const align = alignMap[b2.t];
98248
+ if (align) {
98249
+ p3.onUpdateTextStyle({ align });
98250
+ }
98251
+ };
98252
+ return /* @__PURE__ */ jsx(
98253
+ "button",
98254
+ {
98255
+ type: "button",
98256
+ disabled: !canMut,
98257
+ onMouseDown: (e2) => e2.preventDefault(),
98258
+ onClick: handleClick,
98259
+ className: i3 < a2.length - 1 ? gB : gL,
98260
+ title: b2.t,
98261
+ children: b2.i
98262
+ },
98263
+ b2.t
98264
+ );
98265
+ }) })
98266
+ ] }),
98267
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Paragraph" })
98268
+ ] })
98269
+ ] });
98270
+ }
98271
+
98272
+ // src/viewer/hooks/collaboration/sanitize.ts
98273
+ var ROOM_ID_REGEX = /^[a-zA-Z0-9_-]{1,128}$/;
98274
+ function validateRoomId(roomId) {
98275
+ if (!ROOM_ID_REGEX.test(roomId)) {
98276
+ throw new Error(
98277
+ `Invalid collaboration room ID: "${roomId}". Must be 1-128 alphanumeric characters, hyphens, or underscores.`
98278
+ );
98279
+ }
98280
+ return roomId;
98281
+ }
98282
+ function sanitizeUserName(name) {
98283
+ if (typeof name !== "string") {
98284
+ return "Anonymous";
98285
+ }
98286
+ const stripped = name.replace(/<[^>]*>/g, "");
98287
+ const trimmed = stripped.trim().slice(0, 64);
98288
+ return trimmed || "Anonymous";
98289
+ }
98290
+ function clampCursorPosition(value, min2, max2) {
98291
+ if (typeof value !== "number" || !Number.isFinite(value)) {
98292
+ return 0;
98293
+ }
98294
+ const margin = 20;
98295
+ return Math.max(min2 - margin, Math.min(max2 + margin, value));
98296
+ }
98297
+ var HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
98298
+ function sanitizeColor(color, fallback = "#6366f1") {
98299
+ if (typeof color !== "string") {
98300
+ return fallback;
98301
+ }
98302
+ return HEX_COLOR_REGEX.test(color) ? color : fallback;
98303
+ }
98304
+ function sanitizeAvatarUrl(url) {
98305
+ if (typeof url !== "string") {
98306
+ return void 0;
98307
+ }
98308
+ try {
98309
+ const parsed = new URL(url);
98310
+ if (parsed.protocol === "https:" || parsed.protocol === "http:" || parsed.protocol === "data:") {
98311
+ return url;
98312
+ }
98313
+ } catch {
98314
+ }
98315
+ return void 0;
98316
+ }
98317
+ function sanitizeSlideIndex(value) {
98318
+ if (typeof value !== "number" || !Number.isFinite(value)) {
98319
+ return 0;
98320
+ }
98321
+ return Math.max(0, Math.floor(value));
98322
+ }
98323
+ function sanitizePresence(raw, canvasWidth, canvasHeight) {
98324
+ if (typeof raw.clientId !== "number") {
98325
+ return null;
98326
+ }
98327
+ return {
98328
+ clientId: raw.clientId,
98329
+ userName: sanitizeUserName(raw.userName),
98330
+ userAvatar: sanitizeAvatarUrl(raw.userAvatar),
98331
+ userColor: sanitizeColor(raw.userColor),
98332
+ activeSlideIndex: sanitizeSlideIndex(raw.activeSlideIndex),
98333
+ cursorX: clampCursorPosition(raw.cursorX, 0, canvasWidth),
98334
+ cursorY: clampCursorPosition(raw.cursorY, 0, canvasHeight),
98335
+ lastUpdated: typeof raw.lastUpdated === "string" ? raw.lastUpdated : (/* @__PURE__ */ new Date()).toISOString(),
98336
+ selectedElementId: typeof raw.selectedElementId === "string" ? raw.selectedElementId.slice(0, 128) : void 0
98337
+ };
98338
+ }
98339
+ var BROADCAST_THROTTLE_MS = 50;
98340
+ var STALE_PRESENCE_MS = 3e4;
98341
+ function usePresenceTracking({
98342
+ awareness,
98343
+ localClientId,
98344
+ userName,
98345
+ userColor,
98346
+ userAvatar,
98347
+ canvasWidth,
98348
+ canvasHeight
98349
+ }) {
98350
+ const [remoteUsers, setRemoteUsers] = useState([]);
98351
+ const lastBroadcastRef = useRef(0);
98352
+ const pendingBroadcastRef = useRef(null);
98353
+ const latestLocalState = useRef({});
98354
+ const broadcastPresence = useCallback(
98355
+ (update2) => {
98356
+ if (!awareness) {
98357
+ return;
98358
+ }
98359
+ Object.assign(latestLocalState.current, update2);
98360
+ const now = Date.now();
98361
+ const elapsed = now - lastBroadcastRef.current;
98362
+ const flush = () => {
98363
+ const state2 = {
98364
+ ...latestLocalState.current,
98365
+ userName,
98366
+ userColor,
98367
+ userAvatar,
98368
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98369
+ };
98370
+ awareness.setLocalStateField("presence", state2);
98371
+ lastBroadcastRef.current = Date.now();
98372
+ };
98373
+ if (elapsed >= BROADCAST_THROTTLE_MS) {
98374
+ if (pendingBroadcastRef.current) {
98375
+ clearTimeout(pendingBroadcastRef.current);
98376
+ pendingBroadcastRef.current = null;
98377
+ }
98378
+ flush();
98379
+ } else if (!pendingBroadcastRef.current) {
98380
+ pendingBroadcastRef.current = setTimeout(() => {
98381
+ pendingBroadcastRef.current = null;
98382
+ flush();
98383
+ }, BROADCAST_THROTTLE_MS - elapsed);
98384
+ }
98385
+ },
98386
+ [awareness, userName, userColor, userAvatar]
98387
+ );
98388
+ useEffect(() => {
98389
+ if (!awareness) {
98390
+ return;
98391
+ }
98392
+ awareness.setLocalStateField("presence", {
98393
+ userName,
98394
+ userColor,
98395
+ userAvatar,
98396
+ activeSlideIndex: 0,
98397
+ cursorX: 0,
98398
+ cursorY: 0,
98399
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98400
+ });
98401
+ }, [awareness, userName, userColor, userAvatar]);
98402
+ useEffect(() => {
98403
+ if (!awareness || localClientId === null) {
98404
+ return;
98405
+ }
98406
+ const handleChange = () => {
98407
+ const now = Date.now();
98408
+ const states = awareness.getStates();
98409
+ const users = [];
98410
+ states.forEach((state2, cid) => {
98411
+ if (cid === localClientId) {
97318
98412
  return;
97319
98413
  }
97320
- const alignMap = {
97321
- "Align left": "left",
97322
- "Align center": "center",
97323
- "Align right": "right",
97324
- Justify: "justify"
97325
- };
97326
- const align = alignMap[b2.t];
97327
- if (align) {
97328
- p3.onUpdateTextStyle({ align });
98414
+ const raw = state2?.presence;
98415
+ if (!raw || typeof raw !== "object") {
98416
+ return;
97329
98417
  }
97330
- };
97331
- return /* @__PURE__ */ jsx(
97332
- "button",
98418
+ const sanitized = sanitizePresence({ ...raw, clientId: cid }, canvasWidth, canvasHeight);
98419
+ if (!sanitized) {
98420
+ return;
98421
+ }
98422
+ const updatedAt = new Date(sanitized.lastUpdated).getTime();
98423
+ if (Number.isNaN(updatedAt) || now - updatedAt > STALE_PRESENCE_MS) {
98424
+ return;
98425
+ }
98426
+ users.push(sanitized);
98427
+ });
98428
+ setRemoteUsers(users);
98429
+ };
98430
+ awareness.on("change", handleChange);
98431
+ awareness.on("update", handleChange);
98432
+ handleChange();
98433
+ return () => {
98434
+ awareness.off("change", handleChange);
98435
+ awareness.off("update", handleChange);
98436
+ };
98437
+ }, [awareness, localClientId, canvasWidth, canvasHeight]);
98438
+ useEffect(() => {
98439
+ if (!awareness) {
98440
+ return;
98441
+ }
98442
+ const interval = setInterval(() => {
98443
+ awareness.setLocalStateField("presence", {
98444
+ ...latestLocalState.current,
98445
+ userName,
98446
+ userColor,
98447
+ userAvatar,
98448
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
98449
+ });
98450
+ }, 1e4);
98451
+ return () => clearInterval(interval);
98452
+ }, [awareness, userName, userColor, userAvatar]);
98453
+ useEffect(() => {
98454
+ return () => {
98455
+ if (pendingBroadcastRef.current) {
98456
+ clearTimeout(pendingBroadcastRef.current);
98457
+ }
98458
+ };
98459
+ }, []);
98460
+ return { remoteUsers, broadcastPresence };
98461
+ }
98462
+ function useYjsProvider({ config }) {
98463
+ const [status, setStatus] = useState("disconnected");
98464
+ const [awareness, setAwareness] = useState(null);
98465
+ const [doc2, setDoc] = useState(null);
98466
+ const [clientId, setClientId] = useState(null);
98467
+ const cleanupRef = useRef(null);
98468
+ const init = useCallback(async () => {
98469
+ const roomId = validateRoomId(config.roomId);
98470
+ setStatus("connecting");
98471
+ try {
98472
+ const [Y, { WebsocketProvider: WebsocketProvider2 }] = await Promise.all([Promise.resolve().then(() => (init_yjs(), yjs_exports)), Promise.resolve().then(() => (init_y_websocket(), y_websocket_exports))]);
98473
+ const yDoc = new Y.Doc();
98474
+ const provider = new WebsocketProvider2(
98475
+ config.serverUrl,
98476
+ roomId,
98477
+ yDoc,
98478
+ // eslint-disable-line @typescript-eslint/no-explicit-any
97333
98479
  {
97334
- type: "button",
97335
- disabled: !canMut,
97336
- onMouseDown: (e2) => e2.preventDefault(),
97337
- onClick: handleClick,
97338
- className: i3 < a2.length - 1 ? gB : gL,
97339
- title: b2.t,
97340
- children: b2.i
97341
- },
97342
- b2.t
98480
+ params: config.authToken ? { token: config.authToken } : void 0
98481
+ }
97343
98482
  );
97344
- }) })
97345
- ] });
98483
+ const handleStatus = (event) => {
98484
+ if (event.status === "connected") {
98485
+ setStatus("connected");
98486
+ } else if (event.status === "disconnected") {
98487
+ setStatus("disconnected");
98488
+ }
98489
+ };
98490
+ provider.on("status", handleStatus);
98491
+ if (provider.wsconnected) {
98492
+ setStatus("connected");
98493
+ }
98494
+ setDoc(yDoc);
98495
+ setAwareness(provider.awareness);
98496
+ setClientId(provider.awareness.clientID);
98497
+ cleanupRef.current = () => {
98498
+ provider.off("status", handleStatus);
98499
+ provider.destroy();
98500
+ yDoc.destroy();
98501
+ setDoc(null);
98502
+ setAwareness(null);
98503
+ setClientId(null);
98504
+ setStatus("disconnected");
98505
+ };
98506
+ } catch (err) {
98507
+ console.warn(
98508
+ "[pptx-viewer] Collaboration packages not available:",
98509
+ err instanceof Error ? err.message : err
98510
+ );
98511
+ setStatus("error");
98512
+ }
98513
+ }, [config.roomId, config.serverUrl, config.authToken]);
98514
+ useEffect(() => {
98515
+ init();
98516
+ return () => {
98517
+ cleanupRef.current?.();
98518
+ cleanupRef.current = null;
98519
+ };
98520
+ }, [init]);
98521
+ return { status, awareness, doc: doc2, clientId };
98522
+ }
98523
+
98524
+ // src/viewer/hooks/collaboration/useCollaborativeState.ts
98525
+ function useCollaborativeState({
98526
+ config,
98527
+ canvasWidth,
98528
+ canvasHeight
98529
+ }) {
98530
+ const userColor = sanitizeColor(config.userColor, "#6366f1");
98531
+ const { status, awareness, doc: doc2, clientId } = useYjsProvider({ config });
98532
+ const { remoteUsers, broadcastPresence } = usePresenceTracking({
98533
+ awareness,
98534
+ localClientId: clientId,
98535
+ userName: config.userName,
98536
+ userColor,
98537
+ userAvatar: config.userAvatar,
98538
+ canvasWidth,
98539
+ canvasHeight
98540
+ });
98541
+ const connectedCount = status === "connected" ? remoteUsers.length + 1 : remoteUsers.length;
98542
+ return {
98543
+ status,
98544
+ remoteUsers,
98545
+ broadcastPresence,
98546
+ connectedCount,
98547
+ config,
98548
+ doc: doc2
98549
+ };
98550
+ }
98551
+ var CollaborationContext = createContext(null);
98552
+ function useCollaboration() {
98553
+ return useContext(CollaborationContext);
98554
+ }
98555
+ function CollaborationProvider({
98556
+ config,
98557
+ canvasWidth,
98558
+ canvasHeight,
98559
+ children
98560
+ }) {
98561
+ const value = useCollaborativeState({
98562
+ config,
98563
+ canvasWidth,
98564
+ canvasHeight
98565
+ });
98566
+ return /* @__PURE__ */ jsx(CollaborationContext.Provider, { value, children });
98567
+ }
98568
+ var STATUS_STYLES = {
98569
+ connected: {
98570
+ dot: "bg-green-400",
98571
+ text: "text-green-400",
98572
+ label: "Connected"
98573
+ },
98574
+ connecting: {
98575
+ dot: "bg-yellow-400 animate-pulse",
98576
+ text: "text-yellow-400",
98577
+ label: "Connecting..."
98578
+ },
98579
+ disconnected: {
98580
+ dot: "bg-gray-500",
98581
+ text: "text-gray-500",
98582
+ label: "Disconnected"
98583
+ },
98584
+ error: {
98585
+ dot: "bg-red-400",
98586
+ text: "text-red-400",
98587
+ label: "Connection error"
98588
+ }
98589
+ };
98590
+ function CollaborationStatusIndicator({
98591
+ status,
98592
+ connectedCount
98593
+ }) {
98594
+ const { t: t2 } = useTranslation();
98595
+ const style = STATUS_STYLES[status];
98596
+ return /* @__PURE__ */ jsxs(
98597
+ "div",
98598
+ {
98599
+ "data-testid": "collaboration-status",
98600
+ className: "flex items-center gap-1.5",
98601
+ "aria-label": t2("pptx.collaboration.statusAriaLabel", {
98602
+ status: t2(`pptx.collaboration.status.${status}`),
98603
+ count: connectedCount
98604
+ }),
98605
+ children: [
98606
+ /* @__PURE__ */ jsx("span", { className: `inline-block w-2 h-2 rounded-full ${style.dot}`, "aria-hidden": "true" }),
98607
+ /* @__PURE__ */ jsx("span", { className: `text-[10px] ${style.text}`, children: status === "connected" ? t2("pptx.collaboration.userCount", { count: connectedCount }) : t2(`pptx.collaboration.status.${status}`) })
98608
+ ]
98609
+ }
98610
+ );
97346
98611
  }
97347
98612
  function CustomShowsControls({
97348
98613
  customShows,
@@ -97590,7 +98855,6 @@ function ModeSwitcher({
97590
98855
  mode,
97591
98856
  onSetMode,
97592
98857
  onCloseMasterView,
97593
- onToggleSlideSorter,
97594
98858
  onEnterPresenterView,
97595
98859
  onEnterRehearsalMode,
97596
98860
  onOpenSetUpSlideShow,
@@ -97599,64 +98863,33 @@ function ModeSwitcher({
97599
98863
  showSubtitles
97600
98864
  }) {
97601
98865
  if (mode === "master") {
97602
- return /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2", children: [
97603
- /* @__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" }),
98866
+ return /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-1.5", children: [
98867
+ /* @__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" }),
97604
98868
  /* @__PURE__ */ jsx(
97605
98869
  "button",
97606
98870
  {
97607
98871
  type: "button",
97608
98872
  onClick: onCloseMasterView,
97609
- className: "px-2.5 py-1 rounded bg-muted hover:bg-accent text-[11px] text-foreground transition-colors",
98873
+ className: "px-2 py-0.5 rounded-sm hover:bg-accent text-[10px] text-foreground transition-colors",
97610
98874
  title: "Close master view",
97611
- children: "Close Master View"
98875
+ children: "Close"
97612
98876
  }
97613
98877
  )
97614
98878
  ] });
97615
98879
  }
97616
- return /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center rounded bg-muted text-[11px] overflow-hidden", children: [
97617
- MODES.map(
97618
- (m2) => m2 === "present" ? /* @__PURE__ */ jsx(
97619
- PresentDropdown,
97620
- {
97621
- isActive: mode === m2,
97622
- onPresent: () => onSetMode(m2),
97623
- onPresenterView: onEnterPresenterView,
97624
- onRehearse: onEnterRehearsalMode,
97625
- onSetUpSlideShow: onOpenSetUpSlideShow,
97626
- onBroadcast: onOpenBroadcastDialog,
97627
- onToggleSubtitles,
97628
- showSubtitles
97629
- },
97630
- m2
97631
- ) : /* @__PURE__ */ jsxs(
97632
- "button",
97633
- {
97634
- type: "button",
97635
- onClick: () => onSetMode(m2),
97636
- className: cn(
97637
- "px-2 py-1 transition-colors border-l border-border first:border-l-0",
97638
- mode === m2 ? "bg-primary text-primary-foreground" : "hover:bg-accent text-foreground"
97639
- ),
97640
- title: `${m2[0].toUpperCase()}${m2.slice(1)} mode`,
97641
- children: [
97642
- m2[0].toUpperCase(),
97643
- m2.slice(1)
97644
- ]
97645
- },
97646
- m2
97647
- )
97648
- ),
97649
- /* @__PURE__ */ jsx(
97650
- "button",
97651
- {
97652
- type: "button",
97653
- onClick: onToggleSlideSorter,
97654
- className: "px-2 py-1 border-l border-border hover:bg-accent text-foreground transition-colors",
97655
- title: "Slide sorter",
97656
- children: "Sorter"
97657
- }
97658
- )
97659
- ] });
98880
+ return /* @__PURE__ */ jsx(
98881
+ PresentDropdown,
98882
+ {
98883
+ isActive: mode === "present",
98884
+ onPresent: () => onSetMode("present"),
98885
+ onPresenterView: onEnterPresenterView,
98886
+ onRehearse: onEnterRehearsalMode,
98887
+ onSetUpSlideShow: onOpenSetUpSlideShow,
98888
+ onBroadcast: onOpenBroadcastDialog,
98889
+ onToggleSubtitles,
98890
+ showSubtitles
98891
+ }
98892
+ );
97660
98893
  }
97661
98894
  function OverflowMenu(p3) {
97662
98895
  const ovAct = (k2) => {
@@ -97724,14 +98957,12 @@ function OverflowMenu(p3) {
97724
98957
  ] });
97725
98958
  }
97726
98959
  function ToolbarPrimaryRow(p3) {
98960
+ const { t: t2 } = useTranslation();
97727
98961
  const {
97728
98962
  mode,
97729
98963
  canEdit,
97730
- isNarrowViewport,
97731
98964
  isSidebarCollapsed,
97732
98965
  isInspectorPaneOpen,
97733
- isCompactToolbarOpen,
97734
- scale,
97735
98966
  canUndo,
97736
98967
  canRedo,
97737
98968
  undoLabel,
@@ -97739,26 +98970,21 @@ function ToolbarPrimaryRow(p3) {
97739
98970
  findReplaceOpen,
97740
98971
  onToggleSidebar,
97741
98972
  onToggleInspector,
97742
- onToggleCompactToolbar,
97743
- onZoomIn,
97744
- onZoomOut,
97745
- onZoomToFit,
97746
98973
  onUndo,
97747
98974
  onRedo,
97748
98975
  onToggleFindReplace
97749
98976
  } = p3;
97750
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 max-md:gap-0.5 flex-wrap", children: [
98977
+ const collab = useCollaboration();
98978
+ 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";
98979
+ 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: [
97751
98980
  mode !== "present" && /* @__PURE__ */ jsx(
97752
98981
  "button",
97753
98982
  {
97754
98983
  type: "button",
97755
98984
  onClick: onToggleSidebar,
97756
- className: cn(
97757
- "p-1.5 max-md:p-2.5 max-md:min-h-[44px] max-md:min-w-[44px] rounded transition-colors",
97758
- !isSidebarCollapsed ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
97759
- ),
97760
- title: "Toggle slides panel",
97761
- "aria-label": "Toggle slides panel",
98985
+ className: cn(qab, !isSidebarCollapsed ? "text-foreground" : "text-muted-foreground"),
98986
+ title: t2("pptx.toolbar.toggleSlidesPanel"),
98987
+ "aria-label": t2("pptx.toolbar.toggleSlidesPanel"),
97762
98988
  children: /* @__PURE__ */ jsx(LuPanelLeft, { className: ic2 })
97763
98989
  }
97764
98990
  ),
@@ -97766,77 +98992,84 @@ function ToolbarPrimaryRow(p3) {
97766
98992
  /* @__PURE__ */ jsx(
97767
98993
  "button",
97768
98994
  {
97769
- onClick: onZoomOut,
97770
- 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",
97771
- title: "Zoom out",
97772
- "aria-label": "Zoom out",
97773
- children: /* @__PURE__ */ jsx(LuZoomOut, { className: ics })
97774
- }
97775
- ),
97776
- /* @__PURE__ */ jsxs(
97777
- "button",
97778
- {
97779
- onClick: onZoomToFit,
97780
- 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",
97781
- title: "Zoom to fit",
97782
- children: [
97783
- Math.round(scale * 100),
97784
- "%"
97785
- ]
98995
+ type: "button",
98996
+ onClick: onUndo,
98997
+ disabled: !canEdit || !canUndo,
98998
+ className: cn(qab, "text-muted-foreground"),
98999
+ title: undoLabel ? t2("pptx.toolbar.undoAction", { action: undoLabel }) : t2("pptx.toolbar.undo"),
99000
+ "aria-label": t2("pptx.toolbar.undo"),
99001
+ children: /* @__PURE__ */ jsx(LuUndo, { className: ics })
97786
99002
  }
97787
99003
  ),
97788
99004
  /* @__PURE__ */ jsx(
97789
99005
  "button",
97790
99006
  {
97791
- onClick: onZoomIn,
97792
- 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",
97793
- title: "Zoom in",
97794
- "aria-label": "Zoom in",
97795
- children: /* @__PURE__ */ jsx(LuZoomIn, { className: ics })
99007
+ type: "button",
99008
+ onClick: onRedo,
99009
+ disabled: !canEdit || !canRedo,
99010
+ className: cn(qab, "text-muted-foreground"),
99011
+ title: redoLabel ? t2("pptx.toolbar.redoAction", { action: redoLabel }) : t2("pptx.toolbar.redo"),
99012
+ "aria-label": t2("pptx.toolbar.redo"),
99013
+ children: /* @__PURE__ */ jsx(LuRedo, { className: ics })
97796
99014
  }
97797
99015
  ),
97798
- sep,
97799
- /* @__PURE__ */ jsxs("div", { className: grp, children: [
97800
- /* @__PURE__ */ jsx(
97801
- "button",
97802
- {
97803
- type: "button",
97804
- onClick: onUndo,
97805
- disabled: !canEdit || !canUndo,
97806
- className: gB,
97807
- title: undoLabel ? `Undo: ${undoLabel}` : "Undo",
97808
- "aria-label": "Undo",
97809
- children: /* @__PURE__ */ jsx(LuUndo, { className: ics })
97810
- }
97811
- ),
97812
- /* @__PURE__ */ jsx(
97813
- "button",
97814
- {
97815
- type: "button",
97816
- onClick: onRedo,
97817
- disabled: !canEdit || !canRedo,
97818
- className: gL,
97819
- title: redoLabel ? `Redo: ${redoLabel}` : "Redo",
97820
- "aria-label": "Redo",
97821
- children: /* @__PURE__ */ jsx(LuRedo, { className: ics })
97822
- }
97823
- )
97824
- ] }),
97825
99016
  (mode === "edit" || mode === "master") && /* @__PURE__ */ jsx(
97826
99017
  "button",
97827
99018
  {
97828
99019
  type: "button",
97829
99020
  onClick: onToggleFindReplace,
97830
99021
  className: cn(
97831
- "p-1.5 max-md:p-2.5 max-md:min-h-[44px] max-md:min-w-[44px] rounded transition-colors max-md:hidden",
97832
- findReplaceOpen ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
99022
+ qab,
99023
+ "max-md:hidden",
99024
+ findReplaceOpen ? "text-foreground" : "text-muted-foreground"
97833
99025
  ),
97834
- title: "Find & Replace",
97835
- "aria-label": "Find and Replace",
99026
+ title: t2("pptx.findReplace.title"),
99027
+ "aria-label": t2("pptx.findReplace.title"),
97836
99028
  children: /* @__PURE__ */ jsx(LuSearch, { className: ics })
97837
99029
  }
97838
99030
  ),
97839
99031
  /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-2 max-md:min-w-1" }),
99032
+ (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxs(
99033
+ "button",
99034
+ {
99035
+ type: "button",
99036
+ onClick: p3.onToggleComments,
99037
+ className: cn(
99038
+ qab,
99039
+ "max-md:hidden",
99040
+ p3.isCommentsPanelOpen ? "text-foreground" : "text-muted-foreground"
99041
+ ),
99042
+ title: t2("pptx.toolbar.comments"),
99043
+ "aria-label": t2("pptx.toolbar.comments"),
99044
+ children: [
99045
+ /* @__PURE__ */ jsx(LuMessageSquare, { className: ics }),
99046
+ (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 })
99047
+ ]
99048
+ }
99049
+ ),
99050
+ 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: [
99051
+ collab.remoteUsers.slice(0, 4).map((user) => /* @__PURE__ */ jsx(
99052
+ "div",
99053
+ {
99054
+ className: "w-6 h-6 rounded-full border-2 border-background flex items-center justify-center text-[8px] font-semibold text-white shrink-0",
99055
+ style: { backgroundColor: user.userColor },
99056
+ title: user.userName,
99057
+ children: user.userAvatar ? /* @__PURE__ */ jsx(
99058
+ "img",
99059
+ {
99060
+ src: user.userAvatar,
99061
+ alt: user.userName,
99062
+ className: "w-full h-full rounded-full object-cover"
99063
+ }
99064
+ ) : user.userName.slice(0, 2).toUpperCase()
99065
+ },
99066
+ user.clientId
99067
+ )),
99068
+ 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: [
99069
+ "+",
99070
+ collab.remoteUsers.length - 4
99071
+ ] })
99072
+ ] }),
97840
99073
  /* @__PURE__ */ jsx(
97841
99074
  ModeSwitcher,
97842
99075
  {
@@ -97867,40 +99100,80 @@ function ToolbarPrimaryRow(p3) {
97867
99100
  }
97868
99101
  ),
97869
99102
  sep,
97870
- (mode === "edit" || mode === "master") && /* @__PURE__ */ jsx(
99103
+ (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxs(
97871
99104
  "button",
97872
99105
  {
97873
99106
  type: "button",
97874
- onClick: onToggleInspector,
99107
+ onClick: p3.onOpenShareDialog ?? p3.onPackageForSharing,
97875
99108
  className: cn(
97876
- "p-1.5 max-md:p-2.5 max-md:min-h-[44px] max-md:min-w-[44px] rounded transition-colors",
97877
- isInspectorPaneOpen ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
99109
+ "relative inline-flex items-center gap-1 px-2.5 py-1 rounded-sm text-[11px] font-medium transition-colors",
99110
+ collab && collab.status === "connected" ? "bg-green-600 hover:bg-green-500 text-white" : "bg-primary hover:bg-primary/90 text-primary-foreground"
97878
99111
  ),
97879
- title: "Toggle inspector panel",
97880
- "aria-label": "Toggle inspector panel",
99112
+ title: collab && collab.status === "connected" ? t2("pptx.toolbar.sharingUsers", { count: collab.connectedCount }) : t2("pptx.toolbar.share"),
99113
+ "aria-label": t2("pptx.toolbar.share"),
99114
+ children: [
99115
+ /* @__PURE__ */ jsx(LuShare2, { className: "w-3 h-3" }),
99116
+ /* @__PURE__ */ jsx("span", { className: "max-md:hidden", children: collab && collab.status === "connected" ? t2("pptx.toolbar.sharingCount", { count: collab.connectedCount }) : t2("pptx.toolbar.share") })
99117
+ ]
99118
+ }
99119
+ ),
99120
+ (mode === "edit" || mode === "master") && /* @__PURE__ */ jsx(
99121
+ "button",
99122
+ {
99123
+ type: "button",
99124
+ onClick: onToggleInspector,
99125
+ className: cn(qab, isInspectorPaneOpen ? "text-foreground" : "text-muted-foreground"),
99126
+ title: t2("pptx.toolbar.toggleInspector"),
99127
+ "aria-label": t2("pptx.toolbar.toggleInspector"),
97881
99128
  children: /* @__PURE__ */ jsx(LuPanelRight, { className: ic2 })
97882
99129
  }
97883
99130
  ),
97884
- !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" }),
97885
- /* @__PURE__ */ jsx(OverflowMenu, { ...p3 }),
97886
- isNarrowViewport && (mode === "edit" || mode === "master") && /* @__PURE__ */ jsx(
99131
+ /* @__PURE__ */ jsx(
97887
99132
  "button",
97888
99133
  {
97889
99134
  type: "button",
97890
- onClick: onToggleCompactToolbar,
97891
- className: cn(
97892
- "p-1.5 rounded text-[11px] transition-colors",
97893
- isCompactToolbarOpen ? "bg-primary/80 text-primary-foreground" : "bg-muted hover:bg-accent"
97894
- ),
97895
- title: "Toggle editing tools",
97896
- children: isCompactToolbarOpen ? "Less" : "Tools"
99135
+ onClick: p3.onOpenSettings ?? p3.onToggleShortcuts,
99136
+ className: cn(qab, "text-muted-foreground"),
99137
+ title: t2("pptx.toolbar.settingsShortcuts"),
99138
+ "aria-label": t2("pptx.toolbar.settings"),
99139
+ children: /* @__PURE__ */ jsx(LuSettings, { className: ics })
97897
99140
  }
97898
- )
99141
+ ),
99142
+ !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") }),
99143
+ /* @__PURE__ */ jsx(OverflowMenu, { ...p3 })
97899
99144
  ] });
97900
99145
  }
97901
99146
  function ViewSection(p3) {
97902
99147
  const { t: t2 } = useTranslation();
97903
99148
  return /* @__PURE__ */ jsxs(Fragment, { children: [
99149
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
99150
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
99151
+ /* @__PURE__ */ jsx("button", { className: pill, title: "Normal view", children: "Normal" }),
99152
+ 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" }),
99153
+ /* @__PURE__ */ jsx("button", { className: pill, title: "Reading View", children: "Reading View" })
99154
+ ] }),
99155
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Presentation Views" })
99156
+ ] }),
99157
+ sep,
99158
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
99159
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-0.5", children: /* @__PURE__ */ jsx(
99160
+ "button",
99161
+ {
99162
+ onClick: p3.onEnterMasterView,
99163
+ disabled: !p3.canEdit,
99164
+ className: pill,
99165
+ title: "Edit slide masters and layouts",
99166
+ children: "Slide Master"
99167
+ }
99168
+ ) }),
99169
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Master Views" })
99170
+ ] }),
99171
+ sep,
99172
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-0.5", children: [
99173
+ /* @__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" }) }),
99174
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground leading-none", children: "Zoom" })
99175
+ ] }),
99176
+ sep,
97904
99177
  /* @__PURE__ */ jsx(
97905
99178
  "button",
97906
99179
  {
@@ -97993,60 +99266,105 @@ function ViewSection(p3) {
97993
99266
  title: "Toggle spell check",
97994
99267
  children: "Spell"
97995
99268
  }
97996
- ),
97997
- sep,
97998
- /* @__PURE__ */ jsx(
97999
- "button",
98000
- {
98001
- onClick: p3.onEnterMasterView,
98002
- disabled: !p3.canEdit,
98003
- className: pill,
98004
- title: "Edit slide masters and layouts",
98005
- children: "Slide Master"
98006
- }
98007
99269
  )
98008
99270
  ] });
98009
99271
  }
98010
99272
  function Toolbar(p3) {
98011
99273
  const { mode, isNarrowViewport, isCompactToolbarOpen, toolbarSection, onSetToolbarSection } = p3;
99274
+ const sFil = toolbarSection === "file";
98012
99275
  const sHome = toolbarSection === "home";
98013
- const sIns = sHome || toolbarSection === "insert";
99276
+ const sIns = toolbarSection === "insert";
98014
99277
  const sTxt = sHome || toolbarSection === "text";
98015
99278
  const sArr = toolbarSection === "arrange";
98016
99279
  const sDrw = toolbarSection === "draw";
98017
99280
  const sDes = toolbarSection === "design";
98018
99281
  const sTrn = toolbarSection === "transitions";
99282
+ const sAni = toolbarSection === "animations";
99283
+ const sSlw = toolbarSection === "slideShow";
98019
99284
  const sRev = toolbarSection === "review";
98020
99285
  const sViw = toolbarSection === "view";
99286
+ const sHlp = toolbarSection === "help";
99287
+ const showRibbon = mode === "edit" || mode === "master";
98021
99288
  return /* @__PURE__ */ jsxs(
98022
99289
  "div",
98023
99290
  {
98024
99291
  role: "toolbar",
98025
99292
  "aria-label": "Presentation toolbar",
98026
- 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",
99293
+ className: "relative z-20 border-b border-border bg-secondary/50 overflow-visible",
98027
99294
  children: [
98028
99295
  /* @__PURE__ */ jsx(ToolbarPrimaryRow, { ...p3 }),
98029
- (mode === "edit" || mode === "master") && /* @__PURE__ */ jsxs(
99296
+ 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: [
99297
+ TOOLBAR_SECTIONS.map((s) => /* @__PURE__ */ jsx(
99298
+ "button",
99299
+ {
99300
+ type: "button",
99301
+ onClick: () => onSetToolbarSection(s.id),
99302
+ className: cn(
99303
+ "relative px-3.5 py-2 text-[12px] font-medium whitespace-nowrap transition-colors max-md:min-h-[36px] max-md:px-3",
99304
+ 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"
99305
+ ),
99306
+ children: s.label
99307
+ },
99308
+ s.id
99309
+ )),
99310
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
99311
+ isNarrowViewport && /* @__PURE__ */ jsx(
99312
+ "button",
99313
+ {
99314
+ type: "button",
99315
+ onClick: p3.onToggleCompactToolbar,
99316
+ className: cn(
99317
+ "px-2 py-1 rounded text-[11px] transition-colors mr-1",
99318
+ isCompactToolbarOpen ? "bg-primary/80 text-primary-foreground" : "text-muted-foreground hover:text-foreground"
99319
+ ),
99320
+ title: "Toggle ribbon",
99321
+ children: isCompactToolbarOpen ? "Collapse" : "Expand"
99322
+ }
99323
+ )
99324
+ ] }),
99325
+ showRibbon && /* @__PURE__ */ jsxs(
98030
99326
  "div",
98031
99327
  {
98032
99328
  className: cn(
98033
- "flex items-center gap-1.5 flex-wrap mt-1.5",
99329
+ "flex items-center gap-1.5 px-2 py-1 max-md:px-1 max-md:py-0.5 overflow-visible flex-nowrap",
98034
99330
  isNarrowViewport && !isCompactToolbarOpen && "hidden"
98035
99331
  ),
98036
99332
  children: [
98037
- /* @__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(
98038
- "button",
99333
+ sFil && /* @__PURE__ */ jsx(
99334
+ FileSection,
98039
99335
  {
98040
- type: "button",
98041
- onClick: () => onSetToolbarSection(s.id),
98042
- className: cn(
98043
- "rounded px-2 py-0.5 transition-colors whitespace-nowrap max-md:min-h-[36px] max-md:px-3",
98044
- toolbarSection === s.id ? "bg-primary text-primary-foreground" : "text-foreground hover:bg-accent"
98045
- ),
98046
- children: s.label
98047
- },
98048
- s.id
98049
- )) }),
99336
+ onExportPng: p3.onExportPng,
99337
+ onExportPdf: p3.onExportPdf,
99338
+ onExportVideo: p3.onExportVideo,
99339
+ onExportGif: p3.onExportGif,
99340
+ onPackageForSharing: p3.onPackageForSharing,
99341
+ onSaveAsPpsx: p3.onSaveAsPpsx,
99342
+ onSaveAsPptm: p3.onSaveAsPptm,
99343
+ hasMacros: p3.hasMacros,
99344
+ onCopySlideAsImage: p3.onCopySlideAsImage,
99345
+ onPrint: p3.onPrint,
99346
+ onOpenDocumentProperties: p3.onOpenDocumentProperties,
99347
+ onOpenPasswordProtection: p3.onOpenPasswordProtection,
99348
+ onOpenFontEmbedding: p3.onOpenFontEmbedding,
99349
+ onOpenDigitalSignatures: p3.onOpenDigitalSignatures
99350
+ }
99351
+ ),
99352
+ sHome && /* @__PURE__ */ jsx(
99353
+ HomeSection,
99354
+ {
99355
+ canEdit: p3.canEdit,
99356
+ clipboardPayload: p3.clipboardPayload,
99357
+ formatPainterActive: p3.formatPainterActive,
99358
+ onCopy: p3.onCopy,
99359
+ onCut: p3.onCut,
99360
+ onPaste: p3.onPaste,
99361
+ onToggleFormatPainter: p3.onToggleFormatPainter,
99362
+ layoutOptions: p3.layoutOptions,
99363
+ onInsertSlideFromLayout: p3.onInsertSlideFromLayout,
99364
+ selectedElement: p3.selectedElement,
99365
+ onUpdateTextStyle: p3.onUpdateTextStyle
99366
+ }
99367
+ ),
98050
99368
  sIns && /* @__PURE__ */ jsx(
98051
99369
  InsertSection,
98052
99370
  {
@@ -98109,7 +99427,10 @@ function Toolbar(p3) {
98109
99427
  onToggleThemeGallery: p3.onToggleThemeGallery,
98110
99428
  isThemeGalleryOpen: p3.isThemeGalleryOpen,
98111
99429
  onToggleThemeEditor: p3.onToggleThemeEditor,
98112
- isThemeEditorOpen: p3.isThemeEditorOpen
99430
+ isThemeEditorOpen: p3.isThemeEditorOpen,
99431
+ onOpenDocumentProperties: p3.onOpenDocumentProperties,
99432
+ onToggleInspector: p3.onToggleInspector,
99433
+ isInspectorPaneOpen: p3.isInspectorPaneOpen
98113
99434
  }
98114
99435
  ),
98115
99436
  sTrn && /* @__PURE__ */ jsx(
@@ -98119,6 +99440,33 @@ function Toolbar(p3) {
98119
99440
  onToggleInspector: p3.onToggleInspector
98120
99441
  }
98121
99442
  ),
99443
+ sAni && /* @__PURE__ */ jsx(
99444
+ AnimationsSection,
99445
+ {
99446
+ canEdit: p3.canEdit,
99447
+ selectedElement: p3.selectedElement,
99448
+ isInspectorPaneOpen: p3.isInspectorPaneOpen,
99449
+ onToggleInspector: p3.onToggleInspector
99450
+ }
99451
+ ),
99452
+ sSlw && /* @__PURE__ */ jsx(
99453
+ SlideShowSection,
99454
+ {
99455
+ onPresent: () => p3.onSetMode("present"),
99456
+ onEnterPresenterView: p3.onEnterPresenterView ?? (() => {
99457
+ }),
99458
+ onEnterRehearsalMode: p3.onEnterRehearsalMode ?? (() => {
99459
+ }),
99460
+ onOpenSetUpSlideShow: p3.onOpenSetUpSlideShow ?? (() => {
99461
+ }),
99462
+ onOpenBroadcastDialog: p3.onOpenBroadcastDialog ?? (() => {
99463
+ }),
99464
+ onToggleSubtitles: p3.onToggleSubtitles ?? (() => {
99465
+ }),
99466
+ showSubtitles: p3.showSubtitles ?? false,
99467
+ onSetMode: p3.onSetMode
99468
+ }
99469
+ ),
98122
99470
  sRev && /* @__PURE__ */ jsx(
98123
99471
  ReviewSection,
98124
99472
  {
@@ -98154,7 +99502,29 @@ function Toolbar(p3) {
98154
99502
  eyedropperActive: p3.eyedropperActive,
98155
99503
  onToggleEyedropper: p3.onToggleEyedropper
98156
99504
  }
98157
- )
99505
+ ),
99506
+ sHlp && /* @__PURE__ */ jsxs(Fragment, { children: [
99507
+ /* @__PURE__ */ jsx(
99508
+ "button",
99509
+ {
99510
+ type: "button",
99511
+ onClick: p3.onToggleShortcuts,
99512
+ className: pill,
99513
+ title: "Keyboard shortcuts",
99514
+ children: "Keyboard Shortcuts"
99515
+ }
99516
+ ),
99517
+ /* @__PURE__ */ jsx(
99518
+ "button",
99519
+ {
99520
+ type: "button",
99521
+ onClick: p3.onRunAccessibilityCheck,
99522
+ className: pill,
99523
+ title: "Accessibility check",
99524
+ children: "Accessibility"
99525
+ }
99526
+ )
99527
+ ] })
98158
99528
  ]
98159
99529
  }
98160
99530
  )
@@ -105575,6 +106945,333 @@ function BroadcastDialog({
105575
106945
  ] }) })
105576
106946
  ] });
105577
106947
  }
106948
+ function getInitials(name) {
106949
+ const parts = name.trim().split(/\s+/);
106950
+ if (parts.length >= 2) {
106951
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
106952
+ }
106953
+ return name.slice(0, 2).toUpperCase();
106954
+ }
106955
+ function ShareDialog({
106956
+ open,
106957
+ onClose,
106958
+ activeCollaboration,
106959
+ onStartCollaboration,
106960
+ onStopCollaboration,
106961
+ preconfigured,
106962
+ defaultRoomId,
106963
+ defaultUserName,
106964
+ defaultServerUrl
106965
+ }) {
106966
+ const collab = useCollaboration();
106967
+ const isActive = collab !== null && collab.status !== "disconnected" && collab.status !== "error";
106968
+ const { t: t2 } = useTranslation();
106969
+ const [roomId, setRoomId] = useState(defaultRoomId ?? "");
106970
+ const [userName, setUserName] = useState(defaultUserName ?? "");
106971
+ const [serverUrl, setServerUrl] = useState(defaultServerUrl ?? "");
106972
+ const [copied, setCopied] = useState(false);
106973
+ const dialogRef = useRef(null);
106974
+ useEffect(() => {
106975
+ if (activeCollaboration) {
106976
+ setRoomId(activeCollaboration.roomId);
106977
+ setUserName(activeCollaboration.userName);
106978
+ setServerUrl(activeCollaboration.serverUrl);
106979
+ }
106980
+ }, [activeCollaboration]);
106981
+ useEffect(() => {
106982
+ if (!open) {
106983
+ return;
106984
+ }
106985
+ function handleKeyDown(e2) {
106986
+ if (e2.key === "Escape") {
106987
+ onClose();
106988
+ }
106989
+ }
106990
+ document.addEventListener("keydown", handleKeyDown);
106991
+ return () => document.removeEventListener("keydown", handleKeyDown);
106992
+ }, [open, onClose]);
106993
+ useEffect(() => {
106994
+ if (open && dialogRef.current) {
106995
+ dialogRef.current.focus();
106996
+ }
106997
+ }, [open]);
106998
+ const handleCopyRoomId = useCallback(() => {
106999
+ const config = activeCollaboration ?? { roomId, serverUrl };
107000
+ const shareUrl = typeof window !== "undefined" ? `${window.location.origin}${window.location.pathname}?room=${encodeURIComponent(config.roomId)}&server=${encodeURIComponent(config.serverUrl)}` : config.roomId;
107001
+ void navigator.clipboard.writeText(shareUrl).then(() => {
107002
+ setCopied(true);
107003
+ setTimeout(() => setCopied(false), 2e3);
107004
+ return void 0;
107005
+ });
107006
+ }, [activeCollaboration, roomId, serverUrl]);
107007
+ const handleStartSharing = useCallback(() => {
107008
+ if (!roomId.trim() || !userName.trim()) {
107009
+ return;
107010
+ }
107011
+ onStartCollaboration?.({
107012
+ roomId: roomId.trim(),
107013
+ serverUrl: serverUrl.trim(),
107014
+ userName: userName.trim()
107015
+ });
107016
+ }, [roomId, userName, serverUrl, onStartCollaboration]);
107017
+ const canStart = roomId.trim().length > 0 && userName.trim().length > 0 && serverUrl.trim().length > 0;
107018
+ if (!open) {
107019
+ return null;
107020
+ }
107021
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
107022
+ /* @__PURE__ */ jsx(
107023
+ "button",
107024
+ {
107025
+ type: "button",
107026
+ className: "fixed inset-0 z-[200] bg-black/50",
107027
+ "aria-label": t2("pptx.share.closeDialog"),
107028
+ onClick: onClose
107029
+ }
107030
+ ),
107031
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[201] flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs(
107032
+ "div",
107033
+ {
107034
+ ref: dialogRef,
107035
+ role: "dialog",
107036
+ "aria-modal": "true",
107037
+ "aria-label": t2("pptx.share.title"),
107038
+ tabIndex: -1,
107039
+ className: "pointer-events-auto w-full max-w-md rounded-xl border border-border bg-popover text-foreground shadow-2xl outline-none",
107040
+ children: [
107041
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-3 border-b border-border", children: [
107042
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: isActive ? t2("pptx.share.collaborationActive") : t2("pptx.share.title") }),
107043
+ /* @__PURE__ */ jsx(
107044
+ "button",
107045
+ {
107046
+ type: "button",
107047
+ onClick: onClose,
107048
+ className: "text-muted-foreground hover:text-foreground text-lg leading-none",
107049
+ "aria-label": t2("pptx.share.close"),
107050
+ children: "\xD7"
107051
+ }
107052
+ )
107053
+ ] }),
107054
+ /* @__PURE__ */ jsx("div", { className: "px-5 py-4", children: isActive ? /* @__PURE__ */ jsx(
107055
+ ActiveSessionView,
107056
+ {
107057
+ collab,
107058
+ activeCollaboration,
107059
+ copied,
107060
+ onCopyRoomId: handleCopyRoomId,
107061
+ onStopCollaboration
107062
+ }
107063
+ ) : /* @__PURE__ */ jsx(
107064
+ StartSessionForm,
107065
+ {
107066
+ roomId,
107067
+ userName,
107068
+ serverUrl,
107069
+ onRoomIdChange: setRoomId,
107070
+ onUserNameChange: setUserName,
107071
+ onServerUrlChange: setServerUrl,
107072
+ preconfigured
107073
+ }
107074
+ ) }),
107075
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-5 py-3 border-t border-border", children: [
107076
+ /* @__PURE__ */ jsx(
107077
+ "button",
107078
+ {
107079
+ type: "button",
107080
+ onClick: onClose,
107081
+ className: "px-3 py-1.5 rounded bg-muted hover:bg-accent text-[12px] text-foreground transition-colors",
107082
+ children: isActive ? t2("pptx.share.close") : t2("pptx.share.cancel")
107083
+ }
107084
+ ),
107085
+ !isActive && /* @__PURE__ */ jsx(
107086
+ "button",
107087
+ {
107088
+ type: "button",
107089
+ disabled: !canStart,
107090
+ onClick: handleStartSharing,
107091
+ 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",
107092
+ children: t2("pptx.share.startSharing")
107093
+ }
107094
+ )
107095
+ ] })
107096
+ ]
107097
+ }
107098
+ ) })
107099
+ ] });
107100
+ }
107101
+ function StartSessionForm({
107102
+ roomId,
107103
+ userName,
107104
+ serverUrl,
107105
+ onRoomIdChange,
107106
+ onUserNameChange,
107107
+ onServerUrlChange,
107108
+ preconfigured
107109
+ }) {
107110
+ const { t: t2 } = useTranslation();
107111
+ const inputReadOnlyClass = preconfigured ? " opacity-70 cursor-not-allowed" : "";
107112
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
107113
+ /* @__PURE__ */ jsx("p", { className: "text-[13px] text-muted-foreground leading-relaxed", children: preconfigured ? t2("pptx.share.preconfiguredDescription") : t2("pptx.share.description") }),
107114
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
107115
+ /* @__PURE__ */ jsx("label", { htmlFor: "share-room-id", className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.sessionName") }),
107116
+ /* @__PURE__ */ jsx(
107117
+ "input",
107118
+ {
107119
+ id: "share-room-id",
107120
+ type: "text",
107121
+ value: roomId,
107122
+ onChange: (e2) => onRoomIdChange(e2.target.value),
107123
+ readOnly: preconfigured,
107124
+ placeholder: t2("pptx.share.sessionPlaceholder"),
107125
+ 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}`
107126
+ }
107127
+ ),
107128
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("pptx.share.sessionHint") })
107129
+ ] }),
107130
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
107131
+ /* @__PURE__ */ jsx("label", { htmlFor: "share-user-name", className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.displayName") }),
107132
+ /* @__PURE__ */ jsx(
107133
+ "input",
107134
+ {
107135
+ id: "share-user-name",
107136
+ type: "text",
107137
+ value: userName,
107138
+ onChange: (e2) => onUserNameChange(e2.target.value),
107139
+ readOnly: preconfigured,
107140
+ placeholder: t2("pptx.share.namePlaceholder"),
107141
+ 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}`
107142
+ }
107143
+ )
107144
+ ] }),
107145
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
107146
+ /* @__PURE__ */ jsx("label", { htmlFor: "share-server-url", className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.serverLabel") }),
107147
+ /* @__PURE__ */ jsx(
107148
+ "input",
107149
+ {
107150
+ id: "share-server-url",
107151
+ type: "text",
107152
+ value: serverUrl,
107153
+ onChange: (e2) => onServerUrlChange(e2.target.value),
107154
+ readOnly: preconfigured,
107155
+ placeholder: t2("pptx.share.serverPlaceholder"),
107156
+ 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}`
107157
+ }
107158
+ )
107159
+ ] }),
107160
+ /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-muted-foreground/70 leading-relaxed", children: [
107161
+ "Run",
107162
+ " ",
107163
+ /* @__PURE__ */ jsx("code", { className: "px-1 py-0.5 rounded bg-muted text-[10px] font-mono", children: "bun run collab" }),
107164
+ " ",
107165
+ "to start the server. Others can join at",
107166
+ " ",
107167
+ /* @__PURE__ */ jsx("code", { className: "px-1 py-0.5 rounded bg-muted text-[10px] font-mono", children: "?room=SESSION_NAME" })
107168
+ ] })
107169
+ ] });
107170
+ }
107171
+ function ActiveSessionView({
107172
+ collab,
107173
+ activeCollaboration,
107174
+ copied,
107175
+ onCopyRoomId,
107176
+ onStopCollaboration
107177
+ }) {
107178
+ const { t: t2 } = useTranslation();
107179
+ const statusColor = collab.status === "connected" ? "text-green-400" : collab.status === "connecting" ? "text-yellow-400" : "text-red-400";
107180
+ const statusIcon = collab.status === "connected" || collab.status === "connecting" ? /* @__PURE__ */ jsx(LuWifi, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(LuWifiOff, { className: "w-4 h-4" });
107181
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
107182
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
107183
+ /* @__PURE__ */ jsx("span", { className: statusColor, children: statusIcon }),
107184
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] font-medium text-foreground capitalize", children: collab.status }),
107185
+ /* @__PURE__ */ jsxs("span", { className: "text-[12px] text-muted-foreground ml-auto flex items-center gap-1", children: [
107186
+ /* @__PURE__ */ jsx(LuUsers, { className: "w-3.5 h-3.5" }),
107187
+ t2("pptx.collaboration.userCount", { count: collab.connectedCount })
107188
+ ] })
107189
+ ] }),
107190
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
107191
+ /* @__PURE__ */ jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.shareLink") }),
107192
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
107193
+ /* @__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 }),
107194
+ /* @__PURE__ */ jsx(
107195
+ "button",
107196
+ {
107197
+ type: "button",
107198
+ onClick: onCopyRoomId,
107199
+ 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",
107200
+ title: t2("pptx.share.copyLink"),
107201
+ children: copied ? /* @__PURE__ */ jsxs(Fragment, { children: [
107202
+ /* @__PURE__ */ jsx(LuCheck, { className: "w-3.5 h-3.5 text-green-400" }),
107203
+ /* @__PURE__ */ jsx("span", { children: t2("pptx.share.copied") })
107204
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
107205
+ /* @__PURE__ */ jsx(LuCopy, { className: "w-3.5 h-3.5" }),
107206
+ /* @__PURE__ */ jsx("span", { children: t2("pptx.share.copyUrl") })
107207
+ ] })
107208
+ }
107209
+ )
107210
+ ] }),
107211
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("pptx.share.shareHint") })
107212
+ ] }),
107213
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-[11px] text-muted-foreground", children: [
107214
+ /* @__PURE__ */ jsxs("span", { children: [
107215
+ t2("pptx.share.room"),
107216
+ " ",
107217
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: collab.config.roomId })
107218
+ ] }),
107219
+ /* @__PURE__ */ jsxs("span", { children: [
107220
+ t2("pptx.share.server"),
107221
+ " ",
107222
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-foreground", children: collab.config.serverUrl })
107223
+ ] })
107224
+ ] }),
107225
+ collab.remoteUsers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
107226
+ /* @__PURE__ */ jsx("label", { className: "block text-[12px] font-medium text-foreground", children: t2("pptx.share.connectedUsers") }),
107227
+ /* @__PURE__ */ jsxs("div", { className: "rounded border border-border bg-background divide-y divide-border max-h-[140px] overflow-y-auto", children: [
107228
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
107229
+ /* @__PURE__ */ jsx(
107230
+ "div",
107231
+ {
107232
+ className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
107233
+ style: { backgroundColor: collab.config.userColor ?? "#6366f1" },
107234
+ children: getInitials(activeCollaboration?.userName ?? collab.config.userName)
107235
+ }
107236
+ ),
107237
+ /* @__PURE__ */ jsx("span", { className: "text-[12px] text-foreground truncate", children: activeCollaboration?.userName ?? collab.config.userName }),
107238
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground ml-auto", children: t2("pptx.share.you") })
107239
+ ] }),
107240
+ collab.remoteUsers.map((user) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2", children: [
107241
+ /* @__PURE__ */ jsx(
107242
+ "div",
107243
+ {
107244
+ className: "w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-semibold text-white shrink-0",
107245
+ style: { backgroundColor: user.userColor },
107246
+ children: user.userAvatar ? /* @__PURE__ */ jsx(
107247
+ "img",
107248
+ {
107249
+ src: user.userAvatar,
107250
+ alt: "",
107251
+ className: "w-full h-full rounded-full object-cover"
107252
+ }
107253
+ ) : getInitials(user.userName)
107254
+ }
107255
+ ),
107256
+ /* @__PURE__ */ jsx("span", { className: "text-[12px] text-foreground truncate", children: user.userName }),
107257
+ /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground ml-auto", children: [
107258
+ "Slide ",
107259
+ user.activeSlideIndex + 1
107260
+ ] })
107261
+ ] }, user.clientId))
107262
+ ] })
107263
+ ] }),
107264
+ onStopCollaboration && /* @__PURE__ */ jsx(
107265
+ "button",
107266
+ {
107267
+ type: "button",
107268
+ onClick: onStopCollaboration,
107269
+ 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",
107270
+ children: t2("pptx.share.stopSharing")
107271
+ }
107272
+ )
107273
+ ] });
107274
+ }
105578
107275
 
105579
107276
  // src/viewer/components/hyperlink-edit-types.ts
105580
107277
  var ACTION_VERB_MAP = {
@@ -107475,7 +109172,14 @@ function ViewerBottomPanels({
107475
109172
  onUpdateNotes,
107476
109173
  collaborationSlot,
107477
109174
  notesPanelHeight,
107478
- onResizeBottom
109175
+ onResizeBottom,
109176
+ scale,
109177
+ onZoomIn,
109178
+ onZoomOut,
109179
+ onZoomToFit,
109180
+ mode,
109181
+ onSetMode,
109182
+ onToggleSlideSorter
107479
109183
  }) {
107480
109184
  return /* @__PURE__ */ jsxs(Fragment, { children: [
107481
109185
  onResizeBottom && !isSlideNotesCollapsed && /* @__PURE__ */ jsx(ResizeHandle, { direction: "vertical", onResize: onResizeBottom }),
@@ -107491,18 +109195,25 @@ function ViewerBottomPanels({
107491
109195
  panelHeight: notesPanelHeight
107492
109196
  }
107493
109197
  ),
107494
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
107495
- /* @__PURE__ */ jsx(
107496
- StatusBar,
107497
- {
107498
- slideCount,
107499
- activeSlideIndex,
107500
- isDirty,
107501
- autosaveStatus
107502
- }
107503
- ),
107504
- collaborationSlot
107505
- ] })
109198
+ /* @__PURE__ */ jsx(
109199
+ StatusBar,
109200
+ {
109201
+ slideCount,
109202
+ activeSlideIndex,
109203
+ isDirty,
109204
+ autosaveStatus,
109205
+ scale,
109206
+ onZoomIn,
109207
+ onZoomOut,
109208
+ onZoomToFit,
109209
+ isNotesExpanded: !isSlideNotesCollapsed,
109210
+ onToggleNotes,
109211
+ mode,
109212
+ onSetMode,
109213
+ onToggleSlideSorter,
109214
+ collaborationSlot
109215
+ }
109216
+ )
107506
109217
  ] });
107507
109218
  }
107508
109219
  function ViewerInspector({
@@ -107661,7 +109372,9 @@ function ViewerToolbarSection(props) {
107661
109372
  ops,
107662
109373
  onSetMode,
107663
109374
  onEnterPresenterView,
107664
- onEnterRehearsalMode
109375
+ onEnterRehearsalMode,
109376
+ onOpenSettings,
109377
+ onOpenShareDialog
107665
109378
  } = props;
107666
109379
  return /* @__PURE__ */ jsxs(Fragment, { children: [
107667
109380
  /* @__PURE__ */ jsx(
@@ -107740,12 +109453,14 @@ function ViewerToolbarSection(props) {
107740
109453
  onExportVideo: exportHandlers.handleExportVideo,
107741
109454
  onExportGif: exportHandlers.handleExportGif,
107742
109455
  onPackageForSharing: exportHandlers.handlePackageForSharing,
109456
+ onOpenShareDialog,
107743
109457
  onSaveAsPpsx: exportHandlers.handleSaveAsPpsx,
107744
109458
  onSaveAsPptm: exportHandlers.handleSaveAsPptm,
107745
109459
  hasMacros: s.hasMacros,
107746
109460
  onCopySlideAsImage: exportHandlers.handleCopySlideAsImage,
107747
109461
  onPrint: printHandlers.handlePrint,
107748
109462
  onToggleShortcuts: () => s.setIsShortcutHelpOpen((p3) => !p3),
109463
+ onOpenSettings,
107749
109464
  onRunAccessibilityCheck: dialogs.handleRunAccessibilityCheck,
107750
109465
  onToggleSlideSorter: () => s.setShowSlideSorter((p3) => !p3),
107751
109466
  onUpdateTextStyle: ops.updateSelectedTextStyle,
@@ -107775,7 +109490,12 @@ function ViewerToolbarSection(props) {
107775
109490
  onToggleThemeGallery: () => s.setIsThemeGalleryOpen((p3) => !p3),
107776
109491
  isThemeGalleryOpen: s.isThemeGalleryOpen,
107777
109492
  onCompare: propertyHandlers.handleCompare,
107778
- onToggleComments: () => s.setIsInspectorPaneOpen((p3) => !p3),
109493
+ onToggleComments: () => {
109494
+ s.setSidebarPanelMode("comments");
109495
+ if (!s.isInspectorPaneOpen) {
109496
+ s.setIsInspectorPaneOpen(true);
109497
+ }
109498
+ },
107779
109499
  isCommentsPanelOpen: s.isInspectorPaneOpen,
107780
109500
  slideCommentCount: activeSlide?.comments?.length ?? 0,
107781
109501
  formatPainterActive: s.formatPainterActive,
@@ -109522,7 +111242,7 @@ function ViewerCanvasArea(props) {
109522
111242
  const handleToolbarMouseLeave = useCallback(() => {
109523
111243
  toolbarHoveringRef.current = false;
109524
111244
  }, []);
109525
- return /* @__PURE__ */ jsxs("main", { "aria-label": "Slide editor", className: "flex-1 min-w-0 relative flex flex-col", children: [
111245
+ return /* @__PURE__ */ jsxs("main", { "aria-label": "Slide editor", className: "flex-1 min-w-0 relative flex flex-col bg-background", children: [
109526
111246
  findReplace.findReplaceOpen && /* @__PURE__ */ jsx(
109527
111247
  FindReplacePanel,
109528
111248
  {
@@ -109892,7 +111612,7 @@ var PRESET_THEMES = [
109892
111612
  minorFont: "Gill Sans MT"
109893
111613
  }
109894
111614
  ];
109895
- var COMMON_FONTS = [
111615
+ var COMMON_FONTS2 = [
109896
111616
  "Arial",
109897
111617
  "Calibri",
109898
111618
  "Calibri Light",
@@ -109997,15 +111717,15 @@ function ThemeColorSchemeEditor({
109997
111717
  {
109998
111718
  className: THEME_EDITOR_INPUT,
109999
111719
  disabled: !canEdit,
110000
- value: COMMON_FONTS.includes(majorFont) ? majorFont : "__custom__",
111720
+ value: COMMON_FONTS2.includes(majorFont) ? majorFont : "__custom__",
110001
111721
  onChange: (e2) => {
110002
111722
  if (e2.target.value !== "__custom__") {
110003
111723
  onMajorFontChange(e2.target.value);
110004
111724
  }
110005
111725
  },
110006
111726
  children: [
110007
- COMMON_FONTS.map((f) => /* @__PURE__ */ jsx("option", { value: f, children: f }, f)),
110008
- !COMMON_FONTS.includes(majorFont) && /* @__PURE__ */ jsx("option", { value: "__custom__", children: majorFont })
111727
+ COMMON_FONTS2.map((f) => /* @__PURE__ */ jsx("option", { value: f, children: f }, f)),
111728
+ !COMMON_FONTS2.includes(majorFont) && /* @__PURE__ */ jsx("option", { value: "__custom__", children: majorFont })
110009
111729
  ]
110010
111730
  }
110011
111731
  )
@@ -110017,15 +111737,15 @@ function ThemeColorSchemeEditor({
110017
111737
  {
110018
111738
  className: THEME_EDITOR_INPUT,
110019
111739
  disabled: !canEdit,
110020
- value: COMMON_FONTS.includes(minorFont) ? minorFont : "__custom__",
111740
+ value: COMMON_FONTS2.includes(minorFont) ? minorFont : "__custom__",
110021
111741
  onChange: (e2) => {
110022
111742
  if (e2.target.value !== "__custom__") {
110023
111743
  onMinorFontChange(e2.target.value);
110024
111744
  }
110025
111745
  },
110026
111746
  children: [
110027
- COMMON_FONTS.map((f) => /* @__PURE__ */ jsx("option", { value: f, children: f }, f)),
110028
- !COMMON_FONTS.includes(minorFont) && /* @__PURE__ */ jsx("option", { value: "__custom__", children: minorFont })
111747
+ COMMON_FONTS2.map((f) => /* @__PURE__ */ jsx("option", { value: f, children: f }, f)),
111748
+ !COMMON_FONTS2.includes(minorFont) && /* @__PURE__ */ jsx("option", { value: "__custom__", children: minorFont })
110029
111749
  ]
110030
111750
  }
110031
111751
  )
@@ -110903,403 +112623,293 @@ function ViewerMainContent(props) {
110903
112623
  )
110904
112624
  ] });
110905
112625
  }
110906
-
110907
- // src/viewer/hooks/collaboration/sanitize.ts
110908
- var ROOM_ID_REGEX = /^[a-zA-Z0-9_-]{1,128}$/;
110909
- function validateRoomId(roomId) {
110910
- if (!ROOM_ID_REGEX.test(roomId)) {
110911
- throw new Error(
110912
- `Invalid collaboration room ID: "${roomId}". Must be 1-128 alphanumeric characters, hyphens, or underscores.`
110913
- );
110914
- }
110915
- return roomId;
110916
- }
110917
- function sanitizeUserName(name) {
110918
- if (typeof name !== "string") {
110919
- return "Anonymous";
110920
- }
110921
- const stripped = name.replace(/<[^>]*>/g, "");
110922
- const trimmed = stripped.trim().slice(0, 64);
110923
- return trimmed || "Anonymous";
110924
- }
110925
- function clampCursorPosition(value, min2, max2) {
110926
- if (typeof value !== "number" || !Number.isFinite(value)) {
110927
- return 0;
110928
- }
110929
- const margin = 20;
110930
- return Math.max(min2 - margin, Math.min(max2 + margin, value));
110931
- }
110932
- var HEX_COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
110933
- function sanitizeColor(color, fallback = "#6366f1") {
110934
- if (typeof color !== "string") {
110935
- return fallback;
110936
- }
110937
- return HEX_COLOR_REGEX.test(color) ? color : fallback;
110938
- }
110939
- function sanitizeAvatarUrl(url) {
110940
- if (typeof url !== "string") {
110941
- return void 0;
110942
- }
110943
- try {
110944
- const parsed = new URL(url);
110945
- if (parsed.protocol === "https:" || parsed.protocol === "http:" || parsed.protocol === "data:") {
110946
- return url;
112626
+ function ToggleSwitch({
112627
+ label,
112628
+ enabled,
112629
+ onToggle
112630
+ }) {
112631
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-2.5 px-3", children: [
112632
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: label }),
112633
+ /* @__PURE__ */ jsx(
112634
+ "button",
112635
+ {
112636
+ type: "button",
112637
+ onClick: onToggle,
112638
+ className: cn(
112639
+ "relative inline-flex h-5 w-9 items-center rounded-full transition-colors",
112640
+ enabled ? "bg-primary" : "bg-muted-foreground/30"
112641
+ ),
112642
+ role: "switch",
112643
+ "aria-checked": enabled,
112644
+ "aria-label": label,
112645
+ children: /* @__PURE__ */ jsx(
112646
+ "span",
112647
+ {
112648
+ className: cn(
112649
+ "inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",
112650
+ enabled ? "translate-x-[18px]" : "translate-x-[3px]"
112651
+ )
112652
+ }
112653
+ )
112654
+ }
112655
+ )
112656
+ ] });
112657
+ }
112658
+ function SettingsDialog({
112659
+ isOpen,
112660
+ onClose,
112661
+ spellCheckEnabled = false,
112662
+ onSetSpellCheckEnabled,
112663
+ showGrid = false,
112664
+ onSetShowGrid,
112665
+ showRulers = false,
112666
+ onSetShowRulers,
112667
+ snapToGrid = false,
112668
+ onSetSnapToGrid,
112669
+ reducedMotion = false,
112670
+ onToggleReducedMotion
112671
+ }) {
112672
+ const [activeTab, setActiveTab] = useState("general");
112673
+ const [autoSave, setAutoSave] = useState(true);
112674
+ const { t: t2 } = useTranslation();
112675
+ const handleKeyDown = useCallback(
112676
+ (e2) => {
112677
+ if (e2.key === "Escape") {
112678
+ onClose();
112679
+ }
112680
+ },
112681
+ [onClose]
112682
+ );
112683
+ useEffect(() => {
112684
+ if (isOpen) {
112685
+ document.addEventListener("keydown", handleKeyDown);
112686
+ return () => document.removeEventListener("keydown", handleKeyDown);
110947
112687
  }
110948
- } catch {
110949
- }
110950
- return void 0;
110951
- }
110952
- function sanitizeSlideIndex(value) {
110953
- if (typeof value !== "number" || !Number.isFinite(value)) {
110954
- return 0;
110955
- }
110956
- return Math.max(0, Math.floor(value));
110957
- }
110958
- function sanitizePresence(raw, canvasWidth, canvasHeight) {
110959
- if (typeof raw.clientId !== "number") {
112688
+ }, [isOpen, handleKeyDown]);
112689
+ if (!isOpen) {
110960
112690
  return null;
110961
112691
  }
110962
- return {
110963
- clientId: raw.clientId,
110964
- userName: sanitizeUserName(raw.userName),
110965
- userAvatar: sanitizeAvatarUrl(raw.userAvatar),
110966
- userColor: sanitizeColor(raw.userColor),
110967
- activeSlideIndex: sanitizeSlideIndex(raw.activeSlideIndex),
110968
- cursorX: clampCursorPosition(raw.cursorX, 0, canvasWidth),
110969
- cursorY: clampCursorPosition(raw.cursorY, 0, canvasHeight),
110970
- lastUpdated: typeof raw.lastUpdated === "string" ? raw.lastUpdated : (/* @__PURE__ */ new Date()).toISOString(),
110971
- selectedElementId: typeof raw.selectedElementId === "string" ? raw.selectedElementId.slice(0, 128) : void 0
110972
- };
112692
+ const tabs = [
112693
+ { id: "general", label: t2("pptx.settings.general") },
112694
+ { id: "shortcuts", label: t2("pptx.settings.keyboardShortcuts") }
112695
+ ];
112696
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
112697
+ /* @__PURE__ */ jsx(
112698
+ "button",
112699
+ {
112700
+ type: "button",
112701
+ className: "fixed inset-0 z-50 bg-black/60",
112702
+ "aria-label": t2("pptx.settings.closeSettings"),
112703
+ onClick: onClose
112704
+ }
112705
+ ),
112706
+ /* @__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: [
112707
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4 border-b border-border/60", children: [
112708
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
112709
+ /* @__PURE__ */ jsx(LuSettings, { className: "w-5 h-5 text-primary" }),
112710
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: t2("pptx.settings.title") })
112711
+ ] }),
112712
+ /* @__PURE__ */ jsx(
112713
+ "button",
112714
+ {
112715
+ type: "button",
112716
+ onClick: onClose,
112717
+ className: "p-1 rounded hover:bg-accent transition-colors",
112718
+ "aria-label": t2("pptx.settings.close"),
112719
+ children: /* @__PURE__ */ jsx(LuX, { className: "w-4 h-4 text-muted-foreground" })
112720
+ }
112721
+ )
112722
+ ] }),
112723
+ /* @__PURE__ */ jsx("div", { className: "flex border-b border-border/60 px-5", children: tabs.map((tab) => /* @__PURE__ */ jsxs(
112724
+ "button",
112725
+ {
112726
+ type: "button",
112727
+ onClick: () => setActiveTab(tab.id),
112728
+ className: cn(
112729
+ "px-3 py-2 text-xs font-medium transition-colors relative",
112730
+ activeTab === tab.id ? "text-primary" : "text-muted-foreground hover:text-foreground"
112731
+ ),
112732
+ children: [
112733
+ tab.label,
112734
+ activeTab === tab.id && /* @__PURE__ */ jsx("span", { className: "absolute bottom-0 left-0 right-0 h-0.5 bg-primary rounded-full" })
112735
+ ]
112736
+ },
112737
+ tab.id
112738
+ )) }),
112739
+ /* @__PURE__ */ jsxs("div", { className: "px-5 py-4 max-h-[60vh] overflow-y-auto", children: [
112740
+ activeTab === "general" && /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
112741
+ /* @__PURE__ */ jsx(
112742
+ ToggleSwitch,
112743
+ {
112744
+ label: t2("pptx.settings.autoSave"),
112745
+ enabled: autoSave,
112746
+ onToggle: () => setAutoSave(!autoSave)
112747
+ }
112748
+ ),
112749
+ /* @__PURE__ */ jsx(
112750
+ ToggleSwitch,
112751
+ {
112752
+ label: t2("pptx.settings.spellCheck"),
112753
+ enabled: spellCheckEnabled,
112754
+ onToggle: () => onSetSpellCheckEnabled?.(!spellCheckEnabled)
112755
+ }
112756
+ ),
112757
+ /* @__PURE__ */ jsx(
112758
+ ToggleSwitch,
112759
+ {
112760
+ label: t2("pptx.settings.showGrid"),
112761
+ enabled: showGrid,
112762
+ onToggle: () => onSetShowGrid?.(!showGrid)
112763
+ }
112764
+ ),
112765
+ /* @__PURE__ */ jsx(
112766
+ ToggleSwitch,
112767
+ {
112768
+ label: t2("pptx.settings.showRulers"),
112769
+ enabled: showRulers,
112770
+ onToggle: () => onSetShowRulers?.(!showRulers)
112771
+ }
112772
+ ),
112773
+ /* @__PURE__ */ jsx(
112774
+ ToggleSwitch,
112775
+ {
112776
+ label: t2("pptx.settings.snapToGrid"),
112777
+ enabled: snapToGrid,
112778
+ onToggle: () => onSetSnapToGrid?.(!snapToGrid)
112779
+ }
112780
+ ),
112781
+ /* @__PURE__ */ jsx(
112782
+ ToggleSwitch,
112783
+ {
112784
+ label: t2("pptx.settings.reducedMotion"),
112785
+ enabled: reducedMotion,
112786
+ onToggle: () => onToggleReducedMotion?.()
112787
+ }
112788
+ )
112789
+ ] }),
112790
+ activeTab === "shortcuts" && /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: SHORTCUT_REFERENCE_ITEMS.map((shortcut, i3) => /* @__PURE__ */ jsxs(
112791
+ "div",
112792
+ {
112793
+ className: cn(
112794
+ "flex items-center justify-between gap-3 rounded px-3 py-2",
112795
+ i3 % 2 === 0 ? "bg-muted/60" : ""
112796
+ ),
112797
+ children: [
112798
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-foreground", children: shortcut.action }),
112799
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-muted-foreground whitespace-nowrap", children: shortcut.shortcut })
112800
+ ]
112801
+ },
112802
+ shortcut.action
112803
+ )) })
112804
+ ] })
112805
+ ] }) })
112806
+ ] });
110973
112807
  }
110974
- var BROADCAST_THROTTLE_MS = 50;
110975
- var STALE_PRESENCE_MS = 3e4;
110976
- function usePresenceTracking({
110977
- awareness,
110978
- localClientId,
110979
- userName,
110980
- userColor,
110981
- userAvatar,
110982
- canvasWidth,
110983
- canvasHeight
112808
+ function useYjsDocumentSync({
112809
+ doc: doc2,
112810
+ slides,
112811
+ setSlides,
112812
+ isConnected
110984
112813
  }) {
110985
- const [remoteUsers, setRemoteUsers] = useState([]);
110986
- const lastBroadcastRef = useRef(0);
110987
- const pendingBroadcastRef = useRef(null);
110988
- const latestLocalState = useRef({});
110989
- const broadcastPresence = useCallback(
110990
- (update2) => {
110991
- if (!awareness) {
110992
- return;
112814
+ const isApplyingRemoteRef = useRef(false);
112815
+ const lastSyncedRef = useRef("");
112816
+ const hasInitializedRef = useRef(false);
112817
+ const getDocMap = useCallback(() => {
112818
+ if (!doc2 || typeof doc2 !== "object") {
112819
+ return null;
112820
+ }
112821
+ const d = doc2;
112822
+ if (typeof d.getMap !== "function") {
112823
+ return null;
112824
+ }
112825
+ return d.getMap("slides-data");
112826
+ }, [doc2]);
112827
+ useEffect(() => {
112828
+ if (!isConnected || !doc2) {
112829
+ return;
112830
+ }
112831
+ if (isApplyingRemoteRef.current) {
112832
+ return;
112833
+ }
112834
+ if (slides.length === 0) {
112835
+ return;
112836
+ }
112837
+ const map3 = getDocMap();
112838
+ if (!map3) {
112839
+ return;
112840
+ }
112841
+ const serialized = JSON.stringify(slides);
112842
+ if (serialized === lastSyncedRef.current) {
112843
+ return;
112844
+ }
112845
+ lastSyncedRef.current = serialized;
112846
+ const d = doc2;
112847
+ d.transact(() => {
112848
+ const currentCount = map3.get("count");
112849
+ if (currentCount && currentCount > slides.length) {
112850
+ for (let i3 = slides.length; i3 < currentCount; i3++) {
112851
+ map3.delete(`slide-${i3}`);
112852
+ }
110993
112853
  }
110994
- Object.assign(latestLocalState.current, update2);
110995
- const now = Date.now();
110996
- const elapsed = now - lastBroadcastRef.current;
110997
- const flush = () => {
110998
- const state2 = {
110999
- ...latestLocalState.current,
111000
- userName,
111001
- userColor,
111002
- userAvatar,
111003
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
111004
- };
111005
- awareness.setLocalStateField("presence", state2);
111006
- lastBroadcastRef.current = Date.now();
111007
- };
111008
- if (elapsed >= BROADCAST_THROTTLE_MS) {
111009
- if (pendingBroadcastRef.current) {
111010
- clearTimeout(pendingBroadcastRef.current);
111011
- pendingBroadcastRef.current = null;
112854
+ map3.set("count", slides.length);
112855
+ for (let i3 = 0; i3 < slides.length; i3++) {
112856
+ const slideJson = JSON.stringify(slides[i3]);
112857
+ const existing = map3.get(`slide-${i3}`);
112858
+ if (existing !== slideJson) {
112859
+ map3.set(`slide-${i3}`, slideJson);
111012
112860
  }
111013
- flush();
111014
- } else if (!pendingBroadcastRef.current) {
111015
- pendingBroadcastRef.current = setTimeout(() => {
111016
- pendingBroadcastRef.current = null;
111017
- flush();
111018
- }, BROADCAST_THROTTLE_MS - elapsed);
111019
112861
  }
111020
- },
111021
- [awareness, userName, userColor, userAvatar]
111022
- );
112862
+ });
112863
+ }, [doc2, slides, isConnected, getDocMap]);
111023
112864
  useEffect(() => {
111024
- if (!awareness || localClientId === null) {
112865
+ if (!isConnected || !doc2) {
111025
112866
  return;
111026
112867
  }
111027
- const handleChange = () => {
111028
- const now = Date.now();
111029
- const states = awareness.getStates();
111030
- const users = [];
111031
- states.forEach((state2, cid) => {
111032
- if (cid === localClientId) {
111033
- return;
111034
- }
111035
- const raw = state2?.presence;
111036
- if (!raw || typeof raw !== "object") {
111037
- return;
111038
- }
111039
- const sanitized = sanitizePresence({ ...raw, clientId: cid }, canvasWidth, canvasHeight);
111040
- if (!sanitized) {
111041
- return;
111042
- }
111043
- const updatedAt = new Date(sanitized.lastUpdated).getTime();
111044
- if (Number.isNaN(updatedAt) || now - updatedAt > STALE_PRESENCE_MS) {
111045
- return;
112868
+ const map3 = getDocMap();
112869
+ if (!map3) {
112870
+ return;
112871
+ }
112872
+ const handleUpdate = () => {
112873
+ const count = map3.get("count");
112874
+ if (!count || count === 0) {
112875
+ return;
112876
+ }
112877
+ const remoteSlides = [];
112878
+ for (let i3 = 0; i3 < count; i3++) {
112879
+ const slideJson = map3.get(`slide-${i3}`);
112880
+ if (slideJson) {
112881
+ try {
112882
+ remoteSlides.push(JSON.parse(slideJson));
112883
+ } catch {
112884
+ }
111046
112885
  }
111047
- users.push(sanitized);
111048
- });
111049
- setRemoteUsers(users);
111050
- };
111051
- awareness.on("change", handleChange);
111052
- handleChange();
111053
- return () => {
111054
- awareness.off("change", handleChange);
111055
- };
111056
- }, [awareness, localClientId, canvasWidth, canvasHeight]);
111057
- useEffect(() => {
111058
- return () => {
111059
- if (pendingBroadcastRef.current) {
111060
- clearTimeout(pendingBroadcastRef.current);
111061
112886
  }
112887
+ if (remoteSlides.length === 0) {
112888
+ return;
112889
+ }
112890
+ const serialized = JSON.stringify(remoteSlides);
112891
+ if (serialized === lastSyncedRef.current) {
112892
+ return;
112893
+ }
112894
+ lastSyncedRef.current = serialized;
112895
+ isApplyingRemoteRef.current = true;
112896
+ setSlides(remoteSlides);
112897
+ requestAnimationFrame(() => {
112898
+ isApplyingRemoteRef.current = false;
112899
+ });
111062
112900
  };
111063
- }, []);
111064
- return { remoteUsers, broadcastPresence };
111065
- }
111066
- function useYjsProvider({ config }) {
111067
- const [status, setStatus] = useState("disconnected");
111068
- const [awareness, setAwareness] = useState(null);
111069
- const [doc2, setDoc] = useState(null);
111070
- const [clientId, setClientId] = useState(null);
111071
- const cleanupRef = useRef(null);
111072
- const init = useCallback(async () => {
111073
- const roomId = validateRoomId(config.roomId);
111074
- setStatus("connecting");
111075
- try {
111076
- const [Y, { WebsocketProvider: WebsocketProvider2 }] = await Promise.all([Promise.resolve().then(() => (init_yjs(), yjs_exports)), Promise.resolve().then(() => (init_y_websocket(), y_websocket_exports))]);
111077
- const yDoc = new Y.Doc();
111078
- const provider = new WebsocketProvider2(
111079
- config.serverUrl,
111080
- roomId,
111081
- yDoc,
111082
- // eslint-disable-line @typescript-eslint/no-explicit-any
111083
- {
111084
- params: config.authToken ? { token: config.authToken } : void 0
111085
- }
111086
- );
111087
- const handleStatus = (event) => {
111088
- if (event.status === "connected") {
111089
- setStatus("connected");
111090
- } else if (event.status === "disconnected") {
111091
- setStatus("disconnected");
111092
- }
111093
- };
111094
- provider.on("status", handleStatus);
111095
- if (provider.wsconnected) {
111096
- setStatus("connected");
112901
+ map3.observe(handleUpdate);
112902
+ if (!hasInitializedRef.current) {
112903
+ hasInitializedRef.current = true;
112904
+ const count = map3.get("count");
112905
+ if (count && count > 0) {
112906
+ handleUpdate();
111097
112907
  }
111098
- setDoc(yDoc);
111099
- setAwareness(provider.awareness);
111100
- setClientId(provider.awareness.clientID);
111101
- cleanupRef.current = () => {
111102
- provider.off("status", handleStatus);
111103
- provider.destroy();
111104
- yDoc.destroy();
111105
- setDoc(null);
111106
- setAwareness(null);
111107
- setClientId(null);
111108
- setStatus("disconnected");
111109
- };
111110
- } catch (err) {
111111
- console.warn(
111112
- "[pptx-viewer] Collaboration packages not available:",
111113
- err instanceof Error ? err.message : err
111114
- );
111115
- setStatus("error");
111116
112908
  }
111117
- }, [config.roomId, config.serverUrl, config.authToken]);
111118
- useEffect(() => {
111119
- init();
111120
112909
  return () => {
111121
- cleanupRef.current?.();
111122
- cleanupRef.current = null;
112910
+ map3.unobserve(handleUpdate);
111123
112911
  };
111124
- }, [init]);
111125
- return { status, awareness, doc: doc2, clientId };
111126
- }
111127
-
111128
- // src/viewer/hooks/collaboration/useCollaborativeState.ts
111129
- function useCollaborativeState({
111130
- config,
111131
- canvasWidth,
111132
- canvasHeight
111133
- }) {
111134
- const userColor = sanitizeColor(config.userColor, "#6366f1");
111135
- const { status, awareness, clientId } = useYjsProvider({ config });
111136
- const { remoteUsers, broadcastPresence } = usePresenceTracking({
111137
- awareness,
111138
- localClientId: clientId,
111139
- userName: config.userName,
111140
- userColor,
111141
- userAvatar: config.userAvatar,
111142
- canvasWidth,
111143
- canvasHeight
111144
- });
111145
- const connectedCount = status === "connected" ? remoteUsers.length + 1 : remoteUsers.length;
111146
- return {
111147
- status,
111148
- remoteUsers,
111149
- broadcastPresence,
111150
- connectedCount,
111151
- config
111152
- };
111153
- }
111154
- var CollaborationContext = createContext(null);
111155
- function useCollaboration() {
111156
- return useContext(CollaborationContext);
111157
- }
111158
- function CollaborationProvider({
111159
- config,
111160
- canvasWidth,
111161
- canvasHeight,
111162
- children
111163
- }) {
111164
- const value = useCollaborativeState({
111165
- config,
111166
- canvasWidth,
111167
- canvasHeight
111168
- });
111169
- return /* @__PURE__ */ jsx(CollaborationContext.Provider, { value, children });
111170
- }
111171
- function getInitials(name) {
111172
- const parts = name.trim().split(/\s+/);
111173
- if (parts.length >= 2) {
111174
- return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
111175
- }
111176
- return name.slice(0, 2).toUpperCase();
111177
- }
111178
- function AvatarCircle({
111179
- name,
111180
- color,
111181
- avatar,
111182
- isLocal
111183
- }) {
111184
- const initials = getInitials(name);
111185
- const title = isLocal ? `${name} (you)` : name;
111186
- return /* @__PURE__ */ jsx(
111187
- "div",
111188
- {
111189
- 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",
111190
- style: {
111191
- backgroundColor: color,
111192
- borderColor: isLocal ? "#fff" : color
111193
- },
111194
- title,
111195
- "aria-label": title,
111196
- children: avatar ? /* @__PURE__ */ jsx(
111197
- "img",
111198
- {
111199
- src: avatar,
111200
- alt: "",
111201
- className: "w-full h-full rounded-full object-cover",
111202
- onError: (e2) => {
111203
- e2.target.style.display = "none";
111204
- }
111205
- }
111206
- ) : initials
111207
- }
111208
- );
111209
- }
111210
- function UserAvatarBar({
111211
- remoteUsers,
111212
- localUserName,
111213
- localUserColor,
111214
- localUserAvatar,
111215
- status,
111216
- maxVisible = 5
111217
- }) {
111218
- if (status === "disconnected" || status === "error") {
111219
- return null;
111220
- }
111221
- const allUsers = [
111222
- { name: localUserName, color: localUserColor, avatar: localUserAvatar, isLocal: true },
111223
- ...remoteUsers.map((u2) => ({
111224
- name: u2.userName,
111225
- color: u2.userColor,
111226
- avatar: u2.userAvatar,
111227
- isLocal: false
111228
- }))
111229
- ];
111230
- const visible = allUsers.slice(0, maxVisible);
111231
- const overflow = allUsers.length - maxVisible;
111232
- return /* @__PURE__ */ jsxs(
111233
- "div",
111234
- {
111235
- "data-testid": "user-avatar-bar",
111236
- className: "flex items-center px-2",
111237
- "aria-label": `${allUsers.length} user${allUsers.length !== 1 ? "s" : ""} connected`,
111238
- children: [
111239
- visible.map((user, i3) => /* @__PURE__ */ jsx(
111240
- AvatarCircle,
111241
- {
111242
- name: user.name,
111243
- color: user.color,
111244
- avatar: user.avatar,
111245
- isLocal: user.isLocal
111246
- },
111247
- user.isLocal ? "local" : `remote-${i3}`
111248
- )),
111249
- overflow > 0 && /* @__PURE__ */ jsxs(
111250
- "div",
111251
- {
111252
- 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",
111253
- title: `${overflow} more user${overflow !== 1 ? "s" : ""}`,
111254
- children: [
111255
- "+",
111256
- overflow
111257
- ]
111258
- }
111259
- )
111260
- ]
111261
- }
111262
- );
111263
- }
111264
- var STATUS_STYLES = {
111265
- connected: {
111266
- dot: "bg-green-400",
111267
- text: "text-green-400",
111268
- label: "Connected"
111269
- },
111270
- connecting: {
111271
- dot: "bg-yellow-400 animate-pulse",
111272
- text: "text-yellow-400",
111273
- label: "Connecting..."
111274
- },
111275
- disconnected: {
111276
- dot: "bg-gray-500",
111277
- text: "text-gray-500",
111278
- label: "Disconnected"
111279
- },
111280
- error: {
111281
- dot: "bg-red-400",
111282
- text: "text-red-400",
111283
- label: "Connection error"
111284
- }
111285
- };
111286
- function CollaborationStatusIndicator({
111287
- status,
111288
- connectedCount
111289
- }) {
111290
- const style = STATUS_STYLES[status];
111291
- return /* @__PURE__ */ jsxs(
111292
- "div",
111293
- {
111294
- "data-testid": "collaboration-status",
111295
- className: "flex items-center gap-1.5",
111296
- "aria-label": `Collaboration: ${style.label}. ${connectedCount} user${connectedCount !== 1 ? "s" : ""} connected.`,
111297
- children: [
111298
- /* @__PURE__ */ jsx("span", { className: `inline-block w-2 h-2 rounded-full ${style.dot}`, "aria-hidden": "true" }),
111299
- /* @__PURE__ */ jsx("span", { className: `text-[10px] ${style.text}`, children: status === "connected" ? `${connectedCount} user${connectedCount !== 1 ? "s" : ""}` : style.label })
111300
- ]
111301
- }
111302
- );
112912
+ }, [doc2, isConnected, getDocMap, setSlides]);
111303
112913
  }
111304
112914
  function computeGridSpacingPx(presentationGridSpacing) {
111305
112915
  if (presentationGridSpacing) {
@@ -120974,7 +122584,7 @@ function useViewerUIState() {
120974
122584
  const [isCompactToolbarOpen, setIsCompactToolbarOpen] = useState(false);
120975
122585
  const [toolbarSection, setToolbarSection] = useState("home");
120976
122586
  const [isSlidesPaneOpen, setIsSlidesPaneOpen] = useState(true);
120977
- const [isInspectorPaneOpen, setIsInspectorPaneOpen] = useState(true);
122587
+ const [isInspectorPaneOpen, setIsInspectorPaneOpen] = useState(false);
120978
122588
  const [isSlideNotesCollapsed, setIsSlideNotesCollapsed] = useState(true);
120979
122589
  const [isOverflowMenuOpen, setIsOverflowMenuOpen] = useState(false);
120980
122590
  const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false);
@@ -121257,13 +122867,18 @@ var PowerPointViewer = forwardRef(
121257
122867
  onDirtyChange,
121258
122868
  onActiveSlideChange,
121259
122869
  theme,
121260
- collaboration
122870
+ collaboration,
122871
+ onStartCollaboration,
122872
+ onStopCollaboration,
122873
+ shareDefaults
121261
122874
  } = props;
121262
122875
  const themeStyle = useThemeStyle(theme);
121263
122876
  const [content, setContent] = useState(incomingContent);
121264
122877
  useEffect(() => {
121265
122878
  setContent(incomingContent);
121266
122879
  }, [incomingContent]);
122880
+ const [isSettingsOpen, setIsSettingsOpen] = useState(false);
122881
+ const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);
121267
122882
  const { reducedMotion, toggleReducedMotion } = useReducedMotion();
121268
122883
  const state2 = useViewerState();
121269
122884
  const {
@@ -121464,36 +123079,35 @@ var PowerPointViewer = forwardRef(
121464
123079
  ref: containerRef,
121465
123080
  tabIndex: 0,
121466
123081
  style: themeStyle,
123082
+ "data-pptx-viewer": "",
121467
123083
  className: "h-full w-full bg-background text-foreground flex flex-col relative overflow-hidden outline-none",
121468
123084
  children: [
121469
- /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 bg-gradient-to-b from-purple-500/3 to-transparent z-0" }),
121470
- mode !== "present" && /* @__PURE__ */ jsxs(Fragment, { children: [
121471
- /* @__PURE__ */ jsx(
121472
- ViewerToolbarSection,
121473
- {
121474
- mode,
121475
- canEdit,
121476
- state: state2,
121477
- selectedElement,
121478
- activeSlide,
121479
- zoom,
121480
- history,
121481
- findReplace: editorOps.findReplace,
121482
- manipulation: editorOps.manipulation,
121483
- insertHandlers: editorOps.insertHandlers,
121484
- exportHandlers,
121485
- printHandlers,
121486
- propertyHandlers,
121487
- dialogs,
121488
- slideOps: editorOps.slideOps,
121489
- ops: editorOps.ops,
121490
- onSetMode: handleSetMode,
121491
- onEnterPresenterView: handleEnterPresenterView,
121492
- onEnterRehearsalMode: handleEnterRehearsalMode
121493
- }
121494
- ),
121495
- collaboration && /* @__PURE__ */ jsx(CollaborationToolbarStrip, { collaboration })
121496
- ] }),
123085
+ mode !== "present" && /* @__PURE__ */ jsx(
123086
+ ViewerToolbarSection,
123087
+ {
123088
+ mode,
123089
+ canEdit,
123090
+ state: state2,
123091
+ selectedElement,
123092
+ activeSlide,
123093
+ zoom,
123094
+ history,
123095
+ findReplace: editorOps.findReplace,
123096
+ manipulation: editorOps.manipulation,
123097
+ insertHandlers: editorOps.insertHandlers,
123098
+ exportHandlers,
123099
+ printHandlers,
123100
+ propertyHandlers,
123101
+ dialogs,
123102
+ slideOps: editorOps.slideOps,
123103
+ ops: editorOps.ops,
123104
+ onSetMode: handleSetMode,
123105
+ onEnterPresenterView: handleEnterPresenterView,
123106
+ onEnterRehearsalMode: handleEnterRehearsalMode,
123107
+ onOpenSettings: () => setIsSettingsOpen(true),
123108
+ onOpenShareDialog: () => setIsShareDialogOpen(true)
123109
+ }
123110
+ ),
121497
123111
  /* @__PURE__ */ jsx(
121498
123112
  ViewerMainContent,
121499
123113
  {
@@ -121543,7 +123157,14 @@ var PowerPointViewer = forwardRef(
121543
123157
  onUpdateNotes: propertyHandlers.handleUpdateNotes,
121544
123158
  collaborationSlot: collaboration ? /* @__PURE__ */ jsx(CollaborationStatusStrip, {}) : void 0,
121545
123159
  notesPanelHeight: isMobile ? void 0 : resizablePanels.bottomHeight,
121546
- onResizeBottom: isMobile ? void 0 : resizablePanels.onResizeBottom
123160
+ onResizeBottom: isMobile ? void 0 : resizablePanels.onResizeBottom,
123161
+ scale: zoom.scale,
123162
+ onZoomIn: zoom.handleZoomIn,
123163
+ onZoomOut: zoom.handleZoomOut,
123164
+ onZoomToFit: zoom.handleZoomToFit,
123165
+ mode,
123166
+ onSetMode: handleSetMode,
123167
+ onToggleSlideSorter: () => state2.setShowSlideSorter((p3) => !p3)
121547
123168
  }
121548
123169
  ),
121549
123170
  /* @__PURE__ */ jsx(
@@ -121575,6 +123196,37 @@ var PowerPointViewer = forwardRef(
121575
123196
  onDiscardAnnotations: handleDiscardAnnotations
121576
123197
  }
121577
123198
  ),
123199
+ /* @__PURE__ */ jsx(
123200
+ SettingsDialog,
123201
+ {
123202
+ isOpen: isSettingsOpen,
123203
+ onClose: () => setIsSettingsOpen(false),
123204
+ spellCheckEnabled: state2.spellCheckEnabled,
123205
+ onSetSpellCheckEnabled: state2.setSpellCheckEnabled,
123206
+ showGrid: state2.showGrid,
123207
+ onSetShowGrid: state2.setShowGrid,
123208
+ showRulers: state2.showRulers,
123209
+ onSetShowRulers: state2.setShowRulers,
123210
+ snapToGrid: state2.snapToGrid,
123211
+ onSetSnapToGrid: state2.setSnapToGrid,
123212
+ reducedMotion,
123213
+ onToggleReducedMotion: toggleReducedMotion
123214
+ }
123215
+ ),
123216
+ /* @__PURE__ */ jsx(
123217
+ ShareDialog,
123218
+ {
123219
+ open: isShareDialogOpen,
123220
+ onClose: () => setIsShareDialogOpen(false),
123221
+ activeCollaboration: collaboration,
123222
+ onStartCollaboration,
123223
+ onStopCollaboration,
123224
+ preconfigured: Boolean(collaboration),
123225
+ defaultRoomId: shareDefaults?.roomId,
123226
+ defaultUserName: shareDefaults?.userName,
123227
+ defaultServerUrl: shareDefaults?.serverUrl
123228
+ }
123229
+ ),
121578
123230
  /* @__PURE__ */ jsx(
121579
123231
  ViewerOverlays,
121580
123232
  {
@@ -121616,36 +123268,21 @@ var PowerPointViewer = forwardRef(
121616
123268
  ]
121617
123269
  }
121618
123270
  );
121619
- return /* @__PURE__ */ jsx(ViewerThemeProvider, { theme, children: collaboration ? /* @__PURE__ */ jsx(
123271
+ return /* @__PURE__ */ jsx(ViewerThemeProvider, { theme, children: collaboration ? /* @__PURE__ */ jsxs(
121620
123272
  CollaborationProvider,
121621
123273
  {
121622
123274
  config: collaboration,
121623
123275
  canvasWidth: canvasSize.width,
121624
123276
  canvasHeight: canvasSize.height,
121625
- children: viewerContent
123277
+ children: [
123278
+ /* @__PURE__ */ jsx(CollaborationDocumentSync, { slides, setSlides: state2.setSlides }),
123279
+ viewerContent
123280
+ ]
121626
123281
  }
121627
123282
  ) : viewerContent });
121628
123283
  }
121629
123284
  );
121630
123285
  PowerPointViewer.displayName = "PowerPointViewer";
121631
- function CollaborationToolbarStrip({
121632
- collaboration
121633
- }) {
121634
- const collab = useCollaboration();
121635
- if (!collab) {
121636
- return null;
121637
- }
121638
- 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(
121639
- UserAvatarBar,
121640
- {
121641
- remoteUsers: collab.remoteUsers,
121642
- localUserName: collaboration.userName,
121643
- localUserColor: collab.config.userColor ?? "#6366f1",
121644
- localUserAvatar: collaboration.userAvatar,
121645
- status: collab.status
121646
- }
121647
- ) });
121648
- }
121649
123286
  function CollaborationStatusStrip() {
121650
123287
  const collab = useCollaboration();
121651
123288
  if (!collab) {
@@ -121653,6 +123290,19 @@ function CollaborationStatusStrip() {
121653
123290
  }
121654
123291
  return /* @__PURE__ */ jsx(CollaborationStatusIndicator, { status: collab.status, connectedCount: collab.connectedCount });
121655
123292
  }
123293
+ function CollaborationDocumentSync({
123294
+ slides,
123295
+ setSlides
123296
+ }) {
123297
+ const collab = useCollaboration();
123298
+ useYjsDocumentSync({
123299
+ doc: collab?.doc ?? null,
123300
+ slides,
123301
+ setSlides,
123302
+ isConnected: collab?.status === "connected"
123303
+ });
123304
+ return null;
123305
+ }
121656
123306
  /*! Bundled license information:
121657
123307
 
121658
123308
  three/build/three.core.js: