@bendyline/squisq-editor-react 2.5.0 → 2.6.0

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.
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  RecorderPanel,
17
17
  useModalDialog
18
- } from "./chunk-ZSQN6IO7.js";
18
+ } from "./chunk-JUAQQTJE.js";
19
19
  import {
20
20
  Icon
21
21
  } from "./chunk-GS7QWYFT.js";
@@ -10108,7 +10108,15 @@ function MermaidDiagramTypeThumbnail({ preview }) {
10108
10108
  }
10109
10109
 
10110
10110
  // src/Toolbar.tsx
10111
- import { useCallback as useCallback29, useEffect as useEffect24, useMemo as useMemo24, useReducer, useRef as useRef28, useState as useState31 } from "react";
10111
+ import {
10112
+ useCallback as useCallback29,
10113
+ useEffect as useEffect24,
10114
+ useLayoutEffect as useLayoutEffect4,
10115
+ useMemo as useMemo24,
10116
+ useReducer,
10117
+ useRef as useRef28,
10118
+ useState as useState31
10119
+ } from "react";
10112
10120
  import { VIEWPORT_PRESETS as VIEWPORT_PRESETS2 } from "@bendyline/squisq/schemas";
10113
10121
  import { DEFAULT_THEME, flattenBlocks } from "@bendyline/squisq/doc";
