Files
RSSHub/docs/.format/md/hierarchySlug.js
2020-05-19 16:37:07 +08:00

127 lines
4.7 KiB
JavaScript

// A fork of https://github.com/valeriangalliat/markdown-it-anchor
const slugify = (s) => encodeURIComponent(String(s).trim().toLowerCase().replace(/\s+/g, '-'));
const position = {
false: 'push',
true: 'unshift',
};
const hasProp = Object.prototype.hasOwnProperty;
const permalinkHref = (slug) => `#${slug}`;
const permalinkAttrs = (slug) => ({});
const renderPermalink = (slug, opts, state, idx) => {
const space = () => Object.assign(new state.Token('text', '', 0), { content: ' ' });
const linkTokens = [
Object.assign(new state.Token('link_open', 'a', 1), {
attrs: [['class', opts.permalinkClass], ['href', opts.permalinkHref(slug, state)], ...Object.entries(opts.permalinkAttrs(slug, state))],
}),
Object.assign(new state.Token('html_block', '', 0), {
content: opts.permalinkSymbol,
}),
new state.Token('link_close', 'a', -1),
];
// `push` or `unshift` according to position option.
// Space is at the opposite side.
if (opts.permalinkSpace) {
linkTokens[position[!opts.permalinkBefore]](space());
}
state.tokens[idx + 1].children[position[opts.permalinkBefore]](...linkTokens);
};
const uniqueSlug = (slug, slugs, failOnNonUnique) => {
let uniq = slug;
let i = 2;
if (failOnNonUnique && hasProp.call(slugs, uniq)) {
throw Error(`User defined id attribute '${slug}' is NOT unique. Please fix it in your markdown to continue.`);
} else {
while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`;
}
slugs[uniq] = true;
return uniq;
};
const isLevelSelectedNumber = (selection) => (level) => level >= selection;
const isLevelSelectedArray = (selection) => (level) => selection.includes(level);
const getTitle = (children) => children.filter((token) => token.type === 'text' || token.type === 'code_inline').reduce((acc, t) => acc + t.content, '');
const anchor = (md, opts) => {
opts = Object.assign({}, anchor.defaults, opts);
md.core.ruler.push('anchor', (state) => {
const slugs = {};
const tokens = state.tokens;
const isLevelSelected = Array.isArray(opts.level) ? isLevelSelectedArray(opts.level) : isLevelSelectedNumber(opts.level);
for (let i = 0; i < tokens.length; i++) {
const currentLevel = tokens[i].markup.length;
if (tokens[i].type === 'heading_open' && isLevelSelected(currentLevel)) {
const titleIndex = i + 1;
const currentTitle = getTitle(tokens[titleIndex].children);
let prevTitle = tokens[i].attrGet('id');
let constructedTitle = prevTitle;
// Skip if user has defined a id
if (typeof prevTitle !== 'string') {
if (prevTitle === null) {
prevTitle = [];
}
let recurIndex = i + 2;
while (recurIndex < tokens.length && (tokens[recurIndex].type !== 'heading_open' || tokens[recurIndex].markup.length > currentLevel)) {
if (tokens[recurIndex].type === 'heading_open') {
let recurPrevTitle = tokens[recurIndex].attrGet('id');
if (typeof recurPrevTitle !== 'string') {
if (recurPrevTitle === null) {
recurPrevTitle = [];
}
recurPrevTitle = [...recurPrevTitle, currentTitle];
tokens[recurIndex].attrSet('id', recurPrevTitle);
}
}
recurIndex += 1;
}
prevTitle.push(currentTitle);
//console.log(prevTitle);
// @TODO maybe use previous slugified title so we don't need to calculate that much?
constructedTitle = prevTitle.join('-');
//console.log(constructedTitle);
}
const slug = uniqueSlug(opts.slugify(constructedTitle), slugs, true);
tokens[i].attrSet('id', slug);
if (opts.permalink) {
opts.renderPermalink(slug, opts, state, i);
}
if (opts.callback) {
opts.callback(tokens[i], { slug, constructedTitle });
}
}
}
});
};
anchor.defaults = {
level: 1,
slugify,
permalink: false,
renderPermalink,
permalinkClass: 'header-anchor',
permalinkSpace: true,
permalinkSymbol: '¶',
permalinkBefore: false,
permalinkHref,
permalinkAttrs,
};
module.exports = anchor;