PageSourceSearch

https://sat1regional.de/_build/assets/seoDescription-B_c1FOLC.js

js sat1regional.de collected 2026-10-02 03:06:30 UTC 3,149 bytes, 76 lines download raw bytes

1//#region src/utils/seoDescription.ts
2/**
3* Recursively collect text from a Lexical node tree into `parts`.
4* Only `text` leaf nodes contribute; structure (headings, lists, links) is flattened.
5*/
6function collectLexicalText(node, parts) {
7	if (typeof node !== "object" || node === null) return;
8	const n = node;
9	if (n.type === "text" && typeof n.text === "string") parts.push(n.text);
10	if (Array.isArray(n.children)) for (const child of n.children) collectLexicalText(child, parts);
11}
12/** Flatten a Lexical rich text value to a single normalised plain-text string. */
13function richTextToPlainText(richText) {
14	if (typeof richText !== "object" || richText === null) return "";
15	const root = richText.root;
16	if (typeof root !== "object" || root === null) return "";
17	const parts = [];
18	const children = root.children;
19	if (Array.isArray(children)) for (const child of children) collectLexicalText(child, parts);
20	return parts.join(" ").replace(/\s+/g, " ").trim();
21}
22/**
23* Find the first body rich text in a content array.
24* Mirrors the CMS extractor: Article Story block (`block.richText`) wins over a
25* generic Rich Text block (`block.columns[0].richText`), in document order.
26*/
27function findFirstBodyRichText(content) {
28	if (!content || !Array.isArray(content)) return null;
29	for (const item of content) {
30		if (item.type !== "block" || !item.block || item.block.length === 0) continue;
31		const block = item.block[0];
32		if (block.blockType === "articleStoryBlock" && block.richText) return block.richText;
33		if (block.blockType === "richtext") {
34			const columns = block.columns;
35			if (Array.isArray(columns) && columns.length > 0) {
36				const firstColumn = columns[0];
37				if (firstColumn?.richText) return firstColumn.richText;
38			}
39		}
40	}
41	return null;
42}
43/**
44* Truncate plain text to a meta-description-friendly length.
45*
46* Policy:
47*  - ~160 char ceiling (Google snippet sweet spot; longer text is wasted).
48*  - Trim back to the last whole word so we never cut mid-word.
49*  - Strip trailing punctuation/whitespace before appending an ellipsis.
50*  - Append "…" only when text was actually cut.
51*  - Single token longer than maxLength is hard-cut rather than dropped.
52*/
53function truncateForMeta(text, maxLength = 160) {
54	const normalized = text.trim();
55	if (!normalized) return "";
56	if (normalized.length <= maxLength) return normalized;
57	const slice = normalized.slice(0, maxLength);
58	const lastSpace = slice.lastIndexOf(" ");
59	return `${(lastSpace > 0 ? slice.slice(0, lastSpace) : slice).replace(/[\s.,;:!?]+$/, "")}…`;
60}
61/**
62* Resolve a content document's description, preferring the editor-set
63* `meta.description` and falling back to extracted, truncated body text.
64* Returns "" when nothing is available (callers treat "" as "no description").
65*/
66function getContentDescription(doc) {
67	const explicit = doc.meta?.description?.trim();
68	if (explicit) return explicit;
69	const richText = findFirstBodyRichText(doc.content);
70	if (!richText) return "";
71	return truncateForMeta(richTextToPlainText(richText));
72}
73//#endregion
74export { truncateForMeta as n, getContentDescription as t };
75
76//# sourceMappingURL=seoDescription-B_c1FOLC.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.