@bendyline/squisq-editor-react 2.11.26 → 2.12.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.
package/NOTICE.md CHANGED
@@ -25,6 +25,7 @@ Third-party components remain under their respective license terms.
25
25
  | @tiptap/react | 2.27.3 | MIT | https://github.com/ueberdosis/tiptap |
26
26
  | @tiptap/starter-kit | 2.27.3 | MIT | https://github.com/ueberdosis/tiptap |
27
27
  | @tiptap/suggestion | 2.27.3 | MIT | https://github.com/ueberdosis/tiptap |
28
+ | lucide-react | 1.52.0 | ISC | https://github.com/lucide-icons/lucide |
28
29
  | mermaid | 11.17.2 | MIT | https://github.com/mermaid-js/mermaid |
29
30
  | type-fest | 4.41.0 | MIT OR CC0-1.0 | https://github.com/sindresorhus/type-fest |
30
31
  | @monaco-editor/react _(bundled)_ | 4.7.0 | MIT | https://github.com/suren-atoyan/monaco-react |
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RecorderModal
3
- } from "./chunk-UFK47ZAI.js";
3
+ } from "./chunk-IT3NPQ4J.js";
4
4
 
5
5
  // src/recorder/RecorderButton.tsx
6
6
  import { useCallback, useState } from "react";
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  markdownToTiptap,
7
7
  tiptapToMarkdown
8
- } from "./chunk-HSWWEWT4.js";
8
+ } from "./chunk-WMNL2WEI.js";
9
9
 
10
10
  // src/InsertMenuItems.tsx
11
11
  import { createContext, useContext, useEffect, useMemo, useRef } from "react";
@@ -2946,6 +2946,7 @@ export {
2946
2946
  detectLanguageFromFileName,
2947
2947
  resolveFileKind,
2948
2948
  useBlockNavigator,
2949
+ mediaKindFromUrl,
2949
2950
  collectEmbeddedMedia,
2950
2951
  resolveEmbeddedMediaTiming,
2951
2952
  collectEmbeddedVideoSchedule,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Icon
3
- } from "./chunk-GS7QWYFT.js";
3
+ } from "./chunk-UYB5CPRE.js";
4
4
 
5
5
  // src/ImageViewer.tsx
6
6
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-LRZJD5YD.js";
9
9
  import {
10
10
  Icon
11
- } from "./chunk-GS7QWYFT.js";
11
+ } from "./chunk-UYB5CPRE.js";
12
12
  import {
13
13
  NarrationStage,
14
14
  buildTimingJson,
@@ -1851,7 +1851,9 @@ function resolveSlideVisual(doc, block, slide, theme, viewport, index, totalBloc
1851
1851
  }
1852
1852
 
1853
1853
  // src/recorder/slides/RecorderSlidesPanel.tsx
1854
- import { useCallback as useCallback3 } from "react";
1854
+ import { useBuildNavigation } from "@bendyline/squisq-react";
1855
+ import { blockBuildSteps, buildSourceText } from "@bendyline/squisq/doc";
1856
+ import { useCallback as useCallback3, useEffect as useEffect4 } from "react";
1855
1857
 
1856
1858
  // src/TimelineBlockPreview.tsx
1857
1859
  import { memo } from "react";
@@ -1860,11 +1862,21 @@ import { BlockRenderer, MediaContext } from "@bendyline/squisq-react";
1860
1862
  import { jsx as jsx3 } from "react/jsx-runtime";
1861
1863
  var BlockThumbnail = memo(function BlockThumbnail2({
1862
1864
  visual,
1865
+ buildPlayback = "all",
1863
1866
  viewport = VIEWPORT_PRESETS.landscape,
1864
1867
  basePath = "/",
1865
1868
  mediaProvider = null
1866
1869
  }) {
1867
- return /* @__PURE__ */ jsx3(MediaContext.Provider, { value: mediaProvider, children: /* @__PURE__ */ jsx3(BlockRenderer, { block: visual, blockTime: 0, basePath, viewport }) });
1870
+ return /* @__PURE__ */ jsx3(MediaContext.Provider, { value: mediaProvider, children: /* @__PURE__ */ jsx3(
1871
+ BlockRenderer,
1872
+ {
1873
+ buildPlayback,
1874
+ block: visual,
1875
+ blockTime: 0,
1876
+ basePath,
1877
+ viewport
1878
+ }
1879
+ ) });
1868
1880
  });
1869
1881
 
1870
1882
  // src/recorder/slides/RecorderSlidesPanel.tsx
@@ -1949,13 +1961,28 @@ function RecorderSlidesPanel({
1949
1961
  basePath = "/",
1950
1962
  mediaProvider = null,
1951
1963
  recording = false,
1952
- shownBlockIds
1964
+ shownBlockIds,
1965
+ onBuildShown
1953
1966
  }) {
1954
1967
  const count = slides.length;
1955
1968
  const active = slides[clampSlideIndex(index, count)];
1969
+ const builds = useBuildNavigation(active?.visual, true, true);
1970
+ const { next: nextBuild, previous: previousBuild, enter: enterBuild } = builds;
1971
+ const sourceText = active?.visual ? buildSourceText(blockBuildSteps(active.visual)) : "";
1972
+ useEffect4(() => {
1973
+ if (!active || !onBuildShown) return;
1974
+ for (let step2 = 0; step2 < builds.cursor.revealed; step2++)
1975
+ onBuildShown(active.blockId, step2, sourceText);
1976
+ }, [active, builds.cursor.revealed, sourceText, recording, onBuildShown]);
1956
1977
  const step = useCallback3(
1957
- (delta) => onIndexChange(clampSlideIndex(index + delta, count)),
1958
- [index, count, onIndexChange]
1978
+ (delta) => {
1979
+ if (delta > 0 ? nextBuild() : previousBuild()) return;
1980
+ const next = clampSlideIndex(index + delta, count);
1981
+ if (next === index) return;
1982
+ enterBuild(slides[next]?.visual, delta < 0);
1983
+ onIndexChange(next);
1984
+ },
1985
+ [index, count, onIndexChange, nextBuild, previousBuild, enterBuild, slides]
1959
1986
  );
1960
1987
  const handleKeyDown = useCallback3(
1961
1988
  (event) => {
@@ -1985,7 +2012,8 @@ function RecorderSlidesPanel({
1985
2012
  "Slide ",
1986
2013
  index + 1,
1987
2014
  " of ",
1988
- count
2015
+ count,
2016
+ builds.count > 0 ? ` \xB7 Build ${builds.cursor.revealed} of ${builds.count}` : ""
1989
2017
  ] }),
1990
2018
  /* @__PURE__ */ jsx4("span", { style: dotsStyle, "aria-hidden": "true", children: slides.map((slide, i) => /* @__PURE__ */ jsx4(
1991
2019
  "span",
@@ -2005,7 +2033,7 @@ function RecorderSlidesPanel({
2005
2033
  type: "button",
2006
2034
  style: navButtonStyle,
2007
2035
  onClick: () => step(-1),
2008
- disabled: index <= 0,
2036
+ disabled: index <= 0 && builds.cursor.revealed === 0,
2009
2037
  "aria-label": "Previous slide",
2010
2038
  children: /* @__PURE__ */ jsx4(Icon, { icon: "fa-solid fa-chevron-left" })
2011
2039
  }
@@ -2016,7 +2044,7 @@ function RecorderSlidesPanel({
2016
2044
  type: "button",
2017
2045
  style: navButtonStyle,
2018
2046
  onClick: () => step(1),
2019
- disabled: index >= count - 1,
2047
+ disabled: index >= count - 1 && builds.cursor.revealed >= builds.count && builds.cursor.progress >= 1,
2020
2048
  "aria-label": "Next slide",
2021
2049
  children: /* @__PURE__ */ jsx4(Icon, { icon: "fa-solid fa-chevron-right" })
2022
2050
  }
@@ -2036,6 +2064,7 @@ function RecorderSlidesPanel({
2036
2064
  BlockThumbnail,
2037
2065
  {
2038
2066
  visual: active.visual,
2067
+ buildPlayback: builds.cursor,
2039
2068
  viewport,
2040
2069
  basePath,
2041
2070
  mediaProvider
@@ -2074,10 +2103,11 @@ function RecorderSlidesPanel({
2074
2103
  }
2075
2104
 
2076
2105
  // src/recorder/RecorderModal.tsx
2106
+ import { recordBuildAdvance } from "@bendyline/squisq/schemas";
2077
2107
  import {
2078
2108
  Fragment as Fragment2,
2079
2109
  useCallback as useCallback4,
2080
- useEffect as useEffect4,
2110
+ useEffect as useEffect5,
2081
2111
  useId as useId2,
2082
2112
  useMemo,
2083
2113
  useRef as useRef2,
@@ -2708,6 +2738,19 @@ function RecorderModal({
2708
2738
  const slidesAvailable = slideDeck.length > 0;
2709
2739
  const slidesCaptureTimings = Boolean(slides?.captureTimings && slides?.doc);
2710
2740
  const [slideIndex, setSlideIndex] = useState3(0);
2741
+ const [buildLog, setBuildLog] = useState3([]);
2742
+ const buildLogRef = useRef2(buildLog);
2743
+ buildLogRef.current = buildLog;
2744
+ const handleBuildShown = useCallback4(
2745
+ (blockId, step, sourceText2) => {
2746
+ const elapsed = recorder.getElapsedMs();
2747
+ if (elapsed !== null)
2748
+ setBuildLog(
2749
+ (log) => recordBuildAdvance(log, { blockId, step, sourceText: sourceText2, time: elapsed / 1e3 })
2750
+ );
2751
+ },
2752
+ [recorder]
2753
+ );
2711
2754
  const [advanceLog, setAdvanceLog] = useState3(EMPTY_ADVANCE_LOG);
2712
2755
  const [applySlideTimings, setApplySlideTimings] = useState3(true);
2713
2756
  const slideIndexRef = useRef2(slideIndex);
@@ -2732,11 +2775,12 @@ function RecorderModal({
2732
2775
  [recorder]
2733
2776
  );
2734
2777
  const slideDeckKey = slideDeck.map((slide) => slide.blockId).join("\0");
2735
- useEffect4(() => {
2778
+ useEffect5(() => {
2736
2779
  setAdvanceLog(EMPTY_ADVANCE_LOG);
2780
+ setBuildLog([]);
2737
2781
  setSlideIndex(0);
2738
2782
  }, [slidesOn, slideDeckKey]);
2739
- useEffect4(() => {
2783
+ useEffect5(() => {
2740
2784
  if (!narrationOn || narrationRecording) return;
2741
2785
  const controller = stageRef.current.controller;
2742
2786
  if (recorder.state === "recording") controller.play();
@@ -2754,7 +2798,7 @@ function RecorderModal({
2754
2798
  [narrationRecording]
2755
2799
  );
2756
2800
  const narrationMicDeviceId = stage.controller.prefs.micDeviceId ?? "";
2757
- useEffect4(() => {
2801
+ useEffect5(() => {
2758
2802
  if (!narrationRecording) return;
2759
2803
  setDeviceSettings(
2760
2804
  (current) => current.audio.deviceId === narrationMicDeviceId ? current : {
@@ -2764,7 +2808,7 @@ function RecorderModal({
2764
2808
  );
2765
2809
  }, [narrationMicDeviceId, narrationRecording]);
2766
2810
  const prevNarrationOnRef = useRef2(narrationOn);
2767
- useEffect4(() => {
2811
+ useEffect5(() => {
2768
2812
  const was = prevNarrationOnRef.current;
2769
2813
  prevNarrationOnRef.current = narrationOn;
2770
2814
  if (was === narrationOn) return;
@@ -2779,7 +2823,7 @@ function RecorderModal({
2779
2823
  if (s.float.isOpen) s.float.close();
2780
2824
  }
2781
2825
  }, [narrationOn, narrationRecording, recorder]);
2782
- useEffect4(() => {
2826
+ useEffect5(() => {
2783
2827
  if (!narrationOn) return;
2784
2828
  const onKey = (event) => {
2785
2829
  if (event.key !== "Escape") return;
@@ -2792,7 +2836,7 @@ function RecorderModal({
2792
2836
  const narrationMicLive = stage.controller.mic.status === "live";
2793
2837
  const narrationPreviewWanted = narrationRecording && stage.recorder.withCamera && narrationMicLive && stage.recorder.cameraStream === null && stage.recorder.state !== "processing" && stage.recorder.state !== "review" && stage.recorder.state !== "saving";
2794
2838
  const narrationPreviewRef = useRef2(null);
2795
- useEffect4(() => {
2839
+ useEffect5(() => {
2796
2840
  const existing = narrationPreviewRef.current;
2797
2841
  if (existing) {
2798
2842
  for (const track of existing.getTracks()) track.stop();
@@ -2843,7 +2887,7 @@ function RecorderModal({
2843
2887
  const activePrimaryStream = narrationRecording ? stage.controller.mic.stream : recorder.stream;
2844
2888
  const activeCameraStream = narrationRecording ? narrationCameraStream : recorder.camera?.stream ?? null;
2845
2889
  const { devices: mediaDevices, refresh: refreshMediaDevices } = useMediaDevices();
2846
- useEffect4(() => {
2890
+ useEffect5(() => {
2847
2891
  if (activePrimaryStream || activeCameraStream) refreshMediaDevices();
2848
2892
  }, [activeCameraStream, activePrimaryStream, refreshMediaDevices]);
2849
2893
  useStreamPreview(
@@ -2854,7 +2898,7 @@ function RecorderModal({
2854
2898
  cameraPreviewRef,
2855
2899
  !narrationRecording && isDual && recorder.state !== "stopped" ? recorder.camera?.stream ?? null : null
2856
2900
  );
2857
- useEffect4(() => {
2901
+ useEffect5(() => {
2858
2902
  setPlaybackPositionMs(0);
2859
2903
  if (!recorder.blob) {
2860
2904
  setPlaybackUrl(null);
@@ -2867,7 +2911,7 @@ function RecorderModal({
2867
2911
  };
2868
2912
  }, [recorder.blob]);
2869
2913
  const cameraBlob = recorder.camera?.blob ?? null;
2870
- useEffect4(() => {
2914
+ useEffect5(() => {
2871
2915
  if (!cameraBlob) {
2872
2916
  setCameraPlaybackUrl(null);
2873
2917
  return;
@@ -2881,7 +2925,7 @@ function RecorderModal({
2881
2925
  const captureKey = `${source}:${cameraOn ? micOn : ""}:${includeSystemAudio}:${JSON.stringify(deviceSettings)}`;
2882
2926
  const previousKeyRef = useRef2(captureKey);
2883
2927
  const dualSaveProgressRef = useRef2({});
2884
- useEffect4(() => {
2928
+ useEffect5(() => {
2885
2929
  if (previousKeyRef.current !== captureKey) {
2886
2930
  previousKeyRef.current = captureKey;
2887
2931
  dualSaveProgressRef.current = {};
@@ -2937,6 +2981,7 @@ function RecorderModal({
2937
2981
  setCountingIn(false);
2938
2982
  if (!rolled) return;
2939
2983
  }
2984
+ setBuildLog([]);
2940
2985
  recorder.start();
2941
2986
  const blockId = slideDeckRef.current[slideIndexRef.current]?.blockId;
2942
2987
  setAdvanceLog(blockId ? recordSlideShown(EMPTY_ADVANCE_LOG, blockId, 0) : EMPTY_ADVANCE_LOG);
@@ -2955,7 +3000,10 @@ function RecorderModal({
2955
3000
  if (!slidesOn || !slidesCaptureTimings || !applySlideTimings || !doc) return null;
2956
3001
  const log = advanceLogRef.current;
2957
3002
  if (log.length === 0) return null;
2958
- const timing = buildAdvanceTimingJson(doc, log, durationSec);
3003
+ const timing = {
3004
+ ...buildAdvanceTimingJson(doc, log, durationSec),
3005
+ builds: buildLogRef.current
3006
+ };
2959
3007
  const encoded = encodeNarrationTimingJson(timing);
2960
3008
  const sidecarPath = timingPathFor(mediaRelativePath);
2961
3009
  if (container) {
@@ -3167,13 +3215,16 @@ function RecorderModal({
3167
3215
  [source, recorder.extension, recorder.blob, isDual, basename, filenameSeed]
3168
3216
  );
3169
3217
  const timingDownloadFilename = `${downloadFilename}.timing.json`;
3170
- useEffect4(() => {
3218
+ useEffect5(() => {
3171
3219
  if (narrationRecording || !canSave) {
3172
3220
  setTimingDownloadUrl(null);
3173
3221
  return;
3174
3222
  }
3175
3223
  const duration = recorder.durationMs / 1e3;
3176
- const encoded = slidesOn && slidesCaptureTimings && applySlideTimings && slides?.doc && advanceLog.length > 0 ? encodeNarrationTimingJson(buildAdvanceTimingJson(slides.doc, advanceLog, duration)) : source === "mic" ? encodeTimingJson(buildTimingJson(sourceText, duration)) : null;
3224
+ const encoded = slidesOn && slidesCaptureTimings && applySlideTimings && slides?.doc && advanceLog.length > 0 ? encodeNarrationTimingJson({
3225
+ ...buildAdvanceTimingJson(slides.doc, advanceLog, duration),
3226
+ builds: buildLog
3227
+ }) : source === "mic" ? encodeTimingJson(buildTimingJson(sourceText, duration)) : null;
3177
3228
  if (encoded === null) {
3178
3229
  setTimingDownloadUrl(null);
3179
3230
  return;
@@ -3187,6 +3238,7 @@ function RecorderModal({
3187
3238
  narrationRecording,
3188
3239
  canSave,
3189
3240
  recorder.durationMs,
3241
+ buildLog,
3190
3242
  slidesOn,
3191
3243
  slidesCaptureTimings,
3192
3244
  applySlideTimings,
@@ -3764,6 +3816,7 @@ function RecorderModal({
3764
3816
  slides: slideDeck,
3765
3817
  index: slideIndex,
3766
3818
  onIndexChange: handleSlideIndexChange,
3819
+ onBuildShown: handleBuildShown,
3767
3820
  viewport: slidesViewport,
3768
3821
  basePath: slides?.basePath,
3769
3822
  mediaProvider: slides?.mediaProvider ?? mediaProvider,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MARKDOWN_ESCAPE_ATTR
3
- } from "./chunk-HSWWEWT4.js";
3
+ } from "./chunk-WMNL2WEI.js";
4
4
 
5
5
  // src/MarkdownEscape.ts
6
6
  import { Mark, mergeAttributes } from "@tiptap/core";
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  MarkdownEscape
3
- } from "./chunk-OCQEWWHI.js";
3
+ } from "./chunk-KI5XQWRU.js";
4
4
  import {
5
5
  markdownToTiptap,
6
6
  tiptapToMarkdown
7
- } from "./chunk-HSWWEWT4.js";
7
+ } from "./chunk-WMNL2WEI.js";
8
8
 
9
9
  // src/jsonEditor/JsonEditorContext.tsx
10
10
  import { createContext, useContext, useEffect, useMemo } from "react";