diff --git a/src/editors/automerge/livePreview/wikiLink.test.ts b/src/editors/automerge/livePreview/wikiLink.test.ts index 1e4ab59..d4a74b3 100644 --- a/src/editors/automerge/livePreview/wikiLink.test.ts +++ b/src/editors/automerge/livePreview/wikiLink.test.ts @@ -174,6 +174,34 @@ describe('buildWikiLinkDecorations', () => { ]); }); + it('leaves the `#` visible, so the name and heading stay separated', () => { + // Hiding it along with the brackets rendered `[[Foo#Head]]` as `FooHead`. + const state = makeState('x [[Foo#Head]]', 0, vaultOf(['Foo'])); + const hidden = atomic(state, buildWikiLinkDecorations(state)).map( + (d) => d.text, + ); + expect(hidden).toEqual(['[[', ']]']); + }); + + it('hides the `#` when an alias replaces the whole link part', () => { + const state = makeState('x [[Foo#Head|Alias]]', 0, vaultOf(['Foo'])); + const hidden = atomic(state, buildWikiLinkDecorations(state)).map( + (d) => d.text, + ); + expect(hidden).toContain('#'); + expect( + withClass(decos(state, buildWikiLinkDecorations(state)), 'cm-wikilink'), + ).toMatchObject([{ text: 'Alias' }]); + }); + + it('shows `#Heading` for a same-document reference', () => { + const state = makeState('x [[#Head]]', 0, vaultOf([])); + const r = buildWikiLinkDecorations(state); + expect(withClass(decos(state, r), 'cm-wikilink')).toMatchObject([ + { text: '#Head' }, + ]); + }); + it('reveals the raw source when the cursor is inside', () => { const doc = 'see [[Foo]] here'; const state = makeState(doc, doc.indexOf('Foo'), vaultOf(['Foo'])); diff --git a/src/editors/automerge/livePreview/wikiLinkDecoration.ts b/src/editors/automerge/livePreview/wikiLinkDecoration.ts index 36d9b91..828059d 100644 --- a/src/editors/automerge/livePreview/wikiLinkDecoration.ts +++ b/src/editors/automerge/livePreview/wikiLinkDecoration.ts @@ -119,12 +119,18 @@ export function buildWikiLinkDecorations( vault.resolve(targetText)?.exists !== true; const mark = unresolved ? wikiLinkUnresolvedMark : wikiLinkMark; - // Display text: the alias when present, else target + `#` + anchor so - // `[[Note#Head]]` reads as `Note#Head`. + // Display text: the alias when present, otherwise the target, the `#` + // and the anchor together, so `[[Note#Head]]` reads as `Note#Head`. + // The `#` is deliberately part of it — without a separator the two run + // together as `NoteHead`. + const hashMark = anchor + ? (marks.find((m) => state.doc.sliceString(m.from, m.to) === '#') ?? + null) + : null; if (alias) { out.push(alias.from, alias.to, mark); } else { - const start = target?.from ?? anchor?.from ?? null; + const start = target?.from ?? hashMark?.from ?? anchor?.from ?? null; const end = anchor?.to ?? target?.to ?? null; if (start != null && end != null && end > start) { out.push(start, end, mark); @@ -136,6 +142,10 @@ export function buildWikiLinkDecorations( } for (const m of marks) { + // Keep the `#` — it is the visible separator between the note name + // and the heading. With an alias the whole link part is hidden + // anyway, so it goes with the rest. + if (!alias && m === hashMark) continue; out.push(m.from, m.to, inlineFormattingHiddenMark, true); } // With an alias, the target it stands in for is hidden too. (The `#`