From a92ea7d86e2ea24edc0cd5985b52688b129383c0 Mon Sep 17 00:00:00 2001 From: Riccardo Date: Sun, 28 Feb 2021 19:49:07 +0100 Subject: [PATCH] Improved wikilink definition resolution (fixes #499) (#502) * improved resolution for direct links and wikilink with definition * if the definition of a wikilink points to a non-existing file, create a placeholder for the full path instead * if a link doesn't point to a valid resource, create a placeholder for the full path instead * commented out test-data.js import in dataviz.html * moved test to more appropriate group --- packages/foam-core/src/model/workspace.ts | 9 +- packages/foam-core/test/workspace.test.ts | 165 +++++++++++++++++----- packages/foam-vscode/static/dataviz.html | 2 +- 3 files changed, 134 insertions(+), 42 deletions(-) diff --git a/packages/foam-core/src/model/workspace.ts b/packages/foam-core/src/model/workspace.ts index 2eb6646bd..596e2ad85 100644 --- a/packages/foam-core/src/model/workspace.ts +++ b/packages/foam-core/src/model/workspace.ts @@ -129,14 +129,17 @@ export class FoamWorkspace implements IDisposable { note: Note, link: NoteLink ): URI { - let targetUri: URI | null = null; + let targetUri: URI | undefined; switch (link.type) { case 'wikilink': const definitionUri = note.definitions.find( def => def.label === link.slug )?.url; if (isSome(definitionUri)) { - targetUri = parseUri(note.uri, definitionUri!); + const definedUri = parseUri(note.uri, definitionUri); + targetUri = + FoamWorkspace.find(workspace, definedUri, note.uri)?.uri ?? + placeholderUri(definedUri.path); } else { targetUri = FoamWorkspace.find(workspace, link.slug, note.uri)?.uri ?? @@ -147,7 +150,7 @@ export class FoamWorkspace implements IDisposable { case 'link': targetUri = FoamWorkspace.find(workspace, link.target, note.uri)?.uri ?? - placeholderUri(link.target); + placeholderUri(parseUri(note.uri, link.target).path); break; } diff --git a/packages/foam-core/test/workspace.test.ts b/packages/foam-core/test/workspace.test.ts index c487747ec..0d6d1183d 100644 --- a/packages/foam-core/test/workspace.test.ts +++ b/packages/foam-core/test/workspace.test.ts @@ -53,7 +53,22 @@ describe('Notes workspace', () => { ).toEqual(['/file.pdf', '/page-a.md', 'place-holder']); }); - it('Detects outbound wikilinks', () => { + it('Fails if getting non-existing note', () => { + const noteA = createTestNote({ + uri: '/path/to/page-a.md', + }); + const ws = new FoamWorkspace(); + ws.set(noteA); + + const uri = URI.file('/path/to/another/page-b.md'); + expect(ws.exists(uri)).toBeFalsy(); + expect(ws.find(uri)).toBeNull(); + expect(() => ws.get(uri)).toThrow(); + }); +}); + +describe('Wikilinks', () => { + it('Can be defined with basename, relative path, absolute path, extension', () => { const noteA = createTestNote({ uri: '/path/to/page-a.md', links: [ @@ -91,7 +106,7 @@ describe('Notes workspace', () => { ]); }); - it('Detects inbound wikilinks', () => { + it('Creates inbound connections for target note', () => { const noteA = createTestNote({ uri: '/path/to/page-a.md', links: [{ slug: 'page-b' }], @@ -126,38 +141,27 @@ describe('Notes workspace', () => { ).toEqual(['/path/another/page-c.md', '/somewhere/page-b.md']); }); - it('Detects markdown links', () => { + it('Uses wikilink definitions when available to resolve target', () => { + const ws = new FoamWorkspace(); const noteA = createTestNote({ - uri: '/path/to/page-a.md', - links: [{ to: './another/page-b.md' }, { to: 'more/page-c.md' }], + uri: '/somewhere/from/page-a.md', + links: [{ slug: 'page-b' }], }); - const noteB = createTestNote({ - uri: '/path/to/another/page-b.md', - links: [{ to: '../../to/page-a.md' }], + noteA.definitions.push({ + label: 'page-b', + url: '../to/page-b.md', }); - const noteC = createTestNote({ - uri: '/path/to/more/page-c.md', + const noteB = createTestNote({ + uri: '/somewhere/to/page-b.md', }); - const ws = new FoamWorkspace(); ws.set(noteA) .set(noteB) - .set(noteC) .resolveLinks(); - expect( - ws - .getLinks(noteA.uri) - .map(link => link.path) - .sort() - ).toEqual(['/path/to/another/page-b.md', '/path/to/more/page-c.md']); - - expect(ws.getLinks(noteB.uri)).toEqual([noteA.uri]); - expect(ws.getBacklinks(noteA.uri)).toEqual([noteB.uri]); - expect(ws.getConnections(noteA.uri)).toEqual([ - { source: noteA.uri, target: noteB.uri }, - { source: noteA.uri, target: noteC.uri }, - { source: noteB.uri, target: noteA.uri }, - ]); + expect(ws.getAllConnections()[0]).toEqual({ + source: noteA.uri, + target: noteB.uri, + }); }); it('Resolves wikilink referencing more than one note', () => { @@ -196,18 +200,6 @@ describe('Notes workspace', () => { expect(ws.getLinks(noteA.uri)).toEqual([noteB2.uri, noteB3.uri]); }); - it('Fails if getting non-existing note', () => { - const noteA = createTestNote({ - uri: '/path/to/page-a.md', - }); - const ws = new FoamWorkspace(); - ws.set(noteA); - - const uri = URI.file('/path/to/another/page-b.md'); - expect(ws.exists(uri)).toBeFalsy(); - expect(ws.find(uri)).toBeNull(); - expect(() => ws.get(uri)).toThrow(); - }); it('Supports attachments', () => { const noteA = createTestNote({ uri: '/path/to/page-a.md', @@ -275,6 +267,103 @@ describe('Notes workspace', () => { }); }); +describe('markdown direct links', () => { + it('Support absolute and relative path', () => { + const noteA = createTestNote({ + uri: '/path/to/page-a.md', + links: [{ to: './another/page-b.md' }, { to: 'more/page-c.md' }], + }); + const noteB = createTestNote({ + uri: '/path/to/another/page-b.md', + links: [{ to: '../../to/page-a.md' }], + }); + const noteC = createTestNote({ + uri: '/path/to/more/page-c.md', + }); + const ws = new FoamWorkspace(); + ws.set(noteA) + .set(noteB) + .set(noteC) + .resolveLinks(); + + expect( + ws + .getLinks(noteA.uri) + .map(link => link.path) + .sort() + ).toEqual(['/path/to/another/page-b.md', '/path/to/more/page-c.md']); + + expect(ws.getLinks(noteB.uri)).toEqual([noteA.uri]); + expect(ws.getBacklinks(noteA.uri)).toEqual([noteB.uri]); + expect(ws.getConnections(noteA.uri)).toEqual([ + { source: noteA.uri, target: noteB.uri }, + { source: noteA.uri, target: noteC.uri }, + { source: noteB.uri, target: noteA.uri }, + ]); + }); +}); + +describe('Placeholders', () => { + it('Treats direct links to non-existing files as placeholders', () => { + const ws = new FoamWorkspace(); + const noteA = createTestNote({ + uri: '/somewhere/from/page-a.md', + links: [{ to: '../page-b.md' }, { to: '/path/to/page-c.md' }], + }); + ws.set(noteA).resolveLinks(); + + expect(ws.getAllConnections()[0]).toEqual({ + source: noteA.uri, + target: placeholderUri('/somewhere/page-b.md'), + }); + expect(ws.getAllConnections()[1]).toEqual({ + source: noteA.uri, + target: placeholderUri('/path/to/page-c.md'), + }); + }); + + it('Treats wikilinks without matching file as placeholders', () => { + const ws = new FoamWorkspace(); + const noteA = createTestNote({ + uri: '/somewhere/page-a.md', + links: [{ slug: 'page-b' }], + }); + ws.set(noteA).resolveLinks(); + + expect(ws.getAllConnections()[0]).toEqual({ + source: noteA.uri, + target: placeholderUri('page-b'), + }); + }); + it('Treats wikilink with definition to non-existing file as placeholders', () => { + const ws = new FoamWorkspace(); + const noteA = createTestNote({ + uri: '/somewhere/page-a.md', + links: [{ slug: 'page-b' }, { slug: 'page-c' }], + }); + noteA.definitions.push({ + label: 'page-b', + url: './page-b.md', + }); + noteA.definitions.push({ + label: 'page-c', + url: '/path/to/page-c.md', + }); + ws.set(noteA) + .set(createTestNote({ uri: '/different/location/for/note-b.md' })) + .resolveLinks(); + + expect(ws.getAllConnections()[0]).toEqual({ + source: noteA.uri, + target: placeholderUri('/somewhere/page-b.md'), + }); + expect(ws.getAllConnections()[1]).toEqual({ + source: noteA.uri, + target: placeholderUri('/path/to/page-c.md'), + }); + }); +}); + describe('Updating workspace happy path', () => { it('Update links when modifying note', () => { const noteA = createTestNote({ diff --git a/packages/foam-vscode/static/dataviz.html b/packages/foam-vscode/static/dataviz.html index 7e1b41cae..de4569c99 100644 --- a/packages/foam-vscode/static/dataviz.html +++ b/packages/foam-vscode/static/dataviz.html @@ -21,7 +21,7 @@ 3. uncomment the next +