@vivliostyle/vfm
Version:
Custom Markdown syntax specialized in book authoring.
72 lines (71 loc) • 2.95 kB
JavaScript
import { isElement as is } from 'hast-util-is-element';
import { h } from 'hastscript';
import { visit } from 'unist-util-visit';
const propertyToString = (property) => {
return typeof property === 'string' || typeof property === 'number'
? String(property) // <tag prop="foo" /> || <tag prop=42 />
: Array.isArray(property)
? property.map(String).join(' ') // <tag prop="foo 42 bar" />
: ''; // <tag /> || <tag prop />
};
/**
* Move ID from source element to target properties if assignIdToFigcaption is enabled.
* @param source Element to move ID from (img or code).
* @param targetProps Properties object to receive the ID.
* @param options Figure options.
*/
const moveIdToFigcaption = (source, targetProps, options) => {
if (options.assignIdToFigcaption && source.properties?.id) {
targetProps.id = propertyToString(source.properties.id);
delete source.properties.id;
}
};
/**
* Wrap the single line `<img>` in `<figure>` and generate `<figcaption>` from the `alt` attribute.
*
* A single line `<img>` is a child of `<p>` with no sibling elements. Also, `<figure>` cannot be a child of `<p>`. So convert the parent `<p>` to `<figure>`.
* @param img `<img>` tag.
* @param parent `<p>` tag.
* @param options Figure options.
*/
const wrapFigureImg = (img, parent, options) => {
if (!(img.properties && parent.properties)) {
return;
}
parent.tagName = 'figure';
const figcaptionProps = { 'aria-hidden': 'true' };
moveIdToFigcaption(img, figcaptionProps, options);
const figcaption = h('figcaption', figcaptionProps, propertyToString(img.properties.alt));
if (options.imgFigcaptionOrder === 'figcaption-img') {
parent.children.unshift(figcaption);
}
else {
parent.children.push(figcaption);
}
};
export const hast = (options = {}) => (tree) => {
visit(tree, 'element', (node, index, parent) => {
// handle captioned code block
const maybeCode = node.children?.[0];
if (is(node, 'pre') &&
maybeCode?.properties &&
maybeCode.properties.title) {
const maybeTitle = maybeCode.properties.title;
delete maybeCode.properties.title;
if (Array.isArray(maybeCode.properties.className)) {
const figcaptionProps = {};
moveIdToFigcaption(maybeCode, figcaptionProps, options);
parent.children[index] = h('figure', { class: maybeCode.properties.className[0] }, h('figcaption', figcaptionProps, propertyToString(maybeTitle)), node);
}
return;
}
// handle captioned and single line (like a block) img
if (is(node, 'img') &&
node.properties?.alt &&
parent &&
parent.tagName === 'p' &&
parent.children.length === 1) {
wrapFigureImg(node, parent, options);
}
});
};