Skip to content

Commit d256ec6

Browse files
committed
more link view field stuff
1 parent 19d9caf commit d256ec6

File tree

5 files changed

+180
-20
lines changed

5 files changed

+180
-20
lines changed

exampleVault/Input Fields/List.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ list:
33
- apple
44
- banana
55
- berries
6-
- "[[file]]"
6+
- https://github.com/
77
list2:
88
- "[[Other/Example Notes/Example Note with Image.md|Example Note with Image]]"
99
- "[[Other/Example Notes/Example Note with Callouts.md|Example Note with Callouts]]"
@@ -24,6 +24,8 @@ list5:
2424
INPUT[list(showcase):list]
2525
```
2626

27+
`VIEW[{list}][link]`
28+
2729
```meta-bind
2830
INPUT[listSuggester(optionQuery(#example-note), showcase):list2]
2931
```

src/parsers/MarkdownLinkParser.ts

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,51 @@ import { type Parser } from '@lemons_dev/parsinom/lib/Parser';
44
import { runParser } from './ParsingError';
55
import { P_UTILS } from '@lemons_dev/parsinom/lib/ParserUtils';
66

7-
const mdLinkInnerParser = P.sequence(
7+
const mdWikiLinkInnerParser: Parser<[string, string | undefined, string | undefined]> = P.sequence(
88
filePath, // the file path
99
P.string('#').then(P.manyNotOf('[]#|^:')).optional(), // the optional heading
1010
P.string('|').then(P.manyNotOf('[]')).optional(), // the optional alias
1111
);
1212

13-
const mdLinkParser: Parser<MarkdownLink> = P.sequenceMap(
14-
(a, b) => {
15-
return {
16-
isEmbed: a !== undefined,
17-
target: b[0],
18-
block: b[1],
19-
alias: b[2],
20-
};
21-
},
22-
P.string('!').optional(),
23-
mdLinkInnerParser.wrapString('[[', ']]'),
13+
const mdLinkParser: Parser<MarkdownLink> = P.or(
14+
// wiki links
15+
P.sequenceMap(
16+
(a, b): MarkdownLink => {
17+
return {
18+
isEmbed: a !== undefined,
19+
target: b[0],
20+
block: b[1],
21+
alias: b[2],
22+
internal: true,
23+
};
24+
},
25+
P.string('!').optional(),
26+
mdWikiLinkInnerParser.wrapString('[[', ']]'),
27+
),
28+
// standard markdown links
29+
P.sequenceMap(
30+
(a, b, c): MarkdownLink => {
31+
let internal: boolean;
32+
// if it's a URL, it's external
33+
try {
34+
new URL(c);
35+
internal = false;
36+
} catch (_) {
37+
internal = true;
38+
}
39+
40+
return {
41+
isEmbed: a !== undefined,
42+
target: c,
43+
block: undefined,
44+
alias: b,
45+
internal: internal,
46+
};
47+
},
48+
P.string('!').optional(),
49+
P.manyNotOf('[]').wrapString('[', ']'),
50+
P.manyNotOf('()').wrapString('(', ')'),
51+
),
2452
);
2553

2654
const mdLinkListParser: Parser<MarkdownLink[]> = P.separateBy(mdLinkParser, P.string(',').trim(P_UTILS.optionalWhitespace()));
@@ -30,16 +58,17 @@ export interface MarkdownLink {
3058
target: string;
3159
block?: string;
3260
alias?: string;
61+
internal: boolean;
3362
}
3463

3564
export function parseMdLink(link: string): MarkdownLink {
36-
return runParser(mdLinkParser, link);
65+
return runParser(mdLinkParser.thenEof(), link);
3766
}
3867

3968
export function parseMdLinkList(link: string): MarkdownLink[] {
40-
return runParser(mdLinkListParser, link);
69+
return runParser(mdLinkListParser.thenEof(), link);
4170
}
4271

4372
export function isMdLink(str: string): boolean {
44-
return (str.startsWith('![[') || str.startsWith('[[')) && str.endsWith(']]');
73+
return mdLinkParser.thenEof().tryParse(str).success;
4574
}

src/utils/LinkComponent.svelte

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
export let mdLink: MarkdownLink;
55
66
$: linkHref = mdLink.block ? `${mdLink.target}#${mdLink.block}` : mdLink.target;
7+
$: cssClass = mdLink.internal ? 'internal-link' : 'external-link';
78
</script>
89

910
{#if mdLink.alias}
10-
<a data-href={linkHref} href={linkHref} class="internal-link" target="_blank" rel="noopener" aria-label={linkHref}>
11+
<a data-href={linkHref} href={linkHref} class={cssClass} target="_blank" rel="noopener" aria-label={linkHref}>
1112
{mdLink.alias}
1213
</a>
1314
{:else}
14-
<a data-href={linkHref} href={linkHref} class="internal-link" target="_blank" rel="noopener">
15+
<a data-href={linkHref} href={linkHref} class={cssClass} target="_blank" rel="noopener">
1516
{linkHref}
1617
</a>
1718
{/if}

src/viewFields/fields/LinkVF.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,15 @@ export class LinkVF extends AbstractViewField {
8181
const variable = variables[0];
8282
const content = variable.inputSignal.get();
8383

84+
// we want the return value to be a human-readable string, since someone could save this to the frontmatter
8485
if (typeof content === 'string') {
8586
return this.convertToLink(content);
8687
} else if (Array.isArray(content)) {
87-
return (content.filter(x => typeof x === 'string') as string[]).map(x => this.convertToLink(x)).join(', ');
88+
let strings = content.filter(x => typeof x === 'string') as string[];
89+
return strings
90+
.map(x => this.convertToLink(x))
91+
.filter(x => x !== '')
92+
.join(', ');
8893
} else {
8994
return '';
9095
}
@@ -93,8 +98,21 @@ export class LinkVF extends AbstractViewField {
9398
convertToLink(str: string): string {
9499
if (isMdLink(str)) {
95100
return str;
96-
} else {
101+
} else if (isMdLink(`[[${str}]]`)) {
97102
return `[[${str}]]`;
103+
} else if (this.getUrl(str)) {
104+
const url = this.getUrl(str) as URL;
105+
return `[${url.hostname}](${str})`;
106+
} else {
107+
return '';
108+
}
109+
}
110+
111+
getUrl(str: string): URL | undefined {
112+
try {
113+
return new URL(str);
114+
} catch (_) {
115+
return undefined;
98116
}
99117
}
100118

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { describe, test, expect, beforeEach, spyOn, Mock } from 'bun:test';
2+
import { parseMdLink, parseMdLinkList } from '../../src/parsers/MarkdownLinkParser';
3+
4+
describe('markdown link parser', () => {
5+
describe('parse markdown link', () => {
6+
// --- wiki links ---
7+
8+
test('should parse wiki link', () => {
9+
expect(parseMdLink('[[test]]')).toEqual({
10+
isEmbed: false,
11+
target: 'test',
12+
block: undefined,
13+
alias: undefined,
14+
internal: true,
15+
});
16+
});
17+
18+
test('should parse wiki link embed', () => {
19+
expect(parseMdLink('![[test]]')).toEqual({
20+
isEmbed: true,
21+
target: 'test',
22+
block: undefined,
23+
alias: undefined,
24+
internal: true,
25+
});
26+
});
27+
28+
test('should parse wiki with block', () => {
29+
expect(parseMdLink('[[test#123]]')).toEqual({
30+
isEmbed: false,
31+
target: 'test',
32+
block: '123',
33+
alias: undefined,
34+
internal: true,
35+
});
36+
});
37+
38+
test('should parse wiki with alias', () => {
39+
expect(parseMdLink('[[test|something]]')).toEqual({
40+
isEmbed: false,
41+
target: 'test',
42+
block: undefined,
43+
alias: 'something',
44+
internal: true,
45+
});
46+
});
47+
48+
test('should parse wiki with block and alias', () => {
49+
expect(parseMdLink('[[test#123|something]]')).toEqual({
50+
isEmbed: false,
51+
target: 'test',
52+
block: '123',
53+
alias: 'something',
54+
internal: true,
55+
});
56+
});
57+
58+
// --- markdown links ---
59+
60+
test('should parse markdown link', () => {
61+
expect(parseMdLink('[something](test)')).toEqual({
62+
isEmbed: false,
63+
target: 'test',
64+
block: undefined,
65+
alias: 'something',
66+
internal: true,
67+
});
68+
});
69+
70+
test('should parse markdown link embed', () => {
71+
expect(parseMdLink('![something](test)')).toEqual({
72+
isEmbed: true,
73+
target: 'test',
74+
block: undefined,
75+
alias: 'something',
76+
internal: true,
77+
});
78+
});
79+
80+
test('should parse external markdown link', () => {
81+
expect(parseMdLink('[github](https://github.com)')).toEqual({
82+
isEmbed: false,
83+
target: 'https://github.com',
84+
block: undefined,
85+
alias: 'github',
86+
internal: false,
87+
});
88+
});
89+
90+
// --- errors ---
91+
92+
test('should fail on non markdown link', () => {
93+
expect(() => parseMdLink('something else')).toThrow();
94+
});
95+
});
96+
97+
describe('parse markdown link list', () => {
98+
test('should parse link list', () => {
99+
expect(() => parseMdLinkList('[[test]], [github](https://github.com), [[test#123|something]]')).not.toThrow();
100+
});
101+
102+
test('should fail on non markdown link in list', () => {
103+
expect(() => parseMdLinkList('[[test]], [github](https://github.com), something else')).toThrow();
104+
});
105+
106+
test('should fail on missing comma', () => {
107+
expect(() => parseMdLinkList('[[test]] [github](https://github.com), [[test#123|something]]')).toThrow();
108+
});
109+
});
110+
});

0 commit comments

Comments
 (0)