93 lines
2.6 KiB
TypeScript
93 lines
2.6 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);
|
|
let hostname = parsed.hostname;
|
|
|
|
// hide wwww from beginning of domain
|
|
if (hostname.startsWith("www.")) {
|
|
hostname = hostname.substr(4);
|
|
}
|
|
|
|
// if the path is /, omit it
|
|
const formattedPathname = parsed.pathname === "/" ? "" : parsed.pathname;
|
|
|
|
let formatted = `${hostname}${formattedPathname}`;
|
|
|
|
if (hostname.endsWith("youtube.com") && formattedPathname == "/watch") {
|
|
// otherwise yt video links end up as `youtube.com/watch` which looks weird
|
|
formatted += parsed.search;
|
|
}
|
|
|
|
if (formatted.length > 40) {
|
|
formatted = formatted.substr(0, 40) + "...";
|
|
}
|
|
tokens[index].attrPush(["data-link", formatted]);
|
|
} catch (e) {
|
|
tokens[index].attrPush(["data-link", href]);
|
|
}
|
|
}
|
|
|
|
return defaultRenderer(tokens, index, options, env, self);
|
|
};
|
|
|
|
export function render(text: string): string {
|
|
return md.render(text);
|
|
}
|