10114
10122
  import {
@@ -10286,8 +10294,8 @@ function RecorderEntry({ open, onOpenChange, showTrigger = true } = {}) {
10286
10294
  const handleSave = useCallback16(
10287
10295
  (result) => {
10288
10296
  bumpMediaRevision();
10289
- if (result.source === "mic") {
10290
- if (activeView === "raw" && monacoEditor) {
10297
+ if (result.mediaKind === "audio") {
10298
+ if (result.source === "mic" && activeView === "raw" && monacoEditor) {
10291
10299
  annotateMonacoHeading(monacoEditor, result.filename);
10292
10300
  }
10293
10301
  const audioTag = `<audio src="${result.relativePath}" controls></audio>`;
@@ -16824,6 +16832,8 @@ function Toolbar({
16824
16832
  slotAfterActions,
16825
16833
  slotRight,
16826
16834
  showPlayTab = true,
16835
+ showFormattingControls = true,
16836
+ showInsertControls = true,
16827
16837
  hostMode = "document"
16828
16838
  }) {
16829
16839
  const {
@@ -16915,9 +16925,8 @@ function Toolbar({
16915
16925
  const codeSnippetMenuRef = useRef28(null);
16916
16926
  const mermaidTypeMenuRef = useRef28(null);
16917
16927
  const chartTypeMenuRef = useRef28(null);
16918
- const [insertMenuAnchor, setInsertMenuAnchor] = useState31(
16919
- null
16920
- );
16928
+ const insertMenuTriggerRectRef = useRef28(null);
16929
+ const [insertMenuAnchor, setInsertMenuAnchor] = useState31(null);
16921
16930
  const [codeSnippetMenuAnchor, setCodeSnippetMenuAnchor] = useState31(null);
16922
16931
  const [mermaidTypeMenuAnchor, setMermaidTypeMenuAnchor] = useState31(null);
16923
16932
  const [chartTypeMenuAnchor, setChartTypeMenuAnchor] = useState31(null);
@@ -16933,7 +16942,8 @@ function Toolbar({
16933
16942
  if (left + INSERT_MENU_WIDTH + margin > vw) {
16934
16943
  left = Math.max(margin, rect.right - INSERT_MENU_WIDTH);
16935
16944
  }
16936
- setInsertMenuAnchor({ top: rect.bottom + gap, left });
16945
+ insertMenuTriggerRectRef.current = rect;
16946
+ setInsertMenuAnchor({ top: rect.bottom + gap, left, placement: "down" });
16937
16947
  setCodeSnippetMenuAnchor(null);
16938
16948
  setMermaidTypeMenuAnchor(null);
16939
16949
  setChartTypeMenuAnchor(null);
@@ -16987,6 +16997,7 @@ function Toolbar({
16987
16997
  setMermaidTypeMenuAnchor(null);
16988
16998
  }, []);
16989
16999
  const closeInsertMenu = useCallback29(() => {
17000
+ insertMenuTriggerRectRef.current = null;
16990
17001
  setInsertMenuAnchor(null);
16991
17002
  setCodeSnippetMenuAnchor(null);
16992
17003
  setMermaidTypeMenuAnchor(null);
@@ -17080,6 +17091,28 @@ function Toolbar({
17080
17091
  document.addEventListener("mousedown", handleClick);
17081
17092
  return () => document.removeEventListener("mousedown", handleClick);
17082
17093
  }, [insertMenuAnchor, closeInsertMenu]);
17094
+ const insertMenuOpen = insertMenuAnchor !== null;
17095
+ useLayoutEffect4(() => {
17096
+ if (!insertMenuOpen) return;
17097
+ const menu = insertMenuRef.current;
17098
+ const triggerRect = insertMenuTriggerRectRef.current;
17099
+ if (!menu || !triggerRect) return;
17100
+ const gap = 4;
17101
+ const margin = 8;
17102
+ const spaceBelow = Math.max(0, window.innerHeight - triggerRect.bottom - gap - margin);
17103
+ const spaceAbove = Math.max(0, triggerRect.top - gap - margin);
17104
+ const naturalHeight = Math.max(menu.scrollHeight, menu.getBoundingClientRect().height);
17105
+ const placement = naturalHeight > spaceBelow && spaceAbove > spaceBelow ? "up" : "down";
17106
+ const maxHeight = placement === "up" ? spaceAbove : spaceBelow;
17107
+ const visibleHeight = Math.min(naturalHeight, maxHeight);
17108
+ const top = placement === "up" ? Math.max(margin, triggerRect.top - gap - visibleHeight) : triggerRect.bottom + gap;
17109
+ setInsertMenuAnchor((current) => {
17110
+ if (!current || current.top === top && current.maxHeight === maxHeight && current.placement === placement) {
17111
+ return current;
17112
+ }
17113
+ return { ...current, top, maxHeight, placement };
17114
+ });
17115
+ }, [insertMenuOpen]);
17083
17116
  const [overflowPlacement, setOverflowPlacement] = useState31("down");
17084
17117
  useEffect24(() => {
17085
17118
  if (!showOverflow || !overflowRef.current) return;
@@ -17806,7 +17839,7 @@ ${TASK_LIST_MARKDOWN}
17806
17839
  return Number(m[1]) <= visibleHeadingMax;
17807
17840
  };
17808
17841
  const hasVisibleMediaButtons = MEDIA_BUTTONS.some((b) => isButtonVisible(b.id));
17809
- const showInsertInOverflow = overflowIndex !== null && overflowIndex <= FIRST_MEDIA_INDEX && hasVisibleMediaButtons;
17842
+ const showInsertInOverflow = showInsertControls && overflowIndex !== null && overflowIndex <= FIRST_MEDIA_INDEX && hasVisibleMediaButtons;
17810
17843
  const isInTable = isWysiwyg ? tiptapEditor.isActive("table") : false;
17811
17844
  const wysiwygTemplate = isWysiwyg ? tiptapEditor.isActive("heading") ? tiptapEditor.getAttributes("heading")?.dataTemplate ?? "" : null : null;
17812
17845
  const isRawView = activeView === "raw";
@@ -18026,17 +18059,17 @@ ${TASK_LIST_MARKDOWN}
18026
18059
  dataTemplateParams: updated.templateParams
18027
18060
  }).run();
18028
18061
  };
18029
- const showTemplateInOverflow = currentTemplate !== null && clippedContextual.has("template");
18030
- const showTransitionInOverflow = currentTransition !== null && clippedContextual.has("transition");
18062
+ const showTemplateInOverflow = showFormattingControls && currentTemplate !== null && clippedContextual.has("template");
18063
+ const showTransitionInOverflow = showFormattingControls && currentTransition !== null && clippedContextual.has("transition");
18031
18064
  const showBlockSectionInOverflow = showTemplateInOverflow || showTransitionInOverflow;
18032
18065
  const overflowBlockLabel = currentTemplate ? templateLabel(currentTemplate) : "Heading";
18033
- const showToolbarOverflow = !findMode && !isPreview && !isCodeMode && (overflowIndex !== null || clippedContextual.size > 0);
18066
+ const showToolbarOverflow = !findMode && !isPreview && !isCodeMode && (showFormattingControls && (overflowIndex !== null || clippedContextual.size > 0) || showInsertInOverflow);
18034
18067
  return /* @__PURE__ */ jsxs31(
18035
18068
  "div",
18036
18069
  {
18037
18070
  className: `squisq-toolbar${findMode ? " squisq-toolbar--find" : ""} ${className || ""}`,
18038
18071
  role: "toolbar",
18039
- "aria-label": findMode ? "Find toolbar" : "Formatting toolbar",
18072
+ "aria-label": findMode ? "Find toolbar" : showFormattingControls ? "Formatting toolbar" : "Editor toolbar",
18040
18073
  children: [
18041
18074
  /* @__PURE__ */ jsx41(
18042
18075
  "input",
@@ -18107,8 +18140,8 @@ ${TASK_LIST_MARKDOWN}
18107
18140
  );
18108
18141
  }) }),
18109
18142
  findMode ? /* @__PURE__ */ jsx41(FindToolbar, { onClose: () => setFindMode(false) }) : slotAfterTabs,
18110
- !findMode && !isPreview && !isCodeMode && /* @__PURE__ */ jsxs31("div", { className: "squisq-toolbar-actions", ref: actionsRef, children: [
18111
- groups.map((group, gi) => /* @__PURE__ */ jsxs31("div", { className: "squisq-toolbar-group", children: [
18143
+ !findMode && !isPreview && !isCodeMode && (showFormattingControls || showInsertControls) && /* @__PURE__ */ jsxs31("div", { className: "squisq-toolbar-actions", ref: actionsRef, children: [
18144
+ showFormattingControls && groups.map((group, gi) => /* @__PURE__ */ jsxs31("div", { className: "squisq-toolbar-group", children: [
18112
18145
  gi > 0 && /* @__PURE__ */ jsx41("div", { className: "squisq-toolbar-separator" }),
18113
18146
  BUTTONS.filter((b) => b.group === group && isButtonVisible(b.id)).map((btn) => {
18114
18147
  const active = btn.id === "emoji" ? emojiPickerAnchor !== null : formatActive && formattingEditor ? isTiptapActive(formattingEditor, btn.id) : false;
@@ -18130,8 +18163,8 @@ ${TASK_LIST_MARKDOWN}
18130
18163
  );
18131
18164
  })
18132
18165
  ] }, group)),
18133
- /* @__PURE__ */ jsxs31("div", { className: "squisq-toolbar-group", children: [
18134
- /* @__PURE__ */ jsx41("div", { className: "squisq-toolbar-separator" }),
18166
+ showInsertControls && /* @__PURE__ */ jsxs31("div", { className: "squisq-toolbar-group", children: [
18167
+ showFormattingControls && /* @__PURE__ */ jsx41("div", { className: "squisq-toolbar-separator" }),
18135
18168
  /* @__PURE__ */ jsx41(
18136
18169
  "button",
18137
18170
  {
@@ -18147,7 +18180,7 @@ ${TASK_LIST_MARKDOWN}
18147
18180
  }
18148
18181
  )
18149
18182
  ] }),
18150
- currentTemplate !== null && /* @__PURE__ */ jsxs31(
18183
+ showFormattingControls && currentTemplate !== null && /* @__PURE__ */ jsxs31(
18151
18184
  "div",
18152
18185
  {
18153
18186
  className: `squisq-toolbar-group squisq-toolbar-contextual squisq-template-picker${clippedContextual.has("template") ? " squisq-toolbar-contextual--clipped" : ""}`,
@@ -18167,7 +18200,7 @@ ${TASK_LIST_MARKDOWN}
18167
18200
  ]
18168
18201
  }
18169
18202
  ),
18170
- currentTransition !== null && /* @__PURE__ */ jsxs31(
18203
+ showFormattingControls && currentTransition !== null && /* @__PURE__ */ jsxs31(
18171
18204
  "div",
18172
18205
  {
18173
18206
  className: `squisq-toolbar-group squisq-toolbar-contextual squisq-transition-picker-group${clippedContextual.has("transition") ? " squisq-toolbar-contextual--clipped" : ""}`,
@@ -18186,7 +18219,7 @@ ${TASK_LIST_MARKDOWN}
18186
18219
  ]
18187
18220
  }
18188
18221
  ),
18189
- isInTable && /* @__PURE__ */ jsxs31(
18222
+ showFormattingControls && isInTable && /* @__PURE__ */ jsxs31(
18190
18223
  "div",
18191
18224
  {
18192
18225
  className: `squisq-toolbar-group squisq-toolbar-contextual squisq-table-controls${clippedContextual.has("table") ? " squisq-toolbar-contextual--clipped" : ""}`,
@@ -18386,6 +18419,7 @@ ${TASK_LIST_MARKDOWN}
18386
18419
  }
18387
18420
  )
18388
18421
  ] }),
18422
+ !findMode && !isPreview && !isCodeMode && !showFormattingControls && !showInsertControls && /* @__PURE__ */ jsx41("div", { className: "squisq-toolbar-actions" }),
18389
18423
  showToolbarOverflow && /* @__PURE__ */ jsxs31("div", { className: "squisq-toolbar-overflow", ref: overflowRef, children: [
18390
18424
  /* @__PURE__ */ jsx41(
18391
18425
  "button",
@@ -18404,7 +18438,7 @@ ${TASK_LIST_MARKDOWN}
18404
18438
  {
18405
18439
  className: `squisq-toolbar-overflow-menu squisq-toolbar-overflow-menu--${overflowPlacement}`,
18406
18440
  children: [
18407
- BUTTONS.slice(overflowIndex ?? BUTTONS.length).filter((b) => isButtonVisible(b.id)).filter((b) => b.group !== "media").map((btn) => {
18441
+ showFormattingControls && BUTTONS.slice(overflowIndex ?? BUTTONS.length).filter((b) => isButtonVisible(b.id)).filter((b) => b.group !== "media").map((btn) => {
18408
18442
  const active = btn.id === "emoji" ? emojiPickerAnchor !== null : formatActive && formattingEditor ? isTiptapActive(formattingEditor, btn.id) : false;
18409
18443
  const disabled = btn.id === "image" && !mediaProvider || !buttonAllowed(btn.id);
18410
18444
  return /* @__PURE__ */ jsxs31(
@@ -18480,7 +18514,7 @@ ${TASK_LIST_MARKDOWN}
18480
18514
  accentColor: previewSettings?.activeTheme.colors.primary
18481
18515
  }
18482
18516
  ) }),
18483
- isInTable && clippedContextual.has("table") && /* @__PURE__ */ jsxs31(Fragment13, { children: [
18517
+ showFormattingControls && isInTable && clippedContextual.has("table") && /* @__PURE__ */ jsxs31(Fragment13, { children: [
18484
18518
  /* @__PURE__ */ jsx41(
18485
18519
  "div",
18486
18520
  {
@@ -18608,7 +18642,13 @@ ${TASK_LIST_MARKDOWN}
18608
18642
  ref: insertMenuRef,
18609
18643
  className: "squisq-insert-menu",
18610
18644
  "data-theme": colorScheme,
18611
- style: { position: "fixed", top: insertMenuAnchor.top, left: insertMenuAnchor.left },
18645
+ "data-placement": insertMenuAnchor.placement,
18646
+ style: {
18647
+ position: "fixed",
18648
+ top: insertMenuAnchor.top,
18649
+ left: insertMenuAnchor.left,
18650
+ maxHeight: insertMenuAnchor.maxHeight
18651
+ },
18612
18652
  role: "menu",
18613
18653
  children: [
18614
18654
  showConvertActions && /* @__PURE__ */ jsxs31(Fragment13, { children: [
@@ -22574,7 +22614,7 @@ function mermaidErrorMessage(error) {
22574
22614
  }
22575
22615
 
22576
22616
  // src/mermaid/MermaidDiagramCanvas.tsx
22577
- import { useCallback as useCallback33, useEffect as useEffect27, useId as useId12, useLayoutEffect as useLayoutEffect4, useRef as useRef32, useState as useState36 } from "react";
22617
+ import { useCallback as useCallback33, useEffect as useEffect27, useId as useId12, useLayoutEffect as useLayoutEffect5, useRef as useRef32, useState as useState36 } from "react";
22578
22618
  import { jsx as jsx46, jsxs as jsxs35 } from "react/jsx-runtime";
22579
22619
  var renderSequence = 0;
22580
22620
  function excludesCanvasPan(target, viewport) {
@@ -22704,7 +22744,7 @@ function MermaidDiagramCanvas({
22704
22744
  current = false;
22705
22745
  };
22706
22746
  }, [source, onModelChange, theme]);
22707
- useLayoutEffect4(() => {
22747
+ useLayoutEffect5(() => {
22708
22748
  const scroll = scrollRef.current;
22709
22749
  if (!scroll) return;
22710
22750
  const measure = () => {
@@ -22725,7 +22765,7 @@ function MermaidDiagramCanvas({
22725
22765
  observer.observe(scroll);
22726
22766
  return () => observer.disconnect();
22727
22767
  }, []);
22728
- useLayoutEffect4(() => {
22768
+ useLayoutEffect5(() => {
22729
22769
  setContentSize(readSvgViewBox(svgRootRef.current));
22730
22770
  }, [svg]);
22731
22771
  const fitZoom = contentSize && canvasSize ? calculateFitScale(contentSize, canvasSize) : 1;
@@ -22734,7 +22774,7 @@ function MermaidDiagramCanvas({
22734
22774
  setZoom(fitZoom);
22735
22775
  setPan({ x: 0, y: 0 });
22736
22776
  }, [fitZoom]);
22737
- useLayoutEffect4(() => {
22777
+ useLayoutEffect5(() => {
22738
22778
  if (viewMode !== "fit") return;
22739
22779
  setZoom(fitZoom);
22740
22780
  setPan({ x: 0, y: 0 });
@@ -22801,7 +22841,7 @@ function MermaidDiagramCanvas({
22801
22841
  selectedNodeId,
22802
22842
  selectedTextId
22803
22843
  ]);
22804
- useLayoutEffect4(() => {
22844
+ useLayoutEffect5(() => {
22805
22845
  const node2 = renamingNodeId ? model2?.nodes.find((candidate) => candidate.id === renamingNodeId) : null;
22806
22846
  const edge2 = renamingEdgeId ? model2?.edges.find((candidate) => candidate.id === renamingEdgeId) : null;
22807
22847
  const text = renamingTextId ? model2 && mermaidEditableTexts(model2).find((candidate) => candidate.id === renamingTextId) : null;
@@ -22814,7 +22854,7 @@ function MermaidDiagramCanvas({
22814
22854
  });
22815
22855
  return () => window.cancelAnimationFrame(frame);
22816
22856
  }, [model2, renamingEdgeId, renamingNodeId, renamingTextId]);
22817
- useLayoutEffect4(() => {
22857
+ useLayoutEffect5(() => {
22818
22858
  const root = svgRootRef.current;
22819
22859
  if (!root || !model2) {
22820
22860
  setSelectionAnchor(null);
@@ -23354,7 +23394,7 @@ function CanvasAction({
23354
23394
  }
23355
23395
 
23356
23396
  // src/mermaid/MermaidShapePalette.tsx
23357
- import { useEffect as useEffect28, useLayoutEffect as useLayoutEffect5, useMemo as useMemo29, useRef as useRef33, useState as useState37 } from "react";
23397
+ import { useEffect as useEffect28, useLayoutEffect as useLayoutEffect6, useMemo as useMemo29, useRef as useRef33, useState as useState37 } from "react";
23358
23398
  import { jsx as jsx47, jsxs as jsxs36 } from "react/jsx-runtime";
23359
23399
  var CATEGORIES = ["Basic", "Process", "Data", "Documents", "Symbols"];
23360
23400
  var PALETTE_WIDTH = 360;
@@ -23390,7 +23430,7 @@ function MermaidShapePalette({ selected, onPick, onClose }) {
23390
23430
  )
23391
23431
  })).filter((section) => section.shapes.length > 0);
23392
23432
  }, [query]);
23393
- useLayoutEffect5(() => {
23433
+ useLayoutEffect6(() => {
23394
23434
  const palette = ref.current;
23395
23435
  const anchor = palette?.parentElement;
23396
23436
  if (!palette || !anchor) return;
@@ -23549,7 +23589,7 @@ import { createRoot as createRoot3 } from "react-dom/client";
23549
23589
  import {
23550
23590
  useCallback as useCallback34,
23551
23591
  useEffect as useEffect29,
23552
- useLayoutEffect as useLayoutEffect6,
23592
+ useLayoutEffect as useLayoutEffect7,
23553
23593
  useRef as useRef34,
23554
23594
  useState as useState38,
23555
23595
  useSyncExternalStore
@@ -24443,7 +24483,7 @@ function useDismissibleMermaidPopover(ref, onClose) {
24443
24483
  }
24444
24484
  function useClampedMermaidPopoverPosition(ref, preferredWidth, preferredMaxHeight) {
24445
24485
  const [position, setPosition] = useState38(null);
24446
- useLayoutEffect6(() => {
24486
+ useLayoutEffect7(() => {
24447
24487
  const picker = ref.current;
24448
24488
  const anchor = picker?.parentElement;
24449
24489
  if (!picker || !anchor) return;
@@ -27435,7 +27475,7 @@ function persistFromWrite(bodyMd, state) {
27435
27475
  }
27436
27476
 
27437
27477
  // src/WysiwygEditor.tsx
27438
- import { useCallback as useCallback38, useEffect as useEffect39, useMemo as useMemo35, useRef as useRef40, useState as useState49 } from "react";
27478
+ import { useCallback as useCallback38, useEffect as useEffect40, useMemo as useMemo35, useRef as useRef40, useState as useState50 } from "react";
27439
27479
  import { useEditor as useEditor2, EditorContent as EditorContent2 } from "@tiptap/react";
27440
27480
  import { Selection } from "@tiptap/pm/state";
27441
27481
  import StarterKit2 from "@tiptap/starter-kit";
@@ -27876,6 +27916,7 @@ var ImageWithMediaProvider = Image.extend({
27876
27916
  // src/tiptap/TiptapVideo.tsx
27877
27917
  import { Node as Node3, mergeAttributes as mergeAttributes2 } from "@tiptap/core";
27878
27918
  import { NodeViewWrapper as NodeViewWrapper2, ReactNodeViewRenderer as ReactNodeViewRenderer2 } from "@tiptap/react";
27919
+ import { useEffect as useEffect39, useState as useState49 } from "react";
27879
27920
 
27880
27921
  // src/tiptap/useResolvedMediaSrc.ts
27881
27922
  import { useEffect as useEffect38, useState as useState48 } from "react";
@@ -27934,15 +27975,23 @@ function VideoNodeView({ node: node2, updateAttributes, selected }) {
27934
27975
  const placement = normalizeVideoPlacement(rawPlacement);
27935
27976
  const lockToBlock = normalizeLockToBlock(rawLockToBlock);
27936
27977
  const resolvedSrc = useResolvedMediaSrc(src ?? "");
27978
+ const [audioOnly, setAudioOnly] = useState49(false);
27937
27979
  const resolvedPoster = useResolvedMediaSrc(poster ?? "");
27980
+ useEffect39(() => {
27981
+ setAudioOnly(false);
27982
+ }, [resolvedSrc]);
27983
+ const handleLoadedMetadata = (event) => {
27984
+ const video = event.currentTarget;
27985
+ setAudioOnly(video.videoWidth <= 0 && video.videoHeight <= 0);
27986
+ };
27938
27987
  return /* @__PURE__ */ jsxs43(
27939
27988
  NodeViewWrapper2,
27940
27989
  {
27941
27990
  as: "div",
27942
- className: `squisq-inline-video-player squisq-video-node${selected ? " squisq-video-node--selected" : ""}`,
27991
+ className: `${audioOnly ? "squisq-inline-audio-player squisq-video-node--audio-only" : "squisq-inline-video-player"} squisq-video-node${selected ? " squisq-video-node--selected" : ""}`,
27943
27992
  "data-video-placement": placement,
27944
27993
  children: [
27945
- /* @__PURE__ */ jsxs43(
27994
+ !audioOnly && /* @__PURE__ */ jsxs43(
27946
27995
  "div",
27947
27996
  {
27948
27997
  className: "squisq-video-placement-toolbar",
@@ -27980,7 +28029,16 @@ function VideoNodeView({ node: node2, updateAttributes, selected }) {
27980
28029
  ]
27981
28030
  }
27982
28031
  ),
27983
- /* @__PURE__ */ jsx54(
28032
+ audioOnly ? /* @__PURE__ */ jsx54(
28033
+ "audio",
28034
+ {
28035
+ "data-drag-handle": true,
28036
+ draggable: true,
28037
+ src: resolvedSrc || void 0,
28038
+ controls,
28039
+ preload: "metadata"
28040
+ }
28041
+ ) : /* @__PURE__ */ jsx54(
27984
28042
  "video",
27985
28043
  {
27986
28044
  "data-drag-handle": true,
@@ -27991,7 +28049,8 @@ function VideoNodeView({ node: node2, updateAttributes, selected }) {
27991
28049
  playsInline: true,
27992
28050
  preload: "metadata",
27993
28051
  width: width ?? void 0,
27994
- height: height ?? void 0
28052
+ height: height ?? void 0,
28053
+ onLoadedMetadata: handleLoadedMetadata
27995
28054
  }
27996
28055
  )
27997
28056
  ]
@@ -28584,11 +28643,11 @@ function WysiwygEditor({
28584
28643
  mermaidThemeStoreRef.current = createMermaidThemeStore(activeTheme ?? DEFAULT_THEME4);
28585
28644
  }
28586
28645
  const mermaidThemeStore = mermaidThemeStoreRef.current;
28587
- useEffect39(() => {
28646
+ useEffect40(() => {
28588
28647
  mermaidThemeStore.setTheme(activeTheme ?? DEFAULT_THEME4);
28589
28648
  }, [activeTheme, mermaidThemeStore]);
28590
28649
  const { docTemplates, onDocTemplatesChange } = useDocCustomTemplates();
28591
- const [designerState, setDesignerState] = useState49(
28650
+ const [designerState, setDesignerState] = useState50(
28592
28651
  null
28593
28652
  );
28594
28653
  const handleDesignerSave = useCallback38(
@@ -28604,7 +28663,7 @@ function WysiwygEditor({
28604
28663
  [docTemplates, onDocTemplatesChange]
28605
28664
  );
28606
28665
  const mentionProviderRef = useRef40(mentionProvider);
28607
- useEffect39(() => {
28666
+ useEffect40(() => {
28608
28667
  mentionProviderRef.current = mentionProvider;
28609
28668
  }, [mentionProvider]);
28610
28669
  const resolvedPlaceholder = useMemo35(() => placeholder ?? pickEmptyPrompt(), [placeholder]);
@@ -28612,7 +28671,7 @@ function WysiwygEditor({
28612
28671
  const lastSourceRef = useRef40(editorSource);
28613
28672
  const pendingLocalSourcesRef = useRef40([]);
28614
28673
  const mediaProviderRef = useRef40(mediaProvider);
28615
- useEffect39(() => {
28674
+ useEffect40(() => {
28616
28675
  mediaProviderRef.current = mediaProvider;
28617
28676
  }, [mediaProvider]);
28618
28677
  const frontmatterRef = useRef40(stripFrontmatter(editorSource).frontmatter);
@@ -28629,7 +28688,7 @@ function WysiwygEditor({
28629
28688
  }
28630
28689
  }
28631
28690
  const submitOnEnterRef = useRef40(submitOnEnter);
28632
- useEffect39(() => {
28691
+ useEffect40(() => {
28633
28692
  submitOnEnterRef.current = submitOnEnter;
28634
28693
  }, [submitOnEnter]);
28635
28694
  const editor = useEditor2({
@@ -28811,25 +28870,25 @@ function WysiwygEditor({
28811
28870
  }
28812
28871
  }
28813
28872
  });
28814
- useEffect39(() => {
28873
+ useEffect40(() => {
28815
28874
  if (editor) {
28816
28875
  setTiptapEditor(editor);
28817
28876
  }
28818
28877
  return () => setTiptapEditor(null);
28819
28878
  }, [editor, setTiptapEditor]);
28820
- useEffect39(() => {
28879
+ useEffect40(() => {
28821
28880
  if (editor) editor.setEditable(!readOnly);
28822
28881
  }, [editor, readOnly]);
28823
28882
  const containerRef = useRef40(null);
28824
- const [badgeMenu, setBadgeMenu] = useState49(null);
28825
- const [propsMenu, setPropsMenu] = useState49(null);
28883
+ const [badgeMenu, setBadgeMenu] = useState50(null);
28884
+ const [propsMenu, setPropsMenu] = useState50(null);
28826
28885
  const closeBadgeMenu = useCallback38(() => {
28827
28886
  setBadgeMenu(null);
28828
28887
  requestAnimationFrame(() => {
28829
28888
  if (editor && !editor.isDestroyed) editor.commands.focus();
28830
28889
  });
28831
28890
  }, [editor]);
28832
- useEffect39(() => {
28891
+ useEffect40(() => {
28833
28892
  if (!editor) return;
28834
28893
  const root = containerRef.current;
28835
28894
  if (!root) return;
@@ -28885,7 +28944,7 @@ function WysiwygEditor({
28885
28944
  root.addEventListener("mousedown", onClick);
28886
28945
  return () => root.removeEventListener("mousedown", onClick);
28887
28946
  }, [editor]);
28888
- useEffect39(() => {
28947
+ useEffect40(() => {
28889
28948
  if (!editor) return;
28890
28949
  const pendingIndex = pendingLocalSourcesRef.current.lastIndexOf(editorSource);
28891
28950
  if (pendingIndex >= 0) {
@@ -29142,7 +29201,7 @@ function moveSelectionToDropPoint(view, event) {
29142
29201
  }
29143
29202
 
29144
29203
  // src/InlinePreviewGutter.tsx
29145
- import { useLayoutEffect as useLayoutEffect7, useMemo as useMemo37, useRef as useRef41, useState as useState51 } from "react";
29204
+ import { useLayoutEffect as useLayoutEffect8, useMemo as useMemo37, useRef as useRef41, useState as useState52 } from "react";
29146
29205
  import { VIEWPORT_PRESETS as VIEWPORT_PRESETS4 } from "@bendyline/squisq/schemas";
29147
29206
  import {
29148
29207
  flattenBlocks as flattenBlocks4,
@@ -29154,14 +29213,14 @@ import { extractPlainText as extractPlainText3, getChildren } from "@bendyline/s
29154
29213
  import { BlockRenderer as BlockRenderer3, MediaContext as MediaContext4 } from "@bendyline/squisq-react";
29155
29214
 
29156
29215
  // src/useHeadingLayout.ts
29157
- import { useCallback as useCallback39, useEffect as useEffect40, useMemo as useMemo36, useState as useState50 } from "react";
29216
+ import { useCallback as useCallback39, useEffect as useEffect41, useMemo as useMemo36, useState as useState51 } from "react";
29158
29217
  import { flattenBlocks as flattenBlocks3, hasTemplate } from "@bendyline/squisq/doc";
29159
29218
  function useHeadingLayout(refInsideWrapper) {
29160
29219
  const { doc, activeView, monacoEditor, tiptapEditor } = useEditorContext();
29161
29220
  const flatBlocks = useMemo36(() => doc ? flattenBlocks3(doc.blocks) : [], [doc]);
29162
- const [entries, setEntries] = useState50([]);
29163
- const [pageEdges, setPageEdges] = useState50(null);
29164
- useEffect40(() => {
29221
+ const [entries, setEntries] = useState51([]);
29222
+ const [pageEdges, setPageEdges] = useState51(null);
29223
+ useEffect41(() => {
29165
29224
  if (activeView !== "wysiwyg") return;
29166
29225
  const node2 = refInsideWrapper.current;
29167
29226
  if (!node2) return;
@@ -29229,7 +29288,7 @@ function useHeadingLayout(refInsideWrapper) {
29229
29288
  window.removeEventListener("resize", recompute);
29230
29289
  };
29231
29290
  }, [activeView, flatBlocks, refInsideWrapper]);
29232
- useEffect40(() => {
29291
+ useEffect41(() => {
29233
29292
  if (activeView !== "raw") return;
29234
29293
  if (!monacoEditor) return;
29235
29294
  const node2 = refInsideWrapper.current;
@@ -29290,7 +29349,7 @@ function useHeadingLayout(refInsideWrapper) {
29290
29349
  window.removeEventListener("resize", recompute);
29291
29350
  };
29292
29351
  }, [activeView, monacoEditor, flatBlocks, refInsideWrapper]);
29293
- useEffect40(() => {
29352
+ useEffect41(() => {
29294
29353
  setEntries([]);
29295
29354
  setPageEdges(null);
29296
29355
  }, [activeView]);
@@ -29584,8 +29643,8 @@ function InlinePreviewGutter({
29584
29643
  headingEntries.forEach((e2) => m.set(e2.block.id, e2.top));
29585
29644
  return m;
29586
29645
  }, [headingEntries]);
29587
- const [positions, setPositions] = useState51(/* @__PURE__ */ new Map());
29588
- useLayoutEffect7(() => {
29646
+ const [positions, setPositions] = useState52(/* @__PURE__ */ new Map());
29647
+ useLayoutEffect8(() => {
29589
29648
  if (items.length === 0) {
29590
29649
  setPositions((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
29591
29650
  return;
@@ -30100,10 +30159,10 @@ function buildPreviewDoc(doc, options) {
30100
30159
  // src/OutlinePanel.tsx
30101
30160
  import {
30102
30161
  useCallback as useCallback40,
30103
- useEffect as useEffect41,
30162
+ useEffect as useEffect42,
30104
30163
  useMemo as useMemo38,
30105
30164
  useRef as useRef42,
30106
- useState as useState52
30165
+ useState as useState53
30107
30166
  } from "react";
30108
30167
  import { flattenBlocks as flattenBlocks5, hasTemplate as hasTemplate3 } from "@bendyline/squisq/doc";
30109
30168
  import { extractPlainText as extractPlainText5 } from "@bendyline/squisq/markdown";
@@ -30222,8 +30281,8 @@ function OutlinePanel({ width, className, readOnly = false }) {
30222
30281
  const { scrollToBlock } = useHeadingLayout(paneRef);
30223
30282
  const cursorActiveId = useActiveOutlineBlockId();
30224
30283
  const activeDragRef = useRef42(null);
30225
- const [draggedBlockId, setDraggedBlockId] = useState52(null);
30226
- const [dropTarget, setDropTarget] = useState52(null);
30284
+ const [draggedBlockId, setDraggedBlockId] = useState53(null);
30285
+ const [dropTarget, setDropTarget] = useState53(null);
30227
30286
  const blockModeActiveId = useMemo38(() => {
30228
30287
  if (layoutMode !== "block" || activeBlockStartLine == null || !doc) return null;
30229
30288
  const match = flattenBlocks5(doc.blocks).find(
@@ -30490,11 +30549,11 @@ function OutlineNode({
30490
30549
  function useActiveOutlineBlockId() {
30491
30550
  const { doc, activeView, tiptapEditor, monacoEditor } = useEditorContext();
30492
30551
  const flatBlocks = useMemo38(() => doc ? flattenBlocks5(doc.blocks) : [], [doc]);
30493
- const [activeId, setActiveId] = useState52(null);
30494
- useEffect41(() => {
30552
+ const [activeId, setActiveId] = useState53(null);
30553
+ useEffect42(() => {
30495
30554
  setActiveId(null);
30496
30555
  }, [activeView]);
30497
- useEffect41(() => {
30556
+ useEffect42(() => {
30498
30557
  if (activeView !== "wysiwyg" || !tiptapEditor) return;
30499
30558
  const update = () => {
30500
30559
  const { from } = tiptapEditor.state.selection;
@@ -30516,7 +30575,7 @@ function useActiveOutlineBlockId() {
30516
30575
  tiptapEditor.off("update", update);
30517
30576
  };
30518
30577
  }, [activeView, tiptapEditor, flatBlocks]);
30519
- useEffect41(() => {
30578
+ useEffect42(() => {
30520
30579
  if (activeView !== "raw" || !monacoEditor) return;
30521
30580
  const update = () => {
30522
30581
  const line = monacoEditor.getPosition()?.lineNumber;
@@ -30567,7 +30626,7 @@ function bumpHeadingLevelInSource(source, line, delta) {
30567
30626
  }
30568
30627
 
30569
30628
  // src/codeContext/CodeContextZones.tsx
30570
- import { useCallback as useCallback42, useEffect as useEffect43, useMemo as useMemo40, useRef as useRef44, useState as useState53 } from "react";
30629
+ import { useCallback as useCallback42, useEffect as useEffect44, useMemo as useMemo40, useRef as useRef44, useState as useState54 } from "react";
30571
30630
  import { createPortal as createPortal10 } from "react-dom";
30572
30631
 
30573
30632
  // src/codeContext/diffContextSections.ts
@@ -30691,7 +30750,7 @@ function setOrdinal(zone, ordinal) {
30691
30750
  // src/codeContext/CodeContextSectionView.tsx
30692
30751
  import { parseMarkdown as parseMarkdown8 } from "@bendyline/squisq/markdown";
30693
30752
  import { MarkdownRenderer } from "@bendyline/squisq-react";
30694
- import { useCallback as useCallback41, useEffect as useEffect42, useMemo as useMemo39, useRef as useRef43 } from "react";
30753
+ import { useCallback as useCallback41, useEffect as useEffect43, useMemo as useMemo39, useRef as useRef43 } from "react";
30695
30754
  import { jsx as jsx59, jsxs as jsxs47 } from "react/jsx-runtime";
30696
30755
  function CodeContextSectionView({
30697
30756
  section,
@@ -30711,7 +30770,7 @@ function CodeContextSectionView({
30711
30770
  () => expanded && section.markdown ? parseMarkdown8(section.markdown).children : null,
30712
30771
  [expanded, section.markdown]
30713
30772
  );
30714
- useEffect42(() => {
30773
+ useEffect43(() => {
30715
30774
  const el = rootRef.current;
30716
30775
  if (!el || typeof ResizeObserver === "undefined") return;
30717
30776
  const report = () => {
@@ -30783,11 +30842,11 @@ function CodeContextSectionView({
30783
30842
  import { Fragment as Fragment18, jsx as jsx60 } from "react/jsx-runtime";
30784
30843
  function CodeContextZones({ options }) {
30785
30844
  const { monacoEditor } = useEditorContext();
30786
- const [manager, setManager] = useState53(null);
30787
- const [, setZonesVersion] = useState53(0);
30788
- const [expandedById, setExpandedById] = useState53({});
30845
+ const [manager, setManager] = useState54(null);
30846
+ const [, setZonesVersion] = useState54(0);
30847
+ const [expandedById, setExpandedById] = useState54({});
30789
30848
  const seenIds = useRef44(/* @__PURE__ */ new Set());
30790
- useEffect43(() => {
30849
+ useEffect44(() => {
30791
30850
  if (!monacoEditor) return;
30792
30851
  const mgr = new CodeContextZoneManager(monacoEditor);
30793
30852
  const off = mgr.onDidChangeZones(() => setZonesVersion((v2) => v2 + 1));
@@ -30810,7 +30869,7 @@ function CodeContextZones({ options }) {
30810
30869
  });
30811
30870
  return out;
30812
30871
  }, [fileTop, sections]);
30813
- useEffect43(() => {
30872
+ useEffect44(() => {
30814
30873
  if (!manager) return;
30815
30874
  manager.sync(resolved.map((r) => r.spec));
30816
30875
  setExpandedById((prev) => {
@@ -31246,7 +31305,7 @@ function placeClipInBlock(source, fromLine, targetHeadingLine, spec, startAt) {
31246
31305
  }
31247
31306
 
31248
31307
  // src/TimelineTrack.tsx
31249
- import { useCallback as useCallback44, useEffect as useEffect46, useMemo as useMemo41, useRef as useRef47, useState as useState56 } from "react";
31308
+ import { useCallback as useCallback44, useEffect as useEffect47, useMemo as useMemo41, useRef as useRef47, useState as useState57 } from "react";
31250
31309
  import {
31251
31310
  resolveMediaSchedule as resolveMediaSchedule2,
31252
31311
  getDocPlaybackDuration,
@@ -31405,7 +31464,7 @@ function resolveBlockVisual(doc, block, theme, viewport) {
31405
31464
  }
31406
31465
 
31407
31466
  // src/useTimelineClock.ts
31408
- import { useCallback as useCallback43, useEffect as useEffect44, useRef as useRef45, useState as useState54 } from "react";
31467
+ import { useCallback as useCallback43, useEffect as useEffect45, useRef as useRef45, useState as useState55 } from "react";
31409
31468
  function advanceTime(prev, dt, total) {
31410
31469
  if (total <= 0) return 0;
31411
31470
  return Math.min(total, Math.max(0, prev + dt));
@@ -31425,8 +31484,8 @@ function playTimelineMediaAt(root, time) {
31425
31484
  });
31426
31485
  }
31427
31486
  function useTimelineClock(total) {
31428
- const [currentTime, setCurrentTime] = useState54(0);
31429
- const [isPlaying, setIsPlaying] = useState54(false);
31487
+ const [currentTime, setCurrentTime] = useState55(0);
31488
+ const [isPlaying, setIsPlaying] = useState55(false);
31430
31489
  const rafRef = useRef45(null);
31431
31490
  const lastRef = useRef45(0);
31432
31491
  const currentTimeRef = useRef45(0);
@@ -31434,10 +31493,10 @@ function useTimelineClock(total) {
31434
31493
  const mediaHostRef = useRef45(null);
31435
31494
  currentTimeRef.current = currentTime;
31436
31495
  isPlayingRef.current = isPlaying;
31437
- useEffect44(() => {
31496
+ useEffect45(() => {
31438
31497
  setCurrentTime((t) => Math.min(t, Math.max(0, total)));
31439
31498
  }, [total]);
31440
- useEffect44(() => {
31499
+ useEffect45(() => {
31441
31500
  if (!isPlaying) return;
31442
31501
  lastRef.current = performance.now();
31443
31502
  const tick = (now) => {
@@ -31499,7 +31558,7 @@ function timelineMediaLabel(src, kind) {
31499
31558
  }
31500
31559
 
31501
31560
  // src/TimelineItemMenu.tsx
31502
- import { useEffect as useEffect45, useLayoutEffect as useLayoutEffect8, useRef as useRef46, useState as useState55 } from "react";
31561
+ import { useEffect as useEffect46, useLayoutEffect as useLayoutEffect9, useRef as useRef46, useState as useState56 } from "react";
31503
31562
  import { createPortal as createPortal11 } from "react-dom";
31504
31563
  import { Fragment as Fragment19, jsx as jsx63, jsxs as jsxs49 } from "react/jsx-runtime";
31505
31564
  function TimelineItemMenu({
@@ -31514,8 +31573,8 @@ function TimelineItemMenu({
31514
31573
  onClose
31515
31574
  }) {
31516
31575
  const panelRef = useRef46(null);
31517
- const [style, setStyle] = useState55(() => menuStyle(anchor));
31518
- useLayoutEffect8(() => {
31576
+ const [style, setStyle] = useState56(() => menuStyle(anchor));
31577
+ useLayoutEffect9(() => {
31519
31578
  const panel = panelRef.current;
31520
31579
  if (!panel) return;
31521
31580
  const updatePosition = () => setStyle(menuStyle(anchor, panel));
@@ -31528,7 +31587,7 @@ function TimelineItemMenu({
31528
31587
  resizeObserver?.disconnect();
31529
31588
  };
31530
31589
  }, [anchor]);
31531
- useEffect45(() => {
31590
+ useEffect46(() => {
31532
31591
  const onKeyDown = (event) => {
31533
31592
  if (event.key !== "Escape") return;
31534
31593
  event.preventDefault();
@@ -31588,12 +31647,12 @@ function BlockMenu({
31588
31647
  onStartTime,
31589
31648
  onTransition
31590
31649
  }) {
31591
- const [explicitDuration, setExplicitDuration] = useState55(target.explicitDuration);
31592
- const [duration, setDuration] = useState55(formatNumber(target.duration));
31593
- const [startTime, setStartTime] = useState55(
31650
+ const [explicitDuration, setExplicitDuration] = useState56(target.explicitDuration);
31651
+ const [duration, setDuration] = useState56(formatNumber(target.duration));
31652
+ const [startTime, setStartTime] = useState56(
31594
31653
  target.startTime == null ? "" : formatNumber(target.startTime)
31595
31654
  );
31596
- const [transition, setTransition] = useState55(target.transition);
31655
+ const [transition, setTransition] = useState56(target.transition);
31597
31656
  const toggleAutotime = (autotimed) => {
31598
31657
  setExplicitDuration(!autotimed);
31599
31658
  if (autotimed) {
@@ -31665,11 +31724,11 @@ function VideoMenu({
31665
31724
  target,
31666
31725
  onPatch
31667
31726
  }) {
31668
- const [placement, setPlacement] = useState55(target.placement);
31669
- const [locked, setLocked] = useState55(target.lockToBlock);
31670
- const [pipSize, setPipSize] = useState55(target.pipSize ?? "");
31671
- const [pipShape, setPipShape] = useState55(target.pipShape ?? "");
31672
- const [pipPosition, setPipPosition] = useState55(target.pipPosition ?? "");
31727
+ const [placement, setPlacement] = useState56(target.placement);
31728
+ const [locked, setLocked] = useState56(target.lockToBlock);
31729
+ const [pipSize, setPipSize] = useState56(target.pipSize ?? "");
31730
+ const [pipShape, setPipShape] = useState56(target.pipShape ?? "");
31731
+ const [pipPosition, setPipPosition] = useState56(target.pipPosition ?? "");
31673
31732
  const placed = placement === "picture-in-picture" || placement === "overlay";
31674
31733
  const placementOptions = [
31675
31734
  target.canUseContentPlacement ? { value: "content", label: "In layout" } : { value: "default", label: "Default" },
@@ -31939,9 +31998,9 @@ function TimelineTrack({
31939
31998
  colorScheme
31940
31999
  } = useEditorContext();
31941
32000
  const doc = docProp ?? contextDoc;
31942
- const [drag, setDrag] = useState56(null);
31943
- const [itemMenu, setItemMenu] = useState56(null);
31944
- const [pxPerSecond, setPxPerSecond] = useState56(DEFAULT_PX_PER_SECOND);
32001
+ const [drag, setDrag] = useState57(null);
32002
+ const [itemMenu, setItemMenu] = useState57(null);
32003
+ const [pxPerSecond, setPxPerSecond] = useState57(DEFAULT_PX_PER_SECOND);
31945
32004
  const scrollRef = useRef47(null);
31946
32005
  const blocks = useMemo41(() => doc ? flattenBlocks6(doc.blocks) : [], [doc]);
31947
32006
  const previewSettings = usePreviewSettingsOptional();
@@ -31974,8 +32033,8 @@ function TimelineTrack({
31974
32033
  setDrag(null);
31975
32034
  play();
31976
32035
  }, [play]);
31977
- const [scrubbing, setScrubbing] = useState56(false);
31978
- useEffect46(() => {
32036
+ const [scrubbing, setScrubbing] = useState57(false);
32037
+ useEffect47(() => {
31979
32038
  if (!scrubbing) return;
31980
32039
  const onMove = (e2) => {
31981
32040
  const scroll = scrollRef.current;
@@ -32013,7 +32072,7 @@ function TimelineTrack({
32013
32072
  [blocks]
32014
32073
  );
32015
32074
  const followedBlockRef = useRef47(null);
32016
- useEffect46(() => {
32075
+ useEffect47(() => {
32017
32076
  if (!isPlaying) {
32018
32077
  followedBlockRef.current = null;
32019
32078
  return;
@@ -32024,7 +32083,7 @@ function TimelineTrack({
32024
32083
  const line = headingLine(block);
32025
32084
  if (line != null) goToBlockByLine(line);
32026
32085
  }, [isPlaying, currentTime, blockAtTime, goToBlockByLine]);
32027
- useEffect46(() => {
32086
+ useEffect47(() => {
32028
32087
  if (!isPlaying) return;
32029
32088
  const scroll = scrollRef.current;
32030
32089
  if (!scroll) return;
@@ -32072,8 +32131,8 @@ function TimelineTrack({
32072
32131
  );
32073
32132
  const zoomIn = useCallback44(() => setPxPerSecond((s) => Math.min(ZOOM_MAX, s * ZOOM_FACTOR)), []);
32074
32133
  const zoomOut = useCallback44(() => setPxPerSecond((s) => Math.max(ZOOM_MIN, s / ZOOM_FACTOR)), []);
32075
- const [viewportWidth, setViewportWidth] = useState56(0);
32076
- useEffect46(() => {
32134
+ const [viewportWidth, setViewportWidth] = useState57(0);
32135
+ useEffect47(() => {
32077
32136
  const el = scrollRef.current;
32078
32137
  if (!el || typeof ResizeObserver === "undefined") return;
32079
32138
  const update = () => setViewportWidth(el.clientWidth);
@@ -32087,7 +32146,7 @@ function TimelineTrack({
32087
32146
  const dragRef = useRef47(null);
32088
32147
  dragRef.current = drag;
32089
32148
  const isDragging = drag != null && !drag.committed;
32090
- useEffect46(() => {
32149
+ useEffect47(() => {
32091
32150
  if (!isDragging) return;
32092
32151
  const onMove = (e2) => {
32093
32152
  const d = dragRef.current;
@@ -32118,7 +32177,7 @@ function TimelineTrack({
32118
32177
  window.removeEventListener("pointerup", onUp);
32119
32178
  };
32120
32179
  }, [isDragging]);
32121
- useEffect46(() => {
32180
+ useEffect47(() => {
32122
32181
  if (dragRef.current?.committed) setDrag(null);
32123
32182
  }, [doc]);
32124
32183
  const beginDrag = useCallback44(
@@ -32723,7 +32782,7 @@ import { getDocPlaybackDuration as getDocPlaybackDuration2, resolveMediaSchedule
32723
32782
  import { DocPlayer } from "@bendyline/squisq-react";
32724
32783
 
32725
32784
  // src/usePreviewProjection.ts
32726
- import { useEffect as useEffect47, useState as useState57 } from "react";
32785
+ import { useEffect as useEffect48, useState as useState58 } from "react";
32727
32786
  import { resolveAudioMapping } from "@bendyline/squisq/doc";
32728
32787
  import { applyTransform } from "@bendyline/squisq/transform";
32729
32788
  function hasEquivalentAudio(left, right) {
@@ -32743,8 +32802,8 @@ function preserveEquivalentAudio(previous, next) {
32743
32802
  };
32744
32803
  }
32745
32804
  function usePreviewProjection(doc, transformStyle, workspaceContainer, documentTitle2) {
32746
- const [projection, setProjection] = useState57(null);
32747
- useEffect47(() => {
32805
+ const [projection, setProjection] = useState58(null);
32806
+ useEffect48(() => {
32748
32807
  if (!doc || !doc.blocks.length) {
32749
32808
  setProjection(null);
32750
32809
  return;
@@ -33004,7 +33063,7 @@ function TimelineToolbar({
33004
33063
  }
33005
33064
 
33006
33065
  // src/PlainHtmlPreview.tsx
33007
- import { useCallback as useCallback45, useEffect as useEffect48, useMemo as useMemo44, useRef as useRef48, useState as useState58 } from "react";
33066
+ import { useCallback as useCallback45, useEffect as useEffect49, useMemo as useMemo44, useRef as useRef48, useState as useState59 } from "react";
33008
33067
  import { parseMarkdown as parseMarkdown9 } from "@bendyline/squisq/markdown";
33009
33068
 
33010
33069
  // src/utils/collectInlineFontAwesomeCss.ts
@@ -33070,10 +33129,13 @@ function PlainHtmlPreview({
33070
33129
  style,
33071
33130
  globalKeyboardShortcuts = false,
33072
33131
  onFrameChange,
33073
- onLinkClick
33132
+ onLinkClick,
33133
+ showCodeCopyButton = false,
33134
+ onCopyCode
33074
33135
  }) {
33075
33136
  const iframeRef = useRef48(null);
33076
33137
  const removeFrameLinkHandlerRef = useRef48(() => void 0);
33138
+ const removeFrameCodeCopyControlsRef = useRef48(() => void 0);
33077
33139
  const setIframeRef = useCallback45(
33078
33140
  (frame) => {
33079
33141
  iframeRef.current = frame;
@@ -33082,8 +33144,8 @@ function PlainHtmlPreview({
33082
33144
  [onFrameChange]
33083
33145
  );
33084
33146
  const mdDoc = useMemo44(() => parseMarkdown9(markdown), [markdown]);
33085
- const [resolvedImages, setResolvedImages] = useState58(null);
33086
- useEffect48(() => {
33147
+ const [resolvedImages, setResolvedImages] = useState59(null);
33148
+ useEffect49(() => {
33087
33149
  if (!mediaProvider) {
33088
33150
  setResolvedImages(null);
33089
33151
  return;
@@ -33119,8 +33181,8 @@ function PlainHtmlPreview({
33119
33181
  return merged;
33120
33182
  }, [resolvedImages, images]);
33121
33183
  const iconsCss = useMemo44(() => collectInlineFontAwesomeCss(), []);
33122
- const [renderFn, setRenderFn] = useState58(() => cachedRender);
33123
- useEffect48(() => {
33184
+ const [renderFn, setRenderFn] = useState59(() => cachedRender);
33185
+ useEffect49(() => {
33124
33186
  if (renderFn) return;
33125
33187
  let cancelled = false;
33126
33188
  loadRenderFn().then((fn) => {
@@ -33153,11 +33215,107 @@ function PlainHtmlPreview({
33153
33215
  frameDocument.addEventListener("click", handleClick, true);
33154
33216
  removeFrameLinkHandlerRef.current = () => frameDocument.removeEventListener("click", handleClick, true);
33155
33217
  }, [onLinkClick]);
33156
- useEffect48(() => {
33218
+ useEffect49(() => {
33157
33219
  installFrameLinkHandler();
33158
33220
  return () => removeFrameLinkHandlerRef.current();
33159
33221
  }, [html, installFrameLinkHandler]);
33160
- useEffect48(() => {
33222
+ const installFrameCodeCopyControls = useCallback45(() => {
33223
+ removeFrameCodeCopyControlsRef.current();
33224
+ removeFrameCodeCopyControlsRef.current = () => void 0;
33225
+ const frameDocument = iframeRef.current?.contentDocument;
33226
+ if (!frameDocument || !showCodeCopyButton) return;
33227
+ const style2 = frameDocument.createElement("style");
33228
+ style2.dataset.squisqCodeCopy = "";
33229
+ style2.textContent = `
33230
+ .squisq-preview-code-copy-frame { position: relative; margin: 1em 0; }
33231
+ .squisq-preview-code-copy-frame > pre { margin: 0; padding-right: 5rem; }
33232
+ .squisq-preview-code-copy {
33233
+ position: absolute; z-index: 1; top: .55rem; right: .55rem;
33234
+ min-width: 3.7rem; padding: .28rem .5rem;
33235
+ border: 1px solid color-mix(in srgb, var(--plain-text) 20%, transparent);
33236
+ border-radius: 5px;
33237
+ background: color-mix(in srgb, var(--plain-bg) 90%, transparent);
33238
+ color: inherit; font: 500 .72rem/1.2 system-ui, sans-serif;
33239
+ cursor: pointer; opacity: .58;
33240
+ transition: opacity 120ms ease, background-color 120ms ease;
33241
+ }
33242
+ .squisq-preview-code-copy-frame:hover > .squisq-preview-code-copy,
33243
+ .squisq-preview-code-copy:focus-visible,
33244
+ .squisq-preview-code-copy[data-copy-state="copied"],
33245
+ .squisq-preview-code-copy[data-copy-state="failed"] { opacity: 1; }
33246
+ .squisq-preview-code-copy:hover { background: var(--plain-bg); }
33247
+ .squisq-preview-code-copy:disabled { cursor: wait; }
33248
+ `;
33249
+ frameDocument.head.append(style2);
33250
+ const cleanups = [];
33251
+ const wrappers = [];
33252
+ const timers = /* @__PURE__ */ new Set();
33253
+ for (const code of frameDocument.querySelectorAll(
33254
+ "pre.squisq-code-block > code"
33255
+ )) {
33256
+ const pre = code.parentElement;
33257
+ if (!pre?.parentElement) continue;
33258
+ const wrapper = frameDocument.createElement("div");
33259
+ wrapper.className = "squisq-preview-code-copy-frame";
33260
+ pre.replaceWith(wrapper);
33261
+ wrapper.append(pre);
33262
+ wrappers.push(wrapper);
33263
+ const button = frameDocument.createElement("button");
33264
+ button.type = "button";
33265
+ button.className = "squisq-preview-code-copy";
33266
+ button.dataset.copyState = "idle";
33267
+ button.textContent = "Copy";
33268
+ button.setAttribute("aria-label", "Copy code to clipboard");
33269
+ wrapper.append(button);
33270
+ const languageClass = Array.from(code.classList).find((name) => name.startsWith("language-"));
33271
+ const language = languageClass?.slice("language-".length) || void 0;
33272
+ const handleCopy = async () => {
33273
+ if (button.dataset.copyState === "copying") return;
33274
+ button.dataset.copyState = "copying";
33275
+ button.textContent = "Copying\u2026";
33276
+ button.disabled = true;
33277
+ try {
33278
+ if (onCopyCode) {
33279
+ await onCopyCode(code.textContent ?? "", language === void 0 ? {} : { language });
33280
+ } else {
33281
+ const clipboard = frameDocument.defaultView?.navigator.clipboard ?? (typeof navigator === "undefined" ? void 0 : navigator.clipboard);
33282
+ if (!clipboard?.writeText) throw new Error("Clipboard access is unavailable");
33283
+ await clipboard.writeText(code.textContent ?? "");
33284
+ }
33285
+ button.dataset.copyState = "copied";
33286
+ button.textContent = "Copied";
33287
+ } catch {
33288
+ button.dataset.copyState = "failed";
33289
+ button.textContent = "Copy failed";
33290
+ } finally {
33291
+ button.disabled = false;
33292
+ const timer = setTimeout(() => {
33293
+ timers.delete(timer);
33294
+ button.dataset.copyState = "idle";
33295
+ button.textContent = "Copy";
33296
+ }, 1600);
33297
+ timers.add(timer);
33298
+ }
33299
+ };
33300
+ button.addEventListener("click", handleCopy);
33301
+ cleanups.push(() => button.removeEventListener("click", handleCopy));
33302
+ }
33303
+ removeFrameCodeCopyControlsRef.current = () => {
33304
+ for (const cleanup of cleanups) cleanup();
33305
+ for (const timer of timers) clearTimeout(timer);
33306
+ timers.clear();
33307
+ for (const wrapper of wrappers) {
33308
+ const pre = wrapper.firstElementChild;
33309
+ if (pre && wrapper.parentElement) wrapper.replaceWith(pre);
33310
+ }
33311
+ style2.remove();
33312
+ };
33313
+ }, [onCopyCode, showCodeCopyButton]);
33314
+ useEffect49(() => {
33315
+ installFrameCodeCopyControls();
33316
+ return () => removeFrameCodeCopyControlsRef.current();
33317
+ }, [html, installFrameCodeCopyControls]);
33318
+ useEffect49(() => {
33161
33319
  if (!globalKeyboardShortcuts) return;
33162
33320
  const handleKeyDown = (event) => {
33163
33321
  if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.key !== "ArrowDown" && event.key !== "ArrowUp") {
@@ -33189,7 +33347,10 @@ function PlainHtmlPreview({
33189
33347
  "data-testid": "plain-html-preview",
33190
33348
  title: title ?? "HTML preview",
33191
33349
  srcDoc: html,
33192
- onLoad: installFrameLinkHandler,
33350
+ onLoad: () => {
33351
+ installFrameLinkHandler();
33352
+ installFrameCodeCopyControls();
33353
+ },
33193
33354
  sandbox: "allow-same-origin",
33194
33355
  style: { ...IFRAME_STYLE, ...style }
33195
33356
  }
@@ -33233,7 +33394,7 @@ function collectImageRefs(doc) {
33233
33394
  }
33234
33395
 
33235
33396
  // src/PreviewPanel.tsx
33236
- import { useState as useState61, useEffect as useEffect52, useMemo as useMemo48, useCallback as useCallback49, useRef as useRef52 } from "react";
33397
+ import { useState as useState62, useEffect as useEffect53, useMemo as useMemo48, useCallback as useCallback49, useRef as useRef52 } from "react";
33237
33398
  import { createPortal as createPortal13 } from "react-dom";
33238
33399
  import { DocPlayer as DocPlayer2, LinearDocView as LinearDocView2, useMediaProvider as useMediaProvider2 } from "@bendyline/squisq-react";
33239
33400
  import { resolveTransformStyle as resolveTransformStyle2 } from "@bendyline/squisq/transform";
@@ -33372,12 +33533,12 @@ import {
33372
33533
  createContext as createContext5,
33373
33534
  useCallback as useCallback46,
33374
33535
  useContext as useContext5,
33375
- useEffect as useEffect49,
33536
+ useEffect as useEffect50,
33376
33537
  useId as useId14,
33377
- useLayoutEffect as useLayoutEffect9,
33538
+ useLayoutEffect as useLayoutEffect10,
33378
33539
  useMemo as useMemo45,
33379
33540
  useRef as useRef49,
33380
- useState as useState59
33541
+ useState as useState60
33381
33542
  } from "react";
33382
33543
  import { createPortal as createPortal12 } from "react-dom";
33383
33544
  import { jsx as jsx69, jsxs as jsxs54 } from "react/jsx-runtime";
@@ -33460,10 +33621,10 @@ function PresentationModeProvider({
33460
33621
  const { activeView, colorScheme, doc } = useEditorContext();
33461
33622
  const { activeTheme } = usePreviewSettings();
33462
33623
  const popupNameId = useId14().replace(/[^a-zA-Z0-9_-]/g, "");
33463
- const [selectedTarget, setSelectedTarget] = useState59("control");
33464
- const [activeTarget, setActiveTarget] = useState59(null);
33465
- const [popupRoot, setPopupRoot] = useState59(null);
33466
- const [error, setError] = useState59(null);
33624
+ const [selectedTarget, setSelectedTarget] = useState60("control");
33625
+ const [activeTarget, setActiveTarget] = useState60(null);
33626
+ const [popupRoot, setPopupRoot] = useState60(null);
33627
+ const [error, setError] = useState60(null);
33467
33628
  const activeTargetRef = useRef49(activeTarget);
33468
33629
  activeTargetRef.current = activeTarget;
33469
33630
  const previousActiveTargetRef = useRef49(null);
@@ -33606,12 +33767,12 @@ function PresentationModeProvider({
33606
33767
  },
33607
33768
  [availableTargets, selectedTarget, stop]
33608
33769
  );
33609
- useEffect49(() => {
33770
+ useEffect50(() => {
33610
33771
  if (availableTargets.includes(selectedTarget)) return;
33611
33772
  setSelectedTarget("control");
33612
33773
  if (activeTargetRef.current !== null) void stop();
33613
33774
  }, [availableTargets, selectedTarget, stop]);
33614
- useEffect49(() => {
33775
+ useEffect50(() => {
33615
33776
  const root = rootRef.current;
33616
33777
  const ownerDocument = root?.ownerDocument;
33617
33778
  if (!root || !ownerDocument) return;
@@ -33623,7 +33784,7 @@ function PresentationModeProvider({
33623
33784
  ownerDocument.addEventListener("fullscreenchange", handleFullscreenChange);
33624
33785
  return () => ownerDocument.removeEventListener("fullscreenchange", handleFullscreenChange);
33625
33786
  }, [rootRef]);
33626
- useEffect49(() => {
33787
+ useEffect50(() => {
33627
33788
  const root = rootRef.current;
33628
33789
  if (!root) return;
33629
33790
  if (activeTarget) root.dataset.presentationMode = activeTarget;
@@ -33632,10 +33793,10 @@ function PresentationModeProvider({
33632
33793
  delete root.dataset.presentationMode;
33633
33794
  };
33634
33795
  }, [activeTarget, rootRef]);
33635
- useEffect49(() => {
33796
+ useEffect50(() => {
33636
33797
  if (activeView !== "preview" && activeTargetRef.current !== null) void stop();
33637
33798
  }, [activeView, stop]);
33638
- useEffect49(() => {
33799
+ useEffect50(() => {
33639
33800
  const previous = previousActiveTargetRef.current;
33640
33801
  previousActiveTargetRef.current = activeTarget;
33641
33802
  if (previous === null || activeTarget !== null) return;
@@ -33643,7 +33804,7 @@ function PresentationModeProvider({
33643
33804
  returnFocusRef.current = null;
33644
33805
  if (target?.isConnected) target.focus();
33645
33806
  }, [activeTarget]);
33646
- useEffect49(() => {
33807
+ useEffect50(() => {
33647
33808
  if (activeTarget !== "control") return;
33648
33809
  const ownerDocument = rootRef.current?.ownerDocument;
33649
33810
  if (!ownerDocument) return;
@@ -33655,12 +33816,12 @@ function PresentationModeProvider({
33655
33816
  ownerDocument.addEventListener("keydown", handleKeyDown);
33656
33817
  return () => ownerDocument.removeEventListener("keydown", handleKeyDown);
33657
33818
  }, [activeTarget, rootRef, stop]);
33658
- useEffect49(() => {
33819
+ useEffect50(() => {
33659
33820
  if (!error) return;
33660
33821
  const timer = window.setTimeout(() => setError(null), 6e3);
33661
33822
  return () => window.clearTimeout(timer);
33662
33823
  }, [error]);
33663
- useEffect49(
33824
+ useEffect50(
33664
33825
  () => () => {
33665
33826
  popupCleanupRef.current?.();
33666
33827
  const popup = popupRef.current;
@@ -33763,8 +33924,8 @@ function PresentationModeControl() {
33763
33924
  const { colorScheme } = useEditorContext();
33764
33925
  const triggerRef = useRef49(null);
33765
33926
  const menuRef = useRef49(null);
33766
- const [open, setOpen] = useState59(false);
33767
- const [anchor, setAnchor] = useState59(null);
33927
+ const [open, setOpen] = useState60(false);
33928
+ const [anchor, setAnchor] = useState60(null);
33768
33929
  const options = PRESENTATION_OPTIONS.filter((option) => availableTargets.includes(option.target));
33769
33930
  const selected = options.find((option) => option.target === selectedTarget) ?? PRESENTATION_OPTIONS[0];
33770
33931
  const updatePosition = useCallback46(() => {
@@ -33789,7 +33950,7 @@ function PresentationModeControl() {
33789
33950
  updatePosition();
33790
33951
  setOpen(true);
33791
33952
  }, [updatePosition]);
33792
- useEffect49(() => {
33953
+ useEffect50(() => {
33793
33954
  if (!open) return;
33794
33955
  const handlePointerDown = (event) => {
33795
33956
  const target = event.target;
@@ -33812,7 +33973,7 @@ function PresentationModeControl() {
33812
33973
  window.removeEventListener("scroll", updatePosition, true);
33813
33974
  };
33814
33975
  }, [closeMenu, open, updatePosition]);
33815
- useLayoutEffect9(() => {
33976
+ useLayoutEffect10(() => {
33816
33977
  if (!open || !anchor) return;
33817
33978
  const checked = menuRef.current?.querySelector('[aria-checked="true"]');
33818
33979
  const first = menuRef.current?.querySelector('[role="menuitemradio"]');
@@ -33932,10 +34093,10 @@ import {
33932
34093
  createContext as createContext6,
33933
34094
  useCallback as useCallback47,
33934
34095
  useContext as useContext6,
33935
- useEffect as useEffect50,
34096
+ useEffect as useEffect51,
33936
34097
  useMemo as useMemo46,
33937
34098
  useRef as useRef50,
33938
- useState as useState60
34099
+ useState as useState61
33939
34100
  } from "react";
33940
34101
  import { jsx as jsx70, jsxs as jsxs55 } from "react/jsx-runtime";
33941
34102
  var PrintModeContext = createContext6(null);
@@ -33950,8 +34111,8 @@ function usePrintModeOptional() {
33950
34111
  function PrintModeProvider({ rootRef, children }) {
33951
34112
  const { activeView } = useEditorContext();
33952
34113
  const { activeTarget: presentationTarget, stop: stopPresentation } = usePresentationMode();
33953
- const [active, setActive] = useState60(false);
33954
- const [slidesPerPage, setSlidesPerPage] = useState60(1);
34114
+ const [active, setActive] = useState61(false);
34115
+ const [slidesPerPage, setSlidesPerPage] = useState61(1);
33955
34116
  const customPrintHandlerRef = useRef50(null);
33956
34117
  const printAncestorsRef = useRef50([]);
33957
34118
  const unmarkPrinting = useCallback47(() => {
@@ -34002,7 +34163,7 @@ function PrintModeProvider({ rootRef, children }) {
34002
34163
  markPrinting();
34003
34164
  ownerWindow.print();
34004
34165
  }, [markPrinting, rootRef]);
34005
- useEffect50(() => {
34166
+ useEffect51(() => {
34006
34167
  const root = rootRef.current;
34007
34168
  if (!root) return;
34008
34169
  if (active) root.dataset.printPreview = "true";
@@ -34011,10 +34172,10 @@ function PrintModeProvider({ rootRef, children }) {
34011
34172
  delete root.dataset.printPreview;
34012
34173
  };
34013
34174
  }, [active, rootRef]);
34014
- useEffect50(() => {
34175
+ useEffect51(() => {
34015
34176
  if (activeView !== "preview" && active) close();
34016
34177
  }, [active, activeView, close]);
34017
- useEffect50(() => {
34178
+ useEffect51(() => {
34018
34179
  if (!active) return;
34019
34180
  const ownerWindow = rootRef.current?.ownerDocument.defaultView;
34020
34181
  const ownerDocument = rootRef.current?.ownerDocument;
@@ -34105,7 +34266,7 @@ function PrintPreviewToolbar() {
34105
34266
  }
34106
34267
 
34107
34268
  // src/print/PrintPreview.tsx
34108
- import { useCallback as useCallback48, useEffect as useEffect51, useMemo as useMemo47, useRef as useRef51 } from "react";
34269
+ import { useCallback as useCallback48, useEffect as useEffect52, useMemo as useMemo47, useRef as useRef51 } from "react";
34109
34270
  import {
34110
34271
  BlockRenderer as BlockRenderer5,
34111
34272
  LinearDocView,
@@ -34156,7 +34317,7 @@ function PrintPreview({
34156
34317
  };
34157
34318
  }, [previewDoc?.startBlock, showCover, theme, viewport]);
34158
34319
  const isTextDocument = displayMode === "page" || displayMode === "narrate";
34159
- useEffect51(() => {
34320
+ useEffect52(() => {
34160
34321
  if (!isTextDocument) return registerPrintHandler(null);
34161
34322
  return registerPrintHandler(() => {
34162
34323
  const frameWindow = documentFrameRef.current?.contentWindow;
@@ -34244,7 +34405,9 @@ function PreviewPanel({
34244
34405
  basePath = "/",
34245
34406
  className,
34246
34407
  workspaceContainer,
34247
- onLinkClick
34408
+ onLinkClick,
34409
+ showCodeCopyButton = false,
34410
+ onCopyCode
34248
34411
  }) {
34249
34412
  const {
34250
34413
  doc,
@@ -34280,7 +34443,7 @@ function PreviewPanel({
34280
34443
  } = usePreviewSettings();
34281
34444
  const mainSurfaceRef = useRef52(null);
34282
34445
  const popupSurfaceRef = useRef52(null);
34283
- const [playbackState, setPlaybackState] = useState61(null);
34446
+ const [playbackState, setPlaybackState] = useState62(null);
34284
34447
  const handlePlaybackStateChange = useCallback49((next) => {
34285
34448
  setPlaybackState(next);
34286
34449
  }, []);
@@ -34318,11 +34481,11 @@ function PreviewPanel({
34318
34481
  const isDocumentMode = activeDisplayMode === "page";
34319
34482
  const isPageMode = activeDisplayMode === "linear";
34320
34483
  const isNarrateMode = activeDisplayMode === "narrate";
34321
- useEffect52(() => {
34484
+ useEffect53(() => {
34322
34485
  if (presentation?.activeTarget === "window") return;
34323
34486
  setPlaybackState(null);
34324
34487
  }, [presentation?.activeTarget]);
34325
- useEffect52(() => {
34488
+ useEffect53(() => {
34326
34489
  if (presentation?.activeTarget !== "window" || !presentation.popupRoot) return;
34327
34490
  const mainRoot = mainSurfaceRef.current;
34328
34491
  const followerRoot = popupSurfaceRef.current;
@@ -34426,7 +34589,9 @@ function PreviewPanel({
34426
34589
  mediaRevision,
34427
34590
  theme: activeTheme,
34428
34591
  globalKeyboardShortcuts: !audience,
34429
- onLinkClick
34592
+ onLinkClick,
34593
+ showCodeCopyButton,
34594
+ onCopyCode
34430
34595
  }
34431
34596
  );
34432
34597
  }
@@ -34460,7 +34625,9 @@ function PreviewPanel({
34460
34625
  theme: activeTheme,
34461
34626
  globalKeyboardShortcuts: !audience,
34462
34627
  showCover: activeCoverSlide,
34463
- transformPage: activeTransformStyle ? resolveTransformStyle2(activeTransformStyle).page : void 0
34628
+ transformPage: activeTransformStyle ? resolveTransformStyle2(activeTransformStyle).page : void 0,
34629
+ showCodeCopyButton,
34630
+ onCopyCode
34464
34631
  }
34465
34632
  );
34466
34633
  }
@@ -34494,6 +34661,8 @@ function PreviewPanel({
34494
34661
  audioController: audience ? followerAudioController : void 0,
34495
34662
  enableSwipe: !audience,
34496
34663
  globalKeyboardShortcuts: !audience,
34664
+ showCodeCopyButton,
34665
+ onCopyCode,
34497
34666
  onPlaybackStateChange: !audience && audienceWindowOpen ? handlePlaybackStateChange : void 0
34498
34667
  },
34499
34668
  `${audience ? "audience" : "primary"}-${activeTransformStyle || "none"}`
@@ -34549,7 +34718,7 @@ function PreviewPanel({
34549
34718
  }
34550
34719
 
34551
34720
  // src/MediaBin.tsx
34552
- import { useState as useState62, useEffect as useEffect53, useRef as useRef53, useCallback as useCallback50 } from "react";
34721
+ import { useState as useState63, useEffect as useEffect54, useRef as useRef53, useCallback as useCallback50 } from "react";
34553
34722
  import { jsx as jsx73, jsxs as jsxs58 } from "react/jsx-runtime";
34554
34723
  function formatSize(bytes) {
34555
34724
  if (bytes < 1024) return `${bytes} B`;
@@ -34614,12 +34783,12 @@ function MediaBin({
34614
34783
  isRecorderOpen = false,
34615
34784
  allowBinaryDownloads = true
34616
34785
  }) {
34617
- const [entries, setEntries] = useState62([]);
34618
- const [thumbUrls, setThumbUrls] = useState62({});
34619
- const [loading, setLoading] = useState62(false);
34620
- const [isDropActive, setIsDropActive] = useState62(false);
34621
- const [downloadingPath, setDownloadingPath] = useState62(null);
34622
- const [contextMenu, setContextMenu] = useState62(null);
34786
+ const [entries, setEntries] = useState63([]);
34787
+ const [thumbUrls, setThumbUrls] = useState63({});
34788
+ const [loading, setLoading] = useState63(false);
34789
+ const [isDropActive, setIsDropActive] = useState63(false);
34790
+ const [downloadingPath, setDownloadingPath] = useState63(null);
34791
+ const [contextMenu, setContextMenu] = useState63(null);
34623
34792
  const fileInputRef = useRef53(null);
34624
34793
  const contextMenuRef = useRef53(null);
34625
34794
  const dropDepthRef = useRef53(0);
@@ -34641,7 +34810,7 @@ function MediaBin({
34641
34810
  },
34642
34811
  [onCountChange]
34643
34812
  );
34644
- useEffect53(() => {
34813
+ useEffect54(() => {
34645
34814
  if (!contextMenu) return;
34646
34815
  const handlePointerDown = (event) => {
34647
34816
  if (contextMenuRef.current?.contains(event.target)) return;
@@ -34662,7 +34831,7 @@ function MediaBin({
34662
34831
  window.removeEventListener("resize", close);
34663
34832
  };
34664
34833
  }, [contextMenu]);
34665
- useEffect53(() => {
34834
+ useEffect54(() => {
34666
34835
  if (!mediaProvider) {
34667
34836
  setEntries([]);
34668
34837
  setThumbUrls({});
@@ -35004,7 +35173,7 @@ ${formatSize(entry.size)}`,
35004
35173
  }
35005
35174
 
35006
35175
  // src/DropZoneOverlay.tsx
35007
- import { useState as useState63 } from "react";
35176
+ import { useState as useState64 } from "react";
35008
35177
  import { Fragment as Fragment21, jsx as jsx74, jsxs as jsxs59 } from "react/jsx-runtime";
35009
35178
  function DropZoneOverlay({
35010
35179
  dragContentType,
@@ -35061,7 +35230,7 @@ function DropZone({
35061
35230
  disabled,
35062
35231
  variant
35063
35232
  }) {
35064
- const [isHovering, setIsHovering] = useState63(false);
35233
+ const [isHovering, setIsHovering] = useState64(false);
35065
35234
  const props = zoneProps(target);
35066
35235
  return /* @__PURE__ */ jsxs59(
35067
35236
  "div",
@@ -35105,7 +35274,7 @@ function DropZone({
35105
35274
  }
35106
35275
 
35107
35276
  // src/Tooltip.tsx
35108
- import { useEffect as useEffect54, useLayoutEffect as useLayoutEffect10, useRef as useRef54, useState as useState64 } from "react";
35277
+ import { useEffect as useEffect55, useLayoutEffect as useLayoutEffect11, useRef as useRef54, useState as useState65 } from "react";
35109
35278
  import { createPortal as createPortal14 } from "react-dom";
35110
35279
 
35111
35280
  // src/tooltipPlacement.ts
@@ -35121,12 +35290,12 @@ function clampTooltipLeft(anchorX, tooltipWidth, viewportWidth, edgePadding = ED
35121
35290
  import { jsx as jsx75 } from "react/jsx-runtime";
35122
35291
  var SHOW_DELAY_MS = 180;
35123
35292
  function TooltipLayer() {
35124
- const [state, setState3] = useState64(null);
35293
+ const [state, setState3] = useState65(null);
35125
35294
  const tooltipRef = useRef54(null);
35126
35295
  const timerRef = useRef54(null);
35127
35296
  const currentTargetRef = useRef54(null);
35128
35297
  const visibleRef = useRef54(false);
35129
- useLayoutEffect10(() => {
35298
+ useLayoutEffect11(() => {
35130
35299
  if (!state) return;
35131
35300
  const node2 = tooltipRef.current;
35132
35301
  if (!node2) return;
@@ -35137,7 +35306,7 @@ function TooltipLayer() {
35137
35306
  node2.style.left = `${left}px`;
35138
35307
  node2.style.visibility = "visible";
35139
35308
  }, [state]);
35140
- useEffect54(() => {
35309
+ useEffect55(() => {
35141
35310
  const clearTimer = () => {
35142
35311
  if (timerRef.current) {
35143
35312
  clearTimeout(timerRef.current);
@@ -35223,7 +35392,7 @@ function TooltipLayer() {
35223
35392
  }
35224
35393
 
35225
35394
  // src/EditorShell.tsx
35226
- import { useEffect as useEffect55, useRef as useRef55, useState as useState65, useCallback as useCallback51, useMemo as useMemo50 } from "react";
35395
+ import { useEffect as useEffect56, useRef as useRef55, useState as useState66, useCallback as useCallback51, useMemo as useMemo50 } from "react";
35227
35396
 
35228
35397
  // src/BlockPreviewPanel.tsx
35229
35398
  import { useMemo as useMemo49 } from "react";
@@ -35515,6 +35684,8 @@ function EditorShell({
35515
35684
  basePath = "/",
35516
35685
  onChange,
35517
35686
  onLinkClick,
35687
+ showCodeCopyButton = false,
35688
+ onCopyCode,
35518
35689
  colorScheme = "light",
35519
35690
  className,
35520
35691
  height = "100vh",
@@ -35528,6 +35699,8 @@ function EditorShell({
35528
35699
  versioningAutoSaveIdleMs,
35529
35700
  onSaveVersion,
35530
35701
  showFilesToggle,
35702
+ showFormattingControls = hostMode !== "chat",
35703
+ showInsertControls = true,
35531
35704
  allowBinaryDownloads = true,
35532
35705
  toolbarSlotLeft,
35533
35706
  toolbarSlotAfterActions,
@@ -35621,6 +35794,8 @@ function EditorShell({
35621
35794
  defaultViewportPreset,
35622
35795
  onChange,
35623
35796
  onLinkClick,
35797
+ showCodeCopyButton,
35798
+ onCopyCode,
35624
35799
  className,
35625
35800
  height,
35626
35801
  minHeight,
@@ -35629,6 +35804,8 @@ function EditorShell({
35629
35804
  mediaProvider: effectiveMediaProvider ?? null,
35630
35805
  workspaceContainer: effectiveContainer,
35631
35806
  filesToggleEnabled,
35807
+ showFormattingControls,
35808
+ showInsertControls,
35632
35809
  allowBinaryDownloads,
35633
35810
  toolbarSlotLeft,
35634
35811
  toolbarSlotAfterActions,
@@ -35691,6 +35868,8 @@ function EditorShellInner({
35691
35868
  defaultViewportPreset,
35692
35869
  onChange,
35693
35870
  onLinkClick,
35871
+ showCodeCopyButton,
35872
+ onCopyCode,
35694
35873
  className,
35695
35874
  height,
35696
35875
  minHeight,
@@ -35699,6 +35878,8 @@ function EditorShellInner({
35699
35878
  mediaProvider,
35700
35879
  workspaceContainer,
35701
35880
  filesToggleEnabled,
35881
+ showFormattingControls,
35882
+ showInsertControls,
35702
35883
  allowBinaryDownloads,
35703
35884
  toolbarSlotLeft,
35704
35885
  toolbarSlotAfterActions,
@@ -35797,13 +35978,13 @@ function EditorShellInner({
35797
35978
  );
35798
35979
  const timelineClock = useTimelineClock(isTimelineMode ? timelineDuration : 0);
35799
35980
  const hasTimelineVideo = timelineVideoSchedule.length > 0;
35800
- const [timelineVideoVisible, setTimelineVideoVisible] = useState65(false);
35801
- const [timelineCompositionVisible, setTimelineCompositionVisible] = useState65(false);
35981
+ const [timelineVideoVisible, setTimelineVideoVisible] = useState66(false);
35982
+ const [timelineCompositionVisible, setTimelineCompositionVisible] = useState66(false);
35802
35983
  const timelinePreviewCount = Number(timelineVideoVisible) + Number(timelineCompositionVisible);
35803
- const [showFiles, setShowFiles] = useState65(false);
35804
- const [mediaRefreshKey, setMediaRefreshKey] = useState65(0);
35805
- const [mediaCount, setMediaCount] = useState65(0);
35806
- const [mediaBinRecorderOpen, setMediaBinRecorderOpen] = useState65(false);
35984
+ const [showFiles, setShowFiles] = useState66(false);
35985
+ const [mediaRefreshKey, setMediaRefreshKey] = useState66(0);
35986
+ const [mediaCount, setMediaCount] = useState66(0);
35987
+ const [mediaBinRecorderOpen, setMediaBinRecorderOpen] = useState66(false);
35807
35988
  const mediaListRefreshKey = mediaRefreshKey + mediaRevision;
35808
35989
  const usedMediaPaths = useMemo50(
35809
35990
  () => collectMediaReferencesFromMarkdown(markdownSource),
@@ -35815,13 +35996,13 @@ function EditorShellInner({
35815
35996
  }
35816
35997
  const imageEditFallbackContainer = imageEditFallbackContainerRef.current;
35817
35998
  const isDark = colorScheme === "dark";
35818
- useEffect55(() => {
35999
+ useEffect56(() => {
35819
36000
  if (!isTimelineMode || !hasTimelineVideo) setTimelineVideoVisible(false);
35820
36001
  }, [isTimelineMode, hasTimelineVideo]);
35821
- useEffect55(() => {
36002
+ useEffect56(() => {
35822
36003
  if (!isTimelineMode || !doc?.blocks.length) setTimelineCompositionVisible(false);
35823
36004
  }, [isTimelineMode, doc]);
35824
- useEffect55(() => {
36005
+ useEffect56(() => {
35825
36006
  if (!mediaProvider) {
35826
36007
  setMediaCount(0);
35827
36008
  return;
@@ -35930,7 +36111,7 @@ ${snippet}` : snippet);
35930
36111
  onDrop: handleFileDrop,
35931
36112
  enabled: !readOnly
35932
36113
  });
35933
- useEffect55(() => {
36114
+ useEffect56(() => {
35934
36115
  onChange?.(markdownSource);
35935
36116
  }, [markdownSource, onChange]);
35936
36117
  const handleShellKeyDown = useCallback51(
@@ -36046,6 +36227,8 @@ ${snippet}` : snippet);
36046
36227
  slotAfterActions: toolbarSlotAfterActions,
36047
36228
  slotRight: toolbarSlotRight,
36048
36229
  showPlayTab,
36230
+ showFormattingControls,
36231
+ showInsertControls,
36049
36232
  hostMode
36050
36233
  }
36051
36234
  ) }),
@@ -36158,7 +36341,9 @@ ${snippet}` : snippet);
36158
36341
  {
36159
36342
  basePath,
36160
36343
  workspaceContainer,
36161
- onLinkClick
36344
+ onLinkClick,
36345
+ showCodeCopyButton,
36346
+ onCopyCode
36162
36347
  }
36163
36348
  )
36164
36349
  ]
@@ -36292,9 +36477,9 @@ function ImageEditModal({
36292
36477
  const parent = container ?? new MemoryContentContainer();
36293
36478
  return scopeContainer(parent, `.imageEdits/${scopedName}`);
36294
36479
  }, [container, relativePath]);
36295
- const [initialSrc, setInitialSrc] = useState65(null);
36296
- const [resolveError, setResolveError] = useState65(null);
36297
- useEffect55(() => {
36480
+ const [initialSrc, setInitialSrc] = useState66(null);
36481
+ const [resolveError, setResolveError] = useState66(null);
36482
+ useEffect56(() => {
36298
36483
  let cancelled = false;
36299
36484
  setInitialSrc(null);
36300
36485
  setResolveError(null);