@musnows/scriverse 0.7.2 → 0.7.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.
Files changed (82) hide show
  1. package/README.en.md +3 -0
  2. package/README.md +8 -0
  3. package/dist/ai-connectivity-test.js +109 -0
  4. package/dist/ai-connectivity-test.js.map +1 -0
  5. package/dist/ai-conversation-export.js +70 -0
  6. package/dist/ai-conversation-export.js.map +1 -0
  7. package/dist/ai-stream-timeout.js +18 -0
  8. package/dist/ai-stream-timeout.js.map +1 -0
  9. package/dist/ai.js +926 -354
  10. package/dist/ai.js.map +1 -1
  11. package/dist/app.js +498 -80
  12. package/dist/app.js.map +1 -1
  13. package/dist/attachment-storage.js +28 -12
  14. package/dist/attachment-storage.js.map +1 -1
  15. package/dist/backup-encryption.js +129 -0
  16. package/dist/backup-encryption.js.map +1 -0
  17. package/dist/character-extraction.js +133 -0
  18. package/dist/character-extraction.js.map +1 -0
  19. package/dist/cli-core.js +7 -6
  20. package/dist/cli-core.js.map +1 -1
  21. package/dist/collaboration-presence.js +200 -18
  22. package/dist/collaboration-presence.js.map +1 -1
  23. package/dist/database.js +335 -3
  24. package/dist/database.js.map +1 -1
  25. package/dist/docx-export.js +1 -1
  26. package/dist/docx-export.js.map +1 -1
  27. package/dist/domain.js +18 -0
  28. package/dist/domain.js.map +1 -1
  29. package/dist/epub-export.js +319 -0
  30. package/dist/epub-export.js.map +1 -0
  31. package/dist/hybrid-search.js +8 -0
  32. package/dist/hybrid-search.js.map +1 -1
  33. package/dist/image-metadata.js +17 -2
  34. package/dist/image-metadata.js.map +1 -1
  35. package/dist/presence-store.js +160 -0
  36. package/dist/presence-store.js.map +1 -0
  37. package/dist/public/ai-connectivity-test.d.ts +7 -0
  38. package/dist/public/ai-connectivity-test.js +82 -0
  39. package/dist/public/ai-context-meter.js +27 -0
  40. package/dist/public/ai-mentions.js +14 -0
  41. package/dist/public/ai-request-manager.js +99 -0
  42. package/dist/public/ai-stream-protocol.js +51 -0
  43. package/dist/public/app.js +3587 -559
  44. package/dist/public/background-task-center.js +1 -1
  45. package/dist/public/chapter-editor-virtualization.js +52 -0
  46. package/dist/public/chapter-version-diff.d.ts +20 -0
  47. package/dist/public/chapter-version-diff.js +116 -0
  48. package/dist/public/foreshadow-reminder.d.ts +32 -0
  49. package/dist/public/foreshadow-reminder.js +73 -0
  50. package/dist/public/global-replace-refresh.js +60 -0
  51. package/dist/public/index.html +161 -31
  52. package/dist/public/outline-board.d.ts +61 -0
  53. package/dist/public/outline-board.js +137 -0
  54. package/dist/public/page-route.d.ts +1 -0
  55. package/dist/public/page-route.js +8 -0
  56. package/dist/public/presence-client-id.d.ts +10 -0
  57. package/dist/public/presence-client-id.js +31 -0
  58. package/dist/public/reading-preview.d.ts +32 -0
  59. package/dist/public/reading-preview.js +136 -0
  60. package/dist/public/s3-backup-ui.d.ts +10 -0
  61. package/dist/public/s3-backup-ui.js +29 -0
  62. package/dist/public/setting-filters.d.ts +16 -0
  63. package/dist/public/setting-filters.js +19 -0
  64. package/dist/public/styles.css +522 -11
  65. package/dist/public/upload-progress.d.ts +2 -0
  66. package/dist/public/upload-progress.js +10 -0
  67. package/dist/s3-backup.js +197 -12
  68. package/dist/s3-backup.js.map +1 -1
  69. package/dist/security.js +86 -21
  70. package/dist/security.js.map +1 -1
  71. package/dist/server-runtime.js +38 -18
  72. package/dist/server-runtime.js.map +1 -1
  73. package/dist/store.js +899 -104
  74. package/dist/store.js.map +1 -1
  75. package/dist/upload-limits.js +35 -0
  76. package/dist/upload-limits.js.map +1 -0
  77. package/dist/user-auth.js +56 -10
  78. package/dist/user-auth.js.map +1 -1
  79. package/dist/utils.js +3 -0
  80. package/dist/utils.js.map +1 -1
  81. package/dist/version.js +1 -1
  82. package/package.json +4 -2
