feat: color wikilinks by destination note type

Wikilinks in the editor now show the accent color of the target note's
type (Project=red, Person=yellow, Topic=green, etc.) instead of always
being blue. Broken links (target not found) show muted text with a
dashed underline.

- Extract wikilink color resolution to src/utils/wikilinkColors.ts
- Use getTypeColor from typeColors.ts (single source of truth)
- Support alias and pipe-syntax matching in findEntryByTarget
- Add wikilink--broken CSS class for non-existent targets
- Add 17 unit tests covering all color resolution cases
- Update mock data with wikilinks to various note types + broken link

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-02-23 20:44:30 +01:00
parent d54c956082
commit 7140bf052d
5 changed files with 190 additions and 31 deletions

View File

@@ -1,6 +1,5 @@
/* Wikilink inline content */
/* Wikilink inline content — color is set via inline style per note type */
.wikilink {
color: var(--accent-blue, #2196f3);
cursor: pointer;
text-decoration: underline;
text-decoration-style: dotted;
@@ -10,8 +9,18 @@
}
.wikilink:hover {
background: var(--accent-blue-bg, rgba(33, 150, 243, 0.1));
text-decoration-style: solid;
opacity: 0.85;
}
/* Broken wikilink: dashed underline + lower opacity */
.wikilink--broken {
text-decoration-style: dashed;
opacity: 0.7;
}
.wikilink--broken:hover {
opacity: 0.55;
}
/* BlockNote container */

View File

@@ -15,6 +15,7 @@ import { TabBar } from './TabBar'
import { BreadcrumbBar } from './BreadcrumbBar'
import { useEditorTheme } from '../hooks/useTheme'
import { splitFrontmatter, preProcessWikilinks, injectWikilinks, restoreWikilinksInBlocks, countWords } from '../utils/wikilinks'
import { resolveWikilinkColor as resolveColor } from '../utils/wikilinkColors'
import './Editor.css'
import './EditorTheme.css'
@@ -63,31 +64,8 @@ interface EditorProps {
// Module-level cache so the WikiLink renderer (defined outside React) can access entries
const _wikilinkEntriesRef: { current: VaultEntry[] } = { current: [] }
const TYPE_COLOR_MAP: Record<string, string> = {
red: 'var(--accent-red)',
orange: 'var(--accent-orange)',
yellow: 'var(--accent-yellow)',
green: 'var(--accent-green)',
blue: 'var(--accent-blue)',
purple: 'var(--accent-purple)',
}
function findEntryByTarget(entries: VaultEntry[], target: string): VaultEntry | undefined {
return entries.find(e => e.title === target || e.filename.replace(/\.md$/, '') === target)
}
function lookupColorForEntry(entries: VaultEntry[], entry: VaultEntry): string | undefined {
if (entry.isA === 'Type' && entry.color) return TYPE_COLOR_MAP[entry.color]
if (!entry.isA) return undefined
const typeEntry = entries.find(e => e.isA === 'Type' && e.title === entry.isA)
return typeEntry?.color ? TYPE_COLOR_MAP[typeEntry.color] : undefined
}
function resolveWikilinkColor(target: string): string | undefined {
const entries = _wikilinkEntriesRef.current
if (!entries.length) return undefined
const entry = findEntryByTarget(entries, target)
return entry ? lookupColorForEntry(entries, entry) : undefined
function resolveWikilinkColor(target: string) {
return resolveColor(_wikilinkEntriesRef.current, target)
}
const WikiLink = createReactInlineContentSpec(
@@ -101,12 +79,12 @@ const WikiLink = createReactInlineContentSpec(
{
render: (props) => {
const target = props.inlineContent.props.target
const color = resolveWikilinkColor(target)
const { color, isBroken } = resolveWikilinkColor(target)
return (
<span
className="wikilink"
className={`wikilink${isBroken ? ' wikilink--broken' : ''}`}
data-target={target}
style={color ? { color, textDecorationColor: color } : undefined}
style={{ color, textDecorationColor: color }}
>
{target}
</span>