feat: Implement Canvas file export functionality and enhance Markdown export process
All checks were successful
Node.js build / build (20.x) (push) Successful in 21s
Node.js build / build (22.x) (push) Successful in 20s

- Added support for exporting `.canvas` files by rendering them to images and generating corresponding `.canvas.md` files.
- Updated the export logic to handle both Markdown and Canvas files, including file type checks and notifications.
- Improved resource management by ensuring assets are copied correctly and links are updated in the exported content.
- Refactored settings to change default export paths from 'docs' to 'wiki' for better organization.
- Created detailed implementation notes and flowcharts to document the export processes and core functionalities.
This commit is contained in:
2026-04-21 22:06:37 +08:00
parent e5e7d724c6
commit f0ca3776f0
7 changed files with 445 additions and 1240 deletions

View File

@@ -23,7 +23,7 @@ export default class vitepressExporterPlugin extends Plugin {
this.registerEvent(
// @ts-ignore
this.app.workspace.on("file-menu", (menu: Menu, file: TFile) => {
if (file.extension === "md") {
if (file.extension === "md" || file.extension === "canvas") {
menu.addItem((item: MenuItem) => {
item
@@ -80,8 +80,8 @@ export default class vitepressExporterPlugin extends Plugin {
return;
}
if (activeFile.extension !== 'md') {
new Notice('Current file is not a Markdown file');
if (activeFile.extension !== 'md' && activeFile.extension !== 'canvas') {
new Notice('Current file is not a Markdown or canvas file');
return;
}
@@ -90,6 +90,11 @@ export default class vitepressExporterPlugin extends Plugin {
// 导出单个笔记
async exportNote(file: TFile) {
if (file.extension === 'canvas') {
await this.exportCanvasFile(file);
return;
}
const { vault } = this.app;
// 使用 binary 模式读取文件以保留原始格式(包括行尾空格)
@@ -129,6 +134,63 @@ export default class vitepressExporterPlugin extends Plugin {
console.warn(`Exported ${file.name} to ${docsPath}`);
}
// 导出 canvas 文件
async exportCanvasFile(file: TFile) {
const { vault } = this.app;
// 1. 渲染 canvas 为图片
const renderedImage = await this.renderCanvas(file);
if (!renderedImage) {
new Notice(`Failed to render canvas: ${file.name}`);
return;
}
// 2. 保存图片到 assetsPath (扁平化存储)
const imageFileName = file.name + '.png';
const imagePath = `${this.settings.assetsPath}/${imageFileName}`;
// 确保目标目录存在
await vault.adapter.mkdir(this.settings.assetsPath);
// 写入渲染的图片
await vault.adapter.writeBinary(imagePath, renderedImage.buffer);
console.debug(`Successfully rendered canvas: ${file.path} -> ${imagePath}`);
// 3. 计算 .canvas.md 文件的输出路径(复用现有逻辑)
const fileNameWithoutExt = file.name.replace('.canvas', '');
const filePathWithoutExt = file.path.replace('.canvas', '');
let docsPath: string;
if (this.settings.preserveFolderStructure && this.settings.nestNoteFolder) {
// 保留文件夹 + 嵌套: notes/some-note.canvas > docs/notes/some-note.canvas/index.md
docsPath = `${this.settings.docsPath}/${filePathWithoutExt}.canvas/index.md`;
} else if (this.settings.preserveFolderStructure) {
// 保留文件夹结构: notes/some-note.canvas > docs/notes/some-note.canvas.md
docsPath = `${this.settings.docsPath}/${filePathWithoutExt}.md`;
} else if (this.settings.nestNoteFolder) {
// 嵌套文件夹: notes/some-note.canvas > docs/some-note.canvas/index.md
docsPath = `${this.settings.docsPath}/${fileNameWithoutExt}.canvas/index.md`;
} else {
// 默认: notes/some-note.canvas > docs/some-note.canvas.md
docsPath = `${this.settings.docsPath}/${fileNameWithoutExt}.canvas.md`;
}
// 4. 确保 .canvas.md 目标目录存在
const targetDir = docsPath.substring(0, docsPath.lastIndexOf('/'));
await vault.adapter.mkdir(targetDir);
// 5. 生成 .canvas.md 内容
const content = `<Viewer src="/${imageFileName}" />`;
// 6. 写入 .canvas.md 文件
const contentBuffer = new TextEncoder().encode(content);
await vault.adapter.writeBinary(docsPath, contentBuffer.buffer);
console.warn(`Exported ${file.name} to ${docsPath}`);
new Notice(`Exported ${file.name} to ${docsPath}`);
}
// 处理资源文件
async processAssets(content: string, sourceFile: TFile): Promise<string> {
const { vault, metadataCache } = this.app;
@@ -188,7 +250,7 @@ export default class vitepressExporterPlugin extends Plugin {
if (canvasFile instanceof TFile) {
const renderedImage = await this.renderCanvas(canvasFile);
if (renderedImage) {
const imageFileName = canvasFile.name.replace('.canvas', '.png');
const imageFileName = canvasFile.name + '.png';
// 图片保存到 docs/assets/
const imagePath = `${this.settings.assetsPath}/${imageFileName}`;

View File

@@ -9,8 +9,8 @@ export interface vitepressExporterSettings {
}
export const DEFAULT_SETTINGS: vitepressExporterSettings = {
docsPath: 'docs',
assetsPath: 'docs/public',
docsPath: 'wiki',
assetsPath: 'wiki/public',
nestNoteFolder: true,
preserveFolderStructure: true
}