@lingjingai/script-editor 0.1.18 → 0.1.20
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 +3 -2
- package/dist/index.d.ts +1 -6
- package/dist/index.js +345 -150
- package/dist/index.js.map +1 -1
- package/dist/style.css +9 -6
- package/package.json +6 -2
package/INTEGRATION.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
```ts
|
|
21
21
|
import "@lingjingai/script-editor/style.css";
|
|
22
22
|
```
|
|
23
|
-
-
|
|
23
|
+
- **构建产物**:发布 / 外部消费走 `dist`(`tsup` 编译好的 JS + d.ts + style.css + paper-fibers.png),无需编译包内 TS。本仓 `apps/web` dev 通过 Vite 的 `conditions:["development"]` 走 `src/index.ts` + `src/styles.css`,这样改包内源码能 HMR 生效;外部没有该 condition 时自动落到 `dist`。
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
@@ -178,7 +178,8 @@ function Host({ script, onEdit }) {
|
|
|
178
178
|
```
|
|
179
179
|
|
|
180
180
|
`buildScriptNav(value): ScriptNav` 返回:
|
|
181
|
-
- `
|
|
181
|
+
- `episodes[]` / `actors[]` / `locations[]` / `props[]`,`episodes[].scenes[]` 内嵌。
|
|
182
|
+
(**0.1.19 起**:世界观 & 风格的显示与编辑暂时下线——不再有内联区块,也不再是导航节点;宿主无需在目录里渲染它。`WorldviewEditor` 组件保留但未接线,后续恢复时可直接复用。)
|
|
182
183
|
- 每个节点都带 **`key`**(= `selectionTargetKey(node.selection)`,拿去和 `selectionTargetKey(当前selection)` 比对即判激活)和 **`selection`**(即用型 `StudioSelection`,**不带 origin**;点击时 `withOrigin(node.selection, "sidebar")` 再喂 `onSelectionChange`)。
|
|
183
184
|
- 标签字段(`label`/`title`/`meta`/`name`)与内置目录**同 helper 派生**,长相一致。
|
|
184
185
|
|
package/dist/index.d.ts
CHANGED
|
@@ -163,7 +163,7 @@ type StudioSelectionFrom = {
|
|
|
163
163
|
*
|
|
164
164
|
* 不打 origin 时按 'sidebar' 等价处理(保守滚动)。
|
|
165
165
|
*/
|
|
166
|
-
type StudioSelectionOrigin = "sidebar" | "scroll" | "reference" | "diff" | "chat" | "init" | "episode-toggle";
|
|
166
|
+
type StudioSelectionOrigin = "sidebar" | "scroll" | "reference" | "diff" | "search" | "chat" | "init" | "episode-toggle";
|
|
167
167
|
declare function buildSelectionFrom(kind: StudioSelectionFrom["kind"], id: string): StudioSelectionFrom | undefined;
|
|
168
168
|
declare function fromToSelection(from: StudioSelectionFrom): StudioSelection;
|
|
169
169
|
/** 公共字段:所有 selection variant 都可携带(origin 防双向联动死循环;from 资产回链)。 */
|
|
@@ -565,11 +565,6 @@ interface ScriptNavEntity {
|
|
|
565
565
|
selection: StudioSelection;
|
|
566
566
|
}
|
|
567
567
|
interface ScriptNav {
|
|
568
|
-
worldview: {
|
|
569
|
-
key: string;
|
|
570
|
-
label: string;
|
|
571
|
-
selection: StudioSelection;
|
|
572
|
-
};
|
|
573
568
|
episodes: ScriptNavEpisode[];
|
|
574
569
|
actors: ScriptNavEntity[];
|
|
575
570
|
locations: ScriptNavEntity[];
|
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,
|
|
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, Search, 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
|
{
|
|
@@ -608,6 +596,30 @@ function EntityGroup({
|
|
|
608
596
|
)) : null
|
|
609
597
|
] });
|
|
610
598
|
}
|
|
599
|
+
var HighlightContext = createContext("");
|
|
600
|
+
function HighlightedText({ text }) {
|
|
601
|
+
const highlight = useContext(HighlightContext);
|
|
602
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: renderHighlightedText(text, highlight) });
|
|
603
|
+
}
|
|
604
|
+
function renderHighlightedText(value, query) {
|
|
605
|
+
const needle = query.trim();
|
|
606
|
+
if (!needle) return value;
|
|
607
|
+
const lowerValue = value.toLocaleLowerCase();
|
|
608
|
+
const lowerNeedle = needle.toLocaleLowerCase();
|
|
609
|
+
const out = [];
|
|
610
|
+
let from = 0;
|
|
611
|
+
let hit = lowerValue.indexOf(lowerNeedle);
|
|
612
|
+
while (hit >= 0) {
|
|
613
|
+
if (hit > from) out.push(value.slice(from, hit));
|
|
614
|
+
out.push(
|
|
615
|
+
/* @__PURE__ */ jsx("mark", { className: "lj-se-search-hit", children: value.slice(hit, hit + needle.length) }, `${hit}-${out.length}`)
|
|
616
|
+
);
|
|
617
|
+
from = hit + needle.length;
|
|
618
|
+
hit = lowerValue.indexOf(lowerNeedle, from);
|
|
619
|
+
}
|
|
620
|
+
if (from < value.length) out.push(value.slice(from));
|
|
621
|
+
return out.length > 0 ? out : value;
|
|
622
|
+
}
|
|
611
623
|
var EditingSignalContext = createContext(null);
|
|
612
624
|
var LIVE_COMMIT_MS = 400;
|
|
613
625
|
function caretOffsetFromPoint(x, y) {
|
|
@@ -639,6 +651,7 @@ function EditableText({
|
|
|
639
651
|
const inputRef = useRef(null);
|
|
640
652
|
const committedRef = useRef(value);
|
|
641
653
|
const editingSignal = useContext(EditingSignalContext);
|
|
654
|
+
const highlight = useContext(HighlightContext);
|
|
642
655
|
const liveTimerRef = useRef(null);
|
|
643
656
|
const composingRef = useRef(false);
|
|
644
657
|
const draftRef = useRef(value);
|
|
@@ -722,13 +735,14 @@ function EditableText({
|
|
|
722
735
|
onEditEnd?.();
|
|
723
736
|
}
|
|
724
737
|
if (!editing) {
|
|
738
|
+
const content = value ? renderHighlightedText(value, highlight) : /* @__PURE__ */ jsx("span", { className: "lj-se-ph", children: placeholder ?? "" });
|
|
725
739
|
return /* @__PURE__ */ jsx(
|
|
726
740
|
"span",
|
|
727
741
|
{
|
|
728
742
|
...spanProps,
|
|
729
743
|
className: "lj-se-editable" + (className ? " " + className : "") + (disabled ? " lj-se-editable--ro" : ""),
|
|
730
744
|
onClick: beginEdit,
|
|
731
|
-
children:
|
|
745
|
+
children: content
|
|
732
746
|
}
|
|
733
747
|
);
|
|
734
748
|
}
|
|
@@ -1758,6 +1772,9 @@ function SelectField({
|
|
|
1758
1772
|
style,
|
|
1759
1773
|
emptyOptionLabel
|
|
1760
1774
|
}) {
|
|
1775
|
+
const highlight = useContext(HighlightContext).trim();
|
|
1776
|
+
const selectedLabel = options.find((o) => o.value === value)?.label ?? (value ? value : placeholder ?? emptyOptionLabel ?? "\u2014");
|
|
1777
|
+
const highlighted = !!highlight && selectedLabel.toLocaleLowerCase().includes(highlight.toLocaleLowerCase());
|
|
1761
1778
|
return /* @__PURE__ */ jsxs(
|
|
1762
1779
|
"select",
|
|
1763
1780
|
{
|
|
@@ -1766,7 +1783,7 @@ function SelectField({
|
|
|
1766
1783
|
title,
|
|
1767
1784
|
onChange: (e) => onChange(e.target.value),
|
|
1768
1785
|
style,
|
|
1769
|
-
className: cn("lj-se-select", mono && "lj-se-mono", className),
|
|
1786
|
+
className: cn("lj-se-select", mono && "lj-se-mono", highlighted && "is-search-hit", className),
|
|
1770
1787
|
children: [
|
|
1771
1788
|
!value || !options.some((o) => o.value === value) ? /* @__PURE__ */ jsx("option", { value, children: placeholder ?? emptyOptionLabel ?? "\u2014" }) : null,
|
|
1772
1789
|
options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
|
|
@@ -1790,7 +1807,7 @@ function AssetStateChip({
|
|
|
1790
1807
|
const opts = stateOptions(states2);
|
|
1791
1808
|
const trimmed = (currentStateId ?? "").trim();
|
|
1792
1809
|
return /* @__PURE__ */ jsxs("span", { className: cn("lj-se-chip", orphan && "is-orphan"), title: orphan ? `${name} \u4E0D\u5B58\u5728` : void 0, children: [
|
|
1793
|
-
onNavigate ? /* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-chip-name lj-se-chip-name--link", title: `\u8DF3\u5230 ${name}`, onClick: onNavigate, children: name }) : /* @__PURE__ */ jsx("span", { className: "lj-se-chip-name", children: name }),
|
|
1810
|
+
onNavigate ? /* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-chip-name lj-se-chip-name--link", title: `\u8DF3\u5230 ${name}`, onClick: onNavigate, children: /* @__PURE__ */ jsx(HighlightedText, { text: name }) }) : /* @__PURE__ */ jsx("span", { className: "lj-se-chip-name", children: /* @__PURE__ */ jsx(HighlightedText, { text: name }) }),
|
|
1794
1811
|
/* @__PURE__ */ jsx("span", { className: "lj-se-chip-sep", children: "\xB7" }),
|
|
1795
1812
|
/* @__PURE__ */ jsx(
|
|
1796
1813
|
SelectField,
|
|
@@ -2279,7 +2296,7 @@ function SceneItemRowImpl({
|
|
|
2279
2296
|
className: "lj-se-cue lj-se-cue--structured",
|
|
2280
2297
|
style: resolvedActor ? { color: getActorCueVar(resolvedActorId, tone) } : void 0,
|
|
2281
2298
|
children: [
|
|
2282
|
-
getDialogueSpeakerLabel(item, actorMap, speakerMap) || "\u672A\u77E5\u53D1\u58F0\u6E90",
|
|
2299
|
+
/* @__PURE__ */ jsx(HighlightedText, { text: getDialogueSpeakerLabel(item, actorMap, speakerMap) || "\u672A\u77E5\u53D1\u58F0\u6E90" }),
|
|
2283
2300
|
getDialogueDeliveryLabel(item.delivery) ? /* @__PURE__ */ jsxs("span", { className: "lj-se-cue-delivery", children: [
|
|
2284
2301
|
"\uFF08",
|
|
2285
2302
|
getDialogueDeliveryLabel(item.delivery),
|
|
@@ -2371,7 +2388,7 @@ function SceneItemRowImpl({
|
|
|
2371
2388
|
const tone = isInner ? "inner" : "dialogue";
|
|
2372
2389
|
const tag = isInner ? emotion ? `OS\xB7${emotion}` : "OS" : emotion;
|
|
2373
2390
|
body = /* @__PURE__ */ jsxs("p", { className: cn("lj-se-asian-cueline", isInner && "is-inner"), children: [
|
|
2374
|
-
multiSpeaker ? /* @__PURE__ */ jsx("strong", { className: "lj-se-asian-speaker", style: resolvedActor ? { color: getActorCueVar(resolvedActorId, tone) } : void 0, children: getDialogueSpeakerLabel(item, actorMap, speakerMap) }) : /* @__PURE__ */ jsx(
|
|
2391
|
+
multiSpeaker ? /* @__PURE__ */ jsx("strong", { className: "lj-se-asian-speaker", style: resolvedActor ? { color: getActorCueVar(resolvedActorId, tone) } : void 0, children: /* @__PURE__ */ jsx(HighlightedText, { text: getDialogueSpeakerLabel(item, actorMap, speakerMap) }) }) : /* @__PURE__ */ jsx(
|
|
2375
2392
|
SelectField,
|
|
2376
2393
|
{
|
|
2377
2394
|
value: resolvedActorId,
|
|
@@ -2602,82 +2619,6 @@ function SceneEditorImpl({
|
|
|
2602
2619
|
] });
|
|
2603
2620
|
}
|
|
2604
2621
|
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
2622
|
|
|
2682
2623
|
// src/stream/flat-items.ts
|
|
2683
2624
|
var SCENE_KEY = (e, s) => `ep:${e}:sc:${s}`;
|
|
@@ -2704,7 +2645,6 @@ function buildFlatStream(data) {
|
|
|
2704
2645
|
if (item.kind === "asset-card") assetIndexLookup.set(ASSET_KEY(item.group, item.assetId), items.length);
|
|
2705
2646
|
items.push(item);
|
|
2706
2647
|
};
|
|
2707
|
-
push({ key: "worldview", kind: "worldview" });
|
|
2708
2648
|
if (!data) {
|
|
2709
2649
|
return { items, indexByKey, sceneIndexLookup, sceneIdLookup, assetIndexLookup, paperStartKey: null, paperEndKey: null };
|
|
2710
2650
|
}
|
|
@@ -2751,8 +2691,6 @@ function buildFlatStream(data) {
|
|
|
2751
2691
|
}
|
|
2752
2692
|
function selectionToFlatIndex(stream, sel) {
|
|
2753
2693
|
switch (sel.kind) {
|
|
2754
|
-
case "worldview":
|
|
2755
|
-
return stream.indexByKey.get("worldview") ?? null;
|
|
2756
2694
|
case "episode":
|
|
2757
2695
|
return stream.indexByKey.get(`ep:${sel.episodeIdx}:header`) ?? null;
|
|
2758
2696
|
case "scene":
|
|
@@ -2779,8 +2717,6 @@ function flatIndexToSelection(stream, idx) {
|
|
|
2779
2717
|
const item = stream.items[idx];
|
|
2780
2718
|
if (!item) return null;
|
|
2781
2719
|
switch (item.kind) {
|
|
2782
|
-
case "worldview":
|
|
2783
|
-
return { kind: "worldview" };
|
|
2784
2720
|
case "episode-header":
|
|
2785
2721
|
return { kind: "episode", episodeIdx: item.episodeIdx };
|
|
2786
2722
|
case "scene":
|
|
@@ -3168,7 +3104,7 @@ function AssetCardImpl({
|
|
|
3168
3104
|
const description = rawDescription.trim();
|
|
3169
3105
|
const states2 = entity.states ?? [];
|
|
3170
3106
|
const fromRef = buildSelectionFrom(kind, assetId);
|
|
3171
|
-
return /* @__PURE__ */ jsx("div", { className: "lj-se-assetcard-row", children: /* @__PURE__ */ jsxs("article", { className: "lj-se-assetcard", "data-no-quote-selection": true, children: [
|
|
3107
|
+
return /* @__PURE__ */ jsx("div", { className: "lj-se-assetcard-row", children: /* @__PURE__ */ jsx(HighlightContext.Provider, { value: "", children: /* @__PURE__ */ jsxs("article", { className: "lj-se-assetcard", "data-no-quote-selection": true, children: [
|
|
3172
3108
|
/* @__PURE__ */ jsxs("div", { className: "lj-se-assetcard-head", children: [
|
|
3173
3109
|
/* @__PURE__ */ jsx("span", { className: "lj-se-assetcard-name", children: /* @__PURE__ */ jsx(
|
|
3174
3110
|
EditableText,
|
|
@@ -3274,7 +3210,7 @@ function AssetCardImpl({
|
|
|
3274
3210
|
}
|
|
3275
3211
|
)
|
|
3276
3212
|
) : null
|
|
3277
|
-
] }) });
|
|
3213
|
+
] }) }) });
|
|
3278
3214
|
}
|
|
3279
3215
|
function areEqual(prev, next) {
|
|
3280
3216
|
if (prev.group !== next.group || prev.assetId !== next.assetId || prev.disabled !== next.disabled) return false;
|
|
@@ -3402,8 +3338,6 @@ function ScriptStream({
|
|
|
3402
3338
|
itemContent: (_index, item) => {
|
|
3403
3339
|
if (!item) return null;
|
|
3404
3340
|
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
3341
|
case "episode-header": {
|
|
3408
3342
|
const episode = value.episodes?.[item.episodeIdx];
|
|
3409
3343
|
if (!episode) return null;
|
|
@@ -3979,6 +3913,250 @@ function QuoteLayer({
|
|
|
3979
3913
|
}
|
|
3980
3914
|
);
|
|
3981
3915
|
}
|
|
3916
|
+
function ScriptSearchBox({
|
|
3917
|
+
query,
|
|
3918
|
+
current,
|
|
3919
|
+
total,
|
|
3920
|
+
inputRef,
|
|
3921
|
+
onQueryChange,
|
|
3922
|
+
onPrev,
|
|
3923
|
+
onNext,
|
|
3924
|
+
onClose
|
|
3925
|
+
}) {
|
|
3926
|
+
return /* @__PURE__ */ jsxs("div", { className: "lj-se-search", children: [
|
|
3927
|
+
/* @__PURE__ */ jsx(Search, { className: "lj-se-ic-3", strokeWidth: 2 }),
|
|
3928
|
+
/* @__PURE__ */ jsx(
|
|
3929
|
+
"input",
|
|
3930
|
+
{
|
|
3931
|
+
ref: inputRef,
|
|
3932
|
+
className: "lj-se-search-input",
|
|
3933
|
+
value: query,
|
|
3934
|
+
placeholder: "\u641C\u7D22",
|
|
3935
|
+
"aria-label": "\u641C\u7D22\u5267\u672C",
|
|
3936
|
+
onChange: (e) => onQueryChange(e.target.value),
|
|
3937
|
+
onKeyDown: (e) => {
|
|
3938
|
+
if (e.key === "Enter") {
|
|
3939
|
+
e.preventDefault();
|
|
3940
|
+
if (e.shiftKey) onPrev();
|
|
3941
|
+
else onNext();
|
|
3942
|
+
} else if (e.key === "Escape") {
|
|
3943
|
+
e.preventDefault();
|
|
3944
|
+
onClose();
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
),
|
|
3949
|
+
/* @__PURE__ */ jsxs("span", { className: "lj-se-search-count", children: [
|
|
3950
|
+
total > 0 ? current + 1 : 0,
|
|
3951
|
+
"/",
|
|
3952
|
+
total
|
|
3953
|
+
] }),
|
|
3954
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-search-btn", title: "\u4E0A\u4E00\u5904", disabled: total === 0, onClick: onPrev, children: /* @__PURE__ */ jsx(ChevronLeft, { className: "lj-se-ic-3", strokeWidth: 2.25 }) }),
|
|
3955
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-search-btn", title: "\u4E0B\u4E00\u5904", disabled: total === 0, onClick: onNext, children: /* @__PURE__ */ jsx(ChevronRight, { className: "lj-se-ic-3", strokeWidth: 2.25 }) }),
|
|
3956
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "lj-se-search-btn", title: "\u5173\u95ED\u641C\u7D22", onClick: onClose, children: /* @__PURE__ */ jsx(X, { className: "lj-se-ic-3", strokeWidth: 2.25 }) })
|
|
3957
|
+
] });
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
// src/search/search-index.ts
|
|
3961
|
+
var SPACE_LABEL = {
|
|
3962
|
+
interior: "INT. \u5185",
|
|
3963
|
+
exterior: "EXT. \u5916"
|
|
3964
|
+
};
|
|
3965
|
+
var TIME_LABEL = {
|
|
3966
|
+
day: "DAY \u65E5",
|
|
3967
|
+
night: "NIGHT \u591C",
|
|
3968
|
+
dawn: "DAWN \u6668",
|
|
3969
|
+
dusk: "DUSK \u66AE"
|
|
3970
|
+
};
|
|
3971
|
+
function add(parts, value) {
|
|
3972
|
+
const text = value?.trim();
|
|
3973
|
+
if (text) parts.push(text);
|
|
3974
|
+
}
|
|
3975
|
+
function findStateName(states2, stateId) {
|
|
3976
|
+
const id = stateId?.trim();
|
|
3977
|
+
if (!id) return "";
|
|
3978
|
+
return getStateDisplayName(states2?.find((state) => state.state_id?.trim() === id));
|
|
3979
|
+
}
|
|
3980
|
+
function normalize(value) {
|
|
3981
|
+
return value.trim().toLocaleLowerCase();
|
|
3982
|
+
}
|
|
3983
|
+
function sceneSearchText(scene, sceneIdx, maps) {
|
|
3984
|
+
const parts = [];
|
|
3985
|
+
const context = getSceneContext(scene);
|
|
3986
|
+
const primaryLocationId = getPrimarySceneLocationId(scene);
|
|
3987
|
+
const location = primaryLocationId ? maps.locationMap.get(primaryLocationId) : void 0;
|
|
3988
|
+
add(parts, String(getSceneNumber(scene, sceneIdx)));
|
|
3989
|
+
add(parts, SPACE_LABEL[scene.environment?.space ?? ""] ?? scene.environment?.space);
|
|
3990
|
+
add(parts, location ? getLocationDisplayName(location) : primaryLocationId);
|
|
3991
|
+
add(parts, TIME_LABEL[scene.environment?.time ?? ""] ?? scene.environment?.time);
|
|
3992
|
+
for (const ref of context.actors ?? []) {
|
|
3993
|
+
const id = ref.actor_id?.trim();
|
|
3994
|
+
if (!id) continue;
|
|
3995
|
+
const actor = maps.actorMap.get(id);
|
|
3996
|
+
add(parts, actor ? getActorDisplayName(actor) : id);
|
|
3997
|
+
add(parts, findStateName(actor?.states, ref.state_id));
|
|
3998
|
+
}
|
|
3999
|
+
for (const ref of context.props ?? []) {
|
|
4000
|
+
const id = ref.prop_id?.trim();
|
|
4001
|
+
if (!id) continue;
|
|
4002
|
+
const prop = maps.propMap.get(id);
|
|
4003
|
+
add(parts, prop ? getPropDisplayName(prop) : id);
|
|
4004
|
+
add(parts, findStateName(prop?.states, ref.state_id));
|
|
4005
|
+
}
|
|
4006
|
+
for (const item of scene.actions ?? []) {
|
|
4007
|
+
addItemSearchText(parts, item, maps.actorMap, maps.speakerMap);
|
|
4008
|
+
}
|
|
4009
|
+
return parts.join("\n");
|
|
4010
|
+
}
|
|
4011
|
+
function addItemSearchText(parts, item, actorMap, speakerMap) {
|
|
4012
|
+
add(parts, item.content);
|
|
4013
|
+
add(parts, translateEmotionLabel(item.emotion));
|
|
4014
|
+
if (isActorBearingContentType(item.type)) {
|
|
4015
|
+
add(parts, getDialogueSpeakerLabel(item, actorMap, speakerMap));
|
|
4016
|
+
}
|
|
4017
|
+
for (const line of item.lines ?? []) {
|
|
4018
|
+
add(parts, getDialogueSpeakerLabel({ speaker_id: line.speaker_id }, actorMap, speakerMap));
|
|
4019
|
+
add(parts, line.content);
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
function buildScriptSearchEntries(value) {
|
|
4023
|
+
const entries = [];
|
|
4024
|
+
const maps = {
|
|
4025
|
+
actorMap: getActorMap(value.actors ?? []),
|
|
4026
|
+
speakerMap: getSpeakerMap(value.speakers ?? []),
|
|
4027
|
+
locationMap: getLocationMap(value.locations ?? []),
|
|
4028
|
+
propMap: getPropMap(value.props ?? [])
|
|
4029
|
+
};
|
|
4030
|
+
const title = value.title?.trim();
|
|
4031
|
+
if (title && (value.episodes?.length ?? 0) > 0) {
|
|
4032
|
+
entries.push({
|
|
4033
|
+
key: "title",
|
|
4034
|
+
text: title,
|
|
4035
|
+
selection: { kind: "episode", episodeIdx: 0 }
|
|
4036
|
+
});
|
|
4037
|
+
}
|
|
4038
|
+
(value.episodes ?? []).forEach((episode, episodeIdx) => {
|
|
4039
|
+
entries.push({
|
|
4040
|
+
key: `episode:${episodeIdx}`,
|
|
4041
|
+
text: getEpisodeTitle(episode, episodeIdx),
|
|
4042
|
+
selection: { kind: "episode", episodeIdx }
|
|
4043
|
+
});
|
|
4044
|
+
(episode.scenes ?? []).forEach((scene, sceneIdx) => {
|
|
4045
|
+
entries.push({
|
|
4046
|
+
key: `scene:${episodeIdx}:${sceneIdx}`,
|
|
4047
|
+
text: sceneSearchText(scene, sceneIdx, maps),
|
|
4048
|
+
selection: { kind: "scene", episodeIdx, sceneIdx }
|
|
4049
|
+
});
|
|
4050
|
+
});
|
|
4051
|
+
});
|
|
4052
|
+
return entries;
|
|
4053
|
+
}
|
|
4054
|
+
function findScriptSearchMatches(entries, query) {
|
|
4055
|
+
const needle = normalize(query);
|
|
4056
|
+
if (!needle) return [];
|
|
4057
|
+
const matches = [];
|
|
4058
|
+
for (const entry of entries) {
|
|
4059
|
+
if (normalize(entry.text).includes(needle)) {
|
|
4060
|
+
matches.push({ entry, index: matches.length });
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
return matches;
|
|
4064
|
+
}
|
|
4065
|
+
function ScriptSearchLayer({
|
|
4066
|
+
value,
|
|
4067
|
+
disabled,
|
|
4068
|
+
rootRef,
|
|
4069
|
+
selection,
|
|
4070
|
+
onSelectionChange,
|
|
4071
|
+
children
|
|
4072
|
+
}) {
|
|
4073
|
+
const editorActiveRef = useRef(false);
|
|
4074
|
+
const inputRef = useRef(null);
|
|
4075
|
+
const [open, setOpen] = useState(false);
|
|
4076
|
+
const [query, setQuery] = useState("");
|
|
4077
|
+
const [cursor, setCursor] = useState(0);
|
|
4078
|
+
const entries = useMemo(() => buildScriptSearchEntries(value), [value]);
|
|
4079
|
+
const matches = useMemo(() => findScriptSearchMatches(entries, query), [entries, query]);
|
|
4080
|
+
const total = matches.length;
|
|
4081
|
+
const current = total > 0 ? Math.min(cursor, total - 1) : 0;
|
|
4082
|
+
const selectMatch = useCallback(
|
|
4083
|
+
(index) => {
|
|
4084
|
+
const match = matches[index];
|
|
4085
|
+
if (!match) return;
|
|
4086
|
+
const next = withOrigin(match.entry.selection, "search");
|
|
4087
|
+
setCursor(index);
|
|
4088
|
+
if (!isSameSelectionTarget(next, selection)) onSelectionChange(next);
|
|
4089
|
+
},
|
|
4090
|
+
[matches, onSelectionChange, selection]
|
|
4091
|
+
);
|
|
4092
|
+
const openSearch = useCallback(() => {
|
|
4093
|
+
setOpen(true);
|
|
4094
|
+
requestAnimationFrame(() => {
|
|
4095
|
+
inputRef.current?.focus();
|
|
4096
|
+
inputRef.current?.select();
|
|
4097
|
+
});
|
|
4098
|
+
}, []);
|
|
4099
|
+
const closeSearch = useCallback(() => setOpen(false), []);
|
|
4100
|
+
const updateQuery = useCallback((next) => {
|
|
4101
|
+
setQuery(next);
|
|
4102
|
+
setCursor(0);
|
|
4103
|
+
}, []);
|
|
4104
|
+
const nextMatch = useCallback(() => {
|
|
4105
|
+
if (total > 0) selectMatch((current + 1) % total);
|
|
4106
|
+
}, [current, selectMatch, total]);
|
|
4107
|
+
const prevMatch = useCallback(() => {
|
|
4108
|
+
if (total > 0) selectMatch((current - 1 + total) % total);
|
|
4109
|
+
}, [current, selectMatch, total]);
|
|
4110
|
+
useEffect(() => {
|
|
4111
|
+
if (open && total > 0) selectMatch(current);
|
|
4112
|
+
}, [current, open, selectMatch, total]);
|
|
4113
|
+
useEffect(() => {
|
|
4114
|
+
if (!disabled && open) openSearch();
|
|
4115
|
+
}, [disabled, open, openSearch]);
|
|
4116
|
+
useEffect(() => {
|
|
4117
|
+
if (disabled) return;
|
|
4118
|
+
const rememberActive = (target) => {
|
|
4119
|
+
editorActiveRef.current = target instanceof Node && !!rootRef.current?.contains(target);
|
|
4120
|
+
};
|
|
4121
|
+
const onPointerDown = (e) => rememberActive(e.target);
|
|
4122
|
+
const onFocusIn = (e) => rememberActive(e.target);
|
|
4123
|
+
const onKeyDown = (e) => {
|
|
4124
|
+
if (!(e.metaKey || e.ctrlKey) || e.key.toLowerCase() !== "f") return;
|
|
4125
|
+
const target = e.target;
|
|
4126
|
+
const targetInside = target instanceof Node && !!rootRef.current?.contains(target);
|
|
4127
|
+
if (!targetInside) {
|
|
4128
|
+
if (target instanceof HTMLElement && target !== document.body) return;
|
|
4129
|
+
if (!editorActiveRef.current) return;
|
|
4130
|
+
}
|
|
4131
|
+
e.preventDefault();
|
|
4132
|
+
openSearch();
|
|
4133
|
+
};
|
|
4134
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
4135
|
+
document.addEventListener("focusin", onFocusIn, true);
|
|
4136
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
4137
|
+
return () => {
|
|
4138
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
4139
|
+
document.removeEventListener("focusin", onFocusIn, true);
|
|
4140
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
4141
|
+
};
|
|
4142
|
+
}, [disabled, openSearch, rootRef]);
|
|
4143
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
4144
|
+
open ? /* @__PURE__ */ jsx(
|
|
4145
|
+
ScriptSearchBox,
|
|
4146
|
+
{
|
|
4147
|
+
query,
|
|
4148
|
+
current,
|
|
4149
|
+
total,
|
|
4150
|
+
inputRef,
|
|
4151
|
+
onQueryChange: updateQuery,
|
|
4152
|
+
onPrev: prevMatch,
|
|
4153
|
+
onNext: nextMatch,
|
|
4154
|
+
onClose: closeSearch
|
|
4155
|
+
}
|
|
4156
|
+
) : null,
|
|
4157
|
+
/* @__PURE__ */ jsx(HighlightContext.Provider, { value: open ? query : "", children })
|
|
4158
|
+
] });
|
|
4159
|
+
}
|
|
3982
4160
|
function ScriptEditor({
|
|
3983
4161
|
value,
|
|
3984
4162
|
onEdit,
|
|
@@ -4063,6 +4241,7 @@ function ScriptEditor({
|
|
|
4063
4241
|
},
|
|
4064
4242
|
[onFormatChange]
|
|
4065
4243
|
);
|
|
4244
|
+
const rootRef = useRef(null);
|
|
4066
4245
|
const sceneKindByKey = useMemo(() => {
|
|
4067
4246
|
const m = /* @__PURE__ */ new Map();
|
|
4068
4247
|
for (const s of diff?.scenes ?? []) m.set(sceneChangeKey(s.episodeIdx, s.sceneId), s.kind);
|
|
@@ -4152,54 +4331,71 @@ function ScriptEditor({
|
|
|
4152
4331
|
onRequestAssetDelete
|
|
4153
4332
|
}
|
|
4154
4333
|
);
|
|
4155
|
-
return /* @__PURE__ */ jsx(EditingSignalContext.Provider, { value: editingSignal, children: /* @__PURE__ */ jsxs(
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
}
|
|
4164
|
-
) : null,
|
|
4165
|
-
/* @__PURE__ */ jsxs("div", { className: "lj-se-main", children: [
|
|
4166
|
-
/* @__PURE__ */ jsx(
|
|
4167
|
-
CanvasTopBar,
|
|
4168
|
-
{
|
|
4169
|
-
title: value.title,
|
|
4170
|
-
onTitleChange: disabled ? void 0 : onTitleChange,
|
|
4171
|
-
titleDisabled: disabled,
|
|
4172
|
-
contextLabel,
|
|
4173
|
-
format,
|
|
4174
|
-
onFormatChange: setFormat,
|
|
4175
|
-
canUndo,
|
|
4176
|
-
canRedo,
|
|
4177
|
-
onUndo,
|
|
4178
|
-
onRedo,
|
|
4179
|
-
actions: topBarActions
|
|
4180
|
-
}
|
|
4181
|
-
),
|
|
4182
|
-
/* @__PURE__ */ jsxs("div", { className: "lj-se-canvas", ref: canvasRef, children: [
|
|
4183
|
-
center,
|
|
4184
|
-
onAddChatReferences ? /* @__PURE__ */ jsx(QuoteLayer, { containerRef: canvasRef, onAddChatReferences }) : null,
|
|
4185
|
-
/* @__PURE__ */ jsx(
|
|
4186
|
-
SaveCapsule,
|
|
4334
|
+
return /* @__PURE__ */ jsx(EditingSignalContext.Provider, { value: editingSignal, children: /* @__PURE__ */ jsxs(
|
|
4335
|
+
"div",
|
|
4336
|
+
{
|
|
4337
|
+
className: rootClass,
|
|
4338
|
+
ref: rootRef,
|
|
4339
|
+
children: [
|
|
4340
|
+
toc === "sidebar" ? /* @__PURE__ */ jsx(
|
|
4341
|
+
StudioToc,
|
|
4187
4342
|
{
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
onDiffPrev: changedSelections.length > 0 ? () => navDiff(-1) : void 0,
|
|
4193
|
-
onDiffNext: changedSelections.length > 0 ? () => navDiff(1) : void 0,
|
|
4194
|
-
onAcceptAll: diff?.onAcceptAll,
|
|
4195
|
-
onRejectAll: diff?.onRejectAll,
|
|
4196
|
-
returnLabel: returnTarget?.label,
|
|
4197
|
-
onReturn: returnTarget ? () => setSelection(returnTarget.selection) : void 0
|
|
4343
|
+
data: value,
|
|
4344
|
+
selection,
|
|
4345
|
+
onSelect: setSelection,
|
|
4346
|
+
onAddEpisode: disabled ? void 0 : () => onEdit((p) => addEpisode(p))
|
|
4198
4347
|
}
|
|
4199
|
-
)
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4348
|
+
) : null,
|
|
4349
|
+
/* @__PURE__ */ jsxs("div", { className: "lj-se-main", children: [
|
|
4350
|
+
/* @__PURE__ */ jsx(
|
|
4351
|
+
CanvasTopBar,
|
|
4352
|
+
{
|
|
4353
|
+
title: value.title,
|
|
4354
|
+
onTitleChange: disabled ? void 0 : onTitleChange,
|
|
4355
|
+
titleDisabled: disabled,
|
|
4356
|
+
contextLabel,
|
|
4357
|
+
format,
|
|
4358
|
+
onFormatChange: setFormat,
|
|
4359
|
+
canUndo,
|
|
4360
|
+
canRedo,
|
|
4361
|
+
onUndo,
|
|
4362
|
+
onRedo,
|
|
4363
|
+
actions: topBarActions
|
|
4364
|
+
}
|
|
4365
|
+
),
|
|
4366
|
+
/* @__PURE__ */ jsxs("div", { className: "lj-se-canvas", ref: canvasRef, children: [
|
|
4367
|
+
/* @__PURE__ */ jsx(
|
|
4368
|
+
ScriptSearchLayer,
|
|
4369
|
+
{
|
|
4370
|
+
value,
|
|
4371
|
+
disabled,
|
|
4372
|
+
rootRef,
|
|
4373
|
+
selection,
|
|
4374
|
+
onSelectionChange: setSelection,
|
|
4375
|
+
children: center
|
|
4376
|
+
}
|
|
4377
|
+
),
|
|
4378
|
+
onAddChatReferences ? /* @__PURE__ */ jsx(QuoteLayer, { containerRef: canvasRef, onAddChatReferences }) : null,
|
|
4379
|
+
/* @__PURE__ */ jsx(
|
|
4380
|
+
SaveCapsule,
|
|
4381
|
+
{
|
|
4382
|
+
saveStatus,
|
|
4383
|
+
diffCount,
|
|
4384
|
+
baselineStatus: diff?.baselineStatus,
|
|
4385
|
+
canApplyDiff: !!(diff?.onAcceptAll || diff?.onRejectAll),
|
|
4386
|
+
onDiffPrev: changedSelections.length > 0 ? () => navDiff(-1) : void 0,
|
|
4387
|
+
onDiffNext: changedSelections.length > 0 ? () => navDiff(1) : void 0,
|
|
4388
|
+
onAcceptAll: diff?.onAcceptAll,
|
|
4389
|
+
onRejectAll: diff?.onRejectAll,
|
|
4390
|
+
returnLabel: returnTarget?.label,
|
|
4391
|
+
onReturn: returnTarget ? () => setSelection(returnTarget.selection) : void 0
|
|
4392
|
+
}
|
|
4393
|
+
)
|
|
4394
|
+
] })
|
|
4395
|
+
] })
|
|
4396
|
+
]
|
|
4397
|
+
}
|
|
4398
|
+
) });
|
|
4203
4399
|
}
|
|
4204
4400
|
function buildContextLabel(value, selection) {
|
|
4205
4401
|
if (selection.kind === "worldview") {
|
|
@@ -4299,7 +4495,6 @@ function buildScriptNav(value) {
|
|
|
4299
4495
|
return { ...nav({ kind: "prop", propId: id }), id, name: getPropDisplayName(p) };
|
|
4300
4496
|
}).filter((e) => e.id);
|
|
4301
4497
|
return {
|
|
4302
|
-
worldview: { ...nav({ kind: "worldview" }), label: "\u4E16\u754C\u89C2 & \u98CE\u683C" },
|
|
4303
4498
|
episodes,
|
|
4304
4499
|
actors,
|
|
4305
4500
|
locations,
|