Skip to content

Commit

Permalink
feat: Apply themes from Obsidian to Mermaid
Browse files Browse the repository at this point in the history
  • Loading branch information
andymac4182 committed May 3, 2023
1 parent 5668e02 commit b599336
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 69 deletions.
140 changes: 106 additions & 34 deletions packages/mermaid-electron-renderer/src/index.ts
@@ -1,60 +1,84 @@
import { BrowserWindow } from "@electron/remote";
import { ChartData, MermaidRenderer } from "@markdown-confluence/lib";
import mermaid from "mermaid";
import mermaid, { MermaidConfig } from "mermaid";
import { v4 as uuidv4 } from "uuid";
import { getFileContentBlob } from "./receiver";

let mermaidRenderHtml: string;

const pluginMermaidConfig = {
theme: "base",
themeVariables: {
background: "#ffffff",
mainBkg: "#ddebff",
primaryColor: "#ddebff",
primaryTextColor: "#192b50",
primaryBorderColor: "#0052cc",
secondaryColor: "#ff8f73",
secondaryTextColor: "#192b50",
secondaryBorderColor: "#df360c",
tertiaryColor: "#c0b6f3",
tertiaryTextColor: "#fefefe",
tertiaryBorderColor: "#5243aa",
noteBkgColor: "#ffc403",
noteTextColor: "#182a4e",
textColor: "#ff0000",
titleColor: "#0052cc",
},
};

export class ElectronMermaidRenderer implements MermaidRenderer {
constructor(
private extraStyleSheets: string[],
private extraStyles: string[],
private mermaidConfig: MermaidConfig = pluginMermaidConfig,
private bodyClasses = ""
) {}

async captureMermaidCharts(
charts: ChartData[]
): Promise<Map<string, Buffer>> {
if (!mermaidRenderHtml) {
mermaidRenderHtml = URL.createObjectURL(getFileContentBlob());
mermaidRenderHtml = URL.createObjectURL(
this.getFileContentBlob(
this.extraStyleSheets,
this.extraStyles,
this.bodyClasses
)
);
}

const capturedCharts = new Map<string, Buffer>();

const promises = charts.map(async (chart) => {
const debug = false;

const chartWindow = new BrowserWindow({
width: 800,
height: 600,
show: false,
frame: false,
show: debug,
frame: debug,
});

if (debug) {
chartWindow.webContents.openDevTools();
}

await chartWindow.loadURL(mermaidRenderHtml);

const mermaidConfig = {
theme: "base",
themeVariables: {
background: "#ffffff",
mainBkg: "#ddebff",
primaryColor: "#ddebff",
primaryTextColor: "#192b50",
primaryBorderColor: "#0052cc",
secondaryColor: "#ff8f73",
secondaryTextColor: "#192b50",
secondaryBorderColor: "#df360c",
tertiaryColor: "#c0b6f3",
tertiaryTextColor: "#fefefe",
tertiaryBorderColor: "#5243aa",
noteBkgColor: "#ffc403",
noteTextColor: "#182a4e",
textColor: "#ff0000",
titleColor: "#0052cc",
},
};

mermaid.initialize({ ...mermaidConfig, startOnLoad: false });
const { themeVariables, ...mermaidInitConfig } = this.mermaidConfig;

mermaid.initialize({ ...mermaidInitConfig, startOnLoad: false });

if (themeVariables) {
mermaid.mermaidAPI.updateSiteConfig({ themeVariables });
}

const id = "mm" + uuidv4().replace(/-/g, "");
const { svg } = await mermaid.render(id, chart.data);

// Render the chart and get the dimensions
const dimensions = await chartWindow.webContents.executeJavaScript(
`renderSvg(\`${JSON.stringify(svg)}\`);`
`renderSvg(${JSON.stringify(svg)});`
);

// Resize the window to fit the chart dimensions
Expand All @@ -69,19 +93,67 @@ export class ElectronMermaidRenderer implements MermaidRenderer {
);
// Convert the NativeImage to a PNG buffer
const imageBuffer = image.toPNG();
// Clean up the window
chartWindow.close();
// Add the buffer to the capturedCharts map
capturedCharts.set(chart.name, imageBuffer);
// Resolve the promise
} catch (error) {
// Handle errors and clean up
chartWindow.close();
throw error;
} finally {
if (!debug) {
chartWindow.close();
}
}
});

await Promise.all(promises);
return capturedCharts;
}

