14 KiB
14 KiB
核心功能
- Markdown 文件导出:将
.md文件导出到静态文档站点目录,自动处理资源文件 - Canvas 文件导出:将
.canvas文件渲染为高清 PNG 图片并生成对应的.canvas.md文件 - 资源文件管理:自动复制和处理图片、Canvas 等资源文件到统一目录
- 路径规则配置:支持多种目录结构和命名规则,灵活配置输出路径
- 透明背景支持:Canvas 导出时可选择透明背景,移除 Obsidian 的点状背景图案
主要流程
整体架构流程
graph TB
Start[用户触发导出] --> CheckType{检查文件类型}
CheckType -->|Canvas 文件| CanvasFlow[Canvas 导出流程]
CheckType -->|Markdown 文件| MarkdownFlow[Markdown 导出流程]
CanvasFlow --> RenderCanvas[渲染 Canvas 为图片]
RenderCanvas --> SaveImage[保存图片到 assetsPath]
SaveImage --> GenerateCanvasMD[生成 .canvas.md 文件]
GenerateCanvasMD --> WriteCanvas[写入文件]
WriteCanvas --> End[完成]
MarkdownFlow --> ReadContent[读取文件内容]
ReadContent --> ProcessAssets[扫描并处理资源]
ProcessAssets --> CollectLinks[收集链接和嵌入]
CollectLinks --> Separate{分类资源}
Separate -->|Canvas 文件| RenderCanvas
Separate -->|普通资源| CopyAssets[复制资源到 assetsPath]
CopyAssets --> UpdateLinks[更新内容中的链接]
RenderCanvas --> UpdateCanvasLinks[更新 Canvas 链接为 Viewer 组件]
UpdateCanvasLinks --> CalculatePath[计算输出路径]
UpdateLinks --> CalculatePath
CalculatePath --> ApplyRules{应用路径规则}
ApplyRules --> PreserveFolder[preserveFolderStructure?]
PreserveFolder -->|是| KeepStructure[保留文件夹结构]
PreserveFolder -->|否| Flatten[扁平化]
KeepStructure --> NestNote{nestNoteFolder?}
Flatten --> NestNote
NestNote -->|是| CreateIndex[创建 index.md]
NestNote -->|否| UseFilename[使用原文件名]
CreateIndex --> WriteMD[写入 Markdown 文件]
UseFilename --> WriteMD
WriteMD --> End
资源处理流程
graph TB
Start[processAssets 开始] --> GetCache[获取文件缓存]
GetCache --> CheckCache{缓存存在?}
CheckCache -->|否| ReturnContent[返回原内容]
CheckCache -->|是| CollectStart[收集资源]
CollectStart --> ScanEmbeds[扫描 embeds]
ScanEmbeds --> CheckEmbed{是 Canvas?}
CheckEmbed -->|是| AddCanvas[添加到 canvasFiles]
CheckEmbed -->|否| AddAsset[添加到 assetsToCopy]
AddCanvas --> ScanLinks[扫描 links]
AddAsset --> ScanLinks
ScanLinks --> CheckLink{是本地资源?}
CheckLink -->|是| AddAsset
CheckLink -->|否| ProcessCanvas
ProcessCanvas[处理 Canvas 文件] --> RenderEach[遍历每个 Canvas]
RenderEach --> CallRender[调用 renderCanvas]
CallRender --> SavePNG[保存为 PNG]
SavePNG --> UpdateCanvasLink[更新为 Viewer 组件]
UpdateCanvasLink --> CopyAssets[复制普通资源]
CopyAssets --> CopyEach[遍历每个资源]
CopyEach --> ReadBinary[Binary 模式读取]
ReadBinary --> WriteAsset[写入 assetsPath]
WriteAsset --> UpdateAssetLink[更新资源链接]
UpdateAssetLink --> ReturnContent[返回更新后的内容]
Canvas 渲染流程
graph TB
Start[renderCanvas 开始] --> OpenFile[打开 Canvas 文件]
OpenFile --> GetCanvas[获取 Canvas 对象]
GetCanvas --> CheckCanvas{Canvas 存在?}
CheckCanvas -->|否| ReturnNull[返回 null]
CheckCanvas -->|是| CalcSystem[计算系统参数]
CalcSystem --> GetZoom[获取缩放级别]
GetZoom --> CalcPixel[计算设备像素比]
CalcPixel --> CalcScale[计算缩放校正系数]
CalcScale --> GetNodes[获取所有节点]
GetNodes --> CalcBBox[计算边界框]
CalcBBox --> CalcSize[计算宽度和高度]
CalcSize --> CalcLimit{计算缩放限制}
CalcLimit --> CheckLimit{超过 16284px?}
CheckLimit -->|是| UseLimit[使用限制值]
CheckLimit -->|否| UseZoom[使用原始缩放]
UseLimit --> PrepareCanvas[准备 Canvas 环境]
UseZoom --> PrepareCanvas
PrepareCanvas --> DeselectAll[取消选择]
DeselectAll --> AddScreenshotting[添加截图样式]
AddScreenshotting --> CheckTransparent{透明背景?}
CheckTransparent -->|是| AddTransparentClass[添加透明样式类]
CheckTransparent -->|否| HideElements[隐藏页面元素]
AddTransparentClass --> HideElements
HideElements --> AppendToBody[附加到 body]
AppendToBody --> SetPosition[设置位置]
SetPosition --> CalcViewport[计算视口]
CalcViewport --> SetCanvasZoom[设置 Canvas 缩放]
SetCanvasZoom --> RequestFrame[请求帧]
RequestFrame --> WaitRender[等待渲染完成]
WaitRender --> CalcTiles[计算分块数量]
CalcTiles --> CreateFinalCanvas[创建最终 Canvas]
CreateFinalCanvas --> RenderLoop[渲染循环]
RenderLoop --> SetViewport[设置视口位置]
SetViewport --> RequestTileFrame[请求分块帧]
RequestTileFrame --> WaitTile[等待渲染]
WaitTile --> CapturePage[捕获页面]
CapturePage --> ConvertToPNG[转换为 PNG]
ConvertToPNG --> DrawToFinal[绘制到最终 Canvas]
DrawToFinal --> CheckNext{还有分块?}
CheckNext -->|是| SetViewport
CheckNext -->|否| ConvertFinal[转换最终 Canvas]
ConvertFinal --> ToBlob[转换为 Blob]
ToBlob --> ToArrayBuffer[转换为 ArrayBuffer]
ToArrayBuffer -> ToUint8Array[转换为 Uint8Array]
ToUint8Array --> ReturnResult[返回结果]
ReturnResult --> Cleanup[清理环境]
Cleanup --> CheckTransparentCleanup{透明背景?}
CheckTransparentCleanup -->|是| RestoreHiddenElements[恢复隐藏元素]
CheckTransparentCleanup -->|否| RestoreStyles[恢复样式]
RestoreHiddenElements --> RemoveBodyClasses[移除 body 类]
RemoveBodyClasses --> RestoreStyles
RestoreStyles --> ResetCanvas[重置 Canvas]
ResetCanvas --> End[结束]
ReturnNull --> End
核心方法说明
1. exportCurrentNote()
位置:main.ts:75-89
功能:导出当前活动的文件
- 获取活动文件
- 验证文件类型(md 或 canvas)
- 调用 exportNote()
2. exportNote()
位置:main.ts:92-135
功能:导出单个文件的主入口
- 判断文件类型并分发到不同的处理逻辑
- Markdown 文件:读取内容 → 处理资源 → 计算路径 → 写入文件
- Canvas 文件:调用 exportCanvasFile()
3. exportCanvasFile()
位置:main.ts:138-192
功能:专门处理 Canvas 文件导出
- 渲染 Canvas 为 PNG 图片(支持透明背景)
- 保存图片到 assetsPath(扁平化存储)
- 计算 .canvas.md 文件路径
- 使用配置的嵌入模板生成 Markdown 内容
- 写入文件
路径规则示例:
// 保留文件夹 + 嵌套
notes/测试用.canvas → docs/notes/测试用.canvas/index.md
// 仅嵌套
notes/测试用.canvas → docs/测试用.canvas/index.md
// 保留文件夹
notes/测试用.canvas → docs/notes/测试用.canvas.md
// 默认
notes/测试用.canvas → docs/测试用.canvas.md
4. processAssets()
位置:main.ts:195-308
功能:扫描并处理 Markdown 文件中的资源
处理流程:
- 使用 metadataCache 获取文件的 embeds 和 links
- 分类资源:Canvas 文件 vs 普通资源
- 处理 Canvas:渲染为图片并更新链接为
<Viewer>组件 - 复制普通资源到 assetsPath
- 更新内容中的所有资源链接
链接格式支持:
- Markdown 格式:
 - Wikilink 格式:
