dsh-novels 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +174 -2
- package/README.zh-CN.md +132 -0
- package/client/index.mjs +1376 -0
- package/cordis.patch.yml +4 -0
- package/docs/reader.jpg +0 -0
- package/docs/shelf.jpg +0 -0
- package/docs/toc.jpg +0 -0
- package/icon-180.png +0 -0
- package/icon.svg +6 -0
- package/lib/index.mjs +357 -0
- package/lib/store.mjs +366 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +51 -4
- package/screenshots.json +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 darlingsakurua
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,175 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-novels
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**English** | [简体中文](README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
Read `.txt` novels inside DeepSeek Harness: import a file, get chapters split
|
|
6
|
+
automatically, and read it in a panel next to the conversation.
|
|
7
|
+
|
|
8
|
+
<img width="900" alt="Novel shelf inside DeepSeek Harness" src="docs/shelf.jpg">
|
|
9
|
+
|
|
10
|
+
Status: **Phase 2/3 complete** — upload, shelf, reader, progress restore, rename,
|
|
11
|
+
delete, reader prefs (font size, line height, page width), upload progress,
|
|
12
|
+
long-book table-of-contents grouping, empty/error/loading states.
|
|
13
|
+
|
|
14
|
+
Features:
|
|
15
|
+
|
|
16
|
+
- Import `.txt` by button or drag & drop; 20 MB cap, UTF-8/BOM/GB18030/GBK/Big5/UTF-16.
|
|
17
|
+
- Automatic chapter split with a 4000-character block fallback.
|
|
18
|
+
- Shelf cards show chapter/character counts, encoding, reading progress bar and
|
|
19
|
+
"读到第 N 章"; rename in place; delete with confirmation.
|
|
20
|
+
- Reader: table of contents, previous/next chapter, font size, line height, page
|
|
21
|
+
width, keyboard (← → PageUp PageDown Space j k Esc Ctrl±), chapter counter and
|
|
22
|
+
a bottom progress line.
|
|
23
|
+
- Reading position (chapter + scroll offset) and reader prefs persist on the Host,
|
|
24
|
+
so they survive a reload and a restart. `/api` ports change every launch, so the
|
|
25
|
+
client keeps no state of its own.
|
|
26
|
+
- Long books (>300 chapters) get a grouped table of contents (100-chapter blocks,
|
|
27
|
+
only the active block expanded).
|
|
28
|
+
|
|
29
|
+
## Screenshots
|
|
30
|
+
|
|
31
|
+
**Reader** — table of contents, font size / line height / page width controls,
|
|
32
|
+
chapter counter, keyboard navigation.
|
|
33
|
+
|
|
34
|
+
<img width="900" alt="Reader panel" src="docs/reader.jpg">
|
|
35
|
+
|
|
36
|
+
**Grouped table of contents** — books over 300 chapters are split into blocks of
|
|
37
|
+
100, with only the current block expanded.
|
|
38
|
+
|
|
39
|
+
<img width="900" alt="Grouped table of contents" src="docs/toc.jpg">
|
|
40
|
+
|
|
41
|
+
## Panel styling
|
|
42
|
+
|
|
43
|
+
The stylesheet is installed **once into `document.head`** when the plugin
|
|
44
|
+
activates (`ensureStyles()`, tag `style[data-plugin-css="dsh-novels/panel.css"]`).
|
|
45
|
+
|
|
46
|
+
It used to be a `<style>` element rendered inside the shelf's own tree. Every
|
|
47
|
+
rule lives in the same string, so the shelf looked right — but opening a book
|
|
48
|
+
unmounted the shelf, the style element went with it, and the reader lost the
|
|
49
|
+
whole sheet: no `height:100%`, no flex column, no `overflow:auto`. The symptom
|
|
50
|
+
was a reader that could not scroll and a chapter footer pushed out of the panel.
|
|
51
|
+
Keeping the sheet in `head` makes it independent of whichever view is mounted.
|
|
52
|
+
`render.test.mjs` asserts both halves of that contract (sheet in `head`, no
|
|
53
|
+
`<style>` inside the panel tree).
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
```powershell
|
|
58
|
+
# clone once, then install from the DSH profile that should carry the plugin
|
|
59
|
+
git clone https://github.com/darlingsakurua/dsh-novels.git
|
|
60
|
+
plugin_manager install_bundle target="<path-to-your-clone>"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The bundle is linked (not copied), so edits under `client/` and `lib/` apply
|
|
64
|
+
after the plugin is reloaded (`plugin_manager set_plugin target="include:dsh-novels" enabled=false` then `true`).
|
|
65
|
+
|
|
66
|
+
## Layout
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
dsh-novels/
|
|
70
|
+
package.json dsh.bundle.patch + dsh.client (platform: web)
|
|
71
|
+
cordis.patch.yml inserts the bundle into the profile
|
|
72
|
+
icon.svg sidebar glyph (currentColor)
|
|
73
|
+
locale/{zh,en}.json panel title for the sidebar tooltip
|
|
74
|
+
lib/index.mjs Host half: HTTP routes under /dsh-novels/api/v1
|
|
75
|
+
lib/store.mjs Host half: library index, atomic writes, path checks
|
|
76
|
+
client/index.mjs Web half: shelf + reader, plus the text pipeline
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Data lives in `<profileDir>/plugin-data/dsh-novels/`:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
library.json {version, prefs, books[]}
|
|
83
|
+
books/<id>.txt normalized UTF-8 body
|
|
84
|
+
books/<id>.chapters.json [{i, title, start}]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## API
|
|
88
|
+
|
|
89
|
+
All routes are gated exactly like the shipped `/api` channel (the request must
|
|
90
|
+
carry the browser session the shell already forwards).
|
|
91
|
+
|
|
92
|
+
| Method | Path | Purpose |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| GET | `/ping` | diagnostics (`dataDir`, `bookCount`, version) |
|
|
95
|
+
| GET | `/library` | books + reader prefs |
|
|
96
|
+
| POST | `/books` | `{name, text, encoding, chapters}` → book |
|
|
97
|
+
| GET | `/books/{id}` | book + chapter offsets |
|
|
98
|
+
| GET | `/books/{id}/text` | full body as `text/plain` |
|
|
99
|
+
| PATCH | `/books/{id}` | rename (`{title, author}`) |
|
|
100
|
+
| PATCH | `/books/{id}/progress` | `{chapterIndex, offsetChars, percent}` |
|
|
101
|
+
| DELETE | `/books/{id}` | remove book and its files |
|
|
102
|
+
| PATCH | `/prefs` | `{fontSize, lineHeight, measure, fontFamily}` |
|
|
103
|
+
|
|
104
|
+
Envelopes are `{ok: true, ...}` or `{ok: false, error, message}`; request bodies
|
|
105
|
+
are capped at 24 MB for uploads and 64 KB for everything else. The client rejects
|
|
106
|
+
files over 20 MB before reading them; the Host cap leaves room for the JSON
|
|
107
|
+
envelope (body + chapter offsets).
|
|
108
|
+
|
|
109
|
+
## Text pipeline
|
|
110
|
+
|
|
111
|
+
`client/index.mjs` decodes bytes in this order: BOM (UTF-8/UTF-16) → strict
|
|
112
|
+
UTF-8 → GB18030 → GBK → Big5. Line endings are normalized to `\n` before
|
|
113
|
+
chapter offsets are computed, because every offset is a character index into
|
|
114
|
+
that normalized text.
|
|
115
|
+
|
|
116
|
+
Chapter headings are matched with `^第…[章节回卷篇部集]` and
|
|
117
|
+
`^Chapter/Part/Book N`. A heading with less than 200 characters before the next
|
|
118
|
+
one is treated as a false positive and dropped (the previous chapter absorbs
|
|
119
|
+
it); the final chapter is always kept. Files with no heading at all are split
|
|
120
|
+
into 4000-character blocks.
|
|
121
|
+
|
|
122
|
+
Tables of contents above 300 chapters are rendered in blocks of 100 (`1–100`,
|
|
123
|
+
`101–200`, …) with only the block holding the current chapter expanded. Grouping
|
|
124
|
+
by the first character of the title was tried first and abandoned: nearly every
|
|
125
|
+
Chinese chapter heading starts with 第, so it produced a single bucket.
|
|
126
|
+
|
|
127
|
+
## Tests
|
|
128
|
+
|
|
129
|
+
No test runner needed — plain Node scripts, each printing `ok`/`FAIL` lines:
|
|
130
|
+
|
|
131
|
+
```powershell
|
|
132
|
+
node _test/store.test.mjs # Host store: index, atomic writes, clamping
|
|
133
|
+
node _test/client-parse.test.mjs # decode/normalize/split (bundled client code)
|
|
134
|
+
node _test/host-api.test.mjs # routes, envelopes, 401/403 gate, limits
|
|
135
|
+
node _test/perf.test.mjs # 5 MB GB18030 sample end to end
|
|
136
|
+
node _test/render.test.mjs # shelf → upload → read → delete, headless React
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`render.test.mjs` loads the real client bundle with a miniature React (hooks +
|
|
140
|
+
static tree) and points `fetch` at a server wired to the plugin's own Host
|
|
141
|
+
routes, so the browser flow is exercised without a browser. `_test/tmp/`
|
|
142
|
+
holds its throwaway profiles.
|
|
143
|
+
|
|
144
|
+
`_test/make-sample.mjs` regenerates the GB18030 samples in `samples/`
|
|
145
|
+
(the 5 MB one is handy for checking upload time by hand).
|
|
146
|
+
|
|
147
|
+
## Manual acceptance
|
|
148
|
+
|
|
149
|
+
These cannot be automated from here (the sandbox cannot start a browser):
|
|
150
|
+
|
|
151
|
+
1. Import `samples/sample-gb18030-small.txt` and `samples/sample-gb18030-5mb.txt`.
|
|
152
|
+
2. Read past chapter 10 of the 5 MB book, scroll mid-chapter, go back to the shelf
|
|
153
|
+
— the card should read "读到第 N 章" with a filled bar.
|
|
154
|
+
3. Reopen it: same chapter, same scroll offset, same font size/line height/width.
|
|
155
|
+
4. Rename a book on the shelf; the reader header follows the new title.
|
|
156
|
+
5. Toggle the app theme (light/dark): all panel surfaces and text follow the
|
|
157
|
+
`--dsw-alias-*` tokens.
|
|
158
|
+
6. Disable the plugin (`set_plugin target="include:dsh-novels" enabled=false`):
|
|
159
|
+
the 书架 icon disappears and `/dsh-novels/api/v1/ping` returns 404; re-enable
|
|
160
|
+
and refresh to get it back with the same books.
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
## Rules this plugin follows
|
|
164
|
+
|
|
165
|
+
- Never require `@deepseek-ai/dsh-client-*`; only `react` and the builtins.
|
|
166
|
+
- Never poke `document.body` or use an iframe; the panel is a React tree in a slot.
|
|
167
|
+
(The stylesheet is a `<style>` in `head`, installed imperatively — like the
|
|
168
|
+
shell's own style service does — not DOM inside the panel.)
|
|
169
|
+
- Styles use `--dsw-alias-*` tokens only — no literal colors.
|
|
170
|
+
- Keyboard shortcuts live on the reader container, never on `window`.
|
|
171
|
+
- The API prefix is `/dsh-novels/api/v1`; `/api` belongs to the connection channel.
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
[MIT](LICENSE) © 2026 darlingsakurua
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# dsh-novels
|
|
2
|
+
|
|
3
|
+
[English](README.md) | **简体中文**
|
|
4
|
+
|
|
5
|
+
在 DeepSeek Harness 里阅读 `.txt` 小说:导入文件后自动分章,在对话旁边的面板里直接阅读。
|
|
6
|
+
|
|
7
|
+
<img width="900" alt="DeepSeek Harness 中的书架界面" src="docs/shelf.jpg">
|
|
8
|
+
|
|
9
|
+
状态:**Phase 2/3 已完成** —— 导入、书架、阅读器、进度恢复、改名、删除、阅读偏好(字号、行距、页宽)、上传进度、长书目录分组、空/错误/加载状态。
|
|
10
|
+
|
|
11
|
+
## 功能
|
|
12
|
+
|
|
13
|
+
- 点击按钮或拖拽导入 `.txt`;上限 20 MB,支持 UTF-8 / BOM / GB18030 / GBK / Big5 / UTF-16。
|
|
14
|
+
- 自动分章,无法识别章节标记时按 4000 字切块。
|
|
15
|
+
- 书架卡片显示章节数、字数、编码、阅读进度条和「读到第 N 章」;可就地改名;删除带确认。
|
|
16
|
+
- 阅读器:目录、上一章 / 下一章、字号、行距、页宽、键盘操作(← → PageUp PageDown 空格 j k Esc Ctrl±)、章节计数、底部进度条。
|
|
17
|
+
- 阅读位置(章节 + 滚动偏移)和阅读偏好持久化在 Host 端,刷新和重启都不会丢。`/api` 端口每次启动都会变,所以客户端不保存任何自身状态。
|
|
18
|
+
- 超过 300 章的长书会启用分组目录(每 100 章一块,只展开当前所在的块)。
|
|
19
|
+
|
|
20
|
+
## 截图
|
|
21
|
+
|
|
22
|
+
**阅读器** —— 目录、字号 / 行距 / 页宽控制、章节计数、键盘导航。
|
|
23
|
+
|
|
24
|
+
<img width="900" alt="阅读器面板" src="docs/reader.jpg">
|
|
25
|
+
|
|
26
|
+
**分组目录** —— 超过 300 章的书按每 100 章分块,只展开当前块。
|
|
27
|
+
|
|
28
|
+
<img width="900" alt="分组目录" src="docs/toc.jpg">
|
|
29
|
+
|
|
30
|
+
## 面板样式
|
|
31
|
+
|
|
32
|
+
样式表在插件激活时**一次性写入 `document.head`**(`ensureStyles()`,标签为 `style[data-plugin-css="dsh-novels/panel.css"]`)。
|
|
33
|
+
|
|
34
|
+
它原本是渲染在书架自身组件树里的 `<style>` 元素。所有规则都在同一个字符串里,书架看起来是对的——但打开一本书会卸载书架,样式元素也随之下线,阅读器就丢掉了整张样式表:没有 `height:100%`,没有 flex 列布局,没有 `overflow:auto`。表现出来就是阅读器滚不动、章节尾部被挤出面板。
|
|
35
|
+
|
|
36
|
+
把样式表放在 `head` 里,就不再依赖当前挂载的是哪个视图。`render.test.mjs` 断言了这个约定的两头(样式表在 `head` 中、面板树里没有 `<style>`)。
|
|
37
|
+
|
|
38
|
+
## 安装
|
|
39
|
+
|
|
40
|
+
```powershell
|
|
41
|
+
# 克隆一次,然后在要承载插件的 DSH profile 里安装
|
|
42
|
+
git clone https://github.com/darlingsakurua/dsh-novels.git
|
|
43
|
+
plugin_manager install_bundle target="<你的克隆路径>"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
bundle 是以链接方式(而非复制)接入的,所以 `client/` 和 `lib/` 下的改动在插件重载后即生效(先 `plugin_manager set_plugin target="include:dsh-novels" enabled=false`,再设回 `true`)。
|
|
47
|
+
|
|
48
|
+
## 目录结构
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
dsh-novels/
|
|
52
|
+
package.json dsh.bundle.patch + dsh.client (platform: web)
|
|
53
|
+
cordis.patch.yml 把 bundle 插入 profile
|
|
54
|
+
icon.svg 侧边栏图标(currentColor)
|
|
55
|
+
locale/{zh,en}.json 侧边栏提示用的面板标题
|
|
56
|
+
lib/index.mjs Host 端:/dsh-novels/api/v1 下的 HTTP 路由
|
|
57
|
+
lib/store.mjs Host 端:书库索引、原子写入、路径校验
|
|
58
|
+
client/index.mjs Web 端:书架 + 阅读器,以及文本处理管线
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
数据存放在 `<profileDir>/plugin-data/dsh-novels/`:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
library.json {version, prefs, books[]}
|
|
65
|
+
books/<id>.txt 归一化后的 UTF-8 正文
|
|
66
|
+
books/<id>.chapters.json [{i, title, start}]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## API
|
|
70
|
+
|
|
71
|
+
所有路由的鉴权方式与内置的 `/api` 通道一致(请求必须携带 shell 已转发的浏览器会话)。
|
|
72
|
+
|
|
73
|
+
| 方法 | 路径 | 用途 |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| GET | `/ping` | 诊断信息(`dataDir`、`bookCount`、版本号) |
|
|
76
|
+
| GET | `/library` | 书籍列表 + 阅读偏好 |
|
|
77
|
+
| POST | `/books` | `{name, text, encoding, chapters}` → 创建书籍 |
|
|
78
|
+
| GET | `/books/{id}` | 书籍 + 章节偏移 |
|
|
79
|
+
| GET | `/books/{id}/text` | 以 `text/plain` 返回完整正文 |
|
|
80
|
+
| PATCH | `/books/{id}` | 改名(`{title, author}`) |
|
|
81
|
+
| PATCH | `/books/{id}/progress` | `{chapterIndex, offsetChars, percent}` |
|
|
82
|
+
| DELETE | `/books/{id}` | 删除书籍及其文件 |
|
|
83
|
+
| PATCH | `/prefs` | `{fontSize, lineHeight, measure, fontFamily}` |
|
|
84
|
+
|
|
85
|
+
响应信封统一为 `{ok: true, ...}` 或 `{ok: false, error, message}`;请求体上限:上传 24 MB,其余 64 KB。客户端在读取前就会拒绝超过 20 MB 的文件;Host 端的余量留给 JSON 信封(正文 + 章节偏移)。
|
|
86
|
+
|
|
87
|
+
## 文本处理流程
|
|
88
|
+
|
|
89
|
+
`client/index.mjs` 按以下顺序解码字节:BOM(UTF-8/UTF-16)→ 严格 UTF-8 → GB18030 → GBK → Big5。在计算章节偏移之前,换行符会统一归一化为 `\n`,因为每个偏移都是这段归一化文本中的字符索引。
|
|
90
|
+
|
|
91
|
+
章节标题用 `^第…[章节回卷篇部集]` 和 `^Chapter/Part/Book N` 匹配。如果某个标题到下个标题之间不足 200 字符,会被判定为误匹配并丢弃(并入上一章);最后一章始终保留。完全没有标题的文件按 4000 字一块切分。
|
|
92
|
+
|
|
93
|
+
超过 300 章的目录按每 100 章分块渲染(`1–100`、`101–200`……),只展开包含当前章节的那一块。曾先尝试按标题首字分组,但放弃了:几乎所有中文章节标题都以「第」开头,结果只会分出一个桶。
|
|
94
|
+
|
|
95
|
+
## 测试
|
|
96
|
+
|
|
97
|
+
不需要测试框架 —— 普通 Node 脚本,各自打印 `ok`/`FAIL` 行:
|
|
98
|
+
|
|
99
|
+
```powershell
|
|
100
|
+
node _test/store.test.mjs # Host store:索引、原子写入、边界钳制
|
|
101
|
+
node _test/client-parse.test.mjs # 解码/归一化/分章(针对打包后的客户端代码)
|
|
102
|
+
node _test/host-api.test.mjs # 路由、信封、401/403 鉴权、请求上限
|
|
103
|
+
node _test/perf.test.mjs # 5 MB GB18030 样本端到端
|
|
104
|
+
node _test/render.test.mjs # 书架 → 上传 → 阅读 → 删除,无头 React
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`render.test.mjs` 用一个迷你 React(hooks + 静态树)加载真实的客户端 bundle,并把 `fetch` 指向一套接在插件自身 Host 路由上的服务,从而在没有浏览器的情况下跑通浏览器流程。`_test/tmp/` 存放它用的一次性 profile。
|
|
108
|
+
|
|
109
|
+
`_test/make-sample.mjs` 可以重新生成 `samples/` 下的 GB18030 样本(那个 5 MB 的适合手动验证上传耗时)。
|
|
110
|
+
|
|
111
|
+
## 手动验收
|
|
112
|
+
|
|
113
|
+
以下项目无法在此环境中自动化(沙箱起不了浏览器):
|
|
114
|
+
|
|
115
|
+
1. 导入 `samples/sample-gb18030-small.txt` 和 `samples/sample-gb18030-5mb.txt`。
|
|
116
|
+
2. 把 5 MB 那本读超过第 10 章,在章节中间滚动,再返回书架 —— 卡片应显示「读到第 N 章」且进度条有填充。
|
|
117
|
+
3. 重新打开:章节、滚动位置、字号/行距/页宽都应保持一致。
|
|
118
|
+
4. 在书架上给一本书改名;阅读器头部应跟随新标题。
|
|
119
|
+
5. 切换应用主题(浅色/深色):所有面板表面与文字都应跟随 `--dsw-alias-*` 令牌。
|
|
120
|
+
6. 禁用插件(`set_plugin target="include:dsh-novels" enabled=false`):书架图标消失,`/dsh-novels/api/v1/ping` 返回 404;重新启用并刷新,书还在。
|
|
121
|
+
|
|
122
|
+
## 本插件遵守的约定
|
|
123
|
+
|
|
124
|
+
- 绝不 require `@deepseek-ai/dsh-client-*`;只用 `react` 和内置模块。
|
|
125
|
+
- 绝不操作 `document.body`,也不使用 iframe;面板是插槽里的一个 React 树。(样式表是 `head` 里的 `<style>`,以命令式方式安装 —— 与 shell 自身的 style service 一致 —— 而不是面板内的 DOM。)
|
|
126
|
+
- 样式只使用 `--dsw-alias-*` 令牌 —— 不写死颜色。
|
|
127
|
+
- 键盘快捷键挂在阅读器容器上,绝不挂到 `window`。
|
|
128
|
+
- API 前缀是 `/dsh-novels/api/v1`;`/api` 属于 connection 通道。
|
|
129
|
+
|
|
130
|
+
## 许可
|
|
131
|
+
|
|
132
|
+
[MIT](LICENSE) © 2026 darlingsakurua
|