| 1 | import { unified } from "unified"; |
| 2 | import remarkParse from "remark-parse"; |
| 3 | import remarkGfm from "remark-gfm"; |
| 4 | import remarkMath from "remark-math"; |
| 5 | |
| 6 | const blockBudget = 32 << 10; |
| 7 | function chunks(text: string): string[] { |
| 8 | const out: string[] = []; |
| 9 | while (text.length > blockBudget) { |
| 10 | let end = text.lastIndexOf("\n", blockBudget); |
| 11 | if (end < blockBudget / 2) end = text.lastIndexOf(" ", blockBudget); |
| 12 | if (end < blockBudget / 2) end = blockBudget; |
| 13 | else end++; |
| 14 | const code = text.charCodeAt(end - 1); if (code >= 0xd800 && code <= 0xdbff) end--; |
| 15 | out.push(text.slice(0, end)); text = text.slice(end); |
| 16 | } |
| 17 | if (text) out.push(text); |
| 18 | return out; |
| 19 | } |
| 20 | /** Keep definitions available when a message's top-level blocks are paged. */ |
| 21 | export function splitExportMarkdown(source: string): string[] { |
| 22 | if (source.length <= blockBudget) return [source]; |
| 23 | const tree = unified().use(remarkParse).use(remarkGfm).use(remarkMath).parse(source); |
| 24 | const definitions = tree.children.filter(node => node.type === "definition" || node.type === "footnoteDefinition") |
| 25 | .map(node => source.slice(node.position?.start.offset, node.position?.end.offset)).join("\n"); |
| 26 | const out: string[] = []; |
| 27 | for (const node of tree.children) { |
| 28 | if (node.type === "definition" || node.type === "footnoteDefinition") continue; |
| 29 | const text = source.slice(node.position?.start.offset, node.position?.end.offset); |
| 30 | if (node.type === "code" && text.length > blockBudget) { |
| 31 | const fence = "`".repeat(Math.max(2, ...Array.from(node.value.matchAll(/`+/g), match => match[0].length)) + 1); |
| 32 | for (const part of chunks(node.value)) out.push(`${fence}${node.lang ?? ""}\n${part}\n${fence}`); |
| 33 | } else if (node.type === "table" && text.length > blockBudget) { |
| 34 | const lines = text.split("\n"); const header = lines.slice(0, 2).join("\n"); |
| 35 | let group = header; |
| 36 | for (const line of lines.slice(2)) { if (group.length + line.length > blockBudget && group !== header) { out.push(group + "\n" + definitions); group = header; } group += "\n" + line; } |
| 37 | out.push(group + "\n" + definitions); |
| 38 | } else { |
| 39 | for (const part of chunks(text)) out.push(part + (definitions ? "\n\n" + definitions : "")); |
| 40 | } |
| 41 | } |
| 42 | return out; |
| 43 | } |
| 44 |