027d7c7559
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>
400 lines
16 KiB
Markdown
400 lines
16 KiB
Markdown
# 笔临 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/数据模型无矛盾 | ✅ |
|
||
| 测试用例覆盖四模块 | ✅ |
|