@musnows/scriverse 1.1.2 → 1.1.4

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.
@@ -259,7 +259,8 @@ const state = {
259
259
  relationshipExpandedMap: null,
260
260
  collapsedVolumeIds: new Set(),
261
261
  collapsedRaceIds: new Set(),
262
- contextChapterId: null
262
+ contextChapterId: null,
263
+ contextVolumeId: null
263
264
  };
264
265
 
265
266
  let platformAiProtocolOptions = [];
@@ -1274,6 +1275,7 @@ let latestAiContextUsage = null;
1274
1275
  const aiConversationHistoryPageLimit = 20;
1275
1276
  let aiConversationHistoryPage = { page: 1, limit: aiConversationHistoryPageLimit, hasMore: false, nextPage: null };
1276
1277
  let workScopedUiGeneration = 0;
1278
+ let workDirectoryRevision = 0;
1277
1279
  let workSelectionRequestGeneration = 0;
1278
1280
  let initialWorkDetail = null;
1279
1281
  let initialReaderChapterRequest = null;
@@ -9471,6 +9473,7 @@ async function selectWork(workId, preferredChapterId = null) {
9471
9473
  chapterEditorReadOnly = true;
9472
9474
  if (!canReadModule(state.module)) state.module = firstReadableUiModule(state.work) ?? "editor";
9473
9475
  applyWorkAccessMode();
9476
+ void refreshChapterTitleFormat();
9474
9477
  startBackgroundTaskCenter(nextWork.id);
9475
9478
  updateDocumentTitle(state.work);
9476
9479
  $("#work-meta").textContent = `${state.work.title}${state.work.author ? ` · ${state.work.author}` : ""} · ${Number(state.work.wordCount ?? 0).toLocaleString("zh-CN")} 字`;
@@ -9490,26 +9493,27 @@ async function loadVolumeChapters(volumeId) {
9490
9493
  if (existingRequest) return existingRequest;
9491
9494
  const workId = state.work.id;
9492
9495
  const generation = workScopedUiGeneration;
9496
+ const directoryRevision = workDirectoryRevision;
9493
9497
  volumeChapterLoadingIds.add(volumeId);
9494
9498
  renderTree([volumeId]);
9495
9499
  const request = (async () => {
9496
9500
  try {
9497
9501
  const chapters = await apiAllPages(`/api/volumes/${encodeURIComponent(volumeId)}/chapters`, 100);
9498
- if (state.work?.id !== workId || generation !== workScopedUiGeneration) return;
9502
+ if (state.work?.id !== workId || generation !== workScopedUiGeneration || directoryRevision !== workDirectoryRevision) return;
9499
9503
  const volume = state.work.volumes.find((item) => item.id === volumeId);
9500
9504
  if (!volume) return;
9501
9505
  volume.chapters = chapters;
9502
9506
  volume.chapterCount = chapters.length;
9503
9507
  loadedVolumeChapterIds.add(volumeId);
9504
9508
  } catch (error) {
9505
- if (state.work?.id === workId && generation === workScopedUiGeneration) {
9509
+ if (state.work?.id === workId && generation === workScopedUiGeneration && directoryRevision === workDirectoryRevision) {
9506
9510
  toast(`加载分卷章节失败:${error.message}`, "error");
9507
9511
  }
9508
9512
  } finally {
9509
9513
  if (volumeChapterRequests.get(volumeId) === request) {
9510
9514
  volumeChapterLoadingIds.delete(volumeId);
9511
9515
  volumeChapterRequests.delete(volumeId);
9512
- if (state.work?.id === workId && generation === workScopedUiGeneration) renderTree([volumeId]);
9516
+ if (state.work?.id === workId && generation === workScopedUiGeneration && directoryRevision === workDirectoryRevision) renderTree([volumeId]);
9513
9517
  }
9514
9518
  }
9515
9519
  })();
@@ -9616,7 +9620,7 @@ function renderTree(volumeIds = null) {
9616
9620
  return `
9617
9621
  <div class="volume-node ${collapsed ? "is-collapsed" : ""}" data-volume-id="${esc(volume.id)}">
9618
9622
  <div class="volume-title">
9619
- <button class="volume-toggle" type="button" data-volume-toggle="${esc(volume.id)}" aria-expanded="${collapsed ? "false" : "true"}" title="左键展开或折叠;右键打开分卷详情;可将章节拖到这里追加"><span>${esc(volume.title)}</span><span class="volume-chapter-count"><span class="volume-chapter-count-number">${Number(volume.chapterCount ?? chapters.length)}</span><span class="volume-chapter-count-unit"> 章</span></span></button>
9623
+ <button class="volume-toggle" type="button" data-volume-toggle="${esc(volume.id)}" aria-expanded="${collapsed ? "false" : "true"}" title="左键展开或折叠;右键打开分卷菜单;可将章节拖到这里追加"><span>${esc(volume.title)}</span><span class="volume-chapter-count"><span class="volume-chapter-count-number">${Number(volume.chapterCount ?? chapters.length)}</span><span class="volume-chapter-count-unit"> 章</span></span></button>
9620
9624
  ${proseEditable ? `<button class="ghost-button volume-detail-button" type="button" data-volume-detail="${esc(volume.id)}" aria-label="打开“${esc(volume.title)}”分卷详情" title="打开“${esc(volume.title)}”分卷详情"><svg class="volume-detail-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5"></circle><path d="M12 10.5v5.5M12 7.5h.01"></path></svg></button>` : ""}
9621
9625
  ${proseEditable ? `<button class="add-button chapter-add-button" type="button" data-new-chapter-volume="${esc(volume.id)}" aria-label="在“${esc(volume.title)}”中新建章节" title="在“${esc(volume.title)}”中新建章节">+</button>` : ""}
9622
9626
  </div>
@@ -9650,12 +9654,13 @@ function renderTree(volumeIds = null) {
9650
9654
  button.addEventListener("contextmenu", (event) => {
9651
9655
  if (!canEditProse()) return;
9652
9656
  event.preventDefault();
9653
- openVolumeDialog(state.work.volumes.find((volume) => volume.id === button.dataset.volumeToggle));
9657
+ openVolumeContextMenu(button.dataset.volumeToggle, event.clientX, event.clientY);
9654
9658
  });
9655
9659
  button.addEventListener("keydown", (event) => {
9656
9660
  if (!canEditProse() || (event.key !== "ContextMenu" && !(event.shiftKey && event.key === "F10"))) return;
9657
9661
  event.preventDefault();
9658
- openVolumeDialog(state.work.volumes.find((volume) => volume.id === button.dataset.volumeToggle));
9662
+ const rect = button.getBoundingClientRect();
9663
+ openVolumeContextMenu(button.dataset.volumeToggle, rect.left, rect.bottom);
9659
9664
  });
9660
9665
  if (proseEditable) {
9661
9666
  button.addEventListener("dragover", (event) => {
@@ -9940,6 +9945,7 @@ function closeChapterTypeMenu() {
9940
9945
  }
9941
9946
 
9942
9947
  function openChapterTypeMenu(chapterId, clientX, clientY) {
9948
+ closeVolumeContextMenu();
9943
9949
  const chapter = state.work?.volumes.flatMap((volume) => volume.chapters).find((item) => item.id === chapterId);
9944
9950
  if (!chapter) return;
9945
9951
  state.contextChapterId = chapterId;
@@ -9947,9 +9953,10 @@ function openChapterTypeMenu(chapterId, clientX, clientY) {
9947
9953
  const canManageChapter = canEditProse();
9948
9954
  const canAddAiReference = canWritePermissionModule(state.work, "ai-chat");
9949
9955
  menu.querySelector("strong").textContent = `操作“${chapter.title}”`;
9950
- menu.querySelectorAll("[data-chapter-type], [data-delete-chapter]").forEach((button) => button.classList.toggle("hidden", !canManageChapter));
9956
+ menu.querySelectorAll("[data-chapter-type], [data-delete-chapter], [data-insert-chapter-before], [data-insert-chapter-after]").forEach((button) => button.classList.toggle("hidden", !canManageChapter));
9951
9957
  menu.querySelector("[data-add-chapter-ai-reference]")?.classList.toggle("hidden", !canAddAiReference);
9952
9958
  menu.querySelector("#chapter-type-ai-reference-separator")?.classList.toggle("hidden", !(canManageChapter && canAddAiReference));
9959
+ menu.querySelector("#chapter-insert-separator")?.classList.toggle("hidden", !canManageChapter);
9953
9960
  menu.querySelectorAll("[data-chapter-type]").forEach((button) => {
9954
9961
  button.classList.toggle("active", button.dataset.chapterType === (chapter.chapterType || "正文"));
9955
9962
  button.setAttribute("aria-checked", String(button.classList.contains("active")));
@@ -16285,19 +16292,159 @@ async function deleteWork(work) {
16285
16292
  }
16286
16293
  }
16287
16294
 
16295
+ const chapterTitleFormatLabels = {
16296
+ off: "不自动编号",
16297
+ auto: "自动识别",
16298
+ dot: "N. 标题",
16299
+ "chapter-arabic": "第N章 标题",
16300
+ "chapter-chinese": "第N章(中文数字)",
16301
+ enumeration: "N、标题",
16302
+ english: "Chapter N:"
16303
+ };
16304
+ let chapterTitleFormatStatus = null;
16305
+ let chapterTitleOnFocus = "";
16306
+
16307
+ function chapterTitleHasNumber(title) {
16308
+ const source = String(title ?? "").trim();
16309
+ return /^(?:第\s*(?:[0-90-9]+|[〇零一二三四五六七八九十百千万两]+)\s*章|chap(?:ter)?\.?\s*[0-90-9]+|[0-90-9]+\s*[..、])/iu.test(source);
16310
+ }
16311
+
16312
+ function currentChapterTitleFormatStatus() {
16313
+ if (!chapterTitleFormatStatus || chapterTitleFormatStatus.workId !== state.work?.id) return null;
16314
+ return chapterTitleFormatStatus;
16315
+ }
16316
+
16317
+ function currentChapterTitlePreference() {
16318
+ const preference = state.work?.chapterTitleFormat || currentChapterTitleFormatStatus()?.preference || "off";
16319
+ return chapterTitleFormatLabels[preference] ? preference : "off";
16320
+ }
16321
+
16322
+ function describeChapterTitleFormatNote(preference, status) {
16323
+ if (!state.work) return "选择作品后,可以识别已有标题格式,或指定一种常见格式。";
16324
+ if (preference === "off") return "关闭后,新建和插入章节保持手写标题。需要整理某一卷时,再打开该卷的标题编号并重新编号。";
16325
+ if (preference === "auto") {
16326
+ if (status?.detected) return `已识别为「${chapterTitleFormatLabels[status.detected]}」。插入章节时按此格式编号,并只后移本卷后面的序号。`;
16327
+ return "现有标题还没有压倒性的同一种格式。请选择一种常见格式,或重新编号本卷。";
16328
+ }
16329
+ const label = chapterTitleFormatLabels[preference] ?? preference;
16330
+ return `新插入的章节使用「${label}」。切换格式不会改写已有标题;只有重新编号本卷才会改写这一卷。`;
16331
+ }
16332
+
16333
+ function syncVolumeTitleNumberingNote() {
16334
+ const note = $("#volume-title-number-note");
16335
+ const select = $("#dialog-fields select[name='chapterTitleFormat']");
16336
+ if (!note || !select) return;
16337
+ const preference = currentChapterTitlePreference();
16338
+ if (document.activeElement !== select && chapterTitleFormatLabels[preference]) select.value = preference;
16339
+ note.textContent = describeChapterTitleFormatNote(select.value || preference, currentChapterTitleFormatStatus());
16340
+ }
16341
+
16342
+ async function refreshChapterTitleFormat() {
16343
+ const workId = state.work?.id;
16344
+ if (!workId) {
16345
+ chapterTitleFormatStatus = null;
16346
+ syncVolumeTitleNumberingNote();
16347
+ return;
16348
+ }
16349
+ const requestId = ++refreshChapterTitleFormat.requestId;
16350
+ try {
16351
+ const status = await api(`/api/works/${encodeURIComponent(workId)}/chapter-title-format`);
16352
+ if (requestId !== refreshChapterTitleFormat.requestId || state.work?.id !== workId) return;
16353
+ chapterTitleFormatStatus = { ...status, workId };
16354
+ state.work.chapterTitleFormat = status.preference;
16355
+ syncVolumeTitleNumberingNote();
16356
+ } catch (error) {
16357
+ if (requestId === refreshChapterTitleFormat.requestId && state.work?.id === workId) syncVolumeTitleNumberingNote();
16358
+ }
16359
+ }
16360
+ refreshChapterTitleFormat.requestId = 0;
16361
+
16362
+ async function reloadWorkDirectory(workId) {
16363
+ const revision = ++workDirectoryRevision;
16364
+ const directory = await api(`/api/works/${encodeURIComponent(workId)}`);
16365
+ if (state.work?.id !== workId || revision !== workDirectoryRevision) return null;
16366
+ const collapsed = state.collapsedVolumeIds;
16367
+ state.work = {
16368
+ ...directory,
16369
+ volumes: (directory.volumes ?? []).map((volume) => ({
16370
+ ...volume,
16371
+ chapters: Array.isArray(volume.chapters) ? volume.chapters : [],
16372
+ chapterCount: Array.isArray(volume.chapters) ? volume.chapters.length : Number(volume.chapterCount ?? 0)
16373
+ }))
16374
+ };
16375
+ state.collapsedVolumeIds = collapsed;
16376
+ loadedVolumeChapterIds.clear();
16377
+ for (const volume of state.work.volumes) loadedVolumeChapterIds.add(volume.id);
16378
+ renderTree();
16379
+ if (state.chapter) {
16380
+ const fresh = state.work.volumes.flatMap((volume) => volume.chapters).find((item) => item.id === state.chapter.id);
16381
+ // 插入或重排会改写已打开章节的序号。目录已经是新标题,但内存中的当前章节仍可能是旧标题;先对齐再同步,避免把旧序号写回目录节点。
16382
+ if (fresh && fresh.title !== state.chapter.title) {
16383
+ const previousTitle = state.chapter.title;
16384
+ state.chapter = {
16385
+ ...state.chapter,
16386
+ title: fresh.title,
16387
+ sortOrder: fresh.sortOrder,
16388
+ versionNo: fresh.versionNo,
16389
+ wordCount: fresh.wordCount,
16390
+ chapterType: fresh.chapterType ?? state.chapter.chapterType
16391
+ };
16392
+ const titleInput = $("#chapter-title");
16393
+ if (titleInput.value.trim() === previousTitle) {
16394
+ titleInput.value = fresh.title;
16395
+ chapterTitleOnFocus = fresh.title;
16396
+ if (lastSavedChapterSnapshot?.chapterId === state.chapter.id) {
16397
+ lastSavedChapterSnapshot = { ...lastSavedChapterSnapshot, title: fresh.title };
16398
+ }
16399
+ }
16400
+ }
16401
+ syncChapterTreeSelection();
16402
+ }
16403
+ return state.work;
16404
+ }
16405
+
16406
+ function rememberWorkVersion(work) {
16407
+ if (!work?.id) return;
16408
+ const listed = state.works.find((item) => item.id === work.id);
16409
+ if (listed) listed.versionNo = work.versionNo;
16410
+ }
16411
+
16288
16412
  async function openChapterDialog(volumeId = null) {
16413
+ const insertAfterChapterId = openChapterDialog.insertAfterChapterId ?? null;
16414
+ const insertBeforeChapterId = openChapterDialog.insertBeforeChapterId ?? null;
16415
+ openChapterDialog.insertAfterChapterId = null;
16416
+ openChapterDialog.insertBeforeChapterId = null;
16289
16417
  if (!state.work) return openWorkDialog();
16290
16418
  if (!canEditProse()) return toast("当前权限只能编辑设定资料,正文为只读", "error");
16291
16419
  if (!state.work.volumes.length) {
16292
16420
  await api(`/api/works/${state.work.id}/volumes`, { method: "POST", body: { title: "正文", kind: "main" } });
16293
- state.work = await api(`/api/works/${state.work.id}`);
16294
- renderTree();
16295
- }
16296
- const selectedVolumeId = state.work.volumes.some((volume) => volume.id === volumeId) ? volumeId : state.work.volumes[0].id;
16297
- openDialog("新建章节", field("title", "章节标题") + field("volumeId", "所属卷", "select", selectedVolumeId, state.work.volumes.map((volume) => [volume.id, volume.title])) + field("chapterType", "章节类型", "select", "正文", chapterTypes.map((value) => [value, value])), async (form) => {
16298
- const chapter = await api(`/api/works/${state.work.id}/chapters`, { method: "POST", body: { title: form.get("title"), volumeId: form.get("volumeId"), chapterType: form.get("chapterType"), content: "" } });
16299
- state.work = await api(`/api/works/${state.work.id}`);
16421
+ await reloadWorkDirectory(state.work.id);
16422
+ }
16423
+ const anchorId = insertBeforeChapterId ?? insertAfterChapterId;
16424
+ const anchorLocation = anchorId ? findChapterLocation(anchorId) : null;
16425
+ const anchor = anchorLocation?.chapter ?? null;
16426
+ const insertBefore = Boolean(anchor && insertBeforeChapterId);
16427
+ const preferredVolumeId = anchorLocation?.volume.id ?? volumeId;
16428
+ const selectedVolumeId = state.work.volumes.some((volume) => volume.id === preferredVolumeId) ? preferredVolumeId : state.work.volumes[0].id;
16429
+ const volumeField = anchor
16430
+ ? `<input type="hidden" name="volumeId" value="${esc(selectedVolumeId)}"><p class="form-field-note">插入到“${esc(anchor.title)}”${insertBefore ? "之前" : "之后"}。开启标题编号时,只为本卷补序号并后移本卷后续章节,其他分卷保持不变。</p>`
16431
+ : field("volumeId", "所属卷", "select", selectedVolumeId, state.work.volumes.map((volume) => [volume.id, volume.title]));
16432
+ openDialog(anchor ? "插入章节" : "新建章节", field("title", "章节标题") + volumeField + field("chapterType", "章节类型", "select", "正文", chapterTypes.map((value) => [value, value])), async (form) => {
16433
+ const preference = currentChapterTitlePreference();
16434
+ const body = {
16435
+ title: form.get("title"),
16436
+ volumeId: form.get("volumeId"),
16437
+ chapterType: form.get("chapterType"),
16438
+ content: ""
16439
+ };
16440
+ if (anchor && insertBefore) body.insertBeforeChapterId = anchor.id;
16441
+ else if (anchor) body.insertAfterChapterId = anchor.id;
16442
+ if (preference !== "off") body.numberTitle = true;
16443
+ const chapter = await api(`/api/works/${state.work.id}/chapters`, { method: "POST", body });
16444
+ await reloadWorkDirectory(state.work.id);
16300
16445
  await selectChapter(chapter.id, { editMode: true });
16446
+ if (state.chapter?.id === chapter.id) $("#chapter-title").value = state.chapter.title;
16447
+ void refreshChapterTitleFormat();
16301
16448
  });
16302
16449
  }
16303
16450
 
@@ -16340,6 +16487,145 @@ function openVolumeDialog(item) {
16340
16487
  });
16341
16488
  }
16342
16489
 
16490
+ function closeVolumeContextMenu() {
16491
+ state.contextVolumeId = null;
16492
+ $("#volume-context-menu")?.classList.add("hidden");
16493
+ }
16494
+
16495
+ function openVolumeContextMenu(volumeId, clientX, clientY) {
16496
+ const volume = state.work?.volumes.find((item) => item.id === volumeId);
16497
+ if (!volume || !canEditProse()) return;
16498
+ closeChapterTypeMenu();
16499
+ state.contextVolumeId = volumeId;
16500
+ const menu = $("#volume-context-menu");
16501
+ menu.querySelector("strong").textContent = `操作“${volume.title}”`;
16502
+ menu.classList.remove("hidden");
16503
+ const rect = menu.getBoundingClientRect();
16504
+ menu.style.left = `${Math.max(8, Math.min(clientX, window.innerWidth - rect.width - 8))}px`;
16505
+ menu.style.top = `${Math.max(8, Math.min(clientY, window.innerHeight - rect.height - 8))}px`;
16506
+ }
16507
+
16508
+ let chapterTitleFormatSave = null;
16509
+
16510
+ async function saveChapterTitleFormat(next, select = null) {
16511
+ const run = () => saveChapterTitleFormatNow(next, select);
16512
+ const pending = (chapterTitleFormatSave ?? Promise.resolve()).then(run, run);
16513
+ chapterTitleFormatSave = pending.finally(() => {
16514
+ if (chapterTitleFormatSave === pending) chapterTitleFormatSave = null;
16515
+ });
16516
+ return chapterTitleFormatSave;
16517
+ }
16518
+
16519
+ async function saveChapterTitleFormatNow(next, select = null) {
16520
+ const previous = currentChapterTitlePreference();
16521
+ if (!state.work || !chapterTitleFormatLabels[next] || next === previous) {
16522
+ syncVolumeTitleNumberingNote();
16523
+ return next === previous;
16524
+ }
16525
+ if (!canEditProse()) {
16526
+ if (select) select.value = previous;
16527
+ toast("当前权限不能修改正文目录", "error");
16528
+ return false;
16529
+ }
16530
+ if (select) select.disabled = true;
16531
+ try {
16532
+ const status = await api(`/api/works/${encodeURIComponent(state.work.id)}/chapter-title-format`, {
16533
+ method: "PATCH",
16534
+ body: { chapterTitleFormat: next, expectedVersionNo: state.work.versionNo }
16535
+ });
16536
+ if (state.work?.id) {
16537
+ state.work.chapterTitleFormat = status.preference;
16538
+ state.work.versionNo = status.versionNo;
16539
+ rememberWorkVersion(state.work);
16540
+ chapterTitleFormatStatus = { ...status, workId: state.work.id };
16541
+ }
16542
+ syncVolumeTitleNumberingNote();
16543
+ toast(next === "off"
16544
+ ? "已关闭自动编号,已有章节标题保持不变"
16545
+ : `标题编号已改为「${chapterTitleFormatLabels[next]}」。已有章节不会立刻改写,可在该卷的标题编号里重新编号。`);
16546
+ return true;
16547
+ } catch (error) {
16548
+ if (select) select.value = previous;
16549
+ toast(error.message, "error");
16550
+ return false;
16551
+ } finally {
16552
+ if (select?.isConnected) select.disabled = false;
16553
+ syncVolumeTitleNumberingNote();
16554
+ }
16555
+ }
16556
+
16557
+ function openVolumeTitleNumberingDialog(item) {
16558
+ if (!state.work || !item || !canEditProse()) return toast("当前权限不能修改分卷", "error");
16559
+ const preference = currentChapterTitlePreference();
16560
+ openDialog("标题编号",
16561
+ field("chapterTitleFormat", "编号格式", "select", preference, Object.entries(chapterTitleFormatLabels)) +
16562
+ `<p id="volume-title-number-note" class="form-field-note">${esc(describeChapterTitleFormatNote(preference, currentChapterTitleFormatStatus()))}</p>` +
16563
+ `<p class="form-field-note">编号格式供这部作品的新章节使用,切换后不会改写已有标题。重新编号只改写“${esc(item.title)}”,其他分卷保持不变。插入章节时,也只给所在分卷补序号并后移该卷后续章节。</p>` +
16564
+ `<div class="entity-dialog-management-actions"><button class="ghost-button" type="button" data-volume-title-renumber>重新编号本卷</button></div>`,
16565
+ async () => {},
16566
+ "分卷",
16567
+ { submitLabel: "完成", meta: item.title }
16568
+ );
16569
+ const select = $("#dialog-fields select[name='chapterTitleFormat']");
16570
+ select?.addEventListener("change", () => {
16571
+ void saveChapterTitleFormat(select.value, select);
16572
+ });
16573
+ $("#dialog-fields").querySelector("[data-volume-title-renumber]")?.addEventListener("click", async () => {
16574
+ if (select && select.value !== currentChapterTitlePreference()) {
16575
+ const saved = await saveChapterTitleFormat(select.value, select);
16576
+ if (!saved) return;
16577
+ }
16578
+ await renumberVolumeChapterTitles(item);
16579
+ });
16580
+ void refreshChapterTitleFormat();
16581
+ }
16582
+
16583
+ async function renumberVolumeChapterTitles(item) {
16584
+ if (!state.work || !item || !canEditProse()) return toast("当前权限不能修改分卷", "error");
16585
+ const preference = $("#dialog-fields select[name='chapterTitleFormat']")?.value || currentChapterTitlePreference();
16586
+ const status = currentChapterTitleFormatStatus();
16587
+ if (preference === "off" || (preference === "auto" && status && !status.resolved)) {
16588
+ toast("请先选择标题编号格式,或使用自动识别", "error");
16589
+ return;
16590
+ }
16591
+ $("#form-dialog").close();
16592
+ const confirmed = await confirmToast(`按当前标题编号格式,重新编排分卷“${item.title}”里的章节序号。只影响这一卷,序号以外的标题文字会保留,其他分卷不变。`, {
16593
+ title: "重新编号本卷",
16594
+ confirmLabel: "重新编号"
16595
+ });
16596
+ if (!confirmed) return openVolumeTitleNumberingDialog(item);
16597
+ try {
16598
+ const result = await api(`/api/volumes/${encodeURIComponent(item.id)}/renumber-titles`, { method: "POST", body: {} });
16599
+ const workId = state.work.id;
16600
+ const selectedId = state.chapter?.id ?? null;
16601
+ const selectedVolumeId = state.chapter?.volumeId ?? null;
16602
+ const contentBefore = selectedId ? $("#chapter-content").value : "";
16603
+ await reloadWorkDirectory(workId);
16604
+ if (selectedId && selectedVolumeId === item.id && state.work?.id === workId) {
16605
+ const fresh = await api(`/api/chapters/${encodeURIComponent(selectedId)}`);
16606
+ if (state.chapter?.id === selectedId) {
16607
+ const contentDirty = Boolean(lastSavedChapterSnapshot) && contentBefore !== lastSavedChapterSnapshot.content;
16608
+ state.chapter = { ...state.chapter, title: fresh.title, versionNo: fresh.versionNo, updatedAt: fresh.updatedAt };
16609
+ $("#chapter-title").value = fresh.title;
16610
+ lastSavedChapterSnapshot = {
16611
+ chapterId: selectedId,
16612
+ title: fresh.title,
16613
+ content: contentDirty ? lastSavedChapterSnapshot.content : state.chapter.content
16614
+ };
16615
+ updateChapterPath();
16616
+ syncChapterTreeSelection();
16617
+ if (contentDirty) scheduleChapterAutoSave(120);
16618
+ }
16619
+ }
16620
+ const label = chapterTitleFormatLabels[result.format] ?? result.format;
16621
+ toast(`已按「${label}」重新编号分卷“${item.title}”,从 ${result.startAt} 起更新 ${result.updated} 章`);
16622
+ void refreshChapterTitleFormat();
16623
+ } catch (error) {
16624
+ toast(error.message, "error");
16625
+ openVolumeTitleNumberingDialog(item);
16626
+ }
16627
+ }
16628
+
16343
16629
  async function deleteVolume(item) {
16344
16630
  if (!state.work || !item || !canEditProse()) return toast("当前权限不能删除分卷", "error");
16345
16631
  $("#form-dialog").close();
@@ -21567,6 +21853,12 @@ $("#appearance-form").addEventListener("submit", (event) => {
21567
21853
  $("#appearance-dialog").close();
21568
21854
  toast(persisted ? "显示设置已保存" : "显示设置已应用,但当前浏览器无法保存偏好", persisted ? "info" : "error");
21569
21855
  });
21856
+ $("#chapter-title").addEventListener("focus", () => {
21857
+ chapterTitleOnFocus = $("#chapter-title").value.trim();
21858
+ });
21859
+ $("#chapter-title").addEventListener("blur", () => {
21860
+ void applyEditedChapterTitleNumber();
21861
+ });
21570
21862
  $("#chapter-title").addEventListener("input", () => scheduleChapterAutoSave());
21571
21863
  $("#chapter-content").addEventListener("beforeinput", (event) => {
21572
21864
  if (!state.chapter) return;
@@ -22063,6 +22355,59 @@ $("#cover-file").addEventListener("change", async (event) => {
22063
22355
  event.target.value = "";
22064
22356
  }
22065
22357
  });
22358
+ async function applyEditedChapterTitleNumber() {
22359
+ if (!state.chapter || !canEditProse() || chapterEditorReadOnly) return;
22360
+ const preference = currentChapterTitlePreference();
22361
+ if (preference === "off") return;
22362
+ if (chapterSaveInFlight) await chapterSaveInFlight;
22363
+ if (!state.chapter || chapterEditorReadOnly) return;
22364
+ const title = $("#chapter-title").value.trim();
22365
+ const titleAtFocus = chapterTitleOnFocus;
22366
+ if (!title || chapterTitleHasNumber(title)) return;
22367
+ if (title === titleAtFocus && title === lastSavedChapterSnapshot?.title) return;
22368
+ cancelChapterAutoSave();
22369
+ const chapterId = state.chapter.id;
22370
+ const workId = state.work.id;
22371
+ const contentBefore = $("#chapter-content").value;
22372
+ try {
22373
+ const chapter = await api(`/api/chapters/${encodeURIComponent(chapterId)}/apply-title-number`, {
22374
+ method: "POST",
22375
+ body: { title, expectedVersionNo: state.chapter.versionNo }
22376
+ });
22377
+ if (state.work?.id !== workId) return;
22378
+ await reloadWorkDirectory(workId);
22379
+ if (state.chapter?.id !== chapterId || state.work?.id !== workId) return;
22380
+ const contentDirty = Boolean(lastSavedChapterSnapshot) && contentBefore !== lastSavedChapterSnapshot.content;
22381
+ state.chapter = { ...state.chapter, ...chapter, content: state.chapter.content };
22382
+ $("#chapter-title").value = chapter.title;
22383
+ chapterTitleOnFocus = chapter.title;
22384
+ lastSavedChapterSnapshot = {
22385
+ chapterId,
22386
+ title: chapter.title,
22387
+ content: contentDirty ? lastSavedChapterSnapshot.content : state.chapter.content
22388
+ };
22389
+ syncChapterTreeSelection();
22390
+ if (contentDirty) scheduleChapterAutoSave(120);
22391
+ else setSaveState("已保存");
22392
+ void refreshChapterTitleFormat();
22393
+ } catch (error) {
22394
+ toast(error.message, "error");
22395
+ if (state.chapter?.id === chapterId) scheduleChapterAutoSave(120);
22396
+ }
22397
+ }
22398
+
22399
+ $("#volume-context-menu").addEventListener("click", (event) => {
22400
+ const volume = state.work?.volumes.find((item) => item.id === state.contextVolumeId);
22401
+ if (event.target.closest("[data-volume-menu-detail]")) {
22402
+ closeVolumeContextMenu();
22403
+ if (volume) openVolumeDialog(volume);
22404
+ return;
22405
+ }
22406
+ if (event.target.closest("[data-volume-menu-numbering]")) {
22407
+ closeVolumeContextMenu();
22408
+ if (volume) openVolumeTitleNumberingDialog(volume);
22409
+ }
22410
+ });
22066
22411
  $("#chapter-type-menu").addEventListener("click", async (event) => {
22067
22412
  const aiReferenceButton = event.target.closest("[data-add-chapter-ai-reference]");
22068
22413
  if (aiReferenceButton) {
@@ -22071,6 +22416,19 @@ $("#chapter-type-menu").addEventListener("click", async (event) => {
22071
22416
  if (chapterId) addChapterAsAiReference(chapterId);
22072
22417
  return;
22073
22418
  }
22419
+ const insertBeforeButton = event.target.closest("[data-insert-chapter-before]");
22420
+ const insertAfterButton = event.target.closest("[data-insert-chapter-after]");
22421
+ if (insertBeforeButton || insertAfterButton) {
22422
+ const chapterId = state.contextChapterId;
22423
+ const location = chapterId ? findChapterLocation(chapterId) : null;
22424
+ closeChapterTypeMenu();
22425
+ if (location) {
22426
+ if (insertBeforeButton) openChapterDialog.insertBeforeChapterId = chapterId;
22427
+ else openChapterDialog.insertAfterChapterId = chapterId;
22428
+ await openChapterDialog(location.volume.id);
22429
+ }
22430
+ return;
22431
+ }
22074
22432
  const deleteButton = event.target.closest("[data-delete-chapter]");
22075
22433
  if (deleteButton) {
22076
22434
  const chapterId = state.contextChapterId;
@@ -22112,6 +22470,7 @@ document.addEventListener("contextmenu", (event) => {
22112
22470
  });
22113
22471
  document.addEventListener("pointerdown", (event) => {
22114
22472
  if (!event.target.closest("#chapter-type-menu")) closeChapterTypeMenu();
22473
+ if (!event.target.closest("#volume-context-menu")) closeVolumeContextMenu();
22115
22474
  if (!event.target.closest("#line-citation-menu")) closeLineCitationMenu();
22116
22475
  if (!event.target.closest("#markdown-table-menu")) closeMarkdownTableMenu();
22117
22476
  if (!event.target.closest("#manuscript-export-menu") && !event.target.closest("#export-button") && !event.target.closest("#work-export-button")) {
@@ -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="/mobile-app.css?v=20260922-mobile-offline-v2">
13
- <link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=ai-composer-compact-controls-v1&feature=ai-context-meter-ring-only-v2&feature=ai-context-cache-hit-v1&feature=annotation-line-counts-v1&feature=chapter-comment-filters-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=ai-stream-character-count-five-digit-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=work-editor-preference-checkbox-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=ai-question-batch-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=im-narration-contrast-v1&feature=im-member-add-plus-v2&feature=im-button-hierarchy-v1&feature=im-icon-button-size-v1&feature=compact-sidebar-directory-v5&feature=ai-model-config-dialog-v1&feature=system-prompt-override-v3&feature=entity-editor-back-icon-v1&feature=toast-stack-v1&feature=chapter-header-compact-v4&feature=ai-attachment-disabled-v1&feature=ai-prose-acceptance-removed-v1&feature=chapter-line-scroll-bind-v1&feature=writing-goal-module-navigation-v2&feature=invite-registration-v1&feature=ai-send-mode-v2&feature=character-attributes-prune-v1&feature=chapter-directory-click-priority-v1&feature=annotation-bubble-visibility-v1">
13
+ <link rel="stylesheet" href="/styles.css?v=20260816-task-scope-volume-collapse-v2&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v3&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=galaxy-compact-controls-v2&feature=galaxy-motion-mode-v2&feature=chapter-search-replace-v3&feature=task-auto-run-ring-center-v3&feature=character-relationship-delete-v1&feature=ai-assistant-workspace-v2&feature=mobile-module-tab-position-v1&feature=volume-detail-icon-v1&feature=editor-actions-flow-v1&feature=reader-controls-subpanel-v1&feature=reader-focus-ring-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v2&feature=ai-composer-square-controls-v2&feature=ai-composer-compact-controls-v1&feature=ai-context-meter-ring-only-v2&feature=ai-context-cache-hit-v1&feature=annotation-line-counts-v1&feature=chapter-comment-filters-v1&feature=line-number-gutter-fill-v1&feature=ai-relationship-roleplay-v1&feature=ai-model-picker-v1&feature=markdown-word-count-five-digit-v2&feature=ai-stream-character-count-stable-v1&feature=ai-stream-character-count-five-digit-v1&feature=annotation-marker-offset-v1&feature=mobile-ai-entry-hidden-v1&feature=phone-client-entry-v1&feature=ai-stream-idle-timeout-v1&feature=ai-user-message-width-v2&feature=ai-chat-image-attachments-v9&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=toast-click-dismiss-v3&feature=character-avatar-v6&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-token-quota-positive-v5&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-stream-connection-seconds-v1&feature=ai-token-usage-estimated-price-v1&feature=record-favorites-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v1&feature=admin-account-identity-v2&feature=task-detail-failure-orange-v1&feature=character-card-header-alignment-v6&feature=character-card-title-fit-v2&feature=book-import-progress-v1&feature=editor-toolbar-compact-v2&feature=reader-first-frame-v1&feature=auth-compact-v2&feature=editor-preview-toggle-v2&feature=setting-title-chrome-v2&feature=entity-pin-icon-center-v1&feature=chapter-center-bottom-space-v1&feature=work-editor-preferences-v1&feature=work-editor-preference-checkbox-v1&feature=ai-roleplay-memory-v2&feature=ai-roleplay-memory-v3&feature=ai-roleplay-memory-v5&feature=character-editor-header-actions-v1&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=roleplay-memory-pin-border-v1&feature=ai-write-tools-v2&feature=ai-write-card-actions-compact-v1&feature=ai-write-plan-actions-footer-v1&feature=ai-question-actions-footer-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-answer-limit-v1&feature=ai-question-batch-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v1&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=ai-settings-textarea-font-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=ai-skill-slash-menu-v1&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=im-narration-contrast-v1&feature=im-member-add-plus-v2&feature=im-button-hierarchy-v1&feature=im-icon-button-size-v1&feature=compact-sidebar-directory-v5&feature=ai-model-config-dialog-v1&feature=system-prompt-override-v3&feature=entity-editor-back-icon-v1&feature=toast-stack-v1&feature=chapter-header-compact-v4&feature=ai-attachment-disabled-v1&feature=ai-prose-acceptance-removed-v1&feature=chapter-line-scroll-bind-v1&feature=writing-goal-module-navigation-v2&feature=invite-registration-v1&feature=ai-send-mode-v2&feature=character-attributes-prune-v1&feature=chapter-directory-click-priority-v1&feature=annotation-bubble-visibility-v1&feature=chapter-auto-title-menu-v1&feature=chapter-insert-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">
@@ -535,9 +535,19 @@
535
535
  <button type="button" role="menuitemradio" data-chapter-type="其他">其他</button>
536
536
  <div id="chapter-type-ai-reference-separator" class="line-citation-menu-separator hidden" role="separator" aria-orientation="horizontal"></div>
537
537
  <button type="button" role="menuitem" data-add-chapter-ai-reference>添加到助手引用</button>
538
+ <div id="chapter-insert-separator" class="line-citation-menu-separator hidden" role="separator" aria-orientation="horizontal"></div>
539
+ <button type="button" role="menuitem" data-insert-chapter-before>在章节上方插入新章节</button>
540
+ <button type="button" role="menuitem" data-insert-chapter-after>在章节下方插入新章节</button>
538
541
  <button class="danger-button" type="button" role="menuitem" data-delete-chapter>删除章节</button>
539
542
  </div>
540
543
  </div>
544
+ <div id="volume-context-menu" class="chapter-type-menu hidden" role="menu" aria-label="分卷操作">
545
+ <strong>分卷操作</strong>
546
+ <div>
547
+ <button type="button" role="menuitem" data-volume-menu-detail>分卷详情</button>
548
+ <button type="button" role="menuitem" data-volume-menu-numbering>标题编号</button>
549
+ </div>
550
+ </div>
541
551
  <div id="line-citation-menu" class="line-citation-menu hidden" role="menu" aria-label="正文行操作">
542
552
  <strong id="line-citation-label">已选择正文</strong>
543
553
  <button id="add-line-citation" type="button" role="menuitem">添加到创作助手引用</button>
@@ -1449,7 +1459,7 @@
1449
1459
  </dialog>
1450
1460
 
1451
1461
  <div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
1452
- <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v2&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-context-meter-ring-only-v1&feature=ai-context-cache-hit-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v3&feature=ai-background-stream-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=ai-fork-progress-toast-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2&feature=chapter-word-count-consistency-v1&feature=ai-all-message-references-v2&feature=ai-question-render-recovery-v3&feature=setting-category-preservation-v1&feature=toast-stack-v1&feature=toast-stack-label-v2&feature=agent-tool-limits-300-v1&feature=server-dev-logo-v1&feature=chapter-directory-ai-reference-v3&feature=ai-error-origin-v1&feature=ai-stream-render-performance-v2&feature=ai-attachment-disabled-v1&feature=ai-prose-acceptance-removed-v1&feature=ai-inline-message-references-v1&feature=ai-write-plan-status-sync-v1&feature=writing-goal-module-navigation-v2&feature=chapter-line-scroll-bind-v1&feature=invite-registration-v1&feature=ai-send-mode-v2&feature=character-attributes-prune-v1&feature=annotation-note-limit-v1&feature=chapter-directory-performance-v2&feature=chapter-directory-click-priority-v1&feature=mobile-offline-runtime-v6&feature=ai-title-first-turn-v1&feature=ai-optimistic-send-v1"></script>
1462
+ <script type="module" src="/app.js?v=20260816-extended-thinking-effort-v1&feature=ai-history-rename-v1&feature=ai-tool-call-copy-feedback-v2&feature=ai-send-control-v2&feature=analysis-task-queue-refresh-v1&feature=character-gender-v1&feature=character-filter-state-v1&feature=relationship-canvas-scale-v1&feature=relationship-table-scroll-v1&feature=ai-session-id-copy-v2&feature=galaxy-motion-mode-v3&feature=galaxy-edge-label-threshold-v1&feature=calculate-time-tool-v2&feature=analysis-task-expired-toast-v1&feature=global-replace-volume-v1&feature=chapter-search-replace-v1&feature=chapter-save-toast-v1&feature=character-relationship-delete-v1&feature=character-relationship-group-v1&feature=analysis-task-stability-delay-v1&feature=ai-assistant-workspace-v2&feature=volume-detail-icon-v1&feature=volume-story-order-v1&feature=reader-manual-chapter-navigation-v1&feature=ai-message-reference-badges-v1&feature=ai-roleplay-message-reference-v1&feature=ai-message-actions-v1&feature=assistant-responsive-navigation-v3&feature=annotation-permissions-v1&feature=annotation-line-counts-v1&feature=ai-relationship-roleplay-v1&feature=ai-roleplay-user-character-visibility-v1&feature=ai-roleplay-story-recall-v1&feature=ai-model-picker-v1&feature=ai-fork-model-unlock-v1&feature=context-percent-format-v1&feature=annotation-precise-locate-v1&feature=markdown-word-count-stable-v1&feature=ai-stream-character-count-stable-v2&feature=ai-process-empty-intermediate-v1&feature=ai-feed-scroll-follow-v2&feature=ai-message-retry-v1&feature=ai-stream-idle-timeout-v2&feature=ai-config-delete-v1&feature=ai-provider-protocol-options-v1&feature=ai-provider-thinking-type-v1&feature=ai-chat-image-attachments-v8&feature=ai-message-image-preview-v1&feature=ai-thinking-scroll-v2&feature=ai-image-conversation-model-lock-v1&feature=toast-click-dismiss-v2&feature=system-restart-dialog-delay-v1&feature=character-avatar-v6&feature=character-death-position-v1&feature=admin-ai-conversations-v1&feature=ai-usage-pricing-v1&feature=ai-usage-pricing-badge-v2&feature=ai-usage-pricing-label-v1&feature=ai-usage-token-breakdown-v1&feature=ai-usage-pricing-cache-v2&feature=ai-usage-pricing-manual-refresh-v1&feature=ai-monthly-token-quota-v1&feature=ai-provider-token-quota-v1&feature=ai-token-quota-positive-v6&feature=ai-model-thinking-label-v1&feature=ai-model-picker-focus-v1&feature=ai-provider-model-import-v1&feature=ai-assistant-brain-icon-v1&feature=ai-token-usage-details-v1&feature=ai-token-usage-details-centered-v1&feature=ai-token-usage-raw-input-v1&feature=ai-stream-connection-seconds-v1&feature=phone-client-entry-v1&feature=ai-usage-pricing-cache-v1&feature=ai-model-thinking-label-v3&feature=ai-roleplay-speaker-label-v1&feature=toast-modal-host-v1&feature=api-key-copy-existing-v1&feature=character-favorite-v1&feature=record-favorites-v1&feature=ai-token-usage-estimated-price-v1&feature=entity-detail-favorites-v1&feature=entity-pin-v1&feature=entity-pin-icon-v1&feature=roleplay-favorite-label-v1&feature=ai-roleplay-knowledge-tools-v1&feature=character-persona-summary-v1&feature=ai-roleplay-scene-turn-v2&feature=admin-account-identity-v2&feature=ai-provider-analysis-timeout-v1&feature=task-detail-failure-orange-v1&feature=book-import-progress-v1&feature=presence-multiple-users-v1&feature=ai-context-input-output-v1&feature=editor-blank-lines-preserved-v1&feature=reader-first-frame-v1&feature=vditor-lazy-load-v1&feature=ui-module-preload-v1&feature=reader-initial-prefetch-v1&feature=editor-preview-toggle-v2&feature=vditor-fullscreen-disabled-v1&feature=chapter-auto-indent-v1&feature=chapter-centered-scroll-v1&feature=work-editor-preferences-v1&feature=ai-context-output-usage-v1&feature=ai-context-meter-ring-only-v1&feature=ai-context-cache-hit-v1&feature=ai-roleplay-memory-v4&feature=ai-roleplay-memory-v5&feature=roleplay-memory-header-actions-v1&feature=roleplay-memory-header-toolbar-v1&feature=roleplay-memory-action-icons-v1&feature=roleplay-memory-action-colors-v1&feature=annotation-line-anchor-v1&feature=stable-line-ids-v1&feature=live-annotation-anchors-v1&feature=chapter-comment-filters-v1&feature=ai-write-tools-v3&feature=ai-question-option-supplement-v1&feature=ai-question-selection-highlight-v1&feature=ai-question-continuation-ui-v3&feature=ai-background-stream-v1&feature=ai-question-tool-result-v1&feature=ai-question-tool-summary-v1&feature=ai-question-submit-guidance-v1&feature=ai-question-batch-v1&feature=ai-question-answer-limit-v1&feature=semantic-search-v6&feature=chapter-title-renumber-v1&feature=ai-writing-skills-v3&feature=ai-cancel-preserve-process-v2&feature=remote-mcp-v1&feature=character-alias-chips-v2&feature=character-title-input-v1&feature=character-detail-value-wrap-v2&feature=chapter-batch-editor-refresh-v1&feature=ai-usage-stat-display-v1&feature=ai-usage-year-v1&feature=semantic-search-rag-label-v1&feature=ai-skill-slash-menu-v1&feature=ai-roleplay-scene-bubble-v1&feature=ai-roleplay-scene-collapse-v1&feature=markdown-adjacent-blockquotes-v1&feature=chapter-save-shortcut-v2&feature=ai-message-citation-popover-v1&feature=line-citation-menu-separator-v1&feature=global-im-v106&feature=im-sidebar-compact-v1&feature=ai-fork-progress-toast-v1&feature=im-settings-gear-v1&feature=compact-sidebar-directory-v2&feature=chapter-word-count-consistency-v1&feature=ai-all-message-references-v2&feature=ai-question-render-recovery-v3&feature=setting-category-preservation-v1&feature=toast-stack-v1&feature=toast-stack-label-v2&feature=agent-tool-limits-300-v1&feature=server-dev-logo-v1&feature=chapter-directory-ai-reference-v3&feature=ai-error-origin-v1&feature=ai-stream-render-performance-v2&feature=ai-attachment-disabled-v1&feature=ai-prose-acceptance-removed-v1&feature=ai-inline-message-references-v1&feature=ai-write-plan-status-sync-v1&feature=writing-goal-module-navigation-v2&feature=chapter-line-scroll-bind-v1&feature=invite-registration-v1&feature=ai-send-mode-v2&feature=character-attributes-prune-v1&feature=annotation-note-limit-v1&feature=chapter-directory-performance-v2&feature=chapter-directory-click-priority-v1&feature=mobile-offline-runtime-v6&feature=ai-title-first-turn-v1&feature=ai-optimistic-send-v1&feature=chapter-auto-title-menu-v1&feature=chapter-insert-menu-v1"></script>
1453
1463
 
1454
1464
  </body>
1455
1465
  </html>
@@ -1434,6 +1434,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1434
1434
  .module-nav .ai-assistant-entry.active { background: color-mix(in srgb, var(--accent) 10%, var(--surface)); color: var(--accent-dark); font-weight: 650; }
1435
1435
  #module-more-button { grid-column: 2; color: var(--accent-dark); text-align: center; }
1436
1436
  .panel-heading { display: flex; align-items: center; gap: 8px; padding: 15px 0 9px 7px; color: var(--muted); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }
1437
+ .dialog-fields select[name="chapterTitleFormat"] { text-transform: none; letter-spacing: 0; }
1437
1438
  .panel-heading-label { min-width: 0; white-space: nowrap; }
1438
1439
  .panel-heading-label-compact { display: none; }
1439
1440
  .panel-heading-actions { display: flex; align-items: center; gap: 7px; margin-left: auto; white-space: nowrap; }
@@ -3538,10 +3539,10 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
3538
3539
  .chapter-insight-toast-detail { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 8px; padding-top: 9px; border-top: 1px solid var(--line); }
3539
3540
  .chapter-insight-toast-detail strong { color: var(--ink); font-size: 10px; }
3540
3541
  .chapter-insight-toast-empty { color: var(--muted); }
3541
- .chapter-type-menu { position: fixed; z-index: 80; width: 176px; padding: 10px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); box-shadow: 0 14px 38px rgba(49,42,32,.22); }
3542
+ .chapter-type-menu { position: fixed; z-index: 80; width: 220px; padding: 10px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); box-shadow: 0 14px 38px rgba(49,42,32,.22); }
3542
3543
  .chapter-type-menu strong { display: block; overflow: hidden; margin: 0 2px 8px; color: var(--muted); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
3543
3544
  .chapter-type-menu > div { display: grid; gap: 3px; }
3544
- .chapter-type-menu button { border: 0; border-radius: 3px; padding: 7px 8px; background: transparent; text-align: left; font-size: 11px; }
3545
+ .chapter-type-menu button { border: 0; border-radius: 3px; padding: 7px 8px; background: transparent; text-align: left; font-size: 11px; white-space: nowrap; }
3545
3546
  .chapter-type-menu button:hover, .chapter-type-menu button.active { background: var(--paper-deep); color: var(--accent-dark); }
3546
3547
  .chapter-type-menu .danger-button { margin-top: 5px; border-top: 1px solid var(--line); border-radius: 0; color: var(--accent-dark); }
3547
3548
  .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); }