import DOMPurify from 'dompurify' // 后端 goldmark 已转义原始 HTML,这里再过一道 DOMPurify 作纵深防御, // 所有 v-html 出口必须经过它。 export function sanitizeHtml(html) { return DOMPurify.sanitize(html || '') } // 用 Intl.DateTimeFormat — locale 感知,未来要 i18n 只换 locale 即可 const longDateFmt = new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }) const shortDateFmt = new Intl.DateTimeFormat('sv-SE') // YYYY-MM-DD export function formatDate(iso) { if (!iso) return '' const d = new Date(iso) if (Number.isNaN(d.getTime())) return iso return longDateFmt.format(d) } export function formatDateShort(iso) { if (!iso) return '' const d = new Date(iso) if (Number.isNaN(d.getTime())) return iso return shortDateFmt.format(d) } // 时间线上的相对时间:三天内用「几小时前」,同年省略年份 export function relativeDate(iso) { if (!iso) return '' const d = new Date(iso) if (Number.isNaN(d.getTime())) return iso const diff = Date.now() - d.getTime() const hour = 3600 * 1000 if (diff < hour) return `${Math.max(1, Math.floor(diff / 60000))} 分钟前` if (diff < 24 * hour) return `${Math.floor(diff / hour)} 小时前` if (diff < 3 * 24 * hour) return `${Math.floor(diff / (24 * hour))} 天前` const sameYear = d.getFullYear() === new Date().getFullYear() return sameYear ? `${d.getMonth() + 1} 月 ${d.getDate()} 日` : `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()}` } export function stripTags(html) { // 不用 div.innerHTML:那会真正解析并触发 之类的事件 return new DOMParser().parseFromString(html || '', 'text/html').body.textContent || '' } // 短文在列表里没有标题,用正文首句当索引 export function displayTitle(post) { if (post.kind === 'short') return '' return post.title || '无题' } export function excerpt(post, limit = 60) { if (post.summary) return post.summary const text = stripTags(post.content_html || '') return text.length > limit ? text.slice(0, limit) + '…' : text } export function minutesLabel(post) { if (post.kind === 'short') return '短文' return `${post.reading_minutes || 1} 分钟` }