@musnows/scriverse 0.3.3 → 0.3.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 +9 -1
- package/README.md +9 -1
- package/dist/app.js +14 -2
- package/dist/app.js.map +1 -1
- package/dist/public/app.js +181 -11
- package/dist/public/index.html +12 -8
- package/dist/public/page-route.js +5 -1
- package/dist/public/relationship-keywords.js +34 -0
- package/dist/public/styles.css +48 -1
- package/dist/public/theme-init.js +15 -0
- package/dist/store.js +3 -4
- package/dist/store.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/public/app.js
CHANGED
|
@@ -15,6 +15,7 @@ import { buildCharacterDetails, buildCharacterSections, buildCharacterState, cha
|
|
|
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
17
|
import { parsePageRoute, serializePageRoute } from "/page-route.js?v=20260714-refresh-restore";
|
|
18
|
+
import { splitRelationshipKeywordInput, splitRelationshipKeywords, uniqueRelationshipKeywords } from "/relationship-keywords.js?v=20260720-relationship-keyword-chips";
|
|
18
19
|
import { tokenizeVisibleSpaces } from "/whitespace-visualization.js?v=20260718-visible-whitespace";
|
|
19
20
|
|
|
20
21
|
const state = {
|
|
@@ -459,6 +460,8 @@ let aiMentionRange = null;
|
|
|
459
460
|
let settingsReturnContext = null;
|
|
460
461
|
let characterEditorItem = null;
|
|
461
462
|
let characterEditorVersions = [];
|
|
463
|
+
let characterEditorRelationships = [];
|
|
464
|
+
let characterEditorRelationshipsLoading = false;
|
|
462
465
|
let entityHistoryContext = null;
|
|
463
466
|
|
|
464
467
|
function setModuleNavExpanded(expanded) {
|
|
@@ -1393,7 +1396,19 @@ function selectAuthMode(mode) {
|
|
|
1393
1396
|
$("#login-form").classList.toggle("hidden", !login);
|
|
1394
1397
|
$("#register-form").classList.toggle("hidden", login);
|
|
1395
1398
|
$("#auth-error").textContent = "";
|
|
1396
|
-
|
|
1399
|
+
// 验证码默认不加载,等用户点击“点击显示验证码”按钮后再请求,避免首屏空白等待
|
|
1400
|
+
resetAuthCaptcha(login ? "login" : "register");
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
function resetAuthCaptcha(target = "login") {
|
|
1404
|
+
const prefix = target === "register" ? "register" : "login";
|
|
1405
|
+
const image = $(`#${prefix}-captcha-image`);
|
|
1406
|
+
image.hidden = true;
|
|
1407
|
+
image.removeAttribute("src");
|
|
1408
|
+
$(`#${prefix}-captcha-placeholder`).hidden = false;
|
|
1409
|
+
$(`#${prefix}-captcha-id`).value = "";
|
|
1410
|
+
const answerInput = $(`#${prefix}-form`).querySelector('input[name="captchaAnswer"]');
|
|
1411
|
+
if (answerInput) answerInput.value = "";
|
|
1397
1412
|
}
|
|
1398
1413
|
|
|
1399
1414
|
async function refreshAuthCaptcha(target = "login") {
|
|
@@ -1402,7 +1417,10 @@ async function refreshAuthCaptcha(target = "login") {
|
|
|
1402
1417
|
const challenge = (await response.json()).data;
|
|
1403
1418
|
const prefix = target === "register" ? "register" : "login";
|
|
1404
1419
|
$(`#${prefix}-captcha-id`).value = challenge.captchaId;
|
|
1405
|
-
$(`#${prefix}-captcha-image`)
|
|
1420
|
+
const image = $(`#${prefix}-captcha-image`);
|
|
1421
|
+
image.src = challenge.imageDataUrl;
|
|
1422
|
+
image.hidden = false;
|
|
1423
|
+
$(`#${prefix}-captcha-placeholder`).hidden = true;
|
|
1406
1424
|
const answerInput = $(`#${prefix}-form`).querySelector('input[name="captchaAnswer"]');
|
|
1407
1425
|
if (answerInput) answerInput.value = "";
|
|
1408
1426
|
}
|
|
@@ -1434,7 +1452,9 @@ function applyAuthenticatedUser(session) {
|
|
|
1434
1452
|
$("#account-menu-name").textContent = `${session.user.displayName} · @${session.user.username}`;
|
|
1435
1453
|
$("#account-menu-role").textContent = session.user.role === "admin" ? "系统管理员" : "普通用户";
|
|
1436
1454
|
$("#auth-view").classList.add("hidden");
|
|
1437
|
-
document.
|
|
1455
|
+
document.documentElement.classList.remove("login-route");
|
|
1456
|
+
// 注意:auth-pending 由 initializePage 路由完成后才移除,
|
|
1457
|
+
// 避免会话确认后、目标视图渲染前露出无内容的编辑器外壳
|
|
1438
1458
|
}
|
|
1439
1459
|
|
|
1440
1460
|
function applyPlatformUiSettings(settings) {
|
|
@@ -1451,13 +1471,18 @@ async function loadPlatformUiSettings() {
|
|
|
1451
1471
|
}
|
|
1452
1472
|
|
|
1453
1473
|
async function initializeAuthentication() {
|
|
1474
|
+
const route = parsePageRoute(window.location.hash);
|
|
1454
1475
|
const response = await fetch("/api/auth/session", { headers: { Accept: "application/json" } });
|
|
1455
1476
|
if (!response.ok) throw new Error("无法读取登录状态");
|
|
1456
1477
|
const session = (await response.json()).data;
|
|
1457
1478
|
if (!session.authenticated) {
|
|
1479
|
+
// 未登录时一律转到登录页路由;登录页本身则保持原样
|
|
1480
|
+
if (route.view !== "login") window.history.replaceState(null, "", serializePageRoute({ view: "login" }));
|
|
1458
1481
|
showAuth(session.setupRequired, session.registrationOpen === true);
|
|
1459
1482
|
return false;
|
|
1460
1483
|
}
|
|
1484
|
+
// 已登录却停在登录页路由时,回到书架首页
|
|
1485
|
+
if (route.view === "login") window.history.replaceState(null, "", serializePageRoute({ view: "shelf" }));
|
|
1461
1486
|
applyAuthenticatedUser(session);
|
|
1462
1487
|
await loadPlatformUiSettings();
|
|
1463
1488
|
return true;
|
|
@@ -1664,6 +1689,7 @@ async function initializePage() {
|
|
|
1664
1689
|
settingsReturnContext = restoredSettingsReturnContext(route);
|
|
1665
1690
|
}
|
|
1666
1691
|
} finally {
|
|
1692
|
+
document.body.classList.remove("auth-pending");
|
|
1667
1693
|
restoringPageRoute = false;
|
|
1668
1694
|
replacePageRoute(currentPageRoute());
|
|
1669
1695
|
scheduleFirstUseOnboarding();
|
|
@@ -2938,6 +2964,11 @@ function field(name, label, type = "text", value = "", options = []) {
|
|
|
2938
2964
|
const values = Array.isArray(value) && value.length ? value : [""];
|
|
2939
2965
|
return `<div class="form-field item-list-field"><span>${esc(label)}</span><div class="item-list-rows" data-item-list-rows data-name="${esc(name)}" data-label="${esc(label)}">${values.map((item) => `<div class="item-list-row"><input name="${esc(name)}" value="${esc(item)}" aria-label="${esc(label)}"><button type="button" data-item-list-remove aria-label="删除此条">删除</button></div>`).join("")}</div><button class="item-list-add" type="button" data-item-list-add>添加一条</button></div>`;
|
|
2940
2966
|
}
|
|
2967
|
+
if (type === "keyword-chips") {
|
|
2968
|
+
const values = uniqueRelationshipKeywords(Array.isArray(value) ? value : []);
|
|
2969
|
+
const chips = values.map((keyword) => `<span class="keyword-chip" data-keyword-chip><span>${esc(keyword)}</span><input type="hidden" name="${esc(name)}" value="${esc(keyword)}" data-keyword-value><button type="button" data-keyword-chip-remove aria-label="删除关键词:${esc(keyword)}">×</button></span>`).join("");
|
|
2970
|
+
return `<div class="form-field keyword-chip-field" data-keyword-chips data-name="${esc(name)}"><span>${esc(label)}</span><div class="keyword-chip-editor" role="group" aria-label="${esc(label)}">${chips}<input type="text" data-keyword-input aria-label="${esc(label)}" placeholder="输入后按回车添加,逗号可批量添加" autocomplete="off"></div><small>输入关键词后按回车添加;也可用逗号一次添加多个。</small></div>`;
|
|
2971
|
+
}
|
|
2941
2972
|
if (type === "key-value-list") {
|
|
2942
2973
|
const config = Array.isArray(options) ? {} : options;
|
|
2943
2974
|
const keyName = config.keyName ?? "detailLabel";
|
|
@@ -3004,6 +3035,53 @@ function bindDynamicListControls(container) {
|
|
|
3004
3035
|
};
|
|
3005
3036
|
}
|
|
3006
3037
|
|
|
3038
|
+
function appendRelationshipKeywordChips(editor, values) {
|
|
3039
|
+
const input = editor.querySelector("[data-keyword-input]");
|
|
3040
|
+
if (!input) return;
|
|
3041
|
+
const existing = new Set([...editor.querySelectorAll("[data-keyword-value]")].map((control) => String(control.value).toLocaleLowerCase("zh-CN")));
|
|
3042
|
+
const name = editor.dataset.name || "keywords";
|
|
3043
|
+
for (const keyword of uniqueRelationshipKeywords(values)) {
|
|
3044
|
+
const key = keyword.toLocaleLowerCase("zh-CN");
|
|
3045
|
+
if (existing.has(key)) continue;
|
|
3046
|
+
existing.add(key);
|
|
3047
|
+
input.insertAdjacentHTML("beforebegin", `<span class="keyword-chip" data-keyword-chip><span>${esc(keyword)}</span><input type="hidden" name="${esc(name)}" value="${esc(keyword)}" data-keyword-value><button type="button" data-keyword-chip-remove aria-label="删除关键词:${esc(keyword)}">×</button></span>`);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
function commitRelationshipKeywordInput(editor) {
|
|
3052
|
+
const input = editor.querySelector("[data-keyword-input]");
|
|
3053
|
+
if (!input) return;
|
|
3054
|
+
appendRelationshipKeywordChips(editor, splitRelationshipKeywords(input.value));
|
|
3055
|
+
input.value = "";
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
function bindRelationshipKeywordControls(container) {
|
|
3059
|
+
container.querySelectorAll("[data-keyword-chips]").forEach((editor) => {
|
|
3060
|
+
const input = editor.querySelector("[data-keyword-input]");
|
|
3061
|
+
if (!input) return;
|
|
3062
|
+
input.addEventListener("keydown", (event) => {
|
|
3063
|
+
if (event.isComposing || event.key !== "Enter") return;
|
|
3064
|
+
event.preventDefault();
|
|
3065
|
+
commitRelationshipKeywordInput(editor);
|
|
3066
|
+
});
|
|
3067
|
+
input.addEventListener("input", () => {
|
|
3068
|
+
const { completed, remainder } = splitRelationshipKeywordInput(input.value);
|
|
3069
|
+
if (!completed.length) return;
|
|
3070
|
+
appendRelationshipKeywordChips(editor, completed);
|
|
3071
|
+
input.value = remainder;
|
|
3072
|
+
});
|
|
3073
|
+
editor.addEventListener("click", (event) => {
|
|
3074
|
+
const remove = event.target.closest("[data-keyword-chip-remove]");
|
|
3075
|
+
if (!remove) return;
|
|
3076
|
+
remove.closest("[data-keyword-chip]")?.remove();
|
|
3077
|
+
});
|
|
3078
|
+
});
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
function commitRelationshipKeywordInputs(container) {
|
|
3082
|
+
container.querySelectorAll("[data-keyword-chips]").forEach(commitRelationshipKeywordInput);
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3007
3085
|
function openDialog(title, fields, onSubmit, eyebrow = "新增", options = {}) {
|
|
3008
3086
|
$("#dialog-title").textContent = title;
|
|
3009
3087
|
$("#dialog-eyebrow").textContent = eyebrow;
|
|
@@ -3011,6 +3089,7 @@ function openDialog(title, fields, onSubmit, eyebrow = "新增", options = {}) {
|
|
|
3011
3089
|
$("#dialog-submit").textContent = options.submitLabel ?? "保存";
|
|
3012
3090
|
$("#form-dialog").classList.toggle("wide-dialog", Boolean(options.wide));
|
|
3013
3091
|
bindDynamicListControls($("#dialog-fields"));
|
|
3092
|
+
bindRelationshipKeywordControls($("#dialog-fields"));
|
|
3014
3093
|
const form = $("#dynamic-form");
|
|
3015
3094
|
form.onsubmit = async (event) => {
|
|
3016
3095
|
if (event.submitter?.value === "cancel") return;
|
|
@@ -3018,6 +3097,7 @@ function openDialog(title, fields, onSubmit, eyebrow = "新增", options = {}) {
|
|
|
3018
3097
|
const submit = $("#dialog-submit");
|
|
3019
3098
|
submit.disabled = true;
|
|
3020
3099
|
try {
|
|
3100
|
+
commitRelationshipKeywordInputs(form);
|
|
3021
3101
|
await onSubmit(new FormData(form));
|
|
3022
3102
|
$("#form-dialog").close();
|
|
3023
3103
|
} catch (error) {
|
|
@@ -3192,6 +3272,83 @@ function setCharacterHistoryVisible(visible) {
|
|
|
3192
3272
|
$("#character-history-button").setAttribute("aria-expanded", String(visible));
|
|
3193
3273
|
}
|
|
3194
3274
|
|
|
3275
|
+
const relationshipCategoryLabels = {
|
|
3276
|
+
family: "亲属",
|
|
3277
|
+
social: "社交",
|
|
3278
|
+
emotional: "情感",
|
|
3279
|
+
conflict: "冲突",
|
|
3280
|
+
uncertain: "未确定"
|
|
3281
|
+
};
|
|
3282
|
+
|
|
3283
|
+
function renderCharacterEditorRelationships() {
|
|
3284
|
+
const host = $("#character-editor-relationships");
|
|
3285
|
+
if (!host) return;
|
|
3286
|
+
if (!characterEditorItem?.id) {
|
|
3287
|
+
host.innerHTML = '<div class="character-editor-empty-field"><b>人物关系</b><span>保存人物档案后即可添加与其他人物的关系。</span></div>';
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
if (characterEditorRelationshipsLoading) {
|
|
3291
|
+
host.innerHTML = '<p class="character-relationship-status">正在读取人物关系……</p>';
|
|
3292
|
+
return;
|
|
3293
|
+
}
|
|
3294
|
+
const characterId = String(characterEditorItem.id);
|
|
3295
|
+
const nameOf = (id) => state.characters.find((character) => character.id === id)?.name ?? "未知角色";
|
|
3296
|
+
const rows = characterEditorRelationships.map((relationship) => {
|
|
3297
|
+
const isSource = relationship.fromCharacterId === characterId;
|
|
3298
|
+
const otherCharacterId = isSource ? relationship.toCharacterId : relationship.fromCharacterId;
|
|
3299
|
+
const direction = relationship.directed ? (isSource ? "→" : "←") : "↔";
|
|
3300
|
+
const category = relationshipCategoryLabels[relationship.category] ?? relationship.category;
|
|
3301
|
+
const relationLabel = [category, relationship.subtype].filter(Boolean).join(" · ") || "未细分";
|
|
3302
|
+
const keywords = Array.isArray(relationship.keywords) ? relationship.keywords : [];
|
|
3303
|
+
return `<article class="character-relationship-row">
|
|
3304
|
+
<div class="character-relationship-heading"><div><strong>${esc(nameOf(otherCharacterId))}</strong><span>${direction} ${esc(relationLabel)}</span></div>${canEditWork() ? `<button type="button" data-character-relationship-edit="${esc(relationship.id)}">编辑关系</button>` : ""}</div>
|
|
3305
|
+
<div class="character-relationship-keywords"><small>关系关键词</small><div>${keywords.map((keyword) => `<span class="pill relationship-keyword">${esc(keyword)}</span>`).join("") || '<span class="character-relationship-empty-keywords">未填写关键词</span>'}</div></div>
|
|
3306
|
+
</article>`;
|
|
3307
|
+
}).join("");
|
|
3308
|
+
host.innerHTML = `<div class="character-relationship-toolbar"><p>与 ${esc(characterEditorItem.name)} 有关的其他人物及关系关键词。</p>${canEditWork() ? '<button type="button" class="ghost-button" data-character-relationship-create>新建关系</button>' : ""}</div>${rows || '<p class="character-relationship-status">暂未记录与其他人物的关系。</p>'}`;
|
|
3309
|
+
host.querySelectorAll("[data-character-relationship-edit]").forEach((button) => button.addEventListener("click", () => {
|
|
3310
|
+
const relationship = characterEditorRelationships.find((item) => item.id === button.dataset.characterRelationshipEdit);
|
|
3311
|
+
if (relationship) void openRelationshipDialog(relationship, { characterId });
|
|
3312
|
+
}));
|
|
3313
|
+
host.querySelector("[data-character-relationship-create]")?.addEventListener("click", () => void openRelationshipDialog(null, { characterId }));
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
async function loadCharacterEditorRelationships(characterId) {
|
|
3317
|
+
const workId = state.work?.id;
|
|
3318
|
+
if (!workId || characterEditorItem?.id !== characterId) return;
|
|
3319
|
+
characterEditorRelationshipsLoading = true;
|
|
3320
|
+
renderCharacterEditorRelationships();
|
|
3321
|
+
let loaded = false;
|
|
3322
|
+
try {
|
|
3323
|
+
const [characters, relationships] = await Promise.all([
|
|
3324
|
+
api(`/api/works/${workId}/characters`),
|
|
3325
|
+
api(`/api/works/${workId}/relationships`)
|
|
3326
|
+
]);
|
|
3327
|
+
if (state.work?.id !== workId || characterEditorItem?.id !== characterId) return;
|
|
3328
|
+
state.characters = characters;
|
|
3329
|
+
characterEditorRelationships = relationships.filter((relationship) => relationship.fromCharacterId === characterId || relationship.toCharacterId === characterId);
|
|
3330
|
+
loaded = true;
|
|
3331
|
+
} catch (error) {
|
|
3332
|
+
if (state.work?.id === workId && characterEditorItem?.id === characterId) {
|
|
3333
|
+
$("#character-editor-relationships").innerHTML = `<p class="character-relationship-status">关系载入失败:${esc(error.message)}</p>`;
|
|
3334
|
+
}
|
|
3335
|
+
} finally {
|
|
3336
|
+
if (state.work?.id === workId && characterEditorItem?.id === characterId) {
|
|
3337
|
+
characterEditorRelationshipsLoading = false;
|
|
3338
|
+
if (loaded) renderCharacterEditorRelationships();
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
async function refreshRelationshipSurfaces(characterId = null) {
|
|
3344
|
+
const tasks = [];
|
|
3345
|
+
if (state.module === "relationships") tasks.push(renderRelationships());
|
|
3346
|
+
if (characterId && $("#character-editor-dialog").open && characterEditorItem?.id === characterId) {
|
|
3347
|
+
tasks.push(loadCharacterEditorRelationships(characterId));
|
|
3348
|
+
}
|
|
3349
|
+
await Promise.all(tasks);
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3195
3352
|
function renderCharacterEditorFields(item) {
|
|
3196
3353
|
const raceOptions = [["", "未指定"], ...state.races.map((race) => [race.id, race.name])];
|
|
3197
3354
|
const organizationOptions = state.organizations.map((organization) => [organization.id, organization.name]);
|
|
@@ -3228,11 +3385,14 @@ function renderCharacterEditorFields(item) {
|
|
|
3228
3385
|
addLabel: "添加状态"
|
|
3229
3386
|
}) +
|
|
3230
3387
|
'<p class="character-editor-field-help">未修改的数字、布尔值、数组和对象会保留原有数据类型;被修改的值会按文本保存。</p>' +
|
|
3231
|
-
field("lockedFields", "锁定字段", "item-list", item?.lockedFields ?? []))
|
|
3388
|
+
field("lockedFields", "锁定字段", "item-list", item?.lockedFields ?? [])),
|
|
3389
|
+
characterEditorSection("relationships", "人物关系", "查看与其他人物的关系及关键词;编辑入口与“关系”面板共用同一份关系数据。",
|
|
3390
|
+
'<div id="character-editor-relationships" class="character-editor-relationships-field"></div>')
|
|
3232
3391
|
].join("");
|
|
3233
3392
|
const name = $("#character-editor-fields [name='name']");
|
|
3234
3393
|
if (name) name.required = true;
|
|
3235
3394
|
bindDynamicListControls($("#character-editor-fields"));
|
|
3395
|
+
renderCharacterEditorRelationships();
|
|
3236
3396
|
activateCharacterEditorTab("basic");
|
|
3237
3397
|
}
|
|
3238
3398
|
|
|
@@ -3336,6 +3496,8 @@ async function openCharacterDialog(item) {
|
|
|
3336
3496
|
]);
|
|
3337
3497
|
characterEditorItem = item ?? null;
|
|
3338
3498
|
characterEditorVersions = [];
|
|
3499
|
+
characterEditorRelationships = [];
|
|
3500
|
+
characterEditorRelationshipsLoading = Boolean(item);
|
|
3339
3501
|
$("#character-editor-eyebrow").textContent = item ? "人物主档案" : "建立人物档案";
|
|
3340
3502
|
$("#character-editor-title").textContent = item?.name || "新建角色";
|
|
3341
3503
|
$("#character-editor-version").textContent = item ? `v${item.versionNo}` : "新档案";
|
|
@@ -3354,6 +3516,9 @@ async function openCharacterDialog(item) {
|
|
|
3354
3516
|
document.querySelectorAll("[data-character-editor-tab]").forEach((button) => {
|
|
3355
3517
|
button.onclick = () => activateCharacterEditorTab(button.dataset.characterEditorTab);
|
|
3356
3518
|
});
|
|
3519
|
+
const relationshipTab = document.querySelector("[data-character-editor-tab='relationships']");
|
|
3520
|
+
relationshipTab.disabled = !item;
|
|
3521
|
+
relationshipTab.title = item ? "查看和编辑人物关系" : "创建人物档案后即可维护人物关系";
|
|
3357
3522
|
const dialog = $("#character-editor-dialog");
|
|
3358
3523
|
const form = $("#character-editor-form");
|
|
3359
3524
|
form.onsubmit = async (event) => {
|
|
@@ -3378,6 +3543,7 @@ async function openCharacterDialog(item) {
|
|
|
3378
3543
|
}
|
|
3379
3544
|
};
|
|
3380
3545
|
dialog.showModal();
|
|
3546
|
+
if (item) void loadCharacterEditorRelationships(item.id);
|
|
3381
3547
|
}
|
|
3382
3548
|
|
|
3383
3549
|
async function openRaceDialog(item) {
|
|
@@ -3502,14 +3668,16 @@ function openTimelineSplitDialog(item) {
|
|
|
3502
3668
|
}, "原证据同步保留");
|
|
3503
3669
|
}
|
|
3504
3670
|
|
|
3505
|
-
async function openRelationshipDialog(item) {
|
|
3671
|
+
async function openRelationshipDialog(item, options = {}) {
|
|
3506
3672
|
state.characters = await api(`/api/works/${state.work.id}/characters`);
|
|
3507
3673
|
if (state.characters.length < 2) return toast("至少需要两个角色才能创建关系", "error");
|
|
3508
|
-
const
|
|
3509
|
-
|
|
3510
|
-
|
|
3674
|
+
const characterOptions = state.characters.map((item) => [item.id, item.name]);
|
|
3675
|
+
const defaultFrom = options.characterId && state.characters.some((character) => character.id === options.characterId) ? options.characterId : characterOptions[0][0];
|
|
3676
|
+
const defaultTo = characterOptions.find(([id]) => id !== defaultFrom)?.[0] ?? characterOptions[1][0];
|
|
3677
|
+
openDialog(item ? "编辑人物关系" : "新建人物关系", field("from", "起点人物", "select", item?.fromCharacterId ?? defaultFrom, characterOptions) + field("to", "终点人物", "select", item?.toCharacterId ?? defaultTo, characterOptions) + field("category", "关系大类", "select", item?.category ?? "social", [["family", "亲属"], ["social", "社交"], ["emotional", "情感"], ["conflict", "冲突"], ["uncertain", "未确定"]]) + field("subtype", "关系子类", "text", item?.subtype) + field("keywords", "关系关键词", "keyword-chips", item?.keywords ?? []) + field("confidence", "置信度(0-1)", "number", item?.confidence ?? "1") + field("directed", "有方向性", "checkbox", item?.directed ?? false), async (form) => {
|
|
3678
|
+
const keywords = uniqueRelationshipKeywords(form.getAll("keywords").map(String));
|
|
3511
3679
|
await api(item ? `/api/relationships/${item.id}` : `/api/works/${state.work.id}/relationships`, { method: item ? "PATCH" : "POST", body: { fromCharacterId: form.get("from"), toCharacterId: form.get("to"), category: form.get("category"), subtype: form.get("subtype"), keywords, confidence: Number(form.get("confidence")), directed: form.get("directed") === "on", confirmationStatus: item?.confirmationStatus ?? "confirmed" } });
|
|
3512
|
-
await
|
|
3680
|
+
await refreshRelationshipSurfaces(options.characterId ?? null);
|
|
3513
3681
|
}, item ? "关系档案" : "人工确认关系");
|
|
3514
3682
|
}
|
|
3515
3683
|
|
|
@@ -4038,7 +4206,8 @@ $("#login-form").addEventListener("submit", async (event) => {
|
|
|
4038
4206
|
window.location.reload();
|
|
4039
4207
|
} catch (error) {
|
|
4040
4208
|
$("#auth-error").textContent = error.message;
|
|
4041
|
-
|
|
4209
|
+
// 仅在验证码已显示时自动换一张,未加载过则保持默认隐藏状态
|
|
4210
|
+
if (!$("#login-captcha-image").hidden) refreshAuthCaptcha("login").catch(() => {});
|
|
4042
4211
|
}
|
|
4043
4212
|
});
|
|
4044
4213
|
$("#register-form").addEventListener("submit", async (event) => {
|
|
@@ -4064,7 +4233,8 @@ $("#register-form").addEventListener("submit", async (event) => {
|
|
|
4064
4233
|
window.location.reload();
|
|
4065
4234
|
} catch (error) {
|
|
4066
4235
|
$("#auth-error").textContent = error.message;
|
|
4067
|
-
|
|
4236
|
+
// 仅在验证码已显示时自动换一张,未加载过则保持默认隐藏状态
|
|
4237
|
+
if (!$("#register-captcha-image").hidden) refreshAuthCaptcha("register").catch(() => {});
|
|
4068
4238
|
}
|
|
4069
4239
|
});
|
|
4070
4240
|
$("#settings-return").addEventListener("click", () => returnFromSettings().catch((error) => toast(error.message, "error")));
|
package/dist/public/index.html
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
<meta name="description" content="面向长篇小说创作的 AI 协作工作台">
|
|
7
7
|
<meta name="theme-color" content="#8b3d2c">
|
|
8
8
|
<title>叙界 · 小说 AI 创作工作台</title>
|
|
9
|
-
<script src="/theme-init.js?v=
|
|
9
|
+
<script src="/theme-init.js?v=20260720-route-skeleton"></script>
|
|
10
10
|
<link rel="icon" href="/icon.svg?v=20260712" type="image/svg+xml">
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest">
|
|
12
|
-
<link rel="stylesheet" href="/styles.css?v=
|
|
12
|
+
<link rel="stylesheet" href="/styles.css?v=20260720-captcha-lazy-load">
|
|
13
13
|
</head>
|
|
14
14
|
<body class="auth-pending">
|
|
15
|
-
<section id="auth-view" class="auth-view" aria-labelledby="auth-title">
|
|
15
|
+
<section id="auth-view" class="auth-view hidden" aria-labelledby="auth-title">
|
|
16
16
|
<div class="auth-card">
|
|
17
17
|
<div class="auth-brand"><span class="brand-mark">叙</span><div><strong>叙界</strong><small>小说 AI 创作工作台</small></div></div>
|
|
18
18
|
<span class="eyebrow">账户与作品空间</span>
|
|
@@ -28,11 +28,12 @@
|
|
|
28
28
|
<div class="auth-captcha">
|
|
29
29
|
<label>验证码<input name="captchaAnswer" autocomplete="off" inputmode="text" maxlength="8" spellcheck="false" required aria-describedby="login-captcha-hint"></label>
|
|
30
30
|
<button id="login-captcha-refresh" class="auth-captcha-image" type="button" aria-label="刷新验证码" title="刷新验证码">
|
|
31
|
-
<img id="login-captcha-image" alt="验证码图片">
|
|
31
|
+
<img id="login-captcha-image" alt="验证码图片" hidden>
|
|
32
|
+
<span id="login-captcha-placeholder" class="auth-captcha-placeholder">点击显示验证码</span>
|
|
32
33
|
</button>
|
|
33
34
|
<input id="login-captcha-id" name="captchaId" type="hidden" required>
|
|
34
35
|
</div>
|
|
35
|
-
<p id="login-captcha-hint" class="auth-captcha-hint"
|
|
36
|
+
<p id="login-captcha-hint" class="auth-captcha-hint">点击按钮显示验证码,显示后点击图片可刷新</p>
|
|
36
37
|
<button class="primary-button" type="submit">登录</button>
|
|
37
38
|
</form>
|
|
38
39
|
<form id="register-form" class="auth-form hidden">
|
|
@@ -42,11 +43,12 @@
|
|
|
42
43
|
<div class="auth-captcha">
|
|
43
44
|
<label>验证码<input name="captchaAnswer" autocomplete="off" inputmode="text" maxlength="8" spellcheck="false" required aria-describedby="register-captcha-hint"></label>
|
|
44
45
|
<button id="register-captcha-refresh" class="auth-captcha-image" type="button" aria-label="刷新验证码" title="刷新验证码">
|
|
45
|
-
<img id="register-captcha-image" alt="验证码图片">
|
|
46
|
+
<img id="register-captcha-image" alt="验证码图片" hidden>
|
|
47
|
+
<span id="register-captcha-placeholder" class="auth-captcha-placeholder">点击显示验证码</span>
|
|
46
48
|
</button>
|
|
47
49
|
<input id="register-captcha-id" name="captchaId" type="hidden" required>
|
|
48
50
|
</div>
|
|
49
|
-
<p id="register-captcha-hint" class="auth-captcha-hint"
|
|
51
|
+
<p id="register-captcha-hint" class="auth-captcha-hint">点击按钮显示验证码,显示后点击图片可刷新</p>
|
|
50
52
|
<small>至少 10 个字符。系统中的第一个注册用户会自动成为管理员。</small>
|
|
51
53
|
<button class="primary-button" type="submit">创建账户</button>
|
|
52
54
|
</form>
|
|
@@ -332,6 +334,7 @@
|
|
|
332
334
|
<button type="button" role="tab" data-character-editor-tab="profile" aria-selected="false">人物档案<small>简介、身份与动机</small></button>
|
|
333
335
|
<button type="button" role="tab" data-character-editor-tab="settings" aria-selected="false">扩展设定<small>属性与长篇章节</small></button>
|
|
334
336
|
<button type="button" role="tab" data-character-editor-tab="state" aria-selected="false">状态与约束<small>当前状态与锁定项</small></button>
|
|
337
|
+
<button type="button" role="tab" data-character-editor-tab="relationships" aria-selected="false">人物关系<small>关联人物与关系关键词</small></button>
|
|
335
338
|
</nav>
|
|
336
339
|
<main id="character-editor-fields" class="character-editor-fields"></main>
|
|
337
340
|
<aside id="character-history-panel" class="character-history-panel hidden" aria-label="人物版本历史">
|
|
@@ -542,6 +545,7 @@
|
|
|
542
545
|
</section>
|
|
543
546
|
</dialog>
|
|
544
547
|
|
|
545
|
-
<
|
|
548
|
+
<div id="auth-loading" class="auth-loading" role="status" aria-label="正在载入工作台"></div>
|
|
549
|
+
<script type="module" src="/app.js?v=20260720-captcha-lazy-load"></script>
|
|
546
550
|
</body>
|
|
547
551
|
</html>
|
|
@@ -31,7 +31,9 @@ export function serializePageRoute(route = {}) {
|
|
|
31
31
|
const view = String(route.view ?? "shelf");
|
|
32
32
|
const workId = String(route.workId ?? "").trim();
|
|
33
33
|
|
|
34
|
-
if (view === "
|
|
34
|
+
if (view === "login") {
|
|
35
|
+
params.set("view", "login");
|
|
36
|
+
} else if (view === "editor" && workId) {
|
|
35
37
|
params.set("view", "editor");
|
|
36
38
|
params.set("work", workId);
|
|
37
39
|
if (route.chapterId) params.set("chapter", String(route.chapterId));
|
|
@@ -58,6 +60,8 @@ export function parsePageRoute(hash = "") {
|
|
|
58
60
|
const view = value(params, "view");
|
|
59
61
|
const workId = value(params, "work");
|
|
60
62
|
|
|
63
|
+
if (view === "login") return { view: "login" };
|
|
64
|
+
|
|
61
65
|
if (view === "editor" && workId) {
|
|
62
66
|
const chapterId = value(params, "chapter");
|
|
63
67
|
return { view, workId, chapterId: chapterId || null };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const relationshipKeywordSeparator = /[,,、;;\n]/u;
|
|
2
|
+
|
|
3
|
+
function normalizeRelationshipKeyword(value) {
|
|
4
|
+
return String(value ?? "").normalize("NFKC").trim().replace(/\s+/gu, " ");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function splitRelationshipKeywords(value) {
|
|
8
|
+
return String(value ?? "")
|
|
9
|
+
.split(relationshipKeywordSeparator)
|
|
10
|
+
.map(normalizeRelationshipKeyword)
|
|
11
|
+
.filter(Boolean);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function uniqueRelationshipKeywords(values) {
|
|
15
|
+
const keywords = [];
|
|
16
|
+
const seen = new Set();
|
|
17
|
+
for (const value of values) {
|
|
18
|
+
for (const keyword of splitRelationshipKeywords(value)) {
|
|
19
|
+
const key = keyword.toLocaleLowerCase("zh-CN");
|
|
20
|
+
if (seen.has(key)) continue;
|
|
21
|
+
seen.add(key);
|
|
22
|
+
keywords.push(keyword);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return keywords;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function splitRelationshipKeywordInput(value) {
|
|
29
|
+
const parts = String(value ?? "").split(relationshipKeywordSeparator);
|
|
30
|
+
return {
|
|
31
|
+
completed: uniqueRelationshipKeywords(parts.slice(0, -1)),
|
|
32
|
+
remainder: parts.at(-1) ?? ""
|
|
33
|
+
};
|
|
34
|
+
}
|
package/dist/public/styles.css
CHANGED
|
@@ -31,7 +31,26 @@
|
|
|
31
31
|
background: var(--paper);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
.auth-pending .app-shell {
|
|
34
|
+
.auth-pending .app-shell { pointer-events: none; user-select: none; }
|
|
35
|
+
.auth-loading { display: none; }
|
|
36
|
+
.auth-pending .auth-loading { position: fixed; inset: 0; z-index: 900; display: grid; place-items: center; pointer-events: none; }
|
|
37
|
+
.auth-loading::before { content: ""; width: 32px; height: 32px; border: 3px solid color-mix(in srgb, var(--accent) 25%, transparent); border-top-color: var(--accent); border-radius: 50%; animation: auth-loading-spin .8s linear infinite; }
|
|
38
|
+
@keyframes auth-loading-spin { to { transform: rotate(360deg); } }
|
|
39
|
+
@media (prefers-reduced-motion: reduce) { .auth-loading::before { animation-duration: 2s; } }
|
|
40
|
+
.login-route .auth-view.hidden { display: grid !important; }
|
|
41
|
+
.login-route .auth-loading { display: none; }
|
|
42
|
+
/* 会话恢复期间按 data-pending-view 预显示目标视图的骨架(自然 display 与各视图定义一致) */
|
|
43
|
+
[data-pending-view="shelf"] .auth-pending #shelf-view.hidden,
|
|
44
|
+
[data-pending-view="settings"] .auth-pending #settings-hub-view.hidden,
|
|
45
|
+
[data-pending-view="platform-ai"] .auth-pending #platform-ai-view.hidden,
|
|
46
|
+
[data-pending-view="module"] .auth-pending #module-view.hidden { display: block !important; }
|
|
47
|
+
[data-pending-view="welcome"] .auth-pending #welcome-view.hidden,
|
|
48
|
+
[data-pending-view="editor"] .auth-pending #editor-view.hidden { display: grid !important; }
|
|
49
|
+
/* 书架/设置/平台 AI 路由的骨架使用整宽布局,与 shelf-mode 一致 */
|
|
50
|
+
.pending-shelf-mode .auth-pending .app-shell { grid-template-columns: 1fr; }
|
|
51
|
+
.pending-shelf-mode .auth-pending .topbar { grid-template-columns: 280px 1fr auto; }
|
|
52
|
+
.pending-shelf-mode .auth-pending .left-panel, .pending-shelf-mode .auth-pending .ai-panel { display: none; }
|
|
53
|
+
.pending-shelf-mode .auth-pending .main-panel { grid-column: 1 / -1; }
|
|
35
54
|
.auth-view { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; align-content: safe center; padding: 24px; overflow-y: auto; overscroll-behavior: contain; background: radial-gradient(circle at 18% 16%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 34%), var(--paper); }
|
|
36
55
|
.auth-view.hidden { display: none; }
|
|
37
56
|
.auth-card { box-sizing: border-box; width: min(440px, 100%); padding: 34px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); box-shadow: var(--shadow); }
|
|
@@ -163,6 +182,8 @@
|
|
|
163
182
|
cursor: pointer;
|
|
164
183
|
}
|
|
165
184
|
.auth-captcha-image img { display: block; width: 148px; height: 48px; border-radius: 6px; }
|
|
185
|
+
.auth-captcha-image img[hidden], .auth-captcha-placeholder[hidden] { display: none; }
|
|
186
|
+
.auth-captcha-placeholder { color: var(--muted); font-size: 13px; padding: 0 14px; white-space: nowrap; }
|
|
166
187
|
.auth-captcha-hint { margin: -8px 0 0; color: var(--muted); font-size: 12px; line-height: 1.4; }
|
|
167
188
|
.auth-error { min-height: 20px; margin: 14px 0 0 !important; color: var(--accent-dark) !important; }
|
|
168
189
|
|
|
@@ -471,6 +492,8 @@ body.work-viewer-mode #merge-events,
|
|
|
471
492
|
body.work-viewer-mode [data-edit-foreshadow],
|
|
472
493
|
body.work-viewer-mode [data-edit-outline],
|
|
473
494
|
body.work-viewer-mode [data-edit-relationship],
|
|
495
|
+
body.work-viewer-mode [data-character-relationship-edit],
|
|
496
|
+
body.work-viewer-mode [data-character-relationship-create],
|
|
474
497
|
body.work-viewer-mode [data-review-id],
|
|
475
498
|
body.work-viewer-mode .task-auto-run-panel,
|
|
476
499
|
body.work-viewer-mode [data-run-task],
|
|
@@ -1282,6 +1305,14 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1282
1305
|
.item-list-row button, .item-list-add { border: 1px solid var(--line); border-radius: 4px; background: var(--panel); color: var(--muted); font-size: 10px; }
|
|
1283
1306
|
.item-list-row button { padding: 0 10px; }
|
|
1284
1307
|
.item-list-add { justify-self: start; padding: 7px 11px; }
|
|
1308
|
+
.keyword-chip-field > small { color: var(--muted); font-size: 10px; line-height: 1.5; }
|
|
1309
|
+
.keyword-chip-editor { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-height: 42px; padding: 6px 8px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); }
|
|
1310
|
+
.keyword-chip-editor:focus-within { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 14%, transparent); }
|
|
1311
|
+
.keyword-chip-editor > input[data-keyword-input] { flex: 1 1 160px; width: auto; min-width: 140px; padding: 6px 3px; border: 0; background: transparent; color: var(--ink); outline: none; }
|
|
1312
|
+
.keyword-chip-editor > input[data-keyword-input]:focus { border: 0; box-shadow: none; }
|
|
1313
|
+
.keyword-chip { display: inline-flex; align-items: center; gap: 5px; min-height: 28px; padding: 3px 5px 3px 10px; border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--line)); border-radius: 16px; background: var(--paper-deep); color: var(--ink); font-size: 11px; line-height: 1.3; }
|
|
1314
|
+
.keyword-chip button { display: inline-grid; place-items: center; width: 20px; height: 20px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--muted); font-size: 15px; line-height: 1; }
|
|
1315
|
+
.keyword-chip button:hover, .keyword-chip button:focus-visible { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent-dark); outline: none; }
|
|
1285
1316
|
.structured-list-field > small { color: var(--muted); font-size: 9px; line-height: 1.5; }
|
|
1286
1317
|
.structured-list-rows { display: grid; gap: 9px; }
|
|
1287
1318
|
.structured-list-row { display: grid; gap: 7px; padding: 10px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface-soft); }
|
|
@@ -1338,6 +1369,22 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1338
1369
|
.character-editor-section-fields .section-list-row textarea { min-height: 180px; }
|
|
1339
1370
|
.character-editor-section-fields .member-chip { position: relative; display: inline-flex; width: auto; cursor: pointer; }
|
|
1340
1371
|
.character-editor-section-fields .item-list-row button, .character-editor-section-fields .structured-list-row button { min-width: 54px; }
|
|
1372
|
+
.character-editor-relationships-field { display: grid; grid-column: 1 / -1; gap: 12px; }
|
|
1373
|
+
.character-relationship-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
1374
|
+
.character-relationship-toolbar p { margin: 0; color: var(--muted); font-size: 11px; line-height: 1.5; }
|
|
1375
|
+
.character-relationship-toolbar .ghost-button { flex: 0 0 auto; min-height: 32px; }
|
|
1376
|
+
.character-relationship-row { display: grid; gap: 10px; padding: 12px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface-soft); }
|
|
1377
|
+
.character-relationship-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
|
1378
|
+
.character-relationship-heading > div { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; min-width: 0; }
|
|
1379
|
+
.character-relationship-heading strong { font-size: 14px; }
|
|
1380
|
+
.character-relationship-heading span { color: var(--muted); font-size: 10px; }
|
|
1381
|
+
.character-relationship-heading button { flex: 0 0 auto; padding: 6px 9px; border: 1px solid var(--line); border-radius: 4px; background: transparent; color: var(--muted); font-size: 10px; }
|
|
1382
|
+
.character-relationship-heading button:hover { border-color: var(--accent); color: var(--accent-dark); }
|
|
1383
|
+
.character-relationship-keywords { display: grid; gap: 6px; }
|
|
1384
|
+
.character-relationship-keywords > small { color: var(--muted); font-size: 9px; }
|
|
1385
|
+
.character-relationship-keywords > div { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
1386
|
+
.character-relationship-empty-keywords, .character-relationship-status { color: var(--muted); font-size: 10px; }
|
|
1387
|
+
.character-relationship-status { margin: 0; padding: 18px 12px; border: 1px dashed var(--line); border-radius: 4px; text-align: center; }
|
|
1341
1388
|
.character-editor-empty-field { display: grid; align-content: start; gap: 7px; color: var(--muted); font-size: 11px; }
|
|
1342
1389
|
.character-editor-empty-field span { padding: 11px; border: 1px dashed var(--line); border-radius: 4px; font-size: 10px; }
|
|
1343
1390
|
.character-editor-field-help { margin: -8px 0 0; color: var(--muted); font-size: 9px; }
|
|
@@ -5,4 +5,19 @@
|
|
|
5
5
|
const theme = storedTheme === "dark" || storedTheme === "light" ? storedTheme : prefersDark ? "dark" : "light";
|
|
6
6
|
document.documentElement.dataset.theme = theme;
|
|
7
7
|
document.documentElement.style.colorScheme = theme;
|
|
8
|
+
// 登录页路由首帧直接显示登录卡片,避免出现"骨架屏 → 登录页"的跳变
|
|
9
|
+
const routeParams = new URLSearchParams(window.location.hash.replace(/^#/, ""));
|
|
10
|
+
const routeView = routeParams.get("view") ?? "";
|
|
11
|
+
if (routeView === "login") {
|
|
12
|
+
document.documentElement.classList.add("login-route");
|
|
13
|
+
} else {
|
|
14
|
+
// 会话恢复期间按目标路由预显示对应视图的骨架屏
|
|
15
|
+
const pendingView = ["editor", "module", "welcome"].includes(routeView) && routeParams.get("work")
|
|
16
|
+
? routeView
|
|
17
|
+
: ["settings", "platform-ai"].includes(routeView) ? routeView : "shelf";
|
|
18
|
+
document.documentElement.dataset.pendingView = pendingView;
|
|
19
|
+
if (["shelf", "settings", "platform-ai"].includes(pendingView)) {
|
|
20
|
+
document.documentElement.classList.add("pending-shelf-mode");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
8
23
|
})();
|
package/dist/store.js
CHANGED
|
@@ -531,7 +531,7 @@ export class Store {
|
|
|
531
531
|
return {
|
|
532
532
|
fileVersionId: restorePointId,
|
|
533
533
|
restoredFrom: fileVersionId,
|
|
534
|
-
tree: this.
|
|
534
|
+
tree: this.getWorkDirectory(workId)
|
|
535
535
|
};
|
|
536
536
|
});
|
|
537
537
|
}
|
|
@@ -539,7 +539,7 @@ export class Store {
|
|
|
539
539
|
this.getWork(workId);
|
|
540
540
|
let result = {};
|
|
541
541
|
this.db.transaction(() => { result = this.importNovelInTransaction(workId, fileName, fileType, parsed); });
|
|
542
|
-
return result;
|
|
542
|
+
return { ...result, tree: this.getWorkDirectory(workId) };
|
|
543
543
|
}
|
|
544
544
|
createImportedWork(input, fileName, fileType, parsed) {
|
|
545
545
|
return this.db.transaction(() => {
|
|
@@ -573,8 +573,7 @@ export class Store {
|
|
|
573
573
|
fileVersionId,
|
|
574
574
|
warnings: parsed.warnings,
|
|
575
575
|
wordCount: parsed.wordCount,
|
|
576
|
-
paragraphCount: parsed.paragraphCount
|
|
577
|
-
tree: this.getWorkTree(workId)
|
|
576
|
+
paragraphCount: parsed.paragraphCount
|
|
578
577
|
};
|
|
579
578
|
}
|
|
580
579
|
createVolume(workId, input) {
|