![[path]]
5. renderCanvas()
位置:main.ts:368-556
功能:使用 Obsidian 内部 API 将 Canvas 渲染为 PNG 图片
技术要点:
- 使用 Electron 的
webContents.capturePage()API - 分块渲染:处理大型 Canvas(避免超过 16284px 限制)
- 缩放校正:考虑系统缩放和设备像素比
- 像素级精确:使用 binary 模式保留原始格式
- 透明背景:通过临时 CSS 类和 DOM 操作实现透明背景导出
- 智能隐藏:导出时临时隐藏页面其他元素,确保只捕获 Canvas 内容
分块渲染算法:
// 计算分块数量
const cols = Math.ceil((bbox.maxX - bbox.minX) / tileWidth);
const rows = Math.ceil((bbox.maxY - bbox.minY) / tileHeight);
// 逐个渲染分块并拼接
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
// 设置视口位置
canvas.x = canvas.tx = startX + col * tileWidth;
canvas.y = canvas.ty = startY + row * tileHeight;
// 捕获并绘制
const capturedData = await webContents.capturePage(...);
ctx.drawImage(img, col * O * s, row * F * s);
}
}
6. updateLinksInContent()
位置:main.ts:311-364
功能:更新内容中的资源链接
智能匹配: 尝试多种可能的路径格式:
- 完整路径:
notes/image.png - 相对路径:
image.png - URL 编码:
notes%2Fimage.png - 文件名:
image.png
统一输出:
所有资源链接转换为根路径:/filename.png
7. updateCanvasLinksInContent()
位置:main.ts:593-648
功能:将 Canvas 链接转换为 <Viewer> 组件
转换规则:
// 默认模板:
 → 
