import { marked } from 'marked' /** * Calls the Strapi REST API from the server only, so the internal URL and any * future API token never reach the browser. */ export async function strapiFetch(path: string): Promise { const { strapiUrl, strapiToken } = useRuntimeConfig() try { return (await $fetch(path, { baseURL: strapiUrl, headers: strapiToken ? { Authorization: `Bearer ${strapiToken}` } : undefined, })) as T } catch (error) { console.error(`Strapi request failed: ${path}`, error) throw createError({ statusCode: 502, statusMessage: 'Content service unavailable' }) } } /** * Renders the Markdown body server-side, so the HTML is crawlable and `marked` * stays out of the client bundle. * * ponytail: no HTML sanitisation — article bodies come from authenticated * editors only. Add DOMPurify here if authoring is ever opened up further. */ export function renderMarkdown(source: string | null | undefined): string { return source ? (marked.parse(source, { async: false }) as string) : '' } /** * There is no excerpt field: the meta description is the opening of the body, * stripped of Markdown syntax and clipped on a word boundary. */ export function summarise(source: string | null | undefined, maxLength = 155): string { const text = (source ?? '') .replace(/```[\s\S]*?```/g, ' ') .replace(/!\[[^\]]*\]\([^)]*\)/g, ' ') .replace(/\[([^\]]*)\]\([^)]*\)/g, '$1') .replace(/[#>*_`~-]/g, ' ') .replace(/\s+/g, ' ') .trim() if (text.length <= maxLength) return text const clipped = text.slice(0, maxLength) return `${clipped.slice(0, clipped.lastIndexOf(' ')).trimEnd()}…` } export interface StrapiList { data: T[] meta: { pagination: { page: number; pageCount: number; total: number } } }