feat: Rename plugin to Odox and enhance documentation for static site export features
This commit is contained in:
85
README.md
85
README.md
@@ -1,71 +1,96 @@
|
|||||||
# VitePress Exporter - Obsidian Plugin
|
# Odox - Obsidian Plugin
|
||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
|
|
||||||
这个 Obsidian 插件用于自动化发布 Obsidian 笔记到 VitePress 网站:
|
这个 Obsidian 插件用于自动化发布 Obsidian 笔记到静态文档网站(如 VitePress、Docusaurus 等):
|
||||||
|
|
||||||
- ✅ 将当前 markdown 文件导出到 `docs/` 目录
|
- ✅ 将当前 markdown 文件导出到 `docs/` 目录
|
||||||
- ✅ 自动复制引用的资源文件到 `docs/assets/` 目录
|
- ✅ 自动复制引用的资源文件到 `docs/public/` 目录
|
||||||
- ✅ 更新 markdown 中的资源路径
|
- ✅ 更新 markdown 中的资源路径为根路径引用
|
||||||
- 🚧 Canvas 文件渲染(待实现)
|
- ✅ Canvas 文件渲染为高清 PNG 图片
|
||||||
|
- ✅ 支持 Canvas 透明背景导出
|
||||||
|
- ✅ 灵活的目录结构和命名规则配置
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
插件已经安装在这个项目的 `.obsidian/plugins/vitepress-exporter/` 目录下。
|
插件已经安装在这个项目的 `.obsidian/plugins/odox/` 目录下。
|
||||||
|
|
||||||
1. 在 Obsidian 中打开这个项目
|
1. 在 Obsidian 中打开这个项目
|
||||||
2. 进入设置 → 第三方插件
|
2. 进入设置 → 第三方插件
|
||||||
3. 启用 "VitePress Exporter" 插件
|
3. 启用 "Odox" 插件
|
||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
|
||||||
### 方式 1:命令面板
|
### 方式 1:命令面板
|
||||||
1. 打开一个 markdown 文件
|
1. 打开一个 markdown 或 canvas 文件
|
||||||
2. 按 `Ctrl+P` (或 `Cmd+P`) 打开命令面板
|
2. 按 `Ctrl+P` (或 `Cmd+P`) 打开命令面板
|
||||||
3. 输入 "Export to VitePress"
|
3. 输入 "Export to docs"
|
||||||
4. 选择 "Export current note to VitePress"
|
4. 选择 "Export current note to docs"
|
||||||
|
|
||||||
### 方式 2:功能区按钮
|
### 方式 2:文件菜单
|
||||||
- 点击左侧功能区栏的上传图标按钮
|
- 在文件管理器中右键点击文件
|
||||||
|
- 选择 "Export to docs" 选项
|
||||||
|
|
||||||
### 方式 3:快捷键
|
### 方式 3:编辑器菜单
|
||||||
|
- 在编辑器中右键点击
|
||||||
|
- 选择 "Export to docs" 选项
|
||||||
|
|
||||||
|
### 方式 4:快捷键
|
||||||
- 可以在 Obsidian 的快捷键设置中为导出命令设置快捷键
|
- 可以在 Obsidian 的快捷键设置中为导出命令设置快捷键
|
||||||
|
|
||||||
## 配置
|
## 配置
|
||||||
|
|
||||||
在插件设置中可以配置:
|
在插件设置中可以配置:
|
||||||
|
|
||||||
- **Docs path**: 导出的 markdown 文件存放目录(默认:`docs`)
|
- **Wiki path**: 导出的 markdown 文件存放目录(默认:`docs`)
|
||||||
- **Assets path**: 资源文件存放目录(默认:`docs/assets`)
|
- **Assets path**: 资源文件存放目录(默认:`docs/public`)
|
||||||
|
- **Nest note folder**: 是否为每个笔记创建文件夹(默认:开启)
|
||||||
|
- **Preserve folder structure**: 是否保留原始文件夹结构(默认:开启)
|
||||||
|
- **Transparent canvas background**: 导出 Canvas 时使用透明背景(默认:开启)
|
||||||
|
- **Canvas embed template**: Canvas 嵌入模板,使用 `{url}` 作为图片路径占位符
|
||||||
|
|
||||||
## 工作流程
|
## 工作流程
|
||||||
|
|
||||||
1. 用户在 Obsidian 中编写笔记
|
1. 用户在 Obsidian 中编写笔记
|
||||||
2. 笔记中引用的资源(图片、PDF 等)可以放在任意位置
|
2. 笔记中引用的资源(图片、Canvas 等)可以放在任意位置
|
||||||
3. 点击导出按钮
|
3. 触发导出命令
|
||||||
4. 插件自动:
|
4. 插件自动:
|
||||||
- 将 markdown 文件复制到 `docs/` 目录
|
- 将 markdown 文件复制到 `docs/` 目录
|
||||||
- 扫描并复制所有引用的资源到 `docs/assets/` 目录
|
- 扫描并复制所有引用的资源到 `docs/public/` 目录
|
||||||
- 更新 markdown 中的资源路径
|
- 将 Canvas 文件渲染为高清 PNG 图片
|
||||||
|
- 更新 markdown 中的资源路径为根路径引用(如 `/image.png`)
|
||||||
|
|
||||||
|
## Canvas 导出
|
||||||
|
|
||||||
|
Canvas 文件会通过以下方式处理:
|
||||||
|
|
||||||
|
1. 使用 Obsidian 内部 API 渲染 Canvas 为 PNG 图片
|
||||||
|
2. 保存图片到 `docs/public/` 目录
|
||||||
|
3. 生成对应的 `.canvas.md` 文件,使用配置的嵌入模板
|
||||||
|
4. 支持透明背景导出(移除 Obsidian 的点状背景)
|
||||||
|
|
||||||
## 当前状态
|
## 当前状态
|
||||||
|
|
||||||
- ✅ 基础插件框架
|
- ✅ 基础插件框架
|
||||||
- ✅ 文件导出功能
|
- ✅ Markdown 文件导出功能
|
||||||
- ✅ 资源文件自动复制
|
- ✅ 资源文件自动复制和路径更新
|
||||||
- 🚧 Canvas 文件渲染(开发中)
|
- ✅ Canvas 文件渲染为 PNG 图片
|
||||||
|
- ✅ Canvas 透明背景支持
|
||||||
|
- ✅ 分块渲染大型 Canvas(避免尺寸限制)
|
||||||
|
- ✅ 灵活的路径规则配置
|
||||||
|
|
||||||
## Canvas 渲染计划
|
## 技术特点
|
||||||
|
|
||||||
对于 `.canvas` 文件,计划实现的方案:
|
- **Binary 模式文件操作**:保留原始文件格式(包括行尾空格)
|
||||||
|
- **智能链接解析**:支持多种链接格式(Markdown、Wikilink、相对路径、绝对路径)
|
||||||
1. **方案 A(优先)**:基于 Obsidian 自己的渲染(探索中)
|
- **分块渲染**:对大型 Canvas 进行分块渲染,避免超过 16284px 的尺寸限制
|
||||||
2. **方案 B(备选)**:使用 Headless Browser 手动渲染 JSON Canvas
|
- **缩放校正**:考虑系统 DPI 和缩放设置,确保高清输出
|
||||||
|
- **Electron 集成**:使用 Obsidian 内部的 Electron API 实现 Canvas 截图
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd .obsidian/plugins/vitepress-exporter
|
cd .obsidian/plugins/odox
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
npm install
|
npm install
|
||||||
@@ -79,6 +104,8 @@ npm run build
|
|||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
- 确保项目中有 `docs/` 目录
|
- 确保项目中有配置的目标目录
|
||||||
- 资源文件会被复制,不会移动原文件
|
- 资源文件会被复制,不会移动原文件
|
||||||
- 如果目标文件已存在,会被覆盖
|
- 如果目标文件已存在,会被覆盖
|
||||||
|
- Canvas 渲染仅支持桌面版 Obsidian(需要 Electron)
|
||||||
|
- 生成的 `<Viewer>` 组件需要在静态站点中配置对应组件
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"id": "vitepress-exporter",
|
"id": "odox",
|
||||||
"name": "VitePress Exporter",
|
"name": "Odox",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "Export Obsidian notes to VitePress with asset management",
|
"description": "Export Obsidian notes to static docs tools with asset management",
|
||||||
"author": "nan",
|
"author": "nan",
|
||||||
"authorUrl": "https://github.com/icrdr",
|
"authorUrl": "https://github.com/icrdr",
|
||||||
"isDesktopOnly": false
|
"isDesktopOnly": false
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
## 核心功能
|
## 核心功能
|
||||||
|
|
||||||
1. **Markdown 文件导出**:将 `.md` 文件导出到 VitePress 的 docs 目录,自动处理资源文件
|
1. **Markdown 文件导出**:将 `.md` 文件导出到静态文档站点目录,自动处理资源文件
|
||||||
2. **Canvas 文件导出**:将 `.canvas` 文件渲染为图片并生成对应的 `.canvas.md` 文件
|
2. **Canvas 文件导出**:将 `.canvas` 文件渲染为高清 PNG 图片并生成对应的 `.canvas.md` 文件
|
||||||
3. **资源文件管理**:自动复制和处理图片、Canvas 等资源文件
|
3. **资源文件管理**:自动复制和处理图片、Canvas 等资源文件到统一目录
|
||||||
4. **路径规则配置**:支持多种目录结构和命名规则
|
4. **路径规则配置**:支持多种目录结构和命名规则,灵活配置输出路径
|
||||||
|
5. **透明背景支持**:Canvas 导出时可选择透明背景,移除 Obsidian 的点状背景图案
|
||||||
|
|
||||||
## 主要流程
|
## 主要流程
|
||||||
|
|
||||||
@@ -115,7 +116,11 @@ graph TB
|
|||||||
|
|
||||||
PrepareCanvas --> DeselectAll[取消选择]
|
PrepareCanvas --> DeselectAll[取消选择]
|
||||||
DeselectAll --> AddScreenshotting[添加截图样式]
|
DeselectAll --> AddScreenshotting[添加截图样式]
|
||||||
AddScreenshotting --> AppendToBody[附加到 body]
|
AddScreenshotting --> CheckTransparent{透明背景?}
|
||||||
|
CheckTransparent -->|是| AddTransparentClass[添加透明样式类]
|
||||||
|
CheckTransparent -->|否| HideElements[隐藏页面元素]
|
||||||
|
AddTransparentClass --> HideElements
|
||||||
|
HideElements --> AppendToBody[附加到 body]
|
||||||
|
|
||||||
AppendToBody --> SetPosition[设置位置]
|
AppendToBody --> SetPosition[设置位置]
|
||||||
SetPosition --> CalcViewport[计算视口]
|
SetPosition --> CalcViewport[计算视口]
|
||||||
@@ -143,7 +148,11 @@ graph TB
|
|||||||
ToUint8Array --> ReturnResult[返回结果]
|
ToUint8Array --> ReturnResult[返回结果]
|
||||||
|
|
||||||
ReturnResult --> Cleanup[清理环境]
|
ReturnResult --> Cleanup[清理环境]
|
||||||
Cleanup --> RestoreStyles[恢复样式]
|
Cleanup --> CheckTransparentCleanup{透明背景?}
|
||||||
|
CheckTransparentCleanup -->|是| RestoreHiddenElements[恢复隐藏元素]
|
||||||
|
CheckTransparentCleanup -->|否| RestoreStyles[恢复样式]
|
||||||
|
RestoreHiddenElements --> RemoveBodyClasses[移除 body 类]
|
||||||
|
RemoveBodyClasses --> RestoreStyles
|
||||||
RestoreStyles --> ResetCanvas[重置 Canvas]
|
RestoreStyles --> ResetCanvas[重置 Canvas]
|
||||||
ResetCanvas --> End[结束]
|
ResetCanvas --> End[结束]
|
||||||
|
|
||||||
@@ -172,10 +181,10 @@ graph TB
|
|||||||
**位置**:main.ts:138-192
|
**位置**:main.ts:138-192
|
||||||
|
|
||||||
**功能**:专门处理 Canvas 文件导出
|
**功能**:专门处理 Canvas 文件导出
|
||||||
1. 渲染 Canvas 为 PNG 图片
|
1. 渲染 Canvas 为 PNG 图片(支持透明背景)
|
||||||
2. 保存图片到 assetsPath(扁平化)
|
2. 保存图片到 assetsPath(扁平化存储)
|
||||||
3. 计算 .canvas.md 文件路径
|
3. 计算 .canvas.md 文件路径
|
||||||
4. 生成包含 `<Viewer>` 组件的 Markdown 内容
|
4. 使用配置的嵌入模板生成 Markdown 内容
|
||||||
5. 写入文件
|
5. 写入文件
|
||||||
|
|
||||||
**路径规则示例**:
|
**路径规则示例**:
|
||||||
@@ -219,6 +228,8 @@ notes/测试用.canvas → docs/测试用.canvas.md
|
|||||||
- 分块渲染:处理大型 Canvas(避免超过 16284px 限制)
|
- 分块渲染:处理大型 Canvas(避免超过 16284px 限制)
|
||||||
- 缩放校正:考虑系统缩放和设备像素比
|
- 缩放校正:考虑系统缩放和设备像素比
|
||||||
- 像素级精确:使用 binary 模式保留原始格式
|
- 像素级精确:使用 binary 模式保留原始格式
|
||||||
|
- 透明背景:通过临时 CSS 类和 DOM 操作实现透明背景导出
|
||||||
|
- 智能隐藏:导出时临时隐藏页面其他元素,确保只捕获 Canvas 内容
|
||||||
|
|
||||||
**分块渲染算法**:
|
**分块渲染算法**:
|
||||||
```typescript
|
```typescript
|
||||||
@@ -262,6 +273,11 @@ for (let row = 0; row < rows; row++) {
|
|||||||
|
|
||||||
**转换规则**:
|
**转换规则**:
|
||||||
```markdown
|
```markdown
|
||||||
|
// 默认模板:
|
||||||
|
 → 
|
||||||
|
![[测试用.canvas]] → 
|
||||||
|
|
||||||
|
// 自定义模板:<Viewer src="{url}" />
|
||||||
 → <Viewer src="/测试用.canvas.png" />
|
 → <Viewer src="/测试用.canvas.png" />
|
||||||
![[测试用.canvas]] → <Viewer src="/测试用.canvas.png" />
|
![[测试用.canvas]] → <Viewer src="/测试用.canvas.png" />
|
||||||
```
|
```
|
||||||
@@ -271,22 +287,24 @@ for (let row = 0; row < rows; row++) {
|
|||||||
### settings.ts
|
### settings.ts
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
interface vitepressExporterSettings {
|
interface odoxSettings {
|
||||||
docsPath: string; // VitePress docs 目录路径,默认 'docs'
|
docsPath: string; // 文档站点目录路径,默认 'docs'
|
||||||
assetsPath: string; // 资源目录路径,默认 'docs/public'
|
assetsPath: string; // 资源目录路径,默认 'docs/public'
|
||||||
nestNoteFolder: boolean; // 是否为每个笔记创建文件夹,默认 true
|
nestNoteFolder: boolean; // 是否为每个笔记创建文件夹,默认 true
|
||||||
preserveFolderStructure: boolean; // 是否保留原始文件夹结构,默认 true
|
preserveFolderStructure: boolean; // 是否保留原始文件夹结构,默认 true
|
||||||
|
transparentCanvas: boolean; // 是否使用透明背景导出 Canvas,默认 true
|
||||||
|
canvasEmbedTemplate: string; // Canvas 嵌入模板,默认 ''
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 路径规则矩阵
|
### 路径规则矩阵
|
||||||
|
|
||||||
| preserveFolderStructure | nestNoteFolder | 输入 | 输出 |
|
| preserveFolderStructure | nestNoteFolder | Markdown 输入 | Markdown 输出 | Canvas 输入 | Canvas 输出 |
|
||||||
|---------|---------|------|------|
|
|---------|---------|------|------|------|------|
|
||||||
| true | true | notes/test.md | docs/notes/test/index.md |
|
| 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 |
|
| 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 |
|
| 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 |
|
| false | false | notes/test.md | docs/test.md | notes/test.canvas | docs/test.canvas.md |
|
||||||
|
|
||||||
## 技术实现要点
|
## 技术实现要点
|
||||||
|
|
||||||
@@ -329,21 +347,25 @@ const capturedData = await webContents.capturePage({ x, y, width, height });
|
|||||||
|
|
||||||
### 5. Canvas 渲染优化
|
### 5. Canvas 渲染优化
|
||||||
|
|
||||||
- **分块渲染**:避免单个 Canvas 过大
|
- **分块渲染**:避免单个 Canvas 过大,超过 16284px 限制
|
||||||
- **异步处理**:使用 `requestAnimationFrame` 确保渲染完成
|
- **异步处理**:使用 `requestAnimationFrame` 确保渲染完成
|
||||||
- **缩放校正**:考虑系统 DPI 设置
|
- **缩放校正**:考虑系统 DPI 设置,确保高清输出
|
||||||
- **内存管理**:及时释放临时对象(URL.revokeObjectURL)
|
- **内存管理**:及时释放临时对象(URL.revokeObjectURL)
|
||||||
|
- **透明背景**:通过 CSS 类和 DOM 操作实现透明背景导出
|
||||||
|
- **智能隐藏**:导出时临时隐藏页面其他元素,确保只捕获 Canvas 内容
|
||||||
|
|
||||||
## 文件结构
|
## 文件结构
|
||||||
|
|
||||||
```
|
```
|
||||||
.obsidian/plugins/vitepress-exporter/
|
.obsidian/plugins/odox/
|
||||||
├── src/
|
├── src/
|
||||||
│ ├── main.ts # 主插件逻辑(650 行)
|
│ ├── main.ts # 主插件逻辑(约 680 行)
|
||||||
│ └── settings.ts # 设置定义和 UI
|
│ └── settings.ts # 设置定义和 UI
|
||||||
├── notes/ # 文档和说明
|
├── notes/ # 文档和说明
|
||||||
|
│ └── implementation.md # 实现细节文档
|
||||||
├── manifest.json # 插件清单
|
├── manifest.json # 插件清单
|
||||||
└── package.json # 依赖管理
|
├── package.json # 依赖管理
|
||||||
|
└── main.js # 编译后的插件文件
|
||||||
```
|
```
|
||||||
|
|
||||||
## 依赖项
|
## 依赖项
|
||||||
@@ -354,22 +376,25 @@ const capturedData = await webContents.capturePage({ x, y, width, height });
|
|||||||
|
|
||||||
## 使用场景
|
## 使用场景
|
||||||
|
|
||||||
1. **从 Obsidian 迁移到 VitePress**:将个人知识库转换为静态网站
|
1. **从 Obsidian 迁移到静态站点**:将个人知识库转换为 VitePress、Docusaurus 等静态网站
|
||||||
2. **技术文档发布**:在 Obsidian 中编写,发布到 VitePress
|
2. **技术文档发布**:在 Obsidian 中编写,发布到文档站点
|
||||||
3. **Canvas 可视化**:将 Obsidian Canvas 导出为交互式图片
|
3. **Canvas 可视化**:将 Obsidian Canvas 导出为高清 PNG 图片
|
||||||
4. **自动化工作流**:结合 CI/CD 实现自动部署
|
4. **自动化工作流**:结合 CI/CD 实现自动部署文档站点
|
||||||
|
|
||||||
## 局限性
|
## 技术优势
|
||||||
|
|
||||||
1. **平台依赖**:Canvas 渲染仅支持桌面版 Obsidian(需要 Electron)
|
1. **高质量渲染**:使用 Obsidian 原生渲染引擎,确保导出内容与编辑器中一致
|
||||||
2. **VitePress 特定**:生成的 `<Viewer>` 组件需要在 VitePress 中配置
|
2. **智能资源管理**:自动识别、复制和更新资源文件引用
|
||||||
3. **链接转换**:复杂的链接语法可能需要手动调整
|
3. **灵活配置**:支持多种目录结构和命名规则,适应不同的项目需求
|
||||||
4. **大型 Canvas**:分块渲染可能耗时较长
|
4. **透明背景支持**:Canvas 导出时可移除背景图案,适合嵌入各种主题的网站
|
||||||
|
5. **分块渲染技术**:处理大型 Canvas 文件,避免尺寸限制
|
||||||
|
|
||||||
## 未来改进方向
|
## 未来改进方向
|
||||||
|
|
||||||
1. **增量导出**:只导出修改过的文件
|
1. **增量导出**:只导出修改过的文件,提高大项目的导出效率
|
||||||
2. **批量导出**:支持一次性导出整个文件夹
|
2. **批量导出**:支持一次性导出整个文件夹或整个项目
|
||||||
3. **配置预设**:保存常用的导出配置
|
3. **配置预设**:保存常用的导出配置,快速切换不同项目的导出规则
|
||||||
4. **错误恢复**:更完善的错误处理和回滚机制
|
4. **错误恢复**:更完善的错误处理和回滚机制,导出失败时恢复原状态
|
||||||
5. **性能优化**:并行处理多个文件
|
5. **性能优化**:并行处理多个文件,利用多核 CPU 加速导出过程
|
||||||
|
6. **进度显示**:为批量导出操作提供进度条和详细的状态信息
|
||||||
|
7. **导出历史**:记录导出历史,支持撤销和回滚操作
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vitepress-exporter",
|
"name": "odox",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Export Obsidian notes to VitePress",
|
"description": "Export Obsidian notes to static docs tools",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
202
src/main.ts
202
src/main.ts
@@ -1,18 +1,18 @@
|
|||||||
import { Plugin, TFile, Notice, Menu, MenuItem } from 'obsidian';
|
import { Plugin, TFile, Notice, Menu, MenuItem } from 'obsidian';
|
||||||
import { DEFAULT_SETTINGS, vitepressExporterSettings, vitepressExporterSettingTab } from "./settings";
|
import { DEFAULT_SETTINGS, odoxSettings, odoxSettingTab } from "./settings";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default class vitepressExporterPlugin extends Plugin {
|
export default class odoxPlugin extends Plugin {
|
||||||
settings!: vitepressExporterSettings;
|
settings!: odoxSettings;
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
await this.loadSettings();
|
await this.loadSettings();
|
||||||
|
|
||||||
// 添加导出命令
|
// 添加导出命令
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
id: 'export-to-vitepress',
|
id: 'export-to-docs',
|
||||||
name: 'Export current note to vitepress',
|
name: 'Export current note to docs',
|
||||||
callback: () => {
|
callback: () => {
|
||||||
void this.exportCurrentNote();
|
void this.exportCurrentNote();
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
|
|
||||||
menu.addItem((item: MenuItem) => {
|
menu.addItem((item: MenuItem) => {
|
||||||
item
|
item
|
||||||
.setTitle("Export to vitepress")
|
.setTitle("Export to docs")
|
||||||
.setIcon("upload")
|
.setIcon("upload")
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
void this.exportNote(file);
|
void this.exportNote(file);
|
||||||
@@ -43,7 +43,7 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
this.app.workspace.on("editor-menu", (menu: Menu) => {
|
this.app.workspace.on("editor-menu", (menu: Menu) => {
|
||||||
menu.addItem((item: MenuItem) => {
|
menu.addItem((item: MenuItem) => {
|
||||||
item
|
item
|
||||||
.setTitle("Export to vitepress")
|
.setTitle("Export to docs")
|
||||||
.setIcon("upload")
|
.setIcon("upload")
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
void this.exportCurrentNote();
|
void this.exportCurrentNote();
|
||||||
@@ -53,13 +53,13 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 添加设置面板
|
// 添加设置面板
|
||||||
this.addSettingTab(new vitepressExporterSettingTab(this.app, this));
|
this.addSettingTab(new odoxSettingTab(this.app, this));
|
||||||
|
|
||||||
console.warn('vitepress Exporter plugin loaded');
|
console.warn('Odox plugin loaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
onunload() {
|
onunload() {
|
||||||
console.warn('vitepress Exporter plugin unloaded');
|
console.warn('Odox plugin unloaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadSettings() {
|
async loadSettings() {
|
||||||
@@ -155,7 +155,6 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
// 写入渲染的图片
|
// 写入渲染的图片
|
||||||
await vault.adapter.writeBinary(imagePath, renderedImage.buffer);
|
await vault.adapter.writeBinary(imagePath, renderedImage.buffer);
|
||||||
|
|
||||||
console.debug(`Successfully rendered canvas: ${file.path} -> ${imagePath}`);
|
|
||||||
|
|
||||||
// 3. 计算 .canvas.md 文件的输出路径(复用现有逻辑)
|
// 3. 计算 .canvas.md 文件的输出路径(复用现有逻辑)
|
||||||
const fileNameWithoutExt = file.name.replace('.canvas', '');
|
const fileNameWithoutExt = file.name.replace('.canvas', '');
|
||||||
@@ -181,7 +180,7 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
await vault.adapter.mkdir(targetDir);
|
await vault.adapter.mkdir(targetDir);
|
||||||
|
|
||||||
// 5. 生成 .canvas.md 内容
|
// 5. 生成 .canvas.md 内容
|
||||||
const content = `<Viewer src="/${imageFileName}" />`;
|
const content = this.settings.canvasEmbedTemplate.replace('{url}', `/${imageFileName}`);
|
||||||
|
|
||||||
// 6. 写入 .canvas.md 文件
|
// 6. 写入 .canvas.md 文件
|
||||||
const contentBuffer = new TextEncoder().encode(content);
|
const contentBuffer = new TextEncoder().encode(content);
|
||||||
@@ -195,17 +194,9 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
async processAssets(content: string, sourceFile: TFile): Promise<string> {
|
async processAssets(content: string, sourceFile: TFile): Promise<string> {
|
||||||
const { vault, metadataCache } = this.app;
|
const { vault, metadataCache } = this.app;
|
||||||
|
|
||||||
console.debug('Processing assets for file:', sourceFile.path);
|
|
||||||
|
|
||||||
// 使用 Obsidian API 获取文件的所有嵌入和链接
|
// 使用 Obsidian API 获取文件的所有嵌入和链接
|
||||||
const cache = metadataCache.getCache(sourceFile.path);
|
const cache = metadataCache.getCache(sourceFile.path);
|
||||||
|
|
||||||
console.debug('Cache:', cache);
|
|
||||||
console.debug('Embeds:', cache?.embeds);
|
|
||||||
console.debug('Links:', cache?.links);
|
|
||||||
|
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
console.debug('No cache found for file');
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,9 +206,7 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
// 收集嵌入文件(图片等)
|
// 收集嵌入文件(图片等)
|
||||||
if (cache.embeds) {
|
if (cache.embeds) {
|
||||||
for (const embed of cache.embeds) {
|
for (const embed of cache.embeds) {
|
||||||
console.debug('Processing embed:', embed);
|
|
||||||
const resolvedLink = metadataCache.getFirstLinkpathDest(embed.link, sourceFile.path);
|
const resolvedLink = metadataCache.getFirstLinkpathDest(embed.link, sourceFile.path);
|
||||||
console.debug('Resolved link for embed:', resolvedLink?.path);
|
|
||||||
if (resolvedLink) {
|
if (resolvedLink) {
|
||||||
// 检查是否是 canvas 文件
|
// 检查是否是 canvas 文件
|
||||||
if (resolvedLink.extension === 'canvas') {
|
if (resolvedLink.extension === 'canvas') {
|
||||||
@@ -240,10 +229,8 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('Assets to copy:', Array.from(assetsToCopy));
|
// 处理 Canvas 文件:渲染为图片,并收集映射关系
|
||||||
console.debug('Canvas files to render:', Array.from(canvasFiles));
|
const canvasImageMap = new Map<string, string>();
|
||||||
|
|
||||||
// 处理 Canvas 文件:渲染为图片
|
|
||||||
for (const canvasPath of canvasFiles) {
|
for (const canvasPath of canvasFiles) {
|
||||||
try {
|
try {
|
||||||
const canvasFile = vault.getAbstractFileByPath(canvasPath);
|
const canvasFile = vault.getAbstractFileByPath(canvasPath);
|
||||||
@@ -260,25 +247,24 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
// 写入渲染的图片
|
// 写入渲染的图片
|
||||||
await vault.adapter.writeBinary(imagePath, renderedImage.buffer);
|
await vault.adapter.writeBinary(imagePath, renderedImage.buffer);
|
||||||
|
|
||||||
// 更新内容中的 canvas 引用,使用相对路径 assets/xxx.png
|
// 收集 canvas 文件到图片的映射关系
|
||||||
content = this.updateCanvasLinksInContent(content, canvasFile, canvasPath, imageFileName);
|
canvasImageMap.set(canvasPath, imageFileName);
|
||||||
|
|
||||||
console.debug(`Successfully rendered canvas: ${canvasPath} -> ${imagePath}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to render canvas ${canvasPath}:`, error);
|
console.error(`Failed to render canvas ${canvasPath}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const activeLeaf = this.app.workspace.getLeaf();
|
// 统一更新所有 canvas 引用
|
||||||
await activeLeaf.openFile(sourceFile);
|
content = this.updateCanvasInContent(content, canvasImageMap, sourceFile);
|
||||||
|
|
||||||
// 复制资源文件到 docs/assets
|
// 复制资源文件到 docs/assets
|
||||||
for (const assetPath of assetsToCopy) {
|
for (const assetPath of assetsToCopy) {
|
||||||
try {
|
try {
|
||||||
const sourceFile = vault.getAbstractFileByPath(assetPath);
|
const sourceFile = vault.getAbstractFileByPath(assetPath);
|
||||||
console.debug('Source file for asset:', sourceFile);
|
|
||||||
|
|
||||||
if (sourceFile instanceof TFile) {
|
if (sourceFile instanceof TFile) {
|
||||||
// 使用 binary 读取模式来保留文件原始内容
|
// 使用 binary 读取模式来保留文件原始内容
|
||||||
@@ -286,7 +272,6 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
const fileName = sourceFile.name;
|
const fileName = sourceFile.name;
|
||||||
const destPath = `${this.settings.assetsPath}/${fileName}`;
|
const destPath = `${this.settings.assetsPath}/${fileName}`;
|
||||||
|
|
||||||
console.debug('Copying asset:', assetPath, '->', destPath);
|
|
||||||
|
|
||||||
// 确保目标目录存在
|
// 确保目标目录存在
|
||||||
await vault.adapter.mkdir(this.settings.assetsPath);
|
await vault.adapter.mkdir(this.settings.assetsPath);
|
||||||
@@ -297,7 +282,6 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
// 更新内容中的资源路径(使用更智能的替换)
|
// 更新内容中的资源路径(使用更智能的替换)
|
||||||
content = this.updateLinksInContent(content, sourceFile, assetPath, fileName);
|
content = this.updateLinksInContent(content, sourceFile, assetPath, fileName);
|
||||||
|
|
||||||
console.debug(`Successfully copied asset: ${assetPath} -> ${destPath}`);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to copy asset ${assetPath}:`, error);
|
console.error(`Failed to copy asset ${assetPath}:`, error);
|
||||||
@@ -327,7 +311,6 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
encodeURI(sourceFile.name)
|
encodeURI(sourceFile.name)
|
||||||
];
|
];
|
||||||
|
|
||||||
console.debug('Trying to replace links for paths:', possiblePaths);
|
|
||||||
|
|
||||||
// 使用根路径: /image.png
|
// 使用根路径: /image.png
|
||||||
const assetsRef = `/${newFileName}`;
|
const assetsRef = `/${newFileName}`;
|
||||||
@@ -338,22 +321,20 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
// 检查这个路径是否匹配我们的目标文件
|
// 检查这个路径是否匹配我们的目标文件
|
||||||
for (const testPath of possiblePaths) {
|
for (const testPath of possiblePaths) {
|
||||||
if (path === testPath || path === testPath.replace(/^\//, '')) {
|
if (path === testPath || path === testPath.replace(/^\//, '')) {
|
||||||
console.debug(`Found matching link: ${path} -> ${assetsRef}`);
|
|
||||||
return ``;
|
return ``;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return match;
|
return match;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 替换 wikilink 格式: ![[path]] -> ![[assets/newfile.png]]
|
// 替换 docslink 格式: ![[path]] -> ![[assets/newfile.png]]
|
||||||
const wikilinkRegex = /!\[\[([^\]]+)\]\]/g;
|
const docslinkRegex = /!\[\[([^\]]+)\]\]/g;
|
||||||
content = content.replace(wikilinkRegex, (match, link: string) => {
|
content = content.replace(docslinkRegex, (match, link: string) => {
|
||||||
// 移除可能的别名字段
|
// 移除可能的别名字段
|
||||||
const cleanLink = link.split('|')[0]!.trim();
|
const cleanLink = link.split('|')[0]!.trim();
|
||||||
|
|
||||||
for (const testPath of possiblePaths) {
|
for (const testPath of possiblePaths) {
|
||||||
if (cleanLink === testPath || cleanLink === testPath.replace(/^\//, '')) {
|
if (cleanLink === testPath || cleanLink === testPath.replace(/^\//, '')) {
|
||||||
console.debug(`Found matching wikilink: ${cleanLink} -> ${assetsRef}`);
|
|
||||||
return `![[${assetsRef}]]`;
|
return `![[${assetsRef}]]`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -375,13 +356,11 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
const canvas = activeLeaf.view.canvas;
|
const canvas = activeLeaf.view.canvas;
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
console.debug("Could not get canvas object from view");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选项设置(基于用户要求)
|
// 选项设置
|
||||||
const zoomScale = 1.0; // 缩放比例 1.0 (100%)
|
const zoomScale = 1.0;
|
||||||
const privacyMode = false; // 不启用隐私模式
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取系统参数
|
// 获取系统参数
|
||||||
@@ -407,7 +386,6 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
const h = Math.min(2, 16284 / s / c, 16284 / s / u);
|
const h = Math.min(2, 16284 / s / c, 16284 / s / u);
|
||||||
const d = Math.min(zoomScale, h); // 实际使用的缩放比例
|
const d = Math.min(zoomScale, h); // 实际使用的缩放比例
|
||||||
|
|
||||||
console.debug(`Canvas export: scale=${d}, size=${c}x${u}`);
|
|
||||||
|
|
||||||
// Full canvas 模式 - 使用分块渲染
|
// Full canvas 模式 - 使用分块渲染
|
||||||
const e = canvas.wrapperEl;
|
const e = canvas.wrapperEl;
|
||||||
@@ -415,11 +393,28 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
// 准备 Canvas 环境
|
// 准备 Canvas 环境
|
||||||
this.deselectAll();
|
this.deselectAll();
|
||||||
e.addClass("is-screenshotting");
|
e.addClass("is-screenshotting");
|
||||||
if (privacyMode) {
|
// 如果设置了透明背景,添加自定义 CSS 类
|
||||||
e.addClass("is-text-garbled");
|
if (this.settings.transparentCanvas) {
|
||||||
|
e.addClass("canvas-transparent-export");
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
canvas.screenshotting = true;
|
canvas.screenshotting = true;
|
||||||
|
|
||||||
|
// 临时隐藏页面的其他元素,以便透明导出
|
||||||
|
const hiddenElements: HTMLElement[] = [];
|
||||||
|
if (this.settings.transparentCanvas) {
|
||||||
|
// 隐藏 body 的所有直接子元素(除了当前 canvas)
|
||||||
|
Array.from(document.body.children).forEach((child) => {
|
||||||
|
if (child !== e && child instanceof HTMLElement) {
|
||||||
|
child.classList.add('odox-hidden');
|
||||||
|
hiddenElements.push(child);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 临时设置 body 样式
|
||||||
|
document.body.classList.add('odox-capturing');
|
||||||
|
}
|
||||||
|
|
||||||
document.body.appendChild(e);
|
document.body.appendChild(e);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -462,7 +457,6 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
const startX = bbox.minX + tileWidth / 2;
|
const startX = bbox.minX + tileWidth / 2;
|
||||||
const startY = bbox.minY + tileHeight / 2;
|
const startY = bbox.minY + tileHeight / 2;
|
||||||
|
|
||||||
console.debug(`Rendering ${cols}x${rows} tiles (${cols * rows} total)`);
|
|
||||||
|
|
||||||
// 创建最终 Canvas
|
// 创建最终 Canvas
|
||||||
const finalCanvas = document.createElement("canvas");
|
const finalCanvas = document.createElement("canvas");
|
||||||
@@ -510,7 +504,6 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
URL.revokeObjectURL(imageUrl);
|
URL.revokeObjectURL(imageUrl);
|
||||||
|
|
||||||
tileIndex++;
|
tileIndex++;
|
||||||
console.debug(`Rendered tile ${tileIndex}/${totalTiles}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,10 +520,18 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
} finally {
|
} finally {
|
||||||
// 清理
|
// 清理
|
||||||
|
|
||||||
|
// 恢复隐藏的页面元素
|
||||||
|
if (this.settings.transparentCanvas) {
|
||||||
|
document.body.classList.remove('odox-capturing');
|
||||||
|
hiddenElements.forEach((el) => {
|
||||||
|
el.classList.remove('odox-hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.app.workspace.getLeaf().view?.contentEl?.appendChild(e);
|
this.app.workspace.getLeaf().view?.contentEl?.appendChild(e);
|
||||||
e.removeClass("is-screenshotting");
|
e.removeClass("is-screenshotting");
|
||||||
e.removeClass("is-text-garbled");
|
e.removeClass("canvas-transparent-export");
|
||||||
e.setCssStyles({
|
e.setCssStyles({
|
||||||
top: "",
|
top: "",
|
||||||
left: "",
|
left: "",
|
||||||
@@ -589,56 +590,83 @@ export default class vitepressExporterPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新内容中的 Canvas 链接
|
// 统一更新所有 Canvas 链接
|
||||||
updateCanvasLinksInContent(content: string, canvasFile: TFile, canvasPath: string, imageFileName: string): string {
|
updateCanvasInContent(content: string, canvasImageMap: Map<string, string>, sourceFile: TFile): string {
|
||||||
// 获取源文件的目录路径
|
// 获取源文件的目录路径
|
||||||
const sourceDir = canvasFile.path.substring(0, canvasFile.path.lastIndexOf('/'));
|
const sourceDir = sourceFile.path.substring(0, sourceFile.path.lastIndexOf('/'));
|
||||||
if (!sourceDir) return content;
|
if (!sourceDir) return content;
|
||||||
|
|
||||||
// 尝试多种可能的路径格式
|
// 使用根路径: /filename.png
|
||||||
const possiblePaths = [
|
const assetsRef = (imageFileName: string) => `/${imageFileName}`;
|
||||||
// 完整路径
|
|
||||||
canvasPath,
|
|
||||||
// 相对于源文件的路径
|
|
||||||
canvasPath.substring(sourceDir.length + 1),
|
|
||||||
// URL 编码版本
|
|
||||||
encodeURI(canvasPath),
|
|
||||||
encodeURI(canvasPath.substring(sourceDir.length + 1)),
|
|
||||||
// 文件名(包括和不包括 .canvas 扩展名)
|
|
||||||
canvasFile.name,
|
|
||||||
canvasFile.name.replace('.canvas', ''),
|
|
||||||
encodeURI(canvasFile.name),
|
|
||||||
encodeURI(canvasFile.name.replace('.canvas', ''))
|
|
||||||
];
|
|
||||||
|
|
||||||
console.debug('Trying to replace canvas links for paths:', possiblePaths);
|
// 替换 markdown 格式: !(canvas) -> 模板
|
||||||
|
|
||||||
// 使用根路径: /image.png
|
|
||||||
const assetsRef = `/${imageFileName}`;
|
|
||||||
|
|
||||||
// 替换 markdown 格式: !(canvas) -> <Viewer src="/image.png" />
|
|
||||||
const markdownRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
const markdownRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
||||||
content = content.replace(markdownRegex, (match, _alt, path) => {
|
content = content.replace(markdownRegex, (match, _alt, path) => {
|
||||||
// 检查这个路径是否匹配我们的 canvas 文件
|
// 尝试为这个路径找到对应的 canvas 文件
|
||||||
for (const testPath of possiblePaths) {
|
for (const [canvasPath, imageFileName] of canvasImageMap) {
|
||||||
if (path === testPath || path === testPath.replace(/^\//, '')) {
|
const canvasFile = this.app.vault.getAbstractFileByPath(canvasPath);
|
||||||
console.debug(`Found matching canvas link: ${path} -> <Viewer src="${assetsRef}" />`);
|
if (canvasFile instanceof TFile) {
|
||||||
return `<Viewer src="${assetsRef}" />`;
|
// 构建可能的路径格式
|
||||||
|
const possiblePaths = [
|
||||||
|
// 完整路径
|
||||||
|
canvasPath,
|
||||||
|
// 相对于源文件的路径
|
||||||
|
canvasPath.substring(sourceDir.length + 1),
|
||||||
|
// URL 编码版本
|
||||||
|
encodeURI(canvasPath),
|
||||||
|
encodeURI(canvasPath.substring(sourceDir.length + 1)),
|
||||||
|
// 文件名(包括和不包括 .canvas 扩展名)
|
||||||
|
canvasFile.name,
|
||||||
|
canvasFile.name.replace('.canvas', ''),
|
||||||
|
encodeURI(canvasFile.name),
|
||||||
|
encodeURI(canvasFile.name.replace('.canvas', ''))
|
||||||
|
];
|
||||||
|
|
||||||
|
// 检查是否匹配
|
||||||
|
for (const testPath of possiblePaths) {
|
||||||
|
if (path === testPath || path === testPath.replace(/^\//, '')) {
|
||||||
|
const replacement = this.settings.canvasEmbedTemplate.replace("{url}", assetsRef(imageFileName));
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return match;
|
return match;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 替换 wikilink 格式: ![[canvas]] -> <Viewer src="/image.png" />
|
// 替换 docslink 格式: ![[canvas]] -> 模板
|
||||||
const wikilinkRegex = /!\[\[([^\]]+)\]\]/g;
|
const docslinkRegex = /!\[\[([^\]]+)\]\]/g;
|
||||||
content = content.replace(wikilinkRegex, (match, link) => {
|
content = content.replace(docslinkRegex, (match, link) => {
|
||||||
// 移除可能的别名字段
|
// 移除可能的别名字段
|
||||||
const cleanLink = link.split('|')[0].trim();
|
const cleanLink = link.split('|')[0]!.trim();
|
||||||
|
|
||||||
for (const testPath of possiblePaths) {
|
// 尝试为这个链接找到对应的 canvas 文件
|
||||||
if (cleanLink === testPath || cleanLink === testPath.replace(/^\//, '')) {
|
for (const [canvasPath, imageFileName] of canvasImageMap) {
|
||||||
console.debug(`Found matching canvas wikilink: ${cleanLink} -> <Viewer src="${assetsRef}" />`);
|
const canvasFile = this.app.vault.getAbstractFileByPath(canvasPath);
|
||||||
return `<Viewer src="${assetsRef}" />`;
|
if (canvasFile instanceof TFile) {
|
||||||
|
// 构建可能的路径格式
|
||||||
|
const possiblePaths = [
|
||||||
|
// 完整路径
|
||||||
|
canvasPath,
|
||||||
|
// 相对于源文件的路径
|
||||||
|
canvasPath.substring(sourceDir.length + 1),
|
||||||
|
// URL 编码版本
|
||||||
|
encodeURI(canvasPath),
|
||||||
|
encodeURI(canvasPath.substring(sourceDir.length + 1)),
|
||||||
|
// 文件名(包括和不包括 .canvas 扩展名)
|
||||||
|
canvasFile.name,
|
||||||
|
canvasFile.name.replace('.canvas', ''),
|
||||||
|
encodeURI(canvasFile.name),
|
||||||
|
encodeURI(canvasFile.name.replace('.canvas', ''))
|
||||||
|
];
|
||||||
|
|
||||||
|
// 检查是否匹配
|
||||||
|
for (const testPath of possiblePaths) {
|
||||||
|
if (cleanLink === testPath || cleanLink === testPath.replace(/^\//, '')) {
|
||||||
|
const replacement = this.settings.canvasEmbedTemplate.replace("{url}", assetsRef(imageFileName));
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return match;
|
return match;
|
||||||
|
|||||||
@@ -1,25 +1,29 @@
|
|||||||
import {App, PluginSettingTab, Setting} from "obsidian";
|
import {App, PluginSettingTab, Setting} from "obsidian";
|
||||||
import vitepressExporterPlugin from "./main";
|
import odoxPlugin from "./main";
|
||||||
|
|
||||||
export interface vitepressExporterSettings {
|
export interface odoxSettings {
|
||||||
docsPath: string;
|
docsPath: string;
|
||||||
assetsPath: string;
|
assetsPath: string;
|
||||||
nestNoteFolder: boolean;
|
nestNoteFolder: boolean;
|
||||||
preserveFolderStructure: boolean;
|
preserveFolderStructure: boolean;
|
||||||
|
transparentCanvas: boolean;
|
||||||
|
canvasEmbedTemplate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: vitepressExporterSettings = {
|
export const DEFAULT_SETTINGS: odoxSettings = {
|
||||||
docsPath: 'wiki',
|
docsPath: 'docs',
|
||||||
assetsPath: 'wiki/public',
|
assetsPath: 'docs/public',
|
||||||
nestNoteFolder: true,
|
nestNoteFolder: true,
|
||||||
preserveFolderStructure: true
|
preserveFolderStructure: true,
|
||||||
|
transparentCanvas: true,
|
||||||
|
canvasEmbedTemplate: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置面板
|
// 设置面板
|
||||||
export class vitepressExporterSettingTab extends PluginSettingTab {
|
export class odoxSettingTab extends PluginSettingTab {
|
||||||
plugin: vitepressExporterPlugin;
|
plugin: odoxPlugin;
|
||||||
|
|
||||||
constructor(app: App, plugin: vitepressExporterPlugin) {
|
constructor(app: App, plugin: odoxPlugin) {
|
||||||
super(app, plugin);
|
super(app, plugin);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
@@ -30,7 +34,7 @@ export class vitepressExporterSettingTab extends PluginSettingTab {
|
|||||||
containerEl.empty();
|
containerEl.empty();
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Docs path')
|
.setName('Wiki path')
|
||||||
.setDesc('Path to the docs directory (relative to vault root)')
|
.setDesc('Path to the docs directory (relative to vault root)')
|
||||||
.addText(text => text
|
.addText(text => text
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@@ -46,7 +50,7 @@ export class vitepressExporterSettingTab extends PluginSettingTab {
|
|||||||
.setDesc('Path to the assets directory (relative to vault root)')
|
.setDesc('Path to the assets directory (relative to vault root)')
|
||||||
.addText(text => text
|
.addText(text => text
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
.setPlaceholder('docs/assets')
|
.setPlaceholder('docs/public')
|
||||||
.setValue(this.plugin.settings.assetsPath)
|
.setValue(this.plugin.settings.assetsPath)
|
||||||
.onChange(async (value) => {
|
.onChange(async (value) => {
|
||||||
this.plugin.settings.assetsPath = value;
|
this.plugin.settings.assetsPath = value;
|
||||||
@@ -72,5 +76,26 @@ export class vitepressExporterSettingTab extends PluginSettingTab {
|
|||||||
this.plugin.settings.preserveFolderStructure = value;
|
this.plugin.settings.preserveFolderStructure = value;
|
||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Transparent canvas background')
|
||||||
|
.setDesc('Export canvas with transparent background (hide dots pattern)')
|
||||||
|
.addToggle(toggle => toggle
|
||||||
|
.setValue(this.plugin.settings.transparentCanvas)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.transparentCanvas = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}));
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName('Canvas embed template')
|
||||||
|
.setDesc('Template for embedding canvas images. Use {url} as placeholder for the image path. Examples:  or <Viewer src="{url}" />')
|
||||||
|
.addText(text => text
|
||||||
|
.setPlaceholder('')
|
||||||
|
.setValue(this.plugin.settings.canvasEmbedTemplate)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.canvasEmbedTemplate = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
styles.css
22
styles.css
@@ -6,3 +6,25 @@ available in the app when your plugin is enabled.
|
|||||||
If your plugin does not need CSS, delete this file.
|
If your plugin does not need CSS, delete this file.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* 透明导出模式:隐藏洞洞板背景 */
|
||||||
|
.canvas-transparent-export .canvas-background {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 透明导出模式:确保 canvas 容器背景透明 */
|
||||||
|
.canvas-transparent-export {
|
||||||
|
background-color: transparent !important;
|
||||||
|
background-image: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 截图时隐藏页面其他元素 */
|
||||||
|
.odox-hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 截图时的 body 样式 */
|
||||||
|
.odox-capturing {
|
||||||
|
background-color: transparent !important;
|
||||||
|
background-image: none !important;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user