@musnows/scriverse 0.5.9 → 0.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.js +62 -7
- package/dist/ai.js.map +1 -1
- package/dist/app.js +44 -5
- package/dist/app.js.map +1 -1
- package/dist/cli-contract.js +25 -0
- package/dist/cli-contract.js.map +1 -1
- package/dist/cli-core.js +3 -0
- package/dist/cli-core.js.map +1 -1
- package/dist/database.js +48 -1
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +609 -74
- package/dist/public/entity-version.js +2 -0
- package/dist/public/index.html +10 -2
- package/dist/public/markdown.js +6 -1
- package/dist/public/page-route.d.ts +1 -1
- package/dist/public/page-route.js +1 -0
- package/dist/public/stream-typewriter.d.ts +19 -0
- package/dist/public/stream-typewriter.js +79 -0
- package/dist/public/styles.css +75 -9
- package/dist/public/vditor-line-number-layout.d.ts +14 -0
- package/dist/public/vditor-line-number-layout.js +51 -0
- package/dist/public/work-permissions.d.ts +2 -2
- package/dist/public/work-permissions.js +8 -0
- package/dist/store.js +108 -2
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +12 -5
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/work-permissions.js +9 -0
- package/dist/work-permissions.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { levelLabel, relationshipCategoryLabel } from "./display-labels.js?v=20260725-enum-labels-zh";
|
|
2
2
|
|
|
3
3
|
export const VERSIONED_ENTITY_LABELS = Object.freeze({
|
|
4
|
+
draft: "创作草稿",
|
|
4
5
|
setting: "世界观设定",
|
|
5
6
|
race: "种族档案",
|
|
6
7
|
organization: "组织档案",
|
|
@@ -24,6 +25,7 @@ export function entityVersionSourceLabel(source) {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
export function entityVersionSnapshotSummary(type, snapshot = {}) {
|
|
28
|
+
if (type === "draft") return `${snapshot.draftType === "setting" ? "设定草稿" : "正文草稿"} · ${snapshot.title || "未命名草稿"}`;
|
|
27
29
|
if (type === "setting") return `${snapshot.category || "未分类"} · ${snapshot.title || "未命名设定"}`;
|
|
28
30
|
if (type === "race") return `${snapshot.name || "未命名种族"} · ${(snapshot.memberIds ?? []).length} 位角色`;
|
|
29
31
|
if (type === "organization") return `${snapshot.name || "未命名组织"} · ${(snapshot.memberIds ?? []).length} 位成员`;
|
package/dist/public/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest">
|
|
12
12
|
<link rel="stylesheet" href="/vendor/vditor/dist/index.css?v=3.11.2">
|
|
13
|
-
<link rel="stylesheet" href="/styles.css?v=
|
|
13
|
+
<link rel="stylesheet" href="/styles.css?v=20260730-table-wrap-menu-v1">
|
|
14
14
|
</head>
|
|
15
15
|
<body class="auth-pending">
|
|
16
16
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -123,6 +123,7 @@
|
|
|
123
123
|
</div>
|
|
124
124
|
<nav id="module-nav" class="module-nav" aria-label="作品模块">
|
|
125
125
|
<button type="button" data-module="editor" class="active"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>正文</button>
|
|
126
|
+
<button type="button" data-module="drafts"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20h4l11-11a2.8 2.8 0 0 0-4-4L4 16v4Z"/><path d="m13.5 6.5 4 4"/></svg>草稿</button>
|
|
126
127
|
<button type="button" data-module="settings"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m16 6 4 14"/><path d="M12 6v14"/><path d="M8 8v12"/><path d="M4 4v16"/></svg>设定库</button>
|
|
127
128
|
<button type="button" data-module="characters"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>角色</button>
|
|
128
129
|
<button type="button" data-module="organizations"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>组织</button>
|
|
@@ -333,6 +334,12 @@
|
|
|
333
334
|
<button id="add-line-annotation" type="button" role="menuitem">添加批注</button>
|
|
334
335
|
<button id="add-line-todo" type="button" role="menuitem">添加待办</button>
|
|
335
336
|
</div>
|
|
337
|
+
<div id="markdown-table-menu" class="markdown-table-menu hidden" role="menu" aria-label="Markdown 表格显示方式">
|
|
338
|
+
<button id="markdown-table-wrap-toggle" type="button" role="menuitemcheckbox" aria-checked="false">
|
|
339
|
+
<span>表格内容自动换行</span>
|
|
340
|
+
<small>关闭时可横向滚动</small>
|
|
341
|
+
</button>
|
|
342
|
+
</div>
|
|
336
343
|
<input id="new-import-file" class="hidden" type="file" accept=".txt,.docx">
|
|
337
344
|
<input id="cover-file" class="hidden" type="file" accept="image/png,image/jpeg,image/webp">
|
|
338
345
|
<input id="avatar-file" class="hidden" type="file" accept="image/png,image/jpeg,image/webp">
|
|
@@ -713,6 +720,7 @@
|
|
|
713
720
|
<legend>可分页模块单页数量</legend>
|
|
714
721
|
<small>以下模块独立设置,允许 10–100 条;默认均为 30 条。种族层级始终完整展示。</small>
|
|
715
722
|
<div class="pagination-settings-grid">
|
|
723
|
+
<label>草稿列表<input id="page-size-drafts" name="pageSizeDrafts" type="number" min="10" max="100" step="1" required aria-label="草稿列表单页数量"></label>
|
|
716
724
|
<label>设定库<input id="page-size-settings" name="pageSizeSettings" type="number" min="10" max="100" step="1" required aria-label="设定库单页数量"></label>
|
|
717
725
|
<label>角色列表<input id="page-size-characters" name="pageSizeCharacters" type="number" min="10" max="100" step="1" required aria-label="角色列表单页数量"></label>
|
|
718
726
|
<label>组织列表<input id="page-size-organizations" name="pageSizeOrganizations" type="number" min="10" max="100" step="1" required aria-label="组织列表单页数量"></label>
|
|
@@ -876,6 +884,6 @@
|
|
|
876
884
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
877
885
|
<script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
|
|
878
886
|
<script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
|
|
879
|
-
<script type="module" src="/app.js?v=
|
|
887
|
+
<script type="module" src="/app.js?v=20260730-table-wrap-menu-v1"></script>
|
|
880
888
|
</body>
|
|
881
889
|
</html>
|
package/dist/public/markdown.js
CHANGED
|
@@ -128,7 +128,12 @@ function tableAlignments(cells) {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
function renderMarkdownTable(headers, alignments, rows) {
|
|
131
|
-
const renderCell = (tag, value, alignment) =>
|
|
131
|
+
const renderCell = (tag, value, alignment) => {
|
|
132
|
+
const interaction = tag === "th"
|
|
133
|
+
? ' data-markdown-table-header tabindex="0" aria-haspopup="menu" title="右键或按 Shift+F10 设置表格换行"'
|
|
134
|
+
: "";
|
|
135
|
+
return `<${tag} class="markdown-align-${alignment}"${interaction}>${renderInlineMarkdown(value)}</${tag}>`;
|
|
136
|
+
};
|
|
132
137
|
const header = headers.map((cell, index) => renderCell("th", cell, alignments[index])).join("");
|
|
133
138
|
const body = rows.map((row) => {
|
|
134
139
|
const cells = headers.map((_header, index) => renderCell("td", row[index] ?? "", alignments[index])).join("");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type RestorableModule = "settings" | "characters" | "races" | "organizations" | "timeline" | "outlines" | "relationships" | "reviews" | "tasks" | "ai-settings";
|
|
1
|
+
export type RestorableModule = "drafts" | "settings" | "characters" | "races" | "organizations" | "timeline" | "outlines" | "relationships" | "reviews" | "tasks" | "ai-settings";
|
|
2
2
|
export type PageRoute =
|
|
3
3
|
| { view: "shelf" }
|
|
4
4
|
| { view: "editor"; workId: string; chapterId: string | null }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type StreamTypewriterProgress = {
|
|
2
|
+
visibleCharacters: number;
|
|
3
|
+
pendingCharacters: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type StreamTypewriter = {
|
|
7
|
+
append(value: unknown): void;
|
|
8
|
+
finish(): Promise<string>;
|
|
9
|
+
reveal(): string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function streamTypewriterBatchSize(pendingCharacters: number, finishingFrames?: number | null): number;
|
|
13
|
+
|
|
14
|
+
export function createStreamTypewriter<FrameHandle = number>(options: {
|
|
15
|
+
onRender: (text: string, progress: StreamTypewriterProgress) => void;
|
|
16
|
+
scheduleFrame?: (callback: () => void) => FrameHandle;
|
|
17
|
+
cancelFrame?: (handle: FrameHandle) => void;
|
|
18
|
+
reducedMotion?: boolean;
|
|
19
|
+
}): StreamTypewriter;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const DEFAULT_FINISH_FRAMES = 36;
|
|
2
|
+
const DEFAULT_BACKLOG_FRAMES = 18;
|
|
3
|
+
|
|
4
|
+
export function streamTypewriterBatchSize(pendingCharacters, finishingFrames = null) {
|
|
5
|
+
const pending = Math.max(0, Math.floor(Number(pendingCharacters) || 0));
|
|
6
|
+
if (pending === 0) return 0;
|
|
7
|
+
const targetFrames = finishingFrames === null
|
|
8
|
+
? DEFAULT_BACKLOG_FRAMES
|
|
9
|
+
: Math.max(1, Math.floor(Number(finishingFrames) || 1));
|
|
10
|
+
return Math.max(1, Math.ceil(pending / targetFrames));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createStreamTypewriter({
|
|
14
|
+
onRender,
|
|
15
|
+
scheduleFrame = (callback) => window.requestAnimationFrame(callback),
|
|
16
|
+
cancelFrame = (handle) => window.cancelAnimationFrame(handle),
|
|
17
|
+
reducedMotion = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true
|
|
18
|
+
}) {
|
|
19
|
+
if (typeof onRender !== "function") throw new TypeError("onRender must be a function");
|
|
20
|
+
|
|
21
|
+
const visibleCharacters = [];
|
|
22
|
+
const pendingCharacters = [];
|
|
23
|
+
const idleResolvers = [];
|
|
24
|
+
let scheduledFrame = null;
|
|
25
|
+
let finishingFrames = null;
|
|
26
|
+
|
|
27
|
+
const snapshot = () => visibleCharacters.join("");
|
|
28
|
+
const resolveIdle = () => {
|
|
29
|
+
if (pendingCharacters.length || scheduledFrame !== null) return;
|
|
30
|
+
const value = snapshot();
|
|
31
|
+
for (const resolve of idleResolvers.splice(0)) resolve(value);
|
|
32
|
+
};
|
|
33
|
+
const render = () => {
|
|
34
|
+
onRender(snapshot(), {
|
|
35
|
+
visibleCharacters: visibleCharacters.length,
|
|
36
|
+
pendingCharacters: pendingCharacters.length
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
const schedule = () => {
|
|
40
|
+
if (scheduledFrame !== null || pendingCharacters.length === 0) return;
|
|
41
|
+
scheduledFrame = scheduleFrame(() => {
|
|
42
|
+
scheduledFrame = null;
|
|
43
|
+
const batchSize = reducedMotion
|
|
44
|
+
? pendingCharacters.length
|
|
45
|
+
: streamTypewriterBatchSize(pendingCharacters.length, finishingFrames);
|
|
46
|
+
visibleCharacters.push(...pendingCharacters.splice(0, batchSize));
|
|
47
|
+
if (finishingFrames !== null) finishingFrames = Math.max(1, finishingFrames - 1);
|
|
48
|
+
render();
|
|
49
|
+
if (pendingCharacters.length) schedule();
|
|
50
|
+
else resolveIdle();
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
append(value) {
|
|
56
|
+
const characters = Array.from(String(value ?? ""));
|
|
57
|
+
if (!characters.length) return;
|
|
58
|
+
pendingCharacters.push(...characters);
|
|
59
|
+
schedule();
|
|
60
|
+
},
|
|
61
|
+
finish() {
|
|
62
|
+
if (!pendingCharacters.length && scheduledFrame === null) return Promise.resolve(snapshot());
|
|
63
|
+
finishingFrames = reducedMotion ? 1 : DEFAULT_FINISH_FRAMES;
|
|
64
|
+
schedule();
|
|
65
|
+
return new Promise((resolve) => idleResolvers.push(resolve));
|
|
66
|
+
},
|
|
67
|
+
reveal() {
|
|
68
|
+
if (scheduledFrame !== null) {
|
|
69
|
+
cancelFrame(scheduledFrame);
|
|
70
|
+
scheduledFrame = null;
|
|
71
|
+
}
|
|
72
|
+
visibleCharacters.push(...pendingCharacters.splice(0));
|
|
73
|
+
finishingFrames = null;
|
|
74
|
+
render();
|
|
75
|
+
resolveIdle();
|
|
76
|
+
return snapshot();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
package/dist/public/styles.css
CHANGED
|
@@ -509,7 +509,7 @@ input[type="checkbox"][hidden] { display: none; }
|
|
|
509
509
|
|
|
510
510
|
.left-panel, .ai-panel { position: relative; min-height: 0; background: var(--panel); }
|
|
511
511
|
.left-panel { display: flex; flex-direction: column; border-right: 1px solid var(--line); padding: 0; overflow: hidden; }
|
|
512
|
-
.left-panel-body { flex: 1 1 auto; min-height: 0; padding: 18px
|
|
512
|
+
.left-panel-body { flex: 1 1 auto; min-height: 0; padding: 18px 28px 16px 14px; overflow-y: auto; scrollbar-gutter: stable; }
|
|
513
513
|
.ai-panel { border-left: 1px solid var(--line); display: flex; flex-direction: column; padding: 14px 16px 18px; min-width: 0; }
|
|
514
514
|
.panel-collapse-button { z-index: 8; flex: 0 0 auto; width: 24px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: var(--panel); color: var(--muted); font-size: 18px; line-height: 1; }
|
|
515
515
|
.ai-heading #ai-panel-toggle { flex-basis: 30px; width: 30px; height: 30px; }
|
|
@@ -1275,6 +1275,10 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1275
1275
|
.character-filter-toolbar-actions { display: flex; align-items: center; justify-content: flex-end; gap: 12px; min-height: 38px; }
|
|
1276
1276
|
.character-filter-result-count { color: var(--muted); font-size: 11px; white-space: nowrap; }
|
|
1277
1277
|
.character-filter-toolbar-actions button:disabled { cursor: default; opacity: .45; }
|
|
1278
|
+
.draft-filter-toolbar { display: flex; align-items: end; gap: 10px; margin-bottom: 18px; padding: 12px 14px; border: 1px solid var(--line); background: var(--surface-soft); }
|
|
1279
|
+
.draft-filter-toolbar label { color: var(--muted); font-size: 11px; }
|
|
1280
|
+
.draft-filter-toolbar select { min-width: 180px; padding: 8px 10px; background: var(--surface); }
|
|
1281
|
+
.draft-filter-toolbar span { margin-left: auto; align-self: center; color: var(--muted); font-size: 11px; }
|
|
1278
1282
|
.character-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
1279
1283
|
.character-duplicate-review { grid-column: 1 / -1; }
|
|
1280
1284
|
.character-duplicate-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin: 14px 0; }
|
|
@@ -1343,9 +1347,12 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1343
1347
|
border-radius: 5px;
|
|
1344
1348
|
background: var(--surface-soft);
|
|
1345
1349
|
}
|
|
1346
|
-
.task-auto-run-
|
|
1350
|
+
.task-auto-run-header { display: grid; grid-column: 1; grid-template-columns: minmax(0, 1fr) auto; align-items: start; gap: 12px; }
|
|
1351
|
+
.task-auto-run-copy { display: grid; gap: 4px; }
|
|
1347
1352
|
.task-auto-run-copy strong { font-size: 13px; }
|
|
1348
1353
|
.task-auto-run-copy small { color: var(--muted); font-size: 10px; line-height: 1.5; }
|
|
1354
|
+
.task-auto-run-actions { display: flex; align-items: start; justify-content: flex-end; min-height: 32px; }
|
|
1355
|
+
.task-auto-run-actions:empty { display: none; }
|
|
1349
1356
|
.task-auto-run-controls {
|
|
1350
1357
|
display: flex;
|
|
1351
1358
|
grid-column: 1;
|
|
@@ -1558,6 +1565,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1558
1565
|
.task-result-json-content textarea { width: 100%; min-height: 360px; max-height: 52vh; resize: vertical; overflow: auto; margin: 0; padding: 10px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface-soft); color: var(--ink); font-family: var(--font-latin), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 10px; line-height: 1.55; tab-size: 2; white-space: pre; }
|
|
1559
1566
|
.task-detail-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin-top: 12px; }
|
|
1560
1567
|
.task-detail-actions small { color: var(--muted); }
|
|
1568
|
+
.task-rerun-model-intro { grid-column: 1 / -1; color: var(--muted); }
|
|
1561
1569
|
.relationship-identity-repair-entry { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-top: 10px; padding: 12px; border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--line)); border-radius: 6px; background: color-mix(in srgb, var(--accent) 7%, var(--surface)); }
|
|
1562
1570
|
.relationship-identity-repair-entry > div { display: grid; gap: 4px; }
|
|
1563
1571
|
.relationship-identity-repair-entry strong { color: var(--ink); }
|
|
@@ -1894,6 +1902,40 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1894
1902
|
.outline-summary strong { font-size: 26px; }.outline-summary span { color: var(--muted); font-size: 10px; }
|
|
1895
1903
|
.planning-section + .planning-section { margin-top: 42px; }.section-title { display: flex; justify-content: space-between; margin-bottom: 15px; }.section-title h2 { margin: 0; font-size: 21px; }
|
|
1896
1904
|
.foreshadow-card.is-overdue { border-color: #b55743; box-shadow: inset 3px 0 #b55743; }.foreshadow-links { margin-top: 13px; }
|
|
1905
|
+
.foreshadow-editor-section { display: grid; gap: 14px; padding: 18px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-soft); }
|
|
1906
|
+
.foreshadow-editor-section > header, .foreshadow-occurrence-toolbar > div { display: grid; gap: 4px; }
|
|
1907
|
+
.foreshadow-editor-section h3 { margin: 0; color: var(--ink); font-size: 14px; font-weight: 650; }
|
|
1908
|
+
.foreshadow-editor-section header p, .foreshadow-occurrence-toolbar p { margin: 0; color: var(--muted); font-size: 10px; line-height: 1.5; }
|
|
1909
|
+
.foreshadow-overview-grid { display: grid; grid-template-columns: minmax(220px, 1.5fr) repeat(2, minmax(130px, .65fr)) minmax(220px, 1.2fr); gap: 12px; }
|
|
1910
|
+
.foreshadow-overview-description textarea, .foreshadow-resolution-field textarea { min-height: 108px; }
|
|
1911
|
+
.foreshadow-occurrence-field { gap: 16px; }
|
|
1912
|
+
.foreshadow-occurrence-toolbar { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; }
|
|
1913
|
+
.foreshadow-occurrence-toolbar button, .foreshadow-occurrence-remove { flex: 0 0 auto; border: 1px solid var(--line); background: var(--surface); color: var(--ink); }
|
|
1914
|
+
.foreshadow-occurrence-rows { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; align-items: start; }
|
|
1915
|
+
.foreshadow-occurrence-row { --occurrence-color: var(--accent); position: relative; min-width: 0; display: grid; gap: 12px; margin: 0; padding: 14px 14px 15px; border: 1px solid color-mix(in srgb, var(--occurrence-color) 35%, var(--line)); border-left: 4px solid var(--occurrence-color); border-radius: 6px; background: color-mix(in srgb, var(--occurrence-color) 5%, var(--surface)); }
|
|
1916
|
+
.foreshadow-occurrence-row[data-occurrence-role="setup"] { --occurrence-color: #9a6547; }
|
|
1917
|
+
.foreshadow-occurrence-row[data-occurrence-role="reminder"] { --occurrence-color: #80713d; }
|
|
1918
|
+
.foreshadow-occurrence-row[data-occurrence-role="payoff"] { --occurrence-color: #49745c; }
|
|
1919
|
+
.foreshadow-occurrence-row legend { display: flex; max-width: calc(100% - 94px); align-items: center; gap: 7px; padding: 0 7px; color: var(--ink); }
|
|
1920
|
+
.foreshadow-occurrence-row legend strong { font-size: 11px; }
|
|
1921
|
+
.foreshadow-occurrence-row legend small { min-width: 0; overflow: hidden; color: var(--muted); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
|
|
1922
|
+
.foreshadow-occurrence-role-badge { flex: 0 0 auto; padding: 3px 7px; border: 1px solid color-mix(in srgb, var(--occurrence-color) 55%, var(--line)); border-radius: 12px; background: color-mix(in srgb, var(--occurrence-color) 12%, var(--surface)); color: var(--occurrence-color); font-size: 9px; font-weight: 700; }
|
|
1923
|
+
.foreshadow-occurrence-remove { position: absolute; top: 9px; right: 12px; padding: 5px 8px; font-size: 10px; }
|
|
1924
|
+
.foreshadow-occurrence-grid { display: grid; grid-template-columns: minmax(110px, .55fr) minmax(180px, 1.45fr); gap: 10px; }
|
|
1925
|
+
.dialog-fields .foreshadow-occurrence-row textarea { min-height: 68px; }
|
|
1926
|
+
.foreshadow-occurrence-empty { margin: 0; padding: 12px; border: 1px dashed var(--line); color: var(--muted); font-size: 11px; text-align: center; }
|
|
1927
|
+
@media (max-width: 900px) {
|
|
1928
|
+
.foreshadow-overview-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
1929
|
+
.foreshadow-occurrence-rows { grid-template-columns: minmax(0, 1fr); }
|
|
1930
|
+
}
|
|
1931
|
+
@media (max-width: 620px) {
|
|
1932
|
+
.foreshadow-occurrence-toolbar { align-items: stretch; flex-direction: column; }
|
|
1933
|
+
.foreshadow-occurrence-toolbar button { align-self: flex-start; }
|
|
1934
|
+
.foreshadow-overview-grid { grid-template-columns: minmax(0, 1fr); }
|
|
1935
|
+
.foreshadow-occurrence-grid { grid-template-columns: minmax(0, 1fr); }
|
|
1936
|
+
.foreshadow-occurrence-row legend { max-width: calc(100% - 78px); flex-wrap: wrap; }
|
|
1937
|
+
.foreshadow-occurrence-row legend small { flex-basis: 100%; }
|
|
1938
|
+
}
|
|
1897
1939
|
.outline-list { display: grid; gap: 8px; }.outline-row { display: grid; grid-template-columns: minmax(150px, 1.1fr) repeat(3, minmax(130px, 1fr)) auto; gap: 16px; align-items: start; padding: 15px; border: 1px solid var(--line); background: var(--surface-soft); }
|
|
1898
1940
|
.outline-row small { color: var(--accent); font-size: 9px; }.outline-row h3 { margin: 4px 0 0; font-size: 14px; }.outline-row b { font-size: 10px; }.outline-row p { margin: 5px 0 0; color: var(--muted); font-size: 10px; line-height: 1.45; white-space: pre-wrap; }
|
|
1899
1941
|
.outline-row.is-complete { opacity: .66; }.outline-actions { display: grid; gap: 7px; justify-items: end; }.outline-actions span { color: var(--accent); font-size: 9px; }.outline-actions button { border: 1px solid var(--line); background: transparent; border-radius: 3px; padding: 5px 8px; font-size: 9px; }
|
|
@@ -1996,6 +2038,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1996
2038
|
.assistant-message > .message-heading, .user-message > .message-heading { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 5px; opacity: .65; font-size: 9px; }
|
|
1997
2039
|
.message-heading > span { letter-spacing: .12em; }.message-heading time { flex: 0 0 auto; font-variant-numeric: tabular-nums; letter-spacing: .03em; }
|
|
1998
2040
|
.message-body { min-width: 0; overflow-wrap: anywhere; }
|
|
2041
|
+
.is-streaming .message-body:empty::after, .is-streaming .message-body > :last-child::after { content: ""; display: inline-block; width: 2px; height: 1.05em; margin-left: 3px; border-radius: 1px; background: var(--accent-dark); vertical-align: -.16em; animation: ai-stream-cursor 850ms steps(1, end) infinite; }
|
|
1999
2042
|
.message-card-actions { position: absolute; right: 0; bottom: -31px; display: flex; align-items: center; gap: 12px; height: 24px; }
|
|
2000
2043
|
.message-card-actions button { display: inline-flex; align-items: center; gap: 4px; min-width: 0; padding: 3px 2px; border: 0; background: transparent; color: var(--muted); font-size: 9px; }
|
|
2001
2044
|
.message-card-actions button:hover, .message-card-actions button:focus-visible { color: var(--accent-dark); }
|
|
@@ -2014,6 +2057,11 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2014
2057
|
.message-body .markdown-table-scroll:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
2015
2058
|
.message-body .markdown-table-scroll table { width: max-content; min-width: 100%; border-collapse: collapse; }
|
|
2016
2059
|
.message-body .markdown-table-scroll th, .message-body .markdown-table-scroll td { padding: 7px 9px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); overflow-wrap: normal; white-space: nowrap; }
|
|
2060
|
+
.message-body .markdown-table-scroll th { cursor: context-menu; }
|
|
2061
|
+
.message-body .markdown-table-scroll th:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
|
|
2062
|
+
.message-body .markdown-table-scroll.is-wrapping { overflow-x: hidden; scrollbar-gutter: auto; }
|
|
2063
|
+
.message-body .markdown-table-scroll.is-wrapping table { width: 100%; table-layout: fixed; }
|
|
2064
|
+
.message-body .markdown-table-scroll.is-wrapping th, .message-body .markdown-table-scroll.is-wrapping td { overflow-wrap: anywhere; white-space: normal; word-break: break-word; }
|
|
2017
2065
|
.message-body .markdown-table-scroll th:last-child, .message-body .markdown-table-scroll td:last-child { border-right: 0; }
|
|
2018
2066
|
.message-body .markdown-table-scroll tbody tr:last-child td { border-bottom: 0; }
|
|
2019
2067
|
.message-body .markdown-table-scroll th { background: var(--surface-soft); font-weight: 600; }
|
|
@@ -2039,7 +2087,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2039
2087
|
.ai-process-intermediate-step .ai-process-step-body { border-left-color: var(--muted); }
|
|
2040
2088
|
.ai-process-tool-step { padding-top: 1px; }
|
|
2041
2089
|
@keyframes ai-process-pulse { 0%, 100% { opacity: .45; transform: scale(.8); } 50% { opacity: 1; transform: scale(1); } }
|
|
2042
|
-
@
|
|
2090
|
+
@keyframes ai-stream-cursor { 0%, 54% { opacity: 1; } 55%, 100% { opacity: 0; } }
|
|
2091
|
+
@media (prefers-reduced-motion: reduce) { .is-streaming .ai-process-details[open] > summary::before, .is-streaming .message-body:empty::after, .is-streaming .message-body > :last-child::after { animation: none; } }
|
|
2043
2092
|
.ai-tool-call-list { display: grid; gap: 5px; margin-top: 10px; }
|
|
2044
2093
|
.ai-tool-call-summary { display: flex; align-items: center; justify-content: space-between; width: 100%; min-width: 0; padding: 7px 9px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface-soft); color: var(--muted); font: 9px/1.4 var(--font-latin), monospace; text-align: left; }
|
|
2045
2094
|
.ai-tool-call-summary::after { content: "查看详情"; flex: 0 0 auto; margin-left: 8px; color: var(--accent-dark); }
|
|
@@ -2184,6 +2233,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2184
2233
|
.chapter-insight-toast-heading { display: flex; min-width: 0; align-items: center; gap: 8px; }
|
|
2185
2234
|
.chapter-insight-toast-heading strong { font-size: 12px; }
|
|
2186
2235
|
.chapter-insight-toast-heading span { overflow: hidden; color: var(--accent-dark); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
|
|
2236
|
+
.chapter-insight-toast-heading .chapter-insight-toast-chapter-title { min-width: 0; color: var(--ink); font-size: 11px; }
|
|
2187
2237
|
.chapter-insight-toast-close { flex: 0 0 auto; min-height: 28px; padding: 5px 9px; font-size: 10px; }
|
|
2188
2238
|
.chapter-insight-toast-body { display: grid; gap: 10px; min-height: 0; padding: 12px 14px 14px; overflow-y: auto; color: var(--muted); font-size: 11px; line-height: 1.6; overscroll-behavior: contain; }
|
|
2189
2239
|
.chapter-insight-toast-body p { margin: 0; white-space: pre-wrap; }
|
|
@@ -2199,6 +2249,13 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2199
2249
|
.line-citation-menu { position: fixed; z-index: 82; display: grid; min-width: 230px; padding: 8px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); box-shadow: 0 14px 38px rgba(49,42,32,.22); }
|
|
2200
2250
|
.line-citation-menu strong { padding: 5px 7px 8px; color: var(--muted); font-size: 9px; }
|
|
2201
2251
|
.line-citation-menu button { border: 0; border-radius: 4px; padding: 8px; background: transparent; text-align: left; font-size: 11px; }.line-citation-menu button:hover { background: var(--paper-deep); color: var(--accent-dark); }
|
|
2252
|
+
.markdown-table-menu { position: fixed; z-index: 84; min-width: 210px; padding: 7px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); box-shadow: 0 14px 38px rgba(49,42,32,.22); }
|
|
2253
|
+
.markdown-table-menu button { position: relative; display: grid; width: 100%; gap: 2px; padding: 8px 9px 8px 31px; border: 0; border-radius: 4px; background: transparent; color: var(--ink); text-align: left; }
|
|
2254
|
+
.markdown-table-menu button::before { content: ""; position: absolute; top: 12px; left: 10px; width: 11px; height: 11px; border: 1px solid var(--muted); border-radius: 2px; background: var(--surface); }
|
|
2255
|
+
.markdown-table-menu button[aria-checked="true"]::before { border-color: var(--accent); background: var(--accent); box-shadow: inset 0 0 0 3px var(--surface); }
|
|
2256
|
+
.markdown-table-menu button:hover, .markdown-table-menu button:focus-visible { background: var(--paper-deep); color: var(--accent-dark); }
|
|
2257
|
+
.markdown-table-menu button span { font-size: 11px; font-weight: 600; }
|
|
2258
|
+
.markdown-table-menu button small { color: var(--muted); font-size: 9px; }
|
|
2202
2259
|
.chapter-annotations-dialog { width: min(820px, 94vw); }
|
|
2203
2260
|
.chapter-annotations-list { display: grid; gap: 10px; max-height: 62vh; overflow: auto; padding: 18px 24px; }
|
|
2204
2261
|
.chapter-annotation-card { display: grid; gap: 8px; padding: 13px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-soft); }
|
|
@@ -2219,6 +2276,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2219
2276
|
.wide-dialog { width: min(800px, 92vw); }
|
|
2220
2277
|
.trace-dialog { width: min(1180px, 94vw); }
|
|
2221
2278
|
.trace-dialog .dialog-fields { max-height: 68vh; }
|
|
2279
|
+
.large-dialog { width: min(1120px, 94vw); max-height: 94dvh; }
|
|
2280
|
+
.large-dialog .dialog-fields { max-height: 72dvh; padding: 24px 28px; }
|
|
2222
2281
|
.dialog::backdrop { background: rgba(34,30,25,.42); backdrop-filter: blur(3px); }
|
|
2223
2282
|
.dialog-header { display: flex; justify-content: space-between; align-items: flex-start; padding: 22px 24px 16px; border-bottom: 1px solid var(--line); }
|
|
2224
2283
|
.dialog-header h2 { margin: 0; font-size: 23px; font-weight: 500; }
|
|
@@ -2243,6 +2302,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2243
2302
|
outline: none;
|
|
2244
2303
|
}
|
|
2245
2304
|
.dialog-fields { display: grid; gap: 14px; padding: 22px 24px; max-height: 58vh; overflow-y: auto; }
|
|
2305
|
+
.dialog-fields .form-field-note { margin: 0; color: var(--muted); font-size: 11px; line-height: 1.65; }
|
|
2246
2306
|
.dialog-fields label { display: grid; gap: 6px; color: var(--muted); font-size: 11px; }
|
|
2247
2307
|
.dialog-fields input, .dialog-fields textarea, .dialog-fields select { width: 100%; padding: 9px 10px; font-size: 13px; }
|
|
2248
2308
|
.dialog-fields textarea { resize: vertical; min-height: 90px; line-height: 1.6; }
|
|
@@ -2440,6 +2500,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2440
2500
|
.import-mode-dialog .dialog-actions { flex-wrap: wrap; }
|
|
2441
2501
|
.character-filter-toolbar { grid-template-columns: minmax(0, 1fr); }
|
|
2442
2502
|
.character-filter-toolbar-actions { justify-content: flex-start; }
|
|
2503
|
+
.draft-filter-toolbar { align-items: stretch; flex-direction: column; }
|
|
2504
|
+
.draft-filter-toolbar select { width: 100%; min-width: 0; }
|
|
2505
|
+
.draft-filter-toolbar span { margin-left: 0; }
|
|
2443
2506
|
}
|
|
2444
2507
|
.entity-editor-open { overflow: hidden; }
|
|
2445
2508
|
.preview-record-card { cursor: pointer; }
|
|
@@ -2490,6 +2553,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2490
2553
|
.setting-markdown-heading, .markdown-editor-field-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 24px; color: var(--ink); font-size: 14px; }
|
|
2491
2554
|
.setting-markdown-heading small, .markdown-editor-field-heading small { color: var(--muted); font-size: 10px; }
|
|
2492
2555
|
.markdown-editor-field { display: grid; gap: 10px; min-width: 0; }
|
|
2556
|
+
.markdown-editor-field > span { color: var(--muted); font-size: 11px; }
|
|
2493
2557
|
.markdown-editor-field .vditor-editor-host { min-height: 360px; }
|
|
2494
2558
|
.vditor-editor-host { width: 100%; height: 100%; min-width: 0; min-height: 0; }
|
|
2495
2559
|
.vditor-editor-host.vditor { height: 100% !important; min-height: 0; overflow: hidden; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--ink); }
|
|
@@ -2498,9 +2562,10 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2498
2562
|
.vditor-editor-host .vditor-toolbar { display: flex; align-items: center; flex: 0 0 auto; }
|
|
2499
2563
|
.vditor-editor-host .markdown-word-count { display: inline-flex; align-items: center; align-self: center; min-height: 28px; margin-left: 24px; padding: 0 10px; color: var(--muted); font: 10px/1 var(--font-latin), var(--font-cjk), sans-serif; white-space: nowrap; }
|
|
2500
2564
|
.vditor-editor-host .vditor-content { position: relative; }
|
|
2501
|
-
.vditor-editor-host .vditor-line-numbers { display: none; position: absolute; z-index: 1; inset: 0 auto 0 0; width: 42px; padding:
|
|
2502
|
-
.vditor-editor-host.vditor.show-line-numbers .vditor-line-numbers { display:
|
|
2503
|
-
.vditor-editor-host.vditor
|
|
2565
|
+
.vditor-editor-host .vditor-line-numbers { display: none; position: absolute; z-index: 1; inset: 0 auto 0 0; width: 42px; padding: 0; overflow: hidden; border-right: 1px solid var(--line); background: color-mix(in srgb, var(--surface-soft) 88%, transparent); color: var(--muted); font: 12px/1.75 var(--font-latin), monospace; text-align: right; user-select: none; pointer-events: none; will-change: transform; }
|
|
2566
|
+
.vditor-editor-host.vditor.show-line-numbers .vditor-line-numbers { display: block; }
|
|
2567
|
+
.vditor-editor-host .vditor-line-number { position: absolute; top: 0; right: 8px; left: 0; display: block; box-sizing: border-box; overflow: hidden; padding: 0; text-align: right; white-space: nowrap; }
|
|
2568
|
+
.vditor-editor-host.vditor.show-line-numbers .vditor-ir, .vditor-editor-host.vditor.show-line-numbers .vditor-wysiwyg, .vditor-editor-host.vditor.show-line-numbers .vditor-sv, .vditor-editor-host.vditor.show-line-numbers .vditor-sv textarea { padding-left: 42px; }
|
|
2504
2569
|
.vditor-editor-host .vditor-line-number-button[aria-pressed="true"] { color: var(--accent-dark); background: color-mix(in srgb, var(--accent) 16%, transparent); }
|
|
2505
2570
|
.vditor-editor-host .vditor-content { min-height: 0; }
|
|
2506
2571
|
.vditor-editor-host .vditor-reset { color: var(--ink); font-family: var(--font-cjk), var(--font-latin), sans-serif; }
|
|
@@ -2711,7 +2776,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2711
2776
|
}
|
|
2712
2777
|
|
|
2713
2778
|
@media (max-width: 1100px) {
|
|
2714
|
-
.left-panel-body { padding-inline: 10px; }
|
|
2779
|
+
.left-panel-body { padding-inline: 10px 24px; }
|
|
2715
2780
|
.ai-panel { padding-inline: 14px; }
|
|
2716
2781
|
.editor-toolbar { grid-template-areas: "path" "title" "actions"; grid-template-columns: minmax(0, 1fr); gap: 12px; padding-inline: 6%; }
|
|
2717
2782
|
.editor-actions { justify-content: flex-end; flex-wrap: wrap; }
|
|
@@ -2725,7 +2790,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2725
2790
|
.top-actions .ghost-button:not(.settings-button) { display: none; }
|
|
2726
2791
|
.book-shelf { grid-template-columns: repeat(3, minmax(180px, 1fr)); }
|
|
2727
2792
|
.task-auto-run-panel { grid-template-columns: minmax(0, 1fr); column-gap: 0; }
|
|
2728
|
-
.task-auto-run-
|
|
2793
|
+
.task-auto-run-header, .task-auto-run-controls, .task-auto-run-meta { grid-column: 1; }
|
|
2794
|
+
.task-auto-run-help, .task-auto-run-alert { grid-column: 1; }
|
|
2729
2795
|
.task-auto-run-progress { grid-column: 1; grid-row: auto; justify-self: stretch; width: 100%; }
|
|
2730
2796
|
.task-auto-run-progress-ring { display: none; }
|
|
2731
2797
|
.task-auto-run-progress-bar-layout { display: grid; gap: 6px; width: 100%; }
|
|
@@ -2963,7 +3029,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2963
3029
|
.settings-layout-toggle, .module-layout-toggle { width: 100%; }
|
|
2964
3030
|
.settings-layout-toggle button { flex: 1; }
|
|
2965
3031
|
|
|
2966
|
-
.dialog, .wide-dialog, .trace-dialog, .account-dialog {
|
|
3032
|
+
.dialog, .wide-dialog, .trace-dialog, .large-dialog, .account-dialog {
|
|
2967
3033
|
width: calc(100vw - 20px);
|
|
2968
3034
|
max-width: calc(100vw - 20px);
|
|
2969
3035
|
max-height: calc(100dvh - 20px);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type VditorLineRect = {
|
|
2
|
+
top: number;
|
|
3
|
+
height: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type VditorLineNumberRow = VditorLineRect & {
|
|
7
|
+
blank: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function normalizeVditorLineRects(rects: VditorLineRect[], tolerance?: number): VditorLineRect[];
|
|
11
|
+
export function buildVditorLineNumberRows(
|
|
12
|
+
rects: VditorLineRect[],
|
|
13
|
+
options?: { lineHeight?: number; blankLineCount?: number }
|
|
14
|
+
): VditorLineNumberRow[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function normalizeVditorLineRects(rects, tolerance = 1.5) {
|
|
2
|
+
const rows = [];
|
|
3
|
+
const sorted = rects
|
|
4
|
+
.filter((rect) => Number.isFinite(Number(rect.top)) && Number.isFinite(Number(rect.height)) && Number(rect.height) > 0)
|
|
5
|
+
.map((rect) => ({ top: Number(rect.top), height: Number(rect.height) }))
|
|
6
|
+
.sort((left, right) => left.top - right.top);
|
|
7
|
+
for (const rect of sorted) {
|
|
8
|
+
const current = rows[rows.length - 1];
|
|
9
|
+
if (current && Math.abs(current.top - rect.top) <= tolerance) {
|
|
10
|
+
current.height = Math.max(current.height, rect.height);
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
rows.push(rect);
|
|
14
|
+
}
|
|
15
|
+
return rows;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildVditorLineNumberRows(rects, { lineHeight = 0, blankLineCount = 0 } = {}) {
|
|
19
|
+
const normalizedLineHeight = Number(lineHeight) > 0 ? Number(lineHeight) : 0;
|
|
20
|
+
const normalizedBlankLineCount = Math.max(0, Math.floor(Number(blankLineCount) || 0));
|
|
21
|
+
const visualRows = normalizeVditorLineRects(rects);
|
|
22
|
+
if (visualRows.length === 0) {
|
|
23
|
+
return normalizedBlankLineCount > 0
|
|
24
|
+
? Array.from({ length: normalizedBlankLineCount }, (_, index) => ({ top: index * normalizedLineHeight, height: normalizedLineHeight, blank: true }))
|
|
25
|
+
: normalizedLineHeight > 0 ? [{ top: 0, height: normalizedLineHeight, blank: true }] : [];
|
|
26
|
+
}
|
|
27
|
+
const rows = [];
|
|
28
|
+
let remainingBlankLines = normalizedBlankLineCount;
|
|
29
|
+
for (const visualRow of visualRows) {
|
|
30
|
+
const height = normalizedLineHeight || visualRow.height;
|
|
31
|
+
const top = visualRow.top - (height - visualRow.height) / 2;
|
|
32
|
+
const previous = rows[rows.length - 1];
|
|
33
|
+
if (previous && remainingBlankLines > 0 && normalizedLineHeight > 0) {
|
|
34
|
+
const availableSlots = Math.max(0, Math.round((top - previous.top) / normalizedLineHeight) - 1);
|
|
35
|
+
const blankLines = Math.min(remainingBlankLines, availableSlots);
|
|
36
|
+
for (let index = 1; index <= blankLines; index += 1) {
|
|
37
|
+
rows.push({ top: previous.top + normalizedLineHeight * index, height: normalizedLineHeight, blank: true });
|
|
38
|
+
}
|
|
39
|
+
remainingBlankLines -= blankLines;
|
|
40
|
+
}
|
|
41
|
+
rows.push({ top, height, blank: false });
|
|
42
|
+
}
|
|
43
|
+
const last = rows[rows.length - 1];
|
|
44
|
+
let nextTop = last.top;
|
|
45
|
+
while (remainingBlankLines > 0 && normalizedLineHeight > 0) {
|
|
46
|
+
nextTop += normalizedLineHeight;
|
|
47
|
+
rows.push({ top: nextTop, height: normalizedLineHeight, blank: true });
|
|
48
|
+
remainingBlankLines -= 1;
|
|
49
|
+
}
|
|
50
|
+
return rows;
|
|
51
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type WorkPermissionModule = "prose" | "settings" | "characters" | "races" | "organizations" | "timeline" | "relationships" | "outlines" | "reviews" | "ai-chat" | "ai-analysis" | "ai-settings";
|
|
2
|
-
export type WorkUiModule = "editor" | "settings" | "characters" | "races" | "organizations" | "timeline" | "relationships" | "outlines" | "reviews" | "tasks" | "ai-settings";
|
|
1
|
+
export type WorkPermissionModule = "prose" | "drafts" | "settings" | "characters" | "races" | "organizations" | "timeline" | "relationships" | "outlines" | "reviews" | "ai-chat" | "ai-analysis" | "ai-settings";
|
|
2
|
+
export type WorkUiModule = "editor" | "drafts" | "settings" | "characters" | "races" | "organizations" | "timeline" | "relationships" | "outlines" | "reviews" | "tasks" | "ai-settings";
|
|
3
3
|
export type WorkModuleAccess = "none" | "read" | "write";
|
|
4
4
|
export type WorkModulePermissions = Record<WorkPermissionModule, WorkModuleAccess>;
|
|
5
5
|
export type WorkPermissionAware = { accessRole?: string | null; modulePermissions?: WorkModulePermissions };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const WORK_PERMISSION_MODULES = Object.freeze([
|
|
2
2
|
{ id: "prose", uiModule: "editor", label: "正文" },
|
|
3
|
+
{ id: "drafts", uiModule: "drafts", label: "草稿" },
|
|
3
4
|
{ id: "settings", uiModule: "settings", label: "设定库" },
|
|
4
5
|
{ id: "characters", uiModule: "characters", label: "角色" },
|
|
5
6
|
{ id: "races", uiModule: "races", label: "种族" },
|
|
@@ -21,6 +22,13 @@ const validAccess = new Set(["none", "read", "write"]);
|
|
|
21
22
|
function migrateLegacyModulePermissions(value) {
|
|
22
23
|
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
23
24
|
const migrated = { ...value };
|
|
25
|
+
if (!validAccess.has(migrated.drafts)) {
|
|
26
|
+
const prose = validAccess.has(migrated.prose) ? migrated.prose : "none";
|
|
27
|
+
const settings = validAccess.has(migrated.settings) ? migrated.settings : "none";
|
|
28
|
+
migrated.drafts = prose === "write" && settings === "write"
|
|
29
|
+
? "write"
|
|
30
|
+
: prose === "none" || settings === "none" ? "none" : "read";
|
|
31
|
+
}
|
|
24
32
|
const legacyAi = validAccess.has(migrated.ai) ? migrated.ai : null;
|
|
25
33
|
if (!validAccess.has(migrated["ai-chat"]) && legacyAi) migrated["ai-chat"] = legacyAi;
|
|
26
34
|
if (!validAccess.has(migrated["ai-analysis"]) && validAccess.has(migrated.ai)) {
|