@musnows/scriverse 0.5.7 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +5 -0
- package/dist/app.js.map +1 -1
- package/dist/public/app.js +37 -3
- package/dist/public/index.html +3 -3
- package/dist/public/styles.css +3 -1
- package/dist/store.js +30 -1
- package/dist/store.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -2768,6 +2768,7 @@ const workAuditActionLabels = {
|
|
|
2768
2768
|
"chapter.saved": "保存章节",
|
|
2769
2769
|
"chapter.moved": "移动章节",
|
|
2770
2770
|
"chapter.deleted": "删除章节",
|
|
2771
|
+
"chapter.purged": "彻底删除章节",
|
|
2771
2772
|
"chapter.restored": "恢复章节",
|
|
2772
2773
|
"work.imported": "导入正文"
|
|
2773
2774
|
};
|
|
@@ -6354,7 +6355,7 @@ function openWorkSettingsDialog(work) {
|
|
|
6354
6355
|
<button id="work-export-button" class="ghost-button" type="button">下载 ZIP</button>
|
|
6355
6356
|
</section>`;
|
|
6356
6357
|
const recycleBinField = isCurrentWork ? `<section class="work-access-field" aria-labelledby="chapter-recycle-bin-settings-title">
|
|
6357
|
-
<div><strong id="chapter-recycle-bin-settings-title">章节回收站</strong><small
|
|
6358
|
+
<div><strong id="chapter-recycle-bin-settings-title">章节回收站</strong><small>恢复已软删除的章节,或彻底删除正文、版本和关联资料。</small></div>
|
|
6358
6359
|
<button id="chapter-recycle-bin-button" class="ghost-button" type="button" aria-controls="chapter-recycle-bin-dialog" aria-haspopup="dialog" ${canEditProse() ? "" : "disabled"}>打开回收站</button>
|
|
6359
6360
|
</section>` : "";
|
|
6360
6361
|
const whitespaceField = isCurrentWork ? `<section class="work-access-field" aria-labelledby="whitespace-settings-title">
|
|
@@ -6417,7 +6418,7 @@ function openVolumeDialog(item) {
|
|
|
6417
6418
|
if (!canEditProse()) return toast("当前权限只能编辑设定资料,不能修改分卷", "error");
|
|
6418
6419
|
const kindOptions = [["main", "正文卷"], ["prequel", "前传"], ["extra", "番外"], ["epilogue", "后记"], ["appendix", "附录"]];
|
|
6419
6420
|
const management = item ? `<section class="entity-dialog-management" aria-label="分卷操作">
|
|
6420
|
-
<div><strong>分卷操作</strong><small
|
|
6421
|
+
<div><strong>分卷操作</strong><small>仅空分卷可以删除;回收站章节需先彻底删除,或恢复后移动到其他分卷。</small></div>
|
|
6421
6422
|
<div class="entity-dialog-management-actions"><button class="danger-button" type="button" data-dialog-volume-delete>删除分卷</button></div>
|
|
6422
6423
|
</section>` : "";
|
|
6423
6424
|
openDialog(item ? "编辑分卷" : "新建分卷",
|
|
@@ -8458,7 +8459,10 @@ function renderChapterRecycleBin(chapters) {
|
|
|
8458
8459
|
<time>${esc(formatDateTime(chapter.deletedAt))} · ${esc(chapter.actor)}</time>
|
|
8459
8460
|
<p>${esc(chapter.contentPreview || "空白章节")}</p>
|
|
8460
8461
|
<small>${Number(chapter.wordCount).toLocaleString("zh-CN")} 字 · 删除版本 v${Number(chapter.versionNo)}</small>
|
|
8461
|
-
<
|
|
8462
|
+
<footer class="entity-version-actions">
|
|
8463
|
+
<button type="button" data-restore-deleted-chapter="${esc(chapter.id)}">恢复章节</button>
|
|
8464
|
+
<button class="danger-button" type="button" data-purge-deleted-chapter="${esc(chapter.id)}">彻底删除</button>
|
|
8465
|
+
</footer>
|
|
8462
8466
|
</article>`).join("");
|
|
8463
8467
|
host.querySelectorAll("[data-restore-deleted-chapter]").forEach((button) => button.addEventListener("click", async () => {
|
|
8464
8468
|
const chapter = chapters.find((item) => item.id === button.dataset.restoreDeletedChapter);
|
|
@@ -8490,6 +8494,36 @@ function renderChapterRecycleBin(chapters) {
|
|
|
8490
8494
|
toast(error.message, "error");
|
|
8491
8495
|
}
|
|
8492
8496
|
}));
|
|
8497
|
+
host.querySelectorAll("[data-purge-deleted-chapter]").forEach((button) => button.addEventListener("click", async () => {
|
|
8498
|
+
const chapter = chapters.find((item) => item.id === button.dataset.purgeDeletedChapter);
|
|
8499
|
+
if (!chapter || !state.work) return;
|
|
8500
|
+
const dialog = $("#chapter-recycle-bin-dialog");
|
|
8501
|
+
dialog.close();
|
|
8502
|
+
const confirmed = await confirmToast(`彻底删除章节“${chapter.title}”吗?正文、版本和关联资料将无法恢复。`, {
|
|
8503
|
+
title: "彻底删除章节",
|
|
8504
|
+
confirmLabel: "确认彻底删除"
|
|
8505
|
+
});
|
|
8506
|
+
if (!confirmed) {
|
|
8507
|
+
dialog.showModal();
|
|
8508
|
+
return;
|
|
8509
|
+
}
|
|
8510
|
+
button.disabled = true;
|
|
8511
|
+
try {
|
|
8512
|
+
await api(`/api/chapters/${encodeURIComponent(chapter.id)}/permanent`, {
|
|
8513
|
+
method: "DELETE",
|
|
8514
|
+
body: { expectedVersionNo: chapter.versionNo }
|
|
8515
|
+
});
|
|
8516
|
+
state.work = await api(`/api/works/${encodeURIComponent(state.work.id)}`);
|
|
8517
|
+
renderTree();
|
|
8518
|
+
await loadChapterRecycleBin();
|
|
8519
|
+
dialog.showModal();
|
|
8520
|
+
toast(`已彻底删除章节“${chapter.title}”`);
|
|
8521
|
+
} catch (error) {
|
|
8522
|
+
button.disabled = false;
|
|
8523
|
+
dialog.showModal();
|
|
8524
|
+
toast(error.message, "error");
|
|
8525
|
+
}
|
|
8526
|
+
}));
|
|
8493
8527
|
}
|
|
8494
8528
|
|
|
8495
8529
|
async function loadChapterRecycleBin() {
|
package/dist/public/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest">
|
|
12
12
|
<link rel="stylesheet" href="/vendor/vditor/dist/index.css?v=3.11.2">
|
|
13
|
-
<link rel="stylesheet" href="/styles.css?v=20260728-
|
|
13
|
+
<link rel="stylesheet" href="/styles.css?v=20260728-shelf-cover-scale-v1">
|
|
14
14
|
</head>
|
|
15
15
|
<body class="auth-pending">
|
|
16
16
|
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
@@ -653,7 +653,7 @@
|
|
|
653
653
|
<button id="chapter-recycle-bin-close" class="dialog-close" aria-label="关闭章节回收站" type="button">×</button>
|
|
654
654
|
</div>
|
|
655
655
|
<div class="import-history-body">
|
|
656
|
-
<p id="chapter-recycle-bin-description" class="import-history-note"
|
|
656
|
+
<p id="chapter-recycle-bin-description" class="import-history-note">软删除的章节会保留正文、版本和关联资料。可以恢复到原分卷,也可以彻底删除;彻底删除后无法恢复。</p>
|
|
657
657
|
<div id="chapter-recycle-bin-list" class="entity-history-list" aria-live="polite"></div>
|
|
658
658
|
</div>
|
|
659
659
|
</dialog>
|
|
@@ -863,6 +863,6 @@
|
|
|
863
863
|
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
864
864
|
<script id="vditorIconScript" src="/vendor/vditor/dist/js/icons/ant.js?v=3.11.2"></script>
|
|
865
865
|
<script src="/vendor/vditor/dist/index.min.js?v=3.11.2"></script>
|
|
866
|
-
<script type="module" src="/app.js?v=20260728-
|
|
866
|
+
<script type="module" src="/app.js?v=20260728-chapter-purge-v1"></script>
|
|
867
867
|
</body>
|
|
868
868
|
</html>
|
package/dist/public/styles.css
CHANGED
|
@@ -987,7 +987,7 @@ body.work-viewer-mode .character-editor-actions { display: none !important; }
|
|
|
987
987
|
.book-shelf { display: grid; grid-template-columns: repeat(auto-fill, minmax(205px, 1fr)); gap: clamp(22px, 3vw, 38px); width: 100%; min-width: 0; max-width: 1400px; margin: 0 auto; }
|
|
988
988
|
.book-card { position: relative; min-width: 0; border: 0; background: transparent; text-align: left; }
|
|
989
989
|
.book-open { width: 100%; padding: 0; border: 0; background: transparent; text-align: left; }
|
|
990
|
-
.book-cover { position: relative; display: grid; place-items: center; aspect-ratio: 3 / 4.25; overflow: hidden; border-radius: 8px 13px 13px 8px; background: linear-gradient(145deg, #a96350, #612d27); color: rgba(255,255,255,.9); box-shadow: 0 20px 34px rgba(62,39,29,.18), inset 9px 0 0 rgba(0,0,0,.08); transition: transform .2s ease, box-shadow .2s ease; }
|
|
990
|
+
.book-cover { position: relative; display: grid; place-items: center; width: 90%; margin-inline: auto; aspect-ratio: 3 / 4.25; overflow: hidden; border-radius: 8px 13px 13px 8px; background: linear-gradient(145deg, #a96350, #612d27); color: rgba(255,255,255,.9); box-shadow: 0 20px 34px rgba(62,39,29,.18), inset 9px 0 0 rgba(0,0,0,.08); transition: transform .2s ease, box-shadow .2s ease; }
|
|
991
991
|
.book-cover::after { content: ""; position: absolute; inset: 0 auto 0 9px; width: 1px; background: rgba(255,255,255,.2); }
|
|
992
992
|
.book-open:hover .book-cover, .book-open:focus-visible .book-cover { transform: translateY(-6px) rotateY(-2deg); box-shadow: 0 28px 44px rgba(62,39,29,.24), inset 9px 0 0 rgba(0,0,0,.08); }
|
|
993
993
|
.book-cover-fallback { font-size: clamp(46px, 6vw, 74px); font-weight: 700; opacity: .75; }
|
|
@@ -2668,6 +2668,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
2668
2668
|
.entity-version-card > button:not(:disabled):hover { border-color: var(--accent); color: var(--accent-dark); }
|
|
2669
2669
|
.entity-version-card > button.is-confirming { border-color: var(--accent); background: var(--accent); color: #fff; }
|
|
2670
2670
|
.entity-version-card > button:disabled { cursor: default; opacity: .68; }
|
|
2671
|
+
.entity-version-actions { display: flex; justify-self: end; gap: 7px; }
|
|
2672
|
+
.entity-version-actions button { min-height: 30px; padding: 6px 9px; font-size: 9px; }
|
|
2671
2673
|
.entity-history-empty { margin: 0; padding: 28px 12px; color: var(--muted); font-size: 11px; text-align: center; }
|
|
2672
2674
|
.relationship-table .relationship-actions { white-space: nowrap; vertical-align: middle; }
|
|
2673
2675
|
.relationship-table .relationship-actions button { display: inline-block; min-height: 30px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--muted); font: inherit; font-size: 10px; line-height: 1.2; vertical-align: middle; appearance: none; transition: border-color .14s ease, background .14s ease, color .14s ease; }
|
package/dist/store.js
CHANGED
|
@@ -1027,7 +1027,7 @@ export class Store {
|
|
|
1027
1027
|
throw new AppError(409, "VOLUME_NOT_EMPTY", "卷内仍有章节,需先移动或删除章节");
|
|
1028
1028
|
}
|
|
1029
1029
|
if (numberValue(counts ?? {}, "deleted_count") > 0) {
|
|
1030
|
-
throw new AppError(409, "VOLUME_HAS_DELETED_CHAPTERS", "
|
|
1030
|
+
throw new AppError(409, "VOLUME_HAS_DELETED_CHAPTERS", "分卷回收站中仍有章节,请先彻底删除或恢复并移动这些章节");
|
|
1031
1031
|
}
|
|
1032
1032
|
this.recordEntityVersion("volume", volumeId, "delete", null, "删除分卷");
|
|
1033
1033
|
this.db.run("DELETE FROM volumes WHERE id = ?", volumeId);
|
|
@@ -1620,6 +1620,35 @@ export class Store {
|
|
|
1620
1620
|
this.audit(String(chapter.workId), "chapter.deleted", "chapter", chapterId, { versionNo });
|
|
1621
1621
|
});
|
|
1622
1622
|
}
|
|
1623
|
+
permanentlyDeleteChapter(chapterId, expectedVersionNo) {
|
|
1624
|
+
const chapter = this.db.get("SELECT * FROM chapters WHERE id = ?", chapterId);
|
|
1625
|
+
if (!chapter)
|
|
1626
|
+
throw notFound("章节");
|
|
1627
|
+
if (!chapter.deleted_at)
|
|
1628
|
+
throw new AppError(409, "CHAPTER_NOT_IN_RECYCLE_BIN", "仅回收站中的章节可以彻底删除");
|
|
1629
|
+
this.assertExpectedRevision("chapter", chapterId, expectedVersionNo, "章节", numberValue(chapter, "version_no"));
|
|
1630
|
+
const workId = requiredString(chapter, "work_id");
|
|
1631
|
+
const timestamp = now();
|
|
1632
|
+
this.db.transaction(() => {
|
|
1633
|
+
const locked = this.db.get("SELECT * FROM chapters WHERE id = ?", chapterId);
|
|
1634
|
+
if (!locked)
|
|
1635
|
+
throw notFound("章节");
|
|
1636
|
+
if (!locked.deleted_at)
|
|
1637
|
+
throw new AppError(409, "CHAPTER_NOT_IN_RECYCLE_BIN", "仅回收站中的章节可以彻底删除");
|
|
1638
|
+
this.assertExpectedRevision("chapter", chapterId, expectedVersionNo, "章节", numberValue(locked, "version_no"));
|
|
1639
|
+
this.db.run("DELETE FROM chapter_versions WHERE chapter_id = ?", chapterId);
|
|
1640
|
+
this.db.run("DELETE FROM entity_versions WHERE entity_type = 'chapter-outline' AND entity_id = ?", chapterId);
|
|
1641
|
+
this.db.run("DELETE FROM attachment_references WHERE entity_type = 'chapter' AND entity_id = ?", chapterId);
|
|
1642
|
+
this.db.run("DELETE FROM chapters WHERE id = ?", chapterId);
|
|
1643
|
+
this.db.run("UPDATE works SET updated_at = ? WHERE id = ?", timestamp, workId);
|
|
1644
|
+
this.audit(workId, "chapter.purged", "chapter", chapterId, {
|
|
1645
|
+
title: requiredString(locked, "title"),
|
|
1646
|
+
volumeId: requiredString(locked, "volume_id"),
|
|
1647
|
+
versionNo: numberValue(locked, "version_no"),
|
|
1648
|
+
recoverable: false
|
|
1649
|
+
});
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1623
1652
|
insertChapter(workId, volumeId, title, content, sortOrder, source, sourceRef, chapterType = "正文") {
|
|
1624
1653
|
const chapterId = id("chapter");
|
|
1625
1654
|
const timestamp = now();
|