Skip to content

Commit 417fe82

Browse files
committed
implement #455
1 parent 34c91e7 commit 417fe82

File tree

11 files changed

+180
-102
lines changed

11 files changed

+180
-102
lines changed

exampleVault/Input Fields/List.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ list5:
2020
- "[[Other/Example Notes/Example Note with Embeds.md|Example Note with Embeds]]"
2121
- "[[Other/Example Notes/Example Note with Callouts.md|Example Note with Callouts]]"
2222
list6:
23-
- as
23+
- ""
2424
---
2525

2626
### List

exampleVault/View Fields/View Field.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Self Loop Error: `VIEW[**{computed}**][text():computed]`
5858

5959
`INPUT[suggester(optionQuery(#example-note), useLinks(false)):file]`
6060
link with render markdown: `VIEW[\[\[{file}|link\]\]][text(renderMarkdown)]`
61-
link with link view field: `VIEW[{file}][link]`
61+
link with link view field: `VIEW[{file}|this is a link][link]`
6262

6363
```meta-bind
6464
INPUT[imageSuggester(optionQuery("Other/Images")):image]

packages/core/src/fields/viewFields/AbstractViewField.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ import type {
66
ComputedMetadataSubscription,
77
ComputedSubscriptionDependency,
88
} from 'packages/core/src/metadata/ComputedMetadataSubscription';
9-
import { stringifyUnknown } from 'packages/core/src/utils/Literal';
109
import { Mountable } from 'packages/core/src/utils/Mountable';
1110
import { Signal } from 'packages/core/src/utils/Signal';
1211
import { DomHelpers } from 'packages/core/src/utils/Utils';
1312

14-
export abstract class AbstractViewField extends Mountable {
13+
export abstract class AbstractViewField<T> extends Mountable {
1514
readonly plugin: IPlugin;
1615
readonly mountable: ViewFieldMountable;
17-
readonly inputSignal: Signal<unknown>;
16+
readonly inputSignal: Signal<T | undefined>;
1817

1918
private metadataSubscription?: ComputedMetadataSubscription;
2019

@@ -28,7 +27,7 @@ export abstract class AbstractViewField extends Mountable {
2827

2928
this.mountable = mountable;
3029
this.plugin = mountable.plugin;
31-
this.inputSignal = new Signal<unknown>(undefined);
30+
this.inputSignal = new Signal<T | undefined>(undefined);
3231

3332
this.variables = [];
3433

@@ -37,8 +36,7 @@ export abstract class AbstractViewField extends Mountable {
3736

3837
protected abstract buildVariables(): void;
3938

40-
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
41-
protected abstract computeValue(): unknown | Promise<unknown>;
39+
protected abstract computeValue(): T | Promise<T>;
4240

4341
private async initialRender(targetEl: HTMLElement): Promise<void> {
4442
DomHelpers.addClass(targetEl, 'mb-view-text');
@@ -51,20 +49,19 @@ export abstract class AbstractViewField extends Mountable {
5149

5250
await this.onInitialRender(targetEl);
5351

54-
await this.rerender(targetEl, '');
52+
await this.rerender(targetEl, undefined);
5553
}
5654

5755
protected abstract onInitialRender(container: HTMLElement): void | Promise<void>;
5856

59-
private async rerender(targetEl: HTMLElement, value: unknown): Promise<void> {
57+
private async rerender(targetEl: HTMLElement, value: T | undefined): Promise<void> {
6058
if (!this.hidden) {
61-
const text = stringifyUnknown(value, this.mountable.plugin.settings.viewFieldDisplayNullAsEmpty) ?? '';
6259
DomHelpers.empty(targetEl);
63-
await this.onRerender(targetEl, text);
60+
await this.onRerender(targetEl, value);
6461
}
6562
}
6663

67-
protected abstract onRerender(targetEl: HTMLElement, text: string): void | Promise<void>;
64+
protected abstract onRerender(targetEl: HTMLElement, value: T | undefined): void | Promise<void>;
6865

6966
protected onMount(targetEl: HTMLElement): void {
7067
this.buildVariables();

packages/core/src/fields/viewFields/ViewFieldFactory.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { ViewFieldType } from 'packages/core/src/config/FieldConfigs';
2-
import type { AbstractViewField } from 'packages/core/src/fields/viewFields/AbstractViewField';
32
import { ImageVF } from 'packages/core/src/fields/viewFields/fields/ImageVF';
43
import { LinkVF } from 'packages/core/src/fields/viewFields/fields/LinkVF';
54
import { MathVF } from 'packages/core/src/fields/viewFields/fields/MathVF';
@@ -8,14 +7,16 @@ import type { ViewFieldMountable } from 'packages/core/src/fields/viewFields/Vie
87
import type { IPlugin } from 'packages/core/src/IPlugin';
98
import { expectType } from 'packages/core/src/utils/Utils';
109

10+
export type ViewField = MathVF | TextVF | LinkVF | ImageVF;
11+
1112
export class ViewFieldFactory {
1213
plugin: IPlugin;
1314

1415
constructor(plugin: IPlugin) {
1516
this.plugin = plugin;
1617
}
1718

18-
createViewField(mountable: ViewFieldMountable): AbstractViewField | undefined {
19+
createViewField(mountable: ViewFieldMountable): ViewField | undefined {
1920
// Skipped: Date, Time, Image Suggester
2021

2122
const type = mountable.declaration.viewFieldType;

packages/core/src/fields/viewFields/ViewFieldMountable.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { RenderChildType } from 'packages/core/src/config/APIConfigs';
22
import { ViewFieldArgumentType } from 'packages/core/src/config/FieldConfigs';
33
import type { ViewFieldArgumentMapType } from 'packages/core/src/fields/fieldArguments/viewFieldArguments/ViewFieldArgumentFactory';
44
import { FieldMountable } from 'packages/core/src/fields/FieldMountable';
5-
import type { AbstractViewField } from 'packages/core/src/fields/viewFields/AbstractViewField';
5+
import type { ViewField } from 'packages/core/src/fields/viewFields/ViewFieldFactory';
66
import type { IPlugin } from 'packages/core/src/IPlugin';
77
import type { ViewFieldDeclaration } from 'packages/core/src/parsers/viewFieldParser/ViewFieldDeclaration';
88
import { ErrorCollection } from 'packages/core/src/utils/errors/ErrorCollection';
@@ -13,7 +13,7 @@ export class ViewFieldMountable extends FieldMountable {
1313
renderChildType: RenderChildType;
1414
errorCollection: ErrorCollection;
1515

16-
viewField: AbstractViewField | undefined;
16+
viewField: ViewField | undefined;
1717
declarationString: string | undefined;
1818
declaration: ViewFieldDeclaration;
1919

packages/core/src/fields/viewFields/fields/ImageVF.ts

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
import { AbstractViewField } from 'packages/core/src/fields/viewFields/AbstractViewField';
22
import type { ViewFieldMountable } from 'packages/core/src/fields/viewFields/ViewFieldMountable';
3+
import type { ViewFieldVariable } from 'packages/core/src/fields/viewFields/ViewFieldVariable';
34
import type { BindTargetDeclaration } from 'packages/core/src/parsers/bindTargetParser/BindTargetDeclaration';
45
import { MDLinkParser } from 'packages/core/src/parsers/MarkdownLinkParser';
56
import ImageGrid from 'packages/core/src/utils/components/ImageGrid.svelte';
6-
import {
7-
ErrorLevel,
8-
MetaBindExpressionError,
9-
MetaBindValidationError,
10-
} from 'packages/core/src/utils/errors/MetaBindErrors';
7+
import { ErrorLevel, MetaBindValidationError } from 'packages/core/src/utils/errors/MetaBindErrors';
118
import { Signal } from 'packages/core/src/utils/Signal';
129
import { getUUID } from 'packages/core/src/utils/Utils';
1310
import type { Component as SvelteComponent } from 'svelte';
1411
import { mount, unmount } from 'svelte';
1512

16-
export class ImageVF extends AbstractViewField {
13+
export class ImageVF extends AbstractViewField<string> {
1714
component?: ReturnType<SvelteComponent>;
15+
linkVariable?: ViewFieldVariable;
1816

1917
constructor(mountable: ViewFieldMountable) {
2018
super(mountable);
@@ -34,44 +32,35 @@ export class ImageVF extends AbstractViewField {
3432
});
3533
}
3634

37-
const firstEntry = entries[0];
38-
if (typeof firstEntry === 'string') {
35+
const linkEntry = entries[0];
36+
if (typeof linkEntry === 'string') {
3937
throw new MetaBindValidationError({
4038
errorLevel: ErrorLevel.ERROR,
4139
effect: 'can not create view field',
4240
cause: 'image view filed only supports exactly a single bind target and not text content',
4341
});
4442
}
4543

46-
firstEntry.listenToChildren = true;
44+
linkEntry.listenToChildren = true;
4745

48-
this.variables = [
49-
{
50-
bindTargetDeclaration: firstEntry,
51-
inputSignal: new Signal<unknown>(undefined),
52-
uuid: getUUID(),
53-
contextName: `MB_VAR_0`,
54-
},
55-
];
46+
this.linkVariable = {
47+
bindTargetDeclaration: linkEntry,
48+
inputSignal: new Signal<unknown>(undefined),
49+
uuid: getUUID(),
50+
contextName: `MB_VAR_0`,
51+
};
52+
53+
this.variables.push(this.linkVariable);
5654
}
5755

5856
protected computeValue(): string {
59-
if (this.variables.length !== 1) {
60-
throw new MetaBindExpressionError({
61-
errorLevel: ErrorLevel.CRITICAL,
62-
effect: 'failed to evaluate image view field',
63-
cause: 'there should be exactly one variable',
64-
});
65-
}
66-
67-
const variable = this.variables[0];
68-
const content = variable.inputSignal.get();
57+
const linkContent = this.linkVariable!.inputSignal.get();
6958

7059
// we want the return value to be a human-readable string, since someone could save this to the frontmatter
71-
if (typeof content === 'string') {
72-
return MDLinkParser.toLinkString(content);
73-
} else if (Array.isArray(content)) {
74-
const strings = content.filter(x => typeof x === 'string');
60+
if (typeof linkContent === 'string') {
61+
return MDLinkParser.toLinkString(linkContent);
62+
} else if (Array.isArray(linkContent)) {
63+
const strings = linkContent.filter(x => typeof x === 'string');
7564
return strings
7665
.map(x => MDLinkParser.toLinkString(x))
7766
.filter(x => x !== '')
@@ -91,8 +80,8 @@ export class ImageVF extends AbstractViewField {
9180
});
9281
}
9382

94-
protected async onRerender(container: HTMLElement, text: string): Promise<void> {
95-
const linkList = MDLinkParser.parseLinkList(text);
83+
protected async onRerender(container: HTMLElement, value: string | undefined): Promise<void> {
84+
const linkList = value ? MDLinkParser.parseLinkList(value) : [];
9685
if (this.component) {
9786
unmount(this.component);
9887
}

packages/core/src/fields/viewFields/fields/LinkVF.ts

Lines changed: 91 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import { AbstractViewField } from 'packages/core/src/fields/viewFields/AbstractViewField';
22
import type { ViewFieldMountable } from 'packages/core/src/fields/viewFields/ViewFieldMountable';
3+
import type { ViewFieldVariable } from 'packages/core/src/fields/viewFields/ViewFieldVariable';
34
import type { BindTargetDeclaration } from 'packages/core/src/parsers/bindTargetParser/BindTargetDeclaration';
45
import { MDLinkParser } from 'packages/core/src/parsers/MarkdownLinkParser';
56
import LinkListComponent from 'packages/core/src/utils/components/LinkListComponent.svelte';
6-
import {
7-
ErrorLevel,
8-
MetaBindExpressionError,
9-
MetaBindValidationError,
10-
} from 'packages/core/src/utils/errors/MetaBindErrors';
7+
import { ErrorLevel, MetaBindValidationError } from 'packages/core/src/utils/errors/MetaBindErrors';
8+
import { stringifyUnknown } from 'packages/core/src/utils/Literal';
119
import { Signal } from 'packages/core/src/utils/Signal';
1210
import { getUUID } from 'packages/core/src/utils/Utils';
1311
import type { Component as SvelteComponent } from 'svelte';
1412
import { mount, unmount } from 'svelte';
1513

16-
export class LinkVF extends AbstractViewField {
14+
export class LinkVF extends AbstractViewField<string> {
1715
component?: ReturnType<SvelteComponent>;
16+
linkVariable?: ViewFieldVariable;
17+
aliasVariable?: ViewFieldVariable | string;
1818

1919
constructor(mountable: ViewFieldMountable) {
2020
super(mountable);
@@ -26,52 +26,106 @@ export class LinkVF extends AbstractViewField {
2626
.getDeclaration()
2727
.templateDeclaration.filter(x => (typeof x === 'string' ? x : true));
2828

29-
if (entries.length !== 1) {
29+
if (entries.length !== 1 && entries.length !== 2 && entries.length !== 3) {
3030
throw new MetaBindValidationError({
3131
errorLevel: ErrorLevel.ERROR,
3232
effect: 'can not create view field',
33-
cause: 'link view filed only supports exactly a single bind target and not text content',
33+
cause: 'link view field must be of form "{bindTarget}" or "{bindTarget}|{bindTarget}"',
3434
});
3535
}
3636

37-
const firstEntry = entries[0];
38-
if (typeof firstEntry === 'string') {
39-
throw new MetaBindValidationError({
40-
errorLevel: ErrorLevel.ERROR,
41-
effect: 'can not create view field',
42-
cause: 'link view filed only supports exactly a single bind target and not text content',
43-
});
44-
}
37+
const linkEntry = entries[0];
38+
const separatorEntry = entries[1];
39+
const linkTextEntry = entries[2];
40+
41+
this.variables = [];
42+
43+
if (entries.length === 1) {
44+
if (typeof linkEntry === 'string') {
45+
throw new MetaBindValidationError({
46+
errorLevel: ErrorLevel.ERROR,
47+
effect: 'can not create view field',
48+
cause: 'link view field must be of form "{bindTarget}" or "{bindTarget}|{bindTarget}"',
49+
});
50+
}
4551

46-
firstEntry.listenToChildren = true;
52+
linkEntry.listenToChildren = true;
4753

48-
this.variables = [
49-
{
50-
bindTargetDeclaration: firstEntry,
54+
this.linkVariable = {
55+
bindTargetDeclaration: linkEntry,
5156
inputSignal: new Signal<unknown>(undefined),
5257
uuid: getUUID(),
5358
contextName: `MB_VAR_0`,
54-
},
55-
];
59+
};
60+
61+
this.variables.push(this.linkVariable);
62+
} else if (entries.length === 2 || entries.length === 3) {
63+
if (typeof linkEntry === 'string' || typeof separatorEntry !== 'string') {
64+
throw new MetaBindValidationError({
65+
errorLevel: ErrorLevel.ERROR,
66+
effect: 'can not create view field',
67+
cause: 'link view field must be of form "{bindTarget}", "{bindTarget}|alias", or "{bindTarget}|{bindTarget}"',
68+
});
69+
}
70+
71+
linkEntry.listenToChildren = true;
72+
73+
this.linkVariable = {
74+
bindTargetDeclaration: linkEntry,
75+
inputSignal: new Signal<unknown>(undefined),
76+
uuid: getUUID(),
77+
contextName: `MB_VAR_0`,
78+
};
79+
80+
this.variables.push(this.linkVariable);
81+
82+
if (entries.length === 2) {
83+
this.aliasVariable = separatorEntry.slice(1);
84+
} else {
85+
if (typeof linkTextEntry === 'string') {
86+
this.aliasVariable = linkTextEntry;
87+
} else {
88+
linkTextEntry.listenToChildren = true;
89+
90+
this.aliasVariable = {
91+
bindTargetDeclaration: linkTextEntry,
92+
inputSignal: new Signal<unknown>(undefined),
93+
uuid: getUUID(),
94+
contextName: `MB_VAR_1`,
95+
};
96+
97+
this.variables.push(this.aliasVariable);
98+
}
99+
}
100+
} else {
101+
throw new Error('unreachable');
102+
}
56103
}
57104

58-
protected computeValue(): string {
59-
if (this.variables.length !== 1) {
60-
throw new MetaBindExpressionError({
61-
errorLevel: ErrorLevel.CRITICAL,
62-
effect: 'failed to evaluate link view field',
63-
cause: 'there should be exactly one variable',
64-
});
105+
private getAlias(): string | undefined {
106+
if (!this.aliasVariable) {
107+
return undefined;
108+
}
109+
110+
if (typeof this.aliasVariable === 'string') {
111+
return this.aliasVariable;
112+
} else {
113+
return stringifyUnknown(
114+
this.aliasVariable.inputSignal.get(),
115+
this.mountable.plugin.settings.viewFieldDisplayNullAsEmpty,
116+
);
65117
}
118+
}
66119

67-
const variable = this.variables[0];
68-
const content = variable.inputSignal.get();
120+
protected computeValue(): string {
121+
const linkContent = this.linkVariable!.inputSignal.get();
122+
const alias = this.getAlias();
69123

70124
// we want the return value to be a human-readable string, since someone could save this to the frontmatter
71-
if (typeof content === 'string') {
72-
return MDLinkParser.toLinkString(content);
73-
} else if (Array.isArray(content)) {
74-
const strings = content.filter(x => typeof x === 'string');
125+
if (typeof linkContent === 'string') {
126+
return MDLinkParser.toLinkString(linkContent, alias);
127+
} else if (Array.isArray(linkContent)) {
128+
const strings = linkContent.filter(x => typeof x === 'string');
75129
return strings
76130
.map(x => MDLinkParser.toLinkString(x))
77131
.filter(x => x !== '')
@@ -90,8 +144,8 @@ export class LinkVF extends AbstractViewField {
90144
});
91145
}
92146

93-
protected async onRerender(container: HTMLElement, text: string): Promise<void> {
94-
const linkList = MDLinkParser.parseLinkList(text);
147+
protected async onRerender(container: HTMLElement, value: string | undefined): Promise<void> {
148+
const linkList = value ? MDLinkParser.parseLinkList(value) : [];
95149
this.component = mount(LinkListComponent, {
96150
target: container,
97151
props: {

0 commit comments

Comments
 (0)