@musnows/scriverse 0.3.7 → 0.3.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/ai.js +59 -0
- package/dist/ai.js.map +1 -1
- package/dist/app.js +136 -35
- package/dist/app.js.map +1 -1
- package/dist/http-logging.js +2 -2
- package/dist/http-logging.js.map +1 -1
- package/dist/logger.js +2 -2
- package/dist/logger.js.map +1 -1
- package/dist/pagination.js +42 -0
- package/dist/pagination.js.map +1 -0
- package/dist/public/app.js +226 -76
- package/dist/public/index.html +37 -8
- package/dist/public/markdown.js +35 -1
- package/dist/public/page-route.js +12 -0
- package/dist/public/styles.css +64 -22
- package/dist/security.js +41 -4
- package/dist/security.js.map +1 -1
- package/dist/store.js +303 -0
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +30 -0
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
package/dist/public/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildRelationshipGraph, createGalaxyRenderer, renderRelationshipMindMap } from "/relationship-graph.js?v=20260721-release-0.3.6";
|
|
2
2
|
import { collapseExcessBlankLines, formatDateTime, normalizeParagraphSpacing } from "/text-formatting.js?v=20260713-saved-at-seconds";
|
|
3
|
-
import { renderMarkdown } from "/markdown.js?v=
|
|
3
|
+
import { renderMarkdown } from "/markdown.js?v=20260722-inline-code";
|
|
4
4
|
import { buildAiReferenceScope, findAiMention, listAiMentionOptions } from "/ai-mentions.js?v=20260716-chapter-references";
|
|
5
5
|
import { shouldShowAiQuickActions } from "/ai-conversation.js?v=20260713-quick-actions";
|
|
6
6
|
import { calculateLineNumberRowHeight, calculateLineNumberRowTop, calculateLineNumberTextOffset, calculateLineNumberTop } from "/line-number-layout.js?v=20260713-row-box-alignment";
|
|
@@ -14,7 +14,7 @@ import { THEME_STORAGE_KEY, nextTheme, normalizeTheme, themeToggleLabel } from "
|
|
|
14
14
|
import { buildCharacterDetails, buildCharacterState, characterStateEntries, normalizeCharacterDetails, normalizeCharacterSections } from "/character-profile.js?v=20260713-character-editor";
|
|
15
15
|
import { characterVersionSourceLabel, describeCharacterVersionChanges } from "/character-version.js?v=20260713-character-history";
|
|
16
16
|
import { VERSIONED_ENTITY_LABELS, entityVersionSnapshotSummary, entityVersionSourceLabel } from "/entity-version.js?v=20260714-all-knowledge-history";
|
|
17
|
-
import { parsePageRoute, serializePageRoute } from "/page-route.js?v=
|
|
17
|
+
import { parsePageRoute, serializePageRoute } from "/page-route.js?v=20260722-entity-editor-page";
|
|
18
18
|
import { splitRelationshipKeywordInput, splitRelationshipKeywords, uniqueRelationshipKeywords } from "/relationship-keywords.js?v=20260720-relationship-keyword-chips";
|
|
19
19
|
import { tokenizeVisibleSpaces } from "/whitespace-visualization.js?v=20260718-visible-whitespace";
|
|
20
20
|
import { buildRaceForest, eligibleRaceParents, racePathLabel } from "/race-hierarchy.js?v=20260721-race-hierarchy";
|
|
@@ -352,6 +352,10 @@ function replacePageRoute(route) {
|
|
|
352
352
|
|
|
353
353
|
function currentPageRoute() {
|
|
354
354
|
const workId = state.work?.id ?? null;
|
|
355
|
+
if (!$("#entity-editor-view").classList.contains("hidden") && workId && entityEditorType) {
|
|
356
|
+
const entityId = entityEditorType === "setting" ? settingEditorItem?.id : characterEditorItem?.id;
|
|
357
|
+
return { view: "entity-editor", workId, entity: entityEditorType, entityId: entityId ?? null };
|
|
358
|
+
}
|
|
355
359
|
if (!$("#settings-hub-view").classList.contains("hidden")) return { view: "settings", workId, ...settingsRouteContext() };
|
|
356
360
|
if (!$("#platform-ai-view").classList.contains("hidden")) return { view: "platform-ai", workId, ...settingsRouteContext() };
|
|
357
361
|
if (!$("#shelf-view").classList.contains("hidden")) return { view: "shelf" };
|
|
@@ -461,6 +465,9 @@ const chapterAutoSaveDelay = 800;
|
|
|
461
465
|
let aiMentionMatch = null;
|
|
462
466
|
let aiMentionRange = null;
|
|
463
467
|
let settingsReturnContext = null;
|
|
468
|
+
let entityEditorType = null;
|
|
469
|
+
let entityEditorDirty = false;
|
|
470
|
+
let settingEditorItem = null;
|
|
464
471
|
let characterEditorItem = null;
|
|
465
472
|
let characterEditorVersions = [];
|
|
466
473
|
let characterEditorRelationships = [];
|
|
@@ -468,8 +475,49 @@ let characterEditorRelationshipsLoading = false;
|
|
|
468
475
|
let characterEditorSections = [];
|
|
469
476
|
let characterSectionPreviewTimer = null;
|
|
470
477
|
let characterSectionPendingAttachments = [];
|
|
478
|
+
let characterSectionEditorDirty = false;
|
|
471
479
|
let entityHistoryContext = null;
|
|
472
480
|
|
|
481
|
+
function showEntityEditorPage(type) {
|
|
482
|
+
entityEditorType = type;
|
|
483
|
+
entityEditorDirty = false;
|
|
484
|
+
characterSectionEditorDirty = false;
|
|
485
|
+
$("#entity-editor-view").classList.remove("hidden");
|
|
486
|
+
$("#setting-editor-form").classList.toggle("hidden", type !== "setting");
|
|
487
|
+
$("#character-editor-form").classList.toggle("hidden", type !== "character");
|
|
488
|
+
$("#character-section-editor-view").classList.add("hidden");
|
|
489
|
+
$("#app").inert = true;
|
|
490
|
+
document.body.classList.add("entity-editor-open");
|
|
491
|
+
replacePageRoute(currentPageRoute());
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function markEntityEditorDirty() {
|
|
495
|
+
if (entityEditorType && canEditWork()) entityEditorDirty = true;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function confirmEntityEditorDiscard(message) {
|
|
499
|
+
if (!entityEditorDirty) return true;
|
|
500
|
+
return window.confirm(message ?? "当前资料有未保存修改,返回列表将丢弃这些修改。是否继续?");
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async function closeEntityEditor({ force = false } = {}) {
|
|
504
|
+
if (!$("#character-section-editor-view").classList.contains("hidden")) return closeCharacterSectionEditor({ force });
|
|
505
|
+
if (!force && !confirmEntityEditorDiscard()) return false;
|
|
506
|
+
await discardPendingCharacterAttachments();
|
|
507
|
+
const module = entityEditorType === "setting" ? "settings" : "characters";
|
|
508
|
+
entityEditorType = null;
|
|
509
|
+
entityEditorDirty = false;
|
|
510
|
+
settingEditorItem = null;
|
|
511
|
+
characterEditorItem = null;
|
|
512
|
+
$("#entity-editor-view").classList.add("hidden");
|
|
513
|
+
$("#setting-editor-form").classList.add("hidden");
|
|
514
|
+
$("#character-editor-form").classList.add("hidden");
|
|
515
|
+
$("#app").inert = false;
|
|
516
|
+
document.body.classList.remove("entity-editor-open");
|
|
517
|
+
await showModule(module);
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
|
|
473
521
|
function setModuleNavExpanded(expanded) {
|
|
474
522
|
moduleNavExpanded = expanded;
|
|
475
523
|
$("#module-more-button .nav-label").textContent = expanded ? "收起" : "更多";
|
|
@@ -1016,7 +1064,7 @@ function renderAiConversationHistory() {
|
|
|
1016
1064
|
async function loadAiConversations(openLatest = true) {
|
|
1017
1065
|
const workId = state.work?.id;
|
|
1018
1066
|
if (!workId) return;
|
|
1019
|
-
const conversations = await
|
|
1067
|
+
const conversations = (await apiPage(`/api/works/${workId}/ai-conversations`)).items;
|
|
1020
1068
|
if (state.work?.id !== workId) return;
|
|
1021
1069
|
state.aiConversations = conversations;
|
|
1022
1070
|
loadedAiConversationsWorkId = workId;
|
|
@@ -1045,7 +1093,7 @@ async function ensureAiConversationsLoaded() {
|
|
|
1045
1093
|
}
|
|
1046
1094
|
|
|
1047
1095
|
async function openAiConversation(conversationId, hideHistory = true) {
|
|
1048
|
-
const conversation = await api(`/api/ai-conversations/${conversationId}`);
|
|
1096
|
+
const conversation = await api(`/api/ai-conversations/${conversationId}?page=1&limit=100`);
|
|
1049
1097
|
state.aiConversationId = conversation.id;
|
|
1050
1098
|
state.aiPromptSent = conversation.messages.some((message) => message.role === "user");
|
|
1051
1099
|
$("#ai-conversation-title").textContent = conversation.title;
|
|
@@ -1396,6 +1444,24 @@ async function api(path, options = {}) {
|
|
|
1396
1444
|
return payload.data;
|
|
1397
1445
|
}
|
|
1398
1446
|
|
|
1447
|
+
async function apiPage(path, page = 1, limit = 50) {
|
|
1448
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
1449
|
+
const result = await api(`${path}${separator}page=${page}&limit=${limit}`);
|
|
1450
|
+
if (Array.isArray(result)) return { items: result, page, limit, hasMore: false, nextPage: null };
|
|
1451
|
+
return result;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
async function apiAllPages(path, limit = 100) {
|
|
1455
|
+
const items = [];
|
|
1456
|
+
let page = 1;
|
|
1457
|
+
while (true) {
|
|
1458
|
+
const result = await apiPage(path, page, limit);
|
|
1459
|
+
items.push(...(result.items ?? []));
|
|
1460
|
+
if (!result.hasMore || !result.nextPage) return items;
|
|
1461
|
+
page = result.nextPage;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1399
1465
|
function selectAuthMode(mode) {
|
|
1400
1466
|
const registerTab = $("#auth-register-tab");
|
|
1401
1467
|
const login = mode === "login" || registerTab.disabled;
|
|
@@ -1634,7 +1700,7 @@ function updateDocumentTitle(work = null) {
|
|
|
1634
1700
|
}
|
|
1635
1701
|
|
|
1636
1702
|
async function loadWorks(preferredId) {
|
|
1637
|
-
state.works = await
|
|
1703
|
+
state.works = (await apiPage("/api/works")).items;
|
|
1638
1704
|
if (preferredId) {
|
|
1639
1705
|
await selectWork(preferredId);
|
|
1640
1706
|
return;
|
|
@@ -1658,7 +1724,7 @@ async function initializePage() {
|
|
|
1658
1724
|
return;
|
|
1659
1725
|
}
|
|
1660
1726
|
const route = parsePageRoute(window.location.hash);
|
|
1661
|
-
state.works = await
|
|
1727
|
+
state.works = (await apiPage("/api/works")).items;
|
|
1662
1728
|
try {
|
|
1663
1729
|
if (route.view === "shelf") {
|
|
1664
1730
|
showShelf();
|
|
@@ -1672,16 +1738,30 @@ async function initializePage() {
|
|
|
1672
1738
|
}
|
|
1673
1739
|
|
|
1674
1740
|
if (requestedWork) {
|
|
1675
|
-
state.module = route.view === "module"
|
|
1741
|
+
state.module = route.view === "module"
|
|
1742
|
+
? route.module
|
|
1743
|
+
: route.view === "entity-editor"
|
|
1744
|
+
? (route.entity === "setting" ? "settings" : "characters")
|
|
1745
|
+
: "editor";
|
|
1676
1746
|
await selectWork(requestedWork.id, route.view === "editor" ? route.chapterId : null);
|
|
1677
1747
|
}
|
|
1678
1748
|
|
|
1679
1749
|
if (route.view === "editor") {
|
|
1680
|
-
|
|
1681
|
-
if (route.chapterId && chapterExists && state.chapter?.id !== route.chapterId) await selectChapter(route.chapterId);
|
|
1750
|
+
if (route.chapterId && state.chapter?.id !== route.chapterId) await selectChapter(route.chapterId);
|
|
1682
1751
|
return;
|
|
1683
1752
|
}
|
|
1684
1753
|
if (route.view === "module") return;
|
|
1754
|
+
if (route.view === "entity-editor") {
|
|
1755
|
+
const records = route.entity === "setting" ? state.settings : state.characters;
|
|
1756
|
+
const item = route.entityId ? records.find((record) => record.id === route.entityId) : null;
|
|
1757
|
+
if (route.entityId && !item) {
|
|
1758
|
+
toast(route.entity === "setting" ? "未找到要编辑的设定" : "未找到要编辑的角色", "error");
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
if (route.entity === "setting") openSettingEditor(item);
|
|
1762
|
+
else await openCharacterEditor(item);
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1685
1765
|
if (route.view === "welcome") {
|
|
1686
1766
|
showWelcome(true);
|
|
1687
1767
|
return;
|
|
@@ -1784,7 +1864,7 @@ async function openUsersDialog() {
|
|
|
1784
1864
|
}
|
|
1785
1865
|
$("#users-list").innerHTML = '<p class="empty-state">正在读取用户……</p>';
|
|
1786
1866
|
$("#users-dialog").showModal();
|
|
1787
|
-
try { renderUsers(await
|
|
1867
|
+
try { renderUsers((await apiPage("/api/users")).items); }
|
|
1788
1868
|
catch (error) { $("#users-dialog").close(); toast(error.message, "error"); }
|
|
1789
1869
|
}
|
|
1790
1870
|
|
|
@@ -1921,13 +2001,13 @@ async function openSearchResult(result) {
|
|
|
1921
2001
|
if (result.type === "character") {
|
|
1922
2002
|
await showModule("characters");
|
|
1923
2003
|
const character = state.characters.find((item) => item.id === result.id);
|
|
1924
|
-
if (character)
|
|
2004
|
+
if (character) openCharacterEditor(character);
|
|
1925
2005
|
return;
|
|
1926
2006
|
}
|
|
1927
2007
|
if (result.type === "setting") {
|
|
1928
2008
|
await showModule("settings");
|
|
1929
2009
|
const setting = await api(`/api/settings/${encodeURIComponent(result.id)}`);
|
|
1930
|
-
|
|
2010
|
+
openSettingEditor(setting);
|
|
1931
2011
|
return;
|
|
1932
2012
|
}
|
|
1933
2013
|
if (result.type === "race") {
|
|
@@ -2026,7 +2106,7 @@ function renderShelf() {
|
|
|
2026
2106
|
async function selectWork(workId, preferredChapterId = null) {
|
|
2027
2107
|
const discarding = state.work?.id !== workId && state.dirty;
|
|
2028
2108
|
if (discarding && !confirmDiscardChanges()) return false;
|
|
2029
|
-
const nextWork = await api(`/api/works/${workId}`);
|
|
2109
|
+
const nextWork = await api(`/api/works/${workId}?page=1&limit=100`);
|
|
2030
2110
|
if (state.work?.id !== nextWork.id) {
|
|
2031
2111
|
loadedAiModelsWorkId = null;
|
|
2032
2112
|
loadedAiReferencesWorkId = null;
|
|
@@ -2067,7 +2147,8 @@ async function selectWork(workId, preferredChapterId = null) {
|
|
|
2067
2147
|
renderTree();
|
|
2068
2148
|
const chapters = state.work.volumes.flatMap((volume) => volume.chapters);
|
|
2069
2149
|
const targetChapter = chapters.find((chapter) => chapter.id === preferredChapterId) ?? chapters[0];
|
|
2070
|
-
if (state.module === "editor" &&
|
|
2150
|
+
if (state.module === "editor" && preferredChapterId) await selectChapter(preferredChapterId);
|
|
2151
|
+
else if (state.module === "editor" && targetChapter) await selectChapter(targetChapter.id);
|
|
2071
2152
|
else if (state.module === "editor") showWelcome(true);
|
|
2072
2153
|
else await showModule(state.module);
|
|
2073
2154
|
return true;
|
|
@@ -2324,7 +2405,7 @@ function bindEntityHistoryButtons(refresh) {
|
|
|
2324
2405
|
}
|
|
2325
2406
|
|
|
2326
2407
|
async function renderSettings() {
|
|
2327
|
-
const records = await
|
|
2408
|
+
const records = (await apiPage(`/api/works/${state.work.id}/settings`)).items;
|
|
2328
2409
|
state.settings = records;
|
|
2329
2410
|
$("#module-content").innerHTML = records.length ? `<div class="card-grid">${records.map((item) => `
|
|
2330
2411
|
<article class="record-card"><small>${esc(item.category)} · ${item.locked ? "已锁定" : esc(item.status)}</small>
|
|
@@ -2336,15 +2417,15 @@ async function renderSettings() {
|
|
|
2336
2417
|
await renderSettings();
|
|
2337
2418
|
await loadAiReferences();
|
|
2338
2419
|
}));
|
|
2339
|
-
$("#module-content").querySelectorAll("[data-edit-setting]").forEach((button) => button.addEventListener("click", () =>
|
|
2420
|
+
$("#module-content").querySelectorAll("[data-edit-setting]").forEach((button) => button.addEventListener("click", () => openSettingEditor(records.find((item) => item.id === button.dataset.editSetting))));
|
|
2340
2421
|
bindEntityHistoryButtons(async () => { await renderSettings(); await loadAiReferences(); });
|
|
2341
2422
|
}
|
|
2342
2423
|
|
|
2343
2424
|
async function renderCharacters() {
|
|
2344
2425
|
[state.characters, state.races, state.organizations] = await Promise.all([
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2426
|
+
apiPage(`/api/works/${state.work.id}/characters`).then((result) => result.items),
|
|
2427
|
+
apiAllPages(`/api/works/${state.work.id}/races`),
|
|
2428
|
+
apiAllPages(`/api/works/${state.work.id}/organizations`)
|
|
2348
2429
|
]);
|
|
2349
2430
|
const auditPanel = `<section class="character-audit-panel"><div><strong>角色身份确认</strong><small>让 AI 查询角色档案并搜索正文,找出可能被误建成两个档案的同一角色。AI 只提交审核建议,不会自动合并。</small></div><button id="create-character-audit-task" class="ghost-button" type="button" ${state.characters.length < 2 ? "disabled" : ""}>AI 角色查重</button></section>`;
|
|
2350
2431
|
$("#module-content").innerHTML = auditPanel + (state.characters.length ? `<div class="card-grid">${state.characters.map((item) => {
|
|
@@ -2374,7 +2455,7 @@ async function renderCharacters() {
|
|
|
2374
2455
|
}
|
|
2375
2456
|
});
|
|
2376
2457
|
$("#module-content").querySelectorAll("[data-open-character]").forEach((card) => {
|
|
2377
|
-
const open = () =>
|
|
2458
|
+
const open = () => openCharacterEditor(state.characters.find((item) => item.id === card.dataset.openCharacter));
|
|
2378
2459
|
card.addEventListener("click", (event) => { if (!event.target.closest("button")) open(); });
|
|
2379
2460
|
card.addEventListener("keydown", (event) => {
|
|
2380
2461
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
@@ -2382,13 +2463,13 @@ async function renderCharacters() {
|
|
|
2382
2463
|
open();
|
|
2383
2464
|
});
|
|
2384
2465
|
});
|
|
2385
|
-
$("#module-content").querySelectorAll("[data-edit-character]").forEach((button) => button.addEventListener("click", () =>
|
|
2466
|
+
$("#module-content").querySelectorAll("[data-edit-character]").forEach((button) => button.addEventListener("click", () => openCharacterEditor(state.characters.find((item) => item.id === button.dataset.editCharacter))));
|
|
2386
2467
|
}
|
|
2387
2468
|
|
|
2388
2469
|
async function renderRaces() {
|
|
2389
2470
|
[state.races, state.characters] = await Promise.all([
|
|
2390
|
-
|
|
2391
|
-
|
|
2471
|
+
apiAllPages(`/api/works/${state.work.id}/races`),
|
|
2472
|
+
apiAllPages(`/api/works/${state.work.id}/characters`)
|
|
2392
2473
|
]);
|
|
2393
2474
|
const renderRaceNode = (item) => `<details class="race-tree-node" open data-race-node="${esc(item.id)}">
|
|
2394
2475
|
<summary><span>${esc(item.name)}</span><small>${item.children.length} 个直接子种族</small></summary>
|
|
@@ -2410,8 +2491,8 @@ async function renderRaces() {
|
|
|
2410
2491
|
|
|
2411
2492
|
async function renderOrganizations() {
|
|
2412
2493
|
[state.organizations, state.characters] = await Promise.all([
|
|
2413
|
-
|
|
2414
|
-
|
|
2494
|
+
apiAllPages(`/api/works/${state.work.id}/organizations`),
|
|
2495
|
+
apiAllPages(`/api/works/${state.work.id}/characters`)
|
|
2415
2496
|
]);
|
|
2416
2497
|
$("#module-content").innerHTML = state.organizations.length ? `<div class="card-grid organization-grid">${state.organizations.map((item) => `
|
|
2417
2498
|
<article class="record-card organization-card"><small>${item.memberIds.length} 位成员 · ${item.settings.length} 条设定</small>
|
|
@@ -2426,8 +2507,8 @@ async function renderOrganizations() {
|
|
|
2426
2507
|
|
|
2427
2508
|
async function renderTimeline() {
|
|
2428
2509
|
const [events, tracks] = await Promise.all([
|
|
2429
|
-
|
|
2430
|
-
|
|
2510
|
+
apiPage(`/api/works/${state.work.id}/timeline`).then((result) => result.items),
|
|
2511
|
+
apiAllPages(`/api/works/${state.work.id}/timeline-tracks`)
|
|
2431
2512
|
]);
|
|
2432
2513
|
state.timelineTracks = tracks;
|
|
2433
2514
|
const lanes = [...tracks, { id: "", name: "未分组时间轴", description: "尚未归入独立大事件的时间节点。", sortOrder: Number.MAX_SAFE_INTEGER }];
|
|
@@ -2455,8 +2536,8 @@ async function renderTimeline() {
|
|
|
2455
2536
|
async function renderOutlines() {
|
|
2456
2537
|
const currentChapterId = state.chapter?.id;
|
|
2457
2538
|
const [outlines, foreshadows] = await Promise.all([
|
|
2458
|
-
|
|
2459
|
-
|
|
2539
|
+
apiPage(`/api/works/${state.work.id}/outlines`).then((result) => result.items),
|
|
2540
|
+
apiPage(`/api/works/${state.work.id}/foreshadows?status=all${currentChapterId ? `¤tChapterId=${encodeURIComponent(currentChapterId)}` : ""}`).then((result) => result.items)
|
|
2460
2541
|
]);
|
|
2461
2542
|
const unresolved = foreshadows.filter((item) => item.unresolved);
|
|
2462
2543
|
const overdue = unresolved.filter((item) => item.overdue);
|
|
@@ -2484,8 +2565,8 @@ async function renderOutlines() {
|
|
|
2484
2565
|
}
|
|
2485
2566
|
|
|
2486
2567
|
async function renderRelationships() {
|
|
2487
|
-
state.characters = await
|
|
2488
|
-
const relationships = await
|
|
2568
|
+
state.characters = await apiAllPages(`/api/works/${state.work.id}/characters`);
|
|
2569
|
+
const relationships = (await apiPage(`/api/works/${state.work.id}/relationships`)).items;
|
|
2489
2570
|
const nameOf = (id) => state.characters.find((item) => item.id === id)?.name ?? "未知角色";
|
|
2490
2571
|
state.galaxy?.destroy();
|
|
2491
2572
|
state.relationshipExpandedMap?.destroy?.();
|
|
@@ -2516,8 +2597,8 @@ async function renderRelationships() {
|
|
|
2516
2597
|
|
|
2517
2598
|
async function renderReviews() {
|
|
2518
2599
|
const [reviews, characters] = await Promise.all([
|
|
2519
|
-
|
|
2520
|
-
|
|
2600
|
+
apiPage(`/api/works/${state.work.id}/reviews`).then((result) => result.items),
|
|
2601
|
+
apiAllPages(`/api/works/${state.work.id}/characters?includeMerged=1`)
|
|
2521
2602
|
]);
|
|
2522
2603
|
const characterById = new Map(characters.map((character) => [character.id, character]));
|
|
2523
2604
|
const duplicateCard = (item) => {
|
|
@@ -2577,7 +2658,7 @@ async function renderReviews() {
|
|
|
2577
2658
|
|
|
2578
2659
|
async function renderTasks() {
|
|
2579
2660
|
const [tasks, settings] = await Promise.all([
|
|
2580
|
-
|
|
2661
|
+
apiPage(`/api/works/${state.work.id}/tasks`).then((result) => result.items),
|
|
2581
2662
|
api(`/api/works/${state.work.id}/ai-settings`)
|
|
2582
2663
|
]);
|
|
2583
2664
|
const pendingCount = tasks.filter((item) => item.status === "pending").length;
|
|
@@ -3012,8 +3093,8 @@ async function loadAiReferences() {
|
|
|
3012
3093
|
const workId = state.work?.id;
|
|
3013
3094
|
if (!workId) return;
|
|
3014
3095
|
const [characters, settings] = await Promise.all([
|
|
3015
|
-
|
|
3016
|
-
|
|
3096
|
+
apiAllPages(`/api/works/${workId}/characters`),
|
|
3097
|
+
apiAllPages(`/api/works/${workId}/settings`)
|
|
3017
3098
|
]);
|
|
3018
3099
|
if (state.work?.id !== workId) return;
|
|
3019
3100
|
state.characters = characters;
|
|
@@ -3230,7 +3311,7 @@ function bindWorkCoverControls(work) {
|
|
|
3230
3311
|
$("#work-cover-remove")?.addEventListener("click", async () => {
|
|
3231
3312
|
try {
|
|
3232
3313
|
await api(`/api/works/${work.id}/cover`, { method: "DELETE" });
|
|
3233
|
-
state.works = await
|
|
3314
|
+
state.works = (await apiPage("/api/works")).items;
|
|
3234
3315
|
const updated = state.works.find((item) => item.id === work.id) ?? { ...work, coverUrl: null };
|
|
3235
3316
|
Object.assign(work, updated);
|
|
3236
3317
|
const coverField = $("#dialog-fields")?.querySelector(".work-cover-field");
|
|
@@ -3257,7 +3338,7 @@ function openWorkSettingsDialog(work) {
|
|
|
3257
3338
|
workCoverFieldHtml(work) + field("title", "作品名称", "text", work.title) + field("author", "作者", "text", work.author) + field("description", "简介", "textarea", work.description) + accessField,
|
|
3258
3339
|
async (form) => {
|
|
3259
3340
|
await api(`/api/works/${work.id}`, { method: "PATCH", body: { title: form.get("title"), author: form.get("author"), description: form.get("description") } });
|
|
3260
|
-
state.works = await
|
|
3341
|
+
state.works = (await apiPage("/api/works")).items;
|
|
3261
3342
|
const updated = state.works.find((item) => item.id === work.id);
|
|
3262
3343
|
if (updated) Object.assign(work, updated);
|
|
3263
3344
|
if (state.work?.id === work.id) {
|
|
@@ -3314,17 +3395,50 @@ function openVolumeDialog(item) {
|
|
|
3314
3395
|
}, "分卷设置");
|
|
3315
3396
|
}
|
|
3316
3397
|
|
|
3317
|
-
function
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3398
|
+
function openSettingEditor(item = null) {
|
|
3399
|
+
settingEditorItem = item;
|
|
3400
|
+
$("#setting-editor-eyebrow").textContent = item ? "人工修正" : "作者事实";
|
|
3401
|
+
$("#setting-editor-title").textContent = item ? `编辑“${item.title}”` : "新建设定";
|
|
3402
|
+
$("#setting-editor-name").value = item?.title ?? "";
|
|
3403
|
+
$("#setting-editor-category").value = item?.category ?? "世界规则";
|
|
3404
|
+
$("#setting-editor-locked").checked = Boolean(item?.locked);
|
|
3405
|
+
$("#setting-editor-body").value = item?.content ?? "";
|
|
3406
|
+
$("#setting-change-note").value = "";
|
|
3407
|
+
$("#setting-change-note-field").classList.toggle("hidden", !item);
|
|
3408
|
+
$("#setting-editor-submit").textContent = item ? "保存新版本" : "创建设定";
|
|
3409
|
+
const viewOnly = !canEditWork();
|
|
3410
|
+
$("#setting-editor-form").querySelectorAll("input, textarea").forEach((control) => { control.readOnly = viewOnly; });
|
|
3411
|
+
$("#setting-editor-form").querySelectorAll("select, input[type='checkbox']").forEach((control) => { control.disabled = viewOnly; });
|
|
3412
|
+
$("#setting-editor-submit").classList.toggle("hidden", viewOnly);
|
|
3413
|
+
$("#setting-editor-form").onsubmit = async (event) => {
|
|
3414
|
+
event.preventDefault();
|
|
3415
|
+
if (!canEditWork()) return;
|
|
3416
|
+
const form = new FormData(event.currentTarget);
|
|
3417
|
+
const submit = $("#setting-editor-submit");
|
|
3418
|
+
submit.disabled = true;
|
|
3419
|
+
try {
|
|
3420
|
+
const locked = form.get("locked") === "on";
|
|
3421
|
+
const body = {
|
|
3422
|
+
title: String(form.get("title") ?? "").trim(),
|
|
3423
|
+
category: String(form.get("category") ?? "世界规则"),
|
|
3424
|
+
content: String(form.get("content") ?? ""),
|
|
3425
|
+
locked,
|
|
3426
|
+
status: locked ? "confirmed" : (item?.status ?? "draft"),
|
|
3427
|
+
...(item ? { changeNote: String(form.get("changeNote") ?? "").trim() } : {})
|
|
3428
|
+
};
|
|
3324
3429
|
await api(item ? `/api/settings/${item.id}` : `/api/works/${state.work.id}/settings`, { method: item ? "PATCH" : "POST", body });
|
|
3325
|
-
|
|
3430
|
+
entityEditorDirty = false;
|
|
3326
3431
|
await loadAiReferences();
|
|
3327
|
-
|
|
3432
|
+
await closeEntityEditor({ force: true });
|
|
3433
|
+
toast(item ? "设定新版本已保存" : "设定已创建");
|
|
3434
|
+
} catch (error) {
|
|
3435
|
+
toast(error.message, "error");
|
|
3436
|
+
} finally {
|
|
3437
|
+
submit.disabled = false;
|
|
3438
|
+
}
|
|
3439
|
+
};
|
|
3440
|
+
showEntityEditorPage("setting");
|
|
3441
|
+
$("#setting-editor-name").focus();
|
|
3328
3442
|
}
|
|
3329
3443
|
|
|
3330
3444
|
function characterEditorSection(key, title, description, content) {
|
|
@@ -3400,8 +3514,8 @@ async function loadCharacterEditorRelationships(characterId) {
|
|
|
3400
3514
|
let loaded = false;
|
|
3401
3515
|
try {
|
|
3402
3516
|
const [characters, relationships] = await Promise.all([
|
|
3403
|
-
|
|
3404
|
-
|
|
3517
|
+
apiAllPages(`/api/works/${workId}/characters`),
|
|
3518
|
+
apiAllPages(`/api/works/${workId}/relationships`)
|
|
3405
3519
|
]);
|
|
3406
3520
|
if (state.work?.id !== workId || characterEditorItem?.id !== characterId) return;
|
|
3407
3521
|
state.characters = characters;
|
|
@@ -3422,7 +3536,7 @@ async function loadCharacterEditorRelationships(characterId) {
|
|
|
3422
3536
|
async function refreshRelationshipSurfaces(characterId = null) {
|
|
3423
3537
|
const tasks = [];
|
|
3424
3538
|
if (state.module === "relationships") tasks.push(renderRelationships());
|
|
3425
|
-
if (characterId &&
|
|
3539
|
+
if (characterId && entityEditorType === "character" && !$("#entity-editor-view").classList.contains("hidden") && characterEditorItem?.id === characterId) {
|
|
3426
3540
|
tasks.push(loadCharacterEditorRelationships(characterId));
|
|
3427
3541
|
}
|
|
3428
3542
|
await Promise.all(tasks);
|
|
@@ -3459,9 +3573,29 @@ function scheduleCharacterSectionPreview() {
|
|
|
3459
3573
|
}, 260);
|
|
3460
3574
|
}
|
|
3461
3575
|
|
|
3576
|
+
async function closeCharacterSectionEditor({ force = false } = {}) {
|
|
3577
|
+
if (!force && characterSectionEditorDirty && !window.confirm("当前 Markdown 章节有未保存修改,返回人物档案将丢弃这些修改。是否继续?")) return false;
|
|
3578
|
+
if (characterSectionPreviewTimer !== null) {
|
|
3579
|
+
clearTimeout(characterSectionPreviewTimer);
|
|
3580
|
+
characterSectionPreviewTimer = null;
|
|
3581
|
+
}
|
|
3582
|
+
await discardPendingCharacterAttachments();
|
|
3583
|
+
characterSectionEditorDirty = false;
|
|
3584
|
+
$("#character-section-editor-view").classList.add("hidden");
|
|
3585
|
+
$("#character-editor-form").classList.remove("hidden");
|
|
3586
|
+
$("#character-section-editor-host").innerHTML = "";
|
|
3587
|
+
replacePageRoute(currentPageRoute());
|
|
3588
|
+
return true;
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3462
3591
|
function characterSectionEditorHtml(section = null) {
|
|
3463
3592
|
const options = Object.entries(characterSectionTypeLabels).map(([value, label]) => `<option value="${value}" ${section?.sectionType === value ? "selected" : ""}>${esc(label)}</option>`).join("");
|
|
3464
|
-
return `<
|
|
3593
|
+
return `<div class="character-section-editor-shell">
|
|
3594
|
+
<header class="character-section-editor-header">
|
|
3595
|
+
<div><span class="eyebrow">人物 Markdown 档案</span><h2 id="character-section-editor-title">${section ? `编辑“${esc(section.title)}”` : "新建档案章节"}</h2></div>
|
|
3596
|
+
<button class="entity-editor-back" type="button" data-character-section-edit-close>返回人物档案</button>
|
|
3597
|
+
</header>
|
|
3598
|
+
<section class="character-markdown-editor" aria-label="${section ? "编辑" : "新建"}人物 Markdown 章节">
|
|
3465
3599
|
<div class="character-markdown-editor-meta">
|
|
3466
3600
|
<label>章节类型<select id="character-section-type">${options}</select></label>
|
|
3467
3601
|
<label>章节标题<input id="character-section-title" maxlength="200" value="${esc(section?.title ?? "")}" placeholder="例如:背景故事" required></label>
|
|
@@ -3476,17 +3610,23 @@ function characterSectionEditorHtml(section = null) {
|
|
|
3476
3610
|
<label>Markdown 原文<textarea id="character-section-markdown" maxlength="500000" spellcheck="true" placeholder="支持标题、列表、引用、表格、链接和图片">${esc(section?.contentMarkdown ?? "")}</textarea></label>
|
|
3477
3611
|
<div><span class="character-markdown-preview-label">安全预览</span><article id="character-section-preview" class="character-markdown-document message-body">${renderMarkdown(section?.contentMarkdown ?? "") || '<p class="character-markdown-empty">预览区域暂无内容。</p>'}</article></div>
|
|
3478
3612
|
</div>
|
|
3479
|
-
<
|
|
3480
|
-
|
|
3481
|
-
|
|
3613
|
+
<div class="character-markdown-editor-footer">
|
|
3614
|
+
<label class="character-markdown-change-note">版本说明<input id="character-section-change-note" maxlength="500" placeholder="可选,例如:补充远古时期经历"></label>
|
|
3615
|
+
<div class="character-markdown-editor-actions"><button type="button" data-character-section-edit-cancel>取消</button><button type="button" class="primary-button" data-character-section-edit-save>${section ? "保存章节版本" : "创建章节"}</button></div>
|
|
3616
|
+
</div>
|
|
3617
|
+
</section>
|
|
3618
|
+
</div>`;
|
|
3482
3619
|
}
|
|
3483
3620
|
|
|
3484
3621
|
async function openCharacterSectionEditor(section = null) {
|
|
3485
3622
|
await discardPendingCharacterAttachments();
|
|
3486
|
-
const host = $("#character-
|
|
3487
|
-
if (!host) return;
|
|
3623
|
+
const host = $("#character-section-editor-host");
|
|
3488
3624
|
host.innerHTML = characterSectionEditorHtml(section);
|
|
3625
|
+
characterSectionEditorDirty = false;
|
|
3626
|
+
$("#character-editor-form").classList.add("hidden");
|
|
3627
|
+
$("#character-section-editor-view").classList.remove("hidden");
|
|
3489
3628
|
const textarea = $("#character-section-markdown");
|
|
3629
|
+
host.querySelectorAll("input, textarea, select").forEach((control) => control.addEventListener("input", () => { characterSectionEditorDirty = true; }));
|
|
3490
3630
|
textarea.addEventListener("input", scheduleCharacterSectionPreview);
|
|
3491
3631
|
$("#character-section-attachment").addEventListener("change", async (event) => {
|
|
3492
3632
|
const file = event.target.files?.[0];
|
|
@@ -3506,6 +3646,7 @@ async function openCharacterSectionEditor(section = null) {
|
|
|
3506
3646
|
const suffix = end < textarea.value.length && !textarea.value.slice(end).startsWith("\n") ? "\n\n" : "";
|
|
3507
3647
|
textarea.setRangeText(`${prefix}${insertion}${suffix}`, start, end, "end");
|
|
3508
3648
|
textarea.focus();
|
|
3649
|
+
characterSectionEditorDirty = true;
|
|
3509
3650
|
scheduleCharacterSectionPreview();
|
|
3510
3651
|
toast(attachment.storedMimeType === "image/webp" ? "图片已转换为无损 WebP 并插入" : "图片已插入;转换后未变小,因此保留原格式");
|
|
3511
3652
|
} catch (error) {
|
|
@@ -3515,10 +3656,8 @@ async function openCharacterSectionEditor(section = null) {
|
|
|
3515
3656
|
input.value = "";
|
|
3516
3657
|
}
|
|
3517
3658
|
});
|
|
3518
|
-
host.querySelector("[data-character-section-edit-
|
|
3519
|
-
|
|
3520
|
-
renderCharacterMarkdownSections();
|
|
3521
|
-
});
|
|
3659
|
+
host.querySelector("[data-character-section-edit-close]").addEventListener("click", () => void closeCharacterSectionEditor());
|
|
3660
|
+
host.querySelector("[data-character-section-edit-cancel]").addEventListener("click", () => void closeCharacterSectionEditor());
|
|
3522
3661
|
host.querySelector("[data-character-section-edit-save]").addEventListener("click", async (event) => {
|
|
3523
3662
|
const button = event.currentTarget;
|
|
3524
3663
|
const title = $("#character-section-title").value.trim();
|
|
@@ -3547,6 +3686,8 @@ async function openCharacterSectionEditor(section = null) {
|
|
|
3547
3686
|
characterEditorSections = await api(`/api/characters/${characterEditorItem.id}/sections`);
|
|
3548
3687
|
renderCharacterMarkdownSections();
|
|
3549
3688
|
await Promise.all([renderCharacters(), loadAiReferences()]);
|
|
3689
|
+
characterSectionEditorDirty = false;
|
|
3690
|
+
await closeCharacterSectionEditor({ force: true });
|
|
3550
3691
|
toast(section ? `“${saved.title}”已保存为 v${saved.versionNo}` : `已创建“${saved.title}”`);
|
|
3551
3692
|
} catch (error) {
|
|
3552
3693
|
toast(error.message, "error");
|
|
@@ -3780,10 +3921,10 @@ async function showCharacterHistory() {
|
|
|
3780
3921
|
}
|
|
3781
3922
|
}
|
|
3782
3923
|
|
|
3783
|
-
async function
|
|
3924
|
+
async function openCharacterEditor(item = null) {
|
|
3784
3925
|
[state.races, state.organizations] = await Promise.all([
|
|
3785
|
-
|
|
3786
|
-
|
|
3926
|
+
apiAllPages(`/api/works/${state.work.id}/races`),
|
|
3927
|
+
apiAllPages(`/api/works/${state.work.id}/organizations`)
|
|
3787
3928
|
]);
|
|
3788
3929
|
characterEditorItem = item ?? null;
|
|
3789
3930
|
characterEditorVersions = [];
|
|
@@ -3805,13 +3946,14 @@ async function openCharacterDialog(item) {
|
|
|
3805
3946
|
$("#character-editor-fields").querySelectorAll("input, textarea").forEach((control) => { control.readOnly = true; });
|
|
3806
3947
|
$("#character-editor-fields").querySelectorAll("select, input[type='checkbox']").forEach((control) => { control.disabled = true; });
|
|
3807
3948
|
}
|
|
3949
|
+
$("#character-change-note").readOnly = viewOnly;
|
|
3950
|
+
$("#character-editor-submit").classList.toggle("hidden", viewOnly);
|
|
3808
3951
|
document.querySelectorAll("[data-character-editor-tab]").forEach((button) => {
|
|
3809
3952
|
button.onclick = () => activateCharacterEditorTab(button.dataset.characterEditorTab);
|
|
3810
3953
|
});
|
|
3811
3954
|
const relationshipTab = document.querySelector("[data-character-editor-tab='relationships']");
|
|
3812
3955
|
relationshipTab.disabled = !item;
|
|
3813
3956
|
relationshipTab.title = item ? "查看和编辑人物关系" : "创建人物档案后即可维护人物关系";
|
|
3814
|
-
const dialog = $("#character-editor-dialog");
|
|
3815
3957
|
const form = $("#character-editor-form");
|
|
3816
3958
|
form.onsubmit = async (event) => {
|
|
3817
3959
|
event.preventDefault();
|
|
@@ -3825,8 +3967,9 @@ async function openCharacterDialog(item) {
|
|
|
3825
3967
|
const previousVersion = characterEditorItem?.versionNo;
|
|
3826
3968
|
if (!wasEditing) delete body.changeNote;
|
|
3827
3969
|
const saved = await api(wasEditing ? `/api/characters/${characterEditorItem.id}` : `/api/works/${state.work.id}/characters`, { method: wasEditing ? "PATCH" : "POST", body });
|
|
3828
|
-
|
|
3829
|
-
await
|
|
3970
|
+
entityEditorDirty = false;
|
|
3971
|
+
await loadAiReferences();
|
|
3972
|
+
await closeEntityEditor({ force: true });
|
|
3830
3973
|
toast(!wasEditing ? "人物档案已创建" : saved.versionNo === previousVersion ? "没有检测到人物档案变更" : `人物档案已保存为 v${saved.versionNo}`);
|
|
3831
3974
|
} catch (error) {
|
|
3832
3975
|
toast(error.message, "error");
|
|
@@ -3834,7 +3977,7 @@ async function openCharacterDialog(item) {
|
|
|
3834
3977
|
submit.disabled = false;
|
|
3835
3978
|
}
|
|
3836
3979
|
};
|
|
3837
|
-
|
|
3980
|
+
showEntityEditorPage("character");
|
|
3838
3981
|
if (item) {
|
|
3839
3982
|
void loadCharacterEditorRelationships(item.id);
|
|
3840
3983
|
void loadCharacterMarkdownSections(item.id);
|
|
@@ -3842,7 +3985,7 @@ async function openCharacterDialog(item) {
|
|
|
3842
3985
|
}
|
|
3843
3986
|
|
|
3844
3987
|
async function openRaceDialog(item) {
|
|
3845
|
-
state.characters = await
|
|
3988
|
+
state.characters = await apiAllPages(`/api/works/${state.work.id}/characters`);
|
|
3846
3989
|
const memberOptions = state.characters.map((character) => [character.id, `${character.name}${character.aliases.length ? `(${character.aliases.join("、")})` : ""}`]);
|
|
3847
3990
|
const parentOptions = [["", "无(根种族)"], ...eligibleRaceParents(state.races, item?.id)
|
|
3848
3991
|
.sort((left, right) => racePathLabel(left).localeCompare(racePathLabel(right), "zh-CN"))
|
|
@@ -3863,7 +4006,7 @@ async function openRaceDialog(item) {
|
|
|
3863
4006
|
}
|
|
3864
4007
|
|
|
3865
4008
|
async function openOrganizationDialog(item) {
|
|
3866
|
-
state.characters = await
|
|
4009
|
+
state.characters = await apiAllPages(`/api/works/${state.work.id}/characters`);
|
|
3867
4010
|
const memberOptions = state.characters.map((character) => [character.id, `${character.name}${character.aliases.length ? `(${character.aliases.join("、")})` : ""}`]);
|
|
3868
4011
|
openDialog(item ? "编辑组织" : "新建组织",
|
|
3869
4012
|
field("name", "组织名称", "text", item?.name) +
|
|
@@ -3968,7 +4111,7 @@ function openTimelineSplitDialog(item) {
|
|
|
3968
4111
|
}
|
|
3969
4112
|
|
|
3970
4113
|
async function openRelationshipDialog(item, options = {}) {
|
|
3971
|
-
state.characters = await
|
|
4114
|
+
state.characters = await apiAllPages(`/api/works/${state.work.id}/characters`);
|
|
3972
4115
|
if (state.characters.length < 2) return toast("至少需要两个角色才能创建关系", "error");
|
|
3973
4116
|
const characterOptions = state.characters.map((item) => [item.id, item.name]);
|
|
3974
4117
|
const defaultFrom = options.characterId && state.characters.some((character) => character.id === options.characterId) ? options.characterId : characterOptions[0][0];
|
|
@@ -4596,9 +4739,16 @@ $("#versions-button").addEventListener("click", showVersions);
|
|
|
4596
4739
|
$("#versions-close").addEventListener("click", () => $("#versions-dialog").close());
|
|
4597
4740
|
$("#entity-history-close").addEventListener("click", () => $("#entity-history-dialog").close());
|
|
4598
4741
|
$("#ai-tool-call-close").addEventListener("click", () => $("#ai-tool-call-dialog").close());
|
|
4599
|
-
$("#
|
|
4600
|
-
$("#character-editor-
|
|
4601
|
-
$("#character-editor-
|
|
4742
|
+
$("#setting-editor-back").addEventListener("click", () => { void closeEntityEditor(); });
|
|
4743
|
+
$("#character-editor-close").addEventListener("click", () => { void closeEntityEditor(); });
|
|
4744
|
+
$("#character-editor-cancel").addEventListener("click", () => { void closeEntityEditor(); });
|
|
4745
|
+
$("#setting-editor-form").addEventListener("input", markEntityEditorDirty);
|
|
4746
|
+
$("#setting-editor-form").addEventListener("change", markEntityEditorDirty);
|
|
4747
|
+
$("#character-editor-form").addEventListener("input", markEntityEditorDirty);
|
|
4748
|
+
$("#character-editor-form").addEventListener("change", markEntityEditorDirty);
|
|
4749
|
+
$("#character-editor-fields").addEventListener("click", (event) => {
|
|
4750
|
+
if (event.target.closest("[data-item-list-add], [data-structured-list-add], [data-item-list-remove], [data-structured-list-remove]")) markEntityEditorDirty();
|
|
4751
|
+
});
|
|
4602
4752
|
$("#character-history-button").addEventListener("click", () => {
|
|
4603
4753
|
if ($("#character-history-panel").classList.contains("hidden")) void showCharacterHistory();
|
|
4604
4754
|
else setCharacterHistoryVisible(false);
|
|
@@ -4697,7 +4847,7 @@ $("#module-nav").addEventListener("click", (event) => {
|
|
|
4697
4847
|
if (button.dataset.module) showModule(button.dataset.module);
|
|
4698
4848
|
});
|
|
4699
4849
|
$("#module-more-button").addEventListener("click", () => setModuleNavExpanded(!moduleNavExpanded));
|
|
4700
|
-
$("#module-create-button").addEventListener("click", () => ({ settings:
|
|
4850
|
+
$("#module-create-button").addEventListener("click", () => ({ settings: openSettingEditor, characters: openCharacterEditor, races: openRaceDialog, organizations: openOrganizationDialog, timeline: openTimelineDialog, outlines: openForeshadowDialog, relationships: openRelationshipDialog, reviews: openReviewDialog, tasks: openTaskDialog })[state.module]?.());
|
|
4701
4851
|
$("#ai-prompt").addEventListener("input", async () => {
|
|
4702
4852
|
updateAiMentionMenu();
|
|
4703
4853
|
scheduleAiContextUsage();
|
|
@@ -4769,7 +4919,7 @@ $("#cover-file").addEventListener("change", async (event) => {
|
|
|
4769
4919
|
body.append("file", file);
|
|
4770
4920
|
try {
|
|
4771
4921
|
await api(`/api/works/${workId}/cover`, { method: "PUT", body });
|
|
4772
|
-
state.works = await
|
|
4922
|
+
state.works = (await apiPage("/api/works")).items;
|
|
4773
4923
|
const updated = state.works.find((item) => item.id === workId);
|
|
4774
4924
|
const coverField = $("#dialog-fields")?.querySelector(".work-cover-field");
|
|
4775
4925
|
if (updated && coverField && $("#form-dialog")?.open) {
|
|
@@ -4908,7 +5058,7 @@ $("#search-form").addEventListener("submit", async (event) => {
|
|
|
4908
5058
|
$("#export-button").addEventListener("click", () => {
|
|
4909
5059
|
if (state.work) window.location.href = `/api/works/${state.work.id}/export?format=markdown`;
|
|
4910
5060
|
});
|
|
4911
|
-
window.addEventListener("beforeunload", (event) => { if (state.dirty) event.preventDefault(); });
|
|
5061
|
+
window.addEventListener("beforeunload", (event) => { if (state.dirty || entityEditorDirty || characterSectionEditorDirty) event.preventDefault(); });
|
|
4912
5062
|
|
|
4913
5063
|
initializePage().catch((error) => {
|
|
4914
5064
|
restoringPageRoute = false;
|