// These `unist-util-*` utilities are super useful when work with
// unified-compatible syntax trees
const is = require("unist-util-is");
const visit = require("unist-util-visit");
// We're going to need this to convert some mdast nodes to hast nodes later on
const mdastToHast = require("mdast-util-to-hast");
// Our plugin's constructor function. This would receive configuration options.
function subtitlePlugin() {
// Plugins need to return a transform function that takes a unified compatable
// AST and manipulate or walk it.
return async function transform(tree) {
// Go through the markdown document (in mdast form) and call my callback
// whenever you see paragraph nodes.
visit(tree, "paragraph", (paragraphNode) => {
const { children } = paragraphNode;
// Get the first child node under the paragraph and make sure it's a text
// node. If it's not, skip processing this paragraph node.
const textNode = children && children[0];
if (!is(textNode, "text")) {
return;
}
// Does this text node start with a sequence of hash ('#') signs followed
// by a dash ('-')?
const text =
typeof textNode.value === "string" ? textNode.value.trimLeft() : "";
const re = /^(#{1,6})-\s+/;
const matches = text.match(re);
if (typeof text === "string" && !matches) {
return;
}
// If it did let's count the number of '#'s as that will be our subtitle
// depth
const depth = matches[1].length;
// Once we have what we need, let's make a copy of this text node without
// the leading subtitle syntax.
// i.e. '##- hello world' becomes 'hello world'
const newValue = text.replace(re, "");
// We can now attach some metadata to an mdast node. If the node is being
// serialized to html by a hast-compatible library, it will know to use
// these overrides instead of the default behaviour of rendering a plain
// <p> tag.
paragraphNode.data = {
// we could use a different html tag but "p" is semantically correct for
// the subtitle
hName: "p",
// The <p> tag will have the following attributes added to it.
// Note that we need to use "className" for the html "class" attribute.
hProperties: {
className: `subtitle subtitle--${depth}`,
"data-remark-subtype": "subtitle",
"data-subtitle": depth,
},
// When we are passing custom children, it is our responsibility to make
// sure they are in hast format instead of mdast. We use the library,
// mdast-util-to-hast, to do this conversion.
hChildren: [
// We pass in a modified text node without the leading subtitle
// characters
{
...textNode,
value: newValue,
},
// Then we pass in the rest of the children under this paragraph node
...children.slice(1),
].map(mdastToHast), // Finally convert it all to hast
};
});
};
};
// === Our pipeline ===
// A markdown parser that spits out mdast
const remark = require("remark");
// An mdast to html serializer
const html = require("remark-html");
const text = `
# Hello
###- How are __you__?
Great!`;
remark()
.use(subtitlePlugin) // the plugin we'll write
.use(html)
.process(text /* markdown in */, function (err, file) {
if (err) throw err;
console.log(String(file)); /* html out */
});