getFileContentBlob(
extraStyleSheets: string[],
extraStyles: string[],
bodyClasses: string
): Blob {
const styleSheetTags = extraStyleSheets
.map(
(url) =>
`<link href="${url}" type="text/css" rel="stylesheet"/>`
)
.join("\n");
const extraStylesTag = `
<style>
${extraStyles.join("\n")}
</style>`;

const fileContents = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Mermaid Chart</title>
${styleSheetTags}
${extraStylesTag}
</head>
<body class="${bodyClasses}">
<div id="graphDiv"></div>
<script type="text/javascript">
window.renderSvg = (svg) => {
const chartElement = document.querySelector("#graphDiv");
chartElement.innerHTML = svg;
const svgElement = document.querySelector("#graphDiv svg");
return {
width: svgElement.scrollWidth,
height: svgElement.scrollHeight,
};
}
</script>
</body>
</html>
`;

const bytes = new Uint8Array(fileContents.length);
for (let i = 0; i < fileContents.length; i++) {
bytes[i] = fileContents.charCodeAt(i);
}
return new Blob([bytes], { type: "text/html" });
}
}
32 changes: 0 additions & 32 deletions packages/mermaid-electron-renderer/src/receiver.ts

This file was deleted.

24 changes: 24 additions & 0 deletions packages/obsidian/src/ConfluenceSettingTab.ts
Expand Up @@ -96,5 +96,29 @@ export class ConfluenceSettingTab extends PluginSettingTab {
await this.plugin.saveSettings();
})
);

new Setting(containerEl)
.setName("Mermaid Diagram Theme")
.setDesc("Pick the theme to apply to mermaid diagrams")
.addDropdown((dropdown) => {
/* eslint-disable @typescript-eslint/naming-convention */
dropdown
.addOptions({
"match-obsidian": "Match Obsidian",
"light-obsidian": "Obsidian Theme - Light",
"dark-obsidian": "Obsidian Theme - Dark",
default: "Mermaid - Default",
neutral: "Mermaid - Neutral",
dark: "Mermaid - Dark",
forest: "Mermaid - Forest",
})
.setValue(this.plugin.settings.mermaidTheme)
.onChange(async (value) => {
// @ts-expect-error
this.plugin.settings.mermaidTheme = value;
await this.plugin.saveSettings();
});
/* eslint-enable @typescript-eslint/naming-convention */
});
}
}
88 changes: 85 additions & 3 deletions packages/obsidian/src/main.ts
@@ -1,4 +1,4 @@
import { Plugin, Notice, MarkdownView, Workspace } from "obsidian";
import { Plugin, Notice, MarkdownView, Workspace, loadMermaid } from "obsidian";
import {
ConfluenceUploadSettings,
Publisher,
Expand All @@ -14,9 +14,22 @@ import {
ConfluencePerPageUIValues,
mapFrontmatterToConfluencePerPageUIValues,
} from "./ConfluencePerPageForm";
import { Mermaid } from "mermaid";

export interface ObsidianPluginSettings
extends ConfluenceUploadSettings.ConfluenceSettings {
mermaidTheme:
| "match-obsidian"
| "light-obsidian"
| "dark-obsidian"
| "default"
| "neutral"
| "dark"
| "forest";
}

export default class ConfluencePlugin extends Plugin {
settings!: ConfluenceUploadSettings.ConfluenceSettings;
settings!: ObsidianPluginSettings;
private isSyncing = false;
workspace!: Workspace;
publisher!: Publisher;
Expand All @@ -36,7 +49,14 @@ export default class ConfluencePlugin extends Plugin {
this.settings,
this.app
);
const mermaidRenderer = new ElectronMermaidRenderer();

const mermaidItems = await this.getMermaidItems();
const mermaidRenderer = new ElectronMermaidRenderer(
mermaidItems.extraStyleSheets,
mermaidItems.extraStyles,
mermaidItems.mermaidConfig,
mermaidItems.bodyStyles
);
const confluenceClient = new ObsidianConfluenceClient({
host: this.settings.confluenceBaseUrl,
authentication: {
Expand All @@ -57,6 +77,67 @@ export default class ConfluencePlugin extends Plugin {
);
}

async getMermaidItems() {
const extraStyles: string[] = [];
const extraStyleSheets: string[] = [];
let bodyStyles = "";
const body = document.querySelector("body") as HTMLBodyElement;

switch (this.settings.mermaidTheme) {
case "default":
case "neutral":
case "dark":
case "forest":
return {
extraStyleSheets,
extraStyles,
mermaidConfig: { theme: this.settings.mermaidTheme },
bodyStyles,
};
case "match-obsidian":
bodyStyles = body.className;
break;
case "dark-obsidian":
bodyStyles = "theme-dark";
break;
case "light-obsidian":
bodyStyles = "theme-dark";
break;
default:
throw new Error("Missing theme");
}

extraStyleSheets.push("app://obsidian.md/app.css");

// @ts-expect-error
const cssTheme = this.app.vault?.getConfig("cssTheme") as string;
if (cssTheme) {
const themeCss = await this.app.vault.adapter.read(
`.obsidian/themes/${cssTheme}/theme.css`
);
extraStyles.push(themeCss);
}

const cssSnippets =
// @ts-expect-error
(this.app.vault?.getConfig("enabledCssSnippets") as string[]) ?? [];
for (const snippet of cssSnippets) {
const themeCss = await this.app.vault.adapter.read(
`.obsidian/snippets/${snippet}.css`
);
extraStyles.push(themeCss);
}

return {
extraStyleSheets,
extraStyles,
mermaidConfig: (
(await loadMermaid()) as Mermaid
).mermaidAPI.getConfig(),
bodyStyles,
};
}

override async onload() {
await this.init();

Expand Down Expand Up @@ -318,6 +399,7 @@ export default class ConfluencePlugin extends Plugin {
this.settings = Object.assign(
{},
ConfluenceUploadSettings.DEFAULT_SETTINGS,
{ mermaidTheme: "match-obsidian" },
await this.loadData()
);
}
Expand Down

0 comments on commit b599336

Please sign in to comment.