@musnows/scriverse 0.6.5 → 0.6.6
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 +11 -5
- package/dist/ai.js.map +1 -1
- package/dist/app.js +3 -0
- package/dist/app.js.map +1 -1
- package/dist/cli-contract.js +6 -5
- package/dist/cli-contract.js.map +1 -1
- package/dist/database.js +28 -1
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +80 -21
- package/dist/public/character-version.js +1 -0
- package/dist/public/index.html +5 -5
- package/dist/public/styles.css +80 -26
- package/dist/public/timeline-view.d.ts +50 -0
- package/dist/public/timeline-view.js +119 -0
- package/dist/store.js +33 -22
- package/dist/store.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** 时间轴轨道色板(与 CSS `--timeline-track-palette-*` 索引对应) */
|
|
2
|
+
export const TIMELINE_TRACK_PALETTE_SIZE = 8;
|
|
3
|
+
|
|
4
|
+
/** 未分组轨道使用的色板索引 */
|
|
5
|
+
export const TIMELINE_UNGROUPED_COLOR_INDEX = TIMELINE_TRACK_PALETTE_SIZE - 1;
|
|
6
|
+
|
|
7
|
+
function timelineEventTrackKey(event) {
|
|
8
|
+
return String(event?.trackId ?? "");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function compareNullableTimeSort(left, right, direction = "asc") {
|
|
12
|
+
const leftSort = left?.timeSort;
|
|
13
|
+
const rightSort = right?.timeSort;
|
|
14
|
+
const leftMissing = leftSort === null || leftSort === undefined || Number.isNaN(Number(leftSort));
|
|
15
|
+
const rightMissing = rightSort === null || rightSort === undefined || Number.isNaN(Number(rightSort));
|
|
16
|
+
if (leftMissing && rightMissing) return 0;
|
|
17
|
+
if (leftMissing) return 1;
|
|
18
|
+
if (rightMissing) return -1;
|
|
19
|
+
const delta = Number(leftSort) - Number(rightSort);
|
|
20
|
+
if (delta === 0) return 0;
|
|
21
|
+
return direction === "desc" ? -delta : delta;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function compareStableTieBreak(left, right, direction = "asc") {
|
|
25
|
+
const leftUpdated = String(left?.updatedAt ?? "");
|
|
26
|
+
const rightUpdated = String(right?.updatedAt ?? "");
|
|
27
|
+
if (leftUpdated !== rightUpdated) {
|
|
28
|
+
const delta = leftUpdated < rightUpdated ? -1 : 1;
|
|
29
|
+
return direction === "desc" ? -delta : delta;
|
|
30
|
+
}
|
|
31
|
+
const leftId = String(left?.id ?? "");
|
|
32
|
+
const rightId = String(right?.id ?? "");
|
|
33
|
+
if (leftId === rightId) return 0;
|
|
34
|
+
const delta = leftId < rightId ? -1 : 1;
|
|
35
|
+
return direction === "desc" ? -delta : delta;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function normalizeTimelineSortDirection(direction) {
|
|
39
|
+
return direction === "desc" ? "desc" : "asc";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 按 timeSort 排序;缺失或无效的排序值始终置底;同值按 updatedAt、id 稳定排序。
|
|
44
|
+
*/
|
|
45
|
+
export function sortTimelineEvents(events = [], { direction = "asc" } = {}) {
|
|
46
|
+
const sortDirection = normalizeTimelineSortDirection(direction);
|
|
47
|
+
return [...events].sort((left, right) => {
|
|
48
|
+
const bySort = compareNullableTimeSort(left, right, sortDirection);
|
|
49
|
+
if (bySort !== 0) return bySort;
|
|
50
|
+
return compareStableTieBreak(left, right, sortDirection);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 按轨道筛选。trackIds 为空表示不过滤;未分组轨道用空字符串 ""。
|
|
56
|
+
*/
|
|
57
|
+
export function filterTimelineEvents(events = [], { trackIds = [] } = {}) {
|
|
58
|
+
const selected = new Set(trackIds.map((id) => String(id)));
|
|
59
|
+
if (selected.size === 0) return events;
|
|
60
|
+
return events.filter((event) => selected.has(timelineEventTrackKey(event)));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 将轨道映射到固定色板索引。按 sortOrder、id 排序后取模;未分组固定为末位索引。
|
|
65
|
+
*/
|
|
66
|
+
export function timelineTrackColorIndex(trackId, tracks = []) {
|
|
67
|
+
const key = String(trackId ?? "");
|
|
68
|
+
if (!key) return TIMELINE_UNGROUPED_COLOR_INDEX;
|
|
69
|
+
const ordered = [...tracks]
|
|
70
|
+
.filter((track) => String(track?.id ?? ""))
|
|
71
|
+
.sort((left, right) => {
|
|
72
|
+
const orderDelta = Number(left?.sortOrder ?? 0) - Number(right?.sortOrder ?? 0);
|
|
73
|
+
if (orderDelta !== 0) return orderDelta;
|
|
74
|
+
const leftId = String(left?.id ?? "");
|
|
75
|
+
const rightId = String(right?.id ?? "");
|
|
76
|
+
if (leftId === rightId) return 0;
|
|
77
|
+
return leftId < rightId ? -1 : 1;
|
|
78
|
+
});
|
|
79
|
+
const index = ordered.findIndex((track) => String(track.id) === key);
|
|
80
|
+
if (index < 0) return TIMELINE_UNGROUPED_COLOR_INDEX;
|
|
81
|
+
return index % (TIMELINE_TRACK_PALETTE_SIZE - 1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 先筛选再按时间排序,供列表渲染使用。
|
|
86
|
+
*/
|
|
87
|
+
export function prepareTimelineEvents(events = [], filters = {}, { direction = "asc" } = {}) {
|
|
88
|
+
return sortTimelineEvents(filterTimelineEvents(events, filters), { direction });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 解析当前激活的轨道 Tab。优先保留已选轨道;失效时回退到第一条真实轨道,再回退未分组。
|
|
93
|
+
* null/undefined 表示尚未选择,回退到第一条真实轨道。
|
|
94
|
+
*/
|
|
95
|
+
export function resolveTimelineActiveTrackId(activeTrackId, tracks = []) {
|
|
96
|
+
const orderedIds = [...tracks]
|
|
97
|
+
.filter((track) => String(track?.id ?? ""))
|
|
98
|
+
.sort((left, right) => {
|
|
99
|
+
const orderDelta = Number(left?.sortOrder ?? 0) - Number(right?.sortOrder ?? 0);
|
|
100
|
+
if (orderDelta !== 0) return orderDelta;
|
|
101
|
+
const leftId = String(left?.id ?? "");
|
|
102
|
+
const rightId = String(right?.id ?? "");
|
|
103
|
+
if (leftId === rightId) return 0;
|
|
104
|
+
return leftId < rightId ? -1 : 1;
|
|
105
|
+
})
|
|
106
|
+
.map((track) => String(track.id));
|
|
107
|
+
const fallback = orderedIds[0] ?? "";
|
|
108
|
+
if (activeTrackId === null || activeTrackId === undefined) return fallback;
|
|
109
|
+
const key = String(activeTrackId);
|
|
110
|
+
if (orderedIds.includes(key) || key === "") return key;
|
|
111
|
+
return fallback;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function timelineTrackDisplayName(trackId, tracks = []) {
|
|
115
|
+
const key = String(trackId ?? "");
|
|
116
|
+
if (!key) return "未分组";
|
|
117
|
+
const track = tracks.find((item) => String(item?.id ?? "") === key);
|
|
118
|
+
return track?.name || "未知轨道";
|
|
119
|
+
}
|
package/dist/store.js
CHANGED
|
@@ -374,6 +374,7 @@ export class Store {
|
|
|
374
374
|
if (type === "race")
|
|
375
375
|
return {
|
|
376
376
|
name: entity.name,
|
|
377
|
+
isExtinct: entity.isExtinct,
|
|
377
378
|
parentRaceId: entity.parentRaceId,
|
|
378
379
|
description: entity.description,
|
|
379
380
|
settings: entity.settings,
|
|
@@ -383,6 +384,7 @@ export class Store {
|
|
|
383
384
|
if (type === "organization")
|
|
384
385
|
return {
|
|
385
386
|
name: entity.name,
|
|
387
|
+
isDissolved: entity.isDissolved,
|
|
386
388
|
description: entity.description,
|
|
387
389
|
settings: entity.settings,
|
|
388
390
|
settingsSections: entity.settingsSections,
|
|
@@ -552,9 +554,9 @@ export class Store {
|
|
|
552
554
|
else if (type === "setting")
|
|
553
555
|
restored = this.updateSetting(entityId, snapshot, "restore", sourceRef, changeNote, expectedVersionNo);
|
|
554
556
|
else if (type === "race")
|
|
555
|
-
restored = this.updateRace(entityId, snapshot, "restore", sourceRef, changeNote, expectedVersionNo);
|
|
557
|
+
restored = this.updateRace(entityId, { isExtinct: false, ...snapshot }, "restore", sourceRef, changeNote, expectedVersionNo);
|
|
556
558
|
else if (type === "organization")
|
|
557
|
-
restored = this.updateOrganization(entityId, snapshot, "restore", sourceRef, changeNote, expectedVersionNo);
|
|
559
|
+
restored = this.updateOrganization(entityId, { isDissolved: false, ...snapshot }, "restore", sourceRef, changeNote, expectedVersionNo);
|
|
558
560
|
else if (type === "timeline-track")
|
|
559
561
|
restored = this.updateTimelineTrack(entityId, snapshot, "restore", sourceRef, changeNote, expectedVersionNo);
|
|
560
562
|
else if (type === "timeline-event")
|
|
@@ -595,10 +597,10 @@ export class Store {
|
|
|
595
597
|
return this.insertSettingWithId(workId, entityId, snapshot, "restore", sourceRef, changeNote);
|
|
596
598
|
}
|
|
597
599
|
if (type === "race") {
|
|
598
|
-
return this.insertRaceWithId(workId, entityId, snapshot, "restore", sourceRef, changeNote);
|
|
600
|
+
return this.insertRaceWithId(workId, entityId, { isExtinct: false, ...snapshot }, "restore", sourceRef, changeNote);
|
|
599
601
|
}
|
|
600
602
|
if (type === "organization") {
|
|
601
|
-
return this.insertOrganizationWithId(workId, entityId, snapshot, "restore", sourceRef, changeNote);
|
|
603
|
+
return this.insertOrganizationWithId(workId, entityId, { isDissolved: false, ...snapshot }, "restore", sourceRef, changeNote);
|
|
602
604
|
}
|
|
603
605
|
if (type === "timeline-track") {
|
|
604
606
|
return this.insertTimelineTrackWithId(workId, entityId, snapshot, "restore", sourceRef, changeNote);
|
|
@@ -2606,8 +2608,8 @@ export class Store {
|
|
|
2606
2608
|
const settings = settingsFromKnowledgeSections(settingsSections);
|
|
2607
2609
|
const timestamp = now();
|
|
2608
2610
|
this.db.transaction(() => {
|
|
2609
|
-
this.db.run(`INSERT INTO races (id, work_id, parent_race_id, name, normalized_name, description, settings_json, settings_sections_json, created_at, updated_at)
|
|
2610
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, raceId, workId, parentRaceId, name, normalizedName, input.description ?? "", JSON.stringify(settings), JSON.stringify(settingsSections), timestamp, timestamp);
|
|
2611
|
+
this.db.run(`INSERT INTO races (id, work_id, parent_race_id, name, normalized_name, description, is_extinct, settings_json, settings_sections_json, created_at, updated_at)
|
|
2612
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, raceId, workId, parentRaceId, name, normalizedName, input.description ?? "", input.isExtinct ? 1 : 0, JSON.stringify(settings), JSON.stringify(settingsSections), timestamp, timestamp);
|
|
2611
2613
|
this.syncMarkdownAttachmentReferences(workId, "race", raceId, settingsMarkdownFromList(settings));
|
|
2612
2614
|
this.replaceRaceMembers(raceId, name, memberIds);
|
|
2613
2615
|
this.recordMembershipVersions(memberSnapshots, "race", raceId, `设为种族“${name}”`);
|
|
@@ -2672,7 +2674,7 @@ export class Store {
|
|
|
2672
2674
|
const settings = settingsFromKnowledgeSections(settingsSections);
|
|
2673
2675
|
this.db.transaction(() => {
|
|
2674
2676
|
this.assertExpectedVersion("race", raceId, expectedVersionNo, "种族");
|
|
2675
|
-
this.db.run(`UPDATE races SET parent_race_id = ?, name = ?, normalized_name = ?, description = ?, settings_json = ?, settings_sections_json = ?, updated_at = ? WHERE id = ?`, parentRaceId, name, normalizedName, input.description ?? String(current.description), JSON.stringify(settings), JSON.stringify(settingsSections), now(), raceId);
|
|
2677
|
+
this.db.run(`UPDATE races SET parent_race_id = ?, name = ?, normalized_name = ?, description = ?, is_extinct = ?, settings_json = ?, settings_sections_json = ?, updated_at = ? WHERE id = ?`, parentRaceId, name, normalizedName, input.description ?? String(current.description), input.isExtinct === undefined ? (current.isExtinct ? 1 : 0) : (input.isExtinct ? 1 : 0), JSON.stringify(settings), JSON.stringify(settingsSections), now(), raceId);
|
|
2676
2678
|
this.syncMarkdownAttachmentReferences(workId, "race", raceId, settingsMarkdownFromList(settings));
|
|
2677
2679
|
if (nameChanged)
|
|
2678
2680
|
this.db.run("UPDATE characters SET species = ?, updated_at = ? WHERE race_id = ?", name, now(), raceId);
|
|
@@ -2764,6 +2766,7 @@ export class Store {
|
|
|
2764
2766
|
parentRaceId: optionalString(row, "parent_race_id"),
|
|
2765
2767
|
name: requiredString(row, "name"),
|
|
2766
2768
|
description: requiredString(row, "description"),
|
|
2769
|
+
isExtinct: booleanValue(row, "is_extinct"),
|
|
2767
2770
|
...(row.child_count === undefined ? {} : { childCount: numberValue(row, "child_count") }),
|
|
2768
2771
|
...(includeMarkdown
|
|
2769
2772
|
? { settings, settingsMarkdown: settingsMarkdownFromList(settings), settingsSections }
|
|
@@ -2879,8 +2882,8 @@ export class Store {
|
|
|
2879
2882
|
const settings = settingsFromKnowledgeSections(settingsSections);
|
|
2880
2883
|
const timestamp = now();
|
|
2881
2884
|
this.db.transaction(() => {
|
|
2882
|
-
this.db.run(`INSERT INTO organizations (id, work_id, name, normalized_name, description, settings_json, settings_sections_json, created_at, updated_at)
|
|
2883
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, organizationId, workId, name, normalizedName, input.description ?? "", JSON.stringify(settings), JSON.stringify(settingsSections), timestamp, timestamp);
|
|
2885
|
+
this.db.run(`INSERT INTO organizations (id, work_id, name, normalized_name, description, is_dissolved, settings_json, settings_sections_json, created_at, updated_at)
|
|
2886
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, organizationId, workId, name, normalizedName, input.description ?? "", input.isDissolved ? 1 : 0, JSON.stringify(settings), JSON.stringify(settingsSections), timestamp, timestamp);
|
|
2884
2887
|
this.syncMarkdownAttachmentReferences(workId, "organization", organizationId, settingsMarkdownFromList(settings));
|
|
2885
2888
|
this.replaceOrganizationMembers(organizationId, memberIds);
|
|
2886
2889
|
this.recordMembershipVersions(memberSnapshots, "organization", organizationId, `加入组织“${name}”`);
|
|
@@ -2925,7 +2928,7 @@ export class Store {
|
|
|
2925
2928
|
const settings = settingsFromKnowledgeSections(settingsSections);
|
|
2926
2929
|
this.db.transaction(() => {
|
|
2927
2930
|
this.assertExpectedVersion("organization", organizationId, expectedVersionNo, "组织");
|
|
2928
|
-
this.db.run(`UPDATE organizations SET name = ?, normalized_name = ?, description = ?, settings_json = ?, settings_sections_json = ?, updated_at = ? WHERE id = ?`, name, normalizedName, input.description ?? String(current.description), JSON.stringify(settings), JSON.stringify(settingsSections), now(), organizationId);
|
|
2931
|
+
this.db.run(`UPDATE organizations SET name = ?, normalized_name = ?, description = ?, is_dissolved = ?, settings_json = ?, settings_sections_json = ?, updated_at = ? WHERE id = ?`, name, normalizedName, input.description ?? String(current.description), input.isDissolved === undefined ? (current.isDissolved ? 1 : 0) : (input.isDissolved ? 1 : 0), JSON.stringify(settings), JSON.stringify(settingsSections), now(), organizationId);
|
|
2929
2932
|
this.syncMarkdownAttachmentReferences(workId, "organization", organizationId, settingsMarkdownFromList(settings));
|
|
2930
2933
|
if (memberIds) {
|
|
2931
2934
|
this.replaceOrganizationMembers(organizationId, memberIds);
|
|
@@ -3001,6 +3004,7 @@ export class Store {
|
|
|
3001
3004
|
workId: requiredString(row, "work_id"),
|
|
3002
3005
|
name: requiredString(row, "name"),
|
|
3003
3006
|
description: requiredString(row, "description"),
|
|
3007
|
+
isDissolved: booleanValue(row, "is_dissolved"),
|
|
3004
3008
|
...(includeMarkdown
|
|
3005
3009
|
? { settings, settingsMarkdown: settingsMarkdownFromList(settings), settingsSections }
|
|
3006
3010
|
: { settings: [], settingsCount: settingsSections.length }),
|
|
@@ -3053,6 +3057,7 @@ export class Store {
|
|
|
3053
3057
|
delete profile.sections;
|
|
3054
3058
|
return {
|
|
3055
3059
|
name: String(character.name),
|
|
3060
|
+
isDead: Boolean(character.isDead),
|
|
3056
3061
|
code: String(character.code),
|
|
3057
3062
|
aliases: [...character.aliases],
|
|
3058
3063
|
raceId: character.raceId,
|
|
@@ -3106,8 +3111,8 @@ export class Store {
|
|
|
3106
3111
|
this.assertOrganizationsInWork(workId, organizationIds);
|
|
3107
3112
|
this.db.transaction(() => {
|
|
3108
3113
|
this.db.run(`INSERT INTO characters (id, work_id, name, code, aliases_json, species, race_id, attributes_json, profile_json, current_state_json,
|
|
3109
|
-
locked_fields_json, first_chapter_id, created_at, updated_at)
|
|
3110
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, characterId, workId, names.name, input.code?.trim() ?? "", JSON.stringify(names.aliases), species, raceId, JSON.stringify(input.attributes ?? {}), JSON.stringify(input.profile ?? {}), JSON.stringify(input.currentState ?? {}), JSON.stringify(input.lockedFields ?? []), input.firstChapterId ?? null, timestamp, timestamp);
|
|
3114
|
+
is_dead, locked_fields_json, first_chapter_id, created_at, updated_at)
|
|
3115
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, characterId, workId, names.name, input.code?.trim() ?? "", JSON.stringify(names.aliases), species, raceId, JSON.stringify(input.attributes ?? {}), JSON.stringify(input.profile ?? {}), JSON.stringify(input.currentState ?? {}), input.isDead ? 1 : 0, JSON.stringify(input.lockedFields ?? []), input.firstChapterId ?? null, timestamp, timestamp);
|
|
3111
3116
|
this.insertCharacterNames(workId, characterId, names.entries);
|
|
3112
3117
|
this.replaceCharacterOrganizations(characterId, organizationIds);
|
|
3113
3118
|
this.insertCharacterVersion(characterId, 1, "create", null, "建立人物档案", timestamp);
|
|
@@ -3547,7 +3552,7 @@ export class Store {
|
|
|
3547
3552
|
const lockedCurrent = this.getCharacter(characterId);
|
|
3548
3553
|
this.assertExpectedRevision("character", characterId, expectedVersionNo, "人物", Number(lockedCurrent.versionNo));
|
|
3549
3554
|
this.db.run(`UPDATE characters SET name = ?, code = ?, aliases_json = ?, species = ?, race_id = ?, attributes_json = ?, profile_json = ?, current_state_json = ?,
|
|
3550
|
-
locked_fields_json = ?, first_chapter_id = ?, updated_at = ? WHERE id = ?`, names.name, input.code === undefined ? String(current.code) : input.code.trim(), JSON.stringify(names.aliases), species, raceId, JSON.stringify(attributes), JSON.stringify(input.profile ?? current.profile), JSON.stringify(input.currentState ?? current.currentState), JSON.stringify(input.lockedFields ?? current.lockedFields), input.firstChapterId === undefined ? current.firstChapterId : input.firstChapterId, now(), characterId);
|
|
3555
|
+
is_dead = ?, locked_fields_json = ?, first_chapter_id = ?, updated_at = ? WHERE id = ?`, names.name, input.code === undefined ? String(current.code) : input.code.trim(), JSON.stringify(names.aliases), species, raceId, JSON.stringify(attributes), JSON.stringify(input.profile ?? current.profile), JSON.stringify(input.currentState ?? current.currentState), input.isDead === undefined ? (current.isDead ? 1 : 0) : (input.isDead ? 1 : 0), JSON.stringify(input.lockedFields ?? current.lockedFields), input.firstChapterId === undefined ? current.firstChapterId : input.firstChapterId, now(), characterId);
|
|
3551
3556
|
this.db.run("DELETE FROM character_names WHERE character_id = ?", characterId);
|
|
3552
3557
|
this.insertCharacterNames(workId, characterId, names.entries);
|
|
3553
3558
|
if (organizationIds)
|
|
@@ -3616,7 +3621,7 @@ export class Store {
|
|
|
3616
3621
|
this.assertExpectedRevision("character", characterId, expectedVersionNo, "人物", this.currentCharacterVersionNo(characterId));
|
|
3617
3622
|
return this.recreateCharacterFromVersion(characterId, version, snapshot, versionNo);
|
|
3618
3623
|
}
|
|
3619
|
-
return this.updateCharacter(characterId, { ...snapshot, code: snapshot.code ?? "" }, "restore", requiredString(version, "id"), `恢复至 v${versionNo}`, expectedVersionNo);
|
|
3624
|
+
return this.updateCharacter(characterId, { ...snapshot, isDead: snapshot.isDead ?? false, code: snapshot.code ?? "" }, "restore", requiredString(version, "id"), `恢复至 v${versionNo}`, expectedVersionNo);
|
|
3620
3625
|
}
|
|
3621
3626
|
recreateCharacterFromVersion(characterId, version, snapshot, versionNo) {
|
|
3622
3627
|
const workId = requiredString(version, "work_id");
|
|
@@ -3634,8 +3639,8 @@ export class Store {
|
|
|
3634
3639
|
const nextVersionNo = numberValue(this.db.get("SELECT COALESCE(MAX(version_no), 0) AS version_no FROM character_versions WHERE character_id = ?", characterId) ?? {}, "version_no") + 1;
|
|
3635
3640
|
this.db.transaction(() => {
|
|
3636
3641
|
this.db.run(`INSERT INTO characters (id, work_id, name, code, aliases_json, species, race_id, attributes_json, profile_json, current_state_json,
|
|
3637
|
-
locked_fields_json, first_chapter_id, version_no, created_at, updated_at)
|
|
3638
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, characterId, workId, names.name, snapshot.code ?? "", JSON.stringify(names.aliases), species, raceId, JSON.stringify(snapshot.attributes ?? {}), JSON.stringify(snapshot.profile ?? {}), JSON.stringify(snapshot.currentState ?? {}), JSON.stringify(snapshot.lockedFields ?? []), snapshot.firstChapterId ?? null, nextVersionNo, timestamp, timestamp);
|
|
3642
|
+
is_dead, locked_fields_json, first_chapter_id, version_no, created_at, updated_at)
|
|
3643
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, characterId, workId, names.name, snapshot.code ?? "", JSON.stringify(names.aliases), species, raceId, JSON.stringify(snapshot.attributes ?? {}), JSON.stringify(snapshot.profile ?? {}), JSON.stringify(snapshot.currentState ?? {}), snapshot.isDead ? 1 : 0, JSON.stringify(snapshot.lockedFields ?? []), snapshot.firstChapterId ?? null, nextVersionNo, timestamp, timestamp);
|
|
3639
3644
|
this.insertCharacterNames(workId, characterId, names.entries);
|
|
3640
3645
|
this.replaceCharacterOrganizations(characterId, organizationIds);
|
|
3641
3646
|
this.insertCharacterVersion(characterId, nextVersionNo, "restore", requiredString(version, "id"), `恢复至 v${versionNo}`, timestamp, workId);
|
|
@@ -3674,12 +3679,13 @@ export class Store {
|
|
|
3674
3679
|
}
|
|
3675
3680
|
mapCharacter(row, includeProfileSections = true, includeRaceMarkdown = true) {
|
|
3676
3681
|
const indexedAliases = this.db.all("SELECT display_name FROM character_names WHERE character_id = ? AND kind = 'alias' ORDER BY sort_order", requiredString(row, "id")).map((item) => requiredString(item, "display_name"));
|
|
3677
|
-
const organizations = this.db.all(`SELECT o.id, o.name, m.role, m.note
|
|
3682
|
+
const organizations = this.db.all(`SELECT o.id, o.name, o.is_dissolved, m.role, m.note
|
|
3678
3683
|
FROM character_organization_memberships m
|
|
3679
3684
|
JOIN organizations o ON o.id = m.organization_id
|
|
3680
3685
|
WHERE m.character_id = ? ORDER BY o.name`, requiredString(row, "id")).map((item) => ({
|
|
3681
3686
|
organizationId: requiredString(item, "id"),
|
|
3682
3687
|
name: requiredString(item, "name"),
|
|
3688
|
+
isDissolved: booleanValue(item, "is_dissolved"),
|
|
3683
3689
|
role: requiredString(item, "role"),
|
|
3684
3690
|
note: requiredString(item, "note")
|
|
3685
3691
|
}));
|
|
@@ -3716,6 +3722,7 @@ export class Store {
|
|
|
3716
3722
|
race: race ? {
|
|
3717
3723
|
id: String(race.id),
|
|
3718
3724
|
name: species,
|
|
3725
|
+
isExtinct: race.isExtinct,
|
|
3719
3726
|
lineage: race.lineage,
|
|
3720
3727
|
effectiveSettings: race.effectiveSettings
|
|
3721
3728
|
} : null,
|
|
@@ -3726,6 +3733,7 @@ export class Store {
|
|
|
3726
3733
|
profile,
|
|
3727
3734
|
profileSectionCount,
|
|
3728
3735
|
currentState: json(requiredString(row, "current_state_json"), {}),
|
|
3736
|
+
isDead: booleanValue(row, "is_dead"),
|
|
3729
3737
|
lockedFields: json(requiredString(row, "locked_fields_json"), []),
|
|
3730
3738
|
firstChapterId: optionalString(row, "first_chapter_id"),
|
|
3731
3739
|
mergedIntoCharacterId: optionalString(row, "merged_into_character_id"),
|
|
@@ -6048,14 +6056,14 @@ export class Store {
|
|
|
6048
6056
|
), character_race_paths AS (
|
|
6049
6057
|
SELECT character_id, path FROM character_race_lineage WHERE parent_race_id IS NULL
|
|
6050
6058
|
)
|
|
6051
|
-
SELECT character.id, character.name, character.aliases_json, character.species,
|
|
6059
|
+
SELECT character.id, character.name, character.aliases_json, character.species, character.is_dead,
|
|
6052
6060
|
COALESCE(path.path, character.species) AS race_path
|
|
6053
6061
|
FROM characters character LEFT JOIN character_race_paths path ON path.character_id = character.id
|
|
6054
6062
|
WHERE character.work_id = ? AND (
|
|
6055
6063
|
character.name LIKE ? ESCAPE '\\' OR character.aliases_json LIKE ? ESCAPE '\\' OR character.species LIKE ? ESCAPE '\\'
|
|
6056
6064
|
OR EXISTS (SELECT 1 FROM character_race_lineage lineage WHERE lineage.character_id = character.id AND lineage.name LIKE ? ESCAPE '\\')
|
|
6057
6065
|
) LIMIT 50`, workId, workId, pattern, pattern, pattern, pattern);
|
|
6058
|
-
const organizations = this.db.all("SELECT id, name, description, settings_json FROM organizations WHERE work_id = ? AND (name LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\' OR settings_json LIKE ? ESCAPE '\\') LIMIT 50", workId, pattern, pattern, pattern);
|
|
6066
|
+
const organizations = this.db.all("SELECT id, name, description, is_dissolved, settings_json FROM organizations WHERE work_id = ? AND (name LIKE ? ESCAPE '\\' OR description LIKE ? ESCAPE '\\' OR settings_json LIKE ? ESCAPE '\\') LIMIT 50", workId, pattern, pattern, pattern);
|
|
6059
6067
|
const characterSections = this.searchCharacterProfileSections(workId, query, 30);
|
|
6060
6068
|
const snippet = (content) => {
|
|
6061
6069
|
const index = content.toLocaleLowerCase().indexOf(query.toLocaleLowerCase());
|
|
@@ -6068,7 +6076,8 @@ export class Store {
|
|
|
6068
6076
|
id: requiredString(row, "id"),
|
|
6069
6077
|
title: requiredString(row, "name"),
|
|
6070
6078
|
snippet: [requiredString(row, "race_path"), ...json(requiredString(row, "aliases_json"), [])].filter(Boolean).join("、"),
|
|
6071
|
-
racePath: requiredString(row, "race_path")
|
|
6079
|
+
racePath: requiredString(row, "race_path"),
|
|
6080
|
+
isDead: booleanValue(row, "is_dead")
|
|
6072
6081
|
})),
|
|
6073
6082
|
...characterSections.map((section) => ({
|
|
6074
6083
|
type: "character",
|
|
@@ -6076,7 +6085,8 @@ export class Store {
|
|
|
6076
6085
|
sectionId: String(section.id),
|
|
6077
6086
|
title: `${String(section.characterName)} / ${String(section.title)}`,
|
|
6078
6087
|
snippet: snippet(String(section.contentMarkdown)),
|
|
6079
|
-
sectionType: String(section.sectionType)
|
|
6088
|
+
sectionType: String(section.sectionType),
|
|
6089
|
+
isDead: Boolean(this.getCharacter(String(section.characterId)).isDead)
|
|
6080
6090
|
})),
|
|
6081
6091
|
...settings.map((row) => ({ type: "setting", id: requiredString(row, "id"), title: requiredString(row, "title"), snippet: snippet(requiredString(row, "content")), category: requiredString(row, "category") })),
|
|
6082
6092
|
...races.map((race) => {
|
|
@@ -6087,11 +6097,12 @@ export class Store {
|
|
|
6087
6097
|
id: String(race.id),
|
|
6088
6098
|
title: String(race.name),
|
|
6089
6099
|
snippet: snippet(`${lineage.map((item) => item.name).join(" / ")}\n${String(race.description)}\n${effectiveSettings.map((item) => `${item.sourceRaceName}:${item.value}`).join("\n")}`),
|
|
6100
|
+
isExtinct: Boolean(race.isExtinct),
|
|
6090
6101
|
lineage,
|
|
6091
6102
|
effectiveSettings
|
|
6092
6103
|
};
|
|
6093
6104
|
}),
|
|
6094
|
-
...organizations.map((row) => ({ type: "organization", id: requiredString(row, "id"), title: requiredString(row, "name"), snippet: snippet(`${requiredString(row, "description")}\n${json(requiredString(row, "settings_json"), []).join("\n")}`) })),
|
|
6105
|
+
...organizations.map((row) => ({ type: "organization", id: requiredString(row, "id"), title: requiredString(row, "name"), snippet: snippet(`${requiredString(row, "description")}\n${json(requiredString(row, "settings_json"), []).join("\n")}`), isDissolved: booleanValue(row, "is_dissolved") })),
|
|
6095
6106
|
...chapters.map((row) => ({ type: "chapter", id: requiredString(row, "id"), title: requiredString(row, "title"), snippet: snippet(requiredString(row, "content")), volumeId: requiredString(row, "volume_id") }))
|
|
6096
6107
|
];
|
|
6097
6108
|
}
|