@waterwx/dsh-novel-forge 2.0.0-alpha.1 → 2.0.0
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.md +286 -285
- package/lib/client.js +19561 -19439
- package/lib/index.js +8159 -4456
- package/lib/types/action-guard.d.ts +61 -0
- package/lib/types/ai-scan.d.ts +0 -6
- package/lib/types/assets.d.ts +20 -9
- package/lib/types/assistant.d.ts +29 -0
- package/lib/types/author-assets.d.ts +1 -3
- package/lib/types/bookshelf.d.ts +13 -3
- package/lib/types/client/api.d.ts +136 -27
- package/lib/types/client/chip-prefs.d.ts +36 -0
- package/lib/types/client/docx.d.ts +2 -2
- package/lib/types/client/llmLive.d.ts +31 -0
- package/lib/types/client/locales.d.ts +15 -70
- package/lib/types/client/mount.d.ts +0 -2
- package/lib/types/client/panel/AiTag.d.ts +1 -0
- package/lib/types/client/panel/AssistantTab.d.ts +4 -1
- package/lib/types/client/panel/AuthorHome.d.ts +13 -11
- package/lib/types/client/panel/BookAnalysisView.d.ts +4 -0
- package/lib/types/client/panel/CmdPalette.d.ts +12 -0
- package/lib/types/client/panel/CreateBookView.d.ts +5 -1
- package/lib/types/client/panel/DirectorView.d.ts +7 -0
- package/lib/types/client/panel/IdeaInspirationView.d.ts +6 -0
- package/lib/types/client/panel/KnowledgeBaseView.d.ts +4 -0
- package/lib/types/client/panel/LiveFeedLog.d.ts +13 -0
- package/lib/types/client/panel/MarketRadarView.d.ts +5 -0
- package/lib/types/client/panel/ModelManager.d.ts +2 -2
- package/lib/types/client/panel/NovelPanel.d.ts +6 -1
- package/lib/types/client/panel/PageHeader.d.ts +18 -0
- package/lib/types/client/panel/PromptSlotsView.d.ts +11 -0
- package/lib/types/client/panel/SettingsView.d.ts +12 -9
- package/lib/types/client/panel/ShelfView.d.ts +10 -1
- package/lib/types/client/panel/SubPage.d.ts +27 -0
- package/lib/types/client/panel/TensionView.d.ts +18 -0
- package/lib/types/client/panel/TimelineView.d.ts +18 -0
- package/lib/types/client/panel/helpers.d.ts +5 -3
- package/lib/types/client/panel/market-radar-state.d.ts +56 -0
- package/lib/types/client/panel/toast.d.ts +2 -0
- package/lib/types/client/panel/views.d.ts +21 -35
- package/lib/types/client/run-status.d.ts +21 -0
- package/lib/types/client/sidebar-entry.d.ts +0 -2
- package/lib/types/docx-core.d.ts +16 -0
- package/lib/types/docx.d.ts +2 -3
- package/lib/types/engine.d.ts +78 -103
- package/lib/types/global-assets.d.ts +6 -0
- package/lib/types/home.d.ts +11 -0
- package/lib/types/index.d.ts +26 -12
- package/lib/types/llm-live.d.ts +70 -0
- package/lib/types/llm-retry.d.ts +38 -0
- package/lib/types/market-radar-scan.d.ts +18 -0
- package/lib/types/market-radar-sources.d.ts +31 -0
- package/lib/types/migrate-output.d.ts +25 -0
- package/lib/types/model-capability.d.ts +93 -0
- package/lib/types/output-budget.d.ts +86 -0
- package/lib/types/prompt-slots.d.ts +57 -0
- package/lib/types/prompting.d.ts +10 -0
- package/lib/types/protocol.d.ts +816 -601
- package/lib/types/revision.d.ts +76 -0
- package/lib/types/routes.d.ts +9 -3
- package/lib/types/run.d.ts +23 -1
- package/lib/types/settings-persist.d.ts +59 -0
- package/lib/types/snapshots.d.ts +46 -0
- package/lib/types/stage-contract.d.ts +58 -0
- package/lib/types/tension.d.ts +53 -0
- package/lib/types/timeline.d.ts +37 -0
- package/package.json +50 -25
- package/src/action-guard.ts +127 -0
- package/src/ai-scan.ts +0 -24
- package/src/assets.ts +1801 -879
- package/src/assistant.ts +1098 -770
- package/src/author-assets.ts +131 -147
- package/src/bookshelf.ts +193 -167
- package/src/client/api.ts +767 -617
- package/src/client/chip-prefs.ts +121 -0
- package/src/client/docx.ts +32 -69
- package/src/client/index.ts +2 -0
- package/src/client/llmLive.ts +111 -0
- package/src/client/locales.ts +15 -274
- package/src/client/mount.tsx +3 -2
- package/src/client/panel/AdaptModeView.tsx +535 -520
- package/src/client/panel/AiTag.tsx +7 -0
- package/src/client/panel/AssetsTab.tsx +448 -458
- package/src/client/panel/AssistantTab.tsx +355 -301
- package/src/client/panel/AuthorAssetsView.tsx +205 -207
- package/src/client/panel/AuthorHome.tsx +309 -204
- package/src/client/panel/BookAnalysisView.tsx +63 -0
- package/src/client/panel/CmdPalette.tsx +102 -0
- package/src/client/panel/CreateBookView.tsx +366 -352
- package/src/client/panel/DirectorView.tsx +200 -0
- package/src/client/panel/GlobalAssetLibraryView.tsx +168 -172
- package/src/client/panel/IdeaInspirationView.tsx +76 -0
- package/src/client/panel/ImportModal.tsx +302 -302
- package/src/client/panel/KnowledgeBaseView.tsx +125 -0
- package/src/client/panel/LiveFeedLog.tsx +204 -0
- package/src/client/panel/MarketRadarView.tsx +415 -0
- package/src/client/panel/ModelManager.tsx +205 -205
- package/src/client/panel/NovelPanel.tsx +3144 -2205
- package/src/client/panel/PageHeader.tsx +37 -0
- package/src/client/panel/ProgressConsole.tsx +63 -64
- package/src/client/panel/PromptSlotsView.tsx +97 -0
- package/src/client/panel/ReaderView.tsx +306 -306
- package/src/client/panel/RunPanel.tsx +56 -24
- package/src/client/panel/SettingsView.tsx +175 -106
- package/src/client/panel/ShelfView.tsx +63 -39
- package/src/client/panel/SubPage.tsx +42 -0
- package/src/client/panel/TensionView.tsx +212 -0
- package/src/client/panel/TimelineView.tsx +208 -0
- package/src/client/panel/WorldTab.tsx +202 -202
- package/src/client/panel/helpers.ts +7 -5
- package/src/client/panel/market-radar-state.ts +79 -0
- package/src/client/panel/panel.module.css +5767 -4360
- package/src/client/panel/toast.ts +51 -0
- package/src/client/panel/views.tsx +316 -314
- package/src/client/run-status.ts +171 -0
- package/src/client/sidebar-entry.ts +3 -3
- package/src/docx-core.ts +53 -0
- package/src/docx.ts +44 -83
- package/src/engine.ts +1197 -1638
- package/src/global-assets.ts +58 -0
- package/src/home.ts +33 -0
- package/src/index.ts +181 -101
- package/src/llm-live.ts +245 -0
- package/src/llm-retry.ts +77 -0
- package/src/market-radar-scan.ts +35 -0
- package/src/market-radar-sources.ts +173 -0
- package/src/migrate-output.ts +76 -0
- package/src/model-capability.ts +215 -0
- package/src/novel-context.ts +286 -287
- package/src/output-budget.ts +147 -0
- package/src/prompt-slots.ts +125 -0
- package/src/prompting.ts +26 -0
- package/src/protocol.ts +835 -614
- package/src/revision.ts +283 -0
- package/src/routes.ts +1286 -910
- package/src/run.ts +113 -17
- package/src/settings-persist.ts +114 -0
- package/src/snapshots.ts +174 -0
- package/src/stage-contract.ts +186 -0
- package/src/tension.ts +251 -0
- package/src/timeline.ts +159 -0
- package/assets/styles/arcane-thick-3d.webp +0 -0
- package/assets/styles/chinese-ink.webp +0 -0
- package/assets/styles/clay-stopmotion-3d.webp +0 -0
- package/assets/styles/dark-ghibli.webp +0 -0
- package/assets/styles/dark-gothic-3d.webp +0 -0
- package/assets/styles/film-photo.webp +0 -0
- package/assets/styles/guofeng-heavy.webp +0 -0
- package/assets/styles/guoman-2d-gufeng.webp +0 -0
- package/assets/styles/guoman-3d-tianyuan.webp +0 -0
- package/assets/styles/guoman-3d-xuanhuan.webp +0 -0
- package/assets/styles/horror-game-3d.webp +0 -0
- package/assets/styles/hyperreal-3d.webp +0 -0
- package/assets/styles/japan-cel-dark.webp +0 -0
- package/assets/styles/korean-webtoon.webp +0 -0
- package/assets/styles/low-cyberpunk-3d.webp +0 -0
- package/assets/styles/nextgen-cg.webp +0 -0
- package/assets/styles/noir-film.webp +0 -0
- package/assets/styles/openworld-survival.webp +0 -0
- package/assets/styles/paper-cut-3d.webp +0 -0
- package/assets/styles/pixar-adult-3d.webp +0 -0
- package/assets/styles/pop-marvel-2d.webp +0 -0
- package/assets/styles/sandiao-qban.webp +0 -0
- package/assets/styles/shinkai-anime.webp +0 -0
- package/assets/styles/tactical-stealth.webp +0 -0
- package/assets/styles/urban-lightreal-3d.webp +0 -0
- package/assets/styles/vaporwave-3d.webp +0 -0
- package/assets/styles/vhs90.webp +0 -0
- package/lib/types/client/panel/BookshelfBar.d.ts +0 -11
- package/lib/types/client/panel/FlowGuide.d.ts +0 -22
- package/lib/types/client/panel/MangaRoleLibrary.d.ts +0 -20
- package/lib/types/client/panel/MangaWorkspace.d.ts +0 -12
- package/lib/types/client/panel/ModelPicker.d.ts +0 -11
- package/lib/types/client/panel/ProgressHomeView.d.ts +0 -5
- package/lib/types/client/panel/PropLibrary.d.ts +0 -8
- package/lib/types/client/panel/SceneLibrary.d.ts +0 -16
- package/lib/types/client/panel/StoryboardTab.d.ts +0 -19
- package/lib/types/client/panel/StyleCard.d.ts +0 -8
- package/lib/types/client/panel/StyleGallery.d.ts +0 -1
- package/lib/types/comic-presets.d.ts +0 -12
- package/lib/types/manga-genre-rules.d.ts +0 -22
- package/lib/types/prompt-utils.d.ts +0 -35
- package/lib/types/review-policy.d.ts +0 -97
- package/lib/types/shot-language.d.ts +0 -46
- package/lib/types/story-beat-language.d.ts +0 -35
- package/lib/types/style-library.d.ts +0 -33
- package/skills/chapter-batch/SKILL.md +0 -81
- package/src/client/mount.tsx._new +0 -97
- package/src/client/mount.tsx._nf +0 -97
- package/src/client/mount.tsx._tmp +0 -97
- package/src/client/panel/BookshelfBar.tsx +0 -170
- package/src/client/panel/FlowGuide.tsx +0 -84
- package/src/client/panel/MangaRoleLibrary.tsx +0 -1002
- package/src/client/panel/MangaWorkspace.tsx +0 -715
- package/src/client/panel/ProgressHomeView.tsx +0 -87
- package/src/client/panel/PropLibrary.tsx +0 -175
- package/src/client/panel/SceneLibrary.tsx +0 -453
- package/src/client/panel/StoryboardTab.tsx +0 -472
- package/src/client/panel/StyleCard.tsx +0 -90
- package/src/client/panel/StyleGallery.tsx +0 -58
- package/src/comic-presets.ts +0 -67
- package/src/manga-genre-rules.ts +0 -87
- package/src/prompt-utils.ts +0 -73
- package/src/review-policy.ts +0 -161
- package/src/shot-language.ts +0 -181
- package/src/story-beat-language.ts +0 -130
- package/src/style-library.ts +0 -84
package/src/bookshelf.ts
CHANGED
|
@@ -1,167 +1,193 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 书架(Bookshelf)— 多书管理:一本书记录一个独立输出目录。
|
|
3
|
-
* 状态持久化到
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
/** 书架配置文件路径。 */
|
|
14
|
-
export function bookshelfFile(): string {
|
|
15
|
-
return
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface BookshelfStore {
|
|
19
|
-
books: BookEntry[]
|
|
20
|
-
activeBookId: string | null
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function defaultStore(): BookshelfStore {
|
|
24
|
-
return { books: [], activeBookId: null }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** 读取书架(无则返回空)。 */
|
|
28
|
-
export function loadBookshelf(): BookshelfStore {
|
|
29
|
-
const file = bookshelfFile()
|
|
30
|
-
if (!existsSync(file)) return defaultStore()
|
|
31
|
-
try {
|
|
32
|
-
let raw = readFileSync(file, 'utf8')
|
|
33
|
-
if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1)
|
|
34
|
-
const parsed = JSON.parse(raw) as BookshelfStore
|
|
35
|
-
if (!Array.isArray(parsed.books)) return defaultStore()
|
|
36
|
-
return { books: parsed.books, activeBookId: parsed.activeBookId ?? null }
|
|
37
|
-
} catch {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
book
|
|
92
|
-
book
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
store.
|
|
139
|
-
book
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
store.books.
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 书架(Bookshelf)— 多书管理:一本书记录一个独立输出目录。
|
|
3
|
+
* 状态持久化到 <DSH_HOME>/dsh-novel-forge-bookshelf.json(跟随 dsh 配置惯例)。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
7
|
+
import { join } from 'node:path'
|
|
8
|
+
import { randomBytes } from 'node:crypto'
|
|
9
|
+
import type { BookEntry, BookshelfSnapshot } from './protocol.ts'
|
|
10
|
+
import { loadProject } from './engine.ts'
|
|
11
|
+
import { dshHome, dshHomePath } from './home.ts'
|
|
12
|
+
|
|
13
|
+
/** 书架配置文件路径。 */
|
|
14
|
+
export function bookshelfFile(): string {
|
|
15
|
+
return dshHomePath('dsh-novel-forge-bookshelf.json')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface BookshelfStore {
|
|
19
|
+
books: BookEntry[]
|
|
20
|
+
activeBookId: string | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function defaultStore(): BookshelfStore {
|
|
24
|
+
return { books: [], activeBookId: null }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 读取书架(无则返回空)。 */
|
|
28
|
+
export function loadBookshelf(): BookshelfStore {
|
|
29
|
+
const file = bookshelfFile()
|
|
30
|
+
if (!existsSync(file)) return defaultStore()
|
|
31
|
+
try {
|
|
32
|
+
let raw = readFileSync(file, 'utf8')
|
|
33
|
+
if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1)
|
|
34
|
+
const parsed = JSON.parse(raw) as BookshelfStore
|
|
35
|
+
if (!Array.isArray(parsed.books)) return defaultStore()
|
|
36
|
+
return { books: parsed.books, activeBookId: parsed.activeBookId ?? null }
|
|
37
|
+
} catch (error) {
|
|
38
|
+
// 书架文件损坏回退默认值会遮蔽数据丢失,留痕到服务端日志。
|
|
39
|
+
console.error(`[novel-forge] bookshelf file unreadable/corrupt: ${file} — ${(error as Error).message}`)
|
|
40
|
+
return defaultStore()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 持久化书架。 */
|
|
45
|
+
function saveBookshelf(store: BookshelfStore): void {
|
|
46
|
+
const file = bookshelfFile()
|
|
47
|
+
mkdirSync(dshHome(), { recursive: true })
|
|
48
|
+
writeFileSync(file, JSON.stringify(store, null, 2), 'utf8')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 当前激活的书。 */
|
|
52
|
+
export function activeBook(store: BookshelfStore): BookEntry | undefined {
|
|
53
|
+
return store.books.find(b => b.id === store.activeBookId)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 书架快照(含每本书的进度摘要)。 */
|
|
57
|
+
export function bookshelfSnapshot(store: BookshelfStore): BookshelfSnapshot {
|
|
58
|
+
return {
|
|
59
|
+
books: store.books.map(book => {
|
|
60
|
+
const project = loadProject(book.outputDir)
|
|
61
|
+
const done = project === undefined ? 0 : project.chapters.filter(c => c.status === 'approved' || c.status === 'written' || c.status === 'rejected').length
|
|
62
|
+
const hasCover = project?.coverPath !== undefined && project.coverPath !== '' && existsSync(join(book.outputDir, project.coverPath))
|
|
63
|
+
return {
|
|
64
|
+
...book,
|
|
65
|
+
done,
|
|
66
|
+
total: project?.chapters.length ?? 0,
|
|
67
|
+
hasProject: project !== undefined,
|
|
68
|
+
hasCover,
|
|
69
|
+
blurb: project?.blurb,
|
|
70
|
+
}
|
|
71
|
+
}),
|
|
72
|
+
activeBookId: store.activeBookId,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** 新建一本书(自动成为当前书)。 */
|
|
77
|
+
export function createBook(bookName: string, outputDir: string): BookEntry {
|
|
78
|
+
const store = loadBookshelf()
|
|
79
|
+
const id = `book-${Date.now().toString(36)}-${randomBytes(3).toString('hex')}`
|
|
80
|
+
const now = new Date().toISOString()
|
|
81
|
+
const book: BookEntry = { id, bookName, outputDir, createdAt: now, updatedAt: now }
|
|
82
|
+
store.books.push(book)
|
|
83
|
+
store.activeBookId = id
|
|
84
|
+
saveBookshelf(store)
|
|
85
|
+
return book
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** 更新某本书的书名(开书向导导入大纲后书名以大纲首行为准)。 */
|
|
89
|
+
export function renameBook(id: string, bookName: string): boolean {
|
|
90
|
+
const store = loadBookshelf()
|
|
91
|
+
const book = store.books.find(b => b.id === id)
|
|
92
|
+
if (book === undefined) return false
|
|
93
|
+
book.bookName = bookName
|
|
94
|
+
book.updatedAt = new Date().toISOString()
|
|
95
|
+
saveBookshelf(store)
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 播种:书架为空时,把指定输出目录下已有的项目自动登记为第一本书。
|
|
101
|
+
* 兼容升级场景 —— 旧版插件直接在输出目录写项目,从未登记书架。
|
|
102
|
+
* @param outputDir - 候选输出目录(通常为 settings 的默认输出目录)。
|
|
103
|
+
* @returns 是否发生了播种。
|
|
104
|
+
*/
|
|
105
|
+
export function seedBookshelfFromOutputDir(outputDir: string): boolean {
|
|
106
|
+
const store = loadBookshelf()
|
|
107
|
+
if (store.books.length > 0) return false
|
|
108
|
+
if (!existsSync(outputDir)) return false
|
|
109
|
+
// 有项目文件,或至少有章节文件,才视为"已有的书"。
|
|
110
|
+
const hasProject = existsSync(join(outputDir, 'novel-project.json'))
|
|
111
|
+
const hasChapters = existsSync(outputDir)
|
|
112
|
+
if (!hasProject && !hasChapters) return false
|
|
113
|
+
const project = loadProject(outputDir)
|
|
114
|
+
const bookName = project?.bookName ?? outputDir.split(/[\\/]/).pop() ?? '未命名小说'
|
|
115
|
+
createBook(bookName, outputDir)
|
|
116
|
+
return true
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 导入已有项目目录到书架:校验 novel-project.json,已存在则直接激活。 */
|
|
120
|
+
export function importDir(outputDir: string): { book: BookEntry; existed: boolean } {
|
|
121
|
+
const project = loadProject(outputDir)
|
|
122
|
+
if (project === undefined) throw new Error(`目录中未找到有效的 novel-project.json:${outputDir}`)
|
|
123
|
+
const store = loadBookshelf()
|
|
124
|
+
const existed = store.books.find(b => b.outputDir === outputDir)
|
|
125
|
+
if (existed !== undefined) {
|
|
126
|
+
store.activeBookId = existed.id
|
|
127
|
+
saveBookshelf(store)
|
|
128
|
+
return { book: existed, existed: true }
|
|
129
|
+
}
|
|
130
|
+
const bookName = project.bookName !== '' ? project.bookName : (outputDir.split(/[\\/]/).pop() ?? '未命名小说')
|
|
131
|
+
const book = createBook(bookName, outputDir)
|
|
132
|
+
return { book, existed: false }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 激活一本书。 */
|
|
136
|
+
export function activateBook(id: string): BookEntry | undefined {
|
|
137
|
+
const store = loadBookshelf()
|
|
138
|
+
const book = store.books.find(b => b.id === id)
|
|
139
|
+
if (book === undefined) return undefined
|
|
140
|
+
store.activeBookId = id
|
|
141
|
+
book.updatedAt = new Date().toISOString()
|
|
142
|
+
saveBookshelf(store)
|
|
143
|
+
return book
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** 移除一本书。 */
|
|
147
|
+
export function removeBook(id: string): boolean {
|
|
148
|
+
const store = loadBookshelf()
|
|
149
|
+
const idx = store.books.findIndex(b => b.id === id)
|
|
150
|
+
if (idx === -1) return false
|
|
151
|
+
store.books.splice(idx, 1)
|
|
152
|
+
if (store.activeBookId === id) {
|
|
153
|
+
store.activeBookId = store.books[0]?.id ?? null
|
|
154
|
+
}
|
|
155
|
+
saveBookshelf(store)
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 当前书输出目录(无书架则 undefined,回退 settings)。 */
|
|
160
|
+
export function activeBookOutputDir(): string | undefined {
|
|
161
|
+
const book = activeBook(loadBookshelf())
|
|
162
|
+
return book?.outputDir
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 书目录重指向:把 outputDir 等于 from 的书条目全部改为 to(目录迁移联动)。
|
|
167
|
+
* @returns 重指向的条目数。
|
|
168
|
+
*/
|
|
169
|
+
export function repointBookDirs(from: string, to: string): number {
|
|
170
|
+
const store = loadBookshelf()
|
|
171
|
+
let count = 0
|
|
172
|
+
for (const book of store.books) {
|
|
173
|
+
if (book.outputDir === from) {
|
|
174
|
+
book.outputDir = to
|
|
175
|
+
book.updatedAt = new Date().toISOString()
|
|
176
|
+
count += 1
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (count > 0) saveBookshelf(store)
|
|
180
|
+
return count
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 新书默认输出目录:`<配置的输出目录>/书名`;未配置时回退 `<DSH_HOME>/novels/书名`。
|
|
185
|
+
* @param bookName - 书名(剔除文件系统非法字符并截断到 40 字)。
|
|
186
|
+
* @param baseDir - settings 里配置的输出目录(未经激活书合并的原始值)。
|
|
187
|
+
* @returns 该书应写入的绝对目录。
|
|
188
|
+
*/
|
|
189
|
+
export function defaultOutputDirFor(bookName: string, baseDir?: string): string {
|
|
190
|
+
const clean = bookName.replace(/[\\/:*?"<>|]/g, '').trim().slice(0, 40) || '未命名小说'
|
|
191
|
+
const base = baseDir !== undefined && baseDir.trim() !== '' ? baseDir.trim() : dshHomePath('novels')
|
|
192
|
+
return join(base, clean)
|
|
193
|
+
}
|