import prisma from '@/lib/prisma' /** [[Titre]] ou [[Titre|noteId]] */ const WIKILINK_RE = /\[\[([^\]|#]+?)(?:\|([^\]|#]+?))?(?:[#][^\]]+)?\]\]/g const OPEN_NOTE_IN_HREF_RE = /href\s*=\s*["']([^"']*openNote=([^"'&#]+)[^"']*)["']/gi export interface ParsedNoteLinkTarget { title: string noteId?: string snippet: string } export function extractNoteLinkTargets(content: string): ParsedNoteLinkTarget[] { const results: ParsedNoteLinkTarget[] = [] const seen = new Set() const push = (title: string, noteId: string | undefined, snippet: string) => { const key = noteId ? `id:${noteId}` : `title:${title.toLowerCase()}` if (!title && !noteId) return if (seen.has(key)) return seen.add(key) results.push({ title: title || 'Sans titre', noteId, snippet }) } const plain = content.replace(/<[^>]+>/g, ' ') let match: RegExpExecArray | null WIKILINK_RE.lastIndex = 0 while ((match = WIKILINK_RE.exec(plain)) !== null) { const title = match[1].trim() const noteId = match[2]?.trim() const start = Math.max(0, match.index - 50) const end = Math.min(plain.length, match.index + match[0].length + 50) const snippet = plain.slice(start, end).replace(/\s+/g, ' ').trim() push(title, noteId, snippet) } OPEN_NOTE_IN_HREF_RE.lastIndex = 0 while ((match = OPEN_NOTE_IN_HREF_RE.exec(content)) !== null) { const href = match[1] const noteId = decodeURIComponent(match[2].trim()) const idx = content.indexOf(href) const start = Math.max(0, idx - 50) const end = Math.min(content.length, idx + href.length + 50) const snippet = content.slice(start, end).replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim() push('', noteId, snippet) } return results } /** Parse [[wikilinks]] + liens internes openNote, synchronise la table NoteLink. */ export async function syncNoteLinksForNote( noteId: string, userId: string, content: string, ): Promise { const note = await prisma.note.findUnique({ where: { id: noteId }, select: { id: true, userId: true }, }) if (!note || note.userId !== userId) return 0 const targets = extractNoteLinkTargets(content) const upsertedIds: string[] = [] for (const target of targets) { let targetNoteId = target.noteId if (targetNoteId) { const byId = await prisma.note.findFirst({ where: { id: targetNoteId, userId, trashedAt: null }, select: { id: true }, }) if (!byId) { if (!target.title) continue targetNoteId = undefined } } if (!targetNoteId && target.title) { let targetNote = await prisma.note.findFirst({ where: { userId, title: { equals: target.title, mode: 'insensitive' }, trashedAt: null, }, select: { id: true }, }) if (!targetNote) { targetNote = await prisma.note.create({ data: { title: target.title, content: '', userId, type: 'richtext', color: 'default', isMarkdown: false, order: 0, }, select: { id: true }, }) } targetNoteId = targetNote.id } if (!targetNoteId || targetNoteId === noteId) continue await prisma.noteLink.upsert({ where: { sourceNoteId_targetNoteId: { sourceNoteId: noteId, targetNoteId, }, }, update: { contextSnippet: target.snippet.slice(0, 200) }, create: { sourceNoteId: noteId, targetNoteId, contextSnippet: target.snippet.slice(0, 200), }, }) upsertedIds.push(targetNoteId) } if (upsertedIds.length > 0) { await prisma.noteLink.deleteMany({ where: { sourceNoteId: noteId, targetNoteId: { notIn: upsertedIds }, }, }) } else { await prisma.noteLink.deleteMany({ where: { sourceNoteId: noteId } }) } return upsertedIds.length }