![[测试用.canvas]] → 
// 自定义模板:<Viewer src="{url}" />
 → <Viewer src="/测试用.canvas.png" />
![[测试用.canvas]] → <Viewer src="/测试用.canvas.png" />
配置选项
settings.ts
interface odoxSettings {
docsPath: string; // 文档站点目录路径,默认 'docs'
assetsPath: string; // 资源目录路径,默认 'docs/public'
nestNoteFolder: boolean; // 是否为每个笔记创建文件夹,默认 true
preserveFolderStructure: boolean; // 是否保留原始文件夹结构,默认 true
transparentCanvas: boolean; // 是否使用透明背景导出 Canvas,默认 true
canvasEmbedTemplate: string; // Canvas 嵌入模板,默认 ''
}
路径规则矩阵
| preserveFolderStructure | nestNoteFolder | Markdown 输入 | Markdown 输出 | Canvas 输入 | Canvas 输出 |
|---|---|---|---|---|---|
| true | true | notes/test.md | docs/notes/test/index.md | notes/test.canvas | docs/notes/test.canvas/index.md |
| true | false | notes/test.md | docs/notes/test.md | notes/test.canvas | docs/notes/test.md |
| false | true | notes/test.md | docs/test/index.md | notes/test.canvas | docs/test.canvas/index.md |
| false | false | notes/test.md | docs/test.md | notes/test.canvas | docs/test.canvas.md |
技术实现要点
1. Binary 模式文件操作
使用 binary 模式保留文件原始格式:
// 读取
const contentBuffer = await vault.readBinary(file);
const content = new TextDecoder().decode(contentBuffer);
// 写入
const buffer = new TextEncoder().encode(content);
await vault.adapter.writeBinary(path, buffer.buffer);
2. Obsidian 元数据缓存
使用 metadataCache 解析链接:
const cache = metadataCache.getCache(sourceFile.path);
const resolvedLink = metadataCache.getFirstLinkpathDest(link, sourceFile.path);
3. Electron API 集成
使用 Obsidian 内部的 Electron API:
const electron = window.electron?.remote;
const webContents = electron?.getCurrentWebContents();
const capturedData = await webContents.capturePage({ x, y, width, height });
4. 链接解析兼容性
支持多种链接格式:
- 绝对路径和相对路径
- URL 编码和未编码
- Markdown 标准格式和 Obsidian Wikilink
5. Canvas 渲染优化
- 分块渲染:避免单个 Canvas 过大,超过 16284px 限制
- 异步处理:使用
requestAnimationFrame确保渲染完成 - 缩放校正:考虑系统 DPI 设置,确保高清输出
- 内存管理:及时释放临时对象(URL.revokeObjectURL)
- 透明背景:通过 CSS 类和 DOM 操作实现透明背景导出
- 智能隐藏:导出时临时隐藏页面其他元素,确保只捕获 Canvas 内容
文件结构
.obsidian/plugins/odox/
├── src/
│ ├── main.ts # 主插件逻辑(约 680 行)
│ └── settings.ts # 设置定义和 UI
├── notes/ # 文档和说明
│ └── implementation.md # 实现细节文档
├── manifest.json # 插件清单
├── package.json # 依赖管理
└── main.js # 编译后的插件文件
依赖项
- Obsidian API:插件框架
- Electron:Canvas 渲染(桌面版)
- TypeScript:类型安全
使用场景
- 从 Obsidian 迁移到静态站点:将个人知识库转换为 VitePress、Docusaurus 等静态网站
- 技术文档发布:在 Obsidian 中编写,发布到文档站点
- Canvas 可视化:将 Obsidian Canvas 导出为高清 PNG 图片
- 自动化工作流:结合 CI/CD 实现自动部署文档站点
技术优势
- 高质量渲染:使用 Obsidian 原生渲染引擎,确保导出内容与编辑器中一致
- 智能资源管理:自动识别、复制和更新资源文件引用
- 灵活配置:支持多种目录结构和命名规则,适应不同的项目需求
- 透明背景支持:Canvas 导出时可移除背景图案,适合嵌入各种主题的网站
- 分块渲染技术:处理大型 Canvas 文件,避免尺寸限制
未来改进方向
- 增量导出:只导出修改过的文件,提高大项目的导出效率
- 批量导出:支持一次性导出整个文件夹或整个项目
- 配置预设:保存常用的导出配置,快速切换不同项目的导出规则
- 错误恢复:更完善的错误处理和回滚机制,导出失败时恢复原状态
- 性能优化:并行处理多个文件,利用多核 CPU 加速导出过程
- 进度显示:为批量导出操作提供进度条和详细的状态信息
- 导出历史:记录导出历史,支持撤销和回滚操作