import type { Note } from '@/lib/types'
const MD_IMG = /!\[[^\]]*\]\(([^)\s]+)\)/g
const HTML_IMG = /
]+src=["']([^"']+)["'][^>]*>/gi
/** SSR-safe HTML entity decode so excerpt text matches server and client. */
function decodeHtmlEntities(text: string): string {
return text
.replace(/ /gi, ' ')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/(?:*39;|')/g, "'")
.replace(/(\d+);/g, (_, code: string) => {
const n = Number(code)
return n > 0 && n < 0x110000 ? String.fromCodePoint(n) : `${code};`
})
.replace(/([0-9a-f]+);/gi, (_, hex: string) => {
const n = parseInt(hex, 16)
return n > 0 && n < 0x110000 ? String.fromCodePoint(n) : `${hex};`
})
}
/**
* Plain-text preview for list view (light markdown stripping).
*/
export function stripMarkdownPreview(raw: string, maxLen = 180): string {
if (!raw?.trim()) return ''
const t = raw
.replace(/^#{1,6}\s+/gm, '')
.replace(/```[\s\S]*?```/g, ' ')
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/\*([^*]+)\*/g, '$1')
.replace(/`([^`]+)`/g, '$1')
.replace(/\[(.+?)]\([^)]+\)/g, '$1')
.replace(/^\s*[-*+]\s+/gm, '')
.replace(/^\s*\d+\.\s+/gm, '')
.replace(/\n+/g, ' ')
.replace(/\s+/g, ' ')
.trim()
if (t.length > maxLen) {
return `${t.slice(0, maxLen).trim()}…`
}
return t
}
export function getNoteDisplayTitle(note: { title: string | null; content: string; type: string }, untitled: string): string {
const title = note.title?.trim()
if (title) return title
if (note.type === 'checklist') {
const line = note.content?.split('\n').find((l) => l.trim())
if (line) return stripMarkdownPreview(line, 80) || untitled
}
const preview = stripMarkdownPreview(note.content || '', 100)
return preview || untitled
}
export function getNoteFeedImage(note: Note): string | null {
const first = note.images?.[0]?.trim()
if (first) return first
const content = note.content || ''
MD_IMG.lastIndex = 0
const md = MD_IMG.exec(content)
if (md?.[1]) return md[1]
HTML_IMG.lastIndex = 0
const html = HTML_IMG.exec(content)
if (html?.[1]) return html[1]
return null
}
/** Plain excerpt for collection cards (HTML + markdown noise stripped). */
export function getNotePlainExcerpt(note: Note, maxLen = 240): string {
let raw = note.content || ''
raw = raw.replace(/!\[[^\]]*\]\([^)]+\)/g, ' ')
raw = raw.replace(/<[^>]+>/g, ' ')
raw = decodeHtmlEntities(raw)
return stripMarkdownPreview(raw, maxLen)
}
/** Adapte un SVG miniature (viewBox 4:3 liste) pour remplir une vignette carte 16:10. */
export function prepareNoteIllustrationForGrid(svg: string): string {
return svg.replace(/