@musnows/scriverse 0.4.9 → 0.4.10

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.
@@ -1 +1 @@
1
- {"version":3,"file":"collaboration-presence.js","sourceRoot":"","sources":["../src/collaboration-presence.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"collaboration-presence.js","sourceRoot":"","sources":["../src/collaboration-presence.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { buildRelationshipGraph, createGalaxyRenderer, renderRelationshipMindMap } from "/relationship-graph.js?v=20260725-galaxy-ripple";
2
2
  import { collapseExcessBlankLines, formatDateTime, normalizeParagraphSpacing } from "/text-formatting.js?v=20260713-saved-at-seconds";
3
- import { renderMarkdown } from "/markdown.js?v=20260722-inline-code";
3
+ import { renderMarkdown } from "/markdown.js?v=20260725-ordered-list";
4
4
  import { buildAiReferenceScope, findAiMention, listAiMentionOptions } from "/ai-mentions.js?v=20260716-chapter-references";
5
5
  import { shouldShowAiQuickActions } from "/ai-conversation.js?v=20260713-quick-actions";
6
6
  import { calculateLineNumberRowHeight, calculateLineNumberRowTop, calculateLineNumberTextOffset, calculateLineNumberTop } from "/line-number-layout.js?v=20260713-row-box-alignment";
@@ -29,8 +29,9 @@ import {
29
29
  searchResultTypeLabel,
30
30
  settingStatusLabel,
31
31
  taskScopeLabel,
32
- timelineStatusLabel
33
- } from "/display-labels.js?v=20260725-unified-permissions";
32
+ timelineStatusLabel,
33
+ characterStateFieldLabel
34
+ } from "/display-labels.js?v=20260725-state-field-zh";
34
35
  import { parsePageRoute, serializePageRoute } from "/page-route.js?v=20260723-knowledge-editor-page";
35
36
  import { splitRelationshipKeywordInput, splitRelationshipKeywords, uniqueRelationshipKeywords } from "/relationship-keywords.js?v=20260720-relationship-keyword-chips";
36
37
  import { tokenizeVisibleSpaces } from "/whitespace-visualization.js?v=20260718-visible-whitespace";
@@ -97,6 +98,9 @@ let presenceParticipants = [];
97
98
  let presenceHeartbeatTimer = null;
98
99
  let presenceHeartbeatQueued = null;
99
100
  let presenceHeartbeatRequest = 0;
101
+ const acknowledgedCollaborativeChangeIds = new Set();
102
+ let collaborativeChangePromptOpen = false;
103
+ let peerPageStale = false;
100
104
  let collaborationAutoSaveDisabled = false;
101
105
 
102
106
  let timelineMultiSelectEnabled = false;
@@ -547,14 +551,16 @@ async function refreshPresence() {
547
551
  return [];
548
552
  }
549
553
  try {
550
- const participants = await api(`/api/works/${encodeURIComponent(workId)}/presence`, {
554
+ const payload = await api(`/api/works/${encodeURIComponent(workId)}/presence`, {
551
555
  method: "POST",
552
556
  body: { clientId: presenceClientId, page },
553
557
  skipOptimisticVersion: true
554
558
  });
555
559
  if (state.work?.id === workId && presenceHeartbeatRequest === requestId) {
556
- presenceParticipants = participants;
560
+ presenceParticipants = Array.isArray(payload) ? payload : (payload?.participants ?? []);
557
561
  renderPresence();
562
+ const recentChanges = Array.isArray(payload) ? [] : (payload?.recentChanges ?? []);
563
+ void handleCollaborativeChanges(recentChanges);
558
564
  }
559
565
  } catch {
560
566
  if (state.work?.id === workId) renderPresence();
@@ -564,6 +570,58 @@ async function refreshPresence() {
564
570
  return presenceParticipants;
565
571
  }
566
572
 
573
+ function localEditingDirty() {
574
+ return Boolean(state.dirty || entityEditorDirty || characterSectionEditorDirty || knowledgeSectionEditorDirty);
575
+ }
576
+
577
+ async function handleCollaborativeChanges(recentChanges) {
578
+ if (!Array.isArray(recentChanges) || !recentChanges.length || collaborativeChangePromptOpen) return;
579
+ const localKey = presencePageKey(presencePageForRoute());
580
+ if (!localKey) return;
581
+ const selfId = state.user?.userId;
582
+ const incoming = recentChanges.filter((change) => (
583
+ change
584
+ && change.pageKey === localKey
585
+ && change.actorUserId
586
+ && change.actorUserId !== selfId
587
+ && change.id
588
+ && !acknowledgedCollaborativeChangeIds.has(change.id)
589
+ ));
590
+ if (!incoming.length) return;
591
+ const latest = incoming[0];
592
+ for (const change of incoming) acknowledgedCollaborativeChangeIds.add(change.id);
593
+ while (acknowledgedCollaborativeChangeIds.size > 200) {
594
+ const oldest = acknowledgedCollaborativeChangeIds.values().next().value;
595
+ acknowledgedCollaborativeChangeIds.delete(oldest);
596
+ }
597
+ peerPageStale = true;
598
+ collaborativeChangePromptOpen = true;
599
+ const dirtyHint = localEditingDirty()
600
+ ? "你当前页面还有未保存修改。请先把正在编辑的内容复制保存到别处(本页无法代为另存),再刷新页面后重新提交。"
601
+ : "请先确认本地没有需要保留的草稿;如有,请复制保存到别处(本页无法代为另存),再刷新页面后继续编辑。";
602
+ try {
603
+ const shouldReload = await confirmToast(`${latest.actorDisplayName || "协作者"}已更新「${latest.label || "当前页面"}」。${dirtyHint}`, {
604
+ title: "协作者已更新",
605
+ confirmLabel: "刷新页面",
606
+ cancelLabel: "稍后处理"
607
+ });
608
+ if (shouldReload) window.location.reload();
609
+ } finally {
610
+ collaborativeChangePromptOpen = false;
611
+ }
612
+ }
613
+
614
+ async function confirmPeerStaleSave() {
615
+ if (!peerPageStale) return true;
616
+ const confirmed = await confirmToast("协作者已更新当前页面。请先把正在编辑的内容复制保存到别处(本页无法代为另存),再刷新页面后重新提交。继续保存可能覆盖对方修改或触发冲突。", {
617
+ title: "页面内容已过期",
618
+ confirmLabel: "仍然保存",
619
+ cancelLabel: "暂不保存"
620
+ });
621
+ if (confirmed) peerPageStale = false;
622
+ return confirmed;
623
+ }
624
+
567
625
  function schedulePresenceHeartbeat() {
568
626
  if (presenceHeartbeatQueued !== null) clearTimeout(presenceHeartbeatQueued);
569
627
  presenceHeartbeatQueued = setTimeout(() => {
@@ -574,6 +632,7 @@ function schedulePresenceHeartbeat() {
574
632
 
575
633
  async function confirmConcurrentSave() {
576
634
  await refreshPresence();
635
+ if (!(await confirmPeerStaleSave())) return false;
577
636
  const localKey = presencePageKey(presencePageForRoute());
578
637
  const peers = presenceParticipants.filter((participant) => participant.clientId !== presenceClientId && participant.page.key === localKey);
579
638
  if (!peers.length) return true;
@@ -2735,7 +2794,7 @@ function resetWorkScopedUiCaches() {
2735
2794
  async function selectWork(workId, preferredChapterId = null) {
2736
2795
  const discarding = state.work?.id !== workId && state.dirty;
2737
2796
  if (discarding && !(await confirmDiscardChanges())) return false;
2738
- const nextWork = await api(`/api/works/${workId}?page=1&limit=100`);
2797
+ const nextWork = await api(`/api/works/${workId}`);
2739
2798
  if (state.work?.id !== nextWork.id) resetWorkScopedUiCaches();
2740
2799
  if (discarding) setSaveState("就绪");
2741
2800
  $("#app").classList.remove("shelf-mode");
@@ -3403,14 +3462,14 @@ async function renderCharacters(page = characterListPage) {
3403
3462
  ${item.aliases.length ? `<div class="character-aliases"><b>别名</b>${item.aliases.map((alias) => `<span class="pill">${esc(alias)}</span>`).join("")}</div>` : ""}
3404
3463
  ${item.code ? `<div class="character-code"><b>编号</b><span class="pill">${esc(item.code)}</span></div>` : ""}
3405
3464
  ${item.species ? `<div class="character-species"><b>种族</b><span class="pill">${esc(racePathLabel(item.race) || item.species)}</span></div>` : ""}
3406
- ${details.length ? `<dl class="character-detail-list">${details.slice(0, 4).map((detail) => `<div><dt>${esc(detail.label)}</dt><dd>${esc(detail.value)}</dd></div>`).join("")}</dl>` : ""}
3465
+ ${details.length ? `<dl class="character-detail-list">${details.slice(0, 4).map((detail) => `<div><dt>${esc(characterStateFieldLabel(detail.label))}</dt><dd>${esc(detail.value)}</dd></div>`).join("")}</dl>` : ""}
3407
3466
  <div class="organization-links"><b>所属组织</b>${(item.organizations ?? []).length ? item.organizations.map((organization) => `<span class="pill organization-pill">${esc(organization.name)}</span>`).join("") : '<span class="organization-empty">未加入组织</span>'}</div>
3408
- ${item.profile?.summary ? `<p class="character-summary">${esc(item.profile.summary)}</p>` : `<p>${esc(Object.entries(item.currentState).map(([key, value]) => `${key}:${value}`).join("\n") || "尚未记录当前状态")}</p>`}
3467
+ ${item.profile?.summary ? `<p class="character-summary">${esc(item.profile.summary)}</p>` : `<p>${esc(Object.entries(item.currentState).map(([key, value]) => `${characterStateFieldLabel(key)}:${value}`).join("\n") || "尚未记录当前状态")}</p>`}
3409
3468
  ${item.profileSectionCount ? `<small class="character-section-count">${item.profileSectionCount} 个设定章节</small>` : ""}
3410
3469
  </article>`;
3411
3470
  }).join("")}</div>`;
3412
3471
  const characterRows = () => `<div class="module-row-list">${state.characters.map((item) => {
3413
- const preview = moduleRowPreview(item.profile?.summary || item.attributes?.identity || Object.entries(item.currentState).map(([key, value]) => `${key}:${value}`).join(" ") || "尚未记录当前状态");
3472
+ const preview = moduleRowPreview(item.profile?.summary || item.attributes?.identity || Object.entries(item.currentState).map(([key, value]) => `${characterStateFieldLabel(key)}:${value}`).join(" ") || "尚未记录当前状态");
3414
3473
  const meta = [
3415
3474
  item.code ? `编号 ${item.code}` : "",
3416
3475
  item.species ? (racePathLabel(item.race) || item.species) : "",
@@ -13,3 +13,4 @@ export function providerConnectionLabel(value: unknown): string;
13
13
  export function chapterVersionSourceLabel(value: unknown): string;
14
14
  export function occurrenceRoleLabel(value: unknown): string;
15
15
  export function searchResultTypeLabel(value: unknown): string;
16
+ export function characterStateFieldLabel(value: unknown): string;
@@ -75,3 +75,11 @@ export function occurrenceRoleLabel(value) {
75
75
  export function searchResultTypeLabel(value) {
76
76
  return enumLabel({ chapter: "章节", setting: "设定", character: "角色", race: "种族", organization: "组织" }, value, "其他资料");
77
77
  }
78
+
79
+ export function characterStateFieldLabel(value) {
80
+ const normalized = String(value ?? "").trim();
81
+ return enumLabel({
82
+ location: "位置",
83
+ condition: "状况"
84
+ }, normalized, normalized || "未命名字段");
85
+ }
@@ -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=20260725-module-header-scale">
13
+ <link rel="stylesheet" href="/styles.css?v=20260725-module-count-vditor-left-panel">
14
14
  </head>
15
15
  <body class="auth-pending">
16
16
  <section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
@@ -105,40 +105,42 @@
105
105
 
106
106
  <aside class="left-panel">
107
107
  <div id="left-panel-resize" class="panel-resize-handle" role="separator" aria-label="调整作品侧栏宽度" aria-orientation="vertical" tabindex="0"></div>
108
- <div class="left-actions">
109
- <div class="left-primary-actions">
110
- <label id="import-file-button" class="file-button" aria-label="导入 TXT / DOCX">
111
- <span class="import-file-label import-file-label-full" aria-hidden="true">导入 TXT / DOCX</span>
112
- <span class="import-file-label import-file-label-compact" aria-hidden="true">导入TXT/DOCX</span>
113
- <span class="import-file-label import-file-label-short" aria-hidden="true">导入</span>
114
- <input id="import-file" type="file" accept=".txt,.docx">
115
- </label>
116
- <button id="left-panel-toggle" class="panel-collapse-button" type="button" aria-label="收起作品侧栏" aria-expanded="true">‹</button>
108
+ <div class="left-panel-body">
109
+ <div class="left-actions">
110
+ <div class="left-primary-actions">
111
+ <label id="import-file-button" class="file-button" aria-label="导入 TXT / DOCX">
112
+ <span class="import-file-label import-file-label-full" aria-hidden="true">导入 TXT / DOCX</span>
113
+ <span class="import-file-label import-file-label-compact" aria-hidden="true">导入TXT/DOCX</span>
114
+ <span class="import-file-label import-file-label-short" aria-hidden="true">导入</span>
115
+ <input id="import-file" type="file" accept=".txt,.docx">
116
+ </label>
117
+ <button id="left-panel-toggle" class="panel-collapse-button" type="button" aria-label="收起作品侧栏" aria-expanded="true">‹</button>
118
+ </div>
117
119
  </div>
118
- </div>
119
- <nav id="module-nav" class="module-nav" aria-label="作品模块">
120
- <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>
121
- <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>
122
- <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>
123
- <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>
124
- <button type="button" data-module="timeline"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>时间轴</button>
125
- <button type="button" data-module="relationships"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="m8.59 13.51 6.83 3.98"/><path d="m15.41 6.51-6.82 3.98"/></svg>关系</button>
126
- <button type="button" data-module="races"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="4" r="2"/><circle cx="18" cy="8" r="2"/><circle cx="20" cy="16" r="2"/><path d="M9 10a5 5 0 0 1 5 5v3.5a3.5 3.5 0 0 1-6.84 1.045Q6.52 17.48 4.46 16.84A3.5 3.5 0 0 1 5.5 10Z"/></svg>种族</button>
127
- <button class="ai-analysis-entry" type="button" data-module="tasks"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></svg>AI 分析</button>
128
- <button id="module-more-button" type="button" aria-expanded="false"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg><span class="nav-label">更多</span></button>
129
- <button class="module-nav-secondary hidden" type="button" data-module="outlines"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></svg><span class="nav-label">大纲/伏笔</span></button>
130
- <button class="module-nav-secondary hidden" type="button" data-module="reviews"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="m9 14 2 2 4-4"/></svg>审核</button>
131
- <button class="module-nav-secondary hidden" type="button" data-module="ai-settings"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><rect width="16" height="16" x="4" y="4" rx="2"/><rect width="6" height="6" x="9" y="9" rx="1"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M2 9h2"/><path d="M20 15h2"/><path d="M20 9h2"/><path d="M9 2v2"/><path d="M9 20v2"/></svg>AI 设置</button>
132
- <button class="module-nav-secondary hidden" type="button" data-work-settings><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.09a2 2 0 0 1 1 1.74v.5a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z"/><circle cx="12" cy="12" r="3"/></svg>作品设置</button>
133
- </nav>
134
- <div class="panel-heading">
135
- <span>作品目录</span>
136
- <div class="panel-heading-actions">
137
- <span id="chapter-count">0 章</span>
138
- <button id="new-volume-button" class="add-button" type="button" aria-label="新建分卷" title="新建分卷">+</button>
120
+ <nav id="module-nav" class="module-nav" aria-label="作品模块">
121
+ <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>
122
+ <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>
123
+ <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>
124
+ <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>
125
+ <button type="button" data-module="timeline"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>时间轴</button>
126
+ <button type="button" data-module="relationships"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="m8.59 13.51 6.83 3.98"/><path d="m15.41 6.51-6.82 3.98"/></svg>关系</button>
127
+ <button type="button" data-module="races"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="4" r="2"/><circle cx="18" cy="8" r="2"/><circle cx="20" cy="16" r="2"/><path d="M9 10a5 5 0 0 1 5 5v3.5a3.5 3.5 0 0 1-6.84 1.045Q6.52 17.48 4.46 16.84A3.5 3.5 0 0 1 5.5 10Z"/></svg>种族</button>
128
+ <button class="ai-analysis-entry" type="button" data-module="tasks"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></svg>AI 分析</button>
129
+ <button id="module-more-button" type="button" aria-expanded="false"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg><span class="nav-label">更多</span></button>
130
+ <button class="module-nav-secondary hidden" type="button" data-module="outlines"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/></svg><span class="nav-label">大纲/伏笔</span></button>
131
+ <button class="module-nav-secondary hidden" type="button" data-module="reviews"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="m9 14 2 2 4-4"/></svg>审核</button>
132
+ <button class="module-nav-secondary hidden" type="button" data-module="ai-settings"><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><rect width="16" height="16" x="4" y="4" rx="2"/><rect width="6" height="6" x="9" y="9" rx="1"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M2 9h2"/><path d="M20 15h2"/><path d="M20 9h2"/><path d="M9 2v2"/><path d="M9 20v2"/></svg>AI 设置</button>
133
+ <button class="module-nav-secondary hidden" type="button" data-work-settings><svg class="nav-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.09a2 2 0 0 1 1 1.74v.5a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z"/><circle cx="12" cy="12" r="3"/></svg>作品设置</button>
134
+ </nav>
135
+ <div class="panel-heading">
136
+ <span>作品目录</span>
137
+ <div class="panel-heading-actions">
138
+ <span id="chapter-count">0 章</span>
139
+ <button id="new-volume-button" class="add-button" type="button" aria-label="新建分卷" title="新建分卷">+</button>
140
+ </div>
139
141
  </div>
142
+ <div id="novel-tree" class="novel-tree empty-copy">创建或选择作品后开始写作</div>
140
143
  </div>
141
- <div id="novel-tree" class="novel-tree empty-copy">创建或选择作品后开始写作</div>
142
144
  </aside>
143
145
 
144
146
  <main id="main-panel" class="main-panel">
@@ -731,6 +733,6 @@
731
733
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
732
734
  <script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
733
735
  <script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
734
- <script type="module" src="/app.js?v=20260725-module-header-scale"></script>
736
+ <script type="module" src="/app.js?v=20260725-collab-refresh-left-panel-resize"></script>
735
737
  </body>
736
738
  </html>
@@ -199,6 +199,22 @@ export function renderMarkdown(value) {
199
199
  }
200
200
  flushQuote();
201
201
  if (!line.trim()) {
202
+ // CommonMark 松散列表:同类型列表项之间的空行不结束列表,
203
+ // 否则每个条目会各自生成 <ol>/<ul>,有序序号会全部显示为 1。
204
+ if (list) {
205
+ let nextIndex = lineIndex + 1;
206
+ while (nextIndex < lines.length && !lines[nextIndex].trim()) nextIndex += 1;
207
+ if (nextIndex < lines.length) {
208
+ const nextListItem = lines[nextIndex].match(/^(\s*)([-+*]|\d+\.)\s+(.+)$/u);
209
+ if (nextListItem) {
210
+ const nextTag = /\d+\./u.test(nextListItem[2]) ? "ol" : "ul";
211
+ if (nextTag === list.tag) {
212
+ flushParagraph();
213
+ continue;
214
+ }
215
+ }
216
+ }
217
+ }
202
218
  flushBlocks();
203
219
  continue;
204
220
  }
@@ -485,7 +485,8 @@ input[type="checkbox"][hidden] { display: none; }
485
485
  @keyframes onboarding-popover-in { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
486
486
 
487
487
  .left-panel, .ai-panel { position: relative; min-height: 0; background: var(--panel); }
488
- .left-panel { border-right: 1px solid var(--line); padding: 18px 14px 16px; overflow-y: auto; }
488
+ .left-panel { display: flex; flex-direction: column; border-right: 1px solid var(--line); padding: 0; overflow: hidden; }
489
+ .left-panel-body { flex: 1 1 auto; min-height: 0; padding: 18px 14px 16px; overflow-y: auto; }
489
490
  .ai-panel { border-left: 1px solid var(--line); display: flex; flex-direction: column; padding: 18px; min-width: 0; }
490
491
  .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; }
491
492
  .ai-heading #ai-panel-toggle { flex-basis: 30px; width: 30px; height: 30px; }
@@ -496,7 +497,10 @@ input[type="checkbox"][hidden] { display: none; }
496
497
  body.is-panel-resizing { cursor: col-resize; user-select: none; }
497
498
  .app-shell.left-panel-collapsed { --left-panel-width: 42px !important; }.app-shell.ai-panel-collapsed { --ai-panel-width: 42px !important; }
498
499
  .app-shell.left-panel-collapsed .left-panel, .app-shell.ai-panel-collapsed .ai-panel { padding: 0; overflow: hidden; }
499
- .app-shell.left-panel-collapsed .left-panel > :not(.left-actions), .app-shell.ai-panel-collapsed .ai-panel > :not(.ai-heading) { display: none; }
500
+ .app-shell.left-panel-collapsed .left-panel > .panel-resize-handle,
501
+ .app-shell.left-panel-collapsed .left-panel-body > :not(.left-actions),
502
+ .app-shell.ai-panel-collapsed .ai-panel > :not(.ai-heading) { display: none; }
503
+ .app-shell.left-panel-collapsed .left-panel-body { padding: 0; overflow: hidden; }
500
504
  .app-shell.left-panel-collapsed .left-actions, .app-shell.ai-panel-collapsed .ai-heading { display: flex; justify-content: center; padding: 8px 0; border: 0; }
501
505
  .app-shell.left-panel-collapsed .left-actions > :not(.left-primary-actions), .app-shell.left-panel-collapsed .left-primary-actions > :not(.panel-collapse-button), .app-shell.ai-panel-collapsed .ai-heading > :not(.panel-collapse-button) { display: none; }
502
506
  .app-shell.left-panel-collapsed .left-primary-actions { display: flex; justify-content: center; }
@@ -1018,6 +1022,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1018
1022
  .module-header { display: flex; justify-content: space-between; align-items: flex-end; gap: 18px; padding-bottom: 24px; border-bottom: 1px solid var(--line); }
1019
1023
  .module-header-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
1020
1024
  .module-header-actions > [data-module-header-action] { order: 1; }
1025
+ .module-header-actions > .module-count-badge { order: 0; }
1021
1026
  .module-header-actions > #module-create-button { order: 2; min-height: 36px; }
1022
1027
  .module-count-badge { display: inline-grid; min-width: 28px; height: 28px; padding: 0 7px; place-items: center; border: 1px solid var(--line); border-radius: 14px; background: var(--surface); color: var(--accent-dark); font-family: var(--font-latin), monospace; font-size: 11px; font-weight: 650; }
1023
1028
  .module-header h1 { margin: 0 0 6px; font-weight: 500; font-size: 24px; line-height: 1.25; }
@@ -1859,6 +1864,10 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1859
1864
  .vditor-editor-host .vditor-reset { color: var(--ink); font-family: var(--font-cjk), var(--font-latin), sans-serif; }
1860
1865
  .vditor-editor-host .vditor-ir, .vditor-editor-host .vditor-sv, .vditor-editor-host .vditor-wysiwyg { background: var(--surface); color: var(--ink); }
1861
1866
  .vditor-editor-host .vditor-sv textarea { color: var(--ink); }
1867
+ .vditor-editor-host.vditor--dark .vditor-reset table { background: var(--surface); color: var(--ink); }
1868
+ .vditor-editor-host.vditor--dark .vditor-reset table tr { background: var(--surface); border-top-color: var(--line); }
1869
+ .vditor-editor-host.vditor--dark .vditor-reset table td, .vditor-editor-host.vditor--dark .vditor-reset table th { border-color: var(--line); color: var(--ink); }
1870
+ .vditor-editor-host.vditor--dark .vditor-reset table tbody tr:nth-child(2n) { background: var(--surface-soft); }
1862
1871
  .record-markdown-preview { display: -webkit-box; margin: 12px 0 8px; overflow: hidden; overflow-wrap: anywhere; color: var(--ink); font-size: 12px; line-height: 1.7; -webkit-box-orient: vertical; -webkit-line-clamp: 12; }
1863
1872
  .record-markdown-preview > :first-child, .knowledge-markdown-block .message-body > :first-child { margin-top: 0; }
1864
1873
  .record-markdown-preview > :last-child, .knowledge-markdown-block .message-body > :last-child { margin-bottom: 0; }
@@ -2058,7 +2067,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2058
2067
  }
2059
2068
 
2060
2069
  @media (max-width: 1100px) {
2061
- .left-panel { padding-inline: 10px; }
2070
+ .left-panel-body { padding-inline: 10px; }
2062
2071
  .ai-panel { padding-inline: 14px; }
2063
2072
  .editor-toolbar { grid-template-areas: "path" "title" "actions"; grid-template-columns: minmax(0, 1fr); gap: 12px; padding-inline: 6%; }
2064
2073
  .editor-actions { justify-content: flex-end; flex-wrap: wrap; }
@@ -2197,17 +2206,19 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2197
2206
  grid-column: auto;
2198
2207
  grid-row: auto;
2199
2208
  min-width: 0;
2200
- padding: 76px var(--mobile-gutter) 18px;
2209
+ padding: 0;
2201
2210
  border-left: 1px solid var(--line);
2202
2211
  border-bottom: 1px solid var(--line);
2203
2212
  box-shadow: -12px 0 36px rgba(36, 30, 24, .2);
2204
- overflow-y: auto;
2213
+ overflow: hidden;
2205
2214
  transform: translateX(0);
2206
2215
  transition: transform .2s ease;
2207
2216
  }
2208
- .app-shell.left-panel-collapsed .left-panel { padding: 76px var(--mobile-gutter) 18px; transform: translateX(100%); }
2209
- .app-shell.left-panel-collapsed .left-panel > :not(.left-actions) { display: block; }
2210
- .app-shell.left-panel-collapsed .left-panel > .module-nav { display: grid; }
2217
+ .left-panel-body { padding: 76px var(--mobile-gutter) 18px; }
2218
+ .app-shell.left-panel-collapsed .left-panel { transform: translateX(100%); }
2219
+ .app-shell.left-panel-collapsed .left-panel > .panel-resize-handle { display: none; }
2220
+ .app-shell.left-panel-collapsed .left-panel-body > :not(.left-actions) { display: block; }
2221
+ .app-shell.left-panel-collapsed .left-panel-body > .module-nav { display: grid; }
2211
2222
  .left-panel > .panel-resize-handle { display: none; }
2212
2223
  .left-actions { margin: 0 0 8px; }
2213
2224
  .left-primary-actions { gap: 6px; }
@@ -2223,7 +2234,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2223
2234
  .module-nav .nav-icon { width: 16px; height: 16px; }
2224
2235
  .module-nav .module-nav-secondary.hidden { display: none !important; }
2225
2236
  .panel-heading, #novel-tree { display: none; }
2226
- .app-shell.left-panel-collapsed .left-panel { padding: 8px var(--mobile-gutter) 0; }
2237
+ .app-shell.left-panel-collapsed .left-panel-body { padding: 8px var(--mobile-gutter) 0; }
2227
2238
  .app-shell.left-panel-collapsed .left-actions { display: block; padding: 0; }
2228
2239
 
2229
2240
  .main-panel {
package/dist/store.js CHANGED
@@ -764,9 +764,9 @@ export class Store {
764
764
  const chapterRows = this.db.all(`SELECT id, work_id, volume_id, title, chapter_type, sort_order, word_count, version_no,
765
765
  analysis_status, excluded_from_analysis, created_at, updated_at
766
766
  FROM chapters WHERE work_id = ? ORDER BY sort_order, created_at${page.sql}`, workId, ...page.params);
767
- const chapters = chapterRows.map((row) => this.mapChapterDirectoryEntry(row));
767
+ const pageResult = paginated(chapterRows.map((row) => this.mapChapterDirectoryEntry(row)), pagination);
768
768
  const chaptersByVolume = new Map();
769
- for (const chapter of chapters) {
769
+ for (const chapter of pageResult.items) {
770
770
  const volumeId = String(chapter.volumeId);
771
771
  const list = chaptersByVolume.get(volumeId) ?? [];
772
772
  list.push(chapter);
@@ -776,7 +776,7 @@ export class Store {
776
776
  ...this.mapVolume(row),
777
777
  chapters: chaptersByVolume.get(requiredString(row, "id")) ?? []
778
778
  }));
779
- return { ...work, volumes, directoryPage: paginated(chapters, pagination) };
779
+ return { ...work, volumes, directoryPage: pageResult };
780
780
  }
781
781
  listFileVersions(workId) {
782
782
  this.getWork(workId);