Skip to content

Commit

Permalink
refactor: ast -> html
Browse files Browse the repository at this point in the history
  • Loading branch information
Airkro committed Dec 1, 2023
1 parent 074f203 commit 211c5f5
Show file tree
Hide file tree
Showing 19 changed files with 781 additions and 166 deletions.
20 changes: 20 additions & 0 deletions lib/ast.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
function attrString(attributes = []) {
return attributes.length > 0
? attributes.map(({ name, value }) => ` ${name}="${value}"`).join('')
: '';
}

export function create(target, ast) {
if (target === 'mdx3') {
return ast;
}

const { name, attributes, children: [{ value: child } = {}] = [] } = ast;

return {
type: target,
value: child
? [`<${name}${attrString(attributes)}>`, child, `</${name}>`].join('')
: `<${name}${attrString(attributes)} />`,
};
}
18 changes: 18 additions & 0 deletions lib/fetch.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import nodeFetch from 'node-fetch';

export function httpPost({ url, body, headers }) {
return nodeFetch(url, {
method: 'POST',
body,
headers: {
...headers,
'Content-Type': 'text/plain',
},
}).then(async (response) => {
if (!response.ok) {
throw new Error(await response.text());
}

return response.arrayBuffer();
});
}
5 changes: 3 additions & 2 deletions lib/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ export function remarkKroki({
headers = {},
alias = [],
output = outputType[0],
target = 'html',
} = {}) {
validate({ server, headers, alias, output });
validate({ server, headers, alias, output, target });

const condition = isKroki(alias);

return async (tree) => {
const temp = [];

visit(tree, condition, (node) => {
temp.push(transform({ node, server, headers, output }));
temp.push(transform({ node, server, headers, output, target }));
});

// eslint-disable-next-line no-empty
Expand Down
145 changes: 116 additions & 29 deletions lib/transform.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getValue, parse } from 'markdown-code-block-meta';

import { create } from './ast.mjs';
import { fetchData, mime, toDataURL } from './utils.mjs';

/* eslint-disable no-param-reassign */
Expand All @@ -9,39 +10,125 @@ function removeXML(string) {
}

const modes = {
'img-base64': ({ node, diagramType, data, alt }) => {
node.type = 'paragraph';
node.children = [
{
type: 'image',
alt: alt || diagramType,
url: toDataURL(data),
},
];
'img-base64': ({ diagramType, data, alt }) => {
return {
type: 'paragraph',
children: [
{
type: 'image',
_meta: { kroki: true, type: diagramType },
alt: alt || diagramType,
url: toDataURL(data),
},
],
};
},
'object-base64': ({ node, diagramType, data, alt }) => {
node.type = 'html';
node.value = `<object type="${mime}" class="kroki-object" data-type="${diagramType}" title="${
alt || diagramType
}" data="${toDataURL(data)}">Load SVG fail...</object>`;
'object-base64': ({ target, diagramType, data, alt }) => {
return create(target, {
type: 'mdxJsxFlowElement',
name: 'object',
children: [
{
type: 'text',
value: 'Load SVG fail...',
},
],
attributes: [
{
type: 'mdxJsxAttribute',
name: 'type',
value: mime,
},
{
type: 'mdxJsxAttribute',
name: 'class',
value: 'kroki-object',
},
{
type: 'mdxJsxAttribute',
name: 'data-type',
value: diagramType,
},
{
type: 'mdxJsxAttribute',
name: 'title',
value: alt || diagramType,
},
{
type: 'mdxJsxAttribute',
name: 'data',
value: toDataURL(data),
},
],
});
},
'img-html-base64': ({ node, diagramType, data, alt }) => {
node.type = 'html';
node.value = `<img class="kroki-image" alt="${
alt || diagramType
}" src="${toDataURL(data)}" />`;
'img-html-base64': ({ target, diagramType, data, alt }) => {
return {
type: 'paragraph',
children: [
create(target, {
type: 'mdxJsxTextElement',
name: 'img',
attributes: [
{
type: 'mdxJsxAttribute',
name: 'class',
value: 'kroki-image',
},
{
type: 'mdxJsxAttribute',
name: 'alt',
value: alt || diagramType,
},
{
type: 'mdxJsxAttribute',
name: 'data-type',
value: diagramType,
},
{
type: 'mdxJsxAttribute',
name: 'src',
value: toDataURL(data),
},
],
}),
],
};
},
'inline-svg': ({ node, diagramType, data, alt }) => {
node.type = 'html';
node.value = `<div class="kroki-inline-svg" data-type="${diagramType}" data-alt="${
alt || diagramType
}">${removeXML(data.toString())}</div>`;
'inline-svg': ({ target, diagramType, data, alt }) => {
return create(target, {
type: 'mdxJsxFlowElement',
name: 'p',
attributes: [
{
type: 'mdxJsxAttribute',
name: 'class',
value: 'kroki-inline-svg',
},
{
type: 'mdxJsxAttribute',
name: 'data-type',
value: diagramType,
},
{
type: 'mdxJsxAttribute',
name: 'data-alt',
value: alt || diagramType,
},
],
children: [
{
type: 'html',
value: removeXML(data.toString()),
},
],
});
},
};

export const outputType = Object.keys(modes);

export async function transform({ node, server, headers, output }) {
export async function transform({ node, server, headers, output, target }) {
const { meta, value, lang } = node;

const object = parse(meta);
Expand All @@ -58,9 +145,9 @@ export async function transform({ node, server, headers, output }) {
value,
});

delete node.lang;
delete node.value;
delete node.meta;
for (const key of Object.keys(node)) {
delete node[key];
}

modes[output]({ node, diagramType, data, alt });
Object.assign(node, modes[output]({ diagramType, data, alt, target }));
}
50 changes: 15 additions & 35 deletions lib/utils.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { readFileSync } from 'node:fs';

import { parse } from 'markdown-code-block-meta';
import nodeFetch from 'node-fetch';
import pMemoize from 'p-memoize';

import { httpPost } from './fetch.mjs';

export function isKroki(alias = []) {
return ({ type, lang, meta, value }) => {
return (
Expand All @@ -16,52 +17,31 @@ export function isKroki(alias = []) {
};
}

const fail = readFileSync(new URL('fail.svg', import.meta.url), 'utf8');
const failImage = new URL('fail.svg', import.meta.url);

function createFailImageBuffer(message) {
return Buffer.from(fail.replace('======', message.slice(0, 500)));
function createFailImage(message) {
return readFileSync(failImage, 'utf8').replace(
'======',
message.slice(0, 500),
);
}

function Fetch({ server, headers = {}, type, value }) {
return nodeFetch(`${server}/${type}/svg`, {
method: 'POST',
function Fetch({ server, headers, type, value }) {
return httpPost({
url: `${server}/${type}/svg`,
body: value,
headers: {
...headers,
'Content-Type': 'text/plain',
},
headers,
})
.then(
(response) => {
if (!response.ok || response.statusCode >= 400) {
if (response.statusCode === 404) {
return 'Error: 404';
}

return response.text();
}

return response.arrayBuffer();
},
(error) => `Error: ${error.message}`,
)
.then((data) =>
typeof data === 'string'
? createFailImageBuffer(data)
: Buffer.from(data),
);
.catch((error) => createFailImage(error.message))
.then((data) => Buffer.from(data));
}

export const mime = 'image/svg+xml';

function base64Url(base64) {
return `data:${mime};base64,${base64}`;
}

export function toDataURL(buffer) {
const base64 = buffer.toString('base64');

return base64Url(base64);
return `data:${mime};base64,${base64}`;
}

export const fetchData = pMemoize(Fetch, {
Expand Down
12 changes: 7 additions & 5 deletions lib/validate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import isPlainObject from 'is-plain-obj';

import { outputType } from './transform.mjs';

export function validate({ server, headers, alias, output }) {
const targets = ['html', 'mdx3'];

export function validate({ server, headers, alias, output, target }) {
try {
assert(
typeof server === 'string',
Expand Down Expand Up @@ -43,13 +45,13 @@ export function validate({ server, headers, alias, output }) {
);

assert(
typeof output === 'string',
new TypeError('`output` should be string'),
outputType.includes(output),
new TypeError(`\`output\` should be one of \`${outputType.join('/')}\``),
);

assert(
outputType.includes(output),
new TypeError(`\`output\` should be one of \`${outputType.join('/')}\``),
targets.includes(target),
new TypeError(`\`target\` should be one of \`${targets.join('/')}\``),
);
} catch (error) {
throw error.actual || error;
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"garou": "^0.6.19",
"prettier": "^3.1.0",
"remark": "^15.0.1",
"remark-mdx": "^3.0.0",
"unist-util-remove-position": "^5.0.0"
},
"engines": {
Expand All @@ -75,7 +76,7 @@
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"packageManager": "pnpm@8.10.5",
"packageManager": "pnpm@8.11.0",
"eslintConfig": {
"extends": "@nice-move/eslint-config-base"
},
Expand Down
Loading

0 comments on commit 211c5f5

Please sign in to comment.