- Add NoteCoUsage model and co-usage tracking when viewing notes - Add creationSource field to notes (form/quick/import) - Add dashboard metrics API (/api/metrics) - Add centrality calculation (/api/centrality) - Add feature flags system for toggling features - Add multiline QuickAdd with smart paste type detection - Add internal link suggestions while editing notes - Add type inference for automatic note type detection - Add comprehensive tests for type-inference and link-suggestions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
73 lines
1.9 KiB
TypeScript
73 lines
1.9 KiB
TypeScript
import { prisma } from '@/lib/prisma'
|
|
|
|
export interface LinkSuggestion {
|
|
term: string
|
|
noteId: string
|
|
noteTitle: string
|
|
}
|
|
|
|
/**
|
|
* Find potential wiki-link suggestions in content.
|
|
* Returns notes whose titles appear in the content and could be converted to [[links]].
|
|
*/
|
|
export async function findLinkSuggestions(
|
|
content: string,
|
|
currentNoteId?: string
|
|
): Promise<LinkSuggestion[]> {
|
|
if (!content.trim() || content.length < 10) {
|
|
return []
|
|
}
|
|
|
|
// Get all note titles except current note
|
|
const allNotes = await prisma.note.findMany({
|
|
where: currentNoteId ? { id: { not: currentNoteId } } : undefined,
|
|
select: { id: true, title: true },
|
|
})
|
|
|
|
if (allNotes.length === 0) {
|
|
return []
|
|
}
|
|
|
|
// Find titles that appear in content (case-insensitive)
|
|
const suggestions: LinkSuggestion[] = []
|
|
const contentLower = content.toLowerCase()
|
|
|
|
for (const note of allNotes) {
|
|
const titleLower = note.title.toLowerCase()
|
|
// Check if title appears as a whole word in content
|
|
const regex = new RegExp(`\\b${escapeRegex(titleLower)}\\b`, 'i')
|
|
if (regex.test(content)) {
|
|
suggestions.push({
|
|
term: note.title,
|
|
noteId: note.id,
|
|
noteTitle: note.title,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Sort by title length (longer titles first - more specific matches)
|
|
return suggestions.sort((a, b) => b.noteTitle.length - a.noteTitle.length)
|
|
}
|
|
|
|
/**
|
|
* Replace terms in content with wiki-links
|
|
*/
|
|
export function applyWikiLinks(
|
|
content: string,
|
|
replacements: { term: string; noteId: string }[]
|
|
): string {
|
|
let result = content
|
|
|
|
for (const { term, noteId } of replacements) {
|
|
// Replace all occurrences of the term (case-insensitive, whole word only)
|
|
const regex = new RegExp(`\\b(${escapeRegex(term)})\\b`, 'gi')
|
|
result = result.replace(regex, `[[${term}]]`)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
function escapeRegex(str: string): string {
|
|
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
}
|