@diplodoc/transform
Version:
A simple transformer of text in YFM (Yandex Flavored Markdown) to HTML
93 lines (74 loc) • 2.83 kB
text/typescript
import Token from 'markdown-it/lib/token';
import {Heading} from './typings';
function getTitle(token: Token) {
return (
token.children?.reduce((acc, tok) => {
if (tok.type === 'text' || tok.type === 'code_inline') {
return acc + tok.content;
}
return acc;
}, '') || ''
);
}
function getHref(token: Token) {
return '#' + (token.attrGet('id') || '');
}
export = function getHeadings(tokens: Token[], needFlatListHeadings?: boolean, href = '') {
return needFlatListHeadings
? getFlatListHeadings(tokens, href)
: getFilteredHeadings(tokens, href);
};
function getFilteredHeadings(tokens: Token[], href: string) {
const headings: Heading[] = [];
let parents = [headings];
let prevLevel;
for (let i = 0; i < tokens.length; i++) {
const isHeading = tokens[i].type === 'heading_open';
const level = Number.parseInt(tokens[i].tag.slice(1), 10);
if (isHeading && level >= 2) {
const entry = {
title: getTitle(tokens[i + 1]),
href: href + getHref(tokens[i]),
level,
};
let closestParent = parents[parents.length - 1];
if ((!prevLevel && level === 2) || prevLevel === level) {
closestParent.push(entry);
prevLevel = level;
// skip if nested heading level is lower than for previous by 2 or more
} else if (prevLevel && level - prevLevel === 1) {
const lastItemInClosestParent = closestParent[closestParent.length - 1];
const newParent = (lastItemInClosestParent.items = [entry]);
parents.push(newParent);
prevLevel = level;
} else if (prevLevel && level < prevLevel) {
const levelDiff = prevLevel - level;
const closestParentIndex = parents.length - levelDiff - 1;
if (closestParentIndex < 0) {
continue;
}
closestParent = parents[closestParentIndex];
closestParent.push(entry);
parents = parents.slice(0, closestParentIndex + 1);
prevLevel = level;
}
}
}
return headings;
}
function getFlatListHeadings(tokens: Token[], href: string) {
const headings: Heading[] = [];
for (let i = 0; i < tokens.length; i++) {
const isHeading = tokens[i].type === 'heading_open';
const level = Number.parseInt(tokens[i].tag.slice(1), 10);
if (!isHeading) {
continue;
}
headings.push({
title: getTitle(tokens[i + 1]),
href: href + getHref(tokens[i]),
level,
});
}
return headings;
}