Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wip): support mermaid via api #254

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
"esbuild": "^0.14.47",
"express": "^4.18.1",
"express-basic-auth": "^1.2.1",
"hast-util-from-html": "^1.0.0",
"hast-util-parse-selector": "^3.1.0",
"headless-mermaid": "^1.3.0",
"http-status": "^1.5.2",
"is-badge": "^2.1.0",
"js-yaml": "^4.1.0",
Expand Down
1 change: 1 addition & 0 deletions api/src/bundler/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export async function bundle(
return {
code,
frontmatter,
//@ts-ignore
errors,
headings: output.headings,
};
Expand Down
70 changes: 52 additions & 18 deletions api/src/bundler/plugins/rehype-code-blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,73 @@ import { visit } from 'unist-util-visit';
import { Node } from 'hast-util-heading-rank';
import { toString } from 'mdast-util-to-string';
import * as shiki from 'shiki';

let highlighter: shiki.Highlighter;

//@ts-ignore
import mermaid from 'headless-mermaid';
//@ts-ignore
import { fromHtml } from 'hast-util-from-html';
/**
* Matches any `pre code` elements and extracts the raw code and titles from the code block and assigns to the parent.
* @returns
*/
export default function rehypeCodeBlocks(): (ast: Node) => void {
function visitor(node: any, _i: number, parent: any) {
if (!parent || parent.tagName !== 'pre' || node.tagName !== 'code') {
return;
}

const language = getLanguage(node);
const raw = toString(node);
return async (ast: Node): Promise<null> => {

// Raw value of the `code` block - used for copy/paste
parent.properties['raw'] = raw;
parent.properties['html'] = highlighter.codeToHtml(raw, language);
const promises: any[] = [];

// Get any metadata from the code block
const meta = (node.data?.meta as string) ?? '';
async function visitor(node: any, i: number, parent: any) {
if (!parent || parent.tagName !== 'pre' || node.tagName !== 'code') {
return;
}

const title = extractTitle(meta);
if (title) parent.properties['title'] = title;
}
const language = getLanguage(node);

const raw = toString(node);

if (language === 'mermaid') {

const value = node.children[0].value;


promises.push(mermaid.execute(value).then((svgResult: string) => {
//@ts-ignore
console.log(fromHtml(svgResult).children[0].children[1].children)
//@ts-ignore
const svgNode = fromHtml(svgResult).children[0].children[1].children[0];

Object.assign(parent, svgNode);
}))

return;
}

// If the user provides the `console` language, we add the 'shell' language and remove $ from the raw code, for copying purposes.
if (language === 'console') {
const removedDollarSymbol = raw.replace(/^(^ *)\$/g, '');

parent.properties['raw'] = removedDollarSymbol;
parent.properties['html'] = highlighter.codeToHtml(raw, { lang: 'shell' });
} else {
parent.properties['raw'] = raw;
parent.properties['html'] = highlighter.codeToHtml(raw, { lang: language });
}

// Get any metadata from the code block
const meta = (node.data?.meta as string) ?? '';

const title = extractTitle(meta);
if (title) parent.properties['title'] = title;
}

return async (ast: Node): Promise<void> => {
highlighter = await shiki.getHighlighter({
theme: 'github-dark',
});
// @ts-ignore
visit(ast, 'element', visitor);

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

Expand Down Expand Up @@ -76,4 +110,4 @@ function getLanguage(node: any): string | undefined {
return value.slice(9);
}
}
}
}
Loading