@lingjingai/script-editor 0.1.17 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/INTEGRATION.md CHANGED
@@ -64,6 +64,7 @@ function Host({ script, setScript }) {
64
64
  - `selection` / `onSelectionChange`(`StudioSelection`,不传则内部自管)
65
65
  - `format` / `onFormatChange`(`"standard" | "asian"`)
66
66
  - `disabled`、`dark`、`className`
67
+ - `toc?: "sidebar" | "none"`:内置左侧目录。默认 `"sidebar"` 渲染自带 256px 目录列;`"none"` = **无目录模式**——只渲染顶栏 + 画布(占满宽度),宿主自建导航。见 §10。`0.1.18+`
67
68
 
68
69
  **HUD(顶栏)**
69
70
  - `onTitleChange?(next: string)`:顶栏左侧常驻**剧本标题**(取 `value.title`)。传了它,标题变**点击即改**;不传则只读展示(`value.title` 为空时不显示)。包**不碰存储**——拿到新标题宿主自己持久化(可 `onEdit(p => ({...p, title}))` 写回 `script.json`,或另调重命名接口)。`0.1.16+`
@@ -127,3 +128,62 @@ function Host({ script, setScript }) {
127
128
  - `apps/web/src/features/script-editor/useScriptDocument.ts` —— 自动保存 + undo/redo + 8s 轮询探测
128
129
  - `apps/web/src/features/script-editor/script-output-api.ts` —— 后端存取
129
130
  - `apps/web/src/main.tsx` / `apps/web/vite.config.ts` —— CSS 引入 + `development` 解析条件
131
+
132
+ ---
133
+
134
+ ## 10. 无目录模式 / 自建导航(`toc="none"`)`0.1.18+`
135
+
136
+ 把内置左侧目录关掉、导航**透出**给宿主自己渲染(嵌进已有全局导航、或做成面包屑下拉 / ⌘K 命令面板 / 移动端 sheet 等完全不同的形态)。包只给**纯数据模型**,不给 UI。
137
+
138
+ ```tsx
139
+ import {
140
+ ScriptEditor,
141
+ buildScriptNav,
142
+ selectionTargetKey,
143
+ withOrigin,
144
+ type StudioSelection,
145
+ } from "@lingjingai/script-editor";
146
+
147
+ function Host({ script, onEdit }) {
148
+ // 无目录模式下 selection 必须受控(宿主自己的导航要驱动它)
149
+ const [sel, setSel] = useState<StudioSelection>({ kind: "empty" });
150
+ const nav = buildScriptNav(script); // 派生导航树(纯函数)
151
+ const active = selectionTargetKey(sel); // 当前激活节点的 key
152
+
153
+ return (
154
+ <>
155
+ {/* 宿主自建的任意导航 UI —— 这里是最朴素的列表 */}
156
+ <MyNav>
157
+ {nav.episodes.flatMap((e) => e.scenes).map((s) => (
158
+ <button
159
+ key={s.key}
160
+ data-active={s.key === active}
161
+ onClick={() => setSel(withOrigin(s.selection, "sidebar"))}
162
+ >
163
+ {s.label} · {s.meta}
164
+ </button>
165
+ ))}
166
+ </MyNav>
167
+
168
+ <ScriptEditor
169
+ value={script}
170
+ onEdit={onEdit}
171
+ toc="none" {/* ← 关掉内置目录,画布占满 */}
172
+ selection={sel}
173
+ onSelectionChange={setSel} {/* ← 编辑器内滚动 spy 也会回吐 selection */}
174
+ />
175
+ </>
176
+ );
177
+ }
178
+ ```
179
+
180
+ `buildScriptNav(value): ScriptNav` 返回:
181
+ - `episodes[]` / `actors[]` / `locations[]` / `props[]`,`episodes[].scenes[]` 内嵌。
182
+ (**0.1.19 起**:世界观 & 风格的显示与编辑暂时下线——不再有内联区块,也不再是导航节点;宿主无需在目录里渲染它。`WorldviewEditor` 组件保留但未接线,后续恢复时可直接复用。)
183
+ - 每个节点都带 **`key`**(= `selectionTargetKey(node.selection)`,拿去和 `selectionTargetKey(当前selection)` 比对即判激活)和 **`selection`**(即用型 `StudioSelection`,**不带 origin**;点击时 `withOrigin(node.selection, "sidebar")` 再喂 `onSelectionChange`)。
184
+ - 标签字段(`label`/`title`/`meta`/`name`)与内置目录**同 helper 派生**,长相一致。
185
+
186
+ 要点:
187
+ - selection 同时也是**回吐**通道:编辑器画布滚动时会上抛 `onSelectionChange`(origin `"scroll"`),宿主导航据此高亮当前位置。
188
+ - 无目录模式下内置「新增一集」按钮消失——宿主可自行调 `mutations.addEpisode`(已从包导出:`mutations.addEpisode(prev)`)接到自己的按钮上。
189
+ - 形态对照交互演示:`toc-modes-demo.html`(本包根目录,不随包发布)「形态 B」tab,演示用同一个 `buildScriptNav` 模型同时驱动面包屑下拉 + ⌘K 两种 UI。
package/dist/index.d.ts CHANGED
@@ -488,6 +488,12 @@ interface ScriptEditorProps extends ScriptEditorAdapters {
488
488
  disabled?: boolean;
489
489
  className?: string;
490
490
  dark?: boolean;
491
+ /** built-in left directory. Default "sidebar" renders the bundled 256px TOC
492
+ * column; "none" → directory-less mode: the editor renders top bar + canvas
493
+ * only (full width) and the host drives navigation via controlled
494
+ * selection/onSelectionChange + the exported buildScriptNav(value) model.
495
+ * 0.1.18+ */
496
+ toc?: "sidebar" | "none";
491
497
  /** when provided, the script title (value.title) becomes click-to-edit in the
492
498
  * top bar; the host persists the new value (the editor never touches storage,
493
499
  * matching the value + onEdit contract — just one more delegated hook). Omit →
@@ -512,7 +518,60 @@ interface ScriptEditorProps extends ScriptEditorAdapters {
512
518
  * scroll position across editor unmount/remount (tab switch / project nav). */
513
519
  scrollScopeKey?: string;
514
520
  }
515
- declare function ScriptEditor({ value, onEdit, selection: controlledSelection, onSelectionChange, format: controlledFormat, onFormatChange, disabled, className, dark, onTitleChange, topBarActions, saveStatus, canUndo, canRedo, onUndo, onRedo, onEditingChange, referenceFocusRequest, scrollScopeKey, diff, asset, onAddChatReferences, onRequestAssetDelete, }: ScriptEditorProps): react.JSX.Element;
521
+ declare function ScriptEditor({ value, onEdit, selection: controlledSelection, onSelectionChange, format: controlledFormat, onFormatChange, disabled, className, dark, toc, onTitleChange, topBarActions, saveStatus, canUndo, canRedo, onUndo, onRedo, onEditingChange, referenceFocusRequest, scrollScopeKey, diff, asset, onAddChatReferences, onRequestAssetDelete, }: ScriptEditorProps): react.JSX.Element;
522
+
523
+ /**
524
+ * Headless navigation model — surfaces the script's TOC tree (the same
525
+ * episodes/scenes/entity structure `StudioToc` renders) as plain data so a host
526
+ * in directory-less mode (`<ScriptEditor toc="none" />`) can build its OWN
527
+ * navigation UI: breadcrumb dropdown, ⌘K command palette, mobile sheet, etc.
528
+ *
529
+ * Each node carries:
530
+ * - `key` — `selectionTargetKey` of its selection; compare against
531
+ * `selectionTargetKey(currentSelection)` to mark the active node.
532
+ * - `selection` — a ready-to-use `StudioSelection` (no origin). On click do
533
+ * `onSelectionChange(withOrigin(node.selection, "sidebar"))`.
534
+ *
535
+ * Labels reuse the exact same helpers as `StudioToc`, so a custom nav stays
536
+ * label-identical to the built-in one. Pure function, zero side effects.
537
+ */
538
+
539
+ interface ScriptNavScene {
540
+ key: string;
541
+ episodeIdx: number;
542
+ sceneIdx: number;
543
+ sceneId: string;
544
+ /** e.g. "场 7" */
545
+ label: string;
546
+ /** e.g. "scn_007 · 夜内" */
547
+ meta: string;
548
+ selection: StudioSelection;
549
+ }
550
+ interface ScriptNavEpisode {
551
+ key: string;
552
+ episodeIdx: number;
553
+ /** zero-padded episode number, e.g. "01" */
554
+ label: string;
555
+ /** human title (falls back to "第 NN 集") */
556
+ title: string;
557
+ sceneCount: number;
558
+ scenes: ScriptNavScene[];
559
+ selection: StudioSelection;
560
+ }
561
+ interface ScriptNavEntity {
562
+ key: string;
563
+ id: string;
564
+ name: string;
565
+ selection: StudioSelection;
566
+ }
567
+ interface ScriptNav {
568
+ episodes: ScriptNavEpisode[];
569
+ actors: ScriptNavEntity[];
570
+ locations: ScriptNavEntity[];
571
+ props: ScriptNavEntity[];
572
+ }
573
+ /** Derive the navigation tree from a ScriptProject (see file header). */
574
+ declare function buildScriptNav(value: ScriptProject): ScriptNav;
516
575
 
517
576
  /**
518
577
  * Pure ScriptProject mutations.
@@ -739,4 +798,4 @@ declare function DeleteConfirmButton({ onConfirm, disabled, targetScope, title,
739
798
  description?: string;
740
799
  }): react.JSX.Element;
741
800
 
742
- export { type AssetAdapter, type AssetDeleteRequest, type AssetState, type ChangeSet, type ChatReference, DeleteConfirmButton, type DiffActionMark, type DiffAdapter, type DiffSceneMark, EMPTY_SELECTION, EditableText, type EpisodeWorkspaceView, type FieldChange, Popover, type SaveStatus, type Scene, type SceneActionDiff, type SceneChangeKind, type SceneContext, type SceneEnvironment, type ScriptActor, ScriptContentType, type ScriptData, ScriptEditor, type ScriptEditorAdapters, type ScriptEditorProps, type ScriptEpisode, type ScriptFormat, type ScriptItem, type ScriptLocation, type ScriptProject, type ScriptProp, type ScriptReferenceFocusRequest, type ScriptReferenceLocator, type ScriptSpeaker, type ScriptTargetKind, SelectField, type StateChange, type StudioSelection, type StudioSelectionFrom, type StudioSelectionOrigin, type TransitionPrompt, acceptActionIntoBaseline, acceptSceneIntoBaseline, applyActionAccept, applyActionReject, buildActionDiff, buildDiffFromBaseline, buildSelectionFrom, changeSetToDiffScenes, computeChangeSet, computeSceneActionDiff, findBaselineScene, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, index as mutations, rejectActionToBaseline, rejectSceneToBaseline, sceneChangeKey, selectionFromScriptReference, selectionTargetKey, withOrigin };
801
+ export { type AssetAdapter, type AssetDeleteRequest, type AssetState, type ChangeSet, type ChatReference, DeleteConfirmButton, type DiffActionMark, type DiffAdapter, type DiffSceneMark, EMPTY_SELECTION, EditableText, type EpisodeWorkspaceView, type FieldChange, Popover, type SaveStatus, type Scene, type SceneActionDiff, type SceneChangeKind, type SceneContext, type SceneEnvironment, type ScriptActor, ScriptContentType, type ScriptData, ScriptEditor, type ScriptEditorAdapters, type ScriptEditorProps, type ScriptEpisode, type ScriptFormat, type ScriptItem, type ScriptLocation, type ScriptNav, type ScriptNavEntity, type ScriptNavEpisode, type ScriptNavScene, type ScriptProject, type ScriptProp, type ScriptReferenceFocusRequest, type ScriptReferenceLocator, type ScriptSpeaker, type ScriptTargetKind, SelectField, type StateChange, type StudioSelection, type StudioSelectionFrom, type StudioSelectionOrigin, type TransitionPrompt, acceptActionIntoBaseline, acceptSceneIntoBaseline, applyActionAccept, applyActionReject, buildActionDiff, buildDiffFromBaseline, buildScriptNav, buildSelectionFrom, changeSetToDiffScenes, computeChangeSet, computeSceneActionDiff, findBaselineScene, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, index as mutations, rejectActionToBaseline, rejectSceneToBaseline, sceneChangeKey, selectionFromScriptReference, selectionTargetKey, withOrigin };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createContext, memo, useState, useMemo, useCallback, Fragment, useRef, useContext, useEffect, useLayoutEffect, useDeferredValue } from 'react';
2
- import { Plus, Trash2, X, Check, Eraser, ArrowUp, ArrowDown, GripVertical, MessageCircle, Film, Brain, BookOpen, Scissors, ArrowUpToLine, Globe, BookText, ChevronRight, FileText, Users, MapPin, Package, Undo2, Redo2, MessageSquareQuote, ArrowLeft, ChevronLeft, Loader2, AlertCircle } from 'lucide-react';
2
+ import { Plus, Trash2, X, Check, Eraser, ArrowUp, ArrowDown, GripVertical, MessageCircle, Film, Brain, BookOpen, Scissors, ArrowUpToLine, BookText, ChevronRight, FileText, Users, MapPin, Package, Undo2, Redo2, MessageSquareQuote, ArrowLeft, ChevronLeft, Loader2, AlertCircle } from 'lucide-react';
3
3
  import { jsxs, jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
4
4
  import { Virtuoso } from 'react-virtuoso';
5
5
  import { createPortal } from 'react-dom';
@@ -479,18 +479,6 @@ function StudioToc({
479
479
  const pick = (sel) => onSelect(withOrigin(sel, "sidebar"));
480
480
  const epHighlighted = selection.kind === "episode" || selection.kind === "scene" || selection.kind === "clip";
481
481
  return /* @__PURE__ */ jsxs("nav", { className: "lj-se-toc", children: [
482
- /* @__PURE__ */ jsxs(
483
- "button",
484
- {
485
- type: "button",
486
- className: cn("lj-se-tocitem lj-se-toc-l0", selection.kind === "worldview" && "is-active"),
487
- onClick: () => pick({ kind: "worldview" }),
488
- children: [
489
- /* @__PURE__ */ jsx(Globe, { className: "lj-se-tocitem-icon lj-se-ic-4", strokeWidth: 1.5 }),
490
- /* @__PURE__ */ jsx("span", { className: "lj-se-tocitem-label", children: "\u4E16\u754C\u89C2 & \u98CE\u683C" })
491
- ]
492
- }
493
- ),
494
482
  /* @__PURE__ */ jsx(
495
483
  GroupHeader,
496
484
  {
@@ -2602,82 +2590,6 @@ function SceneEditorImpl({
2602
2590
  ] });
2603
2591
  }
2604
2592
  var SceneEditor = memo(SceneEditorImpl);
2605
- function Stat({ label, value }) {
2606
- return /* @__PURE__ */ jsxs("div", { className: "lj-se-stat", children: [
2607
- /* @__PURE__ */ jsx("span", { className: "lj-se-stat-label", children: label }),
2608
- /* @__PURE__ */ jsx("span", { className: "lj-se-stat-value", children: value })
2609
- ] });
2610
- }
2611
- function WorldviewEditor({
2612
- data,
2613
- disabled = false,
2614
- onEdit
2615
- }) {
2616
- const episodes = data.episodes ?? [];
2617
- const totalScenes = episodes.reduce((sum, ep) => sum + (ep.scenes?.length ?? 0), 0);
2618
- const totalLines = episodes.reduce(
2619
- (sum, ep) => sum + (ep.scenes ?? []).reduce((s, sc) => s + (sc.actions?.length ?? 0), 0),
2620
- 0
2621
- );
2622
- return /* @__PURE__ */ jsxs("div", { className: "lj-se-worldview lj-se-scroll", children: [
2623
- /* @__PURE__ */ jsxs("header", { className: "lj-se-wv-header", children: [
2624
- /* @__PURE__ */ jsx("div", { className: "lj-se-wv-kicker", children: "\u5267\u672C \xB7 \u9876\u5C42" }),
2625
- /* @__PURE__ */ jsx("h2", { className: "lj-se-wv-title", children: "\u4E16\u754C\u89C2 & \u98CE\u683C" }),
2626
- /* @__PURE__ */ jsx("p", { className: "lj-se-wv-sub", children: "\u6539\u52A8\u4F1A\u5F71\u54CD\u4E0B\u6E38\u8D44\u4EA7\u751F\u6210\u7684 prompt\uFF1B\u6539\u5B8C\u540E\u53EF\u8BA9 AI \u540C\u6B65\u66F4\u65B0\u4EBA\u8BBE / \u573A\u666F\u3002" })
2627
- ] }),
2628
- /* @__PURE__ */ jsxs("section", { className: "lj-se-stats", children: [
2629
- /* @__PURE__ */ jsx(Stat, { label: "\u96C6\u6570", value: episodes.length }),
2630
- /* @__PURE__ */ jsx(Stat, { label: "\u573A\u666F", value: totalScenes }),
2631
- /* @__PURE__ */ jsx(Stat, { label: "\u53F0\u8BCD", value: totalLines }),
2632
- /* @__PURE__ */ jsx(Stat, { label: "\u89D2\u8272", value: data.actors?.length ?? 0 }),
2633
- /* @__PURE__ */ jsx(Stat, { label: "\u9053\u5177", value: data.props?.length ?? 0 })
2634
- ] }),
2635
- /* @__PURE__ */ jsxs("div", { className: "lj-se-field", children: [
2636
- /* @__PURE__ */ jsx("div", { className: "lj-se-field-label", children: "\u6807\u9898" }),
2637
- /* @__PURE__ */ jsx("div", { className: "lj-se-field-box", children: /* @__PURE__ */ jsx(
2638
- EditableText,
2639
- {
2640
- value: data.title ?? "",
2641
- disabled,
2642
- placeholder: "\u672A\u547D\u540D\u5267\u672C",
2643
- onSave: (v) => onEdit((p) => ({ ...p, title: v }))
2644
- }
2645
- ) })
2646
- ] }),
2647
- /* @__PURE__ */ jsxs("div", { className: "lj-se-field", children: [
2648
- /* @__PURE__ */ jsxs("div", { className: "lj-se-field-label", children: [
2649
- "\u4E16\u754C\u89C2 ",
2650
- /* @__PURE__ */ jsx("span", { className: "lj-se-field-hint", children: "\u591A\u884C\uFF1BAI \u5199\u5267\u672C\u65F6\u4F1A\u5F15\u7528" })
2651
- ] }),
2652
- /* @__PURE__ */ jsx("div", { className: "lj-se-field-box", children: /* @__PURE__ */ jsx(
2653
- EditableText,
2654
- {
2655
- multiline: true,
2656
- value: data.worldview ?? "",
2657
- disabled,
2658
- placeholder: "\u63CF\u8FF0\u6545\u4E8B\u53D1\u751F\u7684\u4E16\u754C\u3001\u80CC\u666F\u8BBE\u5B9A\u2026",
2659
- onSave: (v) => onEdit((p) => ({ ...p, worldview: v }))
2660
- }
2661
- ) })
2662
- ] }),
2663
- /* @__PURE__ */ jsxs("div", { className: "lj-se-field", children: [
2664
- /* @__PURE__ */ jsxs("div", { className: "lj-se-field-label", children: [
2665
- "\u98CE\u683C ",
2666
- /* @__PURE__ */ jsx("span", { className: "lj-se-field-hint", children: "\u8BED\u8A00\u8C03\u6027 / \u955C\u5934\u8BED\u8A00\u503E\u5411" })
2667
- ] }),
2668
- /* @__PURE__ */ jsx("div", { className: "lj-se-field-box", children: /* @__PURE__ */ jsx(
2669
- EditableText,
2670
- {
2671
- multiline: true,
2672
- value: data.style ?? "",
2673
- disabled,
2674
- placeholder: "\u4F8B\u5982\uFF1A\u5199\u5B9E\u3001\u51B7\u5CFB\u3001\u957F\u955C\u5934\u2026",
2675
- onSave: (v) => onEdit((p) => ({ ...p, style: v }))
2676
- }
2677
- ) })
2678
- ] })
2679
- ] });
2680
- }
2681
2593
 
2682
2594
  // src/stream/flat-items.ts
2683
2595
  var SCENE_KEY = (e, s) => `ep:${e}:sc:${s}`;
@@ -2704,7 +2616,6 @@ function buildFlatStream(data) {
2704
2616
  if (item.kind === "asset-card") assetIndexLookup.set(ASSET_KEY(item.group, item.assetId), items.length);
2705
2617
  items.push(item);
2706
2618
  };
2707
- push({ key: "worldview", kind: "worldview" });
2708
2619
  if (!data) {
2709
2620
  return { items, indexByKey, sceneIndexLookup, sceneIdLookup, assetIndexLookup, paperStartKey: null, paperEndKey: null };
2710
2621
  }
@@ -2751,8 +2662,6 @@ function buildFlatStream(data) {
2751
2662
  }
2752
2663
  function selectionToFlatIndex(stream, sel) {
2753
2664
  switch (sel.kind) {
2754
- case "worldview":
2755
- return stream.indexByKey.get("worldview") ?? null;
2756
2665
  case "episode":
2757
2666
  return stream.indexByKey.get(`ep:${sel.episodeIdx}:header`) ?? null;
2758
2667
  case "scene":
@@ -2779,8 +2688,6 @@ function flatIndexToSelection(stream, idx) {
2779
2688
  const item = stream.items[idx];
2780
2689
  if (!item) return null;
2781
2690
  switch (item.kind) {
2782
- case "worldview":
2783
- return { kind: "worldview" };
2784
2691
  case "episode-header":
2785
2692
  return { kind: "episode", episodeIdx: item.episodeIdx };
2786
2693
  case "scene":
@@ -3402,8 +3309,6 @@ function ScriptStream({
3402
3309
  itemContent: (_index, item) => {
3403
3310
  if (!item) return null;
3404
3311
  switch (item.kind) {
3405
- case "worldview":
3406
- return /* @__PURE__ */ jsx("div", { className: "lj-se-stream-worldview", children: /* @__PURE__ */ jsx(WorldviewEditor, { data: value, disabled, onEdit }) });
3407
3312
  case "episode-header": {
3408
3313
  const episode = value.episodes?.[item.episodeIdx];
3409
3314
  if (!episode) return null;
@@ -3989,6 +3894,7 @@ function ScriptEditor({
3989
3894
  disabled = false,
3990
3895
  className,
3991
3896
  dark,
3897
+ toc = "sidebar",
3992
3898
  onTitleChange,
3993
3899
  topBarActions,
3994
3900
  saveStatus = "idle",
@@ -4152,7 +4058,7 @@ function ScriptEditor({
4152
4058
  }
4153
4059
  );
4154
4060
  return /* @__PURE__ */ jsx(EditingSignalContext.Provider, { value: editingSignal, children: /* @__PURE__ */ jsxs("div", { className: rootClass, children: [
4155
- /* @__PURE__ */ jsx(
4061
+ toc === "sidebar" ? /* @__PURE__ */ jsx(
4156
4062
  StudioToc,
4157
4063
  {
4158
4064
  data: value,
@@ -4160,7 +4066,7 @@ function ScriptEditor({
4160
4066
  onSelect: setSelection,
4161
4067
  onAddEpisode: disabled ? void 0 : () => onEdit((p) => addEpisode(p))
4162
4068
  }
4163
- ),
4069
+ ) : null,
4164
4070
  /* @__PURE__ */ jsxs("div", { className: "lj-se-main", children: [
4165
4071
  /* @__PURE__ */ jsx(
4166
4072
  CanvasTopBar,
@@ -4258,6 +4164,53 @@ function resolveReturnTarget(value, selection) {
4258
4164
  return { label, selection: withOrigin(fromToSelection(from), "reference") };
4259
4165
  }
4260
4166
 
4167
+ // src/toc/nav-model.ts
4168
+ function nav(selection) {
4169
+ return { key: selectionTargetKey(selection), selection };
4170
+ }
4171
+ function buildScriptNav(value) {
4172
+ const episodes = (value.episodes ?? []).map((episode, episodeIdx) => {
4173
+ const scenes = (episode.scenes ?? []).map((scene, sceneIdx) => {
4174
+ const selection2 = { kind: "scene", episodeIdx, sceneIdx };
4175
+ return {
4176
+ ...nav(selection2),
4177
+ episodeIdx,
4178
+ sceneIdx,
4179
+ sceneId: scene.scene_id ?? "",
4180
+ label: getSceneNumberLabel(scene, sceneIdx),
4181
+ meta: getSceneSidebarLabel(scene, sceneIdx)
4182
+ };
4183
+ });
4184
+ const selection = { kind: "episode", episodeIdx };
4185
+ return {
4186
+ ...nav(selection),
4187
+ episodeIdx,
4188
+ label: getEpisodeNumberLabel(episode, episodeIdx),
4189
+ title: getEpisodeTitle(episode, episodeIdx),
4190
+ sceneCount: scenes.length,
4191
+ scenes
4192
+ };
4193
+ });
4194
+ const actors = (value.actors ?? []).map((a) => {
4195
+ const id = (a.actor_id ?? "").trim();
4196
+ return { ...nav({ kind: "actor", actorId: id }), id, name: getActorDisplayName(a) };
4197
+ }).filter((e) => e.id);
4198
+ const locations = (value.locations ?? []).map((l) => {
4199
+ const id = (l.location_id ?? "").trim();
4200
+ return { ...nav({ kind: "location", locationId: id }), id, name: getLocationDisplayName(l) };
4201
+ }).filter((e) => e.id);
4202
+ const props = (value.props ?? []).map((p) => {
4203
+ const id = (p.prop_id ?? "").trim();
4204
+ return { ...nav({ kind: "prop", propId: id }), id, name: getPropDisplayName(p) };
4205
+ }).filter((e) => e.id);
4206
+ return {
4207
+ episodes,
4208
+ actors,
4209
+ locations,
4210
+ props
4211
+ };
4212
+ }
4213
+
4261
4214
  // src/mutations/index.ts
4262
4215
  var mutations_exports = {};
4263
4216
  __export(mutations_exports, {
@@ -4299,6 +4252,6 @@ __export(mutations_exports, {
4299
4252
  updateScenePropState: () => updateScenePropState
4300
4253
  });
4301
4254
 
4302
- export { DeleteConfirmButton, EMPTY_SELECTION, EditableText, Popover, ScriptContentType, ScriptEditor, SelectField, acceptActionIntoBaseline, acceptSceneIntoBaseline, applyActionAccept, applyActionReject, buildActionDiff, buildDiffFromBaseline, buildSelectionFrom, changeSetToDiffScenes, computeChangeSet, computeSceneActionDiff, findBaselineScene, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, mutations_exports as mutations, rejectActionToBaseline, rejectSceneToBaseline, sceneChangeKey, selectionFromScriptReference, selectionTargetKey, withOrigin };
4255
+ export { DeleteConfirmButton, EMPTY_SELECTION, EditableText, Popover, ScriptContentType, ScriptEditor, SelectField, acceptActionIntoBaseline, acceptSceneIntoBaseline, applyActionAccept, applyActionReject, buildActionDiff, buildDiffFromBaseline, buildScriptNav, buildSelectionFrom, changeSetToDiffScenes, computeChangeSet, computeSceneActionDiff, findBaselineScene, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, mutations_exports as mutations, rejectActionToBaseline, rejectSceneToBaseline, sceneChangeKey, selectionFromScriptReference, selectionTargetKey, withOrigin };
4303
4256
  //# sourceMappingURL=index.js.map
4304
4257
  //# sourceMappingURL=index.js.map