@lingjingai/script-editor 0.2.1 → 0.3.1

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/dist/index.d.ts CHANGED
@@ -37,6 +37,11 @@ interface ScriptItem {
37
37
  emotion?: string;
38
38
  state_changes?: StateChange[];
39
39
  transition_prompt?: TransitionPrompt;
40
+ /** v3 free-form namespace; `extend.timestamp` is the in-episode "M:SS.mmm". */
41
+ extend?: {
42
+ timestamp?: string;
43
+ [key: string]: unknown;
44
+ };
40
45
  }
41
46
  interface AssetState {
42
47
  state_id?: string;
@@ -500,7 +505,9 @@ interface ScriptEditorAdapters {
500
505
 
501
506
  interface ScriptEditorProps extends ScriptEditorAdapters {
502
507
  value: ScriptProject;
503
- onEdit: (patch: (prev: ScriptProject) => ScriptProject) => void;
508
+ /** the (prev)=>next patch sink. Optional — omit for a pure read-only view
509
+ * (pair with `disabled`); edits become no-ops. */
510
+ onEdit?: (patch: (prev: ScriptProject) => ScriptProject) => void;
504
511
  selection?: StudioSelection;
505
512
  onSelectionChange?: (next: StudioSelection) => void;
506
513
  format?: ScriptFormat;
@@ -537,8 +544,69 @@ interface ScriptEditorProps extends ScriptEditorAdapters {
537
544
  /** stable per-scope key (e.g. projectGroupNo) — remembers & restores the
538
545
  * scroll position across editor unmount/remount (tab switch / project nav). */
539
546
  scrollScopeKey?: string;
547
+ /** render the asset-card groups (角色/场景/道具) below the script. Default true. */
548
+ showAssets?: boolean;
549
+ /** render the top bar's script title. Default true. */
550
+ showTitle?: boolean;
551
+ /** render the 标准/亚洲 format toggle button. Default true (format stays controlled). */
552
+ showFormatToggle?: boolean;
553
+ /** parchment paper surface. Default true; false → flat plain segments. */
554
+ paper?: boolean;
555
+ /** render a MM:SS gutter from each action's extend.timestamp. Default false. */
556
+ showTimestamp?: boolean;
557
+ /** host range highlights (per action), rendered on top of the row. */
558
+ highlights?: ScriptHighlight[];
559
+ /** content column width (script segments + asset cards). Number → px; string
560
+ * passes through as-is (e.g. "100%" to fill, "60rem"). Default 820px. Sets
561
+ * the --lj-se-content-width CSS var; hosts can also override that var in CSS. */
562
+ contentWidth?: number | string;
563
+ /** the floating bottom-center capsule (save status + AI-diff + 返回 breadcrumb).
564
+ * Default true. Set false for a pure display surface with no floating chrome. */
565
+ showSaveCapsule?: boolean;
566
+ /** per-action hover (target on enter, null on leave) — pair with `highlights`
567
+ * to build a hover-to-highlight effect entirely host-side. */
568
+ onActionHover?: (target: {
569
+ episodeIdx: number;
570
+ sceneId: string;
571
+ actionIndex: number;
572
+ } | null) => void;
573
+ /** per-action click, with full address. */
574
+ onActionClick?: (target: {
575
+ episodeIdx: number;
576
+ sceneId: string;
577
+ actionIndex: number;
578
+ }) => void;
579
+ }
580
+ /** A host range highlight targeting one action (same coords as DiffActionMark). */
581
+ interface ScriptHighlight {
582
+ target: {
583
+ episodeIdx: number;
584
+ sceneId: string;
585
+ actionIndex: number;
586
+ };
587
+ className?: string;
588
+ style?: React.CSSProperties;
540
589
  }
541
- 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;
590
+ declare function ScriptEditor({ value, onEdit: onEditProp, selection: controlledSelection, onSelectionChange, format: controlledFormat, onFormatChange, disabled, className, dark, toc, onTitleChange, topBarActions, saveStatus, canUndo, canRedo, onUndo, onRedo, onEditingChange, referenceFocusRequest, scrollScopeKey, showAssets, showTitle, showFormatToggle, paper, showTimestamp, highlights, contentWidth, showSaveCapsule, onActionHover, onActionClick, diff, asset, onAddChatReferences, onRequestAssetDelete, }: ScriptEditorProps): react.JSX.Element;
591
+
592
+ /**
593
+ * Action timestamps live at `action.extend.timestamp` as an in-episode
594
+ * "M:SS.mmm" string (e.g. "00:03.333", "01:12.000"). The editor shows the
595
+ * "MM:SS" part prominently and the ".mmm" fraction de-emphasized.
596
+ *
597
+ * Parsing is lenient: anything that isn't a well-formed timestamp returns null
598
+ * so the caller simply renders no time gutter for that row (never a placeholder).
599
+ */
600
+ interface ParsedTimestamp {
601
+ /** minutes:seconds, zero-padded — the prominent part, e.g. "01:07". */
602
+ mmss: string;
603
+ /** the fractional ".mmm" suffix incl. the dot, or "" when there is none. */
604
+ ms: string;
605
+ /** full normalized "MM:SS.mmm" (or "MM:SS" when no fraction) for title/hover. */
606
+ full: string;
607
+ }
608
+ /** Parse "M:SS.mmm" → { mmss, ms, full }, or null when malformed / empty. */
609
+ declare function formatTimestamp(raw: unknown): ParsedTimestamp | null;
542
610
 
543
611
  /**
544
612
  * Headless navigation model — surfaces the script's TOC tree (the same
@@ -830,4 +898,4 @@ declare function DeleteConfirmButton({ onConfirm, disabled, targetScope, title,
830
898
  description?: string;
831
899
  }): react.JSX.Element;
832
900
 
833
- 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, type Importance, 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, type V3Speaker, 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 };
901
+ 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, type Importance, type ParsedTimestamp, 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 ScriptHighlight, 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, type V3Speaker, acceptActionIntoBaseline, acceptSceneIntoBaseline, applyActionAccept, applyActionReject, buildActionDiff, buildDiffFromBaseline, buildScriptNav, buildSelectionFrom, changeSetToDiffScenes, computeChangeSet, computeSceneActionDiff, findBaselineScene, formatTimestamp, 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
@@ -633,11 +633,11 @@ function EntityGroup({
633
633
  const valid = items.filter((i) => i.id);
634
634
  const featured = valid.filter((i) => i.importance !== "background").sort(byCount);
635
635
  const background = valid.filter((i) => i.importance === "background").sort(byCount);
636
- const row = (i) => /* @__PURE__ */ jsxs(
636
+ const row = (i, bg = false) => /* @__PURE__ */ jsxs(
637
637
  "button",
638
638
  {
639
639
  type: "button",
640
- className: cn("lj-se-tocitem lj-se-toc-l1", isActive(i.id) && "is-active"),
640
+ className: cn("lj-se-tocitem", bg ? "lj-se-toc-bg" : "lj-se-toc-l1", isActive(i.id) && "is-active"),
641
641
  onClick: () => onPick(i.id),
642
642
  children: [
643
643
  /* @__PURE__ */ jsx(Icon, { className: "lj-se-tocitem-icon lj-se-ic-3", strokeWidth: 1.5 }),
@@ -648,9 +648,9 @@ function EntityGroup({
648
648
  i.id
649
649
  );
650
650
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
651
- /* @__PURE__ */ jsx(GroupHeader, { icon: Icon, label, count: valid.length, open, highlighted, onToggle: () => setOpen((v) => !v) }),
651
+ /* @__PURE__ */ jsx(GroupHeader, { icon: Icon, label, count: featured.length, open, highlighted, onToggle: () => setOpen((v) => !v) }),
652
652
  open ? valid.length === 0 ? /* @__PURE__ */ jsx("div", { className: "lj-se-toc-empty", children: emptyText }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
653
- featured.map(row),
653
+ featured.map((i) => row(i)),
654
654
  background.length > 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
655
655
  /* @__PURE__ */ jsxs("button", { type: "button", className: cn("lj-se-toc-fold", bgOpen && "is-open"), onClick: () => setBgOpen((v) => !v), children: [
656
656
  /* @__PURE__ */ jsx(ChevronRight, { className: "lj-se-toc-fold-chev lj-se-ic-3", strokeWidth: 2 }),
@@ -661,7 +661,7 @@ function EntityGroup({
661
661
  background.length
662
662
  ] })
663
663
  ] }),
664
- bgOpen ? background.map(row) : null
664
+ bgOpen ? background.map((i) => row(i, true)) : null
665
665
  ] }) : null
666
666
  ] }) : null
667
667
  ] });
