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
+