Skip to content

Commit

Permalink
Merge pull request #56 from blacktau/master
Browse files Browse the repository at this point in the history
Adding Ability to export diagrams - Issue #55
  • Loading branch information
joethei authored Mar 4, 2024
2 parents ba6dd48 + 7ce059e commit 89cbfc6
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 35 deletions.
182 changes: 148 additions & 34 deletions src/debouncedProcessors.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {debounce, Debouncer, MarkdownPostProcessorContext, Menu, Notice, requestUrl} from "obsidian";
import {v4 as uuidv4} from "uuid";
import { debounce, Debouncer, MarkdownPostProcessorContext, Menu, Notice, TFile } from "obsidian";
import { v4 as uuidv4 } from "uuid";
import PlantumlPlugin from "./main";
import {Processor} from "./processor";
import { Processor } from "./processor";

export class DebouncedProcessors implements Processor {

Expand Down Expand Up @@ -51,6 +51,7 @@ export class DebouncedProcessors implements Processor {
source = this.plugin.settings.header + "\r\n" + source;
await processor(source, el, ctx);
el.addEventListener('contextmenu', (event) => {

const menu = new Menu(this.plugin.app)
.addItem(item => {
item
Expand All @@ -60,6 +61,7 @@ export class DebouncedProcessors implements Processor {
await navigator.clipboard.writeText(originalSource);
})
})

.addItem(item => {
item
.setTitle('Copy diagram')
Expand All @@ -68,37 +70,19 @@ export class DebouncedProcessors implements Processor {
console.log(el);
const img = el.querySelector('img');
if (img) {
const image = new Image();
image.crossOrigin = 'anonymous';
image.src = img.src;
image.addEventListener('load', () => {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, 0, 0);
try {
canvas.toBlob(async (blob) => {
try {
await navigator.clipboard.write([
new ClipboardItem({
"image/png": blob
})
]);
new Notice('Diagram copied to clipboard');
} catch (error) {
new Notice('An error occurred while copying image to clipboard');
console.error(error);
}
});
} catch (error) {
new Notice('An error occurred while copying image to clipboard');
console.error(error);
}
});
this.renderToBlob(
img,
'An error occurred while copying image to clipboard',
async (blob) => {
await navigator.clipboard.write([
new ClipboardItem({
"image/png": blob
})
]);
new Notice('Diagram copied to clipboard');
});
}

const svg = el.querySelector('svg');
if (svg) {
await navigator.clipboard.writeText(svg.outerHTML);
Expand All @@ -110,10 +94,140 @@ export class DebouncedProcessors implements Processor {
new Notice('Diagram copied to clipboard');
}
});
})
.addItem(item => {
item
.setTitle('Export diagram')
.setIcon('image-file')
.onClick(async () => {
const img = el.querySelector('img');

if (img) {
this.renderToBlob(img, 'An error occurred while exporting the diagram', async (blob) => {
const filename = await this.getFilePath(source, ctx, 'png');
const buffer = await blob.arrayBuffer();
const file = this.getFile(filename);
if (file) {
await this.plugin.app.vault.modifyBinary(file, buffer);
} else {
await this.plugin.app.vault.createBinary(filename, buffer);
}

new Notice(`Diagram exported to '${filename}'`);
});
}

const svg = el.querySelector('svg');
if (svg) {
await this.saveTextFile(source, ctx, 'svg', svg.outerHTML);
}

const code = el.querySelector('code');
if (code) {
await this.saveTextFile(source, ctx, 'txt', code.innerText);
}
})
});
menu.showAtMouseEvent(event);
})
}
}

}
renderToBlob = (img: HTMLImageElement, errorMessage: string, handleBlob: (blob: Blob) => Promise<void>) => {
const image = new Image();
image.crossOrigin = 'anonymous';
image.src = img.src;
image.addEventListener('load', () => {
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, 0, 0);
try {
canvas.toBlob(async (blob) => {
try {
await handleBlob(blob);
} catch (error) {
new Notice(errorMessage);
console.error(error);
}
});
} catch (error) {
new Notice(errorMessage);
console.error(error);
}
});
}

getFilename = (source: string, ctx: MarkdownPostProcessorContext) => {
// try extract the title of the diagram
const startuml = source.match(/@startuml (.+)/i);
if (startuml?.length >= 2) {
return `${startuml[1].trim()}`;
}

const now = (new Date()).toISOString().replace(/[:T]+/g, '-');
const filename = this.plugin.app.vault.getAbstractFileByPath(ctx.sourcePath).name;
return `${filename.substring(0, filename.lastIndexOf('.'))}-${now.substring(0, now.lastIndexOf('.'))}`;
}

getFolder = async (ctx: MarkdownPostProcessorContext) => {
let exportPath = this.plugin.settings.exportPath;
if (!exportPath.startsWith('/')) {
// relative to the document
const documentPath = this.plugin.app.vault.getAbstractFileByPath(ctx.sourcePath).parent;
exportPath = `${documentPath.path}/${exportPath}`;
}

const exists = await this.plugin.app.vault.adapter.exists(exportPath);
if (!exists) {
this.plugin.app.vault.createFolder(exportPath);
}

return exportPath;
}

getFilePath = async (source: string, ctx: MarkdownPostProcessorContext, type: string) => {

const filename = this.getFilename(source, ctx);
const path = await this.getFolder(ctx);

return `${path}${filename}.${type}`;
}

getFile = (fileName: string) => {

let fName = fileName;
if (fName.startsWith('/')) {
fName = fName.substring(1);
}

const folderOrFile = this.plugin.app.vault.getAbstractFileByPath(fName);

if (folderOrFile instanceof TFile) {
return folderOrFile;
}

return undefined;
}

saveTextFile = async (source: string, ctx: MarkdownPostProcessorContext, type: string, data: string) => {
try {
const filename = await this.getFilePath(source, ctx, type);
const file = this.getFile(filename);

if (file) {
await this.plugin.app.vault.modify(file, data);
} else {
await this.plugin.app.vault.create(filename, data);
}

new Notice(`Diagram exported to '${filename}'`);
} catch (error) {
new Notice('An error occurred while while exporting the diagram');
console.error(error);
}
}
}
16 changes: 15 additions & 1 deletion src/settings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Notice, Platform, PluginSettingTab, Setting} from "obsidian";
import { Notice, Platform, PluginSettingTab, Setting } from "obsidian";
import PlantumlPlugin from "./main";

export interface PlantUMLSettings {
Expand All @@ -10,6 +10,7 @@ export interface PlantUMLSettings {
dotPath: string;
defaultProcessor: string;
cache: number;
exportPath: string;
}

export const DEFAULT_SETTINGS: PlantUMLSettings = {
Expand All @@ -21,6 +22,7 @@ export const DEFAULT_SETTINGS: PlantUMLSettings = {
dotPath: 'dot',
defaultProcessor: "png",
cache: 60,
exportPath: ''
}

export class PlantUMLSettingsTab extends PluginSettingTab {
Expand Down Expand Up @@ -92,6 +94,18 @@ export class PlantUMLSettingsTab extends PluginSettingTab {
}
)
);

new Setting(containerEl)
.setName("Diagram export path")
.setDesc("Path where exported diagrams will be saved relative to the vault root. Leave blank to save along side the note.")
.addText(text => text.setPlaceholder(DEFAULT_SETTINGS.exportPath)
.setValue(this.plugin.settings.exportPath)
.onChange(async (value) => {
this.plugin.settings.exportPath = value;
await this.plugin.saveSettings();
}
)
);
}

new Setting(containerEl)
Expand Down

0 comments on commit 89cbfc6

Please sign in to comment.