~james/core

core/web/src/lib/markup/text.ts -rw-r--r-- 1.7 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import MarkdownIt from "markdown-it";
import type Token from "markdown-it/lib/token.mjs";
import { full as emoji } from "markdown-it-emoji";

const md = new MarkdownIt({ html: true }).use(emoji);

const inlineText = (tokens: Token[]): string =>
	tokens
		.map((token) => {
			switch (token.type) {
				case "text":
				case "code_inline":
				case "emoji":
					return token.content;
				case "image":
					return inlineText(token.children ?? []);
				case "softbreak":
				case "hardbreak":
					return " ";
				default:
					return "";
			}
		})
		.join("");

export const markdownToText = (source: string): string =>
	md
		.parse(source, {})
		.map((token) => {
			if (token.type === "inline") return inlineText(token.children ?? []);
			if (token.type === "fence" || token.type === "code_block") return token.content;
			return "";
		})
		.filter(Boolean)
		.join(" ")
		.replace(/\s+/g, " ")
		.trim();

// parsing a whole 256 KiB body costs about 12 ms of every page view
const SUMMARY_SOURCE_CHARS = 4096;

const LINK_DEFINITION = /^ {0,3}\[(?!\^)[^\]\n]+\]:[ \t]*\S.*$/gm;

const withoutComments = (source: string): string => {
	let out = "";
	let at = 0;
	for (;;) {
		const open = source.indexOf("<!--", at);
		const close = open < 0 ? -1 : source.indexOf("-->", open + 4);
		if (close < 0) return out + source.slice(at);
		out += source.slice(at, open);
		at = close + 3;
	}
};

export const summarize = (source: string, limit = 160): string => {
	const visible = withoutComments(source).replace(LINK_DEFINITION, "");
	const text = markdownToText(visible.slice(0, SUMMARY_SOURCE_CHARS));
	return text.length > limit || visible.length > SUMMARY_SOURCE_CHARS
		? `${text.slice(0, limit - 1).trimEnd()}…`
		: text;
};