remark-custom-container
Version:
remark parser plugin for custom directive in markdown
110 lines (109 loc) • 4.64 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.plugin = exports.REGEX_END = exports.REGEX_BEGIN = void 0;
const unist_util_visit_1 = require("unist-util-visit");
exports.REGEX_BEGIN = /^\s*:::\s*(\w+)\s*(.*)?/;
exports.REGEX_END = /^\s*:::$/;
const DEFAULT_SETTINGS = {
className: "remark-container",
containerTag: "div",
titleElement: {},
additionalProperties: undefined,
};
const isLiteralNode = (node) => {
return "value" in node;
};
const isParagraph = (node) => {
return "paragraph" === node.type;
};
const plugin = (options) => {
const settings = Object.assign({}, DEFAULT_SETTINGS, options);
// Constructs `Parent` node of custom directive which contains given children.
const constructContainer = (children, className, title) => {
let properties;
if (settings.additionalProperties) {
properties = settings.additionalProperties(className, title ?? "");
}
return {
type: "container",
children,
data: {
hName: settings.containerTag,
hProperties: {
className: [settings.className, className.toLowerCase()],
...(properties && { ...properties }),
},
},
};
};
const constructTitle = (title) => {
return {
type: "paragraph",
children: [{ type: "text", value: title }],
data: {
hName: "div",
hProperties: {
className: [`${settings.className}__title`],
...(settings.titleElement && { ...settings.titleElement }),
},
},
};
};
const transformer = (tree) => {
(0, unist_util_visit_1.visit)(tree, (_node, _index, parent) => {
if (!parent)
return;
const children = [];
const len = parent.children.length;
// we walk through each children in `parent` to look for custom directive.
let currentIndex = -1;
while (currentIndex < len - 1) {
currentIndex += 1;
// check if currentIndex of children contains begin node of custom directive
const currentNode = parent.children[currentIndex];
children.push(currentNode);
if (!isParagraph(currentNode))
continue;
// XXX: Consider checking other children in currentNode
const currentElem = currentNode.children[0];
if (!isLiteralNode(currentElem))
continue;
const match = currentElem.value.match(exports.REGEX_BEGIN);
if (!match)
continue;
// Here we're inside of the custom directive. let's find nearest closing directive.
// remove last element, which is custom directive marker.
children.pop();
const beginIndex = currentIndex;
let innerIndex = currentIndex - 1;
while (innerIndex < len - 1) {
innerIndex += 1;
const currentNode = parent.children[innerIndex];
if (!isParagraph(currentNode))
continue;
const currentElem = currentNode.children[0];
if (!isLiteralNode(currentElem) ||
!currentElem.value.match(exports.REGEX_END))
continue;
// here we found the closing directive.
const [_input, type, title] = match;
// remove surrounding `:::` markers and treat rest of them as children of the container
const containerChildren = parent.children.slice(beginIndex + 1, innerIndex);
// if the title exists and the settings.titleElement is not null, then construct the title div element
if (title?.length && settings.titleElement !== null) {
containerChildren.splice(0, 0, constructTitle(title));
}
const container = constructContainer(containerChildren, type.toLowerCase(), title);
children.push(container);
currentIndex = innerIndex - 1;
break;
}
currentIndex += 1;
}
parent.children = children;
});
};
return transformer;
};
exports.plugin = plugin;
exports.default = exports.plugin;