Rend les liens entre notes visibles et persistants (sync NoteLink au save, auto-save, graphe réseau rafraîchi), ajoute living blocks, Memory Echo, recherche globale, consentement IA explicite et consolide les prototypes design en architectural-grid. Co-authored-by: Cursor <cursoragent@cursor.com>
76 lines
2.4 KiB
TypeScript
76 lines
2.4 KiB
TypeScript
import type { Note } from '@/lib/types'
|
|
|
|
const MD_IMG = /!\[[^\]]*\]\(([^)\s]+)\)/g
|
|
const HTML_IMG = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi
|
|
|
|
/**
|
|
* 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, ' ')
|
|
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(/<svg([^>]*)>/i, (_, attrs: string) => {
|
|
let next = attrs.replace(/\s(width|height)=["'][^"']*["']/gi, '')
|
|
if (/preserveAspectRatio=/i.test(next)) {
|
|
next = next.replace(/preserveAspectRatio=["'][^"']*["']/i, 'preserveAspectRatio="xMidYMid slice"')
|
|
} else {
|
|
next += ' preserveAspectRatio="xMidYMid slice"'
|
|
}
|
|
return `<svg${next} width="100%" height="100%">`
|
|
})
|
|
}
|