myst-cli
Version:
Command line tools for MyST
124 lines (123 loc) • 3.74 kB
JavaScript
import { toText } from 'myst-common';
import { visit, SKIP } from 'unist-util-visit';
const IGNORED_TYPES = ['myst', 'heading'];
const INLINE_BLOCK_TYPES = [
'strong',
'emphasis',
'delete',
'superscript',
'underline',
'subscript',
'abbreviation',
'smallcaps',
'keyboard',
];
export function toSectionedParts(content) {
const sections = [];
const newSection = (heading) => {
var _a;
const info = heading
? {
text: toText(heading),
depth: heading.depth,
html_id: (_a = heading.html_id) !== null && _a !== void 0 ? _a : heading.identifier,
}
: undefined;
sections.push({ heading: info, parts: [] });
};
newSection();
visit(content, (node, index, parent) => {
if (node.type === 'heading') {
newSection(node);
}
// deny-list
if (IGNORED_TYPES.includes(node.type)) {
return SKIP;
}
const section = sections[sections.length - 1];
// Literals are fused together
if ('value' in node && node.value) {
section.parts.push(node.value);
}
// Separate non-inline block elements in terms of the bottom element
if (index != null &&
parent != null &&
// Skip literal nodes
'children' in node &&
// Skip inline-block nodes
!INLINE_BLOCK_TYPES.includes(node.type)) {
// Find the previous rendered node type
let previousNode;
for (let i = index - 1; i >= 0; i--) {
const maybePreviousNode = parent.children[i];
// Stop searching if we hit a heading
if (maybePreviousNode.type === 'heading') {
break;
}
// Stop searching and record result if it wasn't ignored
if (!IGNORED_TYPES.includes(maybePreviousNode.type)) {
previousNode = maybePreviousNode;
break;
}
}
// Don't separate interior nodes
if (previousNode !== undefined) {
// "interior" paragraphs are always
section.parts.push('\n\n');
}
}
});
return sections;
}
/**
* Determine the "level" of a heading as a literal type
*
* @param heading - heading info object
*/
export function sectionToHeadingLevel(heading) {
if (!heading) {
return 'lvl1';
}
switch (heading.depth) {
case 1:
return 'lvl1';
case 2:
return 'lvl2';
case 3:
return 'lvl3';
case 4:
return 'lvl4';
case 5:
return 'lvl5';
case 6:
return 'lvl6';
default:
throw new Error(`unknown heading depth: ${heading.depth}`);
}
}
/**
* Build a DocumentHierarchy object describing the hierarchy of headings
* in an array of appearance-ordered sections.
*
* @param title - document title
* @param sections - array of section
* @param index - current section position
*/
export function buildHierarchy(title, sections, index) {
const result = { lvl1: title };
let currentDepth = 100;
// The first section is always the title section
for (let i = index; i > 0; i--) {
const { heading } = sections[i];
if (heading === undefined) {
throw new Error();
}
if (heading.depth >= currentDepth) {
continue;
}
const lvl = sectionToHeadingLevel(heading);
result[lvl] = heading.text;
currentDepth = heading.depth;
}
return result;
}