File
Blob: src/sites/excerpt.ts
| 1 | import type { JSONContent } from "@tiptap/core"; |
| 2 | |
| 3 | const DESCRIPTION_MIN_BREAK = 150; |
| 4 | const DESCRIPTION_MAX_LENGTH = 160; |
| 5 | const DESCRIPTION_ELLIPSIS = "..."; |
| 6 | |
| 7 | export function extractSiteDescription(content: JSONContent): string | null { |
| 8 | const text = collapseWhitespace(collectText(content).join(" ")); |
| 9 | if (!text) return null; |
| 10 | if (text.length <= DESCRIPTION_MAX_LENGTH) return text; |
| 11 | |
| 12 | const window = text.slice(0, DESCRIPTION_MAX_LENGTH); |
| 13 | const breakAt = window.search(/\s+\S*$/); |
| 14 | if (breakAt >= DESCRIPTION_MIN_BREAK) { |
| 15 | return `${window.slice(0, breakAt).trimEnd()}${DESCRIPTION_ELLIPSIS}`; |
| 16 | } |
| 17 | return `${text.slice(0, DESCRIPTION_MAX_LENGTH - DESCRIPTION_ELLIPSIS.length).trimEnd()}${DESCRIPTION_ELLIPSIS}`; |
| 18 | } |
| 19 | |
| 20 | function collectText(node: JSONContent): string[] { |
| 21 | if (!node || typeof node !== "object") return []; |
| 22 | const chunks: string[] = []; |
| 23 | if (typeof node.text === "string") chunks.push(node.text); |
| 24 | if (node.type === "hardBreak") chunks.push(" "); |
| 25 | if (Array.isArray(node.content)) { |
| 26 | for (const child of node.content) chunks.push(...collectText(child)); |
| 27 | } |
| 28 | return chunks; |
| 29 | } |
| 30 | |
| 31 | function collapseWhitespace(value: string): string { |
| 32 | return value.replace(/\s+/g, " ").trim(); |
| 33 | } |