Files
tolaria/src/utils/suggestionEnrichment.ts
Test c0fed9c5c0 fix: wikilink autocomplete uses relative path, prevent silent rename
Bug 1: Wikilink autocomplete now always inserts the vault-relative path
as the target (e.g. [[docs/adr/0001-tauri-stack|Title]]) instead of
just the filename. This ensures wikilinks are unambiguous and resolve
correctly across subfolders after reload.

Bug 2: unique_dest_path now excludes the source file from collision
checks, preventing false positives where a note's own file is treated
as a naming conflict (causing silent -2 suffix renames).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:15:17 +02:00

66 lines
2.4 KiB
TypeScript

import type { VaultEntry } from '../types'
import { getTypeColor, getTypeLightColor } from './typeColors'
import { getTypeIcon } from '../components/NoteItem'
import { deduplicateByPath, disambiguateTitles } from './wikilinkSuggestions'
import { bestSearchRank } from './fuzzyMatch'
import { filterSuggestionItems } from '@blocknote/core/extensions'
import type { WikilinkSuggestionItem } from '../components/WikilinkSuggestionMenu'
import { relativePathStem } from './wikilink'
const MAX_RESULTS = 20
interface BaseSuggestionItem {
title: string
aliases: string[]
group: string
entryTitle: string
path: string
}
/** Build the wikilink target: relative path stem with pipe display for the title.
* e.g. "docs/adr/0001-tauri-stack|Tauri Stack" for subfolders,
* "roadmap|Roadmap" for root files. */
function buildTarget(item: BaseSuggestionItem, vaultPath: string): string {
const stem = relativePathStem(item.path, vaultPath)
return stem === item.entryTitle ? stem : `${stem}|${item.entryTitle}`
}
/** Add onItemClick to raw suggestion candidates.
* Always inserts the vault-relative path as the wikilink target
* so links are unambiguous and work across subfolders. */
export function attachClickHandlers(
candidates: BaseSuggestionItem[],
insertWikilink: (target: string) => void,
vaultPath: string,
) {
return candidates.map(item => ({
...item,
onItemClick: () => insertWikilink(buildTarget(item, vaultPath)),
}))
}
/** Filter, deduplicate, disambiguate, and enrich suggestion items with type metadata */
export function enrichSuggestionItems(
items: (BaseSuggestionItem & { onItemClick: () => void })[],
query: string,
typeEntryMap: Record<string, VaultEntry>,
): WikilinkSuggestionItem[] {
const filtered = filterSuggestionItems(items, query)
filtered.sort((a, b) =>
bestSearchRank(query, a.entryTitle, a.aliases) - bestSearchRank(query, b.entryTitle, b.aliases),
)
const sliced = filtered.slice(0, MAX_RESULTS)
const final = disambiguateTitles(deduplicateByPath(sliced))
return final.map(({ group, ...rest }) => {
const noteType = group !== 'Note' ? group : undefined
const te = typeEntryMap[group]
return {
...rest,
noteType,
typeColor: noteType ? getTypeColor(group, te?.color) : undefined,
typeLightColor: noteType ? getTypeLightColor(group, te?.color) : undefined,
TypeIcon: noteType ? getTypeIcon(group, te?.icon) : undefined,
}
})
}