import { cache } from 'react'; import sanitizeHtml from 'sanitize-html'; import { tenantApi, safeUrl } from './api'; export type Blog = { id: string | number; title: string; slug?: string; excerpt?: string; summary?: string; content?: string; text?: string; coverUrl?: string; imageUrl?: string; publishedAt?: string }; export type BlogList = { items: Blog[]; total: number; page: number; pageSize: number }; export const getBlogs = cache(async (page: number) => { const data = await tenantApi(`/blogs?page=${page}&pageSize=12`); if (!Array.isArray(data.items) || typeof data.total !== 'number') throw new Error('Invalid blog list response'); return data; }); export const getBlog = cache(async (id: string) => { const data = await tenantApi<{blog: Blog}>(`/blogs/by-id/${encodeURIComponent(id)}`); if (!data.blog || !data.blog.id || typeof data.blog.title !== 'string') throw new Error('Invalid blog detail response'); return data.blog; }); export function titleSlug(title: string) { return title.trim().replace(/[^\p{L}\p{N}\s-]/gu, '').replace(/[\s-]+/g, '-') || 'article'; } export function blogHref(blog: Blog) { return `/blog/${encodeURIComponent(blog.id)}/${encodeURIComponent(titleSlug(blog.title))}`; } export function plainText(value: string) { return sanitizeHtml(value, {allowedTags: [], allowedAttributes: {}}); } export function summary(blog: Blog) { return plainText(blog.excerpt || blog.summary || blog.content || blog.text || '').slice(0,160); } export function cover(blog: Blog) { return safeUrl(blog.coverUrl || blog.imageUrl); } export function articleHtml(blog: Blog) { return sanitizeHtml(blog.content || blog.text || '', { allowedTags: ['p','br','h2','h3','h4','strong','b','em','i','ul','ol','li','blockquote','a','img','figure','figcaption','table','thead','tbody','tr','th','td','hr','pre','code'], allowedAttributes: {a:['href','title'],img:['src','alt','width','height']}, allowedSchemes: ['https','http','mailto'], allowedSchemesByTag: {img:['https']}, allowProtocolRelative: false, transformTags: {h1:'h2'}, }); }