@musnows/scriverse 0.7.4 → 0.7.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 +49 -7
- package/dist/ai.js.map +1 -1
- package/dist/app.js +44 -45
- package/dist/app.js.map +1 -1
- package/dist/epub-export.js +42 -38
- package/dist/epub-export.js.map +1 -1
- package/dist/public/app.js +127 -54
- package/dist/public/index.html +6 -6
- package/dist/public/outline-board.d.ts +16 -10
- package/dist/public/outline-board.js +11 -105
- package/dist/public/reading-preview.d.ts +5 -1
- package/dist/public/reading-preview.js +17 -2
- package/dist/public/styles.css +11 -8
- package/dist/store.js +239 -63
- package/dist/store.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/zip-stream.js +149 -0
- package/dist/zip-stream.js.map +1 -0
- package/package.json +1 -1
|
@@ -6,90 +6,18 @@ function text(value) {
|
|
|
6
6
|
return String(value ?? "");
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
function normalizedSearchText(value) {
|
|
10
|
-
return text(value).normalize("NFKC").trim().toLocaleLowerCase("zh-CN");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function numericOrder(value) {
|
|
14
|
-
const candidate = Number(value);
|
|
15
|
-
return Number.isFinite(candidate) ? candidate : 0;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function stableIdCompare(left, right) {
|
|
19
|
-
return text(left?.id).localeCompare(text(right?.id), "zh-CN");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function treeCompare(left, right) {
|
|
23
|
-
const delta = numericOrder(left?.sortOrder) - numericOrder(right?.sortOrder);
|
|
24
|
-
return delta || stableIdCompare(left, right);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function outlineStatusRank(chapter) {
|
|
28
|
-
if (!chapter?.outline) return 0;
|
|
29
|
-
return { draft: 1, ready: 2, completed: 3 }[chapter.outline.status] ?? 1;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
9
|
function unresolvedForeshadowCount(chapter) {
|
|
33
10
|
return (Array.isArray(chapter?.foreshadows) ? chapter.foreshadows : [])
|
|
34
11
|
.filter((foreshadow) => foreshadow?.status === "planned" || foreshadow?.status === "planted")
|
|
35
12
|
.length;
|
|
36
13
|
}
|
|
37
14
|
|
|
38
|
-
function compareChapters(left, right, sort) {
|
|
39
|
-
if (sort === "status") {
|
|
40
|
-
const delta = outlineStatusRank(left) - outlineStatusRank(right);
|
|
41
|
-
if (delta) return delta;
|
|
42
|
-
}
|
|
43
|
-
if (sort === "foreshadows") {
|
|
44
|
-
const unresolvedDelta = unresolvedForeshadowCount(right) - unresolvedForeshadowCount(left);
|
|
45
|
-
if (unresolvedDelta) return unresolvedDelta;
|
|
46
|
-
const totalDelta = (right?.foreshadows?.length ?? 0) - (left?.foreshadows?.length ?? 0);
|
|
47
|
-
if (totalDelta) return totalDelta;
|
|
48
|
-
}
|
|
49
|
-
if (sort === "title") {
|
|
50
|
-
const delta = text(left?.title).localeCompare(text(right?.title), "zh-CN");
|
|
51
|
-
if (delta) return delta;
|
|
52
|
-
}
|
|
53
|
-
return treeCompare(left, right);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function matchesQuery(chapter, query) {
|
|
57
|
-
if (!query) return true;
|
|
58
|
-
const outline = chapter?.outline ?? {};
|
|
59
|
-
const values = [
|
|
60
|
-
chapter?.title,
|
|
61
|
-
chapter?.chapterType,
|
|
62
|
-
outline.goal,
|
|
63
|
-
outline.conflict,
|
|
64
|
-
outline.turningPoint,
|
|
65
|
-
outline.notes,
|
|
66
|
-
...(Array.isArray(chapter?.foreshadows) ? chapter.foreshadows.map((item) => item?.title) : [])
|
|
67
|
-
];
|
|
68
|
-
return values.some((value) => normalizedSearchText(value).includes(query));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function matchesOutlineStatus(chapter, status) {
|
|
72
|
-
if (status === "all") return true;
|
|
73
|
-
if (status === "empty") return !chapter?.outline;
|
|
74
|
-
return chapter?.outline?.status === status;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function matchesForeshadowStatus(chapter, status) {
|
|
78
|
-
if (status === "all") return true;
|
|
79
|
-
const foreshadows = Array.isArray(chapter?.foreshadows) ? chapter.foreshadows : [];
|
|
80
|
-
if (status === "none") return foreshadows.length === 0;
|
|
81
|
-
if (status === "unresolved") {
|
|
82
|
-
return foreshadows.some((item) => item?.status === "planned" || item?.status === "planted");
|
|
83
|
-
}
|
|
84
|
-
return foreshadows.some((item) => item?.status === status);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
15
|
export function normalizeOutlineBoardState(value = {}) {
|
|
88
16
|
const outlineStatus = text(value?.outlineStatus);
|
|
89
17
|
const foreshadowStatus = text(value?.foreshadowStatus);
|
|
90
18
|
const sort = text(value?.sort);
|
|
91
19
|
return {
|
|
92
|
-
query: text(value?.query),
|
|
20
|
+
query: text(value?.query).slice(0, 200),
|
|
93
21
|
volumeId: text(value?.volumeId),
|
|
94
22
|
outlineStatus: outlineStatuses.has(outlineStatus) ? outlineStatus : "all",
|
|
95
23
|
foreshadowStatus: foreshadowStatuses.has(foreshadowStatus) ? foreshadowStatus : "all",
|
|
@@ -97,39 +25,17 @@ export function normalizeOutlineBoardState(value = {}) {
|
|
|
97
25
|
};
|
|
98
26
|
}
|
|
99
27
|
|
|
100
|
-
|
|
101
|
-
* 保持分卷层级与默认章节树顺序,对章节执行筛选和卷内排序。
|
|
102
|
-
* 未筛选时保留空分卷;指定空分卷时也保留该分组,便于确认数据状态。
|
|
103
|
-
*/
|
|
104
|
-
export function prepareOutlineBoard(board, value = {}) {
|
|
28
|
+
export function outlineBoardRequestPath(workId, value = {}, page = 1, limit = 30) {
|
|
105
29
|
const state = normalizeOutlineBoardState(value);
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
if (volumeFilterActive && text(volume?.id) !== state.volumeId) return [];
|
|
116
|
-
const sourceChapters = Array.isArray(volume?.chapters) ? volume.chapters : [];
|
|
117
|
-
const chapters = sourceChapters
|
|
118
|
-
.filter((chapter) => matchesQuery(chapter, query)
|
|
119
|
-
&& matchesOutlineStatus(chapter, state.outlineStatus)
|
|
120
|
-
&& matchesForeshadowStatus(chapter, state.foreshadowStatus))
|
|
121
|
-
.sort((left, right) => compareChapters(left, right, state.sort));
|
|
122
|
-
const keepEmptyVolume = sourceChapters.length === 0 && (!chapterFilterActive || state.volumeId === text(volume?.id));
|
|
123
|
-
if (chapters.length === 0 && !keepEmptyVolume) return [];
|
|
124
|
-
return [{ ...volume, chapters }];
|
|
125
|
-
});
|
|
126
|
-
return {
|
|
127
|
-
state,
|
|
128
|
-
volumes,
|
|
129
|
-
totalChapterCount,
|
|
130
|
-
visibleChapterCount: volumes.reduce((total, volume) => total + volume.chapters.length, 0),
|
|
131
|
-
filtersActive: chapterFilterActive || volumeFilterActive
|
|
132
|
-
};
|
|
30
|
+
const safePage = Number.isInteger(Number(page)) && Number(page) > 0 ? Number(page) : 1;
|
|
31
|
+
const safeLimit = Number.isInteger(Number(limit)) && Number(limit) >= 1 && Number(limit) <= 100 ? Number(limit) : 30;
|
|
32
|
+
const params = new URLSearchParams({ page: String(safePage), limit: String(safeLimit) });
|
|
33
|
+
if (state.query.trim()) params.set("q", state.query.trim());
|
|
34
|
+
if (state.volumeId) params.set("volumeId", state.volumeId);
|
|
35
|
+
if (state.outlineStatus !== "all") params.set("outlineStatus", state.outlineStatus);
|
|
36
|
+
if (state.foreshadowStatus !== "all") params.set("foreshadowStatus", state.foreshadowStatus);
|
|
37
|
+
if (state.sort !== "tree") params.set("sort", state.sort);
|
|
38
|
+
return `/api/works/${encodeURIComponent(text(workId))}/outline-board?${params.toString()}`;
|
|
133
39
|
}
|
|
134
40
|
|
|
135
41
|
export function outlineBoardUnresolvedCount(chapter) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type ReadingMode = "scroll" | "paged";
|
|
2
|
-
export type ReadingTheme = "paper" | "light" | "dark";
|
|
2
|
+
export type ReadingTheme = "auto" | "paper" | "light" | "dark";
|
|
3
|
+
export type ResolvedReadingTheme = Exclude<ReadingTheme, "auto">;
|
|
3
4
|
export type ReadingPreferences = { mode: ReadingMode; fontSize: number; lineHeight: number; theme: ReadingTheme };
|
|
4
5
|
export type ReadingChapter = {
|
|
5
6
|
id: string;
|
|
@@ -16,8 +17,11 @@ export type ReadingPosition = { chapterId: string; scrollRatio: number; pageInde
|
|
|
16
17
|
|
|
17
18
|
export const READING_PREFERENCES_STORAGE_KEY: string;
|
|
18
19
|
export const READING_POSITION_STORAGE_PREFIX: string;
|
|
20
|
+
export const READING_PREFERENCES_VERSION: number;
|
|
19
21
|
export const DEFAULT_READING_PREFERENCES: Readonly<ReadingPreferences>;
|
|
20
22
|
export function normalizeReadingPreferences(value: unknown): ReadingPreferences;
|
|
23
|
+
export function normalizeStoredReadingPreferences(value: unknown): ReadingPreferences;
|
|
24
|
+
export function resolveReadingTheme(theme: unknown, colorTheme: unknown): ResolvedReadingTheme;
|
|
21
25
|
export function readingPositionStorageKey(workId: unknown): string;
|
|
22
26
|
export function buildReadingChapterSequence(work: unknown): ReadingChapter[];
|
|
23
27
|
export function normalizeReadingPosition(value: unknown, sequence: ReadingChapter[]): ReadingPosition | null;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
export const READING_PREFERENCES_STORAGE_KEY = "scriverse-reading-preferences-v1";
|
|
2
2
|
export const READING_POSITION_STORAGE_PREFIX = "scriverse-reading-position-v1:";
|
|
3
|
+
export const READING_PREFERENCES_VERSION = 2;
|
|
3
4
|
|
|
4
5
|
export const DEFAULT_READING_PREFERENCES = Object.freeze({
|
|
5
6
|
mode: "scroll",
|
|
6
7
|
fontSize: 20,
|
|
7
8
|
lineHeight: 1.9,
|
|
8
|
-
theme: "
|
|
9
|
+
theme: "auto"
|
|
9
10
|
});
|
|
10
11
|
|
|
11
12
|
const readingModes = new Set(["scroll", "paged"]);
|
|
12
13
|
const readingFontSizes = new Set([16, 18, 20, 22, 24]);
|
|
13
14
|
const readingLineHeights = new Set([1.6, 1.8, 1.9, 2, 2.2]);
|
|
14
|
-
const readingThemes = new Set(["paper", "light", "dark"]);
|
|
15
|
+
const readingThemes = new Set(["auto", "paper", "light", "dark"]);
|
|
15
16
|
|
|
16
17
|
function record(value) {
|
|
17
18
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -29,6 +30,20 @@ export function normalizeReadingPreferences(value) {
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
export function normalizeStoredReadingPreferences(value) {
|
|
34
|
+
const candidate = record(value);
|
|
35
|
+
const legacyTheme = candidate.version === READING_PREFERENCES_VERSION
|
|
36
|
+
? candidate.theme
|
|
37
|
+
: candidate.theme === "paper" ? "auto" : candidate.theme;
|
|
38
|
+
return normalizeReadingPreferences({ ...candidate, theme: legacyTheme });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function resolveReadingTheme(theme, colorTheme) {
|
|
42
|
+
const normalized = readingThemes.has(theme) ? theme : DEFAULT_READING_PREFERENCES.theme;
|
|
43
|
+
if (normalized === "auto") return colorTheme === "dark" ? "dark" : "paper";
|
|
44
|
+
return normalized;
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
export function readingPositionStorageKey(workId) {
|
|
33
48
|
return `${READING_POSITION_STORAGE_PREFIX}${encodeURIComponent(String(workId ?? ""))}`;
|
|
34
49
|
}
|
package/dist/public/styles.css
CHANGED
|
@@ -307,10 +307,11 @@
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
* { box-sizing: border-box; }
|
|
310
|
-
* { scrollbar-width:
|
|
311
|
-
*::-webkit-scrollbar { width:
|
|
312
|
-
|
|
313
|
-
.is-scrollbar-visible
|
|
310
|
+
* { scrollbar-width: thin; scrollbar-color: transparent transparent; }
|
|
311
|
+
*::-webkit-scrollbar { width: 8px; }
|
|
312
|
+
*::-webkit-scrollbar-track, *::-webkit-scrollbar-thumb { background: transparent; }
|
|
313
|
+
.is-scrollbar-visible { scrollbar-color: var(--muted) transparent; }
|
|
314
|
+
.is-scrollbar-visible::-webkit-scrollbar-thumb { border-radius: 999px; background: var(--muted); }
|
|
314
315
|
|
|
315
316
|
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; }
|
|
316
317
|
|
|
@@ -1294,8 +1295,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
1294
1295
|
.volume-toggle::before { content: ""; width: 4px; height: 4px; flex: none; border: solid var(--muted); border-width: 0 1px 1px 0; transform: rotate(45deg); margin-right: 8px; }
|
|
1295
1296
|
.volume-node.is-collapsed .volume-toggle::before { transform: rotate(-45deg); }
|
|
1296
1297
|
.volume-toggle span:first-child { flex: 1; }
|
|
1297
|
-
.volume-
|
|
1298
|
-
.volume-export-button[aria-busy="true"] { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); color: var(--accent-dark); opacity: 1; }
|
|
1298
|
+
.volume-detail-button { display: none; }
|
|
1299
1299
|
.chapter-add-button { flex: none; }
|
|
1300
1300
|
.volume-chapters { display: block; }
|
|
1301
1301
|
.volume-node.is-collapsed .volume-chapters { display: none; }
|
|
@@ -3453,6 +3453,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
3453
3453
|
overflow-x: hidden;
|
|
3454
3454
|
overflow-y: hidden;
|
|
3455
3455
|
}
|
|
3456
|
+
.app-shell.ai-hidden-mode:not(.shelf-mode) { grid-template-columns: minmax(0, 1fr); }
|
|
3456
3457
|
.app-shell.shelf-mode {
|
|
3457
3458
|
grid-template-rows: auto minmax(0, 1fr);
|
|
3458
3459
|
min-height: 100dvh;
|
|
@@ -3533,7 +3534,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
|
|
|
3533
3534
|
}
|
|
3534
3535
|
.module-nav .nav-icon { width: 16px; height: 16px; }
|
|
3535
3536
|
.module-nav .module-nav-secondary.hidden { display: none !important; }
|
|
3536
|
-
.panel-heading
|
|
3537
|
+
.panel-heading { display: flex; margin-top: 8px; }
|
|
3538
|
+
#novel-tree { display: block; padding-bottom: 18px; }
|
|
3539
|
+
.volume-detail-button { display: inline-flex; flex: none; align-items: center; min-height: 40px; padding: 7px 9px; font-size: 11px; }
|
|
3537
3540
|
.app-shell.left-panel-collapsed .left-panel-body { padding: 8px var(--mobile-gutter) 0; }
|
|
3538
3541
|
.app-shell.left-panel-collapsed .left-actions { display: block; padding: 0; }
|
|
3539
3542
|
|
|
@@ -4033,7 +4036,7 @@ body { font-size: var(--ui-font-size); }
|
|
|
4033
4036
|
.reader-settings { grid-column: 2; grid-row: 1; }
|
|
4034
4037
|
.reader-settings > summary { min-height: 40px; }
|
|
4035
4038
|
.reader-settings > summary { padding: 7px 10px; }
|
|
4036
|
-
.reader-settings-panel { width: min(250px, calc(100vw - 24px)); }
|
|
4039
|
+
.reader-settings-panel { position: static; width: min(250px, calc(100vw - 24px)); margin-top: 9px; }
|
|
4037
4040
|
.reader-jump-controls { grid-column: 1 / -1; grid-row: 3; grid-template-columns: minmax(0, 1fr) minmax(0, 1.35fr); gap: 7px; }
|
|
4038
4041
|
.reader-jump-controls label:last-child { grid-column: 1 / -1; }
|
|
4039
4042
|
.reader-settings-panel select, .reader-jump-controls select { min-height: 40px; font-size: 10px; }
|