UNPKG

@vivliostyle/vfm

Version:

Custom Markdown syntax specialized in book authoring.

145 lines (144 loc) 4.25 kB
import { doctype } from 'doctype'; import { h } from 'hastscript'; import { u } from 'unist-builder'; /** * Create AST properties from attributes. * @param attributes Attributes. * @returns Properties. */ const createProperties = (attributes) => { if (!attributes) { return {}; } const props = {}; for (const attr of attributes) { props[attr.name] = attr.value; } return props; }; /** * Create Markdown AST for `<head>`. * @param data Metadata. * @param vfile VFile. * @returns AST of `<head>`. */ const createHead = (data, vfile) => { const head = [u('text', '\n'), h('meta', { charset: 'utf-8' })]; // <title>...</title> { const title = typeof data.title === 'string' ? data.title : vfile.stem; if (typeof title === 'string') { head.push(u('text', '\n'), h('title', [title])); } } // <base> if (data.base) { const props = createProperties(data.base); head.push(u('text', '\n'), h('base', props)); } // <meta> { // Add viewport of default const meta = data.meta ? [...data.meta] : []; meta.unshift([ { name: 'name', value: 'viewport' }, { name: 'content', value: 'width=device-width, initial-scale=1' }, ]); for (const attributes of meta) { const props = createProperties(attributes); head.push(u('text', '\n'), h('meta', props)); } head.push(u('text', '\n')); } // <link> if (data.link) { for (const attributes of data.link) { const props = createProperties(attributes); head.push(u('text', '\n'), h('link', props)); } head.push(u('text', '\n')); } // <script> if (data.script) { for (const attributes of data.script) { const props = createProperties(attributes); head.push(u('text', '\n'), h('script', props)); } head.push(u('text', '\n')); } return head; }; /** * Create Markdown AST for `<body>`. * @param metadata Metadata. * @param tree Tree of Markdown AST. * @returns AST of `<body>`. */ const createBody = (metadata, tree) => { // <body>...</body> const contents = tree.type === 'root' && Array.isArray(tree.children) ? tree.children.concat() : [tree]; if (0 < contents.length) { contents.unshift(u('text', '\n')); } contents.push(u('text', '\n')); const props = createProperties(metadata.body); // <body class="root-class body-class1 body-class2 ..."> if (typeof metadata.class === 'string') { props.class = props.class ? `${metadata.class} ${props.class}` : metadata.class; } return h('body', props, contents); }; /** * Create properties for `<html>`. * Sets the value defined for `html` in Frontmatter. * However, if `id`,` lang`, `dir`, and `class` are defined in the root, those are given priority. * @param metadata Metadata. * @param tree Tree of Markdown AST. * @param vfile VFile. * @returns AST of `<html>`. */ const createHTML = (metadata, tree, vfile) => { const props = createProperties(metadata.html); if (typeof metadata.id === 'string') { props.id = metadata.id; } if (typeof metadata.lang === 'string') { props.lang = metadata.lang; } if (typeof metadata.dir === 'string') { props.dir = metadata.dir; } // <html class="root-class html-class1 html-class2 ..."> if (typeof metadata.class === 'string') { props.class = props.class ? `${metadata.class} ${props.class}` : metadata.class; } return h('html', props, [ u('text', '\n'), h('head', ...createHead(metadata, vfile)), u('text', '\n'), createBody(metadata, tree), u('text', '\n'), ]); }; /** * Process Markdown AST. * @param data Options. * @returns Transformer. */ export const mdast = (data) => (tree, vfile) => { return { type: 'root', children: [ { type: 'doctype', name: doctype(5) }, u('text', '\n'), createHTML(data, tree, vfile), u('text', '\n'), ], }; };