@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.
- package/README.en.md +3 -0
- package/README.md +3 -0
- package/dist/ai-response-limit.js +12 -0
- package/dist/ai-response-limit.js.map +1 -0
- package/dist/ai.js +18 -12
- package/dist/ai.js.map +1 -1
- package/dist/app.js +47 -4
- package/dist/app.js.map +1 -1
- package/dist/chapter-title-numbering.js +210 -0
- package/dist/chapter-title-numbering.js.map +1 -1
- package/dist/cli-contract.js +15 -6
- package/dist/cli-contract.js.map +1 -1
- package/dist/cli-core.js +37 -1
- package/dist/cli-core.js.map +1 -1
- package/dist/database.js +21 -2
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +374 -15
- package/dist/public/index.html +12 -2
- package/dist/public/styles.css +3 -2
- package/dist/store.js +190 -10
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +5 -1
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -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="
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16294
|
-
|
|
16295
|
-
|
|
16296
|
-
const
|
|
16297
|
-
|
|
16298
|
-
|
|
16299
|
-
|
|
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")) {
|
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="/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>
|
package/dist/public/styles.css
CHANGED
|
@@ -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:
|
|
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); }
|