dsh-novel 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +1560 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +1436 -0
- package/lib/index.js.map +1 -0
- package/lib/types/client/NovelWorkspaceView.d.ts +3 -0
- package/lib/types/client/entity-client.d.ts +17 -0
- package/lib/types/client/index.d.ts +3 -0
- package/lib/types/client/noveel-shapes.d.ts +1 -0
- package/lib/types/client/novel-api.d.ts +1 -0
- package/lib/types/client/section-icons.d.ts +5 -0
- package/lib/types/client/sections/ChapterSection.d.ts +2 -0
- package/lib/types/client/sections/LevelSection.d.ts +2 -0
- package/lib/types/client/sections/ListDetailSection.d.ts +13 -0
- package/lib/types/client/sections/OutlineSection.d.ts +2 -0
- package/lib/types/client/sections/OverviewSection.d.ts +2 -0
- package/lib/types/client/sections/SkillSection.d.ts +3 -0
- package/lib/types/client/sections/TimelineSection.d.ts +2 -0
- package/lib/types/client/sections/WorldSection.d.ts +2 -0
- package/lib/types/client/sections/owned-sections.d.ts +4 -0
- package/lib/types/client/sections/types.d.ts +14 -0
- package/lib/types/client/styles.d.ts +1 -0
- package/lib/types/client/ui/EntitySidebar.d.ts +10 -0
- package/lib/types/client/ui/RelationPanel.d.ts +6 -0
- package/lib/types/client/ui/TemplateRead.d.ts +13 -0
- package/lib/types/client/ui/TimelinePanel.d.ts +8 -0
- package/lib/types/client/ui/controls.d.ts +22 -0
- package/lib/types/client/ui/live-reload.d.ts +2 -0
- package/lib/types/index.d.ts +5 -0
- package/lib/types/novel/contract.d.ts +2 -0
- package/lib/types/novel/defaults.d.ts +66 -0
- package/lib/types/novel/domains.d.ts +12 -0
- package/lib/types/novel/host-skills.d.ts +58 -0
- package/lib/types/novel/http.d.ts +5 -0
- package/lib/types/novel/normalize-entity.d.ts +18 -0
- package/lib/types/novel/noveel-shapes.d.ts +98 -0
- package/lib/types/novel/outline-labels.d.ts +10 -0
- package/lib/types/novel/schema.d.ts +3 -0
- package/lib/types/novel/seed.d.ts +27 -0
- package/lib/types/novel/store.d.ts +96 -0
- package/lib/types/novel/template-fields.d.ts +17 -0
- package/lib/types/novel/timeline-labels.d.ts +2 -0
- package/lib/types/novel/tools.d.ts +3 -0
- package/package.json +77 -0
- package/seed/skills/chapter//345/216/273AI/345/221/263/344/270/216/350/264/250/346/243/200.md +34 -0
- package/seed/skills/chapter//345/257/271/350/257/235/344/270/216/346/203/205/347/273/252.md +52 -0
- package/seed/skills/chapter//347/253/240/350/212/202/346/225/260/346/215/256/347/273/264/346/212/244.md +61 -0
- package/seed/skills/chapter//347/253/240/350/212/202/350/212/202/345/245/217/344/270/216/345/274/200/345/244/264.md +61 -0
- package/seed/skills/chapter//347/253/240/351/246/226/351/222/251/345/255/220.md +40 -0
- package/seed/skills/creature//345/274/202/345/205/275/344/270/216/347/201/265/346/244/215.md +11 -0
- package/seed/skills/creature//347/224/237/347/211/251/346/241/243/346/241/210/350/246/201/347/264/240.md +13 -0
- package/seed/skills/genres//344/273/231/344/276/240/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +32 -0
- package/seed/skills/genres//344/273/231/344/276/240/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
- package/seed/skills/genres//345/216/206/345/217/262/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +32 -0
- package/seed/skills/genres//345/216/206/345/217/262/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
- package/seed/skills/genres//346/202/254/347/226/221/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +33 -0
- package/seed/skills/genres//346/202/254/347/226/221/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
- package/seed/skills/genres//346/234/253/344/270/226/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +33 -0
- package/seed/skills/genres//346/234/253/344/270/226/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
- package/seed/skills/genres//347/216/204/345/271/273/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +32 -0
- package/seed/skills/genres//347/216/204/345/271/273/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +22 -0
- package/seed/skills/genres//347/247/221/345/271/273/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +33 -0
- package/seed/skills/genres//347/247/221/345/271/273/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
- package/seed/skills/genres//350/250/200/346/203/205/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +31 -0
- package/seed/skills/genres//350/250/200/346/203/205/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
- package/seed/skills/genres//351/203/275/345/270/202/overview//351/242/230/346/235/220/350/247/204/345/210/231.md +32 -0
- package/seed/skills/genres//351/203/275/345/270/202/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +21 -0
- package/seed/skills/item//346/265/201/350/275/254/344/270/216/345/275/222/345/261/236.md +22 -0
- package/seed/skills/item//347/211/251/345/223/201/346/241/243/346/241/210/350/246/201/347/264/240.md +13 -0
- package/seed/skills/level//346/210/220/351/225/277/344/270/216/347/210/275/347/202/271.md +44 -0
- package/seed/skills/level//350/203/275/345/212/233/344/270/216/344/273/243/344/273/267.md +42 -0
- package/seed/skills/outline//345/211/247/346/203/205/351/252/250/346/236/266/346/265/201/346/260/264/347/272/277.md +51 -0
- package/seed/skills/outline//347/273/223/346/236/204/344/270/216/345/215/267/347/253/240/346/250/241/346/235/277.md +68 -0
- package/seed/skills/outline//351/273/204/351/207/221/344/270/211/347/253/240/344/270/216/351/252/214/346/224/266.md +42 -0
- package/seed/skills/overview//345/215/226/347/202/271/344/270/216/345/256/232/344/275/215.md +52 -0
- package/seed/skills/overview//345/274/200/344/271/246/347/247/215/345/255/220.md +60 -0
- package/seed/skills/overview//346/240/207/351/242/230/345/210/233/344/275/234.md +43 -0
- package/seed/skills/review/chapter//347/253/240/350/212/202/345/256/241/346/237/245.md +72 -0
- package/seed/skills/review/outline//345/244/247/347/272/262/345/256/241/346/237/245.md +74 -0
- package/seed/skills/role//344/272/272/347/211/251/345/241/221/351/200/240.md +52 -0
- package/seed/skills/role//345/205/263/347/263/273/344/270/216/351/205/215/350/247/222.md +53 -0
- package/seed/skills/role//350/247/222/350/211/262/346/241/243/346/241/210.md +52 -0
- package/seed/skills/timeline//344/272/213/344/273/266/350/275/264/350/247/204/345/210/222.md +33 -0
- package/seed/skills/timeline//347/212/266/346/200/201/345/220/214/346/255/245.md +41 -0
- package/seed/skills/world//344/270/226/347/225/214/350/247/204/345/210/231.md +30 -0
- package/seed/skills/world//344/274/217/347/254/224/350/264/246/346/234/254.md +44 -0
- package/seed/skills/world//345/212/277/345/212/233/344/270/216/345/234/260/345/233/276.md +52 -0
- package/seed/skills/writing-style/da-feng-da-geng-ren/STYLE.md +132 -0
- package/seed/skills/writing-style/da-feng-da-geng-ren/meta.json +6 -0
- package/seed/skills/writing-style/hei-dao-da-feng/STYLE.md +144 -0
- package/seed/skills/writing-style/hei-dao-da-feng/meta.json +6 -0
- package/seed/skills/writing-style/hei-dao-xie-huang/STYLE.md +139 -0
- package/seed/skills/writing-style/hei-dao-xie-huang/meta.json +6 -0
- package/seed/templates//344/273/231/344/276/240.json +1322 -0
- package/seed/templates//345/216/206/345/217/262.json +1318 -0
- package/seed/templates//346/202/254/347/226/221.json +1318 -0
- package/seed/templates//346/234/253/344/270/226.json +1127 -0
- package/seed/templates//347/216/204/345/271/273.json +1322 -0
- package/seed/templates//347/247/221/345/271/273.json +1322 -0
- package/seed/templates//350/250/200/346/203/205.json +1318 -0
- package/seed/templates//351/203/275/345/270/202.json +1318 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1560 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-novel",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
//#region src/novel/domains.ts
|
|
11
|
+
const DOMAIN_LABELS = {
|
|
12
|
+
info: "基本信息",
|
|
13
|
+
overview: "概述",
|
|
14
|
+
world: "世界",
|
|
15
|
+
role: "人物",
|
|
16
|
+
creature: "生物",
|
|
17
|
+
item: "物品",
|
|
18
|
+
level: "等级",
|
|
19
|
+
timeline: "时间线",
|
|
20
|
+
outline: "大纲",
|
|
21
|
+
chapter: "章节",
|
|
22
|
+
skill: "技能",
|
|
23
|
+
roleRelation: "人物关系",
|
|
24
|
+
roleTimeline: "人物时间线",
|
|
25
|
+
creatureTimeline: "生物时间线",
|
|
26
|
+
itemTimeline: "物品时间线",
|
|
27
|
+
worldTimeline: "世界时间线"
|
|
28
|
+
};
|
|
29
|
+
/** Conversation「内容」Tab 内的竖向分区;基本信息并入概述。模板为内置,不展示。 */
|
|
30
|
+
const CONTENT_SECTIONS = [
|
|
31
|
+
"overview",
|
|
32
|
+
"world",
|
|
33
|
+
"role",
|
|
34
|
+
"creature",
|
|
35
|
+
"item",
|
|
36
|
+
"level",
|
|
37
|
+
"timeline",
|
|
38
|
+
"outline",
|
|
39
|
+
"chapter",
|
|
40
|
+
"skill"
|
|
41
|
+
];
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/novel/contract.ts
|
|
44
|
+
/** Same-origin HTTP prefix for the novel Host API. */
|
|
45
|
+
const NOVEL_API_PREFIX = "/dsh-novel/api";
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/client/novel-api.ts
|
|
48
|
+
async function novelApi(path, init) {
|
|
49
|
+
const response = await fetch(`${NOVEL_API_PREFIX}${path}`, {
|
|
50
|
+
cache: "no-store",
|
|
51
|
+
headers: {
|
|
52
|
+
"content-type": "application/json",
|
|
53
|
+
...init?.headers ?? {}
|
|
54
|
+
},
|
|
55
|
+
...init
|
|
56
|
+
});
|
|
57
|
+
const text = await response.text();
|
|
58
|
+
const body = text.length === 0 ? void 0 : JSON.parse(text);
|
|
59
|
+
if (!response.ok) throw new Error(body?.error ?? `HTTP ${String(response.status)}`);
|
|
60
|
+
return body;
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/novel/outline-labels.ts
|
|
64
|
+
function asOrdinal(value) {
|
|
65
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
66
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
67
|
+
const n = Number(value.trim());
|
|
68
|
+
if (Number.isFinite(n)) return n;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function canonicalizeOutlineKind(raw) {
|
|
72
|
+
if (typeof raw !== "string") return void 0;
|
|
73
|
+
const key = raw.trim();
|
|
74
|
+
if (key === "chapter_segment" || key === "chapter" || key === "章") return "chapter_segment";
|
|
75
|
+
if (key === "volume" || key === "卷") return "volume";
|
|
76
|
+
}
|
|
77
|
+
function outlineKindOf(data) {
|
|
78
|
+
const explicit = canonicalizeOutlineKind(data.kind ?? data.nodeKind);
|
|
79
|
+
if (explicit !== void 0) return explicit;
|
|
80
|
+
return typeof data.parentId === "string" && data.parentId.length > 0 || typeof data.parentName === "string" && data.parentName.length > 0 ? "chapter_segment" : "volume";
|
|
81
|
+
}
|
|
82
|
+
function volumeChapterLabel(ordinal, unit, name) {
|
|
83
|
+
const title = name.trim().length > 0 ? name.trim() : "未命名";
|
|
84
|
+
const n = asOrdinal(ordinal);
|
|
85
|
+
if (n === void 0) return title;
|
|
86
|
+
return `第${n}${unit} ${title}`;
|
|
87
|
+
}
|
|
88
|
+
function shortName(data, fallback) {
|
|
89
|
+
return typeof data.name === "string" && data.name.trim().length > 0 ? data.name.trim() : fallback;
|
|
90
|
+
}
|
|
91
|
+
function outlineNodeLabel(data, fallback) {
|
|
92
|
+
const unit = outlineKindOf(data) === "chapter_segment" ? "章" : "卷";
|
|
93
|
+
return volumeChapterLabel(data.ordinal, unit, shortName(data, fallback));
|
|
94
|
+
}
|
|
95
|
+
function chapterNodeLabel(data, fallback) {
|
|
96
|
+
return volumeChapterLabel(data.ordinal ?? data.chapterNo, "章", shortName(data, fallback));
|
|
97
|
+
}
|
|
98
|
+
function compareOrdinal(a, b) {
|
|
99
|
+
const oa = asOrdinal(a);
|
|
100
|
+
const ob = asOrdinal(b);
|
|
101
|
+
if (oa === void 0 && ob === void 0) return 0;
|
|
102
|
+
if (oa === void 0) return 1;
|
|
103
|
+
if (ob === void 0) return -1;
|
|
104
|
+
return oa - ob;
|
|
105
|
+
}
|
|
106
|
+
function sortByOrdinalName(items, ordinalOf) {
|
|
107
|
+
return [...items].sort((a, b) => {
|
|
108
|
+
const cmp = compareOrdinal(ordinalOf(a), ordinalOf(b));
|
|
109
|
+
if (cmp !== 0) return cmp;
|
|
110
|
+
return (a.name ?? "").localeCompare(b.name ?? "", "zh");
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/novel/normalize-entity.ts
|
|
115
|
+
function asRecord$2(value) {
|
|
116
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
117
|
+
}
|
|
118
|
+
/** Merge nested `profile` onto the root; existing top-level keys win. */
|
|
119
|
+
function hoistProfile(data) {
|
|
120
|
+
return {
|
|
121
|
+
...asRecord$2(data.profile),
|
|
122
|
+
...data
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/** Merge outline `extra` onto the root; existing top-level keys win. */
|
|
126
|
+
function hoistOutlineExtra(data) {
|
|
127
|
+
return {
|
|
128
|
+
...asRecord$2(data.extra),
|
|
129
|
+
...data
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** Shape used by the content tab: hoist profile/extra so template keys resolve. */
|
|
133
|
+
function displayEntityData(data) {
|
|
134
|
+
return hoistOutlineExtra(hoistProfile(data));
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/client/entity-client.ts
|
|
138
|
+
function asRecord$1(value) {
|
|
139
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
140
|
+
}
|
|
141
|
+
async function listEntities(path, domain) {
|
|
142
|
+
return novelApi(`/entities?path=${encodeURIComponent(path)}&domain=${encodeURIComponent(domain)}`);
|
|
143
|
+
}
|
|
144
|
+
function dataOf(item) {
|
|
145
|
+
return asRecord$1(item.data);
|
|
146
|
+
}
|
|
147
|
+
function viewData(item) {
|
|
148
|
+
if (item === void 0) return {};
|
|
149
|
+
return {
|
|
150
|
+
name: item.name ?? "",
|
|
151
|
+
...displayEntityData(dataOf(item))
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function parentIdOf(item, items) {
|
|
155
|
+
const data = asRecord$1(item.data);
|
|
156
|
+
if (typeof data.parentId === "string" && data.parentId.length > 0) return data.parentId;
|
|
157
|
+
if (typeof data.parentName === "string" && data.parentName.length > 0) return items.find((row) => row.name === data.parentName)?.id;
|
|
158
|
+
}
|
|
159
|
+
function filterOwned(items, ownerKey, ownerId) {
|
|
160
|
+
return items.filter((item) => String(dataOf(item)[ownerKey] ?? "") === ownerId);
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/client/section-icons.tsx
|
|
164
|
+
function CreatureIcon() {
|
|
165
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
166
|
+
width: 16,
|
|
167
|
+
height: 16,
|
|
168
|
+
viewBox: "0 0 16 16",
|
|
169
|
+
fill: "none",
|
|
170
|
+
"aria-hidden": "true",
|
|
171
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
172
|
+
d: "M8 2.2c1.4 0 2.5 1.2 2.5 2.6 0 .5-.1 1-.4 1.4 1.3.5 2.2 1.8 2.2 3.3 0 2-1.9 3.6-4.3 3.6S3.7 11.5 3.7 9.5c0-1.5.9-2.8 2.2-3.3-.3-.4-.4-.9-.4-1.4C5.5 3.4 6.6 2.2 8 2.2Z",
|
|
173
|
+
stroke: "currentColor",
|
|
174
|
+
strokeWidth: "1.2"
|
|
175
|
+
})
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function BookIcon() {
|
|
179
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
180
|
+
width: 16,
|
|
181
|
+
height: 16,
|
|
182
|
+
viewBox: "0 0 16 16",
|
|
183
|
+
fill: "none",
|
|
184
|
+
"aria-hidden": "true",
|
|
185
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
186
|
+
d: "M8 3.2c-1.1-.8-2.6-1.1-4.4-.8v9.2c1.7-.3 3.2 0 4.4.8 1.1-.8 2.6-1.1 4.4-.8V2.4c-1.8-.3-3.3 0-4.4.8Z",
|
|
187
|
+
stroke: "currentColor",
|
|
188
|
+
strokeWidth: "1.2",
|
|
189
|
+
strokeLinejoin: "round"
|
|
190
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
191
|
+
d: "M8 3.2v9.2",
|
|
192
|
+
stroke: "currentColor",
|
|
193
|
+
strokeWidth: "1.2"
|
|
194
|
+
})]
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function SectionIcon({ section }) {
|
|
198
|
+
switch (section) {
|
|
199
|
+
case "overview": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BookIcon, {});
|
|
200
|
+
case "world": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGlobeOutline14, {});
|
|
201
|
+
case "role": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconUserOutline16, {});
|
|
202
|
+
case "creature": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreatureIcon, {});
|
|
203
|
+
case "item": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconArchiveOutline20, { size: 16 });
|
|
204
|
+
case "level": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGoalOutline16, {});
|
|
205
|
+
case "timeline": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconQueueOutline14, {});
|
|
206
|
+
case "outline": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChecklistOutline14, {});
|
|
207
|
+
case "chapter": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, {});
|
|
208
|
+
case "skill": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSkillOutline16, {});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//#endregion
|
|
212
|
+
//#region src/novel/template-fields.ts
|
|
213
|
+
function asRecord(value) {
|
|
214
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
215
|
+
}
|
|
216
|
+
function asFields(raw) {
|
|
217
|
+
if (!Array.isArray(raw)) return [];
|
|
218
|
+
return raw.flatMap((row) => {
|
|
219
|
+
if (row === null || typeof row !== "object") return [];
|
|
220
|
+
const rec = row;
|
|
221
|
+
if (typeof rec.key !== "string" || typeof rec.label !== "string") return [];
|
|
222
|
+
const options = flattenOptions(rec.options);
|
|
223
|
+
const field = {
|
|
224
|
+
key: rec.key,
|
|
225
|
+
label: rec.label
|
|
226
|
+
};
|
|
227
|
+
if (typeof rec.component === "string") field.component = rec.component;
|
|
228
|
+
if (options.length > 0) field.options = options;
|
|
229
|
+
return [field];
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function flattenOptions(raw) {
|
|
233
|
+
if (!Array.isArray(raw)) return [];
|
|
234
|
+
const out = [];
|
|
235
|
+
for (const entry of raw) {
|
|
236
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
237
|
+
const rec = entry;
|
|
238
|
+
if (typeof rec.value === "string" && typeof rec.label === "string") {
|
|
239
|
+
out.push({
|
|
240
|
+
label: rec.label,
|
|
241
|
+
value: rec.value
|
|
242
|
+
});
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (Array.isArray(rec.options)) for (const nested of rec.options) {
|
|
246
|
+
if (nested === null || typeof nested !== "object") continue;
|
|
247
|
+
const item = nested;
|
|
248
|
+
if (typeof item.value === "string" && typeof item.label === "string") out.push({
|
|
249
|
+
label: item.label,
|
|
250
|
+
value: item.value
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
/** Pick human-editable fields from a Noveel form_templates.config blob. */
|
|
257
|
+
function fieldsOf(template, section, extra) {
|
|
258
|
+
const block = asRecord(asRecord(template)[section]);
|
|
259
|
+
switch (section) {
|
|
260
|
+
case "overview": return asFields(block.fields);
|
|
261
|
+
case "role":
|
|
262
|
+
case "creature":
|
|
263
|
+
case "item": return extra === "timeline" ? asFields(asRecord(block.timeline).extraFields) : asFields(block.detailFields);
|
|
264
|
+
case "world": return extra === "timeline" ? asFields(asRecord(block.timeline).extraFields) : asFields(block.defaultDetailFields);
|
|
265
|
+
case "level": return asFields(block.levelFields);
|
|
266
|
+
case "timeline": return asFields(block.extraFields);
|
|
267
|
+
case "outline": return extra === "chapterSegment" ? asFields(block.chapterSegmentFields) : asFields(block.volumeFields);
|
|
268
|
+
default: return asFields(block.fields ?? block.detailFields);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function getByPath(obj, path) {
|
|
272
|
+
const parts = path.split(".").filter(Boolean);
|
|
273
|
+
let cur = obj;
|
|
274
|
+
for (const part of parts) {
|
|
275
|
+
if (cur == null || typeof cur !== "object" || Array.isArray(cur)) return void 0;
|
|
276
|
+
cur = cur[part];
|
|
277
|
+
}
|
|
278
|
+
return cur;
|
|
279
|
+
}
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region src/client/ui/TemplateRead.tsx
|
|
282
|
+
function ReadField({ label, children }) {
|
|
283
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
284
|
+
className: "dshNovelViewField",
|
|
285
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
286
|
+
className: "dshNovelReadValue",
|
|
287
|
+
children: children === "" || children === void 0 || children === null ? "—" : children
|
|
288
|
+
})]
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
function displayText(value) {
|
|
292
|
+
if (value === void 0 || value === null || value === "") return "";
|
|
293
|
+
if (Array.isArray(value)) return value.map((item) => String(item)).filter(Boolean).join("、");
|
|
294
|
+
if (typeof value === "boolean") return value ? "是" : "否";
|
|
295
|
+
return String(value);
|
|
296
|
+
}
|
|
297
|
+
function TemplateRead({ template, section, extra, value, omitKeys }) {
|
|
298
|
+
const skip = new Set(omitKeys ?? []);
|
|
299
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: fieldsOf(template, section, extra).filter((field) => !skip.has(field.key)).map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadField, {
|
|
300
|
+
label: field.label,
|
|
301
|
+
children: displayText(getByPath(value, field.key))
|
|
302
|
+
}, field.key)) });
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/client/ui/live-reload.ts
|
|
306
|
+
const HINT = "暂无。可在对话中让 AI 补充。";
|
|
307
|
+
function emptyHint(hasFilter) {
|
|
308
|
+
return hasFilter ? "无匹配项" : HINT;
|
|
309
|
+
}
|
|
310
|
+
function useLiveReload(reload, ms = 5e3) {
|
|
311
|
+
(0, react.useEffect)(() => {
|
|
312
|
+
const tick = () => {
|
|
313
|
+
reload();
|
|
314
|
+
};
|
|
315
|
+
const id = window.setInterval(tick, ms);
|
|
316
|
+
const onVis = () => {
|
|
317
|
+
if (document.visibilityState === "visible") tick();
|
|
318
|
+
};
|
|
319
|
+
document.addEventListener("visibilitychange", onVis);
|
|
320
|
+
return () => {
|
|
321
|
+
window.clearInterval(id);
|
|
322
|
+
document.removeEventListener("visibilitychange", onVis);
|
|
323
|
+
};
|
|
324
|
+
}, [reload, ms]);
|
|
325
|
+
}
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/client/sections/OverviewSection.tsx
|
|
328
|
+
function OverviewSection({ path, template, styles }) {
|
|
329
|
+
const [row, setRow] = (0, react.useState)({});
|
|
330
|
+
const reload = (0, react.useCallback)(async () => {
|
|
331
|
+
setRow(await novelApi(`/novel?path=${encodeURIComponent(path)}`));
|
|
332
|
+
}, [path]);
|
|
333
|
+
(0, react.useEffect)(() => {
|
|
334
|
+
reload();
|
|
335
|
+
}, [reload]);
|
|
336
|
+
useLiveReload(reload);
|
|
337
|
+
const blueprint = asRecord$1(row.blueprint_json);
|
|
338
|
+
const styleName = styles.find((style) => style.id === row.writing_style_id)?.name ?? String(row.writing_style_id ?? "");
|
|
339
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
340
|
+
className: "dshNovelViewForm",
|
|
341
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
342
|
+
className: "dshNovelViewFormBody",
|
|
343
|
+
children: [
|
|
344
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
345
|
+
className: "dshNovelTitle",
|
|
346
|
+
children: String(row.title ?? "未命名")
|
|
347
|
+
}),
|
|
348
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
349
|
+
className: "dshNovelMeta",
|
|
350
|
+
children: [row.category, styleName].filter((item) => item !== void 0 && item !== "").join(" · ") || "—"
|
|
351
|
+
}),
|
|
352
|
+
row.description !== void 0 && row.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
353
|
+
className: "dshNovelProse",
|
|
354
|
+
children: String(row.description)
|
|
355
|
+
}),
|
|
356
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TemplateRead, {
|
|
357
|
+
template,
|
|
358
|
+
section: "overview",
|
|
359
|
+
value: blueprint
|
|
360
|
+
})
|
|
361
|
+
]
|
|
362
|
+
})
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
//#endregion
|
|
366
|
+
//#region src/novel/noveel-shapes.ts
|
|
367
|
+
const WORLD_KINDS = [
|
|
368
|
+
{
|
|
369
|
+
value: "universe",
|
|
370
|
+
label: "宇宙"
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
value: "multiverse",
|
|
374
|
+
label: "多元宇宙"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
value: "galaxy",
|
|
378
|
+
label: "星系"
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
value: "realm",
|
|
382
|
+
label: "界域"
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
value: "domain",
|
|
386
|
+
label: "域"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
value: "plane",
|
|
390
|
+
label: "位面"
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
value: "planet",
|
|
394
|
+
label: "星球"
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
value: "continent",
|
|
398
|
+
label: "大陆"
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
value: "mainland",
|
|
402
|
+
label: "大陆"
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
value: "region",
|
|
406
|
+
label: "地区"
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
value: "mountain",
|
|
410
|
+
label: "山脉"
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
value: "secret_realm",
|
|
414
|
+
label: "秘境/禁地"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
value: "country",
|
|
418
|
+
label: "国家"
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
value: "empire",
|
|
422
|
+
label: "帝国"
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
value: "kingdom",
|
|
426
|
+
label: "王国"
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
value: "city",
|
|
430
|
+
label: "城市"
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
value: "town",
|
|
434
|
+
label: "城镇"
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
value: "settlement",
|
|
438
|
+
label: "聚落"
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
value: "faction",
|
|
442
|
+
label: "势力"
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
value: "sect",
|
|
446
|
+
label: "宗门/门派"
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
value: "clan",
|
|
450
|
+
label: "家族/氏族"
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
value: "guild",
|
|
454
|
+
label: "公会"
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
value: "army",
|
|
458
|
+
label: "军队"
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
value: "corporation",
|
|
462
|
+
label: "公司/集团"
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
value: "alliance",
|
|
466
|
+
label: "联盟"
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
value: "cult",
|
|
470
|
+
label: "教派"
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
value: "academy",
|
|
474
|
+
label: "学院"
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
value: "custom",
|
|
478
|
+
label: "其他"
|
|
479
|
+
}
|
|
480
|
+
];
|
|
481
|
+
function emptyLevelSystem() {
|
|
482
|
+
return {
|
|
483
|
+
name: "自定义体系",
|
|
484
|
+
category: "自定义",
|
|
485
|
+
description: "",
|
|
486
|
+
levels: [{
|
|
487
|
+
name: "第一阶",
|
|
488
|
+
subLevels: [
|
|
489
|
+
{ name: "初期" },
|
|
490
|
+
{ name: "中期" },
|
|
491
|
+
{ name: "后期" }
|
|
492
|
+
],
|
|
493
|
+
promotionCondition: "达到上一阶大圆满,并满足作品内设定的突破条件。",
|
|
494
|
+
abilities: "待补充该等级典型能力与表现。"
|
|
495
|
+
}]
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region src/client/ui/controls.tsx
|
|
500
|
+
function SearchField({ className: _className, ...rest }) {
|
|
501
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
502
|
+
className: "dshNovelViewSearch",
|
|
503
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
|
|
504
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, {}),
|
|
505
|
+
placeholder: "搜索…",
|
|
506
|
+
...rest
|
|
507
|
+
})
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function DetailTabs({ tabs, value, onChange }) {
|
|
511
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
512
|
+
className: "dshNovelTabs",
|
|
513
|
+
role: "tablist",
|
|
514
|
+
children: tabs.map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
515
|
+
type: "button",
|
|
516
|
+
role: "tab",
|
|
517
|
+
"aria-selected": value === tab.id,
|
|
518
|
+
"data-active": value === tab.id || void 0,
|
|
519
|
+
onClick: () => {
|
|
520
|
+
onChange(tab.id);
|
|
521
|
+
},
|
|
522
|
+
children: tab.label
|
|
523
|
+
}, tab.id))
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
function SectionToolbar({ tabs, actions }) {
|
|
527
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
528
|
+
className: "dshNovelViewFormHead",
|
|
529
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
530
|
+
className: "dshNovelViewFormHeadTabs",
|
|
531
|
+
children: tabs
|
|
532
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
533
|
+
className: "dshNovelViewFormHeadActions",
|
|
534
|
+
children: actions
|
|
535
|
+
})]
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
//#endregion
|
|
539
|
+
//#region src/client/ui/EntitySidebar.tsx
|
|
540
|
+
function EntitySidebar({ items, selectedId, onSelect, tree, label, className, files }) {
|
|
541
|
+
const [filter, setFilter] = (0, react.useState)("");
|
|
542
|
+
const [expanded, setExpanded] = (0, react.useState)(/* @__PURE__ */ new Set());
|
|
543
|
+
const nameOf = label ?? ((item) => item.name ?? item.id);
|
|
544
|
+
const q = filter.trim();
|
|
545
|
+
const visibleItems = (0, react.useMemo)(() => {
|
|
546
|
+
if (q.length === 0) return items;
|
|
547
|
+
const match = /* @__PURE__ */ new Set();
|
|
548
|
+
for (const item of items) if (nameOf(item).includes(q)) match.add(item.id);
|
|
549
|
+
for (const item of items) {
|
|
550
|
+
if (!match.has(item.id)) continue;
|
|
551
|
+
let current = parentIdOf(item, items);
|
|
552
|
+
while (current !== void 0) {
|
|
553
|
+
match.add(current);
|
|
554
|
+
const ancestor = items.find((row) => row.id === current);
|
|
555
|
+
current = ancestor === void 0 ? void 0 : parentIdOf(ancestor, items);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return items.filter((item) => match.has(item.id));
|
|
559
|
+
}, [
|
|
560
|
+
items,
|
|
561
|
+
nameOf,
|
|
562
|
+
q
|
|
563
|
+
]);
|
|
564
|
+
(0, react.useEffect)(() => {
|
|
565
|
+
if (q.length === 0) return;
|
|
566
|
+
setExpanded((current) => {
|
|
567
|
+
const next = new Set(current);
|
|
568
|
+
for (const item of visibleItems) {
|
|
569
|
+
let ancestor = parentIdOf(item, items);
|
|
570
|
+
while (ancestor !== void 0) {
|
|
571
|
+
next.add(ancestor);
|
|
572
|
+
const row = items.find((entry) => entry.id === ancestor);
|
|
573
|
+
ancestor = row === void 0 ? void 0 : parentIdOf(row, items);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return next;
|
|
577
|
+
});
|
|
578
|
+
}, [
|
|
579
|
+
items,
|
|
580
|
+
q,
|
|
581
|
+
visibleItems
|
|
582
|
+
]);
|
|
583
|
+
const shown = (0, react.useMemo)(() => {
|
|
584
|
+
if (!tree) return visibleItems.map((item) => ({
|
|
585
|
+
...item,
|
|
586
|
+
depth: 0,
|
|
587
|
+
hasChildren: false
|
|
588
|
+
}));
|
|
589
|
+
return buildTree(visibleItems, items);
|
|
590
|
+
}, [
|
|
591
|
+
tree,
|
|
592
|
+
visibleItems,
|
|
593
|
+
items
|
|
594
|
+
]).filter((row) => {
|
|
595
|
+
if (!tree) return true;
|
|
596
|
+
const parent = parentIdOf(row, items);
|
|
597
|
+
if (parent === void 0) return true;
|
|
598
|
+
let current = parent;
|
|
599
|
+
while (current !== void 0) {
|
|
600
|
+
if (!expanded.has(current)) return false;
|
|
601
|
+
const ancestor = items.find((item) => item.id === current);
|
|
602
|
+
current = ancestor === void 0 ? void 0 : parentIdOf(ancestor, items);
|
|
603
|
+
}
|
|
604
|
+
return true;
|
|
605
|
+
});
|
|
606
|
+
const toggle = (id) => {
|
|
607
|
+
setExpanded((current) => {
|
|
608
|
+
const next = new Set(current);
|
|
609
|
+
if (next.has(id)) next.delete(id);
|
|
610
|
+
else next.add(id);
|
|
611
|
+
return next;
|
|
612
|
+
});
|
|
613
|
+
};
|
|
614
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
|
|
615
|
+
className: `dshNovelViewList${className === void 0 ? "" : ` ${className}`}`,
|
|
616
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
617
|
+
className: "dshNovelViewListToolbar",
|
|
618
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SearchField, {
|
|
619
|
+
value: filter,
|
|
620
|
+
onChange: (event) => {
|
|
621
|
+
setFilter(event.target.value);
|
|
622
|
+
}
|
|
623
|
+
})
|
|
624
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
625
|
+
className: "dshNovelViewListBody",
|
|
626
|
+
children: [shown.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
627
|
+
className: "dshNovelViewMuted",
|
|
628
|
+
children: q ? "无匹配项" : "暂无。可在对话中让 AI 补充。"
|
|
629
|
+
}), shown.map((row) => {
|
|
630
|
+
const folder = isFolder(row);
|
|
631
|
+
const open = expanded.has(row.id);
|
|
632
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
633
|
+
type: "button",
|
|
634
|
+
className: "dshNovelViewRow",
|
|
635
|
+
"data-kind": folder ? "folder" : "file",
|
|
636
|
+
"data-active": !folder && row.id === selectedId ? true : void 0,
|
|
637
|
+
style: { paddingLeft: 8 + row.depth * 12 },
|
|
638
|
+
onClick: () => {
|
|
639
|
+
if (folder) toggle(row.id);
|
|
640
|
+
else onSelect(row.id);
|
|
641
|
+
},
|
|
642
|
+
children: [
|
|
643
|
+
tree && row.hasChildren ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
644
|
+
className: "dshNovelViewChevron",
|
|
645
|
+
"data-open": open || void 0,
|
|
646
|
+
onClick: (event) => {
|
|
647
|
+
event.stopPropagation();
|
|
648
|
+
toggle(row.id);
|
|
649
|
+
},
|
|
650
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
|
|
651
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dshNovelViewChevron" }),
|
|
652
|
+
files ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
653
|
+
className: "dshNovelViewRowIcon",
|
|
654
|
+
children: folder ? open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpenOutline16, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconListPenOutline16, {})
|
|
655
|
+
}) : null,
|
|
656
|
+
nameOf(row)
|
|
657
|
+
]
|
|
658
|
+
}, row.id);
|
|
659
|
+
})]
|
|
660
|
+
})]
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
function isFolder(item) {
|
|
664
|
+
return asRecord$1(item.data).kind === "folder";
|
|
665
|
+
}
|
|
666
|
+
function buildTree(items, all) {
|
|
667
|
+
const byParent = /* @__PURE__ */ new Map();
|
|
668
|
+
for (const item of items) {
|
|
669
|
+
const parent = parentIdOf(item, all);
|
|
670
|
+
const list = byParent.get(parent) ?? [];
|
|
671
|
+
list.push(item);
|
|
672
|
+
byParent.set(parent, list);
|
|
673
|
+
}
|
|
674
|
+
const out = [];
|
|
675
|
+
const walk = (parent, depth) => {
|
|
676
|
+
for (const child of byParent.get(parent) ?? []) {
|
|
677
|
+
out.push({
|
|
678
|
+
...child,
|
|
679
|
+
depth,
|
|
680
|
+
hasChildren: (byParent.get(child.id) ?? []).length > 0
|
|
681
|
+
});
|
|
682
|
+
walk(child.id, depth + 1);
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
walk(void 0, 0);
|
|
686
|
+
const seen = new Set(out.map((row) => row.id));
|
|
687
|
+
for (const item of items) if (!seen.has(item.id)) out.push({
|
|
688
|
+
...item,
|
|
689
|
+
depth: 0,
|
|
690
|
+
hasChildren: false
|
|
691
|
+
});
|
|
692
|
+
return out;
|
|
693
|
+
}
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region src/client/ui/TimelinePanel.tsx
|
|
696
|
+
function TimelinePanel({ path, domain, ownerKey, ownerId, chapters }) {
|
|
697
|
+
const [rows, setRows] = (0, react.useState)([]);
|
|
698
|
+
const reload = (0, react.useCallback)(async () => {
|
|
699
|
+
setRows(filterOwned(await listEntities(path, domain), ownerKey, ownerId));
|
|
700
|
+
}, [
|
|
701
|
+
path,
|
|
702
|
+
domain,
|
|
703
|
+
ownerId,
|
|
704
|
+
ownerKey
|
|
705
|
+
]);
|
|
706
|
+
(0, react.useEffect)(() => {
|
|
707
|
+
reload();
|
|
708
|
+
}, [reload]);
|
|
709
|
+
useLiveReload(reload);
|
|
710
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
711
|
+
className: "dshNovelViewFormBody",
|
|
712
|
+
children: [rows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
713
|
+
className: "dshNovelViewMuted",
|
|
714
|
+
children: emptyHint()
|
|
715
|
+
}), rows.map((row) => {
|
|
716
|
+
const data = dataOf(row);
|
|
717
|
+
const chapter = chapters.find((item) => item.id === data.relatedChapterId);
|
|
718
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("article", {
|
|
719
|
+
className: "dshNovelEntry",
|
|
720
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
721
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
722
|
+
className: "dshNovelEntryTitle",
|
|
723
|
+
children: String(data.title ?? row.name ?? "未命名")
|
|
724
|
+
}),
|
|
725
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
726
|
+
className: "dshNovelViewMuted",
|
|
727
|
+
children: [data.timeLabel, chapter?.name].filter(Boolean).map(String).join(" · ")
|
|
728
|
+
}),
|
|
729
|
+
data.content !== void 0 && data.content !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
730
|
+
className: "dshNovelProse",
|
|
731
|
+
children: String(data.content)
|
|
732
|
+
})
|
|
733
|
+
] })
|
|
734
|
+
}, row.id);
|
|
735
|
+
})]
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region src/client/sections/WorldSection.tsx
|
|
740
|
+
function WorldSection({ path, template, chapters }) {
|
|
741
|
+
const [items, setItems] = (0, react.useState)([]);
|
|
742
|
+
const [selectedId, setSelectedId] = (0, react.useState)();
|
|
743
|
+
const [tab, setTab] = (0, react.useState)("detail");
|
|
744
|
+
const selected = items.find((item) => item.id === selectedId);
|
|
745
|
+
const data = viewData(selected);
|
|
746
|
+
const kindLabel = WORLD_KINDS.find((kind) => kind.value === data.kind)?.label ?? String(data.kind ?? "");
|
|
747
|
+
const parentId = selected === void 0 ? void 0 : parentIdOf(selected, items);
|
|
748
|
+
const parentName = parentId === void 0 ? typeof data.parentName === "string" && data.parentName.length > 0 ? data.parentName : void 0 : items.find((item) => item.id === parentId)?.name;
|
|
749
|
+
const reload = (0, react.useCallback)(async () => {
|
|
750
|
+
const rows = await listEntities(path, "world");
|
|
751
|
+
setItems(rows);
|
|
752
|
+
setSelectedId((current) => current !== void 0 && rows.some((row) => row.id === current) ? current : rows[0]?.id);
|
|
753
|
+
}, [path]);
|
|
754
|
+
(0, react.useEffect)(() => {
|
|
755
|
+
reload();
|
|
756
|
+
}, [reload]);
|
|
757
|
+
useLiveReload(reload);
|
|
758
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntitySidebar, {
|
|
759
|
+
items,
|
|
760
|
+
selectedId,
|
|
761
|
+
tree: true,
|
|
762
|
+
onSelect: (id) => {
|
|
763
|
+
setSelectedId(id);
|
|
764
|
+
setTab("detail");
|
|
765
|
+
}
|
|
766
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
767
|
+
className: "dshNovelViewForm",
|
|
768
|
+
children: [selectedId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionToolbar, { tabs: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailTabs, {
|
|
769
|
+
tabs: [{
|
|
770
|
+
id: "detail",
|
|
771
|
+
label: "详情"
|
|
772
|
+
}, {
|
|
773
|
+
id: "timeline",
|
|
774
|
+
label: "时间线"
|
|
775
|
+
}],
|
|
776
|
+
value: tab,
|
|
777
|
+
onChange: setTab
|
|
778
|
+
}) }), selected === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
779
|
+
className: "dshNovelViewFormBody",
|
|
780
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
781
|
+
className: "dshNovelViewMuted",
|
|
782
|
+
children: emptyHint()
|
|
783
|
+
})
|
|
784
|
+
}) : tab === "detail" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
785
|
+
className: "dshNovelViewFormBody",
|
|
786
|
+
children: [
|
|
787
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
788
|
+
className: "dshNovelTitle",
|
|
789
|
+
children: String(data.name ?? "未命名")
|
|
790
|
+
}),
|
|
791
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
792
|
+
className: "dshNovelMeta",
|
|
793
|
+
children: [kindLabel, parentName].filter(Boolean).join(" · ")
|
|
794
|
+
}),
|
|
795
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadField, {
|
|
796
|
+
label: "类型",
|
|
797
|
+
children: kindLabel
|
|
798
|
+
}),
|
|
799
|
+
parentName !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadField, {
|
|
800
|
+
label: "父节点",
|
|
801
|
+
children: parentName
|
|
802
|
+
}),
|
|
803
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TemplateRead, {
|
|
804
|
+
template,
|
|
805
|
+
section: "world",
|
|
806
|
+
value: data
|
|
807
|
+
})
|
|
808
|
+
]
|
|
809
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimelinePanel, {
|
|
810
|
+
path,
|
|
811
|
+
domain: "worldTimeline",
|
|
812
|
+
ownerKey: "worldNodeId",
|
|
813
|
+
ownerId: selectedId,
|
|
814
|
+
chapters
|
|
815
|
+
})]
|
|
816
|
+
})] });
|
|
817
|
+
}
|
|
818
|
+
//#endregion
|
|
819
|
+
//#region src/client/ui/RelationPanel.tsx
|
|
820
|
+
function RelationPanel({ path, ownerId, roles }) {
|
|
821
|
+
const [rows, setRows] = (0, react.useState)([]);
|
|
822
|
+
const reload = (0, react.useCallback)(async () => {
|
|
823
|
+
const all = await listEntities(path, "roleRelation");
|
|
824
|
+
setRows(all.filter((item) => {
|
|
825
|
+
const data = dataOf(item);
|
|
826
|
+
return String(data.roleId ?? "") === ownerId || String(data.targetRoleId ?? "") === ownerId;
|
|
827
|
+
}));
|
|
828
|
+
}, [path, ownerId]);
|
|
829
|
+
(0, react.useEffect)(() => {
|
|
830
|
+
reload();
|
|
831
|
+
}, [reload]);
|
|
832
|
+
useLiveReload(reload);
|
|
833
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
834
|
+
className: "dshNovelViewFormBody",
|
|
835
|
+
children: [rows.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
836
|
+
className: "dshNovelViewMuted",
|
|
837
|
+
children: emptyHint()
|
|
838
|
+
}), rows.map((row) => {
|
|
839
|
+
const data = dataOf(row);
|
|
840
|
+
const otherId = String(data.roleId ?? "") === ownerId ? data.targetRoleId : data.roleId;
|
|
841
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("article", {
|
|
842
|
+
className: "dshNovelEntry",
|
|
843
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
844
|
+
className: "dshNovelEntryTitle",
|
|
845
|
+
children: [
|
|
846
|
+
(roles.find((role) => role.id === otherId) ?? roles.find((role) => role.name === otherId))?.name ?? String(otherId ?? ""),
|
|
847
|
+
" · ",
|
|
848
|
+
String(data.relationType ?? "")
|
|
849
|
+
]
|
|
850
|
+
}), data.content !== void 0 && data.content !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
851
|
+
className: "dshNovelProse",
|
|
852
|
+
children: String(data.content)
|
|
853
|
+
})] })
|
|
854
|
+
}, row.id);
|
|
855
|
+
})]
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
//#endregion
|
|
859
|
+
//#region src/client/sections/ListDetailSection.tsx
|
|
860
|
+
function ListDetailSection({ path, domain, template, templateSection, chapters, tree, tabs, timelineDomain, ownerKey }) {
|
|
861
|
+
const [items, setItems] = (0, react.useState)([]);
|
|
862
|
+
const [selectedId, setSelectedId] = (0, react.useState)();
|
|
863
|
+
const [tab, setTab] = (0, react.useState)("detail");
|
|
864
|
+
const selected = items.find((item) => item.id === selectedId);
|
|
865
|
+
const data = viewData(selected);
|
|
866
|
+
const reload = (0, react.useCallback)(async () => {
|
|
867
|
+
const rows = await listEntities(path, domain);
|
|
868
|
+
setItems(rows);
|
|
869
|
+
setSelectedId((current) => {
|
|
870
|
+
if (current !== void 0 && rows.some((row) => row.id === current)) return current;
|
|
871
|
+
return rows[0]?.id;
|
|
872
|
+
});
|
|
873
|
+
}, [domain, path]);
|
|
874
|
+
(0, react.useEffect)(() => {
|
|
875
|
+
reload();
|
|
876
|
+
}, [reload]);
|
|
877
|
+
useLiveReload(reload);
|
|
878
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntitySidebar, {
|
|
879
|
+
items,
|
|
880
|
+
selectedId,
|
|
881
|
+
tree,
|
|
882
|
+
onSelect: (id) => {
|
|
883
|
+
setSelectedId(id);
|
|
884
|
+
setTab("detail");
|
|
885
|
+
}
|
|
886
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
887
|
+
className: "dshNovelViewForm",
|
|
888
|
+
children: [tabs.length > 1 && selectedId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionToolbar, { tabs: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailTabs, {
|
|
889
|
+
tabs: tabs.map((id) => ({
|
|
890
|
+
id,
|
|
891
|
+
label: id === "detail" ? "详情" : id === "timeline" ? "时间线" : "关系"
|
|
892
|
+
})),
|
|
893
|
+
value: tab,
|
|
894
|
+
onChange: setTab
|
|
895
|
+
}) }), selected === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
896
|
+
className: "dshNovelViewFormBody",
|
|
897
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
898
|
+
className: "dshNovelViewMuted",
|
|
899
|
+
children: emptyHint()
|
|
900
|
+
})
|
|
901
|
+
}) : tab === "detail" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
902
|
+
className: "dshNovelViewFormBody",
|
|
903
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
904
|
+
className: "dshNovelTitle",
|
|
905
|
+
children: String(data.name ?? "未命名")
|
|
906
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TemplateRead, {
|
|
907
|
+
template,
|
|
908
|
+
section: templateSection,
|
|
909
|
+
value: data
|
|
910
|
+
})]
|
|
911
|
+
}) : tab === "timeline" && timelineDomain && ownerKey ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimelinePanel, {
|
|
912
|
+
path,
|
|
913
|
+
domain: timelineDomain,
|
|
914
|
+
ownerKey,
|
|
915
|
+
ownerId: selectedId,
|
|
916
|
+
chapters
|
|
917
|
+
}) : tab === "relation" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RelationPanel, {
|
|
918
|
+
path,
|
|
919
|
+
ownerId: selectedId,
|
|
920
|
+
roles: items
|
|
921
|
+
}) : null]
|
|
922
|
+
})] });
|
|
923
|
+
}
|
|
924
|
+
//#endregion
|
|
925
|
+
//#region src/client/sections/owned-sections.tsx
|
|
926
|
+
function RoleSection(props) {
|
|
927
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ListDetailSection, {
|
|
928
|
+
...props,
|
|
929
|
+
domain: "role",
|
|
930
|
+
templateSection: "role",
|
|
931
|
+
tabs: [
|
|
932
|
+
"detail",
|
|
933
|
+
"relation",
|
|
934
|
+
"timeline"
|
|
935
|
+
],
|
|
936
|
+
timelineDomain: "roleTimeline",
|
|
937
|
+
ownerKey: "roleId"
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
function CreatureSection(props) {
|
|
941
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ListDetailSection, {
|
|
942
|
+
...props,
|
|
943
|
+
domain: "creature",
|
|
944
|
+
templateSection: "creature",
|
|
945
|
+
tabs: ["detail", "timeline"],
|
|
946
|
+
timelineDomain: "creatureTimeline",
|
|
947
|
+
ownerKey: "creatureId"
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
function ItemSection(props) {
|
|
951
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ListDetailSection, {
|
|
952
|
+
...props,
|
|
953
|
+
domain: "item",
|
|
954
|
+
templateSection: "item",
|
|
955
|
+
tabs: ["detail", "timeline"],
|
|
956
|
+
timelineDomain: "itemTimeline",
|
|
957
|
+
ownerKey: "itemId"
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
//#endregion
|
|
961
|
+
//#region src/client/sections/LevelSection.tsx
|
|
962
|
+
function parseSystem(data) {
|
|
963
|
+
const system = asRecord$1(data.system);
|
|
964
|
+
const levels = Array.isArray(system.levels) ? system.levels : emptyLevelSystem().levels;
|
|
965
|
+
return {
|
|
966
|
+
name: String(system.name ?? ""),
|
|
967
|
+
description: String(system.description ?? ""),
|
|
968
|
+
levels: levels.map((row) => {
|
|
969
|
+
const rec = asRecord$1(row);
|
|
970
|
+
const subs = Array.isArray(rec.subLevels) ? rec.subLevels : [];
|
|
971
|
+
return {
|
|
972
|
+
name: String(rec.name ?? ""),
|
|
973
|
+
promotionCondition: String(rec.promotionCondition ?? ""),
|
|
974
|
+
abilities: String(rec.abilities ?? ""),
|
|
975
|
+
subLevels: subs.map((sub) => String(asRecord$1(sub).name ?? "")).filter(Boolean)
|
|
976
|
+
};
|
|
977
|
+
})
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
function LevelSection({ path }) {
|
|
981
|
+
const [items, setItems] = (0, react.useState)([]);
|
|
982
|
+
const [selectedId, setSelectedId] = (0, react.useState)();
|
|
983
|
+
const [tab, setTab] = (0, react.useState)("detail");
|
|
984
|
+
const selected = items.find((item) => item.id === selectedId);
|
|
985
|
+
const data = viewData(selected);
|
|
986
|
+
const system = parseSystem(data);
|
|
987
|
+
const reload = (0, react.useCallback)(async () => {
|
|
988
|
+
const rows = await listEntities(path, "level");
|
|
989
|
+
setItems(rows);
|
|
990
|
+
setSelectedId((current) => current !== void 0 && rows.some((row) => row.id === current) ? current : rows[0]?.id);
|
|
991
|
+
}, [path]);
|
|
992
|
+
(0, react.useEffect)(() => {
|
|
993
|
+
reload();
|
|
994
|
+
}, [reload]);
|
|
995
|
+
useLiveReload(reload);
|
|
996
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntitySidebar, {
|
|
997
|
+
items,
|
|
998
|
+
selectedId,
|
|
999
|
+
label: (item) => {
|
|
1000
|
+
const system = asRecord$1(asRecord$1(item.data).system);
|
|
1001
|
+
return item.name ?? (typeof system.name === "string" ? system.name : void 0) ?? item.id;
|
|
1002
|
+
},
|
|
1003
|
+
onSelect: (id) => {
|
|
1004
|
+
setSelectedId(id);
|
|
1005
|
+
setTab("detail");
|
|
1006
|
+
}
|
|
1007
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1008
|
+
className: "dshNovelViewForm",
|
|
1009
|
+
children: [selectedId !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionToolbar, { tabs: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailTabs, {
|
|
1010
|
+
tabs: [{
|
|
1011
|
+
id: "detail",
|
|
1012
|
+
label: "详情"
|
|
1013
|
+
}, {
|
|
1014
|
+
id: "tiers",
|
|
1015
|
+
label: "档位"
|
|
1016
|
+
}],
|
|
1017
|
+
value: tab,
|
|
1018
|
+
onChange: setTab
|
|
1019
|
+
}) }), selected === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1020
|
+
className: "dshNovelViewFormBody",
|
|
1021
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1022
|
+
className: "dshNovelViewMuted",
|
|
1023
|
+
children: emptyHint()
|
|
1024
|
+
})
|
|
1025
|
+
}) : tab === "detail" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1026
|
+
className: "dshNovelViewFormBody",
|
|
1027
|
+
children: [
|
|
1028
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1029
|
+
className: "dshNovelTitle",
|
|
1030
|
+
children: String(data.name || system.name || "未命名")
|
|
1031
|
+
}),
|
|
1032
|
+
system.description !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1033
|
+
className: "dshNovelProse",
|
|
1034
|
+
children: system.description
|
|
1035
|
+
}),
|
|
1036
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadField, {
|
|
1037
|
+
label: "体系",
|
|
1038
|
+
children: system.name
|
|
1039
|
+
})
|
|
1040
|
+
]
|
|
1041
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1042
|
+
className: "dshNovelViewFormBody",
|
|
1043
|
+
children: [system.levels.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1044
|
+
className: "dshNovelViewMuted",
|
|
1045
|
+
children: emptyHint()
|
|
1046
|
+
}), system.levels.map((level, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
1047
|
+
className: "dshNovelTier",
|
|
1048
|
+
children: [
|
|
1049
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1050
|
+
className: "dshNovelEntryTitle",
|
|
1051
|
+
children: level.name
|
|
1052
|
+
}),
|
|
1053
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1054
|
+
className: "dshNovelPills",
|
|
1055
|
+
children: level.subLevels.map((sub) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Pill, { children: sub }, sub))
|
|
1056
|
+
}),
|
|
1057
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadField, {
|
|
1058
|
+
label: "突破条件",
|
|
1059
|
+
children: level.promotionCondition
|
|
1060
|
+
}),
|
|
1061
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadField, {
|
|
1062
|
+
label: "能力",
|
|
1063
|
+
children: level.abilities
|
|
1064
|
+
})
|
|
1065
|
+
]
|
|
1066
|
+
}, index))]
|
|
1067
|
+
})]
|
|
1068
|
+
})] });
|
|
1069
|
+
}
|
|
1070
|
+
//#endregion
|
|
1071
|
+
//#region src/novel/timeline-labels.ts
|
|
1072
|
+
const EVENT_TYPE_LABELS = {
|
|
1073
|
+
plot: "主线",
|
|
1074
|
+
conflict: "冲突",
|
|
1075
|
+
suspense: "悬念",
|
|
1076
|
+
foreshadow: "伏笔",
|
|
1077
|
+
turning: "转折",
|
|
1078
|
+
resolution: "收束",
|
|
1079
|
+
other: "其他"
|
|
1080
|
+
};
|
|
1081
|
+
const TRACKED_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
1082
|
+
"conflict",
|
|
1083
|
+
"suspense",
|
|
1084
|
+
"foreshadow"
|
|
1085
|
+
]);
|
|
1086
|
+
function asEventType(value) {
|
|
1087
|
+
if (typeof value !== "string") return void 0;
|
|
1088
|
+
const key = value.trim();
|
|
1089
|
+
return key.length > 0 ? key : void 0;
|
|
1090
|
+
}
|
|
1091
|
+
function asResolved(value) {
|
|
1092
|
+
if (typeof value === "boolean") return value;
|
|
1093
|
+
if (value === "true") return true;
|
|
1094
|
+
if (value === "false") return false;
|
|
1095
|
+
}
|
|
1096
|
+
function timelineEventTypeLabel(eventType) {
|
|
1097
|
+
const key = asEventType(eventType);
|
|
1098
|
+
if (key === void 0) return void 0;
|
|
1099
|
+
return EVENT_TYPE_LABELS[key] ?? key;
|
|
1100
|
+
}
|
|
1101
|
+
function timelineResolvedLabel(eventType, isResolved) {
|
|
1102
|
+
const key = asEventType(eventType);
|
|
1103
|
+
if (key === void 0 || !TRACKED_EVENT_TYPES.has(key)) return void 0;
|
|
1104
|
+
if (asResolved(isResolved) === true) return "已解决";
|
|
1105
|
+
return "未解决";
|
|
1106
|
+
}
|
|
1107
|
+
//#endregion
|
|
1108
|
+
//#region src/client/sections/TimelineSection.tsx
|
|
1109
|
+
function TimelineSection({ path, template, chapters }) {
|
|
1110
|
+
const [items, setItems] = (0, react.useState)([]);
|
|
1111
|
+
const [selectedId, setSelectedId] = (0, react.useState)();
|
|
1112
|
+
const selected = items.find((item) => item.id === selectedId);
|
|
1113
|
+
const data = viewData(selected);
|
|
1114
|
+
const chapter = chapters.find((item) => item.id === data.relatedChapterId);
|
|
1115
|
+
const typeLabel = timelineEventTypeLabel(data.eventType);
|
|
1116
|
+
const resolvedLabel = timelineResolvedLabel(data.eventType, data.isResolved);
|
|
1117
|
+
const reload = (0, react.useCallback)(async () => {
|
|
1118
|
+
const rows = await listEntities(path, "timeline");
|
|
1119
|
+
setItems(rows);
|
|
1120
|
+
setSelectedId((current) => current !== void 0 && rows.some((row) => row.id === current) ? current : rows[0]?.id);
|
|
1121
|
+
}, [path]);
|
|
1122
|
+
(0, react.useEffect)(() => {
|
|
1123
|
+
reload();
|
|
1124
|
+
}, [reload]);
|
|
1125
|
+
useLiveReload(reload);
|
|
1126
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntitySidebar, {
|
|
1127
|
+
items,
|
|
1128
|
+
selectedId,
|
|
1129
|
+
onSelect: setSelectedId
|
|
1130
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
1131
|
+
className: "dshNovelViewForm",
|
|
1132
|
+
children: selected === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1133
|
+
className: "dshNovelViewFormBody",
|
|
1134
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1135
|
+
className: "dshNovelViewMuted",
|
|
1136
|
+
children: emptyHint()
|
|
1137
|
+
})
|
|
1138
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1139
|
+
className: "dshNovelViewFormBody",
|
|
1140
|
+
children: [
|
|
1141
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1142
|
+
className: "dshNovelTitle",
|
|
1143
|
+
children: String(data.title ?? data.name ?? "未命名")
|
|
1144
|
+
}),
|
|
1145
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1146
|
+
className: "dshNovelMeta",
|
|
1147
|
+
children: [
|
|
1148
|
+
data.timeLabel,
|
|
1149
|
+
typeLabel,
|
|
1150
|
+
resolvedLabel,
|
|
1151
|
+
chapter?.name
|
|
1152
|
+
].filter(Boolean).map(String).join(" · ")
|
|
1153
|
+
}),
|
|
1154
|
+
typeLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadField, {
|
|
1155
|
+
label: "类型",
|
|
1156
|
+
children: typeLabel
|
|
1157
|
+
}),
|
|
1158
|
+
resolvedLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReadField, {
|
|
1159
|
+
label: "状态",
|
|
1160
|
+
children: resolvedLabel === "已解决" && data.resolveChapterNo !== void 0 && data.resolveChapterNo !== "" ? `${resolvedLabel} · 回收章 ${String(data.resolveChapterNo)}` : resolvedLabel
|
|
1161
|
+
}),
|
|
1162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TemplateRead, {
|
|
1163
|
+
template,
|
|
1164
|
+
section: "timeline",
|
|
1165
|
+
extra: "timeline",
|
|
1166
|
+
value: data
|
|
1167
|
+
})
|
|
1168
|
+
]
|
|
1169
|
+
})
|
|
1170
|
+
})] });
|
|
1171
|
+
}
|
|
1172
|
+
//#endregion
|
|
1173
|
+
//#region src/client/sections/OutlineSection.tsx
|
|
1174
|
+
function labelData$1(item) {
|
|
1175
|
+
const data = dataOf(item);
|
|
1176
|
+
return {
|
|
1177
|
+
...data,
|
|
1178
|
+
name: item.name ?? data.name
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
function OutlineSection({ path, template }) {
|
|
1182
|
+
const [items, setItems] = (0, react.useState)([]);
|
|
1183
|
+
const [selectedId, setSelectedId] = (0, react.useState)();
|
|
1184
|
+
const selected = items.find((item) => item.id === selectedId);
|
|
1185
|
+
const data = viewData(selected);
|
|
1186
|
+
const isChapter = outlineKindOf(data) === "chapter_segment";
|
|
1187
|
+
const parent = items.find((item) => item.id === data.parentId);
|
|
1188
|
+
const parentLabel = parent === void 0 ? void 0 : outlineNodeLabel(labelData$1(parent), parent.name ?? parent.id);
|
|
1189
|
+
const reload = (0, react.useCallback)(async () => {
|
|
1190
|
+
const rows = sortByOrdinalName(await listEntities(path, "outline"), (item) => dataOf(item).ordinal);
|
|
1191
|
+
setItems(rows);
|
|
1192
|
+
setSelectedId((current) => current !== void 0 && rows.some((row) => row.id === current) ? current : rows[0]?.id);
|
|
1193
|
+
}, [path]);
|
|
1194
|
+
(0, react.useEffect)(() => {
|
|
1195
|
+
reload();
|
|
1196
|
+
}, [reload]);
|
|
1197
|
+
useLiveReload(reload);
|
|
1198
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntitySidebar, {
|
|
1199
|
+
items,
|
|
1200
|
+
selectedId,
|
|
1201
|
+
tree: true,
|
|
1202
|
+
label: (item) => outlineNodeLabel(labelData$1(item), item.name ?? item.id),
|
|
1203
|
+
onSelect: setSelectedId
|
|
1204
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
1205
|
+
className: "dshNovelViewForm",
|
|
1206
|
+
children: selected === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1207
|
+
className: "dshNovelViewFormBody",
|
|
1208
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1209
|
+
className: "dshNovelViewMuted",
|
|
1210
|
+
children: emptyHint()
|
|
1211
|
+
})
|
|
1212
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1213
|
+
className: "dshNovelViewFormBody",
|
|
1214
|
+
children: [
|
|
1215
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1216
|
+
className: "dshNovelTitle",
|
|
1217
|
+
children: outlineNodeLabel(data, String(data.name ?? "未命名"))
|
|
1218
|
+
}),
|
|
1219
|
+
parentLabel !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1220
|
+
className: "dshNovelMeta",
|
|
1221
|
+
children: parentLabel
|
|
1222
|
+
}),
|
|
1223
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TemplateRead, {
|
|
1224
|
+
template,
|
|
1225
|
+
section: "outline",
|
|
1226
|
+
extra: isChapter ? "chapterSegment" : void 0,
|
|
1227
|
+
value: data,
|
|
1228
|
+
omitKeys: ["name"]
|
|
1229
|
+
})
|
|
1230
|
+
]
|
|
1231
|
+
})
|
|
1232
|
+
})] });
|
|
1233
|
+
}
|
|
1234
|
+
//#endregion
|
|
1235
|
+
//#region src/client/sections/ChapterSection.tsx
|
|
1236
|
+
function labelData(item) {
|
|
1237
|
+
const data = dataOf(item);
|
|
1238
|
+
return {
|
|
1239
|
+
...data,
|
|
1240
|
+
name: item.name ?? data.name
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
function ChapterSection({ path }) {
|
|
1244
|
+
const [items, setItems] = (0, react.useState)([]);
|
|
1245
|
+
const [outlines, setOutlines] = (0, react.useState)([]);
|
|
1246
|
+
const [selectedId, setSelectedId] = (0, react.useState)();
|
|
1247
|
+
const selected = items.find((item) => item.id === selectedId);
|
|
1248
|
+
const data = viewData(selected);
|
|
1249
|
+
const related = outlines.find((item) => item.id === data.relatedOutlineId);
|
|
1250
|
+
const outlineName = related === void 0 ? void 0 : outlineNodeLabel(labelData(related), related.name ?? related.id);
|
|
1251
|
+
const reload = (0, react.useCallback)(async () => {
|
|
1252
|
+
const [rows, outlineRows] = await Promise.all([listEntities(path, "chapter"), listEntities(path, "outline")]);
|
|
1253
|
+
const sorted = sortByOrdinalName(rows, (item) => dataOf(item).ordinal ?? dataOf(item).chapterNo);
|
|
1254
|
+
setItems(sorted);
|
|
1255
|
+
setOutlines(outlineRows);
|
|
1256
|
+
setSelectedId((current) => current !== void 0 && sorted.some((row) => row.id === current) ? current : sorted[0]?.id);
|
|
1257
|
+
}, [path]);
|
|
1258
|
+
(0, react.useEffect)(() => {
|
|
1259
|
+
reload();
|
|
1260
|
+
}, [reload]);
|
|
1261
|
+
useLiveReload(reload);
|
|
1262
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntitySidebar, {
|
|
1263
|
+
items,
|
|
1264
|
+
selectedId,
|
|
1265
|
+
label: (item) => chapterNodeLabel(labelData(item), item.name ?? item.id),
|
|
1266
|
+
onSelect: setSelectedId
|
|
1267
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
1268
|
+
className: "dshNovelViewForm",
|
|
1269
|
+
children: selected === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1270
|
+
className: "dshNovelViewFormBody",
|
|
1271
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1272
|
+
className: "dshNovelViewMuted",
|
|
1273
|
+
children: emptyHint()
|
|
1274
|
+
})
|
|
1275
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1276
|
+
className: "dshNovelViewFormBody",
|
|
1277
|
+
children: [
|
|
1278
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1279
|
+
className: "dshNovelTitle",
|
|
1280
|
+
children: chapterNodeLabel(data, String(data.name ?? data.title ?? "未命名"))
|
|
1281
|
+
}),
|
|
1282
|
+
outlineName !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1283
|
+
className: "dshNovelMeta",
|
|
1284
|
+
children: outlineName
|
|
1285
|
+
}),
|
|
1286
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1287
|
+
className: "dshNovelProse",
|
|
1288
|
+
children: String(data.content ?? "") || "—"
|
|
1289
|
+
})
|
|
1290
|
+
]
|
|
1291
|
+
})
|
|
1292
|
+
})] });
|
|
1293
|
+
}
|
|
1294
|
+
//#endregion
|
|
1295
|
+
//#region src/client/sections/SkillSection.tsx
|
|
1296
|
+
function SkillSection({ path }) {
|
|
1297
|
+
const [skills, setSkills] = (0, react.useState)([]);
|
|
1298
|
+
const [selectedId, setSelectedId] = (0, react.useState)();
|
|
1299
|
+
const selected = skills.find((item) => item.id === selectedId);
|
|
1300
|
+
const reload = (0, react.useCallback)(async () => {
|
|
1301
|
+
const rows = await novelApi(`/skills?path=${encodeURIComponent(path)}`);
|
|
1302
|
+
setSkills(rows);
|
|
1303
|
+
setSelectedId((current) => current !== void 0 && rows.some((row) => row.id === current) ? current : void 0);
|
|
1304
|
+
}, [path]);
|
|
1305
|
+
(0, react.useEffect)(() => {
|
|
1306
|
+
reload();
|
|
1307
|
+
}, [reload]);
|
|
1308
|
+
useLiveReload(reload);
|
|
1309
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntitySidebar, {
|
|
1310
|
+
className: "dshNovelSkillTree",
|
|
1311
|
+
files: true,
|
|
1312
|
+
tree: true,
|
|
1313
|
+
items: (0, react.useMemo)(() => skillTreeItems(skills), [skills]),
|
|
1314
|
+
selectedId,
|
|
1315
|
+
onSelect: setSelectedId
|
|
1316
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
1317
|
+
className: "dshNovelViewForm",
|
|
1318
|
+
children: selected === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1319
|
+
className: "dshNovelViewFormBody",
|
|
1320
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1321
|
+
className: "dshNovelViewEmpty",
|
|
1322
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1323
|
+
className: "dshNovelViewMuted",
|
|
1324
|
+
children: skills.length === 0 ? emptyHint() : "选择一份技能阅读。"
|
|
1325
|
+
})
|
|
1326
|
+
})
|
|
1327
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1328
|
+
className: "dshNovelViewFormBody",
|
|
1329
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
|
|
1330
|
+
className: "dshNovelDoc",
|
|
1331
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1332
|
+
className: "dshNovelCrumb",
|
|
1333
|
+
children: crumbOf(selected)
|
|
1334
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1335
|
+
className: "dshNovelMarkdown",
|
|
1336
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, { text: selected.body })
|
|
1337
|
+
})]
|
|
1338
|
+
})
|
|
1339
|
+
})
|
|
1340
|
+
})] });
|
|
1341
|
+
}
|
|
1342
|
+
function crumbOf(skill) {
|
|
1343
|
+
const folder = (skill.folder ?? "").trim();
|
|
1344
|
+
return folder.length === 0 ? skill.title : `${folder.replaceAll("/", " / ")} / ${skill.title}`;
|
|
1345
|
+
}
|
|
1346
|
+
function skillTreeItems(skills) {
|
|
1347
|
+
const folders = /* @__PURE__ */ new Set();
|
|
1348
|
+
for (const skill of skills) {
|
|
1349
|
+
const parts = (skill.folder ?? "").split("/").filter(Boolean);
|
|
1350
|
+
let acc = "";
|
|
1351
|
+
for (const part of parts) {
|
|
1352
|
+
acc = acc.length === 0 ? part : `${acc}/${part}`;
|
|
1353
|
+
folders.add(acc);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
const nodes = [];
|
|
1357
|
+
for (const folder of [...folders].sort()) {
|
|
1358
|
+
const parts = folder.split("/");
|
|
1359
|
+
const parent = parts.slice(0, -1).join("/");
|
|
1360
|
+
nodes.push({
|
|
1361
|
+
id: `dir:${folder}`,
|
|
1362
|
+
name: parts[parts.length - 1] ?? folder,
|
|
1363
|
+
data: {
|
|
1364
|
+
kind: "folder",
|
|
1365
|
+
parentId: parent.length > 0 ? `dir:${parent}` : ""
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
for (const skill of skills) {
|
|
1370
|
+
const folder = (skill.folder ?? "").trim();
|
|
1371
|
+
nodes.push({
|
|
1372
|
+
id: skill.id,
|
|
1373
|
+
name: skill.title,
|
|
1374
|
+
data: {
|
|
1375
|
+
kind: "file",
|
|
1376
|
+
parentId: folder.length > 0 ? `dir:${folder}` : ""
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
return nodes;
|
|
1381
|
+
}
|
|
1382
|
+
//#endregion
|
|
1383
|
+
//#region src/client/NovelWorkspaceView.tsx
|
|
1384
|
+
const sessionMemory = /* @__PURE__ */ new Map();
|
|
1385
|
+
/** Novel workspace occupying the conversation.view tab. Does not switch the active view. */
|
|
1386
|
+
function NovelWorkspaceView({ sessionId, useSessions }) {
|
|
1387
|
+
const workspacePath = useSessions((state) => {
|
|
1388
|
+
const session = state.byId[sessionId];
|
|
1389
|
+
return session?.cwd ?? session?.header?.cwd;
|
|
1390
|
+
});
|
|
1391
|
+
const [section, setSection] = (0, react.useState)(sessionMemory.get(sessionId) ?? "overview");
|
|
1392
|
+
const [isNovel, setIsNovel] = (0, react.useState)(false);
|
|
1393
|
+
const [template, setTemplate] = (0, react.useState)();
|
|
1394
|
+
const [chapters, setChapters] = (0, react.useState)([]);
|
|
1395
|
+
const [styles, setStyles] = (0, react.useState)([]);
|
|
1396
|
+
(0, react.useEffect)(() => {
|
|
1397
|
+
sessionMemory.set(sessionId, section);
|
|
1398
|
+
}, [section, sessionId]);
|
|
1399
|
+
const refreshStatus = (0, react.useCallback)(async () => {
|
|
1400
|
+
if (workspacePath === void 0) {
|
|
1401
|
+
setIsNovel(false);
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
const status = await novelApi(`/status?path=${encodeURIComponent(workspacePath)}`);
|
|
1405
|
+
setIsNovel(status.novel);
|
|
1406
|
+
if (!status.novel) return;
|
|
1407
|
+
const listed = await novelApi(`/styles?path=${encodeURIComponent(workspacePath)}`).catch(() => []);
|
|
1408
|
+
if (listed.length > 0) setStyles(listed);
|
|
1409
|
+
setTemplate(await novelApi(`/template?path=${encodeURIComponent(workspacePath)}`));
|
|
1410
|
+
setChapters(await listEntities(workspacePath, "chapter"));
|
|
1411
|
+
}, [workspacePath]);
|
|
1412
|
+
(0, react.useEffect)(() => {
|
|
1413
|
+
refreshStatus();
|
|
1414
|
+
}, [refreshStatus]);
|
|
1415
|
+
useLiveReload(refreshStatus);
|
|
1416
|
+
const pageProps = {
|
|
1417
|
+
path: workspacePath ?? "",
|
|
1418
|
+
template,
|
|
1419
|
+
chapters
|
|
1420
|
+
};
|
|
1421
|
+
if (workspacePath === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1422
|
+
className: "dshNovelView",
|
|
1423
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1424
|
+
className: "dshNovelViewEmpty",
|
|
1425
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "先在左侧打开或新建会话,并绑定工作区目录。" })
|
|
1426
|
+
})
|
|
1427
|
+
});
|
|
1428
|
+
if (!isNovel) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1429
|
+
className: "dshNovelView",
|
|
1430
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1431
|
+
className: "dshNovelViewEmpty",
|
|
1432
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: "当前目录还不是小说工作区。" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1433
|
+
className: "dshNovelViewMuted",
|
|
1434
|
+
children: "在对话里让 AI 初始化小说工作区。"
|
|
1435
|
+
})]
|
|
1436
|
+
})
|
|
1437
|
+
});
|
|
1438
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1439
|
+
className: "dshNovelView",
|
|
1440
|
+
children: [
|
|
1441
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("nav", {
|
|
1442
|
+
className: "dshNovelViewRail",
|
|
1443
|
+
"aria-label": "小说分区",
|
|
1444
|
+
children: CONTENT_SECTIONS.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
1445
|
+
label: DOMAIN_LABELS[key],
|
|
1446
|
+
side: "right",
|
|
1447
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1448
|
+
type: "button",
|
|
1449
|
+
className: "dshNovelViewRailBtn",
|
|
1450
|
+
"data-active": section === key || void 0,
|
|
1451
|
+
"aria-label": DOMAIN_LABELS[key],
|
|
1452
|
+
onClick: () => {
|
|
1453
|
+
setSection(key);
|
|
1454
|
+
},
|
|
1455
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionIcon, { section: key })
|
|
1456
|
+
})
|
|
1457
|
+
}, key))
|
|
1458
|
+
}),
|
|
1459
|
+
section === "overview" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewSection, {
|
|
1460
|
+
path: workspacePath,
|
|
1461
|
+
template,
|
|
1462
|
+
styles
|
|
1463
|
+
}),
|
|
1464
|
+
section === "world" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorldSection, { ...pageProps }),
|
|
1465
|
+
section === "role" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleSection, { ...pageProps }),
|
|
1466
|
+
section === "creature" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreatureSection, { ...pageProps }),
|
|
1467
|
+
section === "item" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ItemSection, { ...pageProps }),
|
|
1468
|
+
section === "level" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LevelSection, { ...pageProps }),
|
|
1469
|
+
section === "timeline" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimelineSection, { ...pageProps }),
|
|
1470
|
+
section === "outline" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutlineSection, { ...pageProps }),
|
|
1471
|
+
section === "chapter" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChapterSection, { ...pageProps }),
|
|
1472
|
+
section === "skill" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillSection, { path: workspacePath })
|
|
1473
|
+
]
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
//#endregion
|
|
1477
|
+
//#region src/client/styles.ts
|
|
1478
|
+
const NOVEL_STYLES = `
|
|
1479
|
+
.dshNovelView { display: flex; height: 100%; min-height: 0; width: 100%; overflow: hidden; color: var(--dsw-alias-label-primary, inherit); font-size: 13px; line-height: 1.45; background: var(--dsw-alias-bg-base); }
|
|
1480
|
+
.dshNovelViewRail { display: flex; flex-direction: column; gap: 2px; width: 46px; flex-shrink: 0; overflow: auto; padding: 8px 4px; border-right: 1px solid var(--dsw-alias-border-l2); }
|
|
1481
|
+
.dshNovelViewRail button { appearance: none; display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px; padding: 0; border: 0; border-radius: 8px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; transition-property: transform, background-color, color; transition-duration: 120ms; transition-timing-function: cubic-bezier(0.2, 0, 0, 1); }
|
|
1482
|
+
.dshNovelViewRail button:hover { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary, inherit); }
|
|
1483
|
+
.dshNovelViewRail button:active { transform: scale(0.96); }
|
|
1484
|
+
.dshNovelViewRail button[data-active] { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary, inherit); }
|
|
1485
|
+
.dshNovelViewList { display: flex; flex-direction: column; width: 200px; flex-shrink: 0; min-width: 0; min-height: 0; border-right: 1px solid var(--dsw-alias-border-l2); overflow: hidden; }
|
|
1486
|
+
.dshNovelSkillTree { width: 240px; }
|
|
1487
|
+
.dshNovelViewListToolbar { display: flex; align-items: center; gap: 4px; padding: 8px 8px 4px; min-width: 0; overflow: hidden; }
|
|
1488
|
+
.dshNovelViewSearch { display: flex; flex: 1; min-width: 0; overflow: hidden; box-sizing: border-box; }
|
|
1489
|
+
.dshNovelViewSearch > span, .dshNovelInput { display: flex !important; width: 100%; min-width: 0; max-width: 100%; box-sizing: border-box; }
|
|
1490
|
+
.dshNovelControl { display: block; width: 100%; max-width: 100%; box-sizing: border-box; min-width: 0; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; background: var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-base)); color: var(--dsw-alias-label-primary, inherit); padding: 5px 8px; font-size: 14px; line-height: 22px; }
|
|
1491
|
+
.dshNovelControl:focus { outline: none; border-color: var(--dsw-alias-brand-primary); }
|
|
1492
|
+
textarea.dshNovelControl { min-height: 88px; resize: vertical; }
|
|
1493
|
+
.dshNovelJsonEditor { flex: 1; min-height: 0; height: 100%; width: 100%; resize: none; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 18px; padding: 12px; border: 0; border-radius: 12px; background: var(--dsw-alias-markdown-code-block, var(--dsw-alias-bg-layer-1)); color: var(--dsw-alias-label-primary, inherit); box-sizing: border-box; }
|
|
1494
|
+
.dshNovelJsonEditor:focus { outline: none; box-shadow: inset 0 0 0 1px var(--dsw-alias-brand-primary); }
|
|
1495
|
+
.dshNovelTabs { display: flex; gap: 36px; margin-top: 4px; padding-left: 8px; min-height: 0; }
|
|
1496
|
+
.dshNovelTabs button { appearance: none; position: relative; padding: 0 0 11px; border: 0; background: transparent; font-size: 13px; line-height: 16px; font-weight: 500; color: var(--dsw-alias-label-tertiary); cursor: pointer; }
|
|
1497
|
+
.dshNovelTabs button::after { content: ""; position: absolute; right: 0; bottom: 1px; left: 0; height: 2px; border-radius: 2px; background: transparent; }
|
|
1498
|
+
.dshNovelTabs button[data-active] { color: var(--dsw-alias-state-business-primary); }
|
|
1499
|
+
.dshNovelTabs button[data-active]::after { background: var(--dsw-alias-state-business-primary); }
|
|
1500
|
+
.dshNovelEntry { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 6px; }
|
|
1501
|
+
.dshNovelEntryTitle { font-size: 14px; font-weight: 500; line-height: 22px; }
|
|
1502
|
+
.dshNovelTier { display: flex; flex-direction: column; gap: 8px; padding: 12px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 6px; }
|
|
1503
|
+
.dshNovelPills { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
1504
|
+
.dshNovelTitle { margin: 0; font-size: 20px; font-weight: 600; line-height: 28px; }
|
|
1505
|
+
.dshNovelMeta { margin: 0; color: var(--dsw-alias-label-secondary); font-size: 13px; line-height: 20px; }
|
|
1506
|
+
.dshNovelProse { margin: 0; white-space: pre-wrap; font-size: 14px; line-height: 24px; }
|
|
1507
|
+
.dshNovelReadValue { white-space: pre-wrap; font-size: 14px; line-height: 22px; color: var(--dsw-alias-label-primary, inherit); }
|
|
1508
|
+
.dshNovelViewListBody { flex: 1; min-height: 0; overflow: auto; padding: 4px 6px 12px; }
|
|
1509
|
+
.dshNovelViewRow { appearance: none; display: flex; align-items: center; gap: 6px; width: 100%; min-height: 32px; text-align: left; border: 0; background: transparent; color: inherit; padding: 0 8px; border-radius: 6px; cursor: pointer; transition-property: transform, background-color, color; transition-duration: 120ms; transition-timing-function: cubic-bezier(0.2, 0, 0, 1); }
|
|
1510
|
+
.dshNovelViewRow:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
1511
|
+
.dshNovelViewRow:active { transform: scale(0.96); }
|
|
1512
|
+
.dshNovelViewRow[data-active] { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-primary); }
|
|
1513
|
+
.dshNovelViewRow[data-kind="folder"] { color: var(--dsw-alias-label-secondary); font-weight: 500; }
|
|
1514
|
+
.dshNovelViewRowIcon { display: inline-flex; width: 16px; height: 16px; flex-shrink: 0; color: currentColor; }
|
|
1515
|
+
.dshNovelViewChevron { display: inline-flex; align-items: center; justify-content: center; width: 14px; flex-shrink: 0; transition-property: transform; transition-duration: 120ms; color: var(--dsw-alias-label-tertiary, inherit); }
|
|
1516
|
+
.dshNovelViewChevron[data-open] { transform: rotate(90deg); }
|
|
1517
|
+
.dshNovelDoc { display: flex; flex-direction: column; gap: 12px; max-width: 680px; width: 100%; }
|
|
1518
|
+
.dshNovelCrumb { margin: 0; font-size: 12px; line-height: 18px; color: var(--dsw-alias-label-tertiary); }
|
|
1519
|
+
.dshNovelMarkdown { font-size: 14px; line-height: 24px; color: var(--dsw-alias-label-primary); }
|
|
1520
|
+
.dshNovelViewForm { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
1521
|
+
.dshNovelViewFormHead { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; padding: 4px 16px 0 12px; border-bottom: 1px solid var(--dsw-alias-border-l2); min-width: 0; }
|
|
1522
|
+
.dshNovelViewFormHeadTabs { min-width: 0; overflow: hidden; }
|
|
1523
|
+
.dshNovelViewFormHeadActions { display: flex; flex: none; align-items: center; gap: 8px; margin-left: auto; padding-bottom: 6px; }
|
|
1524
|
+
.dshNovelViewFormBody { flex: 1; min-height: 0; overflow: auto; padding: 12px; display: flex; flex-direction: column; gap: 12px; }
|
|
1525
|
+
.dshNovelViewField { display: flex; flex-direction: column; gap: 4px; }
|
|
1526
|
+
.dshNovelViewField label { font-size: 12px; color: var(--dsw-alias-label-secondary, inherit); }
|
|
1527
|
+
.dshNovelViewEmpty { margin: auto; padding: 32px 20px; display: flex; flex-direction: column; gap: 12px; max-width: 420px; width: 100%; }
|
|
1528
|
+
.dshNovelViewMuted { color: var(--dsw-alias-label-secondary, inherit); font-size: 12px; }
|
|
1529
|
+
.dshNovelViewError { color: var(--dsw-alias-state-error-primary, tomato); font-size: 12px; }
|
|
1530
|
+
`;
|
|
1531
|
+
function installNovelStyles() {
|
|
1532
|
+
const style = document.createElement("style");
|
|
1533
|
+
style.dataset.plugin = "dsh-novel";
|
|
1534
|
+
style.dataset.pluginCss = "dsh-novel/content";
|
|
1535
|
+
style.textContent = NOVEL_STYLES;
|
|
1536
|
+
document.head.appendChild(style);
|
|
1537
|
+
return () => {
|
|
1538
|
+
style.remove();
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
//#endregion
|
|
1542
|
+
//#region src/client/index.ts
|
|
1543
|
+
const inject = ["slots", "sessions"];
|
|
1544
|
+
function apply(ctx) {
|
|
1545
|
+
ctx.effect(() => installNovelStyles(), "dsh-novel: content tab styles");
|
|
1546
|
+
ctx.slots.inject("conversation.view", () => ctx.slots.register({
|
|
1547
|
+
name: "conversation.view",
|
|
1548
|
+
id: "novel",
|
|
1549
|
+
order: 20,
|
|
1550
|
+
label: () => "内容"
|
|
1551
|
+
}, NovelWorkspaceView));
|
|
1552
|
+
}
|
|
1553
|
+
//#endregion
|
|
1554
|
+
exports.apply = apply;
|
|
1555
|
+
exports.inject = inject;
|
|
1556
|
+
return module.exports;
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
//# sourceMappingURL=client.js.map
|