@fedify/markdown-it-hashtag
Version:
A markdown-it plugin that parses and renders Mastodon-style #hashtags.
102 lines (101 loc) • 3.56 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.hashtag = void 0;
const html_js_1 = require("./html.js");
const label_js_1 = require("./label.js");
/**
* A markdown-it plugin to parse and render Mastodon-style hashtags.
*/
const hashtag = (md, options) => {
md.core.ruler.after("inline", "hashtag", (state) => parseHashtag(state, options));
md.renderer.rules.hashtag = renderHashtag;
};
exports.hashtag = hashtag;
function parseHashtag(state, options) {
for (const blockToken of state.tokens) {
if (blockToken.type !== "inline")
continue;
if (blockToken.children == null)
continue;
let linkDepth = 0;
let htmlLinkDepth = 0;
blockToken.children = blockToken.children.flatMap((token) => {
if (token.type === "link_open") {
linkDepth++;
}
else if (token.type === "link_close") {
linkDepth--;
}
else if (token.type === "html_inline") {
if ((0, html_js_1.isLinkOpen)(token.content)) {
htmlLinkDepth++;
}
else if ((0, html_js_1.isLinkClose)(token.content)) {
htmlLinkDepth--;
}
}
if (linkDepth > 0 || htmlLinkDepth > 0 || token.type !== "text") {
return [token];
}
return splitTokens(token, state, options);
});
}
}
const HASHTAG_PATTERN = /#[\w\p{L}]+/giu;
function splitTokens(token, state, options) {
const { content, level } = token;
const tokens = [];
let pos = 0;
for (const match of content.matchAll(HASHTAG_PATTERN)) {
if (match.index == null)
continue;
if (match.index > pos) {
const token = new state.Token("text", "", 0);
token.content = content.substring(pos, match.index);
token.level = level;
tokens.push(token);
}
const href = options?.link?.(match[0], state.env);
if (href == null && options?.link != null) {
const token = new state.Token("text", "", 0);
token.content = match[0];
token.level = level;
tokens.push(token);
pos = match.index + match[0].length;
continue;
}
const token = new state.Token("hashtag", "", 0);
token.content = options?.label?.(match[0], state.env) ??
(0, label_js_1.spanHashAndTag)(match[0]);
token.level = level;
const attrs = options?.linkAttributes?.(match[0], state.env) ?? {};
attrs.href = href ?? `${match[0]}`;
token.attrs = Object.entries(attrs);
token.info = match[0];
tokens.push(token);
pos = match.index + match[0].length;
}
if (pos < content.length) {
const token = new state.Token("text", "", 0);
token.content = content.substring(pos);
token.level = level;
tokens.push(token);
}
return tokens;
}
function renderHashtag(tokens, idx, opts,
// deno-lint-ignore no-explicit-any
env, self) {
if (tokens.length <= idx)
return "";
const token = tokens[idx];
if (token.type !== "hashtag")
return self.renderToken(tokens, idx, opts);
if (typeof env === "object" && env !== null) {
if (!("hashtags" in env)) {
env.hashtags = [];
}
env.hashtags.push(token.info);
}
return `<a ${self.renderAttrs(token)}>${token.content}</a>`;
}