@waterwx/dsh-novel-forge 0.1.0 → 1.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 +51 -25
- package/cordis.patch.yml +1 -1
- package/lib/client.js +3456 -1316
- package/lib/client.js.map +1 -1
- package/lib/index.js +436 -37
- package/lib/index.js.map +1 -1
- package/lib/types/bookshelf.d.ts +2 -0
- package/lib/types/client/api.d.ts +32 -2
- package/lib/types/client/panel/AssetsTab.d.ts +6 -1
- package/lib/types/client/panel/NovelPanel.d.ts +1 -1
- package/lib/types/engine.d.ts +25 -6
- package/lib/types/protocol.d.ts +82 -0
- package/package.json +1 -13
- package/src/assistant.ts +24 -3
- package/src/bookshelf.ts +11 -0
- package/src/client/api.ts +38 -3
- package/src/client/panel/AssetsTab.tsx +132 -17
- package/src/client/panel/BookshelfBar.tsx +62 -8
- package/src/client/panel/NovelPanel.tsx +1341 -172
- package/src/client/panel/panel.module.css +863 -59
- package/src/engine.ts +235 -21
- package/src/protocol.ts +84 -0
- package/src/routes.ts +225 -8
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 书架条:显示所有书,点击切换当前书(继续编译),+ 新建。
|
|
3
|
+
* 新建 = 开书向导:书名 + 大纲(拖拽/选择 docx 或粘贴文本),创建即建项目。
|
|
3
4
|
*/
|
|
4
|
-
import { useState } from 'react'
|
|
5
|
+
import { useRef, useState } from 'react'
|
|
5
6
|
import type { NovelApi } from '../api.ts'
|
|
7
|
+
import { extractDocxTextFromBuffer } from '../docx.ts'
|
|
6
8
|
import type { BookshelfSnapshot } from '../../protocol.ts'
|
|
7
9
|
import css from './panel.module.css'
|
|
8
10
|
|
|
@@ -18,8 +20,28 @@ export interface BookshelfBarProps {
|
|
|
18
20
|
export function BookshelfBar({ api, shelf, onSwitch }: BookshelfBarProps) {
|
|
19
21
|
const [creating, setCreating] = useState(false)
|
|
20
22
|
const [name, setName] = useState('')
|
|
23
|
+
const [outlineText, setOutlineText] = useState('')
|
|
24
|
+
const [outlineName, setOutlineName] = useState('')
|
|
21
25
|
const [busy, setBusy] = useState(false)
|
|
22
26
|
const [error, setError] = useState('')
|
|
27
|
+
const outlineFileRef = useRef<HTMLInputElement | null>(null)
|
|
28
|
+
|
|
29
|
+
const handlePickOutlineFile = async (file: File | undefined): Promise<void> => {
|
|
30
|
+
if (file === undefined) return
|
|
31
|
+
try {
|
|
32
|
+
const buffer = await file.arrayBuffer()
|
|
33
|
+
const text = extractDocxTextFromBuffer(buffer)
|
|
34
|
+
if (text.length < 50) {
|
|
35
|
+
setError('大纲内容过短(<50 字符),请检查文件')
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
setOutlineText(text)
|
|
39
|
+
setOutlineName(file.name)
|
|
40
|
+
setError('')
|
|
41
|
+
} catch (err) {
|
|
42
|
+
setError(`读取大纲失败:${(err as Error).message}`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
23
45
|
|
|
24
46
|
const handleCreate = async (): Promise<void> => {
|
|
25
47
|
const bookName = name.trim()
|
|
@@ -27,9 +49,11 @@ export function BookshelfBar({ api, shelf, onSwitch }: BookshelfBarProps) {
|
|
|
27
49
|
setBusy(true)
|
|
28
50
|
setError('')
|
|
29
51
|
try {
|
|
30
|
-
await api.bookCreate(bookName)
|
|
52
|
+
await api.bookCreate(bookName, undefined, outlineText.trim() !== '' ? outlineText.trim() : undefined)
|
|
31
53
|
setCreating(false)
|
|
32
54
|
setName('')
|
|
55
|
+
setOutlineText('')
|
|
56
|
+
setOutlineName('')
|
|
33
57
|
onSwitch()
|
|
34
58
|
} catch (err) {
|
|
35
59
|
setError((err as Error).message)
|
|
@@ -101,12 +125,42 @@ export function BookshelfBar({ api, shelf, onSwitch }: BookshelfBarProps) {
|
|
|
101
125
|
onKeyDown={e => { if (e.key === 'Enter') void handleCreate() }}
|
|
102
126
|
autoFocus
|
|
103
127
|
/>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
128
|
+
{/* 开书向导:大纲(可选,推荐) */}
|
|
129
|
+
<div className={css.bookCreateOutline}>
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
className={`${css.button} ${css.buttonSmall}`}
|
|
133
|
+
onClick={() => { outlineFileRef.current?.click() }}
|
|
134
|
+
>
|
|
135
|
+
{outlineName !== '' ? `✓ ${outlineName}` : '📄 选择大纲 docx'}
|
|
136
|
+
</button>
|
|
137
|
+
<input
|
|
138
|
+
ref={outlineFileRef}
|
|
139
|
+
type="file"
|
|
140
|
+
accept=".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
141
|
+
style={{ display: 'none' }}
|
|
142
|
+
onChange={e => {
|
|
143
|
+
void handlePickOutlineFile(e.target.files?.[0])
|
|
144
|
+
e.target.value = ''
|
|
145
|
+
}}
|
|
146
|
+
/>
|
|
147
|
+
<textarea
|
|
148
|
+
className={css.input}
|
|
149
|
+
style={{ width: 280, minHeight: 64, resize: 'vertical' }}
|
|
150
|
+
placeholder="或直接粘贴大纲文本(提供大纲后开书即建立项目,书名自动从大纲首行识别)"
|
|
151
|
+
value={outlineText}
|
|
152
|
+
onChange={e => { setOutlineText(e.target.value) }}
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
<div className={css.row}>
|
|
156
|
+
<button type="button" className={`${css.button} ${css.buttonSmall} ${css.buttonPrimary}`} disabled={busy || name.trim() === ''} onClick={() => { void handleCreate() }}>
|
|
157
|
+
开书
|
|
158
|
+
</button>
|
|
159
|
+
<button type="button" className={`${css.button} ${css.buttonSmall}`} onClick={() => { setCreating(false) }}>
|
|
160
|
+
取消
|
|
161
|
+
</button>
|
|
162
|
+
<span className={css.meta}>{outlineText.length > 0 ? `大纲 ${outlineText.length} 字` : '未提供大纲(稍后在大纲页导入)'}</span>
|
|
163
|
+
</div>
|
|
110
164
|
</div>
|
|
111
165
|
)}
|
|
112
166
|
</div>
|