@@ -1,5 +1,5 @@
1
1
  const activeTaskStatuses = new Set(["pending", "running"]);
2
- const terminalTaskStatuses = new Set(["review", "completed", "partial", "expired", "cancelled"]);
2
+ const terminalTaskStatuses = new Set(["review", "completed", "partial", "failed", "expired", "cancelled"]);
3
3
 
4
4
  export function backgroundTaskActivityCount(taskPage, relationshipIndex) {
5
5
  const pendingCount = Number(taskPage?.stats?.pendingCount ?? 0);
@@ -0,0 +1,52 @@
1
+ const lineMarker = "\u200b";
2
+
3
+ export function buildChapterLineMirror(value) {
4
+ const lines = String(value ?? "").replace(/\r\n?/gu, "\n").split("\n");
5
+ const offsets = [];
6
+ const parts = [];
7
+ let offset = 0;
8
+ lines.forEach((line, index) => {
9
+ offsets.push(offset);
10
+ parts.push(lineMarker, line);
11
+ offset += lineMarker.length + line.length;
12
+ if (index < lines.length - 1) {
13
+ parts.push("\n");
14
+ offset += 1;
15
+ }
16
+ });
17
+ return { lines, offsets, text: parts.join("") };
18
+ }
19
+
20
+ export function findChapterLineWindow(lineCount, getBounds, viewportStart, viewportEnd) {
21
+ const count = Math.max(0, Math.floor(Number(lineCount) || 0));
22
+ if (count === 0) return { start: 0, end: 0 };
23
+ const startTarget = Number.isFinite(Number(viewportStart)) ? Number(viewportStart) : 0;
24
+ const endTarget = Math.max(startTarget, Number.isFinite(Number(viewportEnd)) ? Number(viewportEnd) : startTarget);
25
+
26
+ let low = 0;
27
+ let high = count - 1;
28
+ let start = count - 1;
29
+ while (low <= high) {
30
+ const middle = Math.floor((low + high) / 2);
31
+ if (getBounds(middle).bottom >= startTarget) {
32
+ start = middle;
33
+ high = middle - 1;
34
+ } else {
35
+ low = middle + 1;
36
+ }
37
+ }
38
+
39
+ low = start;
40
+ high = count - 1;
41
+ let end = start;
42
+ while (low <= high) {
43
+ const middle = Math.floor((low + high) / 2);
44
+ if (getBounds(middle).top <= endTarget) {
45
+ end = middle;
46
+ low = middle + 1;
47
+ } else {
48
+ high = middle - 1;
49
+ }
50
+ }
51
+ return { start, end: end + 1 };
52
+ }
@@ -0,0 +1,20 @@
1
+ export type ChapterDiffRow =
2
+ | { type: "equal"; before: string; after: string; beforeLine: number; afterLine: number }
3
+ | { type: "added"; after: string; afterLine: number }
4
+ | { type: "deleted"; before: string; beforeLine: number }
5
+ | { type: "modified"; before: string; after: string; beforeLine: number; afterLine: number };
6
+
7
+ export type ChapterDiffSummary = {
8
+ added: number;
9
+ deleted: number;
10
+ modified: number;
11
+ unchanged: number;
12
+ };
13
+
14
+ export function diffChapterLines(
15
+ beforeContent: unknown,
16
+ afterContent: unknown,
17
+ matrixCellLimit?: number
18
+ ): ChapterDiffRow[];
19
+
20
+ export function chapterDiffSummary(rows: ChapterDiffRow[]): ChapterDiffSummary;
@@ -0,0 +1,116 @@
1
+ const DEFAULT_MATRIX_CELL_LIMIT = 2_000_000;
2
+
3
+ function chapterLines(content) {
4
+ return String(content ?? "").replace(/\r\n?/gu, "\n").split("\n");
5
+ }
6
+
7
+ function positionalDiff(beforeLines, afterLines) {
8
+ const rows = [];
9
+ const length = Math.max(beforeLines.length, afterLines.length);
10
+ for (let index = 0; index < length; index += 1) {
11
+ const before = beforeLines[index];
12
+ const after = afterLines[index];
13
+ if (before === undefined) rows.push({ type: "added", after, afterLine: index + 1 });
14
+ else if (after === undefined) rows.push({ type: "deleted", before, beforeLine: index + 1 });
15
+ else if (before === after) rows.push({ type: "equal", before, after, beforeLine: index + 1, afterLine: index + 1 });
16
+ else rows.push({ type: "modified", before, after, beforeLine: index + 1, afterLine: index + 1 });
17
+ }
18
+ return rows;
19
+ }
20
+
21
+ function rawLineDiff(beforeLines, afterLines, matrixCellLimit) {
22
+ const rows = beforeLines.length + 1;
23
+ const columns = afterLines.length + 1;
24
+ if (rows * columns > matrixCellLimit) return null;
25
+ const matrix = Array.from({ length: rows }, () => new Uint32Array(columns));
26
+ for (let beforeIndex = beforeLines.length - 1; beforeIndex >= 0; beforeIndex -= 1) {
27
+ for (let afterIndex = afterLines.length - 1; afterIndex >= 0; afterIndex -= 1) {
28
+ matrix[beforeIndex][afterIndex] = beforeLines[beforeIndex] === afterLines[afterIndex]
29
+ ? matrix[beforeIndex + 1][afterIndex + 1] + 1
30
+ : Math.max(matrix[beforeIndex + 1][afterIndex], matrix[beforeIndex][afterIndex + 1]);
31
+ }
32
+ }
33
+
34
+ const diff = [];
35
+ let beforeIndex = 0;
36
+ let afterIndex = 0;
37
+ while (beforeIndex < beforeLines.length || afterIndex < afterLines.length) {
38
+ if (beforeIndex < beforeLines.length && afterIndex < afterLines.length
39
+ && beforeLines[beforeIndex] === afterLines[afterIndex]) {
40
+ diff.push({ type: "equal", text: beforeLines[beforeIndex], beforeLine: beforeIndex + 1, afterLine: afterIndex + 1 });
41
+ beforeIndex += 1;
42
+ afterIndex += 1;
43
+ } else if (afterIndex < afterLines.length
44
+ && (beforeIndex >= beforeLines.length || matrix[beforeIndex][afterIndex + 1] >= matrix[beforeIndex + 1][afterIndex])) {
45
+ diff.push({ type: "added", text: afterLines[afterIndex], afterLine: afterIndex + 1 });
46
+ afterIndex += 1;
47
+ } else {
48
+ diff.push({ type: "deleted", text: beforeLines[beforeIndex], beforeLine: beforeIndex + 1 });
49
+ beforeIndex += 1;
50
+ }
51
+ }
52
+ return diff;
53
+ }
54
+
55
+ function pairChangedLines(changes) {
56
+ const deleted = changes.filter((row) => row.type === "deleted");
57
+ const added = changes.filter((row) => row.type === "added");
58
+ const paired = [];
59
+ const pairCount = Math.min(deleted.length, added.length);
60
+ for (let index = 0; index < pairCount; index += 1) {
61
+ paired.push({
62
+ type: "modified",
63
+ before: deleted[index].text,
64
+ after: added[index].text,
65
+ beforeLine: deleted[index].beforeLine,
66
+ afterLine: added[index].afterLine
67
+ });
68
+ }
69
+ for (let index = pairCount; index < deleted.length; index += 1) {
70
+ paired.push({ type: "deleted", before: deleted[index].text, beforeLine: deleted[index].beforeLine });
71
+ }
72
+ for (let index = pairCount; index < added.length; index += 1) {
73
+ paired.push({ type: "added", after: added[index].text, afterLine: added[index].afterLine });
74
+ }
75
+ return paired;
76
+ }
77
+
78
+ export function diffChapterLines(beforeContent, afterContent, matrixCellLimit = DEFAULT_MATRIX_CELL_LIMIT) {
79
+ const beforeLines = chapterLines(beforeContent);
80
+ const afterLines = chapterLines(afterContent);
81
+ const raw = rawLineDiff(beforeLines, afterLines, matrixCellLimit);
82
+ if (!raw) return positionalDiff(beforeLines, afterLines);
83
+
84
+ const result = [];
85
+ let index = 0;
86
+ while (index < raw.length) {
87
+ if (raw[index].type === "equal") {
88
+ result.push({
89
+ type: "equal",
90
+ before: raw[index].text,
91
+ after: raw[index].text,
92
+ beforeLine: raw[index].beforeLine,
93
+ afterLine: raw[index].afterLine
94
+ });
95
+ index += 1;
96
+ continue;
97
+ }
98
+ const changes = [];
99
+ while (index < raw.length && raw[index].type !== "equal") {
100
+ changes.push(raw[index]);
101
+ index += 1;
102
+ }
103
+ result.push(...pairChangedLines(changes));
104
+ }
105
+ return result;
106
+ }
107
+
108
+ export function chapterDiffSummary(rows) {
109
+ return rows.reduce((summary, row) => {
110
+ if (row.type === "added") summary.added += 1;
111
+ if (row.type === "deleted") summary.deleted += 1;
112
+ if (row.type === "modified") summary.modified += 1;
113
+ if (row.type === "equal") summary.unchanged += 1;
114
+ return summary;
115
+ }, { added: 0, deleted: 0, modified: 0, unchanged: 0 });
116
+ }
@@ -0,0 +1,32 @@
1
+ export type ForeshadowReminder = {
2
+ foreshadowId: string;
3
+ occurrenceId: string;
4
+ title: string;
5
+ description: string;
6
+ status: "planned" | "planted";
7
+ importance: "low" | "medium" | "high";
8
+ role: "reminder" | "payoff";
9
+ note: string;
10
+ versionNo: number;
11
+ updatedAt: string;
12
+ };
13
+
14
+ export const FORESHADOW_REMINDER_SNOOZE_STORAGE_KEY: string;
15
+ export function normalizeForeshadowReminders(value: unknown): ForeshadowReminder[];
16
+ export function foreshadowReminderSnoozeKey(
17
+ workId: unknown,
18
+ chapterId: unknown,
19
+ reminder: Partial<ForeshadowReminder> | null | undefined
20
+ ): string | null;
21
+ export function parseForeshadowReminderSnoozes(serialized: unknown): Set<string>;
22
+ export function serializeForeshadowReminderSnoozes(snoozes: ReadonlySet<string>, limit?: number): string;
23
+ export function visibleForeshadowReminders(
24
+ reminders: unknown,
25
+ workId: unknown,
26
+ chapterId: unknown,
27
+ snoozes: ReadonlySet<string>
28
+ ): ForeshadowReminder[];
29
+ export function foreshadowReminderRequestTargetsState(
30
+ request: { workId: unknown; chapterId: unknown } | null | undefined,
31
+ current: { workId?: unknown; chapterId?: unknown } | null | undefined
32
+ ): boolean;
@@ -0,0 +1,73 @@
1
+ export const FORESHADOW_REMINDER_SNOOZE_STORAGE_KEY = "scriverse.foreshadow-reminder-snoozes.v1";
2
+
3
+ function normalizedId(value) {
4
+ const normalized = String(value ?? "").trim();
5
+ return normalized || null;
6
+ }
7
+
8
+ export function normalizeForeshadowReminders(value) {
9
+ if (!Array.isArray(value)) return [];
10
+ return value.flatMap((item) => {
11
+ if (!item || typeof item !== "object" || Array.isArray(item)) return [];
12
+ const foreshadowId = normalizedId(item.foreshadowId);
13
+ const occurrenceId = normalizedId(item.occurrenceId);
14
+ const title = String(item.title ?? "").trim();
15
+ const role = item.role === "payoff" ? "payoff" : item.role === "reminder" ? "reminder" : null;
16
+ const versionNo = Number(item.versionNo);
17
+ if (!foreshadowId || !occurrenceId || !title || !role || !Number.isInteger(versionNo) || versionNo < 1) return [];
18
+ return [{
19
+ foreshadowId,
20
+ occurrenceId,
21
+ title,
22
+ description: String(item.description ?? ""),
23
+ status: item.status === "planned" ? "planned" : "planted",
24
+ importance: ["low", "medium", "high"].includes(item.importance) ? item.importance : "medium",
25
+ role,
26
+ note: String(item.note ?? ""),
27
+ versionNo,
28
+ updatedAt: String(item.updatedAt ?? "")
29
+ }];
30
+ });
31
+ }
32
+
33
+ export function foreshadowReminderSnoozeKey(workId, chapterId, reminder) {
34
+ const parts = [
35
+ normalizedId(workId),
36
+ normalizedId(chapterId),
37
+ normalizedId(reminder?.foreshadowId),
38
+ normalizedId(reminder?.occurrenceId)
39
+ ];
40
+ const versionNo = Number(reminder?.versionNo);
41
+ if (parts.some((part) => !part) || !Number.isInteger(versionNo) || versionNo < 1) return null;
42
+ return [...parts, `v${versionNo}`].map((part) => encodeURIComponent(part)).join("|");
43
+ }
44
+
45
+ export function parseForeshadowReminderSnoozes(serialized) {
46
+ try {
47
+ const value = JSON.parse(String(serialized ?? "[]"));
48
+ return new Set(Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length <= 1000) : []);
49
+ } catch {
50
+ return new Set();
51
+ }
52
+ }
53
+
54
+ export function serializeForeshadowReminderSnoozes(snoozes, limit = 500) {
55
+ const safeLimit = Number.isInteger(limit) && limit > 0 ? limit : 500;
56
+ const values = [...snoozes].filter((item) => typeof item === "string" && item.length <= 1000);
57
+ return JSON.stringify(values.slice(-safeLimit));
58
+ }
59
+
60
+ export function visibleForeshadowReminders(reminders, workId, chapterId, snoozes) {
61
+ return normalizeForeshadowReminders(reminders).filter((reminder) => {
62
+ const key = foreshadowReminderSnoozeKey(workId, chapterId, reminder);
63
+ return key && !snoozes.has(key);
64
+ });
65
+ }
66
+
67
+ export function foreshadowReminderRequestTargetsState(request, current) {
68
+ return Boolean(
69
+ request
70
+ && normalizedId(request.workId) === normalizedId(current?.workId)
71
+ && normalizedId(request.chapterId) === normalizedId(current?.chapterId)
72
+ );
73
+ }
@@ -0,0 +1,60 @@
1
+ function normalizeId(value) {
2
+ const id = String(value ?? "").trim();
3
+ return id || null;
4
+ }
5
+
6
+ function collapsedIds(value) {
7
+ if (value instanceof Set) return value;
8
+ return new Set(Array.isArray(value) ? value.map(normalizeId).filter(Boolean) : []);
9
+ }
10
+
11
+ function normalizedChapterCount(value) {
12
+ if (value === null || value === undefined || value === "") return null;
13
+ const count = Number(value);
14
+ return Number.isSafeInteger(count) && count >= 0 ? count : null;
15
+ }
16
+
17
+ export function resolveGlobalReplaceChapterCount(volume, previousVolume) {
18
+ const responseCount = normalizedChapterCount(volume?.chapterCount);
19
+ if (responseCount !== null) return responseCount;
20
+ if (Array.isArray(volume?.chapters)) return volume.chapters.length;
21
+ const previousCount = normalizedChapterCount(previousVolume?.chapterCount);
22
+ if (previousCount !== null) return previousCount;
23
+ return Array.isArray(previousVolume?.chapters) ? previousVolume.chapters.length : 0;
24
+ }
25
+
26
+ export function buildGlobalReplaceRefreshPlan({
27
+ volumes = [],
28
+ collapsedVolumeIds = [],
29
+ selectedChapterId = null,
30
+ selectedChapterVolumeId = null,
31
+ routeChapterId = null,
32
+ scope = "",
33
+ chapterCount = 0,
34
+ settingCount = 0
35
+ } = {}) {
36
+ const volumeList = Array.isArray(volumes) ? volumes : [];
37
+ const collapsed = collapsedIds(collapsedVolumeIds);
38
+ const chapterId = normalizeId(selectedChapterId) ?? normalizeId(routeChapterId);
39
+ const chapterVolumeId = normalizeId(selectedChapterVolumeId)
40
+ ?? normalizeId(volumeList.find((volume) => Array.isArray(volume?.chapters)
41
+ && volume.chapters.some((chapter) => normalizeId(chapter?.id) === chapterId))?.id);
42
+ const proseChanged = (scope === "prose" || scope === "prose-and-settings") && Number(chapterCount) > 0;
43
+ const settingsChanged = (scope === "settings" || scope === "prose-and-settings") && Number(settingCount) > 0;
44
+ const expandedVolumeIds = volumeList
45
+ .filter((volume) => !collapsed.has(volume?.id))
46
+ .map((volume) => normalizeId(volume?.id))
47
+ .filter(Boolean);
48
+ const reloadVolumeIds = proseChanged
49
+ ? [...new Set([...expandedVolumeIds, chapterVolumeId].filter(Boolean))]
50
+ : [];
51
+
52
+ return {
53
+ proseChanged,
54
+ settingsChanged,
55
+ selectedChapterId: chapterId,
56
+ selectedChapterVolumeId: chapterVolumeId,
57
+ expandedVolumeIds,
58
+ reloadVolumeIds
59
+ };
60
+ }