shadowfacts.net/lib/markdown.ts

78 lines
2.4 KiB
TypeScript

import MarkdownIt from "markdown-it";
import MarkdownItFootnote from "markdown-it-footnote";
import slugify from "@sindresorhus/slugify";
import * as util from "./util";
import { URL } from "url";
const md = new MarkdownIt({
highlight: util.highlight,
html: true,
typographer: true
});
md.use(MarkdownItFootnote);
// Inserts heading anchors
// Based on https://github.com/valeriangalliat/markdown-it-anchor
md.core.ruler.push("header anchor", (state) => {
const tokens = state.tokens;
tokens
.filter((token) => token.type === "heading_open")
.forEach((token) => {
const index = tokens.indexOf(token);
const title = tokens[index + 1]
.children
.filter((token) => token.type === "text" || token.type == "code_inline")
.reduce((acc, t) => acc + t.content, "");
let slug = token.attrGet("id") || slugify(title);
if (token.attrGet("id") == null) {
token.attrPush(["id", slug]);
}
const linkTokens = [
Object.assign(new state.Token("link_open", "a", 1), {
attrs: [
["class", "header-anchor"],
["href", "#" + slug],
["aria-hidden", "true"],
["role", "presentation"]
]
}),
Object.assign(new state.Token("html_block", "", 0), { content: token.markup }),
new state.Token("link_close", "a", -1),
Object.assign(new state.Token("text", "", 0), { content: " " })
];
state.tokens[index + 1].children.unshift(...linkTokens);
});
});
// Adds data-link attributes to anchor elements
const defaultRenderer = md.renderer.rules.link_open || function(tokens, index, options, env, self) {
return self.renderToken(tokens, index, options);
};
md.renderer.rules.link_open = function(tokens, index, options, env, self) {
const href = tokens[index].attrGet("href");
if (href != null) {
try {
const parsed = new URL(href);
const formattedPathname = parsed.pathname === "/" ? "" : parsed.pathname;//.length > 20 ? parsed.pathname.substr(0, 20) + "..." : parsed.pathname;
let formatted = `${parsed.hostname}${formattedPathname}`;
if (formatted.length > 40) {
formatted = formatted.substr(0, 40) + "...";
}
tokens[index].attrPush(["data-link", formatted]);
} catch (e) {
// URL constructor throws on invalid URLs (such as ones that only contain a fragment), so swallow those exceptions
}
}
return defaultRenderer(tokens, index, options, env, self);
};
export function render(text: string): string {
return md.render(text);
}