@vivliostyle/vfm
Version:
Custom Markdown syntax specialized in book authoring.
174 lines (173 loc) • 6.85 kB
JavaScript
;
/**
* derived from `remark-sectionize`.
* original: 2019 Jake Low
* modified: 2020 Yasuaki Uechi, 2021 and later is Akabeko
* @license MIT
* @see https://github.com/jake-low/remark-sectionize
*/
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.mdast = void 0;
var unist_util_find_after_1 = __importDefault(require("unist-util-find-after"));
var unist_util_visit_parents_1 = __importDefault(require("unist-util-visit-parents"));
/** Maximum depth of hierarchy to process headings. */
var MAX_HEADING_DEPTH = 6;
/**
* Create the attribute properties of a section.
* @param depth - Depth of heading elements that are sections.
* @param node - Node of Markdown AST.
* @returns Properties.
*/
var createProperties = function (depth, node) {
var _a, _b;
var properties = {
class: ["level".concat(depth)],
};
if ((_b = (_a = node === null || node === void 0 ? void 0 : node.data) === null || _a === void 0 ? void 0 : _a.hProperties) === null || _b === void 0 ? void 0 : _b.id) {
properties['aria-labelledby'] = node === null || node === void 0 ? void 0 : node.data.hProperties.id;
}
return properties;
};
var getHeadingLine = function (node, file) {
var _a, _b, _c, _d;
if ((node === null || node === void 0 ? void 0 : node.type) !== 'heading') {
return '';
}
var startOffset = (_b = (_a = node.position) === null || _a === void 0 ? void 0 : _a.start.offset) !== null && _b !== void 0 ? _b : 0;
var endOffset = (_d = (_c = node.position) === null || _c === void 0 ? void 0 : _c.end.offset) !== null && _d !== void 0 ? _d : 0;
var text = file.toString().slice(startOffset, endOffset);
return text.trim();
};
/**
* Check if the heading has a non-section mark (sufficient number of closing hashes).
* @param node Node of Markdown AST.
* @param file Virtual file.
* @returns `true` if the node has a non-section mark.
*/
var hasNonSectionMark = function (node, file) {
var _a, _b, _c;
var line = getHeadingLine(node, file);
return (!!line && ((_c = (_b = (_a = /^#.*[ \t](#+)$/.exec(line)) === null || _a === void 0 ? void 0 : _a[1]) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) >= node.depth);
};
/**
* Check if the node is a section-end mark (line with only hashes).
* @param node Node of Markdown AST.
* @returns `true` if the node is a section-end mark.
*/
var isSectionEndMark = function (node, file) {
var _a, _b;
var line = getHeadingLine(node, file);
return !!line && ((_b = (_a = /^(#+)$/.exec(line)) === null || _a === void 0 ? void 0 : _a[1]) === null || _b === void 0 ? void 0 : _b.length) === node.depth;
};
/**
* Wrap the header in sections.
* - Do not sectionize if parent is `blockquote`.
* - Set the `levelN` class in the section to match the heading depth.
* @param node Node of Markdown AST.
* @param ancestors Parents.
* @todo handle `@subtitle` properly.
*/
var sectionizeIfRequired = function (node, ancestors, file) {
var _a;
if (hasNonSectionMark(node, file)) {
return;
}
var parent = ancestors[ancestors.length - 1];
if (parent.type === 'blockquote') {
return;
}
var start = node;
var depth = start.depth;
// check if it's HTML end tag without corresponding start tag in sibling nodes.
var isHtmlEnd = function (node) {
var _a;
if (node.type !== 'html') {
return false;
}
var tag = (_a = /<\/([^>\s]+)\s*>[^<]*$/.exec(node.value)) === null || _a === void 0 ? void 0 : _a[1];
if (!tag) {
return false;
}
// it's HTML end tag, check if it has corresponding start tag
var isHtmlStart = function (node) {
return node.type === 'html' && new RegExp("<".concat(tag, "\\b[^>]*>")).test(node.value);
};
var htmlStart = (0, unist_util_find_after_1.default)(parent, start, isHtmlStart);
if (!htmlStart ||
parent.children.indexOf(htmlStart) > parent.children.indexOf(node)) {
// corresponding start tag is not found in this section level,
// check if it is found earlier.
var htmlStart1 = (0, unist_util_find_after_1.default)(parent, 0, isHtmlStart);
if (htmlStart1 &&
parent.children.indexOf(htmlStart1) < parent.children.indexOf(start)) {
return true;
}
}
return false;
};
var isEnd = function (node) {
return (node.type === 'heading' && node.depth <= depth) ||
node.type === 'export' ||
isHtmlEnd(node);
};
var end = (0, unist_util_find_after_1.default)(parent, start, isEnd);
var startIndex = parent.children.indexOf(start);
var endIndex = parent.children.indexOf(end);
var between = parent.children.slice(startIndex, endIndex > 0 ? endIndex : undefined);
var hProperties = createProperties(depth, node);
// {hidden} specifier
if (Object.keys(node.data.hProperties).includes('hidden')) {
node.data.hProperties.hidden = 'hidden';
}
var isDuplicated = parent.type === 'section';
if (isDuplicated) {
if ((_a = parent.data) === null || _a === void 0 ? void 0 : _a.hProperties) {
parent.data.hProperties = __assign(__assign({}, parent.data.hProperties), hProperties);
}
return;
}
var type = 'section';
var section = {
type: type,
data: {
hName: type,
hProperties: hProperties,
},
depth: depth,
children: between,
};
parent.children.splice(startIndex, section.children.length +
(isSectionEndMark(end, file) && end.depth === depth ? 1 : 0), section);
};
/**
* Process Markdown AST.
* @returns Transformer.
*/
var mdast = function () { return function (tree, file) {
var sectionize = function (node, ancestors) {
sectionizeIfRequired(node, ancestors, file);
};
var _loop_1 = function (depth) {
(0, unist_util_visit_parents_1.default)(tree, function (node) {
return node.type === 'heading' && node.depth === depth;
}, sectionize);
};
for (var depth = MAX_HEADING_DEPTH; depth > 0; depth--) {
_loop_1(depth);
}
}; };
exports.mdast = mdast;