@@ -887,6 +887,7 @@ function CanvasTopBar({
887
887
  contextLabel,
888
888
  format,
889
889
  onFormatChange,
890
+ showFormatToggle = true,
890
891
  canUndo,
891
892
  canRedo,
892
893
  onUndo,
@@ -912,7 +913,7 @@ function CanvasTopBar({
912
913
  ] }) : null
913
914
  ] }),
914
915
  /* @__PURE__ */ jsxs("div", { className: "lj-se-topbar-right", children: [
915
- /* @__PURE__ */ jsx(FormatToggle, { value: format, onChange: onFormatChange }),
916
+ showFormatToggle ? /* @__PURE__ */ jsx(FormatToggle, { value: format, onChange: onFormatChange }) : null,
916
917
  onUndo || onRedo ? /* @__PURE__ */ jsxs("div", { className: "lj-se-topbar-undo", children: [
917
918
  /* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-rowbtn", disabled: !canUndo, title: "\u64A4\u9500", onClick: onUndo, children: /* @__PURE__ */ jsx(Undo2, { className: "lj-se-ic-4", strokeWidth: 1.75 }) }),
918
919
  /* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-rowbtn", disabled: !canRedo, title: "\u91CD\u505A", onClick: onRedo, children: /* @__PURE__ */ jsx(Redo2, { className: "lj-se-ic-4", strokeWidth: 1.75 }) })
@@ -933,7 +934,17 @@ function SaveCapsule({
933
934
  returnLabel,
934
935
  onReturn
935
936
  }) {
936
- const showSave = saveStatus !== "idle";
937
+ const [savedExpired, setSavedExpired] = useState(false);
938
+ useEffect(() => {
939
+ if (saveStatus !== "saved") {
940
+ setSavedExpired(false);
941
+ return;
942
+ }
943
+ setSavedExpired(false);
944
+ const t = setTimeout(() => setSavedExpired(true), 1600);
945
+ return () => clearTimeout(t);
946
+ }, [saveStatus]);
947
+ const showSave = saveStatus !== "idle" && !(saveStatus === "saved" && savedExpired);
937
948
  const hasDiff = diffCount > 0;
938
949
  const hasReturn = !!(returnLabel && onReturn);
939
950
  if (!showSave && !hasDiff && !hasReturn) return null;
@@ -1861,6 +1872,21 @@ function useDragReorder() {
1861
1872
  return useContext(DragReorderContext);
1862
1873
  }
1863
1874
 
1875
+ // src/timestamp.ts
1876
+ var TS_RE = /^\s*(\d+):([0-5]?\d)(?:\.(\d{1,3}))?\s*$/;
1877
+ function formatTimestamp(raw) {
1878
+ if (typeof raw !== "string") return null;
1879
+ const m = TS_RE.exec(raw);
1880
+ if (!m) return null;
1881
+ const min = Number(m[1]);
1882
+ const sec = Number(m[2]);
1883
+ if (!Number.isFinite(min) || !Number.isFinite(sec)) return null;
1884
+ const mmss = `${String(min).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
1885
+ const frac = m[3] ? m[3].padEnd(3, "0") : "";
1886
+ const ms = frac ? `.${frac}` : "";
1887
+ return { mmss, ms, full: `${mmss}${ms}` };
1888
+ }
1889
+
1864
1890
  // src/mutations/item-mutations.ts
1865
1891
  function withSceneUpdated2(data, episodeIndex, sceneIndex, updater) {
1866
1892
  const episodes = [...data.episodes || []];
@@ -2622,6 +2648,10 @@ function SceneEditorImpl({
2622
2648
  disabled,
2623
2649
  focused = false,
2624
2650
  actionDiff,
2651
+ showTimestamp = false,
2652
+ highlights,
2653
+ onActionHover,
2654
+ onActionClick,
2625
2655
  onEdit,
2626
2656
  onNavigateAsset,
2627
2657
  onRequestAssetDelete
@@ -2629,6 +2659,7 @@ function SceneEditorImpl({
2629
2659
  const [pendingEdit, setPendingEdit] = useState(null);
2630
2660
  const { actorMap, speakerMap, locationMap, propMap } = maps;
2631
2661
  const actions = scene.actions ?? [];
2662
+ const sceneId = scene.scene_id ?? "";
2632
2663
  const actorOptions = useMemo(() => {
2633
2664
  const out = [];
2634
2665
  for (const a of actorMap.values()) {
@@ -2720,9 +2751,11 @@ function SceneEditorImpl({
2720
2751
  onNavigateAsset
2721
2752
  }
2722
2753
  ),
2723
- /* @__PURE__ */ jsxs("div", { className: "lj-se-actions", children: [
2754
+ /* @__PURE__ */ jsxs("div", { className: cn("lj-se-actions", showTimestamp && "has-ts"), children: [
2724
2755
  actions.map((item, idx) => {
2725
2756
  const mark = actionDiff?.byIndex.get(idx);
2757
+ const hl = highlights?.get(idx);
2758
+ const ts = showTimestamp ? formatTimestamp(item.extend?.timestamp) : null;
2726
2759
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2727
2760
  renderGhosts(idx),
2728
2761
  /* @__PURE__ */ jsxs(
@@ -2732,11 +2765,18 @@ function SceneEditorImpl({
2732
2765
  "lj-se-action-slot",
2733
2766
  dragIndex === idx && "is-dragging",
2734
2767
  dropSlot === idx && "drop-before",
2735
- mark && (mark.kind === "added" ? "is-diff-added" : "is-diff-modified")
2768
+ mark && (mark.kind === "added" ? "is-diff-added" : "is-diff-modified"),
2769
+ hl && "lj-se-hl",
2770
+ hl?.className
2736
2771
  ),
2772
+ style: hl?.style,
2737
2773
  onDragOver: (e) => onSlotDragOver(e, idx),
2738
2774
  onDrop: onSlotDrop,
2775
+ onMouseEnter: onActionHover ? () => onActionHover({ episodeIdx: episodeIndex, sceneId, actionIndex: idx }) : void 0,
2776
+ onMouseLeave: onActionHover ? () => onActionHover(null) : void 0,
2777
+ onClick: onActionClick ? () => onActionClick({ episodeIdx: episodeIndex, sceneId, actionIndex: idx }) : void 0,
2739
2778
  children: [
2779
+ ts ? /* @__PURE__ */ jsx("span", { className: "lj-se-ts", title: ts.full, children: ts.mmss }) : null,
2740
2780
  mark ? /* @__PURE__ */ jsx(
2741
2781
  DiffActionControls,
2742
2782
  {
@@ -2817,7 +2857,8 @@ function safeId(raw, fallback) {
2817
2857
  }
2818
2858
  var GROUP_LABEL = { actors: "\u89D2\u8272", locations: "\u573A\u666F", props: "\u9053\u5177" };
2819
2859
  var assetGroupLabel = (g) => GROUP_LABEL[g];
2820
- function buildFlatStream(data) {
2860
+ function buildFlatStream(data, opts = {}) {
2861
+ const includeAssets = opts.includeAssets ?? true;
2821
2862
  const items = [];
2822
2863
  const indexByKey = /* @__PURE__ */ new Map();
2823
2864
  const sceneIndexLookup = /* @__PURE__ */ new Map();
@@ -2865,14 +2906,16 @@ function buildFlatStream(data) {
2865
2906
  }
2866
2907
  });
2867
2908
  push({ key: "add-episode", kind: "add-episode" });
2868
- const specs = [
2869
- { group: "actors", ids: (data.actors ?? []).map((a, i) => safeId(a.actor_id, `__idx:${i}`)) },
2870
- { group: "locations", ids: (data.locations ?? []).map((l, i) => safeId(l.location_id, `__idx:${i}`)) },
2871
- { group: "props", ids: (data.props ?? []).map((p, i) => safeId(p.prop_id, `__idx:${i}`)) }
2872
- ];
2873
- for (const { group, ids } of specs) {
2874
- push({ key: `asset-group:${group}`, kind: "asset-group-header", group });
2875
- ids.forEach((assetId) => push({ key: `asset:${group}:${assetId}`, kind: "asset-card", group, assetId }));
2909
+ if (includeAssets) {
2910
+ const specs = [
2911
+ { group: "actors", ids: (data.actors ?? []).map((a, i) => safeId(a.actor_id, `__idx:${i}`)) },
2912
+ { group: "locations", ids: (data.locations ?? []).map((l, i) => safeId(l.location_id, `__idx:${i}`)) },
2913
+ { group: "props", ids: (data.props ?? []).map((p, i) => safeId(p.prop_id, `__idx:${i}`)) }
2914
+ ];
2915
+ for (const { group, ids } of specs) {
2916
+ push({ key: `asset-group:${group}`, kind: "asset-group-header", group });
2917
+ ids.forEach((assetId) => push({ key: `asset:${group}:${assetId}`, kind: "asset-card", group, assetId }));
2918
+ }
2876
2919
  }
2877
2920
  return { items, indexByKey, sceneIndexLookup, sceneIdLookup, assetIndexLookup, paperStartKey, paperEndKey };
2878
2921
  }
@@ -3553,6 +3596,7 @@ function AssetCardImpl({
3553
3596
  onSave: (v) => onEdit((p) => updateEntityStateField(p, kind, assetId, sid, "state_name", v))
3554
3597
  }
3555
3598
  ),
3599
+ (state.state_id ?? "").trim() ? /* @__PURE__ */ jsx("span", { className: "lj-se-assetcard-state-id", title: "\u72B6\u6001 id", children: (state.state_id ?? "").trim() }) : null,
3556
3600
  !disabled && (state.state_id ?? "").trim() ? /* @__PURE__ */ jsx(
3557
3601
  StateDeleteButton,
3558
3602
  {
@@ -3614,11 +3658,30 @@ function ScriptStream({
3614
3658
  onAcceptAction,
3615
3659
  onRejectAction,
3616
3660
  scrollScopeKey,
3661
+ includeAssets = true,
3662
+ paper = true,
3663
+ showTimestamp = false,
3664
+ highlights,
3665
+ onActionHover,
3666
+ onActionClick,
3617
3667
  onEdit,
3618
3668
  onSelectionChange,
3619
3669
  onRequestAssetDelete
3620
3670
  }) {
3621
- const stream = useMemo(() => buildFlatStream(value), [value]);
3671
+ const stream = useMemo(() => buildFlatStream(value, { includeAssets }), [value, includeAssets]);
3672
+ const highlightsByScene = useMemo(() => {
3673
+ const m = /* @__PURE__ */ new Map();
3674
+ for (const h of highlights ?? []) {
3675
+ const key = `${h.episodeIdx}:${h.sceneId}`;
3676
+ let inner = m.get(key);
3677
+ if (!inner) {
3678
+ inner = /* @__PURE__ */ new Map();
3679
+ m.set(key, inner);
3680
+ }
3681
+ inner.set(h.actionIndex, { className: h.className, style: h.style });
3682
+ }
3683
+ return m;
3684
+ }, [highlights]);
3622
3685
  const maps = useMemo(
3623
3686
  () => ({
3624
3687
  actorMap: getActorMap(value.actors ?? []),
@@ -3686,6 +3749,7 @@ function ScriptStream({
3686
3749
  const end = key === stream.paperEndKey;
3687
3750
  return start && end ? "is-sole" : start ? "is-start" : end ? "is-end" : "is-mid";
3688
3751
  };
3752
+ const segClass = (key, extra) => cn(paper ? "lj-se-paper-seg" : "lj-se-plain-seg", paperEdge(key), extra);
3689
3753
  useEffect(() => {
3690
3754
  const key = selectionTargetKey(selection);
3691
3755
  if (selection.origin === "scroll") {
@@ -3723,7 +3787,7 @@ function ScriptStream({
3723
3787
  case "episode-header": {
3724
3788
  const episode = value.episodes?.[item.episodeIdx];
3725
3789
  if (!episode) return null;
3726
- return /* @__PURE__ */ jsx("div", { className: "lj-se-stream-row", children: /* @__PURE__ */ jsx("div", { className: cn("lj-se-paper-seg", paperEdge(item.key), "lj-se-ep-seg"), children: /* @__PURE__ */ jsxs("div", { className: "lj-se-epbanner-wrap", children: [
3790
+ return /* @__PURE__ */ jsx("div", { className: "lj-se-stream-row", children: /* @__PURE__ */ jsx("div", { className: segClass(item.key, "lj-se-ep-seg"), children: /* @__PURE__ */ jsxs("div", { className: "lj-se-epbanner-wrap", children: [
3727
3791
  !disabled ? /* @__PURE__ */ jsx(EpisodeMenu, { episodeIdx: item.episodeIdx, onEdit }) : null,
3728
3792
  /* @__PURE__ */ jsx(
3729
3793
  EpisodeBanner,
@@ -3739,7 +3803,7 @@ function ScriptStream({
3739
3803
  }
3740
3804
  case "episode-empty":
3741
3805
  if (disabled) return null;
3742
- return /* @__PURE__ */ jsx("div", { className: "lj-se-stream-row", children: /* @__PURE__ */ jsx("div", { className: cn("lj-se-paper-seg", paperEdge(item.key)), children: /* @__PURE__ */ jsxs(
3806
+ return /* @__PURE__ */ jsx("div", { className: "lj-se-stream-row", children: /* @__PURE__ */ jsx("div", { className: segClass(item.key), children: /* @__PURE__ */ jsxs(
3743
3807
  "button",
3744
3808
  {
3745
3809
  type: "button",
@@ -3765,7 +3829,7 @@ function ScriptStream({
3765
3829
  onAccept: onAcceptAction,
3766
3830
  onReject: onRejectAction
3767
3831
  } : void 0;
3768
- return /* @__PURE__ */ jsx("div", { className: "lj-se-stream-row", "data-quote-source": true, children: /* @__PURE__ */ jsxs("div", { className: cn("lj-se-paper-seg", paperEdge(item.key), wholeKind && "is-changed"), children: [
3832
+ return /* @__PURE__ */ jsx("div", { className: "lj-se-stream-row", "data-quote-source": true, children: /* @__PURE__ */ jsxs("div", { className: segClass(item.key, wholeKind ? "is-changed" : void 0), children: [
3769
3833
  sceneDiff && inDiff ? /* @__PURE__ */ jsxs("div", { className: "lj-se-scenediff", children: [
3770
3834
  /* @__PURE__ */ jsx("span", { className: cn("lj-se-scenediff-tag", wholeKind === "added" && "is-added"), children: wholeKind === "added" ? "AI \u65B0\u589E" : "AI \u6539\u52A8" }),
3771
3835
  /* @__PURE__ */ jsx("span", { className: "lj-se-scenediff-spacer" }),
@@ -3799,6 +3863,10 @@ function ScriptStream({
3799
3863
  format,
3800
3864
  disabled,
3801
3865
  focused,
3866
+ showTimestamp,
3867
+ highlights: highlightsByScene.get(diffKey),
3868
+ onActionHover,
3869
+ onActionClick,
3802
3870
  onEdit,
3803
3871
  onNavigateAsset: select,
3804
3872
  onRequestAssetDelete
@@ -4539,9 +4607,11 @@ function ScriptSearchLayer({
4539
4607
  /* @__PURE__ */ jsx(HighlightContext.Provider, { value: open ? query : "", children })
4540
4608
  ] });
4541
4609
  }
4610
+ var NOOP_EDIT = (patch) => {
4611
+ };
4542
4612
  function ScriptEditor({
4543
4613
  value,
4544
- onEdit,
4614
+ onEdit: onEditProp,
4545
4615
  selection: controlledSelection,
4546
4616
  onSelectionChange,
4547
4617
  format: controlledFormat,
@@ -4560,11 +4630,22 @@ function ScriptEditor({
4560
4630
  onEditingChange,
4561
4631
  referenceFocusRequest,
4562
4632
  scrollScopeKey,
4633
+ showAssets = true,
4634
+ showTitle = true,
4635
+ showFormatToggle = true,
4636
+ paper = true,
4637
+ showTimestamp = false,
4638
+ highlights,
4639
+ contentWidth,
4640
+ showSaveCapsule = true,
4641
+ onActionHover,
4642
+ onActionClick,
4563
4643
  diff,
4564
4644
  asset,
4565
4645
  onAddChatReferences,
4566
4646
  onRequestAssetDelete
4567
4647
  }) {
4648
+ const onEdit = onEditProp ?? NOOP_EDIT;
4568
4649
  const editingCount = useRef(0);
4569
4650
  const onEditingChangeRef = useRef(onEditingChange);
4570
4651
  onEditingChangeRef.current = onEditingChange;
@@ -4695,6 +4776,17 @@ function ScriptEditor({
4695
4776
  const contextLabel = buildContextLabel(value, selection);
4696
4777
  const returnTarget = resolveReturnTarget(value, selection);
4697
4778
  const rootClass = ["lj-se-root", dark ? "dark" : "", className].filter(Boolean).join(" ");
4779
+ const rootStyle = contentWidth != null ? { "--lj-se-content-width": typeof contentWidth === "number" ? `${contentWidth}px` : contentWidth } : void 0;
4780
+ const streamHighlights = useMemo(
4781
+ () => highlights?.map((h) => ({
4782
+ episodeIdx: h.target.episodeIdx,
4783
+ sceneId: h.target.sceneId,
4784
+ actionIndex: h.target.actionIndex,
4785
+ className: h.className,
4786
+ style: h.style
4787
+ })),
4788
+ [highlights]
4789
+ );
4698
4790
  const center = /* @__PURE__ */ jsx(
4699
4791
  ScriptStream,
4700
4792
  {
@@ -4708,6 +4800,12 @@ function ScriptEditor({
4708
4800
  onAcceptAction: diff?.onAcceptAction,
4709
4801
  onRejectAction: diff?.onRejectAction,
4710
4802
  scrollScopeKey,
4803
+ includeAssets: showAssets,
4804
+ paper,
4805
+ showTimestamp,
4806
+ highlights: streamHighlights,
4807
+ onActionHover,
4808
+ onActionClick,
4711
4809
  onEdit,
4712
4810
  onSelectionChange: setSelection,
4713
4811
  onRequestAssetDelete
@@ -4717,6 +4815,7 @@ function ScriptEditor({
4717
4815
  "div",
4718
4816
  {
4719
4817
  className: rootClass,
4818
+ style: rootStyle,
4720
4819
  ref: rootRef,
4721
4820
  children: [
4722
4821
  toc === "sidebar" ? /* @__PURE__ */ jsx(
@@ -4732,12 +4831,13 @@ function ScriptEditor({
4732
4831
  /* @__PURE__ */ jsx(
4733
4832
  CanvasTopBar,
4734
4833
  {
4735
- title: value.title,
4736
- onTitleChange: disabled ? void 0 : onTitleChange,
4834
+ title: showTitle ? value.title : void 0,
4835
+ onTitleChange: disabled || !showTitle ? void 0 : onTitleChange,
4737
4836
  titleDisabled: disabled,
4738
4837
  contextLabel,
4739
4838
  format,
4740
4839
  onFormatChange: setFormat,
4840
+ showFormatToggle,
4741
4841
  canUndo,
4742
4842
  canRedo,
4743
4843
  onUndo,
@@ -4758,7 +4858,7 @@ function ScriptEditor({
4758
4858
  }
4759
4859
  ),
4760
4860
  onAddChatReferences ? /* @__PURE__ */ jsx(QuoteLayer, { containerRef: canvasRef, onAddChatReferences }) : null,
4761
- /* @__PURE__ */ jsx(
4861
+ showSaveCapsule ? /* @__PURE__ */ jsx(
4762
4862
  SaveCapsule,
4763
4863
  {
4764
4864
  saveStatus,
@@ -4772,7 +4872,7 @@ function ScriptEditor({
4772
4872
  returnLabel: returnTarget?.label,
4773
4873
  onReturn: returnTarget ? () => setSelection(returnTarget.selection) : void 0
4774
4874
  }
4775
- )
4875
+ ) : null
4776
4876
  ] })
4777
4877
  ] })
4778
4878
  ]
@@ -4977,6 +5077,6 @@ function DeleteConfirmButton({
4977
5077
  );
4978
5078
  }
4979
5079
 
4980
- 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 };
5080
+ export { DeleteConfirmButton, EMPTY_SELECTION, EditableText, Popover, ScriptContentType, ScriptEditor, SelectField, acceptActionIntoBaseline, acceptSceneIntoBaseline, applyActionAccept, applyActionReject, buildActionDiff, buildDiffFromBaseline, buildScriptNav, buildSelectionFrom, changeSetToDiffScenes, computeChangeSet, computeSceneActionDiff, findBaselineScene, formatTimestamp, fromToSelection, getActorCueVar, getActorMap, getEpisodeTitle, getLocationMap, getPropMap, getSceneContext, getSelectionEpisodeIdx, getSpeakerMap, isClipSelected, isEpisodeOpen, isSameSelectionTarget, isSceneSelected, mutations_exports as mutations, rejectActionToBaseline, rejectSceneToBaseline, sceneChangeKey, selectionFromScriptReference, selectionTargetKey, withOrigin };
4981
5081
  //# sourceMappingURL=index.js.map
4982
5082
  //# sourceMappingURL=index.js.map