feat: MVP-3 Sprint 4 - Co-usage, metrics, centrality, creation source, feature flags
- 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>
This commit is contained in:
72
src/lib/link-suggestions.ts
Normal file
72
src/lib/link-suggestions.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
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, '\\$&')
|
||||
}
|
||||
Reference in New Issue
Block a user