From 20aa44f8e4e7a466cdd15580c59fe220d60bb1fe Mon Sep 17 00:00:00 2001 From: Ewan Croft Date: Wed, 1 Oct 2025 16:59:40 +0100 Subject: [PATCH] feat(post-head): generate clean meta excerpts from post content --- src/lib/components/post/PostHead.svelte | 14 +++++-- src/lib/utils/textExtractor.ts | 49 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 src/lib/utils/textExtractor.ts diff --git a/src/lib/components/post/PostHead.svelte b/src/lib/components/post/PostHead.svelte index de40375..d7f352f 100644 --- a/src/lib/components/post/PostHead.svelte +++ b/src/lib/components/post/PostHead.svelte @@ -3,14 +3,20 @@ const { page } = getStores(); import type { Post } from "$components/shared"; import { env } from "$env/dynamic/public"; + import { extractTextFromHTML } from "$utils/textExtractor"; export let post: Post | undefined; + + // Generate a clean excerpt from HTML content for meta tags + $: metaExcerpt = post?.content + ? extractTextFromHTML(post.content, 50) // Shorter excerpt for meta tags + : post?.excerpt || ''; {#if post !== undefined} {post?.title} - Blog - Ewan's Corner - + - + - + {/if} - + \ No newline at end of file diff --git a/src/lib/utils/textExtractor.ts b/src/lib/utils/textExtractor.ts new file mode 100644 index 0000000..6b67ad2 --- /dev/null +++ b/src/lib/utils/textExtractor.ts @@ -0,0 +1,49 @@ +/** + * Extracts plain text from HTML content and limits by word count. + * + * @param html - The HTML string to strip down + * @param maxWords - Maximum number of words in the result + * @returns Clean plain text excerpt + */ +export function extractTextFromHTML(html: string, maxWords: number = 200): string { + if (!html) return ''; + + // SSR fallback: regex-based stripping + if (typeof document === 'undefined') { + let text = html + .replace(/)<[^<]*)*<\/script>/gi, '') + .replace(/)<[^<]*)*<\/style>/gi, '') + .replace(/<[^>]+>/g, ' ') + .replace(/\s+/g, ' ') + .trim(); + + const words = text.split(/\s+/); + if (words.length <= maxWords) { + return text; + } + return words.slice(0, maxWords).join(' ') + '...'; + } + + // Browser environment: use DOM parsing + const temp = document.createElement('div'); + temp.innerHTML = html; + + // Remove script and style elements + const scripts = temp.querySelectorAll('script, style'); + scripts.forEach(el => el.remove()); + + // Get text content + let text = temp.textContent || temp.innerText || ''; + + // Clean up whitespace + text = text.replace(/\s+/g, ' ').trim(); + + // Split into words and truncate + const words = text.split(/\s+/); + if (words.length <= maxWords) { + return text; + } + + return words.slice(0, maxWords).join(' ') + '...'; +} + -- 2.51.2