docs: add v1.1 writer toolkit design spec for templates, import, and export
Capture chapter templates, txt/md/docx import, submission presets, and global inspiration capture for the daily writing workflow release. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
# 笔临 v1.1 作家日更工具包 设计规格
|
||||
|
||||
**版本**:1.0
|
||||
**日期**:2026-07-08
|
||||
**范围**:§10 章节模板 + §11.1 书籍导入(txt/md/docx)+ §20.2 投稿导出预设 + §20.6 全局灵感捕获增强
|
||||
**前置**:v1.0.0 已交付(`4adafa1`:P7 角色关系图谱 + P9 写作分析)
|
||||
**参考**:`design/readme.md` v1.1 §10 / §11.1 / §20.2 / §20.6、`design/ui_pc.html` `#templateModal` / `#importModal` / `#exportModal` / `#inspirationModal`
|
||||
|
||||
**目标版本**:v1.1.0
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
### 1.1 阶段定位
|
||||
|
||||
v1.0.0 已完成「看数据」能力(关系图谱、写作分析、POV)。`design/readme.md` §20.7 审查清单仍有多项 **有 UI 原型、无真实实现** 的作家刚需:
|
||||
|
||||
- 主页「导入书籍」为 `comingSoon`
|
||||
- 章节模板 / 快速新建章仅有 `#templateModal` 静态原型
|
||||
- 导出模态「复制到剪贴板(网文投稿格式)」无后端
|
||||
- 灵感捕获 `Ctrl+Shift+I` 仅编辑器内可用,保存后会跳转侧栏打断心流
|
||||
|
||||
本阶段(v1.1.0)交付 **作家日更工具包**,形成「写得更快 → 发得更顺 → 灵感不漏」闭环。
|
||||
|
||||
### 1.2 已确认决策(Brainstorming 2026-07-08)
|
||||
|
||||
| 维度 | 决策 |
|
||||
|------|------|
|
||||
| 方向 | **方案 B**:作家日更工具包 |
|
||||
| MVP 边界 | **方案 A(完整四模块)**:模板 + txt/md/docx 导入 + 投稿预设 + 灵感增强 |
|
||||
| 模板存储 | `GlobalSettings.chapterTemplates[]`;内置模板代码常量合并展示 |
|
||||
| 模板解析 | **渲染进程** `chapter-template.ts` 替换变量后 `chapter.create` + `chapter.update` |
|
||||
| docx 解析 | **mammoth**(docx → HTML,保留 bold/italic) |
|
||||
| md 解析 | **marked**(md → HTML) |
|
||||
| 导入分章 | 自动正则 / 双换行 / 自定义正则 三档 |
|
||||
| 大文件 | >20MB 导入前二次确认;进度条 IPC 事件 |
|
||||
| 投稿导出 | **单章**复制剪贴板 + 另存 `.txt`;内置「通用网文」预设 |
|
||||
| 投稿预设存储 | `GlobalSettings.submissionPresets[]` |
|
||||
| 灵感增强 | 快捷键全局可用;未开书时模态内选书;保存后仅 toast、不跳转 |
|
||||
| 置顶 | 灵感模态最高 z-index;不新建独立 BrowserWindow |
|
||||
|
||||
### 1.3 验收标准
|
||||
|
||||
用户能完成:
|
||||
|
||||
1. 在章节列表点击「快速新建」,选择模板并输入标题,创建章节且编辑器自动填入模板正文(含变量替换)
|
||||
2. 在设置页创建/编辑/删除自定义章节模板;内置模板只读展示
|
||||
3. 主页「导入书籍」选择 `.txt` / `.md` / `.docx`,预览分章结果,确认后创建新书并导入全部章节
|
||||
4. 导入 >20MB 文件前看到确认提示;导入过程显示进度
|
||||
5. 编辑器内 `Ctrl+Shift+E` 打开导出模态,选择「通用网文」预设,**复制到剪贴板** 得到段首空两格 + `# 第N章 标题` 格式纯文本
|
||||
6. 设置页自定义投稿预设(标题格式、段首缩进、段落间距),导出时应用
|
||||
7. 在主页按 `Ctrl+Shift+I` 唤起灵感捕获,选择书籍后保存,仅 toast 确认、焦点不打断原界面
|
||||
8. 编辑器内灵感捕获行为与 7 一致(不再自动跳转灵感面板)
|
||||
|
||||
### 1.4 本规格不实现(明确排除)
|
||||
|
||||
| 项 | 归属 |
|
||||
|----|------|
|
||||
| `.novel` / `.novel-all` 项目包导入导出 | v1.2+ |
|
||||
| PDF / Word 全书导出、分卷批量投稿复制 | v1.2+ |
|
||||
| §11.2 完整导出矩阵(docx 目录、PDF 排版) | v1.2+ |
|
||||
| §20.5 敏感词合规、预设内敏感词替换规则 | v1.2+ |
|
||||
| §20.3 角色弧线简报 | v1.2+(图谱深化线) |
|
||||
| §5.7 故事时间线、§5.8 连续阅读模式 | v1.2+ |
|
||||
| 导入 `.novel` 包 | v1.2+ |
|
||||
| 平台 API 直连投稿 | 永久 YAGNI |
|
||||
| 模板变量 `{outlineItem}` 自动关联当前章大纲 | v1.1 留空字符串(无关联时);手动关联后续 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构
|
||||
|
||||
### 2.1 进程模型
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 渲染进程 │
|
||||
│ · TemplateQuickCreateModal / TemplateSettingsTab │
|
||||
│ · ImportBookModal │
|
||||
│ · ExportModal + SubmissionPresetSettingsTab │
|
||||
│ · InspirationModal(增强:全局唤起、选书、无跳转) │
|
||||
│ · chapter-template.ts / submission-formatter.ts(纯函数) │
|
||||
└──────────────────────────────┬──────────────────────────────────────┘
|
||||
│ IPC
|
||||
┌────────────────────────────▼────────────────────────────────────────┐
|
||||
│ 主进程 │
|
||||
│ · ImportService(读文件、分章、创建书+卷+章批量写入) │
|
||||
│ · ExportService(HTML→纯文本、应用投稿预设、写剪贴板/文件) │
|
||||
│ · BookRegistryService(已有 create/open) │
|
||||
│ · GlobalSettingsService(扩展 chapterTemplates / submissionPresets)│
|
||||
│ · dialog.showOpenDialog / clipboard │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 模块边界
|
||||
|
||||
| 模块 | 职责 | 依赖 |
|
||||
|------|------|------|
|
||||
| `chapter-template.ts` | 变量替换、章节号计算 | `GlobalSettings`, 书籍 volumes/chapters/settings |
|
||||
| `ImportService` | 解析文件、分章、创建书 | `BookRegistry`, `ChapterRepository`, mammoth, marked |
|
||||
| `ExportService` | 单章格式化、剪贴板、存盘 | `ChapterRepository`, `GlobalSettings` |
|
||||
| `SubmissionFormatter` | HTML 剥离、段首缩进、标题行 | 纯函数,主/渲染均可调用 |
|
||||
| `TemplateQuickCreateModal` | 选模板 → IPC 创建章 | `chapter.create/update` |
|
||||
| `ImportBookModal` | 选文件 → preview → execute | `import.*` |
|
||||
| `ExportModal` | 选预设 → 复制/存盘 | `export.*` |
|
||||
|
||||
**原则**:
|
||||
|
||||
- 导入/导出重 IO 与解析放主进程;模板变量替换可在渲染进程(减少 IPC 往返)
|
||||
- 导入失败整事务回滚:书未创建或创建后写章失败则 `registry.delete(bookId)`
|
||||
- 剪贴板写入仅主进程(Electron `clipboard` API)
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据模型
|
||||
|
||||
### 3.1 GlobalSettings 扩展
|
||||
|
||||
```typescript
|
||||
export interface ChapterTemplate {
|
||||
id: string
|
||||
name: string
|
||||
body: string
|
||||
defaultStatus?: ChapterStatus
|
||||
builtin?: boolean
|
||||
}
|
||||
|
||||
export interface SubmissionPreset {
|
||||
id: string
|
||||
name: string
|
||||
titleFormat: string // 默认 "# 第{chapterNumber}章 {chapterTitle}"
|
||||
indentParagraphs: boolean // 段首两全角空格
|
||||
includeAuthorsNote: boolean // 保留「作者的话」段(检测 —— 分隔)
|
||||
paragraphGap: 'single' | 'double'
|
||||
}
|
||||
|
||||
// GlobalSettings 追加:
|
||||
chapterTemplates?: ChapterTemplate[]
|
||||
submissionPresets?: SubmissionPreset[]
|
||||
defaultSubmissionPresetId?: string
|
||||
```
|
||||
|
||||
**内置模板**(`src/shared/builtin-templates.ts`,不写入 JSON):
|
||||
|
||||
| id | name | body 摘要 |
|
||||
|----|------|-----------|
|
||||
| `builtin-generic` | 通用模板 | `## 第{chapterNumber}章 {chapterTitle}\n\n` |
|
||||
| `builtin-webnovel` | 网文常用 | 正文 + `\n\n——\n作者的话:\n` |
|
||||
|
||||
**内置投稿预设**:
|
||||
|
||||
| id | name | 规则 |
|
||||
|----|------|------|
|
||||
| `builtin-webnovel` | 通用网文 | `titleFormat: "# 第{chapterNumber}章 {chapterTitle}"`, `indentParagraphs: true`, `paragraphGap: 'double'` |
|
||||
|
||||
`GlobalSettingsService` 读取时合并内置项;用户不可删除内置项。
|
||||
|
||||
### 3.2 模板变量
|
||||
|
||||
| 变量 | 来源 |
|
||||
|------|------|
|
||||
| `{chapterNumber}` | 目标卷 `chapters.length + 1` |
|
||||
| `{chapterTitle}` | 用户输入 |
|
||||
| `{penName}` | `settings.penName` |
|
||||
| `{date}` | `localDateString()` |
|
||||
| `{volumeName}` | 当前卷 `name` |
|
||||
| `{previousChapterTitle}` | 同卷上一章 `title`,首章 `""` |
|
||||
| `{outlineItem}` | v1.1 固定 `""`(章节创建时无 outline 关联) |
|
||||
| `{characterList}` | 当前卷内 `settings.type=character` 且 `chapterIds` 与卷内任一章有交集的 `name`,逗号连接 |
|
||||
|
||||
### 3.3 导入分章
|
||||
|
||||
```typescript
|
||||
export type ImportSplitMode = 'auto' | 'paragraph' | 'regex'
|
||||
|
||||
export interface ImportChapterPreview {
|
||||
title: string
|
||||
contentHtml: string
|
||||
wordCount: number
|
||||
}
|
||||
|
||||
export interface ImportPreviewResult {
|
||||
fileName: string
|
||||
fileSizeBytes: number
|
||||
chapters: ImportChapterPreview[]
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
export interface ImportExecuteParams {
|
||||
filePath: string
|
||||
bookName: string
|
||||
category: string
|
||||
splitMode: ImportSplitMode
|
||||
customRegex?: string
|
||||
}
|
||||
```
|
||||
|
||||
**自动分章正则**(依次尝试,命中即用):
|
||||
|
||||
1. `/^#\s*第.+章[^\n]*/gm`(Markdown 标题)
|
||||
2. `/^第[一二三四五六七八九十百千零〇\d]+章[^\n]*/gm`(纯文本章标题行)
|
||||
|
||||
**paragraph 模式**:按 `\n\s*\n` 分段;标题取段首非空行,截断 50 字。
|
||||
|
||||
**首章前导内容**:合并入第一章 `warnings` 提示,不丢弃。
|
||||
|
||||
---
|
||||
|
||||
## 4. IPC
|
||||
|
||||
### 4.1 通道
|
||||
|
||||
```typescript
|
||||
IMPORT_PICK_FILE: 'import:pickFile'
|
||||
IMPORT_PREVIEW: 'import:preview'
|
||||
IMPORT_EXECUTE: 'import:execute'
|
||||
IMPORT_PROGRESS: 'import:progress' // main → renderer event
|
||||
|
||||
EXPORT_FORMAT_CHAPTER: 'export:formatChapter'
|
||||
EXPORT_COPY_CLIPBOARD: 'export:copyClipboard'
|
||||
EXPORT_SAVE_TXT: 'export:saveTxt'
|
||||
```
|
||||
|
||||
### 4.2 载荷
|
||||
|
||||
```typescript
|
||||
// import:pickFile → { canceled, filePath? }
|
||||
// import:preview → { filePath, splitMode, customRegex? } → ImportPreviewResult
|
||||
// import:execute → ImportExecuteParams → { bookId }
|
||||
// import:progress → { phase: 'parsing'|'writing', current, total }
|
||||
|
||||
// export:formatChapter → { bookId, chapterId, presetId } → { text }
|
||||
// export:copyClipboard → { text } → void
|
||||
// export:saveTxt → { text, defaultName } → { savedPath? }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. UI 规格
|
||||
|
||||
### 5.1 TemplateQuickCreateModal
|
||||
|
||||
- 入口:章节列表底部 `data-testid="chapter-quick-new"`
|
||||
- 布局:参考 `#templateModal` 卡片网格 + 标题输入
|
||||
- 流程:选模板 → 输入标题 → 创建 → `switchTarget` 到新章
|
||||
|
||||
### 5.2 设置页 — 章节模板 Tab
|
||||
|
||||
- `data-testid="settings-chapter-templates"`
|
||||
- 列表:内置 + 自定义;自定义可编辑 body(textarea)、删除
|
||||
- 新建:`setting-template-add`
|
||||
|
||||
### 5.3 ImportBookModal
|
||||
|
||||
- 入口:主页 `home.importBook`(替换 comingSoon)
|
||||
- `data-testid="import-book-modal"`
|
||||
- 字段:文件路径(浏览)、书名、分类、分章规则下拉、自定义正则输入(regex 模式显示)
|
||||
- 预览区:章数 + 前 5 章标题列表
|
||||
- >20MB:`import-large-file-confirm` 对话框
|
||||
- 进度:`import-progress-bar`
|
||||
|
||||
### 5.4 ExportModal
|
||||
|
||||
- 入口:编辑器工具栏 `data-testid="export-chapter"` + 快捷键 `exportBook`
|
||||
- `data-testid="export-modal"`
|
||||
- 折叠区「投稿预设」:`export-preset-select`
|
||||
- 操作:`export-copy-clipboard` / `export-save-txt`
|
||||
- 预览:格式化后前 500 字纯文本
|
||||
|
||||
### 5.5 设置页 — 投稿预设 Tab
|
||||
|
||||
- `data-testid="settings-submission-presets"`
|
||||
- 字段:名称、标题格式、段首缩进 checkbox、段落间距 select
|
||||
|
||||
### 5.6 InspirationModal 增强
|
||||
|
||||
- `App.tsx`:`captureInspiration` 移除 `view !== 'editor'` 限制
|
||||
- 未开书:显示 `inspiration-book-select`(书籍列表下拉,默认 `lastOpenedAt` 最新)
|
||||
- 保存后:删除 `switchTarget` + `setSidebarPanel('inspiration')`;仅 `showToast` + `setOpen(false)`
|
||||
- 样式:`inspiration-modal--capture` 提升 z-index(高于 TopBar)
|
||||
|
||||
---
|
||||
|
||||
## 6. 依赖
|
||||
|
||||
| 包 | 用途 |
|
||||
|----|------|
|
||||
| `mammoth` | docx → HTML |
|
||||
| `marked` | md → HTML |
|
||||
|
||||
均在主进程 `ImportService` 使用;electron-vite 打包时 external 或 bundle 按现有惯例。
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试
|
||||
|
||||
| 用例 ID | 层级 | 场景 | 预期 |
|
||||
|---------|------|------|------|
|
||||
| UT-TEMPL-01 | 单元 | 解析含 `{chapterNumber}` `{chapterTitle}` | 正确替换 |
|
||||
| UT-TEMPL-02 | 单元 | `{volumeName}` 替换 | 卷名正确 |
|
||||
| UT-TEMPL-03 | 单元 | `{characterList}` 卷内角色 | 逗号列表 |
|
||||
| IT-TEMPL-01 | 集成 | 模板快速新建写入 DB | content 含替换后正文 |
|
||||
| UT-IMPORT-01 | 单元 | txt 自动分章 3 章 | `chapters.length === 3` |
|
||||
| UT-IMPORT-02 | 单元 | md 转 HTML | 含 `<p>` |
|
||||
| UT-IMPORT-03 | 单元 | docx mammoth | 非空 HTML |
|
||||
| IT-IMPORT-01 | 集成 | execute 创建书+章 | `registry.open` 章数匹配 |
|
||||
| UT-EXPORT-01 | 单元 | 段首空两格 | 每段 ` ` 前缀 |
|
||||
| UT-EXPORT-02 | 单元 | 标题格式 | `# 第1章 标题` |
|
||||
| IT-EXPORT-01 | 集成 | copyClipboard | 剪贴板非空 |
|
||||
| E2E-TEMPL-01 | E2E | 快速新建章 | 编辑器有模板内容 |
|
||||
| E2E-IMPORT-01 | E2E | 导入 txt | 新书可见多章 |
|
||||
| E2E-EXPORT-01 | E2E | 复制投稿格式 | 剪贴板含章标题 |
|
||||
| E2E-INSP-01 | E2E | 主页 Ctrl+Shift+I 保存 | toast,不跳编辑器 |
|
||||
|
||||
---
|
||||
|
||||
## 8. i18n 键(节选)
|
||||
|
||||
```json
|
||||
"template.title": "章节模板",
|
||||
"template.quickNew": "模板新建章",
|
||||
"template.builtin": "内置",
|
||||
"template.custom": "自定义",
|
||||
"import.title": "导入书籍",
|
||||
"import.pickFile": "浏览文件",
|
||||
"import.splitAuto": "自动识别章节",
|
||||
"import.splitParagraph": "双换行分段",
|
||||
"import.splitRegex": "自定义正则",
|
||||
"import.largeFileConfirm": "文件较大({{size}}),导入可能需要 30 秒至 2 分钟,是否继续?",
|
||||
"import.progress": "正在导入 {{current}}/{{total}}",
|
||||
"export.title": "导出章节",
|
||||
"export.submissionPresets": "投稿预设",
|
||||
"export.copyClipboard": "复制到剪贴板",
|
||||
"export.saveTxt": "另存为文本",
|
||||
"export.copied": "已复制到剪贴板",
|
||||
"submission.title": "投稿预设",
|
||||
"submission.titleFormat": "标题格式",
|
||||
"submission.indent": "段首空两格",
|
||||
"inspiration.selectBook": "关联书籍",
|
||||
"inspiration.saved": "灵感已保存"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 文件清单
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `src/shared/types.ts` | `ChapterTemplate`, `SubmissionPreset`, Import/Export DTO |
|
||||
| `src/shared/builtin-templates.ts` | 内置模板与投稿预设 |
|
||||
| `src/shared/ipc-channels.ts` | `import.*` / `export.*` |
|
||||
| `src/main/services/import.service.ts` | 解析 + 分章 + 创建书 |
|
||||
| `src/main/services/export.service.ts` | 格式化 + 剪贴板/存盘 |
|
||||
| `src/main/lib/submission-formatter.ts` | HTML → 投稿纯文本 |
|
||||
| `src/main/lib/chapter-splitter.ts` | 分章逻辑 |
|
||||
| `src/main/ipc/handlers/import.handler.ts` | 导入 IPC |
|
||||
| `src/main/ipc/handlers/export.handler.ts` | 导出 IPC |
|
||||
| `src/renderer/lib/chapter-template.ts` | 模板变量替换 |
|
||||
| `src/renderer/components/chapter/TemplateQuickCreateModal.tsx` | 快速新建 |
|
||||
| `src/renderer/components/import/ImportBookModal.tsx` | 导入 UI |
|
||||
| `src/renderer/components/export/ExportModal.tsx` | 导出 UI |
|
||||
| `src/renderer/components/settings/TemplateSettingsTab.tsx` | 模板管理 |
|
||||
| `src/renderer/components/settings/SubmissionPresetTab.tsx` | 投稿预设管理 |
|
||||
| `src/renderer/components/inspiration/InspirationModal.tsx` | 增强 |
|
||||
| `src/renderer/components/home/HomePage.tsx` | 接入导入 |
|
||||
| `src/renderer/components/layout/EditorLayout.tsx` | 导出入口 |
|
||||
| `tests/main/chapter-template.test.ts` | UT-TEMPL-* |
|
||||
| `tests/main/chapter-splitter.test.ts` | UT-IMPORT-01 |
|
||||
| `tests/main/submission-formatter.test.ts` | UT-EXPORT-* |
|
||||
| `tests/main/import.service.test.ts` | IT-IMPORT-01 |
|
||||
| `e2e/chapter-template.spec.ts` | E2E-TEMPL-01 |
|
||||
| `e2e/import-book.spec.ts` | E2E-IMPORT-01 |
|
||||
| `e2e/export-submission.spec.ts` | E2E-EXPORT-01 |
|
||||
| `e2e/inspiration-capture.spec.ts` | E2E-INSP-01 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 实现顺序建议
|
||||
|
||||
1. 类型 + 内置常量 + `chapter-template.ts` + 快速新建 UI
|
||||
2. `SubmissionFormatter` + ExportService + ExportModal + 设置 Tab
|
||||
3. InspirationModal 增强(独立、改动小)
|
||||
4. ImportService + ImportBookModal(依赖 mammoth/marked,放最后)
|
||||
|
||||
**目标版本号**:实现完成后 `package.json` / README / 关于页 → **v1.1.0**
|
||||
|
||||
---
|
||||
|
||||
## 11. Spec 自检
|
||||
|
||||
| 检查项 | 状态 |
|
||||
|--------|------|
|
||||
| 无 TBD/TODO 占位 | ✅ |
|
||||
| 与方案 A 四模块一致 | ✅ |
|
||||
| `.novel` 明确排除 | ✅ |
|
||||
| 单章投稿导出范围明确 | ✅ |
|
||||
| IPC/数据模型无矛盾 | ✅ |
|
||||
| 测试用例覆盖四模块 | ✅ |
|
||||
Reference in New Issue
Block a user