diff --git a/docs/superpowers/specs/2026-07-08-bilin-v11-writer-toolkit-design.md b/docs/superpowers/specs/2026-07-08-bilin-v11-writer-toolkit-design.md new file mode 100644 index 0000000..9e5b1ca --- /dev/null +++ b/docs/superpowers/specs/2026-07-08-bilin-v11-writer-toolkit-design.md @@ -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 | 含 `
` | +| 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/数据模型无矛盾 | ✅ | +| 测试用例覆盖四模块 | ✅ |