@bendyline/squisq-editor-react 2.7.3 → 2.9.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.
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  markdownToTiptap,
8
8
  tiptapToMarkdown
9
- } from "./chunk-K2WJYVS4.js";
9
+ } from "./chunk-TLRB3IQC.js";
10
10
  import {
11
11
  ImageEditor,
12
12
  ImageViewer,
@@ -384,6 +384,8 @@ function EditorProvider({
384
384
  allowNarrate = true,
385
385
  preserveSourceWrapping = true,
386
386
  fileName,
387
+ saveCoverImageOutput,
388
+ saveDashboardImageOutput,
387
389
  language,
388
390
  findMode: controlledFindMode,
389
391
  onFindModeChange,
@@ -869,6 +871,8 @@ function EditorProvider({
869
871
  fenceRenderers,
870
872
  linkSchemes,
871
873
  fileName,
874
+ saveCoverImageOutput,
875
+ saveDashboardImageOutput,
872
876
  setMarkdownSource,
873
877
  setEditorSource,
874
878
  setLayoutMode,
@@ -931,6 +935,8 @@ function EditorProvider({
931
935
  fenceRenderers,
932
936
  linkSchemes,
933
937
  fileName,
938
+ saveCoverImageOutput,
939
+ saveDashboardImageOutput,
934
940
  setMarkdownSource,
935
941
  setEditorSource,
936
942
  setLayoutMode,
@@ -3289,9 +3295,16 @@ import {
3289
3295
  COVER_SLIDE_TEMPLATE_OPTIONS,
3290
3296
  createTemplateContext,
3291
3297
  expandCoverBlock,
3292
- resolveCoverSlideSettings
3298
+ resolveCoverSlideSettings,
3299
+ DASHBOARD_AUTO_LAYOUT_ID,
3300
+ DASHBOARD_STYLES,
3301
+ listDashboardLayouts,
3302
+ resolveDashboardStyleId
3293
3303
  } from "@bendyline/squisq/doc";
3294
3304
  import { CoverImageExportModal } from "@bendyline/squisq-video-react/cover-image";
3305
+ import {
3306
+ DashboardImageExportModal
3307
+ } from "@bendyline/squisq-video-react/dashboard-image";
3295
3308
 
3296
3309
  // src/transformStyleId.ts
3297
3310
  import { getTransformStyleSummaries } from "@bendyline/squisq/transform";
@@ -3304,7 +3317,12 @@ function resolvePersistedTransformStyleId(value) {
3304
3317
  }
3305
3318
 
3306
3319
  // src/frontmatterSettings.ts
3307
- import { COVER_SLIDE_FRONTMATTER_KEYS, DEFAULT_COVER_SLIDE_SETTINGS } from "@bendyline/squisq/doc";
3320
+ import {
3321
+ COVER_SLIDE_FRONTMATTER_KEYS,
3322
+ DASHBOARD_FRONTMATTER_KEYS,
3323
+ DEFAULT_COVER_SLIDE_SETTINGS,
3324
+ DEFAULT_DASHBOARD_SETTINGS
3325
+ } from "@bendyline/squisq/doc";
3308
3326
  var FRONTMATTER_SETTING_KEYS = {
3309
3327
  theme: { canonical: "squisq-theme", legacy: ["themeId", "theme"] },
3310
3328
  transform: { canonical: "squisq-transform", legacy: "transform-style" },
@@ -3320,7 +3338,10 @@ var FRONTMATTER_SETTING_KEYS = {
3320
3338
  },
3321
3339
  pipSize: { canonical: "squisq-pip-size", legacy: "pip-size" },
3322
3340
  pipShape: { canonical: "squisq-pip-shape", legacy: "pip-shape" },
3323
- pipPosition: { canonical: "squisq-pip-position", legacy: "pip-position" }
3341
+ pipPosition: { canonical: "squisq-pip-position", legacy: "pip-position" },
3342
+ dashboardLayout: DASHBOARD_FRONTMATTER_KEYS.layout,
3343
+ dashboardTitle: DASHBOARD_FRONTMATTER_KEYS.showTitle,
3344
+ dashboardStyle: DASHBOARD_FRONTMATTER_KEYS.style
3324
3345
  };
3325
3346
  var FRONTMATTER_SETTING_DEFAULTS = {
3326
3347
  theme: "standard",
@@ -3334,7 +3355,10 @@ var FRONTMATTER_SETTING_DEFAULTS = {
3334
3355
  videoPresentation: "background",
3335
3356
  pipSize: "small",
3336
3357
  pipShape: "square",
3337
- pipPosition: "bottom-right"
3358
+ pipPosition: "bottom-right",
3359
+ dashboardLayout: DEFAULT_DASHBOARD_SETTINGS.layout,
3360
+ dashboardTitle: DEFAULT_DASHBOARD_SETTINGS.showTitle,
3361
+ dashboardStyle: DEFAULT_DASHBOARD_SETTINGS.style
3338
3362
  };
3339
3363
  function omitFrontmatterDefault(value, defaultValue) {
3340
3364
  return value === defaultValue ? null : value;
@@ -3375,6 +3399,10 @@ function resolveDisplayMode(value) {
3375
3399
  if (v2 === "video" || v2 === "slideshow" || v2 === "linear" || v2 === "narrate") return v2;
3376
3400
  if (v2 === "slides" || v2 === "presentation" || v2 === "deck") return "slideshow";
3377
3401
  if (v2 === "teleprompter" || v2 === "prompter") return "narrate";
3402
+ if (v2 === "dashboard" || v2 === "dash") return "dashboard";
3403
+ if (v2 === "flashcards" || v2 === "flashcard" || v2 === "study" || v2 === "quiz") {
3404
+ return "flashcards";
3405
+ }
3378
3406
  if (v2 === "page" || v2 === "paged") return "linear";
3379
3407
  if (v2 === "document" || v2 === "scroll" || v2 === "html" || v2 === "plain" || v2 === "reader") {
3380
3408
  return "page";
@@ -3451,6 +3479,13 @@ function resolveFrontmatterBoolean(value) {
3451
3479
  if (v2 === "false" || v2 === "no" || v2 === "off" || v2 === "hide" || v2 === "hidden") return false;
3452
3480
  return null;
3453
3481
  }
3482
+ function resolveDashboardLayoutId(value) {
3483
+ if (typeof value !== "string") return null;
3484
+ const normalized = value.trim().toLowerCase();
3485
+ if (!normalized) return null;
3486
+ if (normalized === "auto" || normalized === "default") return DASHBOARD_AUTO_LAYOUT_ID;
3487
+ return normalized;
3488
+ }
3454
3489
  function readFrontmatterKey(fm, canonical, legacy) {
3455
3490
  if (!fm) return void 0;
3456
3491
  return Object.prototype.hasOwnProperty.call(fm, canonical) ? fm[canonical] : fm[legacy];
@@ -3499,7 +3534,7 @@ function PreviewSettingsProvider({
3499
3534
  );
3500
3535
  const [selectedPreset, setSelectedPreset] = useState9(null);
3501
3536
  useEffect8(() => setSelectedPreset(null), [fmPreset]);
3502
- const playbackDefaultPreset = activeDisplayMode === "slideshow" || activeDisplayMode === "video" ? defaultViewportPreset : "landscape";
3537
+ const playbackDefaultPreset = activeDisplayMode === "slideshow" || activeDisplayMode === "video" || activeDisplayMode === "dashboard" ? defaultViewportPreset : "landscape";
3503
3538
  const activePreset = selectedPreset ?? fmPreset ?? playbackDefaultPreset;
3504
3539
  const activeViewport = VIEWPORT_PRESETS[activePreset];
3505
3540
  const custom = useCustomThemes();
@@ -3879,6 +3914,92 @@ function PreviewSettingsProvider({
3879
3914
  } : null,
3880
3915
  [designer.open, designer.editing, handleDesignerSave, closeThemeDesigner]
3881
3916
  );
3917
+ const fmDashboardLayout = useMemo8(
3918
+ () => resolveDashboardLayoutId(
3919
+ readFrontmatterKey(
3920
+ frontmatter,
3921
+ FRONTMATTER_SETTING_KEYS.dashboardLayout.canonical,
3922
+ FRONTMATTER_SETTING_KEYS.dashboardLayout.legacy
3923
+ )
3924
+ ),
3925
+ [frontmatter]
3926
+ );
3927
+ const [selectedDashboardLayout, setSelectedDashboardLayout] = useState9(null);
3928
+ useEffect8(() => setSelectedDashboardLayout(null), [fmDashboardLayout]);
3929
+ const activeDashboardLayout = selectedDashboardLayout ?? fmDashboardLayout ?? FRONTMATTER_SETTING_DEFAULTS.dashboardLayout;
3930
+ const handleSetDashboardLayout = useCallback9(
3931
+ (id) => {
3932
+ const next = resolveDashboardLayoutId(id) ?? FRONTMATTER_SETTING_DEFAULTS.dashboardLayout;
3933
+ setSelectedDashboardLayout(next);
3934
+ persistFrontmatter({
3935
+ [FRONTMATTER_SETTING_KEYS.dashboardLayout.canonical]: omitFrontmatterDefault(
3936
+ next,
3937
+ FRONTMATTER_SETTING_DEFAULTS.dashboardLayout
3938
+ ),
3939
+ [FRONTMATTER_SETTING_KEYS.dashboardLayout.legacy]: null
3940
+ });
3941
+ },
3942
+ [persistFrontmatter]
3943
+ );
3944
+ const fmDashboardTitle = useMemo8(
3945
+ () => resolveFrontmatterBoolean(
3946
+ readFrontmatterKey(
3947
+ frontmatter,
3948
+ FRONTMATTER_SETTING_KEYS.dashboardTitle.canonical,
3949
+ FRONTMATTER_SETTING_KEYS.dashboardTitle.legacy
3950
+ )
3951
+ ),
3952
+ [frontmatter]
3953
+ );
3954
+ const [selectedDashboardTitle, setSelectedDashboardTitle] = useState9(null);
3955
+ useEffect8(() => setSelectedDashboardTitle(null), [fmDashboardTitle]);
3956
+ const activeDashboardTitle = selectedDashboardTitle ?? fmDashboardTitle ?? FRONTMATTER_SETTING_DEFAULTS.dashboardTitle;
3957
+ const handleSetDashboardTitleEnabled = useCallback9(
3958
+ (enabled) => {
3959
+ setSelectedDashboardTitle(enabled);
3960
+ persistFrontmatter({
3961
+ [FRONTMATTER_SETTING_KEYS.dashboardTitle.canonical]: omitFrontmatterDefault(
3962
+ enabled,
3963
+ FRONTMATTER_SETTING_DEFAULTS.dashboardTitle
3964
+ ),
3965
+ [FRONTMATTER_SETTING_KEYS.dashboardTitle.legacy]: null
3966
+ });
3967
+ },
3968
+ [persistFrontmatter]
3969
+ );
3970
+ const fmDashboardStyle = useMemo8(
3971
+ () => resolveDashboardStyleId(
3972
+ readFrontmatterKey(
3973
+ frontmatter,
3974
+ FRONTMATTER_SETTING_KEYS.dashboardStyle.canonical,
3975
+ FRONTMATTER_SETTING_KEYS.dashboardStyle.legacy
3976
+ )
3977
+ ),
3978
+ [frontmatter]
3979
+ );
3980
+ const [selectedDashboardStyle, setSelectedDashboardStyle] = useState9(
3981
+ null
3982
+ );
3983
+ useEffect8(() => setSelectedDashboardStyle(null), [fmDashboardStyle]);
3984
+ const activeDashboardStyle = selectedDashboardStyle ?? fmDashboardStyle ?? FRONTMATTER_SETTING_DEFAULTS.dashboardStyle;
3985
+ const handleSetDashboardStyle = useCallback9(
3986
+ (id) => {
3987
+ const next = resolveDashboardStyleId(id) ?? FRONTMATTER_SETTING_DEFAULTS.dashboardStyle;
3988
+ setSelectedDashboardStyle(next);
3989
+ persistFrontmatter({
3990
+ [FRONTMATTER_SETTING_KEYS.dashboardStyle.canonical]: omitFrontmatterDefault(
3991
+ next,
3992
+ FRONTMATTER_SETTING_DEFAULTS.dashboardStyle
3993
+ ),
3994
+ [FRONTMATTER_SETTING_KEYS.dashboardStyle.legacy]: null
3995
+ });
3996
+ },
3997
+ [persistFrontmatter]
3998
+ );
3999
+ const dashboardLayoutOptions = useMemo8(
4000
+ () => listDashboardLayouts({ frontmatter }),
4001
+ [frontmatter]
4002
+ );
3882
4003
  const value = useMemo8(
3883
4004
  () => ({
3884
4005
  activePreset,
@@ -3905,6 +4026,13 @@ function PreviewSettingsProvider({
3905
4026
  setPipPosition: handlePipPosition,
3906
4027
  activeVideoLoop,
3907
4028
  setVideoLoopEnabled: handleSetVideoLoopEnabled,
4029
+ activeDashboardLayout,
4030
+ setDashboardLayout: handleSetDashboardLayout,
4031
+ activeDashboardTitle,
4032
+ setDashboardTitleEnabled: handleSetDashboardTitleEnabled,
4033
+ activeDashboardStyle,
4034
+ setDashboardStyle: handleSetDashboardStyle,
4035
+ dashboardLayoutOptions,
3908
4036
  activeCoverSlide,
3909
4037
  setCoverSlideEnabled: handleSetCoverSlideEnabled,
3910
4038
  activeCoverSlideTemplate,
@@ -3934,6 +4062,10 @@ function PreviewSettingsProvider({
3934
4062
  activePipShape,
3935
4063
  activePipPosition,
3936
4064
  activeVideoLoop,
4065
+ activeDashboardLayout,
4066
+ activeDashboardTitle,
4067
+ activeDashboardStyle,
4068
+ dashboardLayoutOptions,
3937
4069
  activeCoverSlide,
3938
4070
  activeCoverSlideTemplate,
3939
4071
  activeCoverSlideDuration,
@@ -3946,6 +4078,9 @@ function PreviewSettingsProvider({
3946
4078
  handlePipShape,
3947
4079
  handlePipPosition,
3948
4080
  handleSetVideoLoopEnabled,
4081
+ handleSetDashboardLayout,
4082
+ handleSetDashboardTitleEnabled,
4083
+ handleSetDashboardStyle,
3949
4084
  handleSetCoverSlideEnabled,
3950
4085
  handleSetCoverSlideTemplate,
3951
4086
  handleSetCoverSlideDuration,
@@ -4007,6 +4142,12 @@ var DISPLAY_MODE_OPTIONS = [
4007
4142
  icon: "fa-solid fa-images",
4008
4143
  summary: "Present designed slides one at a time."
4009
4144
  },
4145
+ {
4146
+ key: "flashcards",
4147
+ label: "Flashcards",
4148
+ icon: "fa-solid fa-clone",
4149
+ summary: "Study with progressive reveals and quizzes."
4150
+ },
4010
4151
  {
4011
4152
  key: "video",
4012
4153
  label: "Video",
@@ -4019,6 +4160,12 @@ var DISPLAY_MODE_OPTIONS = [
4019
4160
  icon: "fa-solid fa-window-maximize",
4020
4161
  summary: "Scroll through the fully designed page."
4021
4162
  },
4163
+ {
4164
+ key: "dashboard",
4165
+ label: "Dashboard",
4166
+ icon: "fa-solid fa-table-cells-large",
4167
+ summary: "See every block arranged on one canvas."
4168
+ },
4022
4169
  {
4023
4170
  key: "page",
4024
4171
  label: "Document",
@@ -4042,15 +4189,23 @@ var TRANSFORM_STYLE_OPTIONS = [
4042
4189
  var SUMMARIZE_TOOLTIP = "Extract and summarize content for presentation with these Use modes. Your underlying content is not changed.";
4043
4190
  var CONTROL_KEYS = [
4044
4191
  "format",
4192
+ "layout",
4045
4193
  "video",
4046
4194
  "theme",
4047
4195
  "transform",
4048
4196
  "captions",
4049
4197
  "loop",
4050
- "cover"
4198
+ "cover",
4199
+ "dashboardImage"
4051
4200
  ];
4052
4201
  function controlKeysForMode(displayMode, hasVideoMedia) {
4053
- let keys = CONTROL_KEYS;
4202
+ if (displayMode === "flashcards") {
4203
+ return ["theme", "transform"];
4204
+ }
4205
+ if (displayMode === "dashboard") {
4206
+ return ["format", "layout", "theme", "transform", "dashboardImage"];
4207
+ }
4208
+ let keys = CONTROL_KEYS.filter((key) => key !== "layout" && key !== "dashboardImage");
4054
4209
  if (displayMode !== "video") {
4055
4210
  keys = keys.filter((key) => key !== "loop");
4056
4211
  }
@@ -4097,9 +4252,54 @@ var selectStyle = {
4097
4252
  var COVER_MENU_WIDTH = 336;
4098
4253
  var COVER_MENU_GAP = 4;
4099
4254
  var COVER_MENU_MARGIN = 8;
4255
+ var DASHBOARD_EXPORT_RESOLUTION_BY_PRESET = {
4256
+ landscape: "fhd",
4257
+ portrait: "portrait",
4258
+ square: "square",
4259
+ standard: "standard"
4260
+ };
4261
+ function DashboardImageExportControl({ compact }) {
4262
+ const settings = usePreviewSettings();
4263
+ const { colorScheme, doc, fileName, mediaProvider, saveDashboardImageOutput } = useEditorContext();
4264
+ const [exportOpen, setExportOpen] = useState9(false);
4265
+ return /* @__PURE__ */ jsxs6("div", { className: `squisq-preview-control${compact ? " squisq-preview-control--compact" : ""}`, children: [
4266
+ compact && /* @__PURE__ */ jsx8("label", { style: labelStyle, children: "Export:" }),
4267
+ /* @__PURE__ */ jsx8(
4268
+ "button",
4269
+ {
4270
+ type: "button",
4271
+ className: "squisq-toolbar-button",
4272
+ disabled: !doc,
4273
+ onClick: () => setExportOpen(true),
4274
+ "aria-label": "Export dashboard as image",
4275
+ title: "Export dashboard as image",
4276
+ children: /* @__PURE__ */ jsx8(Icon, { icon: "fa-solid fa-file-image" })
4277
+ }
4278
+ ),
4279
+ exportOpen && doc && createPortal2(
4280
+ /* @__PURE__ */ jsx8(
4281
+ DashboardImageExportModal,
4282
+ {
4283
+ doc,
4284
+ mediaProvider,
4285
+ theme: settings.activeTheme,
4286
+ defaultResolution: DASHBOARD_EXPORT_RESOLUTION_BY_PRESET[settings.activePreset],
4287
+ defaultLayout: settings.activeDashboardLayout,
4288
+ defaultShowTitle: settings.activeDashboardTitle,
4289
+ defaultStyle: settings.activeDashboardStyle,
4290
+ defaultFileName: fileName,
4291
+ colorScheme,
4292
+ saveOutput: saveDashboardImageOutput,
4293
+ onClose: () => setExportOpen(false)
4294
+ }
4295
+ ),
4296
+ document.body
4297
+ )
4298
+ ] });
4299
+ }
4100
4300
  function CoverSlideMenuControl({ compact }) {
4101
4301
  const settings = usePreviewSettings();
4102
- const { colorScheme, doc, fileName, mediaProvider } = useEditorContext();
4302
+ const { colorScheme, doc, fileName, mediaProvider, saveCoverImageOutput } = useEditorContext();
4103
4303
  const triggerRef = useRef7(null);
4104
4304
  const menuRef = useRef7(null);
4105
4305
  const [open, setOpen] = useState9(false);
@@ -4361,6 +4561,7 @@ function CoverSlideMenuControl({ compact }) {
4361
4561
  defaultHeight: settings.activeViewport.height,
4362
4562
  defaultFileName: fileName,
4363
4563
  colorScheme,
4564
+ saveOutput: saveCoverImageOutput,
4364
4565
  onClose: () => setExportOpen(false)
4365
4566
  }
4366
4567
  ),
@@ -4472,6 +4673,56 @@ function PreviewToolbarControls({ displayMode } = {}) {
4472
4673
  },
4473
4674
  "format"
4474
4675
  );
4676
+ case "layout": {
4677
+ const builtIns = s.dashboardLayoutOptions.filter((option) => !option.custom);
4678
+ const customs = s.dashboardLayoutOptions.filter((option) => option.custom);
4679
+ return /* @__PURE__ */ jsxs6(
4680
+ "div",
4681
+ {
4682
+ className: `squisq-preview-control${compact ? " squisq-preview-control--compact" : ""}`,
4683
+ children: [
4684
+ /* @__PURE__ */ jsx8("label", { style: labelStyle, children: "Layout:" }),
4685
+ /* @__PURE__ */ jsxs6(
4686
+ "select",
4687
+ {
4688
+ "aria-label": "Dashboard layout",
4689
+ style: selectStyle,
4690
+ value: s.activeDashboardLayout,
4691
+ onChange: (event) => s.setDashboardLayout(event.target.value),
4692
+ children: [
4693
+ /* @__PURE__ */ jsx8("option", { value: DASHBOARD_AUTO_LAYOUT_ID, children: "Auto" }),
4694
+ builtIns.map((option) => /* @__PURE__ */ jsx8("option", { value: option.id, children: `${option.label} (${option.capacity})` }, option.id)),
4695
+ customs.length > 0 && /* @__PURE__ */ jsx8("optgroup", { label: "Custom", children: customs.map((option) => /* @__PURE__ */ jsx8("option", { value: option.id, children: `${option.label} (${option.capacity})` }, option.id)) })
4696
+ ]
4697
+ }
4698
+ ),
4699
+ /* @__PURE__ */ jsx8("label", { style: labelStyle, children: "Style:" }),
4700
+ /* @__PURE__ */ jsx8(
4701
+ "select",
4702
+ {
4703
+ "aria-label": "Dashboard cell style",
4704
+ style: selectStyle,
4705
+ value: s.activeDashboardStyle,
4706
+ onChange: (event) => s.setDashboardStyle(event.target.value),
4707
+ children: DASHBOARD_STYLES.map((option) => /* @__PURE__ */ jsx8("option", { value: option.id, title: option.description, children: option.label }, option.id))
4708
+ }
4709
+ ),
4710
+ /* @__PURE__ */ jsxs6("label", { className: "squisq-preview-checkbox", children: [
4711
+ /* @__PURE__ */ jsx8(
4712
+ "input",
4713
+ {
4714
+ type: "checkbox",
4715
+ checked: s.activeDashboardTitle,
4716
+ onChange: (event) => s.setDashboardTitleEnabled(event.target.checked)
4717
+ }
4718
+ ),
4719
+ /* @__PURE__ */ jsx8("span", { children: "Title" })
4720
+ ] })
4721
+ ]
4722
+ },
4723
+ "layout"
4724
+ );
4725
+ }
4475
4726
  case "video": {
4476
4727
  const showPipOptions = s.activeVideoPresentation === "picture-in-picture";
4477
4728
  return /* @__PURE__ */ jsxs6(
@@ -4663,6 +4914,8 @@ function PreviewToolbarControls({ displayMode } = {}) {
4663
4914
  );
4664
4915
  case "cover":
4665
4916
  return /* @__PURE__ */ jsx8(CoverSlideMenuControl, { compact }, "cover");
4917
+ case "dashboardImage":
4918
+ return /* @__PURE__ */ jsx8(DashboardImageExportControl, { compact }, "dashboardImage");
4666
4919
  }
4667
4920
  };
4668
4921
  const hasOverflow = visibleCount < controlKeys.length;
@@ -7527,6 +7780,7 @@ import {
7527
7780
  readFrontmatterThemeId as readFrontmatterThemeId2,
7528
7781
  setFrontmatterValues as setFrontmatterValues3
7529
7782
  } from "@bendyline/squisq/markdown";
7783
+ import { markdownToDoc as markdownToDoc2 } from "@bendyline/squisq/doc";
7530
7784
  import { getTransformStyleSummaries as getTransformStyleSummaries3 } from "@bendyline/squisq/transform";
7531
7785
  import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
7532
7786
  function readFm(fm, canonical, legacy) {
@@ -7547,7 +7801,17 @@ function DocumentSettingsDialog({
7547
7801
  const parsed = useMemo14(() => parseMarkdown4(markdownSource), [markdownSource]);
7548
7802
  const frontmatter = parsed.frontmatter;
7549
7803
  const inferredTitle = useMemo14(() => inferDocumentTitle(parsed), [parsed]);
7804
+ const inferredSubtitle = useMemo14(
7805
+ () => markdownToDoc2({
7806
+ ...parsed,
7807
+ // Ignore an explicit subtitle while calculating the value the cover
7808
+ // would otherwise inherit from document content.
7809
+ frontmatter: { ...parsed.frontmatter, subtitle: null }
7810
+ }).startBlock?.subtitle,
7811
+ [parsed]
7812
+ );
7550
7813
  const currentTitle = typeof frontmatter?.title === "string" ? frontmatter.title : "";
7814
+ const currentSubtitle = typeof frontmatter?.subtitle === "string" ? frontmatter.subtitle : "";
7551
7815
  const currentTheme = readFrontmatterThemeId2(frontmatter) ?? "";
7552
7816
  const currentTransform = resolvePersistedTransformStyleId(
7553
7817
  readFm(
@@ -7562,6 +7826,7 @@ function DocumentSettingsDialog({
7562
7826
  FRONTMATTER_SETTING_KEYS.captions.legacy
7563
7827
  );
7564
7828
  const [title, setTitle] = useState15(currentTitle);
7829
+ const [subtitle, setSubtitle] = useState15(currentSubtitle);
7565
7830
  const [theme, setTheme] = useState15(currentTheme);
7566
7831
  const [transform, setTransform] = useState15(currentTransform);
7567
7832
  const [captions, setCaptions] = useState15(currentCaptions);
@@ -7589,8 +7854,12 @@ function DocumentSettingsDialog({
7589
7854
  const trimmedTitle = title.trim();
7590
7855
  const titleMatchesInferred = !!inferredTitle && trimmedTitle === inferredTitle;
7591
7856
  const nextTitle = !trimmedTitle || titleMatchesInferred ? null : trimmedTitle;
7857
+ const trimmedSubtitle = subtitle.trim();
7858
+ const subtitleMatchesInferred = !!inferredSubtitle && trimmedSubtitle === inferredSubtitle.trim();
7859
+ const nextSubtitle = !trimmedSubtitle || subtitleMatchesInferred ? null : trimmedSubtitle;
7592
7860
  const updates = {
7593
7861
  title: nextTitle,
7862
+ subtitle: nextSubtitle,
7594
7863
  [FRONTMATTER_SETTING_KEYS.theme.canonical]: omitFrontmatterDefault(
7595
7864
  theme || FRONTMATTER_SETTING_DEFAULTS.theme,
7596
7865
  FRONTMATTER_SETTING_DEFAULTS.theme
@@ -7612,7 +7881,17 @@ function DocumentSettingsDialog({
7612
7881
  const nextSource = setFrontmatterValues3(markdownSource, updates);
7613
7882
  onSave(nextSource);
7614
7883
  },
7615
- [title, theme, transform, captions, inferredTitle, markdownSource, onSave]
7884
+ [
7885
+ title,
7886
+ subtitle,
7887
+ theme,
7888
+ transform,
7889
+ captions,
7890
+ inferredTitle,
7891
+ inferredSubtitle,
7892
+ markdownSource,
7893
+ onSave
7894
+ ]
7616
7895
  );
7617
7896
  return /* @__PURE__ */ jsx17("div", { ref: overlayRef, className: "squisq-doc-settings-overlay", onMouseDown: handleBackdrop, children: /* @__PURE__ */ jsxs13(
7618
7897
  "form",
@@ -7660,6 +7939,26 @@ function DocumentSettingsDialog({
7660
7939
  " \u2014 leave blank to use it."
7661
7940
  ] }) : /* @__PURE__ */ jsx17("span", { className: "squisq-doc-settings-hint", children: "No title heading found \u2014 set one here or add an H1 to the document." })
7662
7941
  ] }),
7942
+ /* @__PURE__ */ jsxs13("div", { className: "squisq-doc-settings-field", children: [
7943
+ /* @__PURE__ */ jsx17("label", { className: "squisq-doc-settings-label", htmlFor: "squisq-doc-settings-subtitle", children: "Subtitle" }),
7944
+ /* @__PURE__ */ jsx17(
7945
+ "input",
7946
+ {
7947
+ id: "squisq-doc-settings-subtitle",
7948
+ type: "text",
7949
+ className: "squisq-doc-settings-input",
7950
+ value: subtitle,
7951
+ onChange: (e2) => setSubtitle(e2.target.value),
7952
+ placeholder: inferredSubtitle ?? "Cover subtitle",
7953
+ spellCheck: true
7954
+ }
7955
+ ),
7956
+ inferredSubtitle ? /* @__PURE__ */ jsxs13("span", { className: "squisq-doc-settings-hint", children: [
7957
+ "Defaults to ",
7958
+ /* @__PURE__ */ jsx17("strong", { children: inferredSubtitle }),
7959
+ " \u2014 leave blank to use it."
7960
+ ] }) : /* @__PURE__ */ jsx17("span", { className: "squisq-doc-settings-hint", children: "Optional supporting text shown on the document cover." })
7961
+ ] }),
7663
7962
  /* @__PURE__ */ jsxs13("div", { className: "squisq-doc-settings-field", children: [
7664
7963
  /* @__PURE__ */ jsx17("span", { className: "squisq-doc-settings-label", children: "Theme" }),
7665
7964
  /* @__PURE__ */ jsx17(
@@ -18935,18 +19234,25 @@ ${TASK_LIST_MARKDOWN}
18935
19234
  }
18936
19235
 
18937
19236
  // src/StatusBar.tsx
18938
- import { useMemo as useMemo25 } from "react";
19237
+ import { useEffect as useEffect25, useMemo as useMemo25, useState as useState32 } from "react";
18939
19238
  import { countBlocks } from "@bendyline/squisq/doc";
18940
19239
  import { jsx as jsx42, jsxs as jsxs32 } from "react/jsx-runtime";
19240
+ var STATS_DEBOUNCE_MS = 150;
18941
19241
  function StatusBar({ className, slotRight }) {
18942
- const { markdownSource, doc, parseError, isParsing } = useEditorContext();
19242
+ const { markdownSource, doc, parseError } = useEditorContext();
19243
+ const [statsSource, setStatsSource] = useState32(markdownSource);
19244
+ useEffect25(() => {
19245
+ if (statsSource === markdownSource) return;
19246
+ const timeout = setTimeout(() => setStatsSource(markdownSource), STATS_DEBOUNCE_MS);
19247
+ return () => clearTimeout(timeout);
19248
+ }, [markdownSource, statsSource]);
18943
19249
  const stats = useMemo25(() => {
18944
- const chars = markdownSource.length;
18945
- const words = markdownSource.trim() ? markdownSource.trim().split(/\s+/).length : 0;
18946
- const lines2 = markdownSource.split("\n").length;
18947
- const blocks = doc ? countBlocks(doc.blocks) : 0;
18948
- return { chars, words, lines: lines2, blocks };
18949
- }, [markdownSource, doc]);
19250
+ const chars = statsSource.length;
19251
+ const words = statsSource.trim() ? statsSource.trim().split(/\s+/).length : 0;
19252
+ const lines2 = statsSource.split("\n").length;
19253
+ return { chars, words, lines: lines2 };
19254
+ }, [statsSource]);
19255
+ const blocks = useMemo25(() => doc ? countBlocks(doc.blocks) : 0, [doc]);
18950
19256
  return /* @__PURE__ */ jsxs32("div", { className: `squisq-status-bar ${className || ""}`, children: [
18951
19257
  /* @__PURE__ */ jsxs32("span", { className: "squisq-status-item", children: [
18952
19258
  stats.words,
@@ -18961,19 +19267,19 @@ function StatusBar({ className, slotRight }) {
18961
19267
  " lines"
18962
19268
  ] }),
18963
19269
  /* @__PURE__ */ jsxs32("span", { className: "squisq-status-item", children: [
18964
- stats.blocks,
19270
+ blocks,
18965
19271
  " ",
18966
- stats.blocks === 1 ? "block" : "blocks"
19272
+ blocks === 1 ? "block" : "blocks"
18967
19273
  ] }),
18968
19274
  /* @__PURE__ */ jsx42("span", { className: "squisq-status-spacer" }),
18969
19275
  parseError && /* @__PURE__ */ jsx42("span", { className: "squisq-status-item squisq-status-error", title: parseError, children: "\u26A0 Error" }),
18970
- !isParsing && !parseError && /* @__PURE__ */ jsx42("span", { className: "squisq-status-item squisq-status-ok", children: "\u2713 OK" }),
19276
+ !parseError && /* @__PURE__ */ jsx42("span", { className: "squisq-status-item squisq-status-ok", children: "\u2713 OK" }),
18971
19277
  slotRight
18972
19278
  ] });
18973
19279
  }
18974
19280
 
18975
19281
  // src/RawEditor.tsx
18976
- import { useRef as useRef29, useCallback as useCallback30, useEffect as useEffect25, useState as useState32 } from "react";
19282
+ import { useRef as useRef29, useCallback as useCallback30, useEffect as useEffect26, useState as useState33 } from "react";
18977
19283
  import { getAvailableTemplates } from "@bendyline/squisq/doc";
18978
19284
  import { suggestIcons, resolveIcon, iconGlyph } from "@bendyline/squisq/icons";
18979
19285
  import { BLOCK_META_KEY_DESCRIPTORS, tokenizeAttrTokens as tokenizeAttrTokens3 } from "@bendyline/squisq/markdown";
@@ -19045,10 +19351,10 @@ var SQUISQ_THEMES = {
19045
19351
  "vs-dark": "squisq-dark"
19046
19352
  };
19047
19353
  function useDocumentMonacoLanguages(language, source) {
19048
- const [requested, setRequested] = useState32(
19354
+ const [requested, setRequested] = useState33(
19049
19355
  () => monacoLanguagesForDocument(language, source)
19050
19356
  );
19051
- useEffect25(() => {
19357
+ useEffect26(() => {
19052
19358
  const delay = normalizeMonacoLanguage(language) === "markdown" ? 250 : 0;
19053
19359
  const timer = window.setTimeout(() => {
19054
19360
  setRequested(monacoLanguagesForDocument(language, source));
@@ -19084,14 +19390,14 @@ function RawEditor({
19084
19390
  const keyDisposable = useRef29(null);
19085
19391
  const submitOnEnterRef = useRef29(submitOnEnter);
19086
19392
  const readOnlyRef = useRef29(readOnly);
19087
- useEffect25(() => {
19393
+ useEffect26(() => {
19088
19394
  submitOnEnterRef.current = submitOnEnter;
19089
19395
  }, [submitOnEnter]);
19090
- useEffect25(() => {
19396
+ useEffect26(() => {
19091
19397
  readOnlyRef.current = readOnly;
19092
19398
  }, [readOnly]);
19093
19399
  const mentionProviderRef = useRef29(mentionProvider);
19094
- useEffect25(() => {
19400
+ useEffect26(() => {
19095
19401
  mentionProviderRef.current = mentionProvider;
19096
19402
  }, [mentionProvider]);
19097
19403
  const docCustomTemplates = doc?.customTemplates;
@@ -19102,7 +19408,7 @@ function RawEditor({
19102
19408
  detail: "Block template"
19103
19409
  }));
19104
19410
  }
19105
- useEffect25(() => {
19411
+ useEffect26(() => {
19106
19412
  const builtIns = getAvailableTemplates().map((name) => ({
19107
19413
  name,
19108
19414
  detail: "Block template"
@@ -19444,7 +19750,7 @@ function RawEditor({
19444
19750
  },
19445
19751
  [setMonacoEditor, language]
19446
19752
  );
19447
- useEffect25(() => {
19753
+ useEffect26(() => {
19448
19754
  return () => {
19449
19755
  setMonacoEditor(null);
19450
19756
  completionDisposable.current?.dispose();
@@ -19472,7 +19778,7 @@ function RawEditor({
19472
19778
  },
19473
19779
  [setEditorSource]
19474
19780
  );
19475
- useEffect25(() => {
19781
+ useEffect26(() => {
19476
19782
  const editor = editorRef.current;
19477
19783
  if (editor) {
19478
19784
  const currentValue = editor.getValue();
@@ -19483,7 +19789,7 @@ function RawEditor({
19483
19789
  }
19484
19790
  }
19485
19791
  }, [editorSource]);
19486
- useEffect25(() => {
19792
+ useEffect26(() => {
19487
19793
  const editor = editorRef.current;
19488
19794
  if (!editor || !monacoNs) return;
19489
19795
  if (language !== "markdown") return;
@@ -19774,7 +20080,7 @@ function propsBadgeSpec(summary) {
19774
20080
  }
19775
20081
 
19776
20082
  // src/diagram/DiagramCanvas.tsx
19777
- import { useCallback as useCallback31, useMemo as useMemo26, useRef as useRef30, useState as useState33 } from "react";
20083
+ import { useCallback as useCallback31, useMemo as useMemo26, useRef as useRef30, useState as useState34 } from "react";
19778
20084
 
19779
20085
  // src/diagram/diagramConstants.ts
19780
20086
  var DIAGRAM_VIEWPORT = { width: 1600, height: 900 };
@@ -19799,7 +20105,7 @@ function DiagramCanvas({
19799
20105
  () => buildDiagramScene(incomingNodes, incomingEdges),
19800
20106
  [incomingNodes, incomingEdges]
19801
20107
  );
19802
- const [internalToolId, setInternalToolId] = useState33("select");
20108
+ const [internalToolId, setInternalToolId] = useState34("select");
19803
20109
  const activeToolId = controlledToolId ?? internalToolId;
19804
20110
  const setActiveToolId = onActiveToolIdChange ?? setInternalToolId;
19805
20111
  const handleSceneCommand = useCallback31(
@@ -20087,7 +20393,7 @@ function removeNodeOp(diagram, nodeId) {
20087
20393
  }
20088
20394
 
20089
20395
  // src/asciiDiagram/asciiDiagramData.ts
20090
- import { useEffect as useEffect26, useMemo as useMemo28, useState as useState35 } from "react";
20396
+ import { useEffect as useEffect27, useMemo as useMemo28, useState as useState36 } from "react";
20091
20397
  import {
20092
20398
  asciiCellToCanvas,
20093
20399
  asciiDiagramToTemplateData,
@@ -20109,7 +20415,7 @@ import {
20109
20415
  } from "@bendyline/squisq/doc";
20110
20416
 
20111
20417
  // src/asciiDiagram/AsciiDiagramWidget.tsx
20112
- import { useCallback as useCallback32, useMemo as useMemo27, useRef as useRef31, useState as useState34 } from "react";
20418
+ import { useCallback as useCallback32, useMemo as useMemo27, useRef as useRef31, useState as useState35 } from "react";
20113
20419
 
20114
20420
  // src/asciiDiagram/asciiDiagramCommands.ts
20115
20421
  import {
@@ -20357,11 +20663,11 @@ function AsciiDiagramWidget({
20357
20663
  textChannel
20358
20664
  }) {
20359
20665
  const view = useAsciiDiagramData(editor, blockId);
20360
- const [maximized, setMaximized] = useState34(false);
20361
- const [height, setHeight] = useState34(null);
20362
- const [dragHeight, setDragHeight] = useState34(null);
20666
+ const [maximized, setMaximized] = useState35(false);
20667
+ const [height, setHeight] = useState35(null);
20668
+ const [dragHeight, setDragHeight] = useState35(null);
20363
20669
  const canvasOffsetRef = useRef31(null);
20364
- const [canvasOffsetVersion, setCanvasOffsetVersion] = useState34(0);
20670
+ const [canvasOffsetVersion, setCanvasOffsetVersion] = useState35(0);
20365
20671
  const inlineRef = useRef31(null);
20366
20672
  const effectiveHeight = dragHeight ?? height;
20367
20673
  if (view && canvasOffsetRef.current === null) {
@@ -20414,8 +20720,8 @@ function AsciiDiagramWidget({
20414
20720
  setDragHeight(null);
20415
20721
  setHeight(null);
20416
20722
  }, []);
20417
- const [activeToolId, setActiveToolId] = useState34("select");
20418
- const [selectedIds, setSelectedIds] = useState34(() => /* @__PURE__ */ new Set());
20723
+ const [activeToolId, setActiveToolId] = useState35("select");
20724
+ const [selectedIds, setSelectedIds] = useState35(() => /* @__PURE__ */ new Set());
20419
20725
  const selectedNodeIds = useMemo27(
20420
20726
  () => [...selectedIds].map((id) => nodeIdFromCardLayerId(id)).filter((id) => id != null),
20421
20727
  [selectedIds]
@@ -20786,8 +21092,8 @@ function asciiDiagramToCanvas(diagram) {
20786
21092
  return { nodes, edges };
20787
21093
  }
20788
21094
  function useAsciiDiagramData(editor, blockId) {
20789
- const [version, setVersion] = useState35(0);
20790
- useEffect26(() => {
21095
+ const [version, setVersion] = useState36(0);
21096
+ useEffect27(() => {
20791
21097
  const onUpdate = () => setVersion((v2) => v2 + 1);
20792
21098
  editor.on("transaction", onUpdate);
20793
21099
  return () => {
@@ -22617,7 +22923,7 @@ function mermaidErrorMessage(error) {
22617
22923
  }
22618
22924
 
22619
22925
  // src/mermaid/mermaidSurfaceTheme.ts
22620
- import { useEffect as useEffect27, useMemo as useMemo29, useState as useState36 } from "react";
22926
+ import { useEffect as useEffect28, useMemo as useMemo29, useState as useState37 } from "react";
22621
22927
  import { relativeLuminance } from "@bendyline/squisq/schemas";
22622
22928
  var EDITOR_SURFACE = {
22623
22929
  light: {
@@ -22653,8 +22959,8 @@ function resolveMermaidSurfaceTheme(host, theme) {
22653
22959
  return applyHostSurface(readHostSurface(host), theme);
22654
22960
  }
22655
22961
  function useMermaidSurfaceTheme(host, theme) {
22656
- const [surface, setSurface] = useState36(() => readHostSurface(host));
22657
- useEffect27(() => {
22962
+ const [surface, setSurface] = useState37(() => readHostSurface(host));
22963
+ useEffect28(() => {
22658
22964
  const sync = () => setSurface((current) => {
22659
22965
  const next = readHostSurface(host);
22660
22966
  return current.scheme === next.scheme && current.inheritsThemeColors === next.inheritsThemeColors ? current : next;
@@ -22680,7 +22986,7 @@ function useMermaidSurfaceTheme(host, theme) {
22680
22986
  }
22681
22987
 
22682
22988
  // src/mermaid/MermaidDiagramCanvas.tsx
22683
- import { useCallback as useCallback33, useEffect as useEffect28, useId as useId12, useLayoutEffect as useLayoutEffect5, useRef as useRef32, useState as useState37 } from "react";
22989
+ import { useCallback as useCallback33, useEffect as useEffect29, useId as useId12, useLayoutEffect as useLayoutEffect5, useRef as useRef32, useState as useState38 } from "react";
22684
22990
  import { jsx as jsx46, jsxs as jsxs35 } from "react/jsx-runtime";
22685
22991
  var renderSequence = 0;
22686
22992
  function excludesCanvasPan(target, viewport) {
@@ -22744,20 +23050,20 @@ function MermaidDiagramCanvas({
22744
23050
  const renameInputRef = useRef32(null);
22745
23051
  const panDragRef = useRef32(null);
22746
23052
  const renameHintId = useId12();
22747
- const [svg, setSvg] = useState37("");
22748
- const [model2, setModel] = useState37(null);
22749
- const [diagramType, setDiagramType] = useState37("mermaid");
22750
- const [error, setError] = useState37("");
22751
- const [rendering, setRendering] = useState37(true);
22752
- const [zoom, setZoom] = useState37(1);
22753
- const [viewMode, setViewMode] = useState37("fit");
22754
- const [contentSize, setContentSize] = useState37(null);
22755
- const [canvasSize, setCanvasSize] = useState37(null);
22756
- const [pan, setPan] = useState37({ x: 0, y: 0 });
22757
- const [panning, setPanning] = useState37(false);
22758
- const [selectionAnchor, setSelectionAnchor] = useState37(null);
22759
- const [renameValue, setRenameValue] = useState37("");
22760
- useEffect28(() => {
23053
+ const [svg, setSvg] = useState38("");
23054
+ const [model2, setModel] = useState38(null);
23055
+ const [diagramType, setDiagramType] = useState38("mermaid");
23056
+ const [error, setError] = useState38("");
23057
+ const [rendering, setRendering] = useState38(true);
23058
+ const [zoom, setZoom] = useState38(1);
23059
+ const [viewMode, setViewMode] = useState38("fit");
23060
+ const [contentSize, setContentSize] = useState38(null);
23061
+ const [canvasSize, setCanvasSize] = useState38(null);
23062
+ const [pan, setPan] = useState38({ x: 0, y: 0 });
23063
+ const [panning, setPanning] = useState38(false);
23064
+ const [selectionAnchor, setSelectionAnchor] = useState38(null);
23065
+ const [renameValue, setRenameValue] = useState38("");
23066
+ useEffect29(() => {
22761
23067
  if (!panning) return;
22762
23068
  const finish = () => {
22763
23069
  panDragRef.current = null;
@@ -22785,7 +23091,7 @@ function MermaidDiagramCanvas({
22785
23091
  window.removeEventListener("blur", finish);
22786
23092
  };
22787
23093
  }, [panning]);
22788
- useEffect28(() => {
23094
+ useEffect29(() => {
22789
23095
  let current = true;
22790
23096
  const id = `squisq-mermaid-svg-${++renderSequence}`;
22791
23097
  setRendering(true);
@@ -23056,7 +23362,7 @@ function MermaidDiagramCanvas({
23056
23362
  updateSelectionAnchor,
23057
23363
  zoom
23058
23364
  ]);
23059
- useEffect28(() => {
23365
+ useEffect29(() => {
23060
23366
  const scroll = scrollRef.current;
23061
23367
  if (!scroll) return;
23062
23368
  const update = () => updateSelectionAnchor();
@@ -23461,7 +23767,7 @@ function CanvasAction({
23461
23767
  }
23462
23768
 
23463
23769
  // src/mermaid/MermaidShapePalette.tsx
23464
- import { useEffect as useEffect29, useLayoutEffect as useLayoutEffect6, useMemo as useMemo30, useRef as useRef33, useState as useState38 } from "react";
23770
+ import { useEffect as useEffect30, useLayoutEffect as useLayoutEffect6, useMemo as useMemo30, useRef as useRef33, useState as useState39 } from "react";
23465
23771
  import { jsx as jsx47, jsxs as jsxs36 } from "react/jsx-runtime";
23466
23772
  var CATEGORIES = ["Basic", "Process", "Data", "Documents", "Symbols"];
23467
23773
  var PALETTE_WIDTH = 360;
@@ -23469,10 +23775,10 @@ var PALETTE_MAX_HEIGHT = 520;
23469
23775
  var PALETTE_GAP = 6;
23470
23776
  var VIEWPORT_GUTTER = 8;
23471
23777
  function MermaidShapePalette({ selected, onPick, onClose }) {
23472
- const [query, setQuery] = useState38("");
23473
- const [position, setPosition] = useState38(null);
23778
+ const [query, setQuery] = useState39("");
23779
+ const [position, setPosition] = useState39(null);
23474
23780
  const ref = useRef33(null);
23475
- useEffect29(() => {
23781
+ useEffect30(() => {
23476
23782
  const onPointerDown = (event) => {
23477
23783
  const target = event.target;
23478
23784
  if (target?.closest(".squisq-scene-block-toolbar")) return;
@@ -23643,7 +23949,7 @@ function MermaidShapeThumb({ shape }) {
23643
23949
  }
23644
23950
 
23645
23951
  // src/mermaid/mermaidData.ts
23646
- import { useEffect as useEffect31, useMemo as useMemo31, useState as useState40 } from "react";
23952
+ import { useEffect as useEffect32, useMemo as useMemo31, useState as useState41 } from "react";
23647
23953
 
23648
23954
  // src/mermaid/MermaidDiagramExtension.ts
23649
23955
  import { Extension as Extension5 } from "@tiptap/core";
@@ -23655,10 +23961,10 @@ import { createRoot as createRoot3 } from "react-dom/client";
23655
23961
  // src/mermaid/MermaidDiagramWidget.tsx
23656
23962
  import {
23657
23963
  useCallback as useCallback34,
23658
- useEffect as useEffect30,
23964
+ useEffect as useEffect31,
23659
23965
  useLayoutEffect as useLayoutEffect7,
23660
23966
  useRef as useRef34,
23661
- useState as useState39,
23967
+ useState as useState40,
23662
23968
  useSyncExternalStore
23663
23969
  } from "react";
23664
23970
 
@@ -24027,16 +24333,16 @@ function MermaidDiagramWidget({
24027
24333
  themeStore?.getSnapshot ?? getDefaultTheme
24028
24334
  );
24029
24335
  const theme = useMermaidSurfaceTheme(host, docTheme);
24030
- const [model2, setModel] = useState39(null);
24031
- const [selection, setSelection] = useState39(null);
24032
- const [renaming, setRenaming] = useState39(null);
24033
- const [connecting, setConnecting] = useState39(false);
24034
- const [connectSourceId, setConnectSourceId] = useState39(null);
24035
- const [openPopover, setOpenPopover] = useState39(null);
24036
- const [editNotice, setEditNotice] = useState39("");
24037
- const [maximized, setMaximized] = useState39(false);
24038
- const [height, setHeight] = useState39(null);
24039
- const [dragHeight, setDragHeight] = useState39(null);
24336
+ const [model2, setModel] = useState40(null);
24337
+ const [selection, setSelection] = useState40(null);
24338
+ const [renaming, setRenaming] = useState40(null);
24339
+ const [connecting, setConnecting] = useState40(false);
24340
+ const [connectSourceId, setConnectSourceId] = useState40(null);
24341
+ const [openPopover, setOpenPopover] = useState40(null);
24342
+ const [editNotice, setEditNotice] = useState40("");
24343
+ const [maximized, setMaximized] = useState40(false);
24344
+ const [height, setHeight] = useState40(null);
24345
+ const [dragHeight, setDragHeight] = useState40(null);
24040
24346
  const inlineRef = useRef34(null);
24041
24347
  const effectiveHeight = dragHeight ?? height;
24042
24348
  const onModelChange = useCallback34((next) => {
@@ -24295,7 +24601,7 @@ function MermaidDiagramWidget({
24295
24601
  },
24296
24602
  [effectiveHeight]
24297
24603
  );
24298
- useEffect30(() => {
24604
+ useEffect31(() => {
24299
24605
  if (!editNotice) return;
24300
24606
  const timeout = window.setTimeout(() => setEditNotice(""), 7e3);
24301
24607
  return () => window.clearTimeout(timeout);
@@ -24532,7 +24838,7 @@ function MermaidDiagramWidget({
24532
24838
  ] });
24533
24839
  }
24534
24840
  function useDismissibleMermaidPopover(ref, onClose) {
24535
- useEffect30(() => {
24841
+ useEffect31(() => {
24536
24842
  const onPointerDown = (event) => {
24537
24843
  const target = event.target;
24538
24844
  if (target?.closest(".squisq-scene-block-toolbar")) return;
@@ -24550,7 +24856,7 @@ function useDismissibleMermaidPopover(ref, onClose) {
24550
24856
  }, [onClose, ref]);
24551
24857
  }
24552
24858
  function useClampedMermaidPopoverPosition(ref, preferredWidth, preferredMaxHeight) {
24553
- const [position, setPosition] = useState39(null);
24859
+ const [position, setPosition] = useState40(null);
24554
24860
  useLayoutEffect7(() => {
24555
24861
  const picker = ref.current;
24556
24862
  const anchor = picker?.parentElement;
@@ -24616,7 +24922,7 @@ function MermaidPropertiesPalette({
24616
24922
  onApply,
24617
24923
  onClose
24618
24924
  }) {
24619
- const [values, setValues] = useState39(
24925
+ const [values, setValues] = useState40(
24620
24926
  () => Object.fromEntries(properties.map((property) => [property.id, property.value]))
24621
24927
  );
24622
24928
  const ref = useRef34(null);
@@ -24910,8 +25216,8 @@ var MermaidDiagramExtension = Extension5.create({
24910
25216
 
24911
25217
  // src/mermaid/mermaidData.ts
24912
25218
  function useMermaidDiagramData(editor, blockId) {
24913
- const [version, setVersion] = useState40(0);
24914
- useEffect31(() => {
25219
+ const [version, setVersion] = useState41(0);
25220
+ useEffect32(() => {
24915
25221
  const onTransaction = () => setVersion((value) => value + 1);
24916
25222
  editor.on("transaction", onTransaction);
24917
25223
  return () => {
@@ -24935,13 +25241,13 @@ import { createElement as createElement10 } from "react";
24935
25241
  import { createRoot as createRoot4 } from "react-dom/client";
24936
25242
 
24937
25243
  // src/codeSnippet/CodeSnippetWidget.tsx
24938
- import { useCallback as useCallback35, useEffect as useEffect33, useState as useState42 } from "react";
25244
+ import { useCallback as useCallback35, useEffect as useEffect34, useState as useState43 } from "react";
24939
25245
 
24940
25246
  // src/codeSnippet/codeSnippetData.ts
24941
- import { useEffect as useEffect32, useMemo as useMemo32, useState as useState41 } from "react";
25247
+ import { useEffect as useEffect33, useMemo as useMemo32, useState as useState42 } from "react";
24942
25248
  function useCodeSnippetData(editor, blockId) {
24943
- const [version, setVersion] = useState41(0);
24944
- useEffect32(() => {
25249
+ const [version, setVersion] = useState42(0);
25250
+ useEffect33(() => {
24945
25251
  const onTransaction = () => setVersion((value) => value + 1);
24946
25252
  editor.on("transaction", onTransaction);
24947
25253
  return () => {
@@ -24986,10 +25292,10 @@ function schemeFromHost(host) {
24986
25292
  return host?.closest("[data-theme]")?.dataset.theme === "dark" ? "dark" : "light";
24987
25293
  }
24988
25294
  function useHostColorScheme(host) {
24989
- const [scheme, setScheme] = useState42(
25295
+ const [scheme, setScheme] = useState43(
24990
25296
  () => host != null && !host.isConnected ? null : schemeFromHost(host)
24991
25297
  );
24992
- useEffect33(() => {
25298
+ useEffect34(() => {
24993
25299
  if (host != null && !host.isConnected) {
24994
25300
  setScheme(null);
24995
25301
  const raf = requestAnimationFrame(() => {
@@ -25007,8 +25313,8 @@ function useHostColorScheme(host) {
25007
25313
  return scheme;
25008
25314
  }
25009
25315
  function useEditorEditable(editor) {
25010
- const [editable, setEditable] = useState42(editor.isEditable);
25011
- useEffect33(() => {
25316
+ const [editable, setEditable] = useState43(editor.isEditable);
25317
+ useEffect34(() => {
25012
25318
  const sync = () => setEditable(editor.isEditable);
25013
25319
  editor.on("update", sync);
25014
25320
  editor.on("transaction", sync);
@@ -25029,7 +25335,7 @@ function CodeSnippetWidget({
25029
25335
  const { ready } = useMonacoLoader(data?.monacoLanguage);
25030
25336
  const colorScheme = useHostColorScheme(host);
25031
25337
  const editable = useEditorEditable(editor);
25032
- const [modelInstanceId] = useState42(allocateCodeSnippetModelInstanceId);
25338
+ const [modelInstanceId] = useState43(allocateCodeSnippetModelInstanceId);
25033
25339
  const handleChange = useCallback35(
25034
25340
  (value) => {
25035
25341
  if (!editable) return;
@@ -25252,7 +25558,7 @@ import { createRoot as createRoot5 } from "react-dom/client";
25252
25558
  import { createElement as createElement11 } from "react";
25253
25559
 
25254
25560
  // src/fenceWidgets/HostFenceWidget.tsx
25255
- import { Component, useEffect as useEffect34, useMemo as useMemo33, useState as useState43 } from "react";
25561
+ import { Component, useEffect as useEffect35, useMemo as useMemo33, useState as useState44 } from "react";
25256
25562
  import { jsx as jsx50 } from "react/jsx-runtime";
25257
25563
  var HostFenceErrorBoundary = class extends Component {
25258
25564
  constructor() {
@@ -25267,8 +25573,8 @@ var HostFenceErrorBoundary = class extends Component {
25267
25573
  }
25268
25574
  };
25269
25575
  function HostFenceWidget({ editor, blockId, getRenderers, getTheme }) {
25270
- const [version, setVersion] = useState43(0);
25271
- useEffect34(() => {
25576
+ const [version, setVersion] = useState44(0);
25577
+ useEffect35(() => {
25272
25578
  const onUpdate = () => setVersion((v2) => v2 + 1);
25273
25579
  editor.on("transaction", onUpdate);
25274
25580
  return () => {
@@ -25418,7 +25724,7 @@ var HostFenceExtension = Extension7.create({
25418
25724
  });
25419
25725
 
25420
25726
  // src/treeview/treeViewData.ts
25421
- import { useEffect as useEffect35, useMemo as useMemo34, useState as useState45 } from "react";
25727
+ import { useEffect as useEffect36, useMemo as useMemo34, useState as useState46 } from "react";
25422
25728
 
25423
25729
  // src/treeview/TreeViewExtension.ts
25424
25730
  import { Extension as Extension8 } from "@tiptap/core";
@@ -25435,7 +25741,7 @@ import {
25435
25741
  } from "@bendyline/squisq/doc";
25436
25742
 
25437
25743
  // src/treeview/TreeOutlineWidget.tsx
25438
- import { useCallback as useCallback36, useRef as useRef35, useState as useState44 } from "react";
25744
+ import { useCallback as useCallback36, useRef as useRef35, useState as useState45 } from "react";
25439
25745
 
25440
25746
  // src/treeview/treeViewCommands.ts
25441
25747
  import { parseTree, renderTree } from "@bendyline/squisq/doc";
@@ -25672,10 +25978,10 @@ function dropPositionForPointer(event) {
25672
25978
  }
25673
25979
  function TreeOutlineWidget({ editor, blockId }) {
25674
25980
  const view = useTreeViewData(editor, blockId);
25675
- const [collapsed, setCollapsed] = useState44(() => /* @__PURE__ */ new Set());
25981
+ const [collapsed, setCollapsed] = useState45(() => /* @__PURE__ */ new Set());
25676
25982
  const activeDragRef = useRef35(null);
25677
- const [draggedId, setDraggedId] = useState44(null);
25678
- const [dropTarget, setDropTarget] = useState44(null);
25983
+ const [draggedId, setDraggedId] = useState45(null);
25984
+ const [dropTarget, setDropTarget] = useState45(null);
25679
25985
  const dispatch = useCallback36(
25680
25986
  (cmd) => applyTreeCommand(editor, blockId, cmd),
25681
25987
  [editor, blockId]
@@ -25822,7 +26128,7 @@ function TreeRowView({
25822
26128
  const hasChildren = node2.children.length > 0;
25823
26129
  const isCollapsed = collapsed.has(node2.id);
25824
26130
  const isDir = node2.isDir || hasChildren;
25825
- const [draft, setDraft] = useState44(node2.label);
26131
+ const [draft, setDraft] = useState45(node2.label);
25826
26132
  const dropPosition = dropTarget?.id === node2.id ? dropTarget.position : null;
25827
26133
  const itemClassName = [
25828
26134
  "squisq-tree-item",
@@ -26126,8 +26432,8 @@ var TreeViewExtension = Extension8.create({
26126
26432
 
26127
26433
  // src/treeview/treeViewData.ts
26128
26434
  function useTreeViewData(editor, blockId) {
26129
- const [version, setVersion] = useState45(0);
26130
- useEffect35(() => {
26435
+ const [version, setVersion] = useState46(0);
26436
+ useEffect36(() => {
26131
26437
  const onUpdate = () => setVersion((v2) => v2 + 1);
26132
26438
  editor.on("transaction", onUpdate);
26133
26439
  return () => {
@@ -26153,7 +26459,7 @@ function shouldPasteAsTreeFence(text) {
26153
26459
  }
26154
26460
 
26155
26461
  // src/timeline/timelineData.ts
26156
- import { useEffect as useEffect37, useMemo as useMemo36, useRef as useRef37, useState as useState47 } from "react";
26462
+ import { useEffect as useEffect38, useMemo as useMemo36, useRef as useRef37, useState as useState48 } from "react";
26157
26463
 
26158
26464
  // src/timeline/TimelineViewExtension.ts
26159
26465
  import { Extension as Extension9 } from "@tiptap/core";
@@ -26168,7 +26474,7 @@ import {
26168
26474
  } from "@bendyline/squisq/doc";
26169
26475
 
26170
26476
  // src/timeline/TimelineEditorWidget.tsx
26171
- import { useCallback as useCallback37, useEffect as useEffect36, useMemo as useMemo35, useRef as useRef36, useState as useState46 } from "react";
26477
+ import { useCallback as useCallback37, useEffect as useEffect37, useMemo as useMemo35, useRef as useRef36, useState as useState47 } from "react";
26172
26478
  import {
26173
26479
  asciiTimelineToTemplateData
26174
26480
  } from "@bendyline/squisq/doc";
@@ -26652,13 +26958,13 @@ import { Fragment as Fragment15, jsx as jsx52, jsxs as jsxs40 } from "react/jsx-
26652
26958
  var clamp01 = (value) => Math.max(0, Math.min(1, value));
26653
26959
  function TimelineEditorWidget({ editor, blockId }) {
26654
26960
  const view = useTimelineData(editor, blockId);
26655
- const [selectedEventId, setSelectedEventId] = useState46(null);
26656
- const [addingTrackId, setAddingTrackId] = useState46(null);
26657
- const [editingTrackId, setEditingTrackId] = useState46(null);
26658
- const [hover, setHover] = useState46(null);
26659
- const [dragged, setDragged] = useState46(null);
26961
+ const [selectedEventId, setSelectedEventId] = useState47(null);
26962
+ const [addingTrackId, setAddingTrackId] = useState47(null);
26963
+ const [editingTrackId, setEditingTrackId] = useState47(null);
26964
+ const [hover, setHover] = useState47(null);
26965
+ const [dragged, setDragged] = useState47(null);
26660
26966
  const suppressClickEventId = useRef36(null);
26661
- const [announcement, setAnnouncement] = useState46("");
26967
+ const [announcement, setAnnouncement] = useState47("");
26662
26968
  const dispatch = useCallback37(
26663
26969
  (command) => {
26664
26970
  const result = applyTimelineCommand(editor, blockId, command);
@@ -26721,7 +27027,7 @@ function TimelineEditorWidget({ editor, blockId }) {
26721
27027
  }
26722
27028
  return null;
26723
27029
  }, [selectedEventId, view]);
26724
- useEffect36(() => {
27030
+ useEffect37(() => {
26725
27031
  if (!view) return;
26726
27032
  const ids = new Set(
26727
27033
  view.timeline.tracks.flatMap((track) => track.events.map((event) => event.id))
@@ -26729,13 +27035,13 @@ function TimelineEditorWidget({ editor, blockId }) {
26729
27035
  if (selectedEventId && ids.has(selectedEventId)) return;
26730
27036
  setSelectedEventId(view.timeline.tracks[0]?.events[0]?.id ?? null);
26731
27037
  }, [selectedEventId, view]);
26732
- useEffect36(() => {
27038
+ useEffect37(() => {
26733
27039
  if (editable) return;
26734
27040
  setAddingTrackId(null);
26735
27041
  setEditingTrackId(null);
26736
27042
  setHover(null);
26737
27043
  }, [editable]);
26738
- useEffect36(() => {
27044
+ useEffect37(() => {
26739
27045
  if (!editingTrackId || view?.timeline.tracks.some((track) => track.id === editingTrackId)) {
26740
27046
  return;
26741
27047
  }
@@ -27141,8 +27447,8 @@ function InlineTrackLabel({
27141
27447
  onStart,
27142
27448
  onFinish
27143
27449
  }) {
27144
- const [draft, setDraft] = useState46(label);
27145
- useEffect36(() => {
27450
+ const [draft, setDraft] = useState47(label);
27451
+ useEffect37(() => {
27146
27452
  if (editing) setDraft(label);
27147
27453
  }, [editing, label]);
27148
27454
  const commitLabel = () => {
@@ -27199,10 +27505,10 @@ function EventInspector({
27199
27505
  onDelete
27200
27506
  }) {
27201
27507
  const { event } = selected;
27202
- const [label, setLabel] = useState46(event.label);
27203
- const [description, setDescription] = useState46(event.description ?? "");
27204
- useEffect36(() => setLabel(event.label), [event.label]);
27205
- useEffect36(() => setDescription(event.description ?? ""), [event.description]);
27508
+ const [label, setLabel] = useState47(event.label);
27509
+ const [description, setDescription] = useState47(event.description ?? "");
27510
+ useEffect37(() => setLabel(event.label), [event.label]);
27511
+ useEffect37(() => setDescription(event.description ?? ""), [event.description]);
27206
27512
  const update = (patch) => dispatch(patch);
27207
27513
  const commitLabel = () => {
27208
27514
  const next = label.trim();
@@ -27507,9 +27813,9 @@ var TimelineViewExtension = Extension9.create({
27507
27813
 
27508
27814
  // src/timeline/timelineData.ts
27509
27815
  function useTimelineData(editor, blockId) {
27510
- const [version, setVersion] = useState47(0);
27816
+ const [version, setVersion] = useState48(0);
27511
27817
  const dataCache = useRef37(null);
27512
- useEffect37(() => {
27818
+ useEffect38(() => {
27513
27819
  const onEditorChange = () => setVersion((value) => value + 1);
27514
27820
  editor.on("transaction", onEditorChange);
27515
27821
  editor.on("update", onEditorChange);
@@ -27544,7 +27850,7 @@ function shouldPasteAsTimelineFence(text) {
27544
27850
  }
27545
27851
 
27546
27852
  // src/BlockPropertiesPopover.tsx
27547
- import { useEffect as useEffect38, useId as useId13, useRef as useRef38, useState as useState48 } from "react";
27853
+ import { useEffect as useEffect39, useId as useId13, useRef as useRef38, useState as useState49 } from "react";
27548
27854
  import { createPortal as createPortal9 } from "react-dom";
27549
27855
  import { jsx as jsx53, jsxs as jsxs41 } from "react/jsx-runtime";
27550
27856
  var TRANSITION_FLYOUT = ".squisq-transition-flyout";
@@ -27560,13 +27866,13 @@ function BlockPropertiesPopover({
27560
27866
  }) {
27561
27867
  const panelRef = useRef38(null);
27562
27868
  const panelId = `squisq-block-props-portal-${useId13().replace(/:/g, "")}`;
27563
- const [inner, setInner] = useState48(blockAttrs);
27564
- const [templateInner, setTemplateInner] = useState48(templateParams);
27565
- const [style, setStyle] = useState48(() => computeStyle(anchorRect));
27566
- useEffect38(() => {
27869
+ const [inner, setInner] = useState49(blockAttrs);
27870
+ const [templateInner, setTemplateInner] = useState49(templateParams);
27871
+ const [style, setStyle] = useState49(() => computeStyle(anchorRect));
27872
+ useEffect39(() => {
27567
27873
  requestAnimationFrame(() => setStyle(computeStyle(anchorRect, panelRef.current)));
27568
27874
  }, [anchorRect]);
27569
- useEffect38(() => {
27875
+ useEffect39(() => {
27570
27876
  const onKey = (e2) => {
27571
27877
  if (e2.key === "Escape") onClose();
27572
27878
  };
@@ -27728,7 +28034,7 @@ function persistFromWrite(bodyMd, state) {
27728
28034
  }
27729
28035
 
27730
28036
  // src/WysiwygEditor.tsx
27731
- import { useCallback as useCallback38, useEffect as useEffect42, useMemo as useMemo37, useRef as useRef40, useState as useState52 } from "react";
28037
+ import { useCallback as useCallback38, useEffect as useEffect43, useMemo as useMemo37, useRef as useRef40, useState as useState53 } from "react";
27732
28038
  import { useEditor as useEditor2, EditorContent as EditorContent2 } from "@tiptap/react";
27733
28039
  import { Selection } from "@tiptap/pm/state";
27734
28040
  import StarterKit2 from "@tiptap/starter-kit";
@@ -27741,7 +28047,7 @@ import TaskItem from "@tiptap/extension-task-item";
27741
28047
  import Link2 from "@tiptap/extension-link";
27742
28048
  import Placeholder from "@tiptap/extension-placeholder";
27743
28049
  import { resolveFontFamily as resolveFontFamily2, FONT_FALLBACKS as FONT_FALLBACKS2, VIEWPORT_PRESETS as VIEWPORT_PRESETS3 } from "@bendyline/squisq/schemas";
27744
- import { DEFAULT_THEME as DEFAULT_THEME4, flattenBlocks as flattenBlocks2, markdownToDoc as markdownToDoc2 } from "@bendyline/squisq/doc";
28050
+ import { DEFAULT_THEME as DEFAULT_THEME4, flattenBlocks as flattenBlocks2, markdownToDoc as markdownToDoc3 } from "@bendyline/squisq/doc";
27745
28051
  import { parseMarkdown as parseMarkdown6 } from "@bendyline/squisq/markdown";
27746
28052
 
27747
28053
  // src/mermaid/mermaidThemeStore.ts
@@ -27964,7 +28270,7 @@ var InlineIcon = Node2.create({
27964
28270
  });
27965
28271
 
27966
28272
  // src/ImageNodeView.tsx
27967
- import { useEffect as useEffect39, useRef as useRef39, useState as useState49 } from "react";
28273
+ import { useEffect as useEffect40, useRef as useRef39, useState as useState50 } from "react";
27968
28274
  import { NodeViewWrapper, ReactNodeViewRenderer } from "@tiptap/react";
27969
28275
  import Image from "@tiptap/extension-image";
27970
28276
 
@@ -27984,16 +28290,16 @@ import { Fragment as Fragment16, jsx as jsx54, jsxs as jsxs42 } from "react/jsx-
27984
28290
  function ImageComponent({ node: node2, selected, editor, updateAttributes }) {
27985
28291
  const { src, alt, title, width } = node2.attrs;
27986
28292
  const { mediaProvider, imageDisplayMode, openImageEdit, mediaRevision } = useEditorContext();
27987
- const [resolvedSrc, setResolvedSrc] = useState49(src);
27988
- const [hovered, setHovered] = useState49(false);
28293
+ const [resolvedSrc, setResolvedSrc] = useState50(src);
28294
+ const [hovered, setHovered] = useState50(false);
27989
28295
  const imgRef = useRef39(null);
27990
- const [previewWidth, setPreviewWidth] = useState49(null);
28296
+ const [previewWidth, setPreviewWidth] = useState50(null);
27991
28297
  const isThumbnail = imageDisplayMode === "thumbnail";
27992
28298
  const isEditable = editor?.isEditable ?? true;
27993
28299
  const normalizedRelativePath = normalizeMalformedAssetUrl(src);
27994
28300
  const isRelative = src && !src.startsWith("blob:") && !src.startsWith("http") && !src.startsWith("data:") && !src.startsWith("/");
27995
28301
  const resolveAs = normalizedRelativePath ?? (isRelative ? src : null);
27996
- useEffect39(() => {
28302
+ useEffect40(() => {
27997
28303
  if (!mediaProvider || !resolveAs) {
27998
28304
  setResolvedSrc(src);
27999
28305
  return;
@@ -28172,15 +28478,15 @@ var ImageWithMediaProvider = Image.extend({
28172
28478
  // src/tiptap/TiptapVideo.tsx
28173
28479
  import { Node as Node3, mergeAttributes as mergeAttributes2 } from "@tiptap/core";
28174
28480
  import { NodeViewWrapper as NodeViewWrapper2, ReactNodeViewRenderer as ReactNodeViewRenderer2 } from "@tiptap/react";
28175
- import { useEffect as useEffect41, useState as useState51 } from "react";
28481
+ import { useEffect as useEffect42, useState as useState52 } from "react";
28176
28482
 
28177
28483
  // src/tiptap/useResolvedMediaSrc.ts
28178
- import { useEffect as useEffect40, useState as useState50 } from "react";
28484
+ import { useEffect as useEffect41, useState as useState51 } from "react";
28179
28485
  function useResolvedMediaSrc(src) {
28180
28486
  const { mediaProvider, mediaRevision } = useEditorContext();
28181
- const [resolved, setResolved] = useState50(src);
28487
+ const [resolved, setResolved] = useState51(src);
28182
28488
  const isRelative = !!src && !src.startsWith("blob:") && !src.startsWith("http:") && !src.startsWith("https:") && !src.startsWith("data:") && !src.startsWith("/");
28183
- useEffect40(() => {
28489
+ useEffect41(() => {
28184
28490
  if (!mediaProvider || !isRelative) {
28185
28491
  setResolved(src);
28186
28492
  return;
@@ -28231,9 +28537,9 @@ function VideoNodeView({ node: node2, updateAttributes, selected }) {
28231
28537
  const placement = normalizeVideoPlacement(rawPlacement);
28232
28538
  const lockToBlock = normalizeLockToBlock(rawLockToBlock);
28233
28539
  const resolvedSrc = useResolvedMediaSrc(src ?? "");
28234
- const [audioOnly, setAudioOnly] = useState51(false);
28540
+ const [audioOnly, setAudioOnly] = useState52(false);
28235
28541
  const resolvedPoster = useResolvedMediaSrc(poster ?? "");
28236
- useEffect41(() => {
28542
+ useEffect42(() => {
28237
28543
  setAudioOnly(false);
28238
28544
  }, [resolvedSrc]);
28239
28545
  const handleLoadedMetadata = (event) => {
@@ -28900,11 +29206,11 @@ function WysiwygEditor({
28900
29206
  mermaidThemeStoreRef.current = createMermaidThemeStore(activeTheme ?? DEFAULT_THEME4);
28901
29207
  }
28902
29208
  const mermaidThemeStore = mermaidThemeStoreRef.current;
28903
- useEffect42(() => {
29209
+ useEffect43(() => {
28904
29210
  mermaidThemeStore.setTheme(activeTheme ?? DEFAULT_THEME4);
28905
29211
  }, [activeTheme, mermaidThemeStore]);
28906
29212
  const { docTemplates, onDocTemplatesChange } = useDocCustomTemplates();
28907
- const [designerState, setDesignerState] = useState52(
29213
+ const [designerState, setDesignerState] = useState53(
28908
29214
  null
28909
29215
  );
28910
29216
  const handleDesignerSave = useCallback38(
@@ -28920,15 +29226,15 @@ function WysiwygEditor({
28920
29226
  [docTemplates, onDocTemplatesChange]
28921
29227
  );
28922
29228
  const mentionProviderRef = useRef40(mentionProvider);
28923
- useEffect42(() => {
29229
+ useEffect43(() => {
28924
29230
  mentionProviderRef.current = mentionProvider;
28925
29231
  }, [mentionProvider]);
28926
29232
  const fenceRenderersRef = useRef40(fenceRenderers);
28927
- useEffect42(() => {
29233
+ useEffect43(() => {
28928
29234
  fenceRenderersRef.current = fenceRenderers;
28929
29235
  }, [fenceRenderers]);
28930
29236
  const activeThemeRef = useRef40(activeTheme);
28931
- useEffect42(() => {
29237
+ useEffect43(() => {
28932
29238
  activeThemeRef.current = activeTheme;
28933
29239
  }, [activeTheme]);
28934
29240
  const resolvedPlaceholder = useMemo37(() => placeholder ?? pickEmptyPrompt(), [placeholder]);
@@ -28936,7 +29242,7 @@ function WysiwygEditor({
28936
29242
  const lastSourceRef = useRef40(editorSource);
28937
29243
  const pendingLocalSourcesRef = useRef40([]);
28938
29244
  const mediaProviderRef = useRef40(mediaProvider);
28939
- useEffect42(() => {
29245
+ useEffect43(() => {
28940
29246
  mediaProviderRef.current = mediaProvider;
28941
29247
  }, [mediaProvider]);
28942
29248
  const frontmatterRef = useRef40(stripFrontmatter(editorSource).frontmatter);
@@ -28953,7 +29259,7 @@ function WysiwygEditor({
28953
29259
  }
28954
29260
  }
28955
29261
  const submitOnEnterRef = useRef40(submitOnEnter);
28956
- useEffect42(() => {
29262
+ useEffect43(() => {
28957
29263
  submitOnEnterRef.current = submitOnEnter;
28958
29264
  }, [submitOnEnter]);
28959
29265
  const editor = useEditor2({
@@ -29141,25 +29447,25 @@ function WysiwygEditor({
29141
29447
  }
29142
29448
  }
29143
29449
  });
29144
- useEffect42(() => {
29450
+ useEffect43(() => {
29145
29451
  if (editor) {
29146
29452
  setTiptapEditor(editor);
29147
29453
  }
29148
29454
  return () => setTiptapEditor(null);
29149
29455
  }, [editor, setTiptapEditor]);
29150
- useEffect42(() => {
29456
+ useEffect43(() => {
29151
29457
  if (editor) editor.setEditable(!readOnly);
29152
29458
  }, [editor, readOnly]);
29153
29459
  const containerRef = useRef40(null);
29154
- const [badgeMenu, setBadgeMenu] = useState52(null);
29155
- const [propsMenu, setPropsMenu] = useState52(null);
29460
+ const [badgeMenu, setBadgeMenu] = useState53(null);
29461
+ const [propsMenu, setPropsMenu] = useState53(null);
29156
29462
  const closeBadgeMenu = useCallback38(() => {
29157
29463
  setBadgeMenu(null);
29158
29464
  requestAnimationFrame(() => {
29159
29465
  if (editor && !editor.isDestroyed) editor.commands.focus();
29160
29466
  });
29161
29467
  }, [editor]);
29162
- useEffect42(() => {
29468
+ useEffect43(() => {
29163
29469
  if (!editor) return;
29164
29470
  const root = containerRef.current;
29165
29471
  if (!root) return;
@@ -29215,7 +29521,7 @@ function WysiwygEditor({
29215
29521
  root.addEventListener("mousedown", onClick);
29216
29522
  return () => root.removeEventListener("mousedown", onClick);
29217
29523
  }, [editor]);
29218
- useEffect42(() => {
29524
+ useEffect43(() => {
29219
29525
  if (!editor) return;
29220
29526
  const pendingIndex = pendingLocalSourcesRef.current.lastIndexOf(editorSource);
29221
29527
  if (pendingIndex >= 0) {
@@ -29250,7 +29556,7 @@ function WysiwygEditor({
29250
29556
  const badgePreviewSource = useMemo37(() => {
29251
29557
  if (!badgeMenu) return void 0;
29252
29558
  try {
29253
- const previewDoc = markdownToDoc2(parseMarkdown6(editorSource), { autoTemplates: false });
29559
+ const previewDoc = markdownToDoc3(parseMarkdown6(editorSource), { autoTemplates: false });
29254
29560
  const block = flattenBlocks2(previewDoc.blocks)[badgeMenu.headingIndex];
29255
29561
  if (!block) return void 0;
29256
29562
  return {
@@ -29472,7 +29778,7 @@ function moveSelectionToDropPoint(view, event) {
29472
29778
  }
29473
29779
 
29474
29780
  // src/InlinePreviewGutter.tsx
29475
- import { useLayoutEffect as useLayoutEffect8, useMemo as useMemo39, useRef as useRef41, useState as useState54 } from "react";
29781
+ import { useLayoutEffect as useLayoutEffect8, useMemo as useMemo39, useRef as useRef41, useState as useState55 } from "react";
29476
29782
  import { VIEWPORT_PRESETS as VIEWPORT_PRESETS4 } from "@bendyline/squisq/schemas";
29477
29783
  import {
29478
29784
  flattenBlocks as flattenBlocks4,
@@ -29484,14 +29790,14 @@ import { extractPlainText as extractPlainText3, getChildren } from "@bendyline/s
29484
29790
  import { BlockRenderer as BlockRenderer3, MediaContext as MediaContext4 } from "@bendyline/squisq-react";
29485
29791
 
29486
29792
  // src/useHeadingLayout.ts
29487
- import { useCallback as useCallback39, useEffect as useEffect43, useMemo as useMemo38, useState as useState53 } from "react";
29793
+ import { useCallback as useCallback39, useEffect as useEffect44, useMemo as useMemo38, useState as useState54 } from "react";
29488
29794
  import { flattenBlocks as flattenBlocks3, hasTemplate } from "@bendyline/squisq/doc";
29489
29795
  function useHeadingLayout(refInsideWrapper) {
29490
29796
  const { doc, activeView, monacoEditor, tiptapEditor } = useEditorContext();
29491
29797
  const flatBlocks = useMemo38(() => doc ? flattenBlocks3(doc.blocks) : [], [doc]);
29492
- const [entries, setEntries] = useState53([]);
29493
- const [pageEdges, setPageEdges] = useState53(null);
29494
- useEffect43(() => {
29798
+ const [entries, setEntries] = useState54([]);
29799
+ const [pageEdges, setPageEdges] = useState54(null);
29800
+ useEffect44(() => {
29495
29801
  if (activeView !== "wysiwyg") return;
29496
29802
  const node2 = refInsideWrapper.current;
29497
29803
  if (!node2) return;
@@ -29559,7 +29865,7 @@ function useHeadingLayout(refInsideWrapper) {
29559
29865
  window.removeEventListener("resize", recompute);
29560
29866
  };
29561
29867
  }, [activeView, flatBlocks, refInsideWrapper]);
29562
- useEffect43(() => {
29868
+ useEffect44(() => {
29563
29869
  if (activeView !== "raw") return;
29564
29870
  if (!monacoEditor) return;
29565
29871
  const node2 = refInsideWrapper.current;
@@ -29620,7 +29926,7 @@ function useHeadingLayout(refInsideWrapper) {
29620
29926
  window.removeEventListener("resize", recompute);
29621
29927
  };
29622
29928
  }, [activeView, monacoEditor, flatBlocks, refInsideWrapper]);
29623
- useEffect43(() => {
29929
+ useEffect44(() => {
29624
29930
  setEntries([]);
29625
29931
  setPageEdges(null);
29626
29932
  }, [activeView]);
@@ -29914,7 +30220,7 @@ function InlinePreviewGutter({
29914
30220
  headingEntries.forEach((e2) => m.set(e2.block.id, e2.top));
29915
30221
  return m;
29916
30222
  }, [headingEntries]);
29917
- const [positions, setPositions] = useState54(/* @__PURE__ */ new Map());
30223
+ const [positions, setPositions] = useState55(/* @__PURE__ */ new Map());
29918
30224
  useLayoutEffect8(() => {
29919
30225
  if (items.length === 0) {
29920
30226
  setPositions((prev) => prev.size === 0 ? prev : /* @__PURE__ */ new Map());
@@ -30103,11 +30409,12 @@ import {
30103
30409
 
30104
30410
  // src/OutlinePanel.tsx
30105
30411
  import {
30412
+ memo,
30106
30413
  useCallback as useCallback40,
30107
- useEffect as useEffect44,
30414
+ useEffect as useEffect45,
30108
30415
  useMemo as useMemo40,
30109
30416
  useRef as useRef42,
30110
- useState as useState55
30417
+ useState as useState56
30111
30418
  } from "react";
30112
30419
  import { flattenBlocks as flattenBlocks5, hasTemplate as hasTemplate2 } from "@bendyline/squisq/doc";
30113
30420
  import { extractPlainText as extractPlainText4 } from "@bendyline/squisq/markdown";
@@ -30210,8 +30517,9 @@ function splitTrailingLineGap(value) {
30210
30517
 
30211
30518
  // src/OutlinePanel.tsx
30212
30519
  import { jsx as jsx59, jsxs as jsxs46 } from "react/jsx-runtime";
30213
- var OUTLINE_RESPONSIVE_WIDTH = "clamp(180px, 15vw, 260px)";
30520
+ var OUTLINE_RESPONSIVE_WIDTH = "clamp(260px, 30vw, 460px)";
30214
30521
  var OUTLINE_DRAG_MIME = "application/x-squisq-outline-section";
30522
+ var EMPTY_BLOCKS = [];
30215
30523
  function OutlinePanel({ width, className, readOnly = false }) {
30216
30524
  const {
30217
30525
  doc,
@@ -30220,14 +30528,44 @@ function OutlinePanel({ width, className, readOnly = false }) {
30220
30528
  isParsing,
30221
30529
  layoutMode,
30222
30530
  goToBlockByLine,
30223
- activeBlockStartLine
30531
+ activeBlockStartLine,
30532
+ activeView,
30533
+ monacoEditor,
30534
+ tiptapEditor
30224
30535
  } = useEditorContext();
30225
30536
  const paneRef = useRef42(null);
30226
- const { scrollToBlock } = useHeadingLayout(paneRef);
30227
30537
  const cursorActiveId = useActiveOutlineBlockId();
30228
30538
  const activeDragRef = useRef42(null);
30229
- const [draggedBlockId, setDraggedBlockId] = useState55(null);
30230
- const [dropTarget, setDropTarget] = useState55(null);
30539
+ const [draggedBlockId, setDraggedBlockId] = useState56(null);
30540
+ const [dropTarget, setDropTarget] = useState56(null);
30541
+ const nextOutlineBlocks = doc?.blocks ?? EMPTY_BLOCKS;
30542
+ const outlineBlocksRef = useRef42(nextOutlineBlocks);
30543
+ if (!sameOutlineStructure(outlineBlocksRef.current, nextOutlineBlocks)) {
30544
+ outlineBlocksRef.current = nextOutlineBlocks;
30545
+ }
30546
+ const outlineBlocks = outlineBlocksRef.current;
30547
+ const interactionRef = useRef42({
30548
+ doc,
30549
+ markdownSource,
30550
+ setMarkdownSource,
30551
+ isParsing,
30552
+ layoutMode,
30553
+ goToBlockByLine,
30554
+ activeView,
30555
+ monacoEditor,
30556
+ tiptapEditor
30557
+ });
30558
+ interactionRef.current = {
30559
+ doc,
30560
+ markdownSource,
30561
+ setMarkdownSource,
30562
+ isParsing,
30563
+ layoutMode,
30564
+ goToBlockByLine,
30565
+ activeView,
30566
+ monacoEditor,
30567
+ tiptapEditor
30568
+ };
30231
30569
  const blockModeActiveId = useMemo40(() => {
30232
30570
  if (layoutMode !== "block" || activeBlockStartLine == null || !doc) return null;
30233
30571
  const match = flattenBlocks5(doc.blocks).find(
@@ -30236,28 +30574,53 @@ function OutlinePanel({ width, className, readOnly = false }) {
30236
30574
  return match?.id ?? null;
30237
30575
  }, [layoutMode, activeBlockStartLine, doc]);
30238
30576
  const activeBlockId = layoutMode === "block" ? blockModeActiveId : cursorActiveId;
30239
- const handleSelect = useCallback40(
30240
- (block) => {
30241
- if (layoutMode === "block") {
30242
- const line = block.sourceHeading?.position?.start.line;
30243
- if (typeof line === "number") {
30244
- goToBlockByLine(line);
30245
- return;
30577
+ const handleSelect = useCallback40((outlineBlock) => {
30578
+ const current = interactionRef.current;
30579
+ const block = findLiveBlock(current.doc, outlineBlock.id) ?? outlineBlock;
30580
+ if (current.layoutMode === "block") {
30581
+ const line = block.sourceHeading?.position?.start.line;
30582
+ if (typeof line === "number") {
30583
+ current.goToBlockByLine(line);
30584
+ return;
30585
+ }
30586
+ }
30587
+ if (current.activeView === "wysiwyg") {
30588
+ const wrapper = paneRef.current ? findEditorWrapper(paneRef.current) : null;
30589
+ const wysiwygContainer = wrapper?.querySelector(".squisq-wysiwyg-container");
30590
+ if (!wysiwygContainer) return;
30591
+ const headings = wysiwygContainer.querySelectorAll("h1, h2, h3, h4, h5, h6");
30592
+ const blocks = current.doc ? flattenBlocks5(current.doc.blocks) : [];
30593
+ const index2 = blocks.findIndex((candidate) => candidate.id === block.id);
30594
+ if (index2 < 0 || index2 >= headings.length) return;
30595
+ headings[index2].scrollIntoView({ behavior: "smooth", block: "start" });
30596
+ if (current.tiptapEditor) {
30597
+ try {
30598
+ current.tiptapEditor.chain().focus().run();
30599
+ } catch {
30246
30600
  }
30247
30601
  }
30248
- scrollToBlock(block);
30249
- },
30250
- [layoutMode, goToBlockByLine, scrollToBlock]
30251
- );
30602
+ return;
30603
+ }
30604
+ if (current.activeView === "raw" && current.monacoEditor) {
30605
+ const line = block.sourceHeading?.position?.start.line;
30606
+ if (typeof line === "number") {
30607
+ current.monacoEditor.revealLineInCenter(line);
30608
+ current.monacoEditor.setPosition({ lineNumber: line, column: 1 });
30609
+ current.monacoEditor.focus();
30610
+ }
30611
+ }
30612
+ }, []);
30252
30613
  const changeHeadingLevel = useCallback40(
30253
- (block, delta) => {
30254
- if (readOnly || isParsing) return;
30614
+ (outlineBlock, delta) => {
30615
+ const current = interactionRef.current;
30616
+ if (readOnly || current.isParsing) return;
30617
+ const block = findLiveBlock(current.doc, outlineBlock.id) ?? outlineBlock;
30255
30618
  const line = block.sourceHeading?.position?.start.line;
30256
30619
  if (typeof line !== "number") return;
30257
- const next = bumpHeadingLevelInSource(markdownSource, line, delta);
30258
- if (next != null) setMarkdownSource(next);
30620
+ const next = bumpHeadingLevelInSource(current.markdownSource, line, delta);
30621
+ if (next != null) current.setMarkdownSource(next);
30259
30622
  },
30260
- [isParsing, markdownSource, readOnly, setMarkdownSource]
30623
+ [readOnly]
30261
30624
  );
30262
30625
  const clearDragState = useCallback40(() => {
30263
30626
  activeDragRef.current = null;
@@ -30266,26 +30629,31 @@ function OutlinePanel({ width, className, readOnly = false }) {
30266
30629
  }, []);
30267
30630
  const handleDragStart = useCallback40(
30268
30631
  (event, section) => {
30269
- if (readOnly || isParsing) {
30632
+ const current = interactionRef.current;
30633
+ if (readOnly || current.isParsing) {
30270
30634
  event.preventDefault();
30271
30635
  return;
30272
30636
  }
30273
- activeDragRef.current = { ...section, sourceAtStart: markdownSource };
30637
+ const liveBlock = findLiveBlock(current.doc, section.blockId);
30638
+ const liveHeadingLine = liveBlock?.sourceHeading?.position?.start.line;
30639
+ const liveSection = typeof liveHeadingLine === "number" ? { ...section, headingLine: liveHeadingLine } : section;
30640
+ activeDragRef.current = { ...liveSection, sourceAtStart: current.markdownSource };
30274
30641
  setDraggedBlockId(section.blockId);
30275
30642
  setDropTarget(null);
30276
30643
  event.dataTransfer.effectAllowed = "move";
30277
- event.dataTransfer.setData(OUTLINE_DRAG_MIME, String(section.headingLine));
30278
- event.dataTransfer.setData("text/plain", String(section.headingLine));
30644
+ event.dataTransfer.setData(OUTLINE_DRAG_MIME, String(liveSection.headingLine));
30645
+ event.dataTransfer.setData("text/plain", String(liveSection.headingLine));
30279
30646
  },
30280
- [isParsing, markdownSource, readOnly]
30647
+ [readOnly]
30281
30648
  );
30282
30649
  const handleDragOver = useCallback40(
30283
30650
  (event, section) => {
30284
30651
  const dragged = activeDragRef.current;
30285
30652
  if (!dragged) return;
30653
+ const current = interactionRef.current;
30286
30654
  event.preventDefault();
30287
30655
  event.stopPropagation();
30288
- if (!canDropOnSection(dragged, section) || readOnly || isParsing) {
30656
+ if (!canDropOnSection(dragged, section) || readOnly || current.isParsing) {
30289
30657
  event.dataTransfer.dropEffect = "none";
30290
30658
  setDropTarget(null);
30291
30659
  return;
@@ -30293,33 +30661,37 @@ function OutlinePanel({ width, className, readOnly = false }) {
30293
30661
  const placement = placementForPointer(event);
30294
30662
  event.dataTransfer.dropEffect = "move";
30295
30663
  setDropTarget(
30296
- (current) => current?.blockId === section.blockId && current.placement === placement ? current : { blockId: section.blockId, placement }
30664
+ (current2) => current2?.blockId === section.blockId && current2.placement === placement ? current2 : { blockId: section.blockId, placement }
30297
30665
  );
30298
30666
  },
30299
- [isParsing, readOnly]
30667
+ [readOnly]
30300
30668
  );
30301
30669
  const handleDrop = useCallback40(
30302
30670
  (event, section) => {
30303
30671
  const dragged = activeDragRef.current;
30304
30672
  if (!dragged) return;
30673
+ const current = interactionRef.current;
30305
30674
  event.preventDefault();
30306
30675
  event.stopPropagation();
30307
30676
  const placement = placementForPointer(event);
30308
- const canMove = !readOnly && !isParsing && dragged.sourceAtStart === markdownSource && canDropOnSection(dragged, section);
30677
+ const liveTarget = findLiveBlock(current.doc, section.blockId);
30678
+ const liveTargetLine = liveTarget?.sourceHeading?.position?.start.line;
30679
+ const targetSection = typeof liveTargetLine === "number" ? { ...section, headingLine: liveTargetLine } : section;
30680
+ const canMove = !readOnly && !current.isParsing && dragged.sourceAtStart === current.markdownSource && canDropOnSection(dragged, targetSection);
30309
30681
  const next = canMove ? moveHeadingSectionInSource(
30310
- markdownSource,
30682
+ current.markdownSource,
30311
30683
  dragged.headingLine,
30312
- section.headingLine,
30684
+ targetSection.headingLine,
30313
30685
  placement
30314
30686
  ) : null;
30315
30687
  clearDragState();
30316
- if (next != null) setMarkdownSource(next);
30688
+ if (next != null) current.setMarkdownSource(next);
30317
30689
  },
30318
- [clearDragState, isParsing, markdownSource, readOnly, setMarkdownSource]
30690
+ [clearDragState, readOnly]
30319
30691
  );
30320
30692
  const previewSettings = usePreviewSettingsOptional();
30321
30693
  const accentColor = previewSettings?.activeTheme?.colors?.primary;
30322
- const isEmpty2 = !doc || doc.blocks.length === 0 || !hasAnyHeading(doc.blocks);
30694
+ const isEmpty2 = outlineBlocks.length === 0 || !hasAnyHeading(outlineBlocks);
30323
30695
  const basis = width != null ? `${width}px` : `var(--squisq-outline-width, ${OUTLINE_RESPONSIVE_WIDTH})`;
30324
30696
  const paneStyle = {
30325
30697
  width: basis,
@@ -30335,27 +30707,28 @@ function OutlinePanel({ width, className, readOnly = false }) {
30335
30707
  style: paneStyle,
30336
30708
  "data-testid": "outline-panel",
30337
30709
  "aria-label": "Document outline",
30338
- children: isEmpty2 ? /* @__PURE__ */ jsx59("div", { className: "squisq-outline-empty", children: /* @__PURE__ */ jsx59("p", { children: "Add a heading to populate the outline." }) }) : /* @__PURE__ */ jsx59("ul", { className: "squisq-outline-tree", role: "tree", children: doc.blocks.map((b) => /* @__PURE__ */ jsx59(
30339
- OutlineNode,
30710
+ children: isEmpty2 ? /* @__PURE__ */ jsx59("div", { className: "squisq-outline-empty", children: /* @__PURE__ */ jsx59("p", { children: "Add a heading to populate the outline." }) }) : /* @__PURE__ */ jsx59(
30711
+ OutlineTree,
30340
30712
  {
30341
- block: b,
30342
- parentId: null,
30713
+ blocks: outlineBlocks,
30343
30714
  activeBlockId,
30344
30715
  draggedBlockId,
30345
30716
  dropTarget,
30346
- mutationsDisabled: readOnly || isParsing,
30717
+ mutationsDisabled: readOnly,
30347
30718
  onSelect: handleSelect,
30348
30719
  onChangeLevel: changeHeadingLevel,
30349
30720
  onDragStart: handleDragStart,
30350
30721
  onDragOver: handleDragOver,
30351
30722
  onDrop: handleDrop,
30352
30723
  onDragEnd: clearDragState
30353
- },
30354
- b.id
30355
- )) })
30724
+ }
30725
+ )
30356
30726
  }
30357
30727
  );
30358
30728
  }
30729
+ var OutlineTree = memo(function OutlineTree2({ blocks, ...nodeProps }) {
30730
+ return /* @__PURE__ */ jsx59("ul", { className: "squisq-outline-tree", role: "tree", children: blocks.map((block) => /* @__PURE__ */ jsx59(OutlineNode, { block, parentId: null, ...nodeProps }, block.id)) });
30731
+ });
30359
30732
  function OutlineNode({
30360
30733
  block,
30361
30734
  parentId,
@@ -30494,11 +30867,14 @@ function OutlineNode({
30494
30867
  function useActiveOutlineBlockId() {
30495
30868
  const { doc, activeView, tiptapEditor, monacoEditor } = useEditorContext();
30496
30869
  const flatBlocks = useMemo40(() => doc ? flattenBlocks5(doc.blocks) : [], [doc]);
30497
- const [activeId, setActiveId] = useState55(null);
30498
- useEffect44(() => {
30870
+ const flatBlocksRef = useRef42(flatBlocks);
30871
+ flatBlocksRef.current = flatBlocks;
30872
+ const headingLocations = flatBlocks.map((block) => `${block.id}:${block.sourceHeading?.position?.start.line ?? ""}`).join("\0");
30873
+ const [activeId, setActiveId] = useState56(null);
30874
+ useEffect45(() => {
30499
30875
  setActiveId(null);
30500
30876
  }, [activeView]);
30501
- useEffect44(() => {
30877
+ useEffect45(() => {
30502
30878
  if (activeView !== "wysiwyg" || !tiptapEditor) return;
30503
30879
  const update = () => {
30504
30880
  const { from } = tiptapEditor.state.selection;
@@ -30509,7 +30885,7 @@ function useActiveOutlineBlockId() {
30509
30885
  seen += 1;
30510
30886
  if (offset <= from) lastIndex = seen;
30511
30887
  });
30512
- const block = lastIndex >= 0 ? flatBlocks[lastIndex] : null;
30888
+ const block = lastIndex >= 0 ? flatBlocksRef.current[lastIndex] : null;
30513
30889
  setActiveId(block?.id ?? null);
30514
30890
  };
30515
30891
  update();
@@ -30519,8 +30895,8 @@ function useActiveOutlineBlockId() {
30519
30895
  tiptapEditor.off("selectionUpdate", update);
30520
30896
  tiptapEditor.off("update", update);
30521
30897
  };
30522
- }, [activeView, tiptapEditor, flatBlocks]);
30523
- useEffect44(() => {
30898
+ }, [activeView, tiptapEditor, headingLocations]);
30899
+ useEffect45(() => {
30524
30900
  if (activeView !== "raw" || !monacoEditor) return;
30525
30901
  const update = () => {
30526
30902
  const line = monacoEditor.getPosition()?.lineNumber;
@@ -30529,19 +30905,55 @@ function useActiveOutlineBlockId() {
30529
30905
  return;
30530
30906
  }
30531
30907
  let lastIndex = -1;
30532
- flatBlocks.forEach((b, i) => {
30908
+ flatBlocksRef.current.forEach((b, i) => {
30533
30909
  const headingLine2 = b.sourceHeading?.position?.start.line;
30534
30910
  if (typeof headingLine2 === "number" && headingLine2 <= line) lastIndex = i;
30535
30911
  });
30536
- const block = lastIndex >= 0 ? flatBlocks[lastIndex] : null;
30912
+ const block = lastIndex >= 0 ? flatBlocksRef.current[lastIndex] : null;
30537
30913
  setActiveId(block?.id ?? null);
30538
30914
  };
30539
30915
  update();
30540
30916
  const sub = monacoEditor.onDidChangeCursorPosition(update);
30541
30917
  return () => sub.dispose();
30542
- }, [activeView, monacoEditor, flatBlocks]);
30918
+ }, [activeView, monacoEditor, headingLocations]);
30543
30919
  return activeId;
30544
30920
  }
30921
+ function sameOutlineStructure(previous, next) {
30922
+ if (previous === next) return true;
30923
+ if (previous.length !== next.length) return false;
30924
+ for (let index2 = 0; index2 < previous.length; index2 += 1) {
30925
+ const before = previous[index2];
30926
+ const after = next[index2];
30927
+ if (before.id !== after.id) return false;
30928
+ const beforeHeading = before.sourceHeading;
30929
+ const afterHeading = after.sourceHeading;
30930
+ if (!!beforeHeading !== !!afterHeading) return false;
30931
+ if (beforeHeading && afterHeading) {
30932
+ if (beforeHeading.depth !== afterHeading.depth) return false;
30933
+ if (extractPlainText4(beforeHeading).trim() !== extractPlainText4(afterHeading).trim()) {
30934
+ return false;
30935
+ }
30936
+ if (beforeHeading.templateAnnotation?.template !== afterHeading.templateAnnotation?.template) {
30937
+ return false;
30938
+ }
30939
+ }
30940
+ if (!sameOutlineStructure(before.children ?? EMPTY_BLOCKS, after.children ?? EMPTY_BLOCKS)) {
30941
+ return false;
30942
+ }
30943
+ }
30944
+ return true;
30945
+ }
30946
+ function findLiveBlock(doc, blockId) {
30947
+ return doc ? flattenBlocks5(doc.blocks).find((block) => block.id === blockId) ?? null : null;
30948
+ }
30949
+ function findEditorWrapper(node2) {
30950
+ let current = node2.parentElement;
30951
+ while (current) {
30952
+ if (current.classList.contains("squisq-editor-with-gutter")) return current;
30953
+ current = current.parentElement;
30954
+ }
30955
+ return node2.parentElement;
30956
+ }
30545
30957
  function canDropOnSection(dragged, target) {
30546
30958
  return dragged.blockId !== target.blockId && dragged.depth === target.depth && dragged.parentId === target.parentId;
30547
30959
  }
@@ -30571,7 +30983,7 @@ function bumpHeadingLevelInSource(source, line, delta) {
30571
30983
  }
30572
30984
 
30573
30985
  // src/codeContext/CodeContextZones.tsx
30574
- import { useCallback as useCallback42, useEffect as useEffect46, useMemo as useMemo42, useRef as useRef44, useState as useState56 } from "react";
30986
+ import { useCallback as useCallback42, useEffect as useEffect47, useMemo as useMemo42, useRef as useRef44, useState as useState57 } from "react";
30575
30987
  import { createPortal as createPortal10 } from "react-dom";
30576
30988
 
30577
30989
  // src/codeContext/diffContextSections.ts
@@ -30695,7 +31107,7 @@ function setOrdinal(zone, ordinal) {
30695
31107
  // src/codeContext/CodeContextSectionView.tsx
30696
31108
  import { parseMarkdown as parseMarkdown8 } from "@bendyline/squisq/markdown";
30697
31109
  import { MarkdownRenderer } from "@bendyline/squisq-react";
30698
- import { useCallback as useCallback41, useEffect as useEffect45, useMemo as useMemo41, useRef as useRef43 } from "react";
31110
+ import { useCallback as useCallback41, useEffect as useEffect46, useMemo as useMemo41, useRef as useRef43 } from "react";
30699
31111
  import { jsx as jsx60, jsxs as jsxs47 } from "react/jsx-runtime";
30700
31112
  function CodeContextSectionView({
30701
31113
  section,
@@ -30715,7 +31127,7 @@ function CodeContextSectionView({
30715
31127
  () => expanded && section.markdown ? parseMarkdown8(section.markdown).children : null,
30716
31128
  [expanded, section.markdown]
30717
31129
  );
30718
- useEffect45(() => {
31130
+ useEffect46(() => {
30719
31131
  const el = rootRef.current;
30720
31132
  if (!el || typeof ResizeObserver === "undefined") return;
30721
31133
  const report = () => {
@@ -30787,11 +31199,11 @@ function CodeContextSectionView({
30787
31199
  import { Fragment as Fragment18, jsx as jsx61 } from "react/jsx-runtime";
30788
31200
  function CodeContextZones({ options }) {
30789
31201
  const { monacoEditor } = useEditorContext();
30790
- const [manager, setManager] = useState56(null);
30791
- const [, setZonesVersion] = useState56(0);
30792
- const [expandedById, setExpandedById] = useState56({});
31202
+ const [manager, setManager] = useState57(null);
31203
+ const [, setZonesVersion] = useState57(0);
31204
+ const [expandedById, setExpandedById] = useState57({});
30793
31205
  const seenIds = useRef44(/* @__PURE__ */ new Set());
30794
- useEffect46(() => {
31206
+ useEffect47(() => {
30795
31207
  if (!monacoEditor) return;
30796
31208
  const mgr = new CodeContextZoneManager(monacoEditor);
30797
31209
  const off = mgr.onDidChangeZones(() => setZonesVersion((v2) => v2 + 1));
@@ -30814,7 +31226,7 @@ function CodeContextZones({ options }) {
30814
31226
  });
30815
31227
  return out;
30816
31228
  }, [fileTop, sections]);
30817
- useEffect46(() => {
31229
+ useEffect47(() => {
30818
31230
  if (!manager) return;
30819
31231
  manager.sync(resolved.map((r) => r.spec));
30820
31232
  setExpandedById((prev) => {
@@ -31250,7 +31662,7 @@ function placeClipInBlock(source, fromLine, targetHeadingLine, spec, startAt) {
31250
31662
  }
31251
31663
 
31252
31664
  // src/TimelineTrack.tsx
31253
- import { useCallback as useCallback44, useEffect as useEffect49, useMemo as useMemo43, useRef as useRef47, useState as useState59 } from "react";
31665
+ import { useCallback as useCallback44, useEffect as useEffect50, useMemo as useMemo43, useRef as useRef47, useState as useState60 } from "react";
31254
31666
  import {
31255
31667
  resolveMediaSchedule as resolveMediaSchedule2,
31256
31668
  getDocPlaybackDuration,
@@ -31375,11 +31787,11 @@ function collectTimelinePlaybackSchedule(doc, scheduled) {
31375
31787
  }
31376
31788
 
31377
31789
  // src/TimelineBlockPreview.tsx
31378
- import { memo } from "react";
31790
+ import { memo as memo2 } from "react";
31379
31791
  import { VIEWPORT_PRESETS as VIEWPORT_PRESETS5 } from "@bendyline/squisq/schemas";
31380
31792
  import { BlockRenderer as BlockRenderer4, MediaContext as MediaContext5 } from "@bendyline/squisq-react";
31381
31793
  import { jsx as jsx63 } from "react/jsx-runtime";
31382
- var BlockThumbnail = memo(function BlockThumbnail2({
31794
+ var BlockThumbnail = memo2(function BlockThumbnail2({
31383
31795
  visual,
31384
31796
  viewport = VIEWPORT_PRESETS5.landscape,
31385
31797
  basePath = "/",
@@ -31409,7 +31821,7 @@ function resolveBlockVisual(doc, block, theme, viewport) {
31409
31821
  }
31410
31822
 
31411
31823
  // src/useTimelineClock.ts
31412
- import { useCallback as useCallback43, useEffect as useEffect47, useRef as useRef45, useState as useState57 } from "react";
31824
+ import { useCallback as useCallback43, useEffect as useEffect48, useRef as useRef45, useState as useState58 } from "react";
31413
31825
  function advanceTime(prev, dt, total) {
31414
31826
  if (total <= 0) return 0;
31415
31827
  return Math.min(total, Math.max(0, prev + dt));
@@ -31429,8 +31841,8 @@ function playTimelineMediaAt(root, time) {
31429
31841
  });
31430
31842
  }
31431
31843
  function useTimelineClock(total) {
31432
- const [currentTime, setCurrentTime] = useState57(0);
31433
- const [isPlaying, setIsPlaying] = useState57(false);
31844
+ const [currentTime, setCurrentTime] = useState58(0);
31845
+ const [isPlaying, setIsPlaying] = useState58(false);
31434
31846
  const rafRef = useRef45(null);
31435
31847
  const lastRef = useRef45(0);
31436
31848
  const currentTimeRef = useRef45(0);
@@ -31438,10 +31850,10 @@ function useTimelineClock(total) {
31438
31850
  const mediaHostRef = useRef45(null);
31439
31851
  currentTimeRef.current = currentTime;
31440
31852
  isPlayingRef.current = isPlaying;
31441
- useEffect47(() => {
31853
+ useEffect48(() => {
31442
31854
  setCurrentTime((t) => Math.min(t, Math.max(0, total)));
31443
31855
  }, [total]);
31444
- useEffect47(() => {
31856
+ useEffect48(() => {
31445
31857
  if (!isPlaying) return;
31446
31858
  lastRef.current = performance.now();
31447
31859
  const tick = (now) => {
@@ -31503,7 +31915,7 @@ function timelineMediaLabel(src, kind) {
31503
31915
  }
31504
31916
 
31505
31917
  // src/TimelineItemMenu.tsx
31506
- import { useEffect as useEffect48, useLayoutEffect as useLayoutEffect9, useRef as useRef46, useState as useState58 } from "react";
31918
+ import { useEffect as useEffect49, useLayoutEffect as useLayoutEffect9, useRef as useRef46, useState as useState59 } from "react";
31507
31919
  import { createPortal as createPortal11 } from "react-dom";
31508
31920
  import { Fragment as Fragment19, jsx as jsx64, jsxs as jsxs49 } from "react/jsx-runtime";
31509
31921
  function TimelineItemMenu({
@@ -31518,7 +31930,7 @@ function TimelineItemMenu({
31518
31930
  onClose
31519
31931
  }) {
31520
31932
  const panelRef = useRef46(null);
31521
- const [style, setStyle] = useState58(() => menuStyle(anchor));
31933
+ const [style, setStyle] = useState59(() => menuStyle(anchor));
31522
31934
  useLayoutEffect9(() => {
31523
31935
  const panel = panelRef.current;
31524
31936
  if (!panel) return;
@@ -31532,7 +31944,7 @@ function TimelineItemMenu({
31532
31944
  resizeObserver?.disconnect();
31533
31945
  };
31534
31946
  }, [anchor]);
31535
- useEffect48(() => {
31947
+ useEffect49(() => {
31536
31948
  const onKeyDown = (event) => {
31537
31949
  if (event.key !== "Escape") return;
31538
31950
  event.preventDefault();
@@ -31592,12 +32004,12 @@ function BlockMenu({
31592
32004
  onStartTime,
31593
32005
  onTransition
31594
32006
  }) {
31595
- const [explicitDuration, setExplicitDuration] = useState58(target.explicitDuration);
31596
- const [duration, setDuration] = useState58(formatNumber(target.duration));
31597
- const [startTime, setStartTime] = useState58(
32007
+ const [explicitDuration, setExplicitDuration] = useState59(target.explicitDuration);
32008
+ const [duration, setDuration] = useState59(formatNumber(target.duration));
32009
+ const [startTime, setStartTime] = useState59(
31598
32010
  target.startTime == null ? "" : formatNumber(target.startTime)
31599
32011
  );
31600
- const [transition, setTransition] = useState58(target.transition);
32012
+ const [transition, setTransition] = useState59(target.transition);
31601
32013
  const toggleAutotime = (autotimed) => {
31602
32014
  setExplicitDuration(!autotimed);
31603
32015
  if (autotimed) {
@@ -31669,11 +32081,11 @@ function VideoMenu({
31669
32081
  target,
31670
32082
  onPatch
31671
32083
  }) {
31672
- const [placement, setPlacement] = useState58(target.placement);
31673
- const [locked, setLocked] = useState58(target.lockToBlock);
31674
- const [pipSize, setPipSize] = useState58(target.pipSize ?? "");
31675
- const [pipShape, setPipShape] = useState58(target.pipShape ?? "");
31676
- const [pipPosition, setPipPosition] = useState58(target.pipPosition ?? "");
32084
+ const [placement, setPlacement] = useState59(target.placement);
32085
+ const [locked, setLocked] = useState59(target.lockToBlock);
32086
+ const [pipSize, setPipSize] = useState59(target.pipSize ?? "");
32087
+ const [pipShape, setPipShape] = useState59(target.pipShape ?? "");
32088
+ const [pipPosition, setPipPosition] = useState59(target.pipPosition ?? "");
31677
32089
  const placed = placement === "picture-in-picture" || placement === "overlay";
31678
32090
  const placementOptions = [
31679
32091
  target.canUseContentPlacement ? { value: "content", label: "In layout" } : { value: "default", label: "Default" },
@@ -31943,9 +32355,9 @@ function TimelineTrack({
31943
32355
  colorScheme
31944
32356
  } = useEditorContext();
31945
32357
  const doc = docProp ?? contextDoc;
31946
- const [drag, setDrag] = useState59(null);
31947
- const [itemMenu, setItemMenu] = useState59(null);
31948
- const [pxPerSecond, setPxPerSecond] = useState59(DEFAULT_PX_PER_SECOND);
32358
+ const [drag, setDrag] = useState60(null);
32359
+ const [itemMenu, setItemMenu] = useState60(null);
32360
+ const [pxPerSecond, setPxPerSecond] = useState60(DEFAULT_PX_PER_SECOND);
31949
32361
  const scrollRef = useRef47(null);
31950
32362
  const blocks = useMemo43(() => doc ? flattenBlocks6(doc.blocks) : [], [doc]);
31951
32363
  const previewSettings = usePreviewSettingsOptional();
@@ -31978,8 +32390,8 @@ function TimelineTrack({
31978
32390
  setDrag(null);
31979
32391
  play();
31980
32392
  }, [play]);
31981
- const [scrubbing, setScrubbing] = useState59(false);
31982
- useEffect49(() => {
32393
+ const [scrubbing, setScrubbing] = useState60(false);
32394
+ useEffect50(() => {
31983
32395
  if (!scrubbing) return;
31984
32396
  const onMove = (e2) => {
31985
32397
  const scroll = scrollRef.current;
@@ -32017,7 +32429,7 @@ function TimelineTrack({
32017
32429
  [blocks]
32018
32430
  );
32019
32431
  const followedBlockRef = useRef47(null);
32020
- useEffect49(() => {
32432
+ useEffect50(() => {
32021
32433
  if (!isPlaying) {
32022
32434
  followedBlockRef.current = null;
32023
32435
  return;
@@ -32028,7 +32440,7 @@ function TimelineTrack({
32028
32440
  const line = headingLine(block);
32029
32441
  if (line != null) goToBlockByLine(line);
32030
32442
  }, [isPlaying, currentTime, blockAtTime, goToBlockByLine]);
32031
- useEffect49(() => {
32443
+ useEffect50(() => {
32032
32444
  if (!isPlaying) return;
32033
32445
  const scroll = scrollRef.current;
32034
32446
  if (!scroll) return;
@@ -32076,8 +32488,8 @@ function TimelineTrack({
32076
32488
  );
32077
32489
  const zoomIn = useCallback44(() => setPxPerSecond((s) => Math.min(ZOOM_MAX, s * ZOOM_FACTOR)), []);
32078
32490
  const zoomOut = useCallback44(() => setPxPerSecond((s) => Math.max(ZOOM_MIN, s / ZOOM_FACTOR)), []);
32079
- const [viewportWidth, setViewportWidth] = useState59(0);
32080
- useEffect49(() => {
32491
+ const [viewportWidth, setViewportWidth] = useState60(0);
32492
+ useEffect50(() => {
32081
32493
  const el = scrollRef.current;
32082
32494
  if (!el || typeof ResizeObserver === "undefined") return;
32083
32495
  const update = () => setViewportWidth(el.clientWidth);
@@ -32091,7 +32503,7 @@ function TimelineTrack({
32091
32503
  const dragRef = useRef47(null);
32092
32504
  dragRef.current = drag;
32093
32505
  const isDragging = drag != null && !drag.committed;
32094
- useEffect49(() => {
32506
+ useEffect50(() => {
32095
32507
  if (!isDragging) return;
32096
32508
  const onMove = (e2) => {
32097
32509
  const d = dragRef.current;
@@ -32122,7 +32534,7 @@ function TimelineTrack({
32122
32534
  window.removeEventListener("pointerup", onUp);
32123
32535
  };
32124
32536
  }, [isDragging]);
32125
- useEffect49(() => {
32537
+ useEffect50(() => {
32126
32538
  if (dragRef.current?.committed) setDrag(null);
32127
32539
  }, [doc]);
32128
32540
  const beginDrag = useCallback44(
@@ -32727,7 +33139,7 @@ import { getDocPlaybackDuration as getDocPlaybackDuration2, resolveMediaSchedule
32727
33139
  import { DocPlayer } from "@bendyline/squisq-react";
32728
33140
 
32729
33141
  // src/usePreviewProjection.ts
32730
- import { useEffect as useEffect50, useState as useState60 } from "react";
33142
+ import { useEffect as useEffect51, useState as useState61 } from "react";
32731
33143
  import { resolveAudioMapping } from "@bendyline/squisq/doc";
32732
33144
  import { applyTransform } from "@bendyline/squisq/transform";
32733
33145
  function hasEquivalentAudio(left, right) {
@@ -32747,8 +33159,8 @@ function preserveEquivalentAudio(previous, next) {
32747
33159
  };
32748
33160
  }
32749
33161
  function usePreviewProjection(doc, transformStyle, workspaceContainer, documentTitle2) {
32750
- const [projection, setProjection] = useState60(null);
32751
- useEffect50(() => {
33162
+ const [projection, setProjection] = useState61(null);
33163
+ useEffect51(() => {
32752
33164
  if (!doc || !doc.blocks.length) {
32753
33165
  setProjection(null);
32754
33166
  return;
@@ -33008,7 +33420,7 @@ function TimelineToolbar({
33008
33420
  }
33009
33421
 
33010
33422
  // src/PlainHtmlPreview.tsx
33011
- import { useCallback as useCallback45, useEffect as useEffect51, useMemo as useMemo46, useRef as useRef48, useState as useState61 } from "react";
33423
+ import { useCallback as useCallback45, useEffect as useEffect52, useMemo as useMemo46, useRef as useRef48, useState as useState62 } from "react";
33012
33424
  import { parseMarkdown as parseMarkdown9 } from "@bendyline/squisq/markdown";
33013
33425
 
33014
33426
  // src/utils/collectInlineFontAwesomeCss.ts
@@ -33089,8 +33501,8 @@ function PlainHtmlPreview({
33089
33501
  [onFrameChange]
33090
33502
  );
33091
33503
  const mdDoc = useMemo46(() => parseMarkdown9(markdown), [markdown]);
33092
- const [resolvedImages, setResolvedImages] = useState61(null);
33093
- useEffect51(() => {
33504
+ const [resolvedImages, setResolvedImages] = useState62(null);
33505
+ useEffect52(() => {
33094
33506
  if (!mediaProvider) {
33095
33507
  setResolvedImages(null);
33096
33508
  return;
@@ -33126,8 +33538,8 @@ function PlainHtmlPreview({
33126
33538
  return merged;
33127
33539
  }, [resolvedImages, images]);
33128
33540
  const iconsCss = useMemo46(() => collectInlineFontAwesomeCss(), []);
33129
- const [renderFn, setRenderFn] = useState61(() => cachedRender);
33130
- useEffect51(() => {
33541
+ const [renderFn, setRenderFn] = useState62(() => cachedRender);
33542
+ useEffect52(() => {
33131
33543
  if (renderFn) return;
33132
33544
  let cancelled = false;
33133
33545
  loadRenderFn().then((fn) => {
@@ -33160,7 +33572,7 @@ function PlainHtmlPreview({
33160
33572
  frameDocument.addEventListener("click", handleClick, true);
33161
33573
  removeFrameLinkHandlerRef.current = () => frameDocument.removeEventListener("click", handleClick, true);
33162
33574
  }, [onLinkClick]);
33163
- useEffect51(() => {
33575
+ useEffect52(() => {
33164
33576
  installFrameLinkHandler();
33165
33577
  return () => removeFrameLinkHandlerRef.current();
33166
33578
  }, [html, installFrameLinkHandler]);
@@ -33256,11 +33668,11 @@ function PlainHtmlPreview({
33256
33668
  style2.remove();
33257
33669
  };
33258
33670
  }, [onCopyCode, showCodeCopyButton]);
33259
- useEffect51(() => {
33671
+ useEffect52(() => {
33260
33672
  installFrameCodeCopyControls();
33261
33673
  return () => removeFrameCodeCopyControlsRef.current();
33262
33674
  }, [html, installFrameCodeCopyControls]);
33263
- useEffect51(() => {
33675
+ useEffect52(() => {
33264
33676
  if (!globalKeyboardShortcuts) return;
33265
33677
  const handleKeyDown = (event) => {
33266
33678
  if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.key !== "ArrowDown" && event.key !== "ArrowUp") {
@@ -33339,9 +33751,15 @@ function collectImageRefs(doc) {
33339
33751
  }
33340
33752
 
33341
33753
  // src/PreviewPanel.tsx
33342
- import { useState as useState64, useEffect as useEffect55, useMemo as useMemo50, useCallback as useCallback49, useRef as useRef52 } from "react";
33754
+ import { useState as useState65, useEffect as useEffect56, useMemo as useMemo50, useCallback as useCallback49, useRef as useRef52 } from "react";
33343
33755
  import { createPortal as createPortal13 } from "react-dom";
33344
- import { DocPlayer as DocPlayer2, LinearDocView as LinearDocView2, useMediaProvider as useMediaProvider2 } from "@bendyline/squisq-react";
33756
+ import {
33757
+ DashboardView,
33758
+ DocPlayer as DocPlayer2,
33759
+ FlashcardView,
33760
+ LinearDocView as LinearDocView2,
33761
+ useMediaProvider as useMediaProvider2
33762
+ } from "@bendyline/squisq-react";
33345
33763
  import { resolveTransformStyle as resolveTransformStyle2 } from "@bendyline/squisq/transform";
33346
33764
 
33347
33765
  // src/buildDocumentPreviewMarkdown.ts
@@ -33478,12 +33896,12 @@ import {
33478
33896
  createContext as createContext5,
33479
33897
  useCallback as useCallback46,
33480
33898
  useContext as useContext5,
33481
- useEffect as useEffect52,
33899
+ useEffect as useEffect53,
33482
33900
  useId as useId14,
33483
33901
  useLayoutEffect as useLayoutEffect10,
33484
33902
  useMemo as useMemo47,
33485
33903
  useRef as useRef49,
33486
- useState as useState62
33904
+ useState as useState63
33487
33905
  } from "react";
33488
33906
  import { createPortal as createPortal12 } from "react-dom";
33489
33907
  import { jsx as jsx70, jsxs as jsxs54 } from "react/jsx-runtime";
@@ -33585,10 +34003,11 @@ function PresentationModeProvider({
33585
34003
  const { activeView, colorScheme, doc } = useEditorContext();
33586
34004
  const { activeTheme } = usePreviewSettings();
33587
34005
  const popupNameId = useId14().replace(/[^a-zA-Z0-9_-]/g, "");
33588
- const [selectedTarget, setSelectedTarget] = useState62("control");
33589
- const [activeTarget, setActiveTarget] = useState62(null);
33590
- const [popupRoot, setPopupRoot] = useState62(null);
33591
- const [error, setError] = useState62(null);
34006
+ const [selectedTarget, setSelectedTarget] = useState63("control");
34007
+ const [activeTarget, setActiveTarget] = useState63(null);
34008
+ const [popupRoot, setPopupRoot] = useState63(null);
34009
+ const [error, setError] = useState63(null);
34010
+ const [fullscreenExitStyle, setFullscreenExitStyle] = useState63(null);
33592
34011
  const activeTargetRef = useRef49(activeTarget);
33593
34012
  activeTargetRef.current = activeTarget;
33594
34013
  const previousActiveTargetRef = useRef49(null);
@@ -33736,12 +34155,12 @@ function PresentationModeProvider({
33736
34155
  },
33737
34156
  [availableTargets, selectedTarget, stop]
33738
34157
  );
33739
- useEffect52(() => {
34158
+ useEffect53(() => {
33740
34159
  if (availableTargets.includes(selectedTarget)) return;
33741
34160
  setSelectedTarget("control");
33742
34161
  if (activeTargetRef.current !== null) void stop();
33743
34162
  }, [availableTargets, selectedTarget, stop]);
33744
- useEffect52(() => {
34163
+ useEffect53(() => {
33745
34164
  const root = rootRef.current;
33746
34165
  const ownerDocument = root?.ownerDocument;
33747
34166
  if (!root || !ownerDocument) return;
@@ -33753,7 +34172,7 @@ function PresentationModeProvider({
33753
34172
  ownerDocument.addEventListener("fullscreenchange", handleFullscreenChange);
33754
34173
  return () => ownerDocument.removeEventListener("fullscreenchange", handleFullscreenChange);
33755
34174
  }, [rootRef]);
33756
- useEffect52(() => {
34175
+ useEffect53(() => {
33757
34176
  const root = rootRef.current;
33758
34177
  if (!root) return;
33759
34178
  if (activeTarget) root.dataset.presentationMode = activeTarget;
@@ -33762,10 +34181,10 @@ function PresentationModeProvider({
33762
34181
  delete root.dataset.presentationMode;
33763
34182
  };
33764
34183
  }, [activeTarget, rootRef]);
33765
- useEffect52(() => {
34184
+ useEffect53(() => {
33766
34185
  if (activeView !== "preview" && activeTargetRef.current !== null) void stop();
33767
34186
  }, [activeView, stop]);
33768
- useEffect52(() => {
34187
+ useEffect53(() => {
33769
34188
  const previous = previousActiveTargetRef.current;
33770
34189
  previousActiveTargetRef.current = activeTarget;
33771
34190
  if (previous === null || activeTarget !== null) return;
@@ -33773,7 +34192,7 @@ function PresentationModeProvider({
33773
34192
  returnFocusRef.current = null;
33774
34193
  if (target?.isConnected) target.focus();
33775
34194
  }, [activeTarget]);
33776
- useEffect52(() => {
34195
+ useEffect53(() => {
33777
34196
  if (activeTarget !== "control") return;
33778
34197
  const ownerDocument = rootRef.current?.ownerDocument;
33779
34198
  if (!ownerDocument) return;
@@ -33785,12 +34204,42 @@ function PresentationModeProvider({
33785
34204
  ownerDocument.addEventListener("keydown", handleKeyDown);
33786
34205
  return () => ownerDocument.removeEventListener("keydown", handleKeyDown);
33787
34206
  }, [activeTarget, rootRef, stop]);
33788
- useEffect52(() => {
34207
+ useEffect53(() => {
33789
34208
  if (!error) return;
33790
34209
  const timer = window.setTimeout(() => setError(null), 6e3);
33791
34210
  return () => window.clearTimeout(timer);
33792
34211
  }, [error]);
33793
- useEffect52(
34212
+ useLayoutEffect10(() => {
34213
+ if (activeTarget !== "fullscreen") {
34214
+ setFullscreenExitStyle(null);
34215
+ return;
34216
+ }
34217
+ const root = rootRef.current;
34218
+ const controls = root?.querySelector(".doc-controls-slideshow");
34219
+ if (!root || !controls) {
34220
+ setFullscreenExitStyle(null);
34221
+ return;
34222
+ }
34223
+ const updatePosition = () => {
34224
+ const rootBounds = root.getBoundingClientRect();
34225
+ const controlsBounds = controls.getBoundingClientRect();
34226
+ setFullscreenExitStyle({
34227
+ left: "auto",
34228
+ right: `${Math.max(0, rootBounds.right - controlsBounds.left + 8)}px`,
34229
+ transform: "none"
34230
+ });
34231
+ };
34232
+ updatePosition();
34233
+ const resizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(updatePosition);
34234
+ resizeObserver?.observe(root);
34235
+ resizeObserver?.observe(controls);
34236
+ window.addEventListener("resize", updatePosition);
34237
+ return () => {
34238
+ resizeObserver?.disconnect();
34239
+ window.removeEventListener("resize", updatePosition);
34240
+ };
34241
+ }, [activeTarget, rootRef]);
34242
+ useEffect53(
33794
34243
  () => () => {
33795
34244
  popupCleanupRef.current?.();
33796
34245
  const popup = popupRef.current;
@@ -33836,7 +34285,7 @@ function PresentationModeProvider({
33836
34285
  type: "button",
33837
34286
  className: "squisq-presentation-exit",
33838
34287
  "data-theme": colorScheme,
33839
- style: presentationThemeStyle,
34288
+ style: { ...presentationThemeStyle, ...fullscreenExitStyle },
33840
34289
  onClick: () => void stop(),
33841
34290
  autoFocus: activeTarget === "control" || activeTarget === "fullscreen",
33842
34291
  "aria-label": "Exit presentation mode",
@@ -33893,8 +34342,8 @@ function PresentationModeControl() {
33893
34342
  const { colorScheme } = useEditorContext();
33894
34343
  const triggerRef = useRef49(null);
33895
34344
  const menuRef = useRef49(null);
33896
- const [open, setOpen] = useState62(false);
33897
- const [anchor, setAnchor] = useState62(null);
34345
+ const [open, setOpen] = useState63(false);
34346
+ const [anchor, setAnchor] = useState63(null);
33898
34347
  const options = PRESENTATION_OPTIONS.filter((option) => availableTargets.includes(option.target));
33899
34348
  const selected = options.find((option) => option.target === selectedTarget) ?? PRESENTATION_OPTIONS[0];
33900
34349
  const updatePosition = useCallback46(() => {
@@ -33919,7 +34368,7 @@ function PresentationModeControl() {
33919
34368
  updatePosition();
33920
34369
  setOpen(true);
33921
34370
  }, [updatePosition]);
33922
- useEffect52(() => {
34371
+ useEffect53(() => {
33923
34372
  if (!open) return;
33924
34373
  const handlePointerDown = (event) => {
33925
34374
  const target = event.target;
@@ -34062,10 +34511,10 @@ import {
34062
34511
  createContext as createContext6,
34063
34512
  useCallback as useCallback47,
34064
34513
  useContext as useContext6,
34065
- useEffect as useEffect53,
34514
+ useEffect as useEffect54,
34066
34515
  useMemo as useMemo48,
34067
34516
  useRef as useRef50,
34068
- useState as useState63
34517
+ useState as useState64
34069
34518
  } from "react";
34070
34519
  import { jsx as jsx71, jsxs as jsxs55 } from "react/jsx-runtime";
34071
34520
  var PrintModeContext = createContext6(null);
@@ -34080,8 +34529,8 @@ function usePrintModeOptional() {
34080
34529
  function PrintModeProvider({ rootRef, children }) {
34081
34530
  const { activeView } = useEditorContext();
34082
34531
  const { activeTarget: presentationTarget, stop: stopPresentation } = usePresentationMode();
34083
- const [active, setActive] = useState63(false);
34084
- const [slidesPerPage, setSlidesPerPage] = useState63(1);
34532
+ const [active, setActive] = useState64(false);
34533
+ const [slidesPerPage, setSlidesPerPage] = useState64(1);
34085
34534
  const customPrintHandlerRef = useRef50(null);
34086
34535
  const printAncestorsRef = useRef50([]);
34087
34536
  const unmarkPrinting = useCallback47(() => {
@@ -34132,7 +34581,7 @@ function PrintModeProvider({ rootRef, children }) {
34132
34581
  markPrinting();
34133
34582
  ownerWindow.print();
34134
34583
  }, [markPrinting, rootRef]);
34135
- useEffect53(() => {
34584
+ useEffect54(() => {
34136
34585
  const root = rootRef.current;
34137
34586
  if (!root) return;
34138
34587
  if (active) root.dataset.printPreview = "true";
@@ -34141,10 +34590,10 @@ function PrintModeProvider({ rootRef, children }) {
34141
34590
  delete root.dataset.printPreview;
34142
34591
  };
34143
34592
  }, [active, rootRef]);
34144
- useEffect53(() => {
34593
+ useEffect54(() => {
34145
34594
  if (activeView !== "preview" && active) close();
34146
34595
  }, [active, activeView, close]);
34147
- useEffect53(() => {
34596
+ useEffect54(() => {
34148
34597
  if (!active) return;
34149
34598
  const ownerWindow = rootRef.current?.ownerDocument.defaultView;
34150
34599
  const ownerDocument = rootRef.current?.ownerDocument;
@@ -34235,9 +34684,10 @@ function PrintPreviewToolbar() {
34235
34684
  }
34236
34685
 
34237
34686
  // src/print/PrintPreview.tsx
34238
- import { useCallback as useCallback48, useEffect as useEffect54, useMemo as useMemo49, useRef as useRef51 } from "react";
34687
+ import { useCallback as useCallback48, useEffect as useEffect55, useMemo as useMemo49, useRef as useRef51 } from "react";
34239
34688
  import {
34240
34689
  BlockRenderer as BlockRenderer5,
34690
+ FlashcardFaceView,
34241
34691
  LinearDocView,
34242
34692
  useDocPlayback,
34243
34693
  useMediaProvider
@@ -34245,7 +34695,7 @@ import {
34245
34695
  import {
34246
34696
  createTemplateContext as createTemplateContext2
34247
34697
  } from "@bendyline/squisq/schemas";
34248
- import { expandCoverBlock as expandCoverBlock2 } from "@bendyline/squisq/doc";
34698
+ import { expandCoverBlock as expandCoverBlock2, materializeFlashcards } from "@bendyline/squisq/doc";
34249
34699
  import { resolveTransformStyle } from "@bendyline/squisq/transform";
34250
34700
  import { jsx as jsx72, jsxs as jsxs56 } from "react/jsx-runtime";
34251
34701
  function chunk(values, size) {
@@ -34285,8 +34735,12 @@ function PrintPreview({
34285
34735
  layers: expandCoverBlock2(previewDoc.startBlock, context)
34286
34736
  };
34287
34737
  }, [previewDoc?.startBlock, showCover, theme, viewport]);
34738
+ const flashcardDeck = useMemo49(
34739
+ () => contentDoc ? materializeFlashcards(contentDoc) : null,
34740
+ [contentDoc]
34741
+ );
34288
34742
  const isTextDocument = displayMode === "page" || displayMode === "narrate";
34289
- useEffect54(() => {
34743
+ useEffect55(() => {
34290
34744
  if (!isTextDocument) return registerPrintHandler(null);
34291
34745
  return registerPrintHandler(() => {
34292
34746
  const frameWindow = documentFrameRef.current?.contentWindow;
@@ -34326,6 +34780,58 @@ function PrintPreview({
34326
34780
  ) })
34327
34781
  ] });
34328
34782
  }
34783
+ if (displayMode === "flashcards") {
34784
+ const faceProps = {
34785
+ theme,
34786
+ basePath,
34787
+ showCodeCopyButton: false,
34788
+ viewport,
34789
+ customTemplates: contentDoc?.customTemplates
34790
+ };
34791
+ return /* @__PURE__ */ jsxs56(
34792
+ "div",
34793
+ {
34794
+ className: "squisq-print-preview squisq-print-preview--flashcards",
34795
+ style: {
34796
+ "--squisq-print-flashcard-text": theme.colors.text,
34797
+ "--squisq-print-flashcard-muted": theme.colors.textMuted,
34798
+ "--squisq-print-flashcard-accent": theme.colors.primary,
34799
+ "--squisq-print-flashcard-bg": theme.colors.background,
34800
+ "--squisq-print-flashcard-surface": theme.colors.backgroundLight
34801
+ },
34802
+ children: [
34803
+ /* @__PURE__ */ jsx72("style", { children: "@page { size: landscape; margin: 0; }" }),
34804
+ flashcardDeck?.cards.map((card, index2) => /* @__PURE__ */ jsxs56("article", { className: "squisq-print-flashcard-sheet", children: [
34805
+ /* @__PURE__ */ jsxs56("header", { children: [
34806
+ /* @__PURE__ */ jsxs56("span", { children: [
34807
+ "Card ",
34808
+ index2 + 1,
34809
+ " of ",
34810
+ flashcardDeck.cards.length
34811
+ ] }),
34812
+ card.label && /* @__PURE__ */ jsx72("strong", { children: card.label })
34813
+ ] }),
34814
+ /* @__PURE__ */ jsxs56("div", { className: "squisq-print-flashcard-pair", children: [
34815
+ /* @__PURE__ */ jsxs56("section", { className: "squisq-print-flashcard-face", "aria-label": "Question side", children: [
34816
+ /* @__PURE__ */ jsx72("small", { children: "Front" }),
34817
+ /* @__PURE__ */ jsx72(FlashcardFaceView, { ...faceProps, face: card.front }),
34818
+ card.kind === "multiple-choice" && /* @__PURE__ */ jsx72("ol", { className: "squisq-print-flashcard-choices", children: card.choices?.map((choice) => /* @__PURE__ */ jsx72("li", { children: /* @__PURE__ */ jsx72(FlashcardFaceView, { ...faceProps, face: choice.content }) }, choice.id)) })
34819
+ ] }),
34820
+ /* @__PURE__ */ jsxs56("section", { className: "squisq-print-flashcard-face", "aria-label": "Answer side", children: [
34821
+ /* @__PURE__ */ jsx72("small", { children: "Back" }),
34822
+ /* @__PURE__ */ jsx72(FlashcardFaceView, { ...faceProps, face: card.back }),
34823
+ card.explanation && /* @__PURE__ */ jsxs56("div", { className: "squisq-print-flashcard-explanation", children: [
34824
+ /* @__PURE__ */ jsx72("small", { children: "Explanation" }),
34825
+ /* @__PURE__ */ jsx72(FlashcardFaceView, { ...faceProps, face: card.explanation })
34826
+ ] })
34827
+ ] })
34828
+ ] })
34829
+ ] }, card.id)),
34830
+ (!flashcardDeck || flashcardDeck.cards.length === 0) && /* @__PURE__ */ jsx72("p", { className: "squisq-print-empty", children: "Nothing to print." })
34831
+ ]
34832
+ }
34833
+ );
34834
+ }
34329
34835
  const slides = coverBlock ? [coverBlock, ...blocks] : blocks;
34330
34836
  const density = displayMode === "slideshow" ? slidesPerPage : 1;
34331
34837
  const sheets = chunk(slides, density);
@@ -34409,11 +34915,14 @@ function PreviewPanel({
34409
34915
  activeCoverSlide,
34410
34916
  activeCoverSlideTemplate,
34411
34917
  activeCoverSlideDuration,
34412
- activeCoverSlidePlayback
34918
+ activeCoverSlidePlayback,
34919
+ activeDashboardLayout,
34920
+ activeDashboardTitle,
34921
+ activeDashboardStyle
34413
34922
  } = usePreviewSettings();
34414
34923
  const mainSurfaceRef = useRef52(null);
34415
34924
  const popupSurfaceRef = useRef52(null);
34416
- const [playbackState, setPlaybackState] = useState64(null);
34925
+ const [playbackState, setPlaybackState] = useState65(null);
34417
34926
  const handlePlaybackStateChange = useCallback49((next) => {
34418
34927
  setPlaybackState(next);
34419
34928
  }, []);
@@ -34451,11 +34960,13 @@ function PreviewPanel({
34451
34960
  const isDocumentMode = activeDisplayMode === "page";
34452
34961
  const isPageMode = activeDisplayMode === "linear";
34453
34962
  const isNarrateMode = activeDisplayMode === "narrate";
34454
- useEffect55(() => {
34963
+ const isDashboardMode = activeDisplayMode === "dashboard";
34964
+ const isFlashcardsMode = activeDisplayMode === "flashcards";
34965
+ useEffect56(() => {
34455
34966
  if (presentation?.activeTarget === "window") return;
34456
34967
  setPlaybackState(null);
34457
34968
  }, [presentation?.activeTarget]);
34458
- useEffect55(() => {
34969
+ useEffect56(() => {
34459
34970
  if (presentation?.activeTarget !== "window" || !presentation.popupRoot) return;
34460
34971
  const mainRoot = mainSurfaceRef.current;
34461
34972
  const followerRoot = popupSurfaceRef.current;
@@ -34529,7 +35040,7 @@ function PreviewPanel({
34529
35040
  if (!previewDoc && !isDocumentMode && !isNarrateMode) {
34530
35041
  return /* @__PURE__ */ jsx73("div", { className: `squisq-preview-status ${className || ""}`, "data-testid": "preview-panel", children: /* @__PURE__ */ jsx73("p", { children: "No content to preview. Start typing in the editor." }) });
34531
35042
  }
34532
- const fillsContainer = printMode?.active || isDocumentMode || isPageMode || isNarrateMode ? "stretch" : "center";
35043
+ const fillsContainer = printMode?.active || isDocumentMode || isPageMode || isNarrateMode || isFlashcardsMode ? "stretch" : "center";
34533
35044
  const audienceWindowOpen = presentation?.activeTarget === "window";
34534
35045
  const renderSurface = (audience) => {
34535
35046
  if (!audience && printMode?.active) {
@@ -34602,6 +35113,36 @@ function PreviewPanel({
34602
35113
  }
34603
35114
  );
34604
35115
  }
35116
+ if (isFlashcardsMode) {
35117
+ return /* @__PURE__ */ jsx73(
35118
+ FlashcardView,
35119
+ {
35120
+ doc: contentDoc ?? doc,
35121
+ basePath,
35122
+ theme: activeTheme,
35123
+ globalKeyboardShortcuts: !audience,
35124
+ showCodeCopyButton,
35125
+ onCopyCode,
35126
+ fenceRenderers: fenceRenderers ?? void 0,
35127
+ viewport: activeViewport
35128
+ }
35129
+ );
35130
+ }
35131
+ if (isDashboardMode) {
35132
+ return /* @__PURE__ */ jsx73("div", { className: "squisq-dashboard-fit", children: /* @__PURE__ */ jsx73(
35133
+ DashboardView,
35134
+ {
35135
+ doc: contentDoc ?? doc,
35136
+ basePath,
35137
+ viewport: activeViewport,
35138
+ theme: activeTheme,
35139
+ layout: activeDashboardLayout,
35140
+ showTitle: activeDashboardTitle,
35141
+ style: activeDashboardStyle,
35142
+ documentTitle: documentTitleFromFileName(fileName)
35143
+ }
35144
+ ) });
35145
+ }
34605
35146
  if (audience && !followerAudioController) {
34606
35147
  return /* @__PURE__ */ jsx73("div", { className: "squisq-presentation-connecting", children: "Connecting presentation..." });
34607
35148
  }
@@ -34689,7 +35230,7 @@ function PreviewPanel({
34689
35230
  }
34690
35231
 
34691
35232
  // src/MediaBin.tsx
34692
- import { useState as useState65, useEffect as useEffect56, useRef as useRef53, useCallback as useCallback50 } from "react";
35233
+ import { useState as useState66, useEffect as useEffect57, useRef as useRef53, useCallback as useCallback50 } from "react";
34693
35234
  import { jsx as jsx74, jsxs as jsxs58 } from "react/jsx-runtime";
34694
35235
  function formatSize(bytes) {
34695
35236
  if (bytes < 1024) return `${bytes} B`;
@@ -34754,12 +35295,12 @@ function MediaBin({
34754
35295
  isRecorderOpen = false,
34755
35296
  allowBinaryDownloads = true
34756
35297
  }) {
34757
- const [entries, setEntries] = useState65([]);
34758
- const [thumbUrls, setThumbUrls] = useState65({});
34759
- const [loading, setLoading] = useState65(false);
34760
- const [isDropActive, setIsDropActive] = useState65(false);
34761
- const [downloadingPath, setDownloadingPath] = useState65(null);
34762
- const [contextMenu, setContextMenu] = useState65(null);
35298
+ const [entries, setEntries] = useState66([]);
35299
+ const [thumbUrls, setThumbUrls] = useState66({});
35300
+ const [loading, setLoading] = useState66(false);
35301
+ const [isDropActive, setIsDropActive] = useState66(false);
35302
+ const [downloadingPath, setDownloadingPath] = useState66(null);
35303
+ const [contextMenu, setContextMenu] = useState66(null);
34763
35304
  const fileInputRef = useRef53(null);
34764
35305
  const contextMenuRef = useRef53(null);
34765
35306
  const dropDepthRef = useRef53(0);
@@ -34781,7 +35322,7 @@ function MediaBin({
34781
35322
  },
34782
35323
  [onCountChange]
34783
35324
  );
34784
- useEffect56(() => {
35325
+ useEffect57(() => {
34785
35326
  if (!contextMenu) return;
34786
35327
  const handlePointerDown = (event) => {
34787
35328
  if (contextMenuRef.current?.contains(event.target)) return;
@@ -34802,7 +35343,7 @@ function MediaBin({
34802
35343
  window.removeEventListener("resize", close);
34803
35344
  };
34804
35345
  }, [contextMenu]);
34805
- useEffect56(() => {
35346
+ useEffect57(() => {
34806
35347
  if (!mediaProvider) {
34807
35348
  setEntries([]);
34808
35349
  setThumbUrls({});
@@ -35144,7 +35685,7 @@ ${formatSize(entry.size)}`,
35144
35685
  }
35145
35686
 
35146
35687
  // src/DropZoneOverlay.tsx
35147
- import { useState as useState66 } from "react";
35688
+ import { useState as useState67 } from "react";
35148
35689
  import { Fragment as Fragment21, jsx as jsx75, jsxs as jsxs59 } from "react/jsx-runtime";
35149
35690
  function DropZoneOverlay({
35150
35691
  dragContentType,
@@ -35201,7 +35742,7 @@ function DropZone({
35201
35742
  disabled,
35202
35743
  variant
35203
35744
  }) {
35204
- const [isHovering, setIsHovering] = useState66(false);
35745
+ const [isHovering, setIsHovering] = useState67(false);
35205
35746
  const props = zoneProps(target);
35206
35747
  return /* @__PURE__ */ jsxs59(
35207
35748
  "div",
@@ -35245,7 +35786,7 @@ function DropZone({
35245
35786
  }
35246
35787
 
35247
35788
  // src/Tooltip.tsx
35248
- import { useEffect as useEffect57, useLayoutEffect as useLayoutEffect11, useRef as useRef54, useState as useState67 } from "react";
35789
+ import { useEffect as useEffect58, useLayoutEffect as useLayoutEffect11, useRef as useRef54, useState as useState68 } from "react";
35249
35790
  import { createPortal as createPortal14 } from "react-dom";
35250
35791
 
35251
35792
  // src/tooltipPlacement.ts
@@ -35261,7 +35802,7 @@ function clampTooltipLeft(anchorX, tooltipWidth, viewportWidth, edgePadding = ED
35261
35802
  import { jsx as jsx76 } from "react/jsx-runtime";
35262
35803
  var SHOW_DELAY_MS = 180;
35263
35804
  function TooltipLayer() {
35264
- const [state, setState3] = useState67(null);
35805
+ const [state, setState3] = useState68(null);
35265
35806
  const tooltipRef = useRef54(null);
35266
35807
  const timerRef = useRef54(null);
35267
35808
  const currentTargetRef = useRef54(null);
@@ -35277,7 +35818,7 @@ function TooltipLayer() {
35277
35818
  node2.style.left = `${left}px`;
35278
35819
  node2.style.visibility = "visible";
35279
35820
  }, [state]);
35280
- useEffect57(() => {
35821
+ useEffect58(() => {
35281
35822
  const clearTimer = () => {
35282
35823
  if (timerRef.current) {
35283
35824
  clearTimeout(timerRef.current);
@@ -35363,7 +35904,7 @@ function TooltipLayer() {
35363
35904
  }
35364
35905
 
35365
35906
  // src/EditorShell.tsx
35366
- import { useEffect as useEffect58, useRef as useRef55, useState as useState68, useCallback as useCallback51, useMemo as useMemo52 } from "react";
35907
+ import { useEffect as useEffect59, useRef as useRef55, useState as useState69, useCallback as useCallback51, useMemo as useMemo52 } from "react";
35367
35908
 
35368
35909
  // src/BlockPreviewPanel.tsx
35369
35910
  import { useMemo as useMemo51 } from "react";
@@ -35690,6 +36231,8 @@ function EditorShell({
35690
36231
  showStatusBar = true,
35691
36232
  imageDisplayMode = "inline",
35692
36233
  fileName,
36234
+ saveCoverImageOutput,
36235
+ saveDashboardImageOutput,
35693
36236
  language,
35694
36237
  findMode,
35695
36238
  onFindModeChange,
@@ -35749,6 +36292,8 @@ function EditorShell({
35749
36292
  allowNarrate,
35750
36293
  preserveSourceWrapping,
35751
36294
  fileName,
36295
+ saveCoverImageOutput,
36296
+ saveDashboardImageOutput,
35752
36297
  language,
35753
36298
  findMode,
35754
36299
  onFindModeChange,
@@ -35951,13 +36496,13 @@ function EditorShellInner({
35951
36496
  );
35952
36497
  const timelineClock = useTimelineClock(isTimelineMode ? timelineDuration : 0);
35953
36498
  const hasTimelineVideo = timelineVideoSchedule.length > 0;
35954
- const [timelineVideoVisible, setTimelineVideoVisible] = useState68(false);
35955
- const [timelineCompositionVisible, setTimelineCompositionVisible] = useState68(false);
36499
+ const [timelineVideoVisible, setTimelineVideoVisible] = useState69(false);
36500
+ const [timelineCompositionVisible, setTimelineCompositionVisible] = useState69(false);
35956
36501
  const timelinePreviewCount = Number(timelineVideoVisible) + Number(timelineCompositionVisible);
35957
- const [showFiles, setShowFiles] = useState68(false);
35958
- const [mediaRefreshKey, setMediaRefreshKey] = useState68(0);
35959
- const [mediaCount, setMediaCount] = useState68(0);
35960
- const [mediaBinRecorderOpen, setMediaBinRecorderOpen] = useState68(false);
36502
+ const [showFiles, setShowFiles] = useState69(false);
36503
+ const [mediaRefreshKey, setMediaRefreshKey] = useState69(0);
36504
+ const [mediaCount, setMediaCount] = useState69(0);
36505
+ const [mediaBinRecorderOpen, setMediaBinRecorderOpen] = useState69(false);
35961
36506
  const mediaListRefreshKey = mediaRefreshKey + mediaRevision;
35962
36507
  const usedMediaPaths = useMemo52(
35963
36508
  () => collectMediaReferencesFromMarkdown(markdownSource),
@@ -35969,13 +36514,13 @@ function EditorShellInner({
35969
36514
  }
35970
36515
  const imageEditFallbackContainer = imageEditFallbackContainerRef.current;
35971
36516
  const isDark = colorScheme === "dark";
35972
- useEffect58(() => {
36517
+ useEffect59(() => {
35973
36518
  if (!isTimelineMode || !hasTimelineVideo) setTimelineVideoVisible(false);
35974
36519
  }, [isTimelineMode, hasTimelineVideo]);
35975
- useEffect58(() => {
36520
+ useEffect59(() => {
35976
36521
  if (!isTimelineMode || !doc?.blocks.length) setTimelineCompositionVisible(false);
35977
36522
  }, [isTimelineMode, doc]);
35978
- useEffect58(() => {
36523
+ useEffect59(() => {
35979
36524
  if (!mediaProvider) {
35980
36525
  setMediaCount(0);
35981
36526
  return;
@@ -36084,7 +36629,7 @@ ${snippet}` : snippet);
36084
36629
  onDrop: handleFileDrop,
36085
36630
  enabled: !readOnly
36086
36631
  });
36087
- useEffect58(() => {
36632
+ useEffect59(() => {
36088
36633
  onChange?.(markdownSource);
36089
36634
  }, [markdownSource, onChange]);
36090
36635
  const handleShellKeyDown = useCallback51(
@@ -36450,9 +36995,9 @@ function ImageEditModal({
36450
36995
  const parent = container ?? new MemoryContentContainer();
36451
36996
  return scopeContainer(parent, `.imageEdits/${scopedName}`);
36452
36997
  }, [container, relativePath]);
36453
- const [initialSrc, setInitialSrc] = useState68(null);
36454
- const [resolveError, setResolveError] = useState68(null);
36455
- useEffect58(() => {
36998
+ const [initialSrc, setInitialSrc] = useState69(null);
36999
+ const [resolveError, setResolveError] = useState69(null);
37000
+ useEffect59(() => {
36456
37001
  let cancelled = false;
36457
37002
  setInitialSrc(null);
36458
37003
  setResolveError(null);