@musnows/scriverse 0.7.11 → 0.7.12
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 +388 -61
- package/dist/ai.js.map +1 -1
- package/dist/app.js +67 -2
- package/dist/app.js.map +1 -1
- package/dist/public/app.js +186 -20
- package/dist/public/index.html +1 -1
- package/dist/public/plain-text-paste.js +89 -0
- package/dist/public/styles.css +28 -0
- package/dist/store.js +198 -67
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +1 -1
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/store.js
CHANGED
|
@@ -154,6 +154,29 @@ function knowledgeSectionsFromInput(sections, settingsMarkdown, settings, fallba
|
|
|
154
154
|
function settingsFromKnowledgeSections(sections) {
|
|
155
155
|
return sections.map((section) => section.contentMarkdown).filter((content) => content.trim());
|
|
156
156
|
}
|
|
157
|
+
export const chapterOutlineBoardForeshadowSortSql = {
|
|
158
|
+
cte: `WITH foreshadow_associations AS MATERIALIZED (
|
|
159
|
+
SELECT foreshadow.work_id, occurrence.chapter_id, foreshadow.id AS foreshadow_id, foreshadow.status
|
|
160
|
+
FROM foreshadows foreshadow
|
|
161
|
+
JOIN foreshadow_occurrences occurrence ON occurrence.foreshadow_id = foreshadow.id
|
|
162
|
+
WHERE foreshadow.work_id = ?
|
|
163
|
+
UNION
|
|
164
|
+
SELECT foreshadow.work_id, foreshadow.planned_payoff_chapter_id AS chapter_id,
|
|
165
|
+
foreshadow.id AS foreshadow_id, foreshadow.status
|
|
166
|
+
FROM foreshadows foreshadow
|
|
167
|
+
WHERE foreshadow.work_id = ? AND foreshadow.planned_payoff_chapter_id IS NOT NULL
|
|
168
|
+
), foreshadow_association_counts AS MATERIALIZED (
|
|
169
|
+
SELECT association.work_id, association.chapter_id,
|
|
170
|
+
SUM(CASE WHEN association.status IN ('planned', 'planted') THEN 1 ELSE 0 END) AS unresolved_count,
|
|
171
|
+
COUNT(*) AS total_count
|
|
172
|
+
FROM foreshadow_associations association
|
|
173
|
+
GROUP BY association.work_id, association.chapter_id
|
|
174
|
+
)`,
|
|
175
|
+
join: `LEFT JOIN foreshadow_association_counts sorted_association
|
|
176
|
+
ON sorted_association.work_id = chapter.work_id AND sorted_association.chapter_id = chapter.id`,
|
|
177
|
+
order: `COALESCE(sorted_association.unresolved_count, 0) DESC,
|
|
178
|
+
COALESCE(sorted_association.total_count, 0) DESC`
|
|
179
|
+
};
|
|
157
180
|
const CHAPTER_OUTLINE_BOARD_PREVIEW_LENGTH = 600;
|
|
158
181
|
function chapterOutlineBoardLikePattern(value) {
|
|
159
182
|
return `%${value.normalize("NFKC").toLocaleLowerCase("zh-CN").replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_")}%`;
|
|
@@ -1492,7 +1515,12 @@ export class Store {
|
|
|
1492
1515
|
this.db.run(`UPDATE analysis_tasks SET status = 'expired', updated_at = ?
|
|
1493
1516
|
WHERE work_id = ? AND status IN ('pending', 'running', 'completed', 'partial', 'review')
|
|
1494
1517
|
AND (json_extract(scope_json, '$.type') = 'book'
|
|
1495
|
-
OR (json_extract(scope_json, '$.type') = 'volume' AND json_extract(scope_json, '$.volumeId') = ?)
|
|
1518
|
+
OR (json_extract(scope_json, '$.type') = 'volume' AND json_extract(scope_json, '$.volumeId') = ?)
|
|
1519
|
+
OR EXISTS (SELECT 1 FROM json_each(scope_json, '$.volumeIds') WHERE json_each.value = ?)
|
|
1520
|
+
OR EXISTS (
|
|
1521
|
+
SELECT 1 FROM json_each(scope_json, '$.chapterIds') selected_chapter
|
|
1522
|
+
WHERE selected_chapter.value IN (SELECT id FROM chapters WHERE volume_id = ?)
|
|
1523
|
+
))`, timestamp, workId, volumeId, volumeId, volumeId);
|
|
1496
1524
|
this.db.run("UPDATE works SET updated_at = ? WHERE id = ?", timestamp, workId);
|
|
1497
1525
|
this.audit(workId, "volume.deleted", "volume", volumeId, {
|
|
1498
1526
|
versionNo,
|
|
@@ -2445,9 +2473,14 @@ export class Store {
|
|
|
2445
2473
|
AND NOT (status = 'pending' AND task_type = 'chapter-analysis'
|
|
2446
2474
|
AND json_extract(scope_json, '$.chapterId') = ?)
|
|
2447
2475
|
AND (json_extract(scope_json, '$.chapterId') = ?
|
|
2476
|
+
OR EXISTS (SELECT 1 FROM json_each(scope_json, '$.chapterIds') WHERE json_each.value = ?)
|
|
2448
2477
|
OR json_extract(scope_json, '$.type') = 'book'
|
|
2449
2478
|
OR (json_extract(scope_json, '$.type') = 'volume'
|
|
2450
|
-
AND json_extract(scope_json, '$.volumeId') = (SELECT volume_id FROM chapters WHERE id = ?)
|
|
2479
|
+
AND (json_extract(scope_json, '$.volumeId') = (SELECT volume_id FROM chapters WHERE id = ?)
|
|
2480
|
+
OR EXISTS (
|
|
2481
|
+
SELECT 1 FROM json_each(scope_json, '$.volumeIds')
|
|
2482
|
+
WHERE json_each.value = (SELECT volume_id FROM chapters WHERE id = ?)
|
|
2483
|
+
))))`, now(), workId, chapterId, chapterId, chapterId, chapterId, chapterId);
|
|
2451
2484
|
const existing = this.db.get(`SELECT id FROM analysis_tasks WHERE work_id = ? AND task_type = 'chapter-analysis' AND status = 'pending'
|
|
2452
2485
|
AND json_extract(scope_json, '$.chapterId') = ?`, workId, chapterId);
|
|
2453
2486
|
if (!existing) {
|
|
@@ -2653,33 +2686,25 @@ export class Store {
|
|
|
2653
2686
|
whereParams.push(...Array.from({ length: 8 }, () => pattern));
|
|
2654
2687
|
}
|
|
2655
2688
|
const whereSql = where.join(" AND ");
|
|
2656
|
-
const associationCount = (statusSql = "") => `(
|
|
2657
|
-
SELECT COUNT(*) FROM foreshadows sorted_foreshadow
|
|
2658
|
-
WHERE sorted_foreshadow.work_id = chapter.work_id${statusSql}
|
|
2659
|
-
AND (
|
|
2660
|
-
sorted_foreshadow.planned_payoff_chapter_id = chapter.id
|
|
2661
|
-
OR EXISTS (
|
|
2662
|
-
SELECT 1 FROM foreshadow_occurrences sorted_occurrence
|
|
2663
|
-
WHERE sorted_occurrence.foreshadow_id = sorted_foreshadow.id
|
|
2664
|
-
AND sorted_occurrence.chapter_id = chapter.id
|
|
2665
|
-
)
|
|
2666
|
-
)
|
|
2667
|
-
)`;
|
|
2668
2689
|
const chapterTreeOrder = "chapter.sort_order, chapter.created_at, chapter.id";
|
|
2669
2690
|
const chapterOrder = filters.sort === "status"
|
|
2670
2691
|
? `CASE WHEN outline.chapter_id IS NULL THEN 0 WHEN outline.status = 'draft' THEN 1 WHEN outline.status = 'ready' THEN 2 ELSE 3 END, ${chapterTreeOrder}`
|
|
2671
2692
|
: filters.sort === "foreshadows"
|
|
2672
|
-
? `${
|
|
2693
|
+
? `${chapterOutlineBoardForeshadowSortSql.order}, ${chapterTreeOrder}`
|
|
2673
2694
|
: filters.sort === "title"
|
|
2674
2695
|
? `chapter.title COLLATE NOCASE, ${chapterTreeOrder}`
|
|
2675
2696
|
: chapterTreeOrder;
|
|
2676
2697
|
const orderSql = `volume.sort_order, volume.created_at, volume.id, ${chapterOrder}`;
|
|
2698
|
+
const foreshadowSortCte = filters.sort === "foreshadows" ? chapterOutlineBoardForeshadowSortSql.cte : "";
|
|
2699
|
+
const foreshadowSortJoin = filters.sort === "foreshadows" ? chapterOutlineBoardForeshadowSortSql.join : "";
|
|
2700
|
+
const foreshadowSortParams = filters.sort === "foreshadows" ? [workId, workId] : [];
|
|
2677
2701
|
const total = numberValue(this.db.get(`SELECT COUNT(*) AS count
|
|
2678
2702
|
FROM chapters chapter
|
|
2679
2703
|
JOIN volumes volume ON volume.id = chapter.volume_id AND volume.work_id = chapter.work_id
|
|
2680
2704
|
LEFT JOIN chapter_outlines outline ON outline.chapter_id = chapter.id
|
|
2681
2705
|
WHERE ${whereSql}`, ...whereParams) ?? {}, "count");
|
|
2682
|
-
const pageRows = this.db.all(
|
|
2706
|
+
const pageRows = this.db.all(`${foreshadowSortCte}
|
|
2707
|
+
SELECT volume.id AS volume_id, volume.title AS volume_title, volume.sort_order AS volume_order,
|
|
2683
2708
|
chapter.id AS chapter_id, chapter.title AS chapter_title, chapter.chapter_type,
|
|
2684
2709
|
chapter.sort_order AS chapter_order,
|
|
2685
2710
|
outline.chapter_id AS outline_chapter_id,
|
|
@@ -2691,9 +2716,10 @@ export class Store {
|
|
|
2691
2716
|
FROM chapters chapter
|
|
2692
2717
|
JOIN volumes volume ON volume.id = chapter.volume_id AND volume.work_id = chapter.work_id
|
|
2693
2718
|
LEFT JOIN chapter_outlines outline ON outline.chapter_id = chapter.id
|
|
2719
|
+
${foreshadowSortJoin}
|
|
2694
2720
|
WHERE ${whereSql}
|
|
2695
2721
|
ORDER BY ${orderSql}
|
|
2696
|
-
LIMIT ? OFFSET ?`, previewLength, previewLength, previewLength, previewLength, previewLength, previewLength, previewLength, previewLength, ...whereParams, pagination.limit + 1, pagination.offset);
|
|
2722
|
+
LIMIT ? OFFSET ?`, ...foreshadowSortParams, previewLength, previewLength, previewLength, previewLength, previewLength, previewLength, previewLength, previewLength, ...whereParams, pagination.limit + 1, pagination.offset);
|
|
2697
2723
|
const chapterPage = paginated(pageRows, pagination, total);
|
|
2698
2724
|
const volumeRows = this.db.all(`SELECT volume.id, volume.title, volume.sort_order,
|
|
2699
2725
|
COUNT(chapter.id) AS chapter_count
|
|
@@ -5936,65 +5962,98 @@ export class Store {
|
|
|
5936
5962
|
}
|
|
5937
5963
|
relationshipRosterSourceVersions(workId) {
|
|
5938
5964
|
const versions = {};
|
|
5939
|
-
for (const
|
|
5940
|
-
versions[`character:${
|
|
5941
|
-
}
|
|
5942
|
-
for (const race of this.listRaces(workId))
|
|
5943
|
-
versions[`race:${String(race.id)}`] = Number(race.versionNo);
|
|
5944
|
-
for (const organization of this.listOrganizations(workId)) {
|
|
5945
|
-
versions[`organization:${String(organization.id)}`] = Number(organization.versionNo);
|
|
5946
|
-
}
|
|
5947
|
-
for (const relationship of this.listRelationships(workId)) {
|
|
5948
|
-
versions[`relationship:${String(relationship.id)}`] = Number(relationship.versionNo);
|
|
5965
|
+
for (const row of this.db.all("SELECT id, version_no FROM characters WHERE work_id = ? AND merged_into_character_id IS NULL", workId)) {
|
|
5966
|
+
versions[`character:${requiredString(row, "id")}`] = numberValue(row, "version_no");
|
|
5949
5967
|
}
|
|
5968
|
+
this.appendVersionedEntitySourceVersions(versions, workId, "race", "races");
|
|
5969
|
+
this.appendVersionedEntitySourceVersions(versions, workId, "organization", "organizations");
|
|
5970
|
+
this.appendVersionedEntitySourceVersions(versions, workId, "relationship", "relationships");
|
|
5950
5971
|
return versions;
|
|
5951
5972
|
}
|
|
5973
|
+
appendVersionedEntitySourceVersions(versions, workId, entityType, table) {
|
|
5974
|
+
const rows = this.db.all(`SELECT entity.id, COALESCE(MAX(version.version_no), 0) AS version_no
|
|
5975
|
+
FROM ${table} entity
|
|
5976
|
+
LEFT JOIN entity_versions version
|
|
5977
|
+
ON version.work_id = entity.work_id AND version.entity_type = ? AND version.entity_id = entity.id
|
|
5978
|
+
WHERE entity.work_id = ?
|
|
5979
|
+
GROUP BY entity.id`, entityType, workId);
|
|
5980
|
+
for (const row of rows) {
|
|
5981
|
+
versions[`${entityType}:${requiredString(row, "id")}`] = numberValue(row, "version_no");
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5952
5984
|
relationshipSettingsSourceVersions(workId) {
|
|
5953
5985
|
const versions = this.relationshipRosterSourceVersions(workId);
|
|
5954
|
-
const work = this.
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
for (const
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5986
|
+
const work = this.db.get("SELECT version_no FROM works WHERE id = ? AND deleted_at IS NULL", workId);
|
|
5987
|
+
if (!work)
|
|
5988
|
+
throw notFound("作品");
|
|
5989
|
+
versions[`work:${workId}`] = numberValue(work, "version_no");
|
|
5990
|
+
this.appendVersionedEntitySourceVersions(versions, workId, "setting", "settings");
|
|
5991
|
+
for (const row of this.db.all(`SELECT section.id, section.version_no
|
|
5992
|
+
FROM character_profile_sections section
|
|
5993
|
+
JOIN characters character ON character.id = section.character_id
|
|
5994
|
+
WHERE section.work_id = ? AND character.merged_into_character_id IS NULL`, workId)) {
|
|
5995
|
+
versions[`character-section:${requiredString(row, "id")}`] = numberValue(row, "version_no");
|
|
5996
|
+
}
|
|
5997
|
+
this.appendVersionedEntitySourceVersions(versions, workId, "timeline-track", "timeline_tracks");
|
|
5998
|
+
this.appendVersionedEntitySourceVersions(versions, workId, "timeline-event", "timeline_events");
|
|
5999
|
+
for (const row of this.db.all(`SELECT chapter.id, chapter.version_no AS chapter_version_no,
|
|
6000
|
+
COALESCE(MAX(version.version_no), 0) AS outline_version_no
|
|
6001
|
+
FROM chapter_outlines outline
|
|
6002
|
+
JOIN chapters chapter ON chapter.id = outline.chapter_id
|
|
6003
|
+
LEFT JOIN entity_versions version
|
|
6004
|
+
ON version.work_id = chapter.work_id
|
|
6005
|
+
AND version.entity_type = 'chapter-outline'
|
|
6006
|
+
AND version.entity_id = chapter.id
|
|
6007
|
+
WHERE chapter.work_id = ? AND chapter.deleted_at IS NULL
|
|
6008
|
+
GROUP BY chapter.id, chapter.version_no`, workId)) {
|
|
6009
|
+
const chapterId = requiredString(row, "id");
|
|
6010
|
+
versions[`chapter-meta:${chapterId}`] = numberValue(row, "chapter_version_no");
|
|
6011
|
+
versions[`chapter-outline:${chapterId}`] = numberValue(row, "outline_version_no");
|
|
5974
6012
|
}
|
|
5975
|
-
|
|
5976
|
-
|
|
6013
|
+
this.appendVersionedEntitySourceVersions(versions, workId, "foreshadow", "foreshadows");
|
|
6014
|
+
for (const row of this.db.all("SELECT id, updated_at FROM review_items WHERE work_id = ?", workId)) {
|
|
6015
|
+
versions[`review:${requiredString(row, "id")}`] = requiredString(row, "updated_at");
|
|
5977
6016
|
}
|
|
5978
6017
|
return versions;
|
|
5979
6018
|
}
|
|
5980
6019
|
analysisTaskSourceVersions(workId, scope) {
|
|
5981
6020
|
const sourceVersions = {};
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
6021
|
+
const selectedChapterIds = [
|
|
6022
|
+
...(typeof scope.chapterId === "string" ? [scope.chapterId] : []),
|
|
6023
|
+
...(Array.isArray(scope.chapterIds) ? scope.chapterIds.filter((value) => typeof value === "string") : [])
|
|
6024
|
+
];
|
|
6025
|
+
for (const chapterId of [...new Set(selectedChapterIds)]) {
|
|
6026
|
+
const chapter = this.db.get("SELECT work_id, version_no FROM chapters WHERE id = ? AND deleted_at IS NULL", chapterId);
|
|
6027
|
+
if (!chapter)
|
|
6028
|
+
throw notFound("章节");
|
|
6029
|
+
if (requiredString(chapter, "work_id") !== workId)
|
|
5985
6030
|
throw new AppError(400, "CHAPTER_WORK_MISMATCH", "章节不属于当前作品");
|
|
5986
|
-
sourceVersions[
|
|
5987
|
-
}
|
|
5988
|
-
|
|
5989
|
-
const
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
if (scope.type === "volume"
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
6031
|
+
sourceVersions[chapterId] = numberValue(chapter, "version_no");
|
|
6032
|
+
}
|
|
6033
|
+
if (scope.type === "book" || scope.type === "volume") {
|
|
6034
|
+
const selectedVolumeIds = [
|
|
6035
|
+
...(typeof scope.volumeId === "string" ? [scope.volumeId] : []),
|
|
6036
|
+
...(Array.isArray(scope.volumeIds) ? scope.volumeIds.filter((value) => typeof value === "string") : [])
|
|
6037
|
+
];
|
|
6038
|
+
const selectedVolumeIdSet = new Set(selectedVolumeIds);
|
|
6039
|
+
if (scope.type === "volume") {
|
|
6040
|
+
if (selectedVolumeIdSet.size === 0)
|
|
6041
|
+
throw notFound("卷");
|
|
6042
|
+
const volumePlaceholders = [...selectedVolumeIdSet].map(() => "?").join(", ");
|
|
6043
|
+
const volumes = this.db.all(`SELECT id FROM volumes WHERE work_id = ? AND deleted_at IS NULL AND id IN (${volumePlaceholders})`, workId, ...selectedVolumeIdSet);
|
|
6044
|
+
if (volumes.length !== selectedVolumeIdSet.size)
|
|
6045
|
+
throw notFound("卷");
|
|
6046
|
+
}
|
|
6047
|
+
const volumeFilter = scope.type === "volume"
|
|
6048
|
+
? `AND chapter.volume_id IN (${[...selectedVolumeIdSet].map(() => "?").join(", ")})`
|
|
6049
|
+
: "";
|
|
6050
|
+
const chapterRows = this.db.all(`SELECT chapter.id, chapter.version_no
|
|
6051
|
+
FROM chapters chapter
|
|
6052
|
+
JOIN volumes volume ON volume.id = chapter.volume_id
|
|
6053
|
+
WHERE chapter.work_id = ? AND chapter.deleted_at IS NULL AND volume.deleted_at IS NULL
|
|
6054
|
+
${volumeFilter}`, workId, ...(scope.type === "volume" ? [...selectedVolumeIdSet] : []));
|
|
6055
|
+
for (const chapter of chapterRows) {
|
|
6056
|
+
sourceVersions[requiredString(chapter, "id")] = numberValue(chapter, "version_no");
|
|
5998
6057
|
}
|
|
5999
6058
|
}
|
|
6000
6059
|
if (scope.previewRelationshipChanges === true) {
|
|
@@ -6029,10 +6088,12 @@ export class Store {
|
|
|
6029
6088
|
for (const characterId of scope.characterIds) {
|
|
6030
6089
|
if (typeof characterId !== "string")
|
|
6031
6090
|
throw new AppError(400, "CHARACTER_REQUIRED", "被分析角色标识无效");
|
|
6032
|
-
const character = this.
|
|
6033
|
-
if (character
|
|
6091
|
+
const character = this.db.get("SELECT work_id, name FROM characters WHERE id = ?", characterId);
|
|
6092
|
+
if (!character)
|
|
6093
|
+
throw notFound("角色");
|
|
6094
|
+
if (requiredString(character, "work_id") !== workId)
|
|
6034
6095
|
throw new AppError(400, "CHARACTER_WORK_MISMATCH", "被分析角色不属于当前作品");
|
|
6035
|
-
targetCharacters.push({ id: characterId, name:
|
|
6096
|
+
targetCharacters.push({ id: characterId, name: requiredString(character, "name") });
|
|
6036
6097
|
}
|
|
6037
6098
|
if (targetCharacters.length > 0)
|
|
6038
6099
|
scope.targetCharacters = targetCharacters;
|
|
@@ -7158,8 +7219,22 @@ export class Store {
|
|
|
7158
7219
|
}
|
|
7159
7220
|
taskScopeSummaryFromMaps(scope, chapterSummaries, volumeTitles, characterNames, includeCharacterNames = true) {
|
|
7160
7221
|
const targetedSuffix = this.taskTargetedSuffix(scope, characterNames, includeCharacterNames);
|
|
7222
|
+
if (Array.isArray(scope.chapterIds) && scope.chapterIds.length > 0) {
|
|
7223
|
+
const labels = scope.chapterIds
|
|
7224
|
+
.filter((chapterId) => typeof chapterId === "string")
|
|
7225
|
+
.map((chapterId) => chapterSummaries.get(chapterId) ?? "章节已删除");
|
|
7226
|
+
const preview = labels.slice(0, 3).join("、");
|
|
7227
|
+
return `指定章节(${labels.length}):${preview}${labels.length > 3 ? "……" : ""}${targetedSuffix}`;
|
|
7228
|
+
}
|
|
7161
7229
|
if (typeof scope.chapterId === "string")
|
|
7162
7230
|
return `${chapterSummaries.get(scope.chapterId) ?? "章节已删除"}${targetedSuffix}`;
|
|
7231
|
+
if (Array.isArray(scope.volumeIds) && scope.volumeIds.length > 0) {
|
|
7232
|
+
const labels = scope.volumeIds
|
|
7233
|
+
.filter((volumeId) => typeof volumeId === "string")
|
|
7234
|
+
.map((volumeId) => volumeTitles.get(volumeId) ? `分卷 · ${volumeTitles.get(volumeId)}` : "分卷已删除");
|
|
7235
|
+
const preview = labels.slice(0, 3).join("、");
|
|
7236
|
+
return `指定分卷(${labels.length}):${preview}${labels.length > 3 ? "……" : ""}${targetedSuffix}`;
|
|
7237
|
+
}
|
|
7163
7238
|
if (scope.type === "volume" && typeof scope.volumeId === "string") {
|
|
7164
7239
|
const title = volumeTitles.get(scope.volumeId);
|
|
7165
7240
|
return `${title ? `分卷 · ${title}` : "分卷已删除"}${targetedSuffix}`;
|
|
@@ -7178,6 +7253,17 @@ export class Store {
|
|
|
7178
7253
|
}
|
|
7179
7254
|
taskScopeSummary(workId, scope, characterNames, includeCharacterNames = true) {
|
|
7180
7255
|
const targetedSuffix = this.taskTargetedSuffix(scope, characterNames, includeCharacterNames);
|
|
7256
|
+
if (Array.isArray(scope.chapterIds) && scope.chapterIds.length > 0) {
|
|
7257
|
+
const labels = scope.chapterIds.map((chapterId) => {
|
|
7258
|
+
const chapter = this.db.get(`SELECT chapter.title AS title, volume.title AS volume_title
|
|
7259
|
+
FROM chapters chapter
|
|
7260
|
+
JOIN volumes volume ON volume.id = chapter.volume_id
|
|
7261
|
+
WHERE chapter.id = ? AND chapter.work_id = ? AND chapter.deleted_at IS NULL`, chapterId, workId);
|
|
7262
|
+
return chapter ? `${requiredString(chapter, "volume_title")} · ${requiredString(chapter, "title")}` : "章节已删除";
|
|
7263
|
+
});
|
|
7264
|
+
const preview = labels.slice(0, 3).join("、");
|
|
7265
|
+
return `指定章节(${labels.length}):${preview}${labels.length > 3 ? "……" : ""}${targetedSuffix}`;
|
|
7266
|
+
}
|
|
7181
7267
|
if (typeof scope.chapterId === "string") {
|
|
7182
7268
|
const chapter = this.db.get(`SELECT chapter.title AS title, volume.title AS volume_title
|
|
7183
7269
|
FROM chapters chapter
|
|
@@ -7189,6 +7275,14 @@ export class Store {
|
|
|
7189
7275
|
const volumeTitle = requiredString(chapter, "volume_title");
|
|
7190
7276
|
return `${volumeTitle} · ${title}${targetedSuffix}`;
|
|
7191
7277
|
}
|
|
7278
|
+
if (Array.isArray(scope.volumeIds) && scope.volumeIds.length > 0) {
|
|
7279
|
+
const labels = scope.volumeIds.map((volumeId) => {
|
|
7280
|
+
const volume = this.db.get("SELECT title FROM volumes WHERE id = ? AND work_id = ?", volumeId, workId);
|
|
7281
|
+
return volume ? `分卷 · ${requiredString(volume, "title")}` : "分卷已删除";
|
|
7282
|
+
});
|
|
7283
|
+
const preview = labels.slice(0, 3).join("、");
|
|
7284
|
+
return `指定分卷(${labels.length}):${preview}${labels.length > 3 ? "……" : ""}${targetedSuffix}`;
|
|
7285
|
+
}
|
|
7192
7286
|
if (scope.type === "volume" && typeof scope.volumeId === "string") {
|
|
7193
7287
|
const volume = this.db.get("SELECT title FROM volumes WHERE id = ? AND work_id = ?", scope.volumeId, workId);
|
|
7194
7288
|
return `${volume ? `分卷 · ${requiredString(volume, "title")}` : "分卷已删除"}${targetedSuffix}`;
|
|
@@ -7296,6 +7390,25 @@ export class Store {
|
|
|
7296
7390
|
return snapshotNames;
|
|
7297
7391
|
}
|
|
7298
7392
|
taskScopeDetails(workId, scope) {
|
|
7393
|
+
if (Array.isArray(scope.chapterIds) && scope.chapterIds.length > 0) {
|
|
7394
|
+
return scope.chapterIds.map((chapterId) => {
|
|
7395
|
+
const chapter = this.db.get(`SELECT chapter.id AS id, chapter.title AS title, chapter.version_no AS version_no,
|
|
7396
|
+
volume.id AS volume_id, volume.title AS volume_title
|
|
7397
|
+
FROM chapters chapter
|
|
7398
|
+
JOIN volumes volume ON volume.id = chapter.volume_id
|
|
7399
|
+
WHERE chapter.id = ? AND chapter.work_id = ? AND chapter.deleted_at IS NULL`, chapterId, workId);
|
|
7400
|
+
if (!chapter)
|
|
7401
|
+
return { type: "chapter", chapterId, missing: true };
|
|
7402
|
+
return {
|
|
7403
|
+
type: "chapter",
|
|
7404
|
+
chapterId: requiredString(chapter, "id"),
|
|
7405
|
+
title: requiredString(chapter, "title"),
|
|
7406
|
+
versionNo: numberValue(chapter, "version_no"),
|
|
7407
|
+
volumeId: requiredString(chapter, "volume_id"),
|
|
7408
|
+
volumeTitle: requiredString(chapter, "volume_title")
|
|
7409
|
+
};
|
|
7410
|
+
});
|
|
7411
|
+
}
|
|
7299
7412
|
if (typeof scope.chapterId === "string") {
|
|
7300
7413
|
const chapter = this.db.get(`SELECT chapter.id AS id, chapter.title AS title, chapter.version_no AS version_no,
|
|
7301
7414
|
volume.id AS volume_id, volume.title AS volume_title
|
|
@@ -7313,6 +7426,24 @@ export class Store {
|
|
|
7313
7426
|
volumeTitle: requiredString(chapter, "volume_title")
|
|
7314
7427
|
}];
|
|
7315
7428
|
}
|
|
7429
|
+
if (Array.isArray(scope.volumeIds) && scope.volumeIds.length > 0) {
|
|
7430
|
+
return scope.volumeIds.map((volumeId) => {
|
|
7431
|
+
const volume = this.db.get("SELECT id, title FROM volumes WHERE id = ? AND work_id = ?", volumeId, workId);
|
|
7432
|
+
if (!volume)
|
|
7433
|
+
return { type: "volume", volumeId, missing: true };
|
|
7434
|
+
const chapters = this.db.all("SELECT id, title, version_no FROM chapters WHERE volume_id = ? AND deleted_at IS NULL ORDER BY sort_order, created_at", volumeId);
|
|
7435
|
+
return {
|
|
7436
|
+
type: "volume",
|
|
7437
|
+
volumeId: requiredString(volume, "id"),
|
|
7438
|
+
title: requiredString(volume, "title"),
|
|
7439
|
+
chapters: chapters.map((item) => ({
|
|
7440
|
+
chapterId: requiredString(item, "id"),
|
|
7441
|
+
title: requiredString(item, "title"),
|
|
7442
|
+
versionNo: numberValue(item, "version_no")
|
|
7443
|
+
}))
|
|
7444
|
+
};
|
|
7445
|
+
});
|
|
7446
|
+
}
|
|
7316
7447
|
if (scope.type === "volume" && typeof scope.volumeId === "string") {
|
|
7317
7448
|
const volume = this.db.get("SELECT id, title FROM volumes WHERE id = ? AND work_id = ?", scope.volumeId, workId);
|
|
7318
7449
|
if (!volume)
|