-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
affccb5
commit 55f04bb
Showing
15 changed files
with
2,062 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.github |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"trailingComma": "es5", | ||
"tabWidth": 2, | ||
"semi": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,27 @@ | ||
import lit from "@astrojs/lit"; | ||
import mdx from "@astrojs/mdx"; | ||
import { defineConfig } from 'astro/config'; | ||
import remarkMath from "remark-math"; | ||
import rehypeKatex from "rehype-katex"; | ||
import rehypeMathjax from "rehype-mathjax"; | ||
import mermaid from "./plugin/remark-mermaid"; | ||
|
||
// https://astro.build/config | ||
export default defineConfig({ | ||
integrations: [lit(), mdx()], | ||
integrations: [lit(), mdx({ | ||
optimize: true, | ||
remarkPlugins: [ | ||
remarkMath, | ||
mermaid, | ||
], | ||
rehypePlugins: [ | ||
()=> rehypeKatex({ | ||
output: "mathml", | ||
strict:false, | ||
}), | ||
// rehypeMathjax | ||
], | ||
})], | ||
site: 'https://yongsk0066.github.io', | ||
base: '/', | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import fs from 'fs/promises'; | ||
import { visit } from 'unist-util-visit'; | ||
import { render, renderFromFile, getDestinationDir, createMermaidDiv } from './utils'; | ||
|
||
const PLUGIN_NAME = 'remark-mermaid'; | ||
|
||
/** | ||
* Is this title `mermaid:`? | ||
* | ||
* @param {string} title | ||
* @return {boolean} | ||
*/ | ||
const isMermaid = (title) => title === 'mermaid:'; | ||
|
||
/** | ||
* Given a node which contains a `url` property (eg. Link or Image), follow | ||
* the link, generate a graph and then replace the link with the link to the | ||
* generated graph. Checks to ensure node has a title of `mermaid:` before doing. | ||
* | ||
* @param {object} node | ||
* @param {vFile} vFile | ||
* @return {object} | ||
*/ | ||
const replaceUrlWithGraph = async (node, vFile) => { | ||
const { title, url, position } = node; | ||
const { destinationDir } = vFile.data; | ||
|
||
// If the node isn't mermaid, ignore it. | ||
if (!isMermaid(title)) { | ||
return node; | ||
} | ||
|
||
try { | ||
// eslint-disable-next-line no-param-reassign | ||
node.url = await renderFromFile(`${vFile.dirname}/${url}`, destinationDir); | ||
vFile.info('mermaid link replaced with link to graph', position, PLUGIN_NAME); | ||
} catch (error) { | ||
vFile.message(error, position, PLUGIN_NAME); | ||
} | ||
|
||
return node; | ||
}; | ||
|
||
/** | ||
* Given a link to a mermaid diagram, grab the contents from the link and put it | ||
* into a div that Mermaid JS can act upon. | ||
* | ||
* @param {object} node | ||
* @param {integer} index | ||
* @param {object} parent | ||
* @param {vFile} vFile | ||
* @return {object} | ||
*/ | ||
const replaceLinkWithEmbedded = async (node, index, parent, vFile) => { | ||
const { title, url, position } = node; | ||
let newNode; | ||
|
||
// If the node isn't mermaid, ignore it. | ||
if (!isMermaid(title)) { | ||
return node; | ||
} | ||
|
||
try { | ||
const value = await fs.promises.readFile(`${vFile.dirname}/${url}`, { encoding: 'utf-8' }); | ||
|
||
newNode = createMermaidDiv(value); | ||
parent.children.splice(index, 1, newNode); | ||
vFile.info('mermaid link replaced with div', position, PLUGIN_NAME); | ||
} catch (error) { | ||
vFile.message(error, position, PLUGIN_NAME); | ||
return node; | ||
} | ||
|
||
return node; | ||
}; | ||
|
||
/** | ||
* Given the MDAST ast, look for all fenced codeblocks that have a language of | ||
* `mermaid` and pass that to mermaid.cli to render the image. Replaces the | ||
* codeblocks with an image of the rendered graph. | ||
* | ||
* @param {object} ast | ||
* @param {vFile} vFile | ||
* @param {boolean} isSimple | ||
* @return {function} | ||
*/ | ||
const visitCodeBlock = async (ast, vFile, isSimple) => { | ||
return visit(ast, 'code', async (node, index, parent) => { | ||
const { lang, value, position } = node; | ||
const destinationDir = getDestinationDir(vFile); | ||
let newNode; | ||
|
||
// If this codeblock is not mermaid, bail. | ||
if (lang !== 'mermaid') { | ||
return node; | ||
} | ||
|
||
// Are we just transforming to a <div>, or replacing with an image? | ||
if (isSimple) { | ||
newNode = createMermaidDiv(value); | ||
|
||
vFile.info(`${lang} code block replaced with div`, position, PLUGIN_NAME); | ||
|
||
// Otherwise, let's try and generate a graph! | ||
} else { | ||
let graphSvgFilename; | ||
try { | ||
graphSvgFilename = await render(value, destinationDir); | ||
console.log("graphSvgFilename", graphSvgFilename) | ||
vFile.info(`${lang} code block replaced with graph`, position, PLUGIN_NAME); | ||
} catch (error) { | ||
vFile.message(error, position, PLUGIN_NAME); | ||
return node; | ||
} | ||
|
||
newNode = { | ||
type: 'image', | ||
title: '`mermaid` image', | ||
url: graphSvgFilename, | ||
}; | ||
} | ||
|
||
parent.children.splice(index, 1, newNode); | ||
|
||
return node; | ||
}); | ||
} | ||
|
||
/** | ||
* If links have a title attribute called `mermaid:`, follow the link and | ||
* depending on `isSimple`, either generate and link to the graph, or simply | ||
* wrap the graph contents in a div. | ||
* | ||
* @param {object} ast | ||
* @param {vFile} vFile | ||
* @param {boolean} isSimple | ||
* @return {function} | ||
*/ | ||
const visitLink = (ast, vFile, isSimple) =>{ | ||
if (isSimple) { | ||
return visit(ast, 'link', (node, index, parent) => | ||
replaceLinkWithEmbedded(node, index, parent, vFile) | ||
); | ||
} | ||
|
||
return visit(ast, 'link', (node) => replaceUrlWithGraph(node, vFile)); | ||
} | ||
|
||
/** | ||
* If images have a title attribute called `mermaid:`, follow the link and | ||
* depending on `isSimple`, either generate and link to the graph, or simply | ||
* wrap the graph contents in a div. | ||
* | ||
* @param {object} ast | ||
* @param {vFile} vFile | ||
* @param {boolean} isSimple | ||
* @return {function} | ||
*/ | ||
const visitImage = (ast, vFile, isSimple) => { | ||
if (isSimple) { | ||
return visit(ast, 'image', (node, index, parent) => | ||
replaceLinkWithEmbedded(node, index, parent, vFile) | ||
); | ||
} | ||
|
||
return visit(ast, 'image', (node) => replaceUrlWithGraph(node, vFile)); | ||
} | ||
|
||
/** | ||
* Returns the transformer which acts on the MDAST tree and given VFile. | ||
* | ||
* If `options.simple` is passed as a truthy value, the plugin will convert | ||
* to `<div class="mermaid">` rather than a SVG image. | ||
* | ||
* @link https://github.com/unifiedjs/unified#function-transformernode-file-next | ||
* @link https://github.com/syntax-tree/mdast | ||
* @link https://github.com/vfile/vfile | ||
* | ||
* @param {object} options | ||
* @return {function} | ||
*/ | ||
const mermaid = (options = {}) => { | ||
const simpleMode = options.simple ?? false; | ||
|
||
const transformer = (ast, vFile, next) => { | ||
visitCodeBlock(ast, vFile, simpleMode); | ||
visitLink(ast, vFile, simpleMode); | ||
visitImage(ast, vFile, simpleMode); | ||
|
||
if (typeof next === 'function') { | ||
return next(null, ast, vFile); | ||
} | ||
|
||
return ast; | ||
}; | ||
return transformer; | ||
} | ||
|
||
export default mermaid; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { execSync } from 'child_process'; | ||
import { promises as fs } from 'fs'; | ||
import { createHmac } from 'crypto'; | ||
import * as path from 'path'; | ||
import which from 'which'; | ||
|
||
const PLUGIN_NAME = 'remark-mermaid'; | ||
|
||
/** | ||
* Accepts the `source` of the graph as a string, and render an SVG using | ||
* mermaid.cli. Returns the path to the rendered SVG. | ||
* | ||
* @param {string} source | ||
* @param {string} destination | ||
* @return {string} | ||
*/ | ||
export const render = async (source, destination) => { | ||
const unique = createHmac('sha1', PLUGIN_NAME).update(source).digest('hex'); | ||
const mmdcExecutable = which.sync('mmdc'); | ||
const mmdPath = path.join(destination, `${unique}.mmd`); | ||
const svgFilename = `${unique}.svg`; | ||
const svgPath = path.join(destination, svgFilename); | ||
|
||
try { | ||
await fs.writeFile(mmdPath, source); | ||
execSync(`${mmdcExecutable} -i ${mmdPath} -o ${svgPath} -b transparent`); | ||
} catch (error) { | ||
throw new Error(`Failed to render Mermaid graph: ${error.message}`); | ||
} finally { | ||
await fs.unlink(mmdPath); | ||
} | ||
|
||
return `/src/assets/images/${svgFilename}`; | ||
}; | ||
|
||
/** | ||
* Accepts the `source` of the graph as a string, and render an SVG using | ||
* mermaid.cli. Returns the path to the rendered SVG. | ||
* | ||
* @param {string} destination | ||
* @param {string} source | ||
* @return {string} | ||
*/ | ||
export const renderFromFile = (inputFile, destination) => { | ||
const unique = createHmac('sha1', PLUGIN_NAME).update(inputFile).digest('hex'); | ||
const mmdcExecutable = which.sync('mmdc'); | ||
const svgFilename = `${unique}.svg`; | ||
const svgPath = path.join(destination, svgFilename); | ||
|
||
// Invoke mermaid.cli | ||
try { | ||
execSync(`${mmdcExecutable} -i ${inputFile} -o ${svgPath} -b transparent`); | ||
} catch (error) { | ||
throw new Error(`Failed to render Mermaid graph from file: ${error.message}`); | ||
} | ||
return `./${svgFilename}`; | ||
} | ||
|
||
/** | ||
* Returns the destination for the SVG to be rendered at, explicity defined | ||
* using `vFile.data.destinationDir`, or falling back to the file's current | ||
* directory. | ||
* | ||
* @param {vFile} vFile | ||
* @return {string} | ||
*/ | ||
export const getDestinationDir = (vFile) => { | ||
return path.join(path.resolve(), '/src/assets/images') | ||
// return vFile.data.destinationDir ?? vFile.dirname; | ||
} | ||
|
||
/** | ||
* Given the contents, returns a MDAST representation of a HTML node. | ||
* | ||
* @param {string} contents | ||
* @return {object} | ||
*/ | ||
export const createMermaidDiv = (contents) => { | ||
return { | ||
type: 'html', | ||
value: `<div class="mermaid"> | ||
${contents} | ||
</div>`, | ||
}; | ||
} | ||
|
Oops, something went wrong.