dsh-maoying 0.1.12 → 0.1.13
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/README.en.md +169 -0
- package/README.md +3 -1
- package/docs/creator-panel.md +4 -2
- package/lib/client.js +346 -162
- package/package.json +2 -1
package/lib/client.js
CHANGED
|
@@ -55,19 +55,15 @@ window.__ModuleLoader__.load({
|
|
|
55
55
|
var API_TIMEOUT_MS = 60000;
|
|
56
56
|
|
|
57
57
|
var STATUS_FLOW = ["pending", "writing", "review", "accepted"];
|
|
58
|
-
var STATUS_LABEL = { pending: "待写", writing: "写作中", review: "审校中", accepted: "已定稿" };
|
|
59
58
|
var FS_ORDER = ["planted", "progressing", "resolved", "abandoned"];
|
|
60
59
|
var FS_FLOW = { planted: "progressing", progressing: "resolved" };
|
|
61
|
-
var FS_LABEL = { planted: "埋设", progressing: "推进中", resolved: "已回收", abandoned: "已弃用" };
|
|
62
|
-
var PRI_LABEL = { high: "高", normal: "中", low: "低" };
|
|
63
60
|
var PHASES = ["idea", "setting", "outline", "writing", "review", "consistency", "export"];
|
|
64
|
-
|
|
65
|
-
var
|
|
66
|
-
var CLUE_KIND_LABEL = { clue: "线索", red_herring: "红鲱鱼", inference: "推理", question: "疑问" };
|
|
61
|
+
// snake→camel 键表:clue.* 词典键用小驼峰,数据里的 kind 是 snake_case。
|
|
62
|
+
var CLUE_KIND_KEYS = { clue: "clue", red_herring: "redHerring", inference: "inference", question: "question" };
|
|
67
63
|
var TABS = [
|
|
68
|
-
["overview", "
|
|
69
|
-
["foreshadows", "
|
|
70
|
-
["world", "
|
|
64
|
+
["overview", "tabs.overview"], ["chapters", "tabs.chapters"], ["characters", "tabs.characters"],
|
|
65
|
+
["foreshadows", "tabs.foreshadows"], ["relations", "tabs.relations"], ["timeline", "tabs.timeline"],
|
|
66
|
+
["world", "tabs.world"], ["index", "tabs.index"], ["settings", "tabs.settings"],
|
|
71
67
|
];
|
|
72
68
|
|
|
73
69
|
// 角色徽章四色(role 是自由文本):按关键词归 tone,命中即返。
|
|
@@ -85,16 +81,16 @@ window.__ModuleLoader__.load({
|
|
|
85
81
|
}
|
|
86
82
|
function statusBadge(status) {
|
|
87
83
|
var s = STATUS_FLOW.indexOf(status) !== -1 ? status : "pending";
|
|
88
|
-
return h("span", { className: "myt-badge", "data-status": s },
|
|
84
|
+
return h("span", { className: "myt-badge", "data-status": s }, tt("status." + s) + (status && s !== status ? tf("status.rawSuffix", { raw: status }) : ""));
|
|
89
85
|
}
|
|
90
86
|
function phasePill(phase) {
|
|
91
87
|
var p = PHASES.indexOf(phase) !== -1 ? phase : null;
|
|
92
|
-
if (!p) return h("span", { className: "myt-phase", "data-phase": "idea", title: phase || "" }, phase ? String(phase) : "
|
|
93
|
-
return h("span", { className: "myt-phase", "data-phase": p },
|
|
88
|
+
if (!p) return h("span", { className: "myt-phase", "data-phase": "idea", title: phase || "" }, phase ? String(phase) : tt("phase.unset"));
|
|
89
|
+
return h("span", { className: "myt-phase", "data-phase": p }, tt("phase." + p));
|
|
94
90
|
}
|
|
95
91
|
function priDot(pri) {
|
|
96
92
|
var p = pri === "high" || pri === "low" ? pri : "normal";
|
|
97
|
-
return h("span", { className: "myt-priDot", "data-pri": p, title: "
|
|
93
|
+
return h("span", { className: "myt-priDot", "data-pri": p, title: tf("common.priorityTitle", { pri: tt("pri." + p) }), "aria-label": tf("common.priorityAria", { pri: tt("pri." + p) }) });
|
|
98
94
|
}
|
|
99
95
|
|
|
100
96
|
// ── 格式化小工具 ─────────────────────────────────────────────────────
|
|
@@ -114,7 +110,7 @@ window.__ModuleLoader__.load({
|
|
|
114
110
|
// 章节「卷-章」定位:volume 缺省只显示第 N 章。
|
|
115
111
|
function chWhere(ch) {
|
|
116
112
|
if (!ch) return "";
|
|
117
|
-
var num = typeof ch.num === "number" ? "
|
|
113
|
+
var num = typeof ch.num === "number" ? tf("common.chapterNum", { n: ch.num }) : "";
|
|
118
114
|
return ch.volume ? String(ch.volume) + " · " + num : num;
|
|
119
115
|
}
|
|
120
116
|
function chFullTitle(ch) {
|
|
@@ -178,11 +174,11 @@ window.__ModuleLoader__.load({
|
|
|
178
174
|
var d = null;
|
|
179
175
|
try { d = raw ? JSON.parse(raw) : null; } catch (err) { d = null; }
|
|
180
176
|
if (!r.ok) throw new Error((d && d.error) || ("HTTP " + r.status));
|
|
181
|
-
if (!d) throw new Error("
|
|
177
|
+
if (!d) throw new Error(tf("api.notJson", { code: r.status }));
|
|
182
178
|
return d;
|
|
183
179
|
});
|
|
184
180
|
}).catch(function (err) {
|
|
185
|
-
if (ctrl && err && err.name === "AbortError") throw new Error("
|
|
181
|
+
if (ctrl && err && err.name === "AbortError") throw new Error(tf("api.timeout", { s: API_TIMEOUT_MS / 1000 }));
|
|
186
182
|
throw err;
|
|
187
183
|
}).finally(function () {
|
|
188
184
|
if (timer !== null) clearTimeout(timer);
|
|
@@ -196,7 +192,7 @@ window.__ModuleLoader__.load({
|
|
|
196
192
|
qs = usp.toString();
|
|
197
193
|
}
|
|
198
194
|
return maoyingApi(path + (qs ? (path.indexOf("?") === -1 ? "?" : "&") + qs : "")).catch(function (err) {
|
|
199
|
-
toast.err("
|
|
195
|
+
toast.err(tf("toast.prefix", { msg: err.message }));
|
|
200
196
|
throw err;
|
|
201
197
|
});
|
|
202
198
|
}
|
|
@@ -205,17 +201,17 @@ window.__ModuleLoader__.load({
|
|
|
205
201
|
toast.ok(apiPostOkText(path, d));
|
|
206
202
|
return d;
|
|
207
203
|
}, function (err) {
|
|
208
|
-
toast.err("
|
|
204
|
+
toast.err(tf("toast.prefix", { msg: err.message }));
|
|
209
205
|
throw err;
|
|
210
206
|
});
|
|
211
207
|
}
|
|
212
208
|
// 写操作成功提示按路由分流(server 返回 chapters 计数时带上)。
|
|
213
209
|
function apiPostOkText(path, d) {
|
|
214
|
-
if (path === "rebuild-index") return "
|
|
215
|
-
if (path === "chapter-status") return "
|
|
216
|
-
if (path === "foreshadow-status") return "
|
|
217
|
-
if (path === "phase") return "
|
|
218
|
-
return "
|
|
210
|
+
if (path === "rebuild-index") return tf("toast.indexRebuilt", { n: fmtNum(d && d.chapters) });
|
|
211
|
+
if (path === "chapter-status") return tt("toast.chapterStatus");
|
|
212
|
+
if (path === "foreshadow-status") return tt("toast.foreshadowStatus");
|
|
213
|
+
if (path === "phase") return tf("toast.phaseAdvanced", { phase: d && d.phase && PHASES.indexOf(d.phase) !== -1 ? tt("phase." + d.phase) : (d && d.phase) || "—" });
|
|
214
|
+
return tt("toast.success");
|
|
219
215
|
}
|
|
220
216
|
|
|
221
217
|
// ── Toast 控制器(模块级单例;ok 4s / warn 6s 自动消失,err 手动关)────
|
|
@@ -267,7 +263,7 @@ window.__ModuleLoader__.load({
|
|
|
267
263
|
},
|
|
268
264
|
h("div", { className: "myt-toastText" }, it.text),
|
|
269
265
|
h("button", {
|
|
270
|
-
type: "button", className: "myt-toastClose", "aria-label": "
|
|
266
|
+
type: "button", className: "myt-toastClose", "aria-label": tt("common.closeTip"),
|
|
271
267
|
onClick: function () { toast.dismiss(it.id); },
|
|
272
268
|
}, h("span", { className: "myt-closeGlyph", "aria-hidden": "true" }, "×")));
|
|
273
269
|
}));
|
|
@@ -446,14 +442,14 @@ window.__ModuleLoader__.load({
|
|
|
446
442
|
|
|
447
443
|
// ── 引导卡(无 novel 工作区 / 未初始化):maoying init 提示 + 复制 ──────
|
|
448
444
|
function GuideCard(props) {
|
|
449
|
-
var title = props.title || "
|
|
450
|
-
var desc = props.desc || "
|
|
445
|
+
var title = props.title || tt("guide.defaultTitle");
|
|
446
|
+
var desc = props.desc || tt("guide.defaultDesc");
|
|
451
447
|
var _b = useState(false), busy = _b[0], setBusy = _b[1];
|
|
452
448
|
function doCopy() {
|
|
453
449
|
setBusy(true);
|
|
454
450
|
copyText("maoying init").then(function (ok) {
|
|
455
|
-
if (ok) toast.ok("
|
|
456
|
-
else toast.warn("
|
|
451
|
+
if (ok) toast.ok(tt("guide.copied"));
|
|
452
|
+
else toast.warn(tt("guide.copyFailed"));
|
|
457
453
|
}).finally(function () { setBusy(false); });
|
|
458
454
|
}
|
|
459
455
|
return h("div", { className: "myt-card myt-guide" },
|
|
@@ -461,7 +457,7 @@ window.__ModuleLoader__.load({
|
|
|
461
457
|
h("p", { className: "myt-guideText" }, desc),
|
|
462
458
|
h("div", { className: "myt-guideCode" },
|
|
463
459
|
h("code", { className: "myt-guideCodeText" }, "maoying init"),
|
|
464
|
-
h("button", { type: "button", className: "myt-btn myt-copyBtn", disabled: busy, onClick: doCopy }, busy ? "
|
|
460
|
+
h("button", { type: "button", className: "myt-btn myt-copyBtn", disabled: busy, onClick: doCopy }, busy ? tt("guide.copying") : tt("guide.copyCmd"))));
|
|
465
461
|
}
|
|
466
462
|
|
|
467
463
|
// ── 骨架 / 错误 / 空态小件 ──────────────────────────────────────────────
|
|
@@ -474,9 +470,9 @@ window.__ModuleLoader__.load({
|
|
|
474
470
|
return h("div", { className: "myt-strip myt-stripError" },
|
|
475
471
|
h("span", { className: "myt-stripIcon" }, ic("IconWarningOutlineRegular", 14)),
|
|
476
472
|
h("span", { className: "myt-stripText" }, props.text),
|
|
477
|
-
props.onRetry ? h("button", { type: "button", className: "myt-btn", style: { marginLeft: "auto" }, onClick: props.onRetry }, "
|
|
473
|
+
props.onRetry ? h("button", { type: "button", className: "myt-btn", style: { marginLeft: "auto" }, onClick: props.onRetry }, tt("common.retry")) : null);
|
|
478
474
|
}
|
|
479
|
-
function Empty(props) { return h("div", { className: "myt-empty" }, props.text || "
|
|
475
|
+
function Empty(props) { return h("div", { className: "myt-empty" }, props.text || tt("common.empty")); }
|
|
480
476
|
|
|
481
477
|
// tab 内容错误边界:任何 tab / 阅读页渲染抛错都降级为卡内错误态(错误摘要
|
|
482
478
|
// + 重试按钮),不再把整个面板炸成白屏。错误边界只有 class 组件能做
|
|
@@ -501,12 +497,12 @@ window.__ModuleLoader__.load({
|
|
|
501
497
|
render() {
|
|
502
498
|
var err = this.state.err;
|
|
503
499
|
if (!err) return this.props.children;
|
|
504
|
-
var msg = String((err && err.message) || err || "
|
|
500
|
+
var msg = String((err && err.message) || err || tt("common.unknownErr"));
|
|
505
501
|
return h("div", { className: "myt-card" },
|
|
506
|
-
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, "
|
|
507
|
-
h("div", { className: "myt-muted", style: { overflowWrap: "anywhere" } }, "
|
|
502
|
+
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, tt("errBoundary.title"))),
|
|
503
|
+
h("div", { className: "myt-muted", style: { overflowWrap: "anywhere" } }, tf("errBoundary.summary", { msg: truncate(msg, 300) })),
|
|
508
504
|
h("div", { className: "myt-heroMeta" },
|
|
509
|
-
h("button", { type: "button", className: "myt-btn", onClick: this.retry }, "
|
|
505
|
+
h("button", { type: "button", className: "myt-btn", onClick: this.retry }, tt("common.retry"))));
|
|
510
506
|
}
|
|
511
507
|
}
|
|
512
508
|
|
|
@@ -526,7 +522,18 @@ window.__ModuleLoader__.load({
|
|
|
526
522
|
var _rd = useState(null), readerFile = _rd[0], setReaderFile = _rd[1];
|
|
527
523
|
// 系统设置:字号档位持久化 + 面板整体缩放(portal 节点读同一 CSS 变量)。
|
|
528
524
|
var _sm = useState(false), showSettings = _sm[0], setShowSettings = _sm[1];
|
|
529
|
-
var _fs = useState(function () {
|
|
525
|
+
var _fs = useState(function () {
|
|
526
|
+
var stored = lsGet(FONT_SCALE_KEY) || "1";
|
|
527
|
+
return FONT_SCALES.some(function (o) { return o[0] === stored; }) ? stored : "1"; // 旧档位(1.8/2)回落 100%
|
|
528
|
+
}), fontScale = _fs[0], setFontScale = _fs[1];
|
|
529
|
+
var _lg = useState(function () {
|
|
530
|
+
var stored = lsGet(LANG_KEY) || "zh";
|
|
531
|
+
return UI_LANGS.some(function (o) { return o[0] === stored; }) ? stored : "zh";
|
|
532
|
+
}), lang = _lg[0], setLang = _lg[1];
|
|
533
|
+
useEffect(function () {
|
|
534
|
+
UI_LANG = lang;
|
|
535
|
+
lsSet(LANG_KEY, lang);
|
|
536
|
+
}, [lang]);
|
|
530
537
|
useEffect(function () {
|
|
531
538
|
if (typeof document !== "undefined" && document.documentElement) {
|
|
532
539
|
document.documentElement.style.setProperty("--myt-zoom", fontScale);
|
|
@@ -618,19 +625,19 @@ window.__ModuleLoader__.load({
|
|
|
618
625
|
} else if (!effectiveWs) {
|
|
619
626
|
// 全无 novel 工作区:引导卡(maoying init + 复制)。
|
|
620
627
|
body = h("div", { className: "myt-stack" },
|
|
621
|
-
wsErr ? ErrorStrip({ text: "
|
|
628
|
+
wsErr ? ErrorStrip({ text: tf("shell.wsListFailed", { msg: wsErr }), onRetry: loadWorkspaces }) : null,
|
|
622
629
|
h(GuideCard, {
|
|
623
|
-
title: "
|
|
624
|
-
desc: "
|
|
630
|
+
title: tt("guide.noWsTitle"),
|
|
631
|
+
desc: tt("guide.noWsDesc"),
|
|
625
632
|
}));
|
|
626
633
|
} else if (loading && !snap) {
|
|
627
634
|
body = h(SkelPage);
|
|
628
635
|
} else if (stateErr) {
|
|
629
|
-
body = h(ErrorStrip, { text: "
|
|
636
|
+
body = h(ErrorStrip, { text: tf("shell.stateFailed", { msg: stateErr }), onRetry: reload });
|
|
630
637
|
} else if (snap && snap.found === false) {
|
|
631
638
|
body = h(GuideCard, {
|
|
632
|
-
title: "
|
|
633
|
-
desc: "
|
|
639
|
+
title: tt("guide.notInitTitle"),
|
|
640
|
+
desc: tt("guide.notInitDesc"),
|
|
634
641
|
});
|
|
635
642
|
} else if (snap) {
|
|
636
643
|
var tabProps = { snap: snap, reload: reload, wsPath: effectiveWs };
|
|
@@ -650,37 +657,37 @@ window.__ModuleLoader__.load({
|
|
|
650
657
|
h("header", { className: "myt-header" },
|
|
651
658
|
h("div", null,
|
|
652
659
|
h("h1", { className: "myt-title" }, "毛颖创作台",
|
|
653
|
-
ver && ver.current ? h("span", { className: "myt-verTag", title: "
|
|
660
|
+
ver && ver.current ? h("span", { className: "myt-verTag", title: tt("header.curVerTitle") }, ver.current) : null,
|
|
654
661
|
ver && ver.updateAvailable
|
|
655
|
-
? h("a", { className: "myt-verBadge", href: ver.releaseUrl || "#", target: "_blank", rel: "noreferrer", title: "
|
|
656
|
-
"
|
|
662
|
+
? h("a", { className: "myt-verBadge", href: ver.releaseUrl || "#", target: "_blank", rel: "noreferrer", title: tf("header.upgradeTip", { cur: ver.current }) },
|
|
663
|
+
tf("header.newVersion", { ver: ver.latest }))
|
|
657
664
|
: null),
|
|
658
|
-
h("p", { className: "myt-subtitle" }, "
|
|
665
|
+
h("p", { className: "myt-subtitle" }, tt("header.subtitle"))),
|
|
659
666
|
h("div", { className: "myt-headActions" },
|
|
660
667
|
workspaces && novelWorkspaces.length
|
|
661
|
-
? [h("span", { className: "myt-wsLabel", key: "l" }, "
|
|
662
|
-
h("select", { className: "myt-wsSelect", key: "s", value: effectiveWs, onChange: chooseWs, "aria-label": "
|
|
668
|
+
? [h("span", { className: "myt-wsLabel", key: "l" }, tt("header.workspace")),
|
|
669
|
+
h("select", { className: "myt-wsSelect", key: "s", value: effectiveWs, onChange: chooseWs, "aria-label": tt("header.wsAria") },
|
|
663
670
|
workspaces.map(function (w, i) {
|
|
664
671
|
return h("option", { key: w.path || i, value: w.path || "", disabled: !w.hasNovel },
|
|
665
|
-
(w.hasNovel ? "" : "
|
|
672
|
+
(w.hasNovel ? "" : tt("header.noNovel")) + (w.name || w.path || tt("header.untitled")));
|
|
666
673
|
}))]
|
|
667
674
|
: null,
|
|
668
|
-
h("button", { type: "button", className: "myt-btn myt-btnIcon", title: "
|
|
669
|
-
h("button", { type: "button", className: "myt-btn myt-btnIcon", title: "
|
|
670
|
-
h("button", { type: "button", className: "myt-closeBtn", "aria-label": "
|
|
671
|
-
readerFile ? null : h("nav", { className: "myt-tabs", "aria-label": "
|
|
675
|
+
h("button", { type: "button", className: "myt-btn myt-btnIcon", title: tt("header.settings"), "aria-label": tt("header.settings"), onClick: function () { setShowSettings(true); } }, ic("IconSettingsOutlineRegular", 14)),
|
|
676
|
+
h("button", { type: "button", className: "myt-btn myt-btnIcon", title: tt("header.refresh"), "aria-label": tt("header.refresh"), disabled: loading, onClick: reload }, ic("IconRefreshOutlineRegular", 14)),
|
|
677
|
+
h("button", { type: "button", className: "myt-closeBtn", "aria-label": tt("header.closeAria"), title: tt("header.closeTitle"), onClick: onClose }, h("span", { className: "myt-closeGlyph", "aria-hidden": "true" }, "×")))),
|
|
678
|
+
readerFile ? null : h("nav", { className: "myt-tabs", "aria-label": tt("tabs.aria") },
|
|
672
679
|
TABS.map(function (t) {
|
|
673
680
|
return h("button", {
|
|
674
681
|
key: t[0], type: "button", className: "myt-tab", "data-active": tab === t[0] ? "true" : undefined,
|
|
675
682
|
onClick: function () { gotoTab(t[0]); },
|
|
676
|
-
}, t[1]);
|
|
683
|
+
}, tt(t[1]));
|
|
677
684
|
})),
|
|
678
685
|
h("div", { className: "myt-body" },
|
|
679
686
|
h("div", { className: "myt-page" },
|
|
680
687
|
snap && Array.isArray(snap.warnings) && snap.warnings.length ? WarningsStrip(snap.warnings) : null,
|
|
681
688
|
// 错误边界包住 tab 内容:key 随 tab / 阅读页变化重挂,切分区自动清错误态。
|
|
682
689
|
h(TabErrorBoundary, { key: readerFile ? "reader:" + readerFile : tab, onRetry: reload }, body))),
|
|
683
|
-
showSettings ? h(SettingsModal, { scale: fontScale, onPick: setFontScale, onClose: function () { setShowSettings(false); } }) : null,
|
|
690
|
+
showSettings ? h(SettingsModal, { scale: fontScale, onPick: setFontScale, lang: lang, onPickLang: setLang, onClose: function () { setShowSettings(false); } }) : null,
|
|
684
691
|
h(ToastHost));
|
|
685
692
|
}
|
|
686
693
|
|
|
@@ -689,7 +696,7 @@ window.__ModuleLoader__.load({
|
|
|
689
696
|
return h("div", { className: "myt-strip myt-stripWarn" },
|
|
690
697
|
h("span", { className: "myt-stripIcon" }, ic("IconWarningOutlineRegular", 14)),
|
|
691
698
|
h("span", { className: "myt-stripText" },
|
|
692
|
-
h("div", null, h("strong", null, "
|
|
699
|
+
h("div", null, h("strong", null, tt("warnings.title"))),
|
|
693
700
|
warnings.map(function (w, i) { return h("div", { key: i }, "· " + String(w)); })));
|
|
694
701
|
}
|
|
695
702
|
|
|
@@ -713,44 +720,44 @@ window.__ModuleLoader__.load({
|
|
|
713
720
|
return h("div", { className: "myt-stack" },
|
|
714
721
|
h("div", { className: "myt-card myt-hero" },
|
|
715
722
|
h("div", { className: "myt-heroMeta" },
|
|
716
|
-
project.author ? h("span", { className: "myt-muted" }, "
|
|
723
|
+
project.author ? h("span", { className: "myt-muted" }, tf("overview.author", { name: project.author })) : null,
|
|
717
724
|
project.genre ? h("span", { className: "myt-badge" }, project.genre) : null,
|
|
718
725
|
project.length ? h("span", { className: "myt-badge" }, project.length) : null,
|
|
719
726
|
project.language ? h("span", { className: "myt-badge" }, project.language) : null),
|
|
720
727
|
h("h2", { className: "myt-heroTitle" }, orDash(project.title)),
|
|
721
728
|
h("div", { className: "myt-heroMeta" },
|
|
722
729
|
phasePill(project.phase),
|
|
723
|
-
nextPhase ? h("button", { type: "button", className: "myt-btn", disabled: phaseBusy, onClick: advancePhase, title: "
|
|
730
|
+
nextPhase ? h("button", { type: "button", className: "myt-btn", disabled: phaseBusy, onClick: advancePhase, title: tf("phase.advanceTitle", { next: tt("phase." + nextPhase) }) }, phaseBusy ? tt("phase.advancing") : tt("phase.advance")) : h("span", { className: "myt-muted" }, project.phase ? tt("phase.final") : null)),
|
|
724
731
|
project.description ? h("p", { className: "myt-heroDesc" }, project.description) : null),
|
|
725
732
|
h("div", { className: "myt-statRow" },
|
|
726
|
-
h("div", { className: "myt-card myt-stat" }, h("span", { className: "myt-statNum" }, fmtNum(totals.words)), h("span", { className: "myt-statLabel" }, "
|
|
727
|
-
h("div", { className: "myt-card myt-stat" }, h("span", { className: "myt-statNum" }, fmtNum(totals.chapters)), h("span", { className: "myt-statLabel" }, "
|
|
733
|
+
h("div", { className: "myt-card myt-stat" }, h("span", { className: "myt-statNum" }, fmtNum(totals.words)), h("span", { className: "myt-statLabel" }, tt("overview.totalWords"))),
|
|
734
|
+
h("div", { className: "myt-card myt-stat" }, h("span", { className: "myt-statNum" }, fmtNum(totals.chapters)), h("span", { className: "myt-statLabel" }, tt("overview.totalChapters"))),
|
|
728
735
|
h("div", { className: "myt-card myt-stat" },
|
|
729
736
|
h("span", { className: "myt-statNum" }, fmtNum((snap.characters || []).length)),
|
|
730
|
-
h("span", { className: "myt-statLabel" }, "
|
|
737
|
+
h("span", { className: "myt-statLabel" }, tt("overview.characters"))),
|
|
731
738
|
h("div", { className: "myt-card myt-stat" },
|
|
732
739
|
h("span", { className: "myt-statNum" }, fmtNum((snap.foreshadows || []).filter(function (f) { return f.status === "planted" || f.status === "progressing"; }).length)),
|
|
733
|
-
h("span", { className: "myt-statLabel" }, "
|
|
740
|
+
h("span", { className: "myt-statLabel" }, tt("overview.openForeshadows")))),
|
|
734
741
|
h("div", { className: "myt-card" },
|
|
735
|
-
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, "
|
|
742
|
+
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, tt("overview.statusDist"))),
|
|
736
743
|
h("div", { className: "myt-distBar", "aria-hidden": "true" }, STATUS_FLOW.map(function (s) {
|
|
737
744
|
var n = byStatus[s] || 0;
|
|
738
745
|
return n > 0 ? h("div", { key: s, className: "myt-distSeg", "data-status": s, style: { flexGrow: n } }) : null;
|
|
739
746
|
})),
|
|
740
747
|
h("div", { className: "myt-heroMeta" }, STATUS_FLOW.map(function (s) {
|
|
741
|
-
return h("span", { key: s, className: "myt-muted" },
|
|
748
|
+
return h("span", { key: s, className: "myt-muted" }, tt("status." + s) + " " + (byStatus[s] || 0));
|
|
742
749
|
}))),
|
|
743
750
|
h("div", { className: "myt-card" },
|
|
744
|
-
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, "
|
|
751
|
+
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, tt("overview.recent"))),
|
|
745
752
|
recent.length
|
|
746
753
|
? h("div", { className: "myt-recentList" }, recent.map(function (ch) {
|
|
747
754
|
return h("button", { key: ch.file, type: "button", className: "myt-recentRow", onClick: function () { gotoTab("chapters", ch.file); } },
|
|
748
755
|
statusBadge(ch.status),
|
|
749
756
|
h("span", { className: "myt-chTitle" }, orDash(ch.title)),
|
|
750
757
|
h("span", { className: "myt-chPath" }, chWhere(ch)),
|
|
751
|
-
h("span", { className: "myt-wordsCell" }, fmtNum(ch.words)
|
|
758
|
+
h("span", { className: "myt-wordsCell" }, tf("common.charCount", { n: fmtNum(ch.words) })));
|
|
752
759
|
}))
|
|
753
|
-
: h(Empty, { text: "chapters
|
|
760
|
+
: h(Empty, { text: tt("chapters.empty") })));
|
|
754
761
|
}
|
|
755
762
|
|
|
756
763
|
// ── 轻量 Markdown 渲染(零依赖):先整体 HTML 转义再施加 md 变换,杜绝
|
|
@@ -857,28 +864,204 @@ window.__ModuleLoader__.load({
|
|
|
857
864
|
|
|
858
865
|
// ── 系统设置:字号五档(面板整体 zoom,抽屉/弹层 portal 节点同缩放)──────
|
|
859
866
|
var FONT_SCALE_KEY = "maoying.fontScale";
|
|
860
|
-
var FONT_SCALES = [["1", "
|
|
867
|
+
var FONT_SCALES = [["1", "100%"], ["1.1", "110%"], ["1.2", "120%"], ["1.3", "130%"], ["1.4", "140%"], ["1.5", "150%"]];
|
|
868
|
+
// ── 界面语言(系统设置可选中文/English):UI_LANG 为模块级当前语言,
|
|
869
|
+
// PageShell 在 lang state 变化时改写它并整树重渲。tt/tf 是唯一取词口:
|
|
870
|
+
// zh 词典的值必须与历史硬编码文案逐字一致(中文界面零回归),en 词典
|
|
871
|
+
// 不留中文;词典缺键回落 key 本身(便于渐进迁移,一眼看出漏网串)。 ──
|
|
872
|
+
var LANG_KEY = "maoying.lang";
|
|
873
|
+
var UI_LANGS = [["zh", "中文"], ["en", "English"]];
|
|
874
|
+
var UI_LANG = "zh";
|
|
875
|
+
var I18N = {
|
|
876
|
+
zh: {
|
|
877
|
+
"shell.title": "毛颖创作台", "header.subtitle": "网文项目可视化与状态流转 · 只读正文,写状态",
|
|
878
|
+
"header.curVerTitle": "当前版本", "header.upgradeTip": "当前 {cur},点击打开新版发布页", "header.newVersion": "有新版 {ver} ↗",
|
|
879
|
+
"header.workspace": "工作区", "header.wsAria": "选择工作区", "header.noNovel": "(无 novel)", "header.untitled": "未命名",
|
|
880
|
+
"header.settings": "系统设置", "header.refresh": "重新拉取", "header.closeAria": "关闭创作台", "header.closeTitle": "关闭",
|
|
881
|
+
"tabs.aria": "创作台分区",
|
|
882
|
+
"tabs.overview": "概览", "tabs.chapters": "章节", "tabs.characters": "角色", "tabs.foreshadows": "伏笔", "tabs.relations": "关系图",
|
|
883
|
+
"tabs.timeline": "时间线", "tabs.world": "设定", "tabs.index": "索引板", "tabs.settings": "小说设置",
|
|
884
|
+
"shell.wsListFailed": "工作区列表拉取失败:{msg}", "shell.stateFailed": "快照拉取失败:{msg}",
|
|
885
|
+
"guide.noWsTitle": "还没有可用的小说工作区", "guide.noWsDesc": "任一工作区根目录下出现 novel/project.yml 即自动入选;先用命令初始化目录骨架:",
|
|
886
|
+
"guide.notInitTitle": "该工作区尚未初始化小说项目", "guide.notInitDesc": "工作区根目录下没有 novel/project.yml。在该工作区根目录执行:",
|
|
887
|
+
"guide.defaultTitle": "该工作区还没有小说项目", "guide.defaultDesc": "在下面给出的命令里初始化目录骨架(或把已有 novel 目录放进工作区根),然后刷新本面板。",
|
|
888
|
+
"guide.copied": "已复制 maoying init", "guide.copyFailed": "复制失败,请手动选择命令文本", "guide.copying": "复制中…", "guide.copyCmd": "复制命令",
|
|
889
|
+
"common.retry": "重试", "common.empty": "暂无数据", "common.uncategorized": "未分类", "common.charCount": "{n} 字",
|
|
890
|
+
"common.closeTip": "关闭提示", "common.unknownErr": "未知错误", "common.priorityTitle": "优先级:{pri}", "common.priorityAria": "优先级{pri}",
|
|
891
|
+
"common.chapterNum": "第 {n} 章", "pri.high": "高", "pri.normal": "中", "pri.low": "低",
|
|
892
|
+
"toast.prefix": "毛颖创作台:{msg}", "toast.success": "操作成功", "toast.indexRebuilt": "索引已重建({n} 章)",
|
|
893
|
+
"toast.chapterStatus": "章节状态已更新", "toast.foreshadowStatus": "伏笔状态已更新", "toast.phaseAdvanced": "阶段已推进到「{phase}」",
|
|
894
|
+
"api.notJson": "响应不是 JSON(HTTP {code})", "api.timeout": "请求超时({s} 秒)",
|
|
895
|
+
"status.pending": "待写", "status.writing": "写作中", "status.review": "审校中", "status.accepted": "已定稿", "status.rawSuffix": "({raw})",
|
|
896
|
+
"phase.idea": "立意", "phase.setting": "设定", "phase.outline": "大纲", "phase.writing": "写作", "phase.review": "审校",
|
|
897
|
+
"phase.consistency": "一致性", "phase.export": "导出", "phase.unset": "未设阶段",
|
|
898
|
+
"phase.advanceTitle": "推进到「{next}」", "phase.advance": "推进阶段", "phase.advancing": "推进中…", "phase.final": "已是最终阶段",
|
|
899
|
+
"foreshadow.planted": "埋设", "foreshadow.progressing": "推进中", "foreshadow.resolved": "已回收", "foreshadow.abandoned": "已弃用",
|
|
900
|
+
"foreshadow.advance": "推进", "foreshadow.resolve": "回收", "foreshadow.abandon": "弃用", "foreshadow.advancing": "推进中…",
|
|
901
|
+
"foreshadow.plantedUnmarked": "埋设章未标", "foreshadow.resolvedAt": "回收于 {ch}", "foreshadow.priorityLabel": "优先级 {pri}",
|
|
902
|
+
"foreshadow.backfill": "回填", "foreshadow.backfillTip": "从作者正文回填", "foreshadow.missingStatus": "缺 status",
|
|
903
|
+
"foreshadow.noIdTip": "该条目缺 id,无法写状态",
|
|
904
|
+
"foreshadow.noStatusTip": "该条目缺 status 行,服务端会拒绝流转(409);请先在 foreshadows.yml 补上 status",
|
|
905
|
+
"foreshadow.empty": "foreshadows.yml 还没有伏笔条目", "foreshadow.colEmpty": "空",
|
|
906
|
+
"clue.clue": "线索", "clue.redHerring": "红鲱鱼", "clue.inference": "推理", "clue.question": "疑问",
|
|
907
|
+
"sci.known": "已确证", "sci.extrapolation": "推演", "sci.hypothesis": "假说", "world.unlabeled": "未标级",
|
|
908
|
+
"warnings.title": "部分文件解析异常(已跳过):",
|
|
909
|
+
"index.title": "索引板(clue-board / memories)", "index.rebuild": "重建索引", "index.rebuilding": "重建中…",
|
|
910
|
+
"index.rebuildTip": "按 CLI index 同一逻辑重建 index.yml", "index.clueBoard": "线索板", "index.clueEmpty": "clue-board.yml 还没有条目",
|
|
911
|
+
"index.memBoard": "记忆板", "index.memEmpty": "memories.yml 还没有条目", "index.promise": "承诺线",
|
|
912
|
+
"timeline.empty": "timeline.yml 还没有时间线条目", "timeline.importance": "重要度 {n}", "timeline.linkedChapter": "关联章节:{ch}",
|
|
913
|
+
"world.title": "世界观条目", "world.empty": "world/ 目录还没有设定条目", "world.science": "科学设定",
|
|
914
|
+
"world.scienceEmpty": "science/ 目录还没有科学设定", "world.research": "资料检索", "world.ref": "参考 {n}:{r}",
|
|
915
|
+
"relations.title": "角色关系图", "relations.counts": "{n} 节点 · {m} 关系",
|
|
916
|
+
"relations.help": "拖节点固定位置 · 滚轮缩放 · 双击复位 · 悬停边看关系描述",
|
|
917
|
+
"relations.unlinkedTitle": "未入图角色(无关系边)", "relations.noData": "没有角色或关系数据,无法绘图",
|
|
918
|
+
"relations.edgeTipPrefix": "{type}:", "relations.noDesc": "(无描述)",
|
|
919
|
+
"char.empty": "characters/ 目录还没有角色卡",
|
|
920
|
+
"char.field.age": "年龄", "char.field.gender": "性别", "char.field.appearance": "外貌", "char.field.personality": "性格",
|
|
921
|
+
"char.field.background": "背景", "char.field.motivation": "动机", "char.field.arc": "成长弧",
|
|
922
|
+
"char.years": "{n} 岁", "char.linePersonality": "性格 ", "char.lineMotivation": "动机 ",
|
|
923
|
+
"char.modalAria": "角色详情", "char.closeAria": "关闭角色卡", "char.markers": "标记", "char.file": "文件",
|
|
924
|
+
"overview.author": "作者:{name}", "overview.totalWords": "总字数(CJK)", "overview.totalChapters": "章节总数",
|
|
925
|
+
"overview.characters": "角色数", "overview.openForeshadows": "未回收伏笔", "overview.statusDist": "章节状态分布", "overview.recent": "最近章节",
|
|
926
|
+
"chapters.empty": "chapters/ 目录还没有章节文件", "chapters.writeQueue": "按单写作", "chapters.rowTip": "点击打开该章",
|
|
927
|
+
"chapters.outlineWords": "章纲 {n} 字", "chapters.all": "全部", "chapters.thVolume": "卷 · 章", "chapters.thTitle": "标题",
|
|
928
|
+
"chapters.thStatus": "状态", "chapters.thWords": "字数", "chapters.thSummary": "梗概", "chapters.rowReadTip": "点击阅读正文",
|
|
929
|
+
"chapters.emptyFiltered": "该状态下没有章节",
|
|
930
|
+
"reader.back": "返回", "reader.backTip": "返回章节列表", "reader.copyPath": "复制路径", "reader.copyPathTip": "复制章节文件路径",
|
|
931
|
+
"reader.copied": "已复制章节文件路径", "reader.copyFailed": "复制失败,请手动选择路径文本", "reader.loadFailed": "正文读取失败:{msg}",
|
|
932
|
+
"reader.noOutline": "暂无章纲", "reader.emptyBody": "(空章节)", "reader.finalState": "已定稿(终态)", "reader.statusFlow": "状态流转:",
|
|
933
|
+
"reader.flowing": "流转中…", "reader.advanceTo": "推进到「{next}」",
|
|
934
|
+
"outline.title": "章纲对照", "outline.empty": "(章纲为空)", "outline.collapseTip": "收起章纲", "outline.expandTip": "展开章纲",
|
|
935
|
+
"novel.metaTitle": "书稿元信息", "novel.metaNote": "对应 novel/project.yml · 行尾注释会原样保留",
|
|
936
|
+
"novel.fTitle": "书名", "novel.titlePh": "如:长夜余火", "novel.fAuthor": "作者", "novel.authorPh": "笔名",
|
|
937
|
+
"novel.fGenre": "题材", "novel.genrePh": "如:东方玄幻 / 都市异能 / 历史架空", "novel.fLength": "预期篇幅",
|
|
938
|
+
"novel.lengthPh": "如:约100万字 / 300章", "novel.fLanguage": "正文语言", "novel.fPhase": "工作流阶段", "novel.fMode": "共创模式",
|
|
939
|
+
"novel.fDesc": "一句话简介", "novel.descPh": "写封面文案时可直接引用", "novel.titleRequired": "书名不能为空",
|
|
940
|
+
"mode.aiDraft": "AI 代笔(AI 写正文)", "mode.coWrite": "逐章共创(按章指派谁写)", "mode.authorDraft": "作者亲笔(AI 只备料审稿)",
|
|
941
|
+
"novel.saving": "保存中…", "novel.save": "保存设置",
|
|
942
|
+
"settings.title": "系统设置", "settings.closeAria": "关闭设置", "settings.language": "语言",
|
|
943
|
+
"settings.languageHint": "界面文字随语言选项切换,立即生效并记忆。", "settings.fontScale": "字号",
|
|
944
|
+
"settings.fontScaleHint": "按当前字号的百分比缩放整个创作台,立即生效并记忆。",
|
|
945
|
+
"errBoundary.title": "该分区渲染出错", "errBoundary.summary": "错误摘要:{msg}",
|
|
946
|
+
"dim.cognitiveFrame": "认知框架", "dim.emotionalAnchor": "情感锚点", "dim.worldTexture": "世界质感",
|
|
947
|
+
"dim.suspense": "悬念", "dim.interpersonalTension": "人际张力",
|
|
948
|
+
},
|
|
949
|
+
en: {
|
|
950
|
+
"shell.title": "Maoying Studio", "header.subtitle": "Web-novel project dashboard & status flow · read-only text, writable status",
|
|
951
|
+
"header.curVerTitle": "Current version", "header.upgradeTip": "Current {cur} — click to open the release page", "header.newVersion": "New version {ver} ↗",
|
|
952
|
+
"header.workspace": "Workspace", "header.wsAria": "Select workspace", "header.noNovel": "(no novel)", "header.untitled": "Untitled",
|
|
953
|
+
"header.settings": "Settings", "header.refresh": "Refresh", "header.closeAria": "Close Maoying Studio", "header.closeTitle": "Close",
|
|
954
|
+
"tabs.aria": "Studio sections",
|
|
955
|
+
"tabs.overview": "Overview", "tabs.chapters": "Chapters", "tabs.characters": "Characters", "tabs.foreshadows": "Foreshadowing", "tabs.relations": "Relations",
|
|
956
|
+
"tabs.timeline": "Timeline", "tabs.world": "Worldbuilding", "tabs.index": "Index Board", "tabs.settings": "Novel Settings",
|
|
957
|
+
"shell.wsListFailed": "Failed to load workspace list: {msg}", "shell.stateFailed": "Failed to load snapshot: {msg}",
|
|
958
|
+
"guide.noWsTitle": "No novel workspace available yet", "guide.noWsDesc": "A workspace is picked up automatically once novel/project.yml exists at its root; initialize the directory scaffold with this command first:",
|
|
959
|
+
"guide.notInitTitle": "No novel project in this workspace yet", "guide.notInitDesc": "novel/project.yml is missing at the workspace root. Run this at the workspace root:",
|
|
960
|
+
"guide.defaultTitle": "No novel project in this workspace yet", "guide.defaultDesc": "Initialize the directory scaffold with the command below (or drop an existing novel/ directory into the workspace root), then refresh this panel.",
|
|
961
|
+
"guide.copied": "Copied maoying init", "guide.copyFailed": "Copy failed — select the command text manually", "guide.copying": "Copying…", "guide.copyCmd": "Copy command",
|
|
962
|
+
"common.retry": "Retry", "common.empty": "No data", "common.uncategorized": "Uncategorized", "common.charCount": "{n} chars",
|
|
963
|
+
"common.closeTip": "Dismiss notification", "common.unknownErr": "Unknown error", "common.priorityTitle": "Priority: {pri}", "common.priorityAria": "Priority {pri}",
|
|
964
|
+
"common.chapterNum": "Ch. {n}", "pri.high": "High", "pri.normal": "Normal", "pri.low": "Low",
|
|
965
|
+
"toast.prefix": "Maoying Studio: {msg}", "toast.success": "Success", "toast.indexRebuilt": "Index rebuilt ({n} chapters)",
|
|
966
|
+
"toast.chapterStatus": "Chapter status updated", "toast.foreshadowStatus": "Foreshadowing status updated", "toast.phaseAdvanced": "Phase advanced to {phase}",
|
|
967
|
+
"api.notJson": "Response is not JSON (HTTP {code})", "api.timeout": "Request timed out ({s} s)",
|
|
968
|
+
"status.pending": "Pending", "status.writing": "Writing", "status.review": "Review", "status.accepted": "Accepted", "status.rawSuffix": "({raw})",
|
|
969
|
+
"phase.idea": "Idea", "phase.setting": "Setting", "phase.outline": "Outline", "phase.writing": "Writing", "phase.review": "Review",
|
|
970
|
+
"phase.consistency": "Consistency", "phase.export": "Export", "phase.unset": "No phase",
|
|
971
|
+
"phase.advanceTitle": "Advance to {next}", "phase.advance": "Advance phase", "phase.advancing": "Advancing…", "phase.final": "Final phase",
|
|
972
|
+
"foreshadow.planted": "Planted", "foreshadow.progressing": "In Progress", "foreshadow.resolved": "Resolved", "foreshadow.abandoned": "Abandoned",
|
|
973
|
+
"foreshadow.advance": "Advance", "foreshadow.resolve": "Resolve", "foreshadow.abandon": "Abandon", "foreshadow.advancing": "Advancing…",
|
|
974
|
+
"foreshadow.plantedUnmarked": "Planting chapter unmarked", "foreshadow.resolvedAt": "Resolved in {ch}", "foreshadow.priorityLabel": "Priority {pri}",
|
|
975
|
+
"foreshadow.backfill": "Backfill", "foreshadow.backfillTip": "Backfilled from the author's manuscript", "foreshadow.missingStatus": "missing status",
|
|
976
|
+
"foreshadow.noIdTip": "This entry has no id — its status cannot be written",
|
|
977
|
+
"foreshadow.noStatusTip": "This entry has no status line, the server will reject the transition (409); add a status in foreshadows.yml first",
|
|
978
|
+
"foreshadow.empty": "No entries in foreshadows.yml yet", "foreshadow.colEmpty": "Empty",
|
|
979
|
+
"clue.clue": "Clue", "clue.redHerring": "Red herring", "clue.inference": "Inference", "clue.question": "Question",
|
|
980
|
+
"sci.known": "Confirmed", "sci.extrapolation": "Extrapolation", "sci.hypothesis": "Hypothesis", "world.unlabeled": "Unrated",
|
|
981
|
+
"warnings.title": "Some files failed to parse (skipped):",
|
|
982
|
+
"index.title": "Index Board (clue-board / memories)", "index.rebuild": "Rebuild index", "index.rebuilding": "Rebuilding…",
|
|
983
|
+
"index.rebuildTip": "Rebuild index.yml with the same logic as the CLI index command", "index.clueBoard": "Clue board", "index.clueEmpty": "No entries in clue-board.yml yet",
|
|
984
|
+
"index.memBoard": "Memory board", "index.memEmpty": "No entries in memories.yml yet", "index.promise": "Promise thread",
|
|
985
|
+
"timeline.empty": "No entries in timeline.yml yet", "timeline.importance": "Importance {n}", "timeline.linkedChapter": "Related chapter: {ch}",
|
|
986
|
+
"world.title": "Worldbuilding entries", "world.empty": "No worldbuilding entries in world/ yet", "world.science": "Science settings",
|
|
987
|
+
"world.scienceEmpty": "No science entries in science/ yet", "world.research": "Research", "world.ref": "Ref {n}: {r}",
|
|
988
|
+
"relations.title": "Character relations", "relations.counts": "{n} nodes · {m} relations",
|
|
989
|
+
"relations.help": "Drag a node to pin it · scroll to zoom · double-click to reset · hover an edge for its description",
|
|
990
|
+
"relations.unlinkedTitle": "Unlinked characters (no relation edges)", "relations.noData": "No character or relation data to draw",
|
|
991
|
+
"relations.edgeTipPrefix": "{type}: ", "relations.noDesc": "(no description)",
|
|
992
|
+
"char.empty": "No character cards in characters/ yet",
|
|
993
|
+
"char.field.age": "Age", "char.field.gender": "Gender", "char.field.appearance": "Appearance", "char.field.personality": "Personality",
|
|
994
|
+
"char.field.background": "Background", "char.field.motivation": "Motivation", "char.field.arc": "Character arc",
|
|
995
|
+
"char.years": "Age {n}", "char.linePersonality": "Personality ", "char.lineMotivation": "Motivation ",
|
|
996
|
+
"char.modalAria": "Character details", "char.closeAria": "Close character card", "char.markers": "Tags", "char.file": "File",
|
|
997
|
+
"overview.author": "Author: {name}", "overview.totalWords": "Total characters (CJK)", "overview.totalChapters": "Chapters",
|
|
998
|
+
"overview.characters": "Characters", "overview.openForeshadows": "Unresolved foreshadowing", "overview.statusDist": "Chapter status breakdown", "overview.recent": "Recent chapters",
|
|
999
|
+
"chapters.empty": "No chapter files in chapters/ yet", "chapters.writeQueue": "Writing Queue", "chapters.rowTip": "Click to open this chapter",
|
|
1000
|
+
"chapters.outlineWords": "Outline {n} chars", "chapters.all": "All", "chapters.thVolume": "Vol · Ch", "chapters.thTitle": "Title",
|
|
1001
|
+
"chapters.thStatus": "Status", "chapters.thWords": "Words", "chapters.thSummary": "Summary", "chapters.rowReadTip": "Click to read",
|
|
1002
|
+
"chapters.emptyFiltered": "No chapters with this status",
|
|
1003
|
+
"reader.back": "Back", "reader.backTip": "Back to chapter list", "reader.copyPath": "Copy path", "reader.copyPathTip": "Copy chapter file path",
|
|
1004
|
+
"reader.copied": "Copied chapter file path", "reader.copyFailed": "Copy failed — select the path text manually", "reader.loadFailed": "Failed to load chapter text: {msg}",
|
|
1005
|
+
"reader.noOutline": "No chapter outline", "reader.emptyBody": "(empty chapter)", "reader.finalState": "Accepted (final)", "reader.statusFlow": "Status:",
|
|
1006
|
+
"reader.flowing": "Transitioning…", "reader.advanceTo": "Advance to {next}",
|
|
1007
|
+
"outline.title": "Outline", "outline.empty": "(outline is empty)", "outline.collapseTip": "Collapse outline", "outline.expandTip": "Expand outline",
|
|
1008
|
+
"novel.metaTitle": "Manuscript metadata", "novel.metaNote": "Maps to novel/project.yml · trailing comments are preserved",
|
|
1009
|
+
"novel.fTitle": "Title", "novel.titlePh": "e.g. The Long Night", "novel.fAuthor": "Author", "novel.authorPh": "Pen name",
|
|
1010
|
+
"novel.fGenre": "Genre", "novel.genrePh": "e.g. Eastern fantasy / Urban powers / Alternate history", "novel.fLength": "Planned length",
|
|
1011
|
+
"novel.lengthPh": "e.g. ~1M characters / 300 chapters", "novel.fLanguage": "Text language", "novel.fPhase": "Workflow phase", "novel.fMode": "Co-writing mode",
|
|
1012
|
+
"novel.fDesc": "One-line synopsis", "novel.descPh": "Can be quoted directly in cover copy", "novel.titleRequired": "Title is required",
|
|
1013
|
+
"mode.aiDraft": "AI ghostwriting (AI drafts the prose)", "mode.coWrite": "Chapter-by-chapter co-writing (assign a writer per chapter)", "mode.authorDraft": "Author writes (AI only preps and reviews)",
|
|
1014
|
+
"novel.saving": "Saving…", "novel.save": "Save",
|
|
1015
|
+
"settings.title": "Settings", "settings.closeAria": "Close settings", "settings.language": "Language",
|
|
1016
|
+
"settings.languageHint": "UI text follows the language option; it applies immediately and is remembered.", "settings.fontScale": "Font size",
|
|
1017
|
+
"settings.fontScaleHint": "Scales the whole studio as a percentage of the current font size; applies immediately and is remembered.",
|
|
1018
|
+
"errBoundary.title": "This section failed to render", "errBoundary.summary": "Error summary: {msg}",
|
|
1019
|
+
"dim.cognitiveFrame": "Cognitive Frame", "dim.emotionalAnchor": "Emotional Anchor", "dim.worldTexture": "World Texture",
|
|
1020
|
+
"dim.suspense": "Suspense", "dim.interpersonalTension": "Interpersonal Tension",
|
|
1021
|
+
},
|
|
1022
|
+
};
|
|
1023
|
+
function tt(key) {
|
|
1024
|
+
var d = I18N[UI_LANG] || I18N.zh;
|
|
1025
|
+
var v = d[key];
|
|
1026
|
+
if (v === undefined) v = I18N.zh[key];
|
|
1027
|
+
return v === undefined ? key : v;
|
|
1028
|
+
}
|
|
1029
|
+
function tf(key, vars) {
|
|
1030
|
+
var s = tt(key);
|
|
1031
|
+
if (vars) for (var k in vars) s = s.split("{" + k + "}").join(String(vars[k]));
|
|
1032
|
+
return s;
|
|
1033
|
+
}
|
|
861
1034
|
function SettingsModal(props) {
|
|
862
1035
|
var scale = props.scale;
|
|
863
1036
|
var onPick = props.onPick;
|
|
1037
|
+
var lang = props.lang;
|
|
1038
|
+
var onPickLang = props.onPickLang;
|
|
864
1039
|
var onClose = props.onClose;
|
|
865
1040
|
return h(MaskPortal, {
|
|
866
1041
|
className: "myt-settingsMask",
|
|
867
1042
|
onMouseDown: function (e) { if (e.target === e.currentTarget) onClose(); },
|
|
868
1043
|
},
|
|
869
|
-
h("div", { className: "myt-settings", role: "dialog", "aria-modal": "true", "aria-label": "
|
|
1044
|
+
h("div", { className: "myt-settings", role: "dialog", "aria-modal": "true", "aria-label": tt("settings.title") },
|
|
870
1045
|
h("div", { className: "myt-settingsHead" },
|
|
871
|
-
h("h3", { className: "myt-settingsTitle" }, "
|
|
872
|
-
h("button", { type: "button", className: "myt-closeBtn", "aria-label": "
|
|
1046
|
+
h("h3", { className: "myt-settingsTitle" }, tt("settings.title")),
|
|
1047
|
+
h("button", { type: "button", className: "myt-closeBtn", "aria-label": tt("settings.closeAria"), onClick: onClose }, h("span", { className: "myt-closeGlyph", "aria-hidden": "true" }, "×"))),
|
|
873
1048
|
h("div", { className: "myt-settingsBody" },
|
|
874
|
-
h("div", { className: "myt-settingsLabel" }, "
|
|
1049
|
+
h("div", { className: "myt-settingsLabel" }, tt("settings.language")),
|
|
1050
|
+
h("div", { className: "myt-setRow" }, UI_LANGS.map(function (o) {
|
|
1051
|
+
return h("button", {
|
|
1052
|
+
key: o[0], type: "button", className: "myt-setOpt", "data-active": lang === o[0] ? "true" : undefined,
|
|
1053
|
+
onClick: function () { onPickLang(o[0]); },
|
|
1054
|
+
}, o[1]);
|
|
1055
|
+
})),
|
|
1056
|
+
h("div", { className: "myt-settingsHint" }, tt("settings.languageHint")),
|
|
1057
|
+
h("div", { className: "myt-settingsLabel" }, tt("settings.fontScale")),
|
|
875
1058
|
h("div", { className: "myt-setRow" }, FONT_SCALES.map(function (o) {
|
|
876
1059
|
return h("button", {
|
|
877
1060
|
key: o[0], type: "button", className: "myt-setOpt", "data-active": scale === o[0] ? "true" : undefined,
|
|
878
1061
|
onClick: function () { onPick(o[0]); },
|
|
879
1062
|
}, o[1]);
|
|
880
1063
|
})),
|
|
881
|
-
h("div", { className: "myt-settingsHint" }, "
|
|
1064
|
+
h("div", { className: "myt-settingsHint" }, tt("settings.fontScaleHint")))));
|
|
882
1065
|
}
|
|
883
1066
|
|
|
884
1067
|
// ── 章节阅读页(整页视图,替代右侧抽屉):返回按钮 + 章纲对照 + 正文 +
|
|
@@ -922,32 +1105,32 @@ window.__ModuleLoader__.load({
|
|
|
922
1105
|
}
|
|
923
1106
|
function copyChapterPath() {
|
|
924
1107
|
copyText(chapterAbsPath(snap && snap.novelDir, file)).then(function (ok) {
|
|
925
|
-
if (ok) toast.ok("
|
|
926
|
-
else toast.warn("
|
|
1108
|
+
if (ok) toast.ok(tt("reader.copied"));
|
|
1109
|
+
else toast.warn(tt("reader.copyFailed"));
|
|
927
1110
|
});
|
|
928
1111
|
}
|
|
929
1112
|
return h("div", { className: "myt-reader" },
|
|
930
1113
|
h("div", { className: "myt-readerBar" },
|
|
931
|
-
h("button", { type: "button", className: "myt-btn", onClick: onBack, title: "
|
|
932
|
-
ic("IconChevronRightOutlineRegular", 13), "
|
|
1114
|
+
h("button", { type: "button", className: "myt-btn", onClick: onBack, title: tt("reader.backTip") },
|
|
1115
|
+
ic("IconChevronRightOutlineRegular", 13), tt("reader.back")),
|
|
933
1116
|
h("h2", { className: "myt-readerTitle" }, ch ? chFullTitle(ch) : file),
|
|
934
1117
|
ch ? statusBadge(ch.status) : null,
|
|
935
1118
|
h("div", { className: "myt-readerBarActions" },
|
|
936
1119
|
h("button", {
|
|
937
1120
|
type: "button", className: "myt-btn myt-copyBtn",
|
|
938
|
-
title: "
|
|
1121
|
+
title: tt("reader.copyPathTip"), "aria-label": tt("reader.copyPathTip"),
|
|
939
1122
|
onClick: copyChapterPath,
|
|
940
|
-
}, ic("IconCopyOutlineRegular", 13), "
|
|
1123
|
+
}, ic("IconCopyOutlineRegular", 13), tt("reader.copyPath")))),
|
|
941
1124
|
h("div", { className: "myt-readerBody" },
|
|
942
1125
|
!detail || detail.loading ? h("div", { className: "myt-stack" }, h("div", { className: "myt-skel myt-skelRow" }), h("div", { className: "myt-skel myt-skelRow", style: { width: "80%" } }), h("div", { className: "myt-skel myt-skelRow", style: { width: "64%" } }))
|
|
943
|
-
: detail.err ? h(ErrorStrip, { text: "
|
|
1126
|
+
: detail.err ? h(ErrorStrip, { text: tf("reader.loadFailed", { msg: detail.err }), onRetry: function () { loadDetail(file); } })
|
|
944
1127
|
: [
|
|
945
1128
|
(function () {
|
|
946
1129
|
var ol = detail.data && detail.data.outline;
|
|
947
1130
|
return ol ? h(OutlinePanel, { key: "outline", outline: ol })
|
|
948
|
-
: h("div", { key: "outline-none", className: "myt-outlineEmpty" }, "
|
|
1131
|
+
: h("div", { key: "outline-none", className: "myt-outlineEmpty" }, tt("reader.noOutline"));
|
|
949
1132
|
})(),
|
|
950
|
-
h("pre", { key: "body", className: "myt-chapterBody" }, (detail.data && detail.data.body) || "
|
|
1133
|
+
h("pre", { key: "body", className: "myt-chapterBody" }, (detail.data && detail.data.body) || tt("reader.emptyBody")),
|
|
951
1134
|
],
|
|
952
1135
|
ch ? h("div", { className: "myt-dimGrid" }, FIVE_DIMS.map(function (dim) {
|
|
953
1136
|
return h("div", { key: dim[0], className: "myt-dim" },
|
|
@@ -958,10 +1141,10 @@ window.__ModuleLoader__.load({
|
|
|
958
1141
|
(function () {
|
|
959
1142
|
var idx = STATUS_FLOW.indexOf(ch.status);
|
|
960
1143
|
var next = idx !== -1 && idx < STATUS_FLOW.length - 1 ? STATUS_FLOW[idx + 1] : null;
|
|
961
|
-
if (!next) return h("span", { className: "myt-muted" }, "
|
|
962
|
-
return [h("span", { className: "myt-muted", key: "t" }, "
|
|
1144
|
+
if (!next) return h("span", { className: "myt-muted" }, tt("reader.finalState"));
|
|
1145
|
+
return [h("span", { className: "myt-muted", key: "t" }, tt("reader.statusFlow")),
|
|
963
1146
|
h("button", { key: "b", type: "button", className: "myt-btn myt-btnPrimary", disabled: flowBusy, onClick: advance },
|
|
964
|
-
flowBusy ? "
|
|
1147
|
+
flowBusy ? tt("reader.flowing") : tf("reader.advanceTo", { next: tt("status." + next) }))];
|
|
965
1148
|
})()) : null);
|
|
966
1149
|
}
|
|
967
1150
|
|
|
@@ -987,7 +1170,7 @@ window.__ModuleLoader__.load({
|
|
|
987
1170
|
function set(k, v) { setForm(Object.assign({}, form, (function () { var o = {}; o[k] = v; return o; })())); }
|
|
988
1171
|
function save() {
|
|
989
1172
|
if (busy) return;
|
|
990
|
-
if (!String(form.title || "").trim()) { setSaveErr("
|
|
1173
|
+
if (!String(form.title || "").trim()) { setSaveErr(tt("novel.titleRequired")); return; }
|
|
991
1174
|
setBusy(true); setSaveErr("");
|
|
992
1175
|
// 成败提示统一走 apiPost 的 toast 通道(这里不再另弹,避免双 toast);
|
|
993
1176
|
// 卡内 saveErr 只承担表单旁的错误态。
|
|
@@ -1017,27 +1200,27 @@ window.__ModuleLoader__.load({
|
|
|
1017
1200
|
return h("div", { className: "myt-stack" },
|
|
1018
1201
|
h("div", { className: "myt-card" },
|
|
1019
1202
|
h("div", { className: "myt-cardHead" },
|
|
1020
|
-
h("h3", { className: "myt-cardTitle" }, "
|
|
1021
|
-
h("span", { className: "myt-muted" }, "
|
|
1203
|
+
h("h3", { className: "myt-cardTitle" }, tt("novel.metaTitle")),
|
|
1204
|
+
h("span", { className: "myt-muted" }, tt("novel.metaNote"))),
|
|
1022
1205
|
h("div", { className: "myt-setGrid" },
|
|
1023
|
-
field("
|
|
1024
|
-
field("
|
|
1025
|
-
field("
|
|
1026
|
-
field("
|
|
1027
|
-
picker("
|
|
1028
|
-
picker("
|
|
1029
|
-
picker("
|
|
1030
|
-
field("
|
|
1206
|
+
field(tt("novel.fTitle"), "title", tt("novel.titlePh")),
|
|
1207
|
+
field(tt("novel.fAuthor"), "author", tt("novel.authorPh")),
|
|
1208
|
+
field(tt("novel.fGenre"), "genre", tt("novel.genrePh")),
|
|
1209
|
+
field(tt("novel.fLength"), "length", tt("novel.lengthPh")),
|
|
1210
|
+
picker(tt("novel.fLanguage"), "language", [["zh", "中文"], ["en", "English"], ["ja", "日本語"]]),
|
|
1211
|
+
picker(tt("novel.fPhase"), "phase", PHASES.map(function (p) { return [p, tt("phase." + p)]; })),
|
|
1212
|
+
picker(tt("novel.fMode"), "mode", [["ai-draft", tt("mode.aiDraft")], ["co-write", tt("mode.coWrite")], ["author-draft", tt("mode.authorDraft")]]),
|
|
1213
|
+
field(tt("novel.fDesc"), "description", tt("novel.descPh"), true))),
|
|
1031
1214
|
saveErr ? h(ErrorStrip, { text: saveErr }) : null,
|
|
1032
1215
|
h("div", { className: "myt-setActions" },
|
|
1033
1216
|
h("button", { type: "button", className: "myt-btn myt-btnPrimary", disabled: busy, onClick: save },
|
|
1034
|
-
busy ? "
|
|
1217
|
+
busy ? tt("novel.saving") : tt("novel.save"))));
|
|
1035
1218
|
}
|
|
1036
1219
|
|
|
1037
1220
|
// ── 章节 tab:状态筛选 pills + 表格 + 行抽屉(只读正文 + 五维 + 流转)────
|
|
1038
1221
|
var FIVE_DIMS = [
|
|
1039
|
-
["cognitive_frame", "
|
|
1040
|
-
["suspense", "
|
|
1222
|
+
["cognitive_frame", tt("dim.cognitiveFrame")], ["emotional_anchor", tt("dim.emotionalAnchor")], ["world_texture", tt("dim.worldTexture")],
|
|
1223
|
+
["suspense", tt("dim.suspense")], ["interpersonal_tension", tt("dim.interpersonalTension")],
|
|
1041
1224
|
];
|
|
1042
1225
|
// 章纲对照栏(五期):outline 非空时渲染可折叠原文(默认展开,只读,样式
|
|
1043
1226
|
// 与正文区分);共享常量避免重渲染时折叠态意外重置——组件随抽屉挂载,
|
|
@@ -1045,16 +1228,16 @@ window.__ModuleLoader__.load({
|
|
|
1045
1228
|
function OutlinePanel(props) {
|
|
1046
1229
|
var outline = props.outline;
|
|
1047
1230
|
var _op = useState(true), open = _op[0], setOpen = _op[1];
|
|
1048
|
-
var body = outline && typeof outline.body === "string" && outline.body.trim() !== "" ? outline.body : "
|
|
1231
|
+
var body = outline && typeof outline.body === "string" && outline.body.trim() !== "" ? outline.body : tt("outline.empty");
|
|
1049
1232
|
return h("div", { className: "myt-outlinePanel" },
|
|
1050
1233
|
h("button", {
|
|
1051
1234
|
type: "button", className: "myt-outlineHead",
|
|
1052
1235
|
"aria-expanded": open ? "true" : "false",
|
|
1053
|
-
title: open ? "
|
|
1236
|
+
title: open ? tt("outline.collapseTip") : tt("outline.expandTip"),
|
|
1054
1237
|
onClick: function () { setOpen(!open); },
|
|
1055
1238
|
},
|
|
1056
|
-
h("span", { className: "myt-outlineTitle" }, "
|
|
1057
|
-
typeof (outline && outline.words) === "number" ? h("span", { className: "myt-outlineWords" }, fmtNum(outline.words)
|
|
1239
|
+
h("span", { className: "myt-outlineTitle" }, tt("outline.title")),
|
|
1240
|
+
typeof (outline && outline.words) === "number" ? h("span", { className: "myt-outlineWords" }, tf("common.charCount", { n: fmtNum(outline.words) })) : null,
|
|
1058
1241
|
h("span", { className: "myt-outlineChevron", "aria-hidden": "true" },
|
|
1059
1242
|
ic(open ? "IconChevronDownOutlineRegular" : "IconChevronRightOutlineRegular", 14))),
|
|
1060
1243
|
open ? h(MdView, { text: body, className: "myt-outlineBody" }) : null);
|
|
@@ -1093,14 +1276,14 @@ window.__ModuleLoader__.load({
|
|
|
1093
1276
|
return h("div", { className: "myt-stack" },
|
|
1094
1277
|
writeOrder.length ? h("div", { className: "myt-card myt-writeOrder" },
|
|
1095
1278
|
h("div", { className: "myt-cardHead" },
|
|
1096
|
-
h("h3", { className: "myt-cardTitle" }, "
|
|
1279
|
+
h("h3", { className: "myt-cardTitle" }, tt("chapters.writeQueue")),
|
|
1097
1280
|
h("span", { className: "myt-boardCount" }, writeOrder.length)),
|
|
1098
1281
|
h("div", { className: "myt-writeOrderList" }, writeOrder.map(function (item) {
|
|
1099
1282
|
var ch = item.ch;
|
|
1100
1283
|
var ol = item.ol;
|
|
1101
1284
|
return h("button", {
|
|
1102
1285
|
key: ch.file, type: "button", className: "myt-writeOrderRow",
|
|
1103
|
-
title: "
|
|
1286
|
+
title: tt("chapters.rowTip"),
|
|
1104
1287
|
onClick: function () { onOpen(ch.file); },
|
|
1105
1288
|
},
|
|
1106
1289
|
h("span", { className: "myt-chPath" }, chWhere(ch) || ch.file),
|
|
@@ -1108,11 +1291,11 @@ window.__ModuleLoader__.load({
|
|
|
1108
1291
|
statusBadge(ch.status),
|
|
1109
1292
|
ol && typeof ol.excerpt === "string" && ol.excerpt !== ""
|
|
1110
1293
|
? h("span", { className: "myt-writeOrderExcerpt", title: ol.excerpt }, truncate(ol.excerpt, 60)) : null,
|
|
1111
|
-
ol && ol.words ? h("span", { className: "myt-wordsCell" }, "
|
|
1294
|
+
ol && ol.words ? h("span", { className: "myt-wordsCell" }, tf("chapters.outlineWords", { n: fmtNum(ol.words) })) : null);
|
|
1112
1295
|
}))) : null,
|
|
1113
1296
|
h("div", { className: "myt-filterRow" },
|
|
1114
|
-
[{ k: "all", label: "
|
|
1115
|
-
return { k: s, label:
|
|
1297
|
+
[{ k: "all", label: tt("chapters.all"), n: chapters.length }].concat(STATUS_FLOW.map(function (s) {
|
|
1298
|
+
return { k: s, label: tt("status." + s), n: counts[s] || 0 };
|
|
1116
1299
|
})).map(function (f) {
|
|
1117
1300
|
return h("button", {
|
|
1118
1301
|
key: f.k, type: "button", className: "myt-filterPill",
|
|
@@ -1123,22 +1306,22 @@ window.__ModuleLoader__.load({
|
|
|
1123
1306
|
rows.length ? h("div", { className: "myt-tableWrap" },
|
|
1124
1307
|
h("table", { className: "myt-table" },
|
|
1125
1308
|
h("thead", null, h("tr", null,
|
|
1126
|
-
h("th", null, "
|
|
1309
|
+
h("th", null, tt("chapters.thVolume")), h("th", null, tt("chapters.thTitle")), h("th", null, tt("chapters.thStatus")), h("th", null, tt("chapters.thWords")), h("th", null, tt("chapters.thSummary")))),
|
|
1127
1310
|
h("tbody", null, rows.map(function (ch) {
|
|
1128
|
-
return h("tr", { key: ch.file, onClick: function () { onOpen(ch.file); }, title: "
|
|
1311
|
+
return h("tr", { key: ch.file, onClick: function () { onOpen(ch.file); }, title: tt("chapters.rowReadTip") },
|
|
1129
1312
|
h("td", null, h("span", { className: "myt-chPath" }, chWhere(ch) || "—")),
|
|
1130
1313
|
h("td", null, h("span", { className: "myt-chTitle" }, orDash(ch.title))),
|
|
1131
1314
|
h("td", null, statusBadge(ch.status)),
|
|
1132
1315
|
h("td", null, h("span", { className: "myt-wordsCell" }, fmtNum(ch.words))),
|
|
1133
1316
|
h("td", null, h("div", { className: "myt-summaryCell myt-muted" }, orDash(ch.summary))));
|
|
1134
1317
|
}))))
|
|
1135
|
-
: (chapters.length ? h(Empty, { text: "
|
|
1318
|
+
: (chapters.length ? h(Empty, { text: tt("chapters.emptyFiltered") }) : h(Empty, { text: tt("chapters.empty") })));
|
|
1136
1319
|
}
|
|
1137
1320
|
|
|
1138
1321
|
// ── 角色 tab:卡墙 + 全卡弹层 ───────────────────────────────────────────
|
|
1139
1322
|
var CHAR_FULL_ROWS = [
|
|
1140
|
-
["age", "
|
|
1141
|
-
["background", "
|
|
1323
|
+
["age", tt("char.field.age")], ["gender", tt("char.field.gender")], ["appearance", tt("char.field.appearance")], ["personality", tt("char.field.personality")],
|
|
1324
|
+
["background", tt("char.field.background")], ["motivation", tt("char.field.motivation")], ["arc", tt("char.field.arc")],
|
|
1142
1325
|
];
|
|
1143
1326
|
function CharactersTab(props) {
|
|
1144
1327
|
var characters = props.snap.characters || [];
|
|
@@ -1152,20 +1335,20 @@ window.__ModuleLoader__.load({
|
|
|
1152
1335
|
h("span", { className: "myt-charName" }, orDash(c.name)),
|
|
1153
1336
|
roleBadge(c.role)),
|
|
1154
1337
|
h("div", { className: "myt-charMeta" },
|
|
1155
|
-
[c.age !== null && c.age !== undefined ? c.age
|
|
1156
|
-
c.personality ? h("div", { className: "myt-charLine" }, h("span", { className: "myt-charLabel" }, "
|
|
1157
|
-
c.motivation ? h("div", { className: "myt-charLine" }, h("span", { className: "myt-charLabel" }, "
|
|
1158
|
-
})) : h(Empty, { text: "
|
|
1338
|
+
[c.age !== null && c.age !== undefined ? tf("char.years", { n: c.age }) : null, c.gender || null].filter(Boolean).join(" · ") || "—"),
|
|
1339
|
+
c.personality ? h("div", { className: "myt-charLine" }, h("span", { className: "myt-charLabel" }, tt("char.linePersonality")), truncate(c.personality, 60)) : null,
|
|
1340
|
+
c.motivation ? h("div", { className: "myt-charLine" }, h("span", { className: "myt-charLabel" }, tt("char.lineMotivation")), truncate(c.motivation, 60)) : null);
|
|
1341
|
+
})) : h(Empty, { text: tt("char.empty") }),
|
|
1159
1342
|
modal ? h(MaskPortal, {
|
|
1160
1343
|
className: "myt-modalMask",
|
|
1161
1344
|
onMouseDown: function (e) { if (e.target === e.currentTarget) setModalFile(null); },
|
|
1162
1345
|
},
|
|
1163
|
-
h("div", { className: "myt-modalCard", role: "dialog", "aria-modal": "true", "aria-label": "
|
|
1346
|
+
h("div", { className: "myt-modalCard", role: "dialog", "aria-modal": "true", "aria-label": tt("char.modalAria") },
|
|
1164
1347
|
h("div", { className: "myt-modalHead" },
|
|
1165
1348
|
h("h2", { className: "myt-modalTitle" }, orDash(modal.name)),
|
|
1166
1349
|
h("div", { className: "myt-heroMeta" },
|
|
1167
1350
|
roleBadge(modal.role),
|
|
1168
|
-
h("button", { type: "button", className: "myt-closeBtn", "aria-label": "
|
|
1351
|
+
h("button", { type: "button", className: "myt-closeBtn", "aria-label": tt("char.closeAria"), onClick: function () { setModalFile(null); } }, h("span", { className: "myt-closeGlyph", "aria-hidden": "true" }, "×")))),
|
|
1169
1352
|
h("div", { className: "myt-modalBody" },
|
|
1170
1353
|
CHAR_FULL_ROWS.map(function (row) {
|
|
1171
1354
|
return h("div", { key: row[0], className: "myt-kvRow" },
|
|
@@ -1173,13 +1356,13 @@ window.__ModuleLoader__.load({
|
|
|
1173
1356
|
h("span", { className: "myt-kvVal" }, orDash(modal[row[0]])));
|
|
1174
1357
|
}),
|
|
1175
1358
|
h("div", { className: "myt-kvRow" },
|
|
1176
|
-
h("span", { className: "myt-kvKey" }, "
|
|
1359
|
+
h("span", { className: "myt-kvKey" }, tt("char.markers")),
|
|
1177
1360
|
h("span", { className: "myt-kvVal myt-tagRow" },
|
|
1178
1361
|
(modal.markers || []).length
|
|
1179
1362
|
? modal.markers.map(function (m, i) { return h("span", { key: i, className: "myt-markerTag" }, m); })
|
|
1180
1363
|
: "—")),
|
|
1181
1364
|
h("div", { className: "myt-kvRow" },
|
|
1182
|
-
h("span", { className: "myt-kvKey" }, "
|
|
1365
|
+
h("span", { className: "myt-kvKey" }, tt("char.file")),
|
|
1183
1366
|
h("span", { className: "myt-kvVal" }, modal.file || "—"))))) : null);
|
|
1184
1367
|
}
|
|
1185
1368
|
|
|
@@ -1201,7 +1384,7 @@ window.__ModuleLoader__.load({
|
|
|
1201
1384
|
var items = list.filter(function (f) { return (f.status || "planted") === col; });
|
|
1202
1385
|
return h("div", { key: col, className: "myt-boardCol" },
|
|
1203
1386
|
h("div", { className: "myt-boardHead" },
|
|
1204
|
-
h("span", { className: "myt-boardTitle" },
|
|
1387
|
+
h("span", { className: "myt-boardTitle" }, tt("foreshadow." + col)),
|
|
1205
1388
|
h("span", { className: "myt-boardCount" }, items.length)),
|
|
1206
1389
|
items.map(function (fs) {
|
|
1207
1390
|
var next = FS_FLOW[col] || null;
|
|
@@ -1211,26 +1394,26 @@ window.__ModuleLoader__.load({
|
|
|
1211
1394
|
var noStatus = !fs.status;
|
|
1212
1395
|
var noWrite = !fs.id || noStatus;
|
|
1213
1396
|
var noWriteTip = !fs.id
|
|
1214
|
-
? "
|
|
1215
|
-
: "
|
|
1216
|
-
var chRange = [fs.planted_chapter || "
|
|
1397
|
+
? tt("foreshadow.noIdTip")
|
|
1398
|
+
: tt("foreshadow.noStatusTip");
|
|
1399
|
+
var chRange = [fs.planted_chapter || tt("foreshadow.plantedUnmarked"), fs.expected_resolve].filter(Boolean).join(" → ");
|
|
1217
1400
|
return h("div", { key: fs.id || fs.title, className: "myt-fsCard" },
|
|
1218
1401
|
h("div", { className: "myt-fsTitle" },
|
|
1219
1402
|
priDot(fs.priority), h("span", null, orDash(fs.title) || orDash(fs.id)),
|
|
1220
1403
|
// 回填徽章(五期账本回填):区别于写前规划条目,悬浮见来源。
|
|
1221
|
-
fs.source === "backfill" ? h("span", { className: "myt-backfillBadge", title: "
|
|
1222
|
-
noStatus ? h("span", { className: "myt-fsNoStatus", title: noWriteTip }, "
|
|
1404
|
+
fs.source === "backfill" ? h("span", { className: "myt-backfillBadge", title: tt("foreshadow.backfillTip") }, tt("foreshadow.backfill")) : null,
|
|
1405
|
+
noStatus ? h("span", { className: "myt-fsNoStatus", title: noWriteTip }, tt("foreshadow.missingStatus")) : null),
|
|
1223
1406
|
h("div", { className: "myt-fsMeta" },
|
|
1224
|
-
h("span", null, col === "resolved" && fs.resolved_chapter ? "
|
|
1225
|
-
fs.priority && fs.priority !== "normal" ? h("span", null, "
|
|
1407
|
+
h("span", null, col === "resolved" && fs.resolved_chapter ? tf("foreshadow.resolvedAt", { ch: fs.resolved_chapter }) : chRange),
|
|
1408
|
+
fs.priority && fs.priority !== "normal" ? h("span", null, tf("foreshadow.priorityLabel", { pri: ["high", "low", "normal"].indexOf(fs.priority) !== -1 ? tt("pri." + fs.priority) : fs.priority })) : null),
|
|
1226
1409
|
fs.notes ? h("div", { className: "myt-fsNotes", title: fs.notes }, fs.notes) : null,
|
|
1227
1410
|
h("div", { className: "myt-fsActions" },
|
|
1228
|
-
next ? h("button", { type: "button", className: "myt-btn", disabled: busy || noWrite, title: noWriteTip, onClick: function () { setStatus(fs, next); } }, busy ? "
|
|
1411
|
+
next ? h("button", { type: "button", className: "myt-btn", disabled: busy || noWrite, title: noWriteTip, onClick: function () { setStatus(fs, next); } }, busy ? tt("foreshadow.advancing") : (col === "planted" ? tt("foreshadow.advance") : tt("foreshadow.resolve"))) : null,
|
|
1229
1412
|
col !== "resolved" && col !== "abandoned"
|
|
1230
|
-
? h("button", { type: "button", className: "myt-btn myt-btnDanger", disabled: busy || noWrite, title: noWriteTip, onClick: function () { setStatus(fs, "abandoned"); } }, "
|
|
1413
|
+
? h("button", { type: "button", className: "myt-btn myt-btnDanger", disabled: busy || noWrite, title: noWriteTip, onClick: function () { setStatus(fs, "abandoned"); } }, tt("foreshadow.abandon")) : null));
|
|
1231
1414
|
}),
|
|
1232
|
-
items.length ? null : h("div", { className: "myt-muted" }, "
|
|
1233
|
-
})) : h(Empty, { text: "
|
|
1415
|
+
items.length ? null : h("div", { className: "myt-muted" }, tt("foreshadow.colEmpty")));
|
|
1416
|
+
})) : h(Empty, { text: tt("foreshadow.empty") }));
|
|
1234
1417
|
}
|
|
1235
1418
|
|
|
1236
1419
|
// ── 时间线 tab:垂直轴 + importance 点位(1-5 → 大小/深浅)──────────────
|
|
@@ -1239,17 +1422,17 @@ window.__ModuleLoader__.load({
|
|
|
1239
1422
|
if (items.length) {
|
|
1240
1423
|
items.sort(function (a, b) { return String(a.year || "").localeCompare(String(b.year || ""), "zh-CN", { numeric: true }); });
|
|
1241
1424
|
}
|
|
1242
|
-
if (!items.length) return h(Empty, { text: "timeline.
|
|
1425
|
+
if (!items.length) return h(Empty, { text: tt("timeline.empty") });
|
|
1243
1426
|
return h("div", { className: "myt-card" },
|
|
1244
1427
|
h("div", { className: "myt-timeline" }, items.map(function (t, i) {
|
|
1245
1428
|
var imp = Math.min(5, Math.max(1, typeof t.importance === "number" ? t.importance : 3));
|
|
1246
1429
|
return h("div", { key: i, className: "myt-tlRow" },
|
|
1247
1430
|
h("div", { className: "myt-tlYear" }, orDash(t.year)),
|
|
1248
|
-
h("div", { className: "myt-tlAxis" }, h("span", { className: "myt-tlDot", "data-imp": String(imp), title: "
|
|
1431
|
+
h("div", { className: "myt-tlAxis" }, h("span", { className: "myt-tlDot", "data-imp": String(imp), title: tf("timeline.importance", { n: imp }) })),
|
|
1249
1432
|
h("div", { className: "myt-tlCard" },
|
|
1250
1433
|
h("div", { className: "myt-tlTitle" }, orDash(t.title)),
|
|
1251
1434
|
t.description ? h("div", { className: "myt-tlDesc" }, t.description) : null,
|
|
1252
|
-
t.chapter ? h("div", { className: "myt-tlChapter" }, "
|
|
1435
|
+
t.chapter ? h("div", { className: "myt-tlChapter" }, tf("timeline.linkedChapter", { ch: t.chapter })) : null));
|
|
1253
1436
|
})));
|
|
1254
1437
|
}
|
|
1255
1438
|
|
|
@@ -1264,14 +1447,14 @@ window.__ModuleLoader__.load({
|
|
|
1264
1447
|
// 真值 → TypeError → 整面板白屏;Object.create(null) 杜绝。
|
|
1265
1448
|
var groups = Object.create(null);
|
|
1266
1449
|
world.forEach(function (w) {
|
|
1267
|
-
var key = w.category || "
|
|
1450
|
+
var key = w.category || tt("common.uncategorized");
|
|
1268
1451
|
if (!groups[key]) groups[key] = [];
|
|
1269
1452
|
groups[key].push(w);
|
|
1270
1453
|
});
|
|
1271
1454
|
var groupKeys = Object.keys(groups).sort(function (a, b) { return a.localeCompare(b, "zh-CN"); });
|
|
1272
1455
|
return h("div", { className: "myt-stack" },
|
|
1273
1456
|
h("div", { className: "myt-card" },
|
|
1274
|
-
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, "
|
|
1457
|
+
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, tt("world.title"))),
|
|
1275
1458
|
groupKeys.length ? h("div", { className: "myt-stack" }, groupKeys.map(function (key) {
|
|
1276
1459
|
return h("div", { key: key, className: "myt-worldGroup" },
|
|
1277
1460
|
h("div", { className: "myt-listHead" }, key, h("span", { className: "myt-boardCount" }, groups[key].length)),
|
|
@@ -1282,23 +1465,23 @@ window.__ModuleLoader__.load({
|
|
|
1282
1465
|
(w.tags || []).length ? h("span", { className: "myt-tagRow" }, w.tags.map(function (t, j) { return h("span", { key: j, className: "myt-tag" }, t); })) : null),
|
|
1283
1466
|
w.description ? h(MdView, { text: w.description, className: "myt-worldDesc" }) : null);
|
|
1284
1467
|
}));
|
|
1285
|
-
})) : h(Empty, { text: "world
|
|
1468
|
+
})) : h(Empty, { text: tt("world.empty") })),
|
|
1286
1469
|
h("div", { className: "myt-card" },
|
|
1287
|
-
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, "
|
|
1470
|
+
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, tt("world.science")),
|
|
1288
1471
|
h("span", { className: "myt-boardCount" }, science.length)),
|
|
1289
1472
|
science.length ? h("div", { className: "myt-stack" }, science.map(function (s, i) {
|
|
1290
|
-
var label =
|
|
1473
|
+
var label = ["known", "extrapolation", "hypothesis"].indexOf(s.label) !== -1 ? s.label : null;
|
|
1291
1474
|
return h("div", { key: s.file || i, className: "myt-worldItem" },
|
|
1292
1475
|
h("div", { className: "myt-worldHead" },
|
|
1293
1476
|
h("span", { className: "myt-worldName" }, orDash(s.name)),
|
|
1294
1477
|
h("span", { className: "myt-badge myt-sciTag", "data-label": label || "hypothesis", title: s.label && !label ? s.label : undefined },
|
|
1295
|
-
label ?
|
|
1478
|
+
label ? tt("sci." + label) : (s.label || tt("world.unlabeled")))),
|
|
1296
1479
|
s.description ? h(MdView, { text: s.description, className: "myt-worldDesc" }) : null,
|
|
1297
1480
|
(s.references || []).length ? h("div", { className: "myt-refList" },
|
|
1298
|
-
s.references.map(function (r, j) { return h("span", { key: j, className: "myt-refItem" }, "
|
|
1299
|
-
})) : h(Empty, { text: "
|
|
1481
|
+
s.references.map(function (r, j) { return h("span", { key: j, className: "myt-refItem" }, tf("world.ref", { n: j + 1, r: r })); })) : null);
|
|
1482
|
+
})) : h(Empty, { text: tt("world.scienceEmpty") })),
|
|
1300
1483
|
research.length ? h("div", { className: "myt-card" },
|
|
1301
|
-
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, "
|
|
1484
|
+
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, tt("world.research")),
|
|
1302
1485
|
h("span", { className: "myt-boardCount" }, research.length)),
|
|
1303
1486
|
h("div", { className: "myt-tagRow" }, research.map(function (r, i) {
|
|
1304
1487
|
return h("span", { key: r.file || i, className: "myt-tag", title: r.file || "" }, orDash(r.topic) || r.file);
|
|
@@ -1322,27 +1505,28 @@ window.__ModuleLoader__.load({
|
|
|
1322
1505
|
// 无原型容器:kind/category 是数据文件自由文本,见 WorldTab 处说明。
|
|
1323
1506
|
var clueGroups = Object.create(null);
|
|
1324
1507
|
clues.forEach(function (c) {
|
|
1325
|
-
var key = c.kind || "
|
|
1508
|
+
var key = c.kind || tt("common.uncategorized");
|
|
1326
1509
|
if (!clueGroups[key]) clueGroups[key] = [];
|
|
1327
1510
|
clueGroups[key].push(c);
|
|
1328
1511
|
});
|
|
1329
1512
|
var memGroups = Object.create(null);
|
|
1330
1513
|
memories.forEach(function (m) {
|
|
1331
|
-
var key = m.category || "
|
|
1514
|
+
var key = m.category || tt("common.uncategorized");
|
|
1332
1515
|
if (!memGroups[key]) memGroups[key] = [];
|
|
1333
1516
|
memGroups[key].push(m);
|
|
1334
1517
|
});
|
|
1335
1518
|
function groupKey(a, b) { return a.localeCompare(b, "zh-CN"); }
|
|
1336
1519
|
return h("div", { className: "myt-stack" },
|
|
1337
1520
|
h("div", { className: "myt-cardHead" },
|
|
1338
|
-
h("h3", { className: "myt-cardTitle" }, "
|
|
1339
|
-
h("button", { type: "button", className: "myt-btn", disabled: rebuildBusy, onClick: rebuild, title: "
|
|
1340
|
-
rebuildBusy ? "
|
|
1521
|
+
h("h3", { className: "myt-cardTitle" }, tt("index.title")),
|
|
1522
|
+
h("button", { type: "button", className: "myt-btn", disabled: rebuildBusy, onClick: rebuild, title: tt("index.rebuildTip") },
|
|
1523
|
+
rebuildBusy ? tt("index.rebuilding") : tt("index.rebuild"))),
|
|
1341
1524
|
h("div", { className: "myt-indexCols" },
|
|
1342
1525
|
h("div", { className: "myt-card" },
|
|
1343
|
-
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, "
|
|
1526
|
+
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, tt("index.clueBoard")), h("span", { className: "myt-boardCount" }, clues.length)),
|
|
1344
1527
|
clues.length ? Object.keys(clueGroups).sort(groupKey).map(function (kind) {
|
|
1345
|
-
var
|
|
1528
|
+
var ck = CLUE_KIND_KEYS[kind];
|
|
1529
|
+
var label = ck ? tt("clue." + ck) : kind;
|
|
1346
1530
|
return h("div", { key: kind, className: "myt-worldGroup" },
|
|
1347
1531
|
h("div", { className: "myt-listHead" }, label, h("span", { className: "myt-boardCount" }, clueGroups[kind].length)),
|
|
1348
1532
|
clueGroups[kind].map(function (c, i) {
|
|
@@ -1350,21 +1534,21 @@ window.__ModuleLoader__.load({
|
|
|
1350
1534
|
h("span", { className: "myt-clueText" }, orDash(c.content)),
|
|
1351
1535
|
c.related_chapter ? h("span", { className: "myt-clueChapter" }, c.related_chapter) : null);
|
|
1352
1536
|
}));
|
|
1353
|
-
}) : h(Empty, { text: "
|
|
1537
|
+
}) : h(Empty, { text: tt("index.clueEmpty") })),
|
|
1354
1538
|
h("div", { className: "myt-card" },
|
|
1355
|
-
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, "
|
|
1539
|
+
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, tt("index.memBoard")), h("span", { className: "myt-boardCount" }, memories.length)),
|
|
1356
1540
|
memories.length ? Object.keys(memGroups).sort(groupKey).map(function (cat) {
|
|
1357
1541
|
var isPromise = /promise|承诺/i.test(cat);
|
|
1358
1542
|
return h("div", { key: cat, className: "myt-worldGroup" },
|
|
1359
1543
|
h("div", { className: "myt-listHead" },
|
|
1360
|
-
cat, isPromise ? h("span", { className: "myt-markerTag" }, "
|
|
1544
|
+
cat, isPromise ? h("span", { className: "myt-markerTag" }, tt("index.promise")) : null,
|
|
1361
1545
|
h("span", { className: "myt-boardCount" }, memGroups[cat].length)),
|
|
1362
1546
|
memGroups[cat].map(function (m, i) {
|
|
1363
1547
|
return h("div", { key: i, className: "myt-memItem", "data-promise": isPromise ? "true" : undefined },
|
|
1364
1548
|
h("span", { className: "myt-clueText" }, orDash(m.content)),
|
|
1365
1549
|
m.source_chapter ? h("span", { className: "myt-memChapter" }, m.source_chapter) : null);
|
|
1366
1550
|
}));
|
|
1367
|
-
}) : h(Empty, { text: "
|
|
1551
|
+
}) : h(Empty, { text: tt("index.memEmpty") }))));
|
|
1368
1552
|
}
|
|
1369
1553
|
|
|
1370
1554
|
// ── 关系图 tab:canvas 力导向(斥力 + 弹簧 + 中心引力,rAF 循环)─────────
|
|
@@ -1573,7 +1757,7 @@ window.__ModuleLoader__.load({
|
|
|
1573
1757
|
tipRafRef.current = 0;
|
|
1574
1758
|
var e2 = hitEdge(p.x, p.y);
|
|
1575
1759
|
if (e2) {
|
|
1576
|
-
setTip({ x: p.x, y: p.y, text: (e2.type ? e2.type + ":" : "") + (e2.desc || "
|
|
1760
|
+
setTip({ x: p.x, y: p.y, text: (e2.type ? e2.type + ":" : "") + (e2.desc || tt("relations.noDesc")) });
|
|
1577
1761
|
} else {
|
|
1578
1762
|
setTip(function (prev) { return prev ? null : prev; });
|
|
1579
1763
|
}
|
|
@@ -1606,18 +1790,18 @@ window.__ModuleLoader__.load({
|
|
|
1606
1790
|
canvas.removeEventListener("dblclick", onDbl);
|
|
1607
1791
|
};
|
|
1608
1792
|
}, [snap]);
|
|
1609
|
-
if (!graph.nodes.length) return h(Empty, { text: "
|
|
1793
|
+
if (!graph.nodes.length) return h(Empty, { text: tt("relations.noData") });
|
|
1610
1794
|
return h("div", { className: "myt-stack" },
|
|
1611
1795
|
h("div", { className: "myt-card" },
|
|
1612
1796
|
h("div", { className: "myt-cardHead" },
|
|
1613
|
-
h("h3", { className: "myt-cardTitle" }, "
|
|
1614
|
-
h("span", { className: "myt-muted" }, graph.nodes.length
|
|
1797
|
+
h("h3", { className: "myt-cardTitle" }, tt("relations.title")),
|
|
1798
|
+
h("span", { className: "myt-muted" }, tf("relations.counts", { n: graph.nodes.length, m: graph.edges.length }))),
|
|
1615
1799
|
h("div", { className: "myt-graphWrap", ref: wrapRef },
|
|
1616
1800
|
h("canvas", { className: "myt-graphCanvas", ref: canvasRef, "data-dragging": "false" }),
|
|
1617
1801
|
tip ? h("div", { className: "myt-graphTip", style: { left: Math.min(tip.x + 12, 320) + "px", top: Math.max(tip.y - 34, 4) + "px" } }, tip.text) : null),
|
|
1618
|
-
h("div", { className: "myt-graphFoot" }, "
|
|
1802
|
+
h("div", { className: "myt-graphFoot" }, tt("relations.help"))),
|
|
1619
1803
|
graph.unlinked.length ? h("div", { className: "myt-card" },
|
|
1620
|
-
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, "
|
|
1804
|
+
h("div", { className: "myt-cardHead" }, h("h3", { className: "myt-cardTitle" }, tt("relations.unlinkedTitle"))),
|
|
1621
1805
|
h("div", { className: "myt-unlinked" }, graph.unlinked.map(function (n, i) {
|
|
1622
1806
|
return h("span", { key: n || i, className: "myt-unlinkedTag" }, n);
|
|
1623
1807
|
}))) : null);
|