Skip to content

Commit f2ee980

Browse files
committed
fix some things
1 parent 9d0aa86 commit f2ee980

File tree

9 files changed

+129
-17
lines changed

9 files changed

+129
-17
lines changed

exampleVault/Advanced Examples/Using JS Engine for Complex things.md

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
---
2-
text: abcasdasd
2+
text: abcasdas
33
locked: true
4+
from_year: a2323
5+
to_year: 23423dss
46
---
57

68
Locked: `INPUT[toggle:locked]`
@@ -39,12 +41,10 @@ const reactive = engine.reactive(render, mb.getMetadata(bindTarget));
3941
// then we subscribe to the metadata that the bind target points to and rerender the reactive component everythime the bind target value changes
4042
const subscription = mb.subscribeToMetadata(
4143
bindTarget,
44+
component,
4245
(value) => reactive.refresh(value)
4346
);
4447
45-
// don't forget to unregister the subscription when this code block unloads
46-
component.register(() => subscription.unsubscribe());
47-
4848
return reactive;
4949
```
5050

@@ -61,10 +61,47 @@ const reactive = engine.reactive(onUpdate, mb.getMetadata(bindTarget));
6161
6262
const subscription = mb.subscribeToMetadata(
6363
bindTarget,
64+
component,
6465
(value) => reactive.refresh(value)
6566
);
6667
67-
component.register(() => subscription.unsubscribe());
68-
6968
return reactive;
7069
```
70+
71+
`INPUT[text:from_year]`
72+
`INPUT[text:to_year]`
73+
74+
```js-engine
75+
// Grab metabind API and extract metadata fields
76+
const mb = engine.getPlugin('obsidian-meta-bind-plugin').api;
77+
const mbFrom = mb.parseBindTarget('from_year', context.file.path);
78+
const mbTo = mb.parseBindTarget('to_year', context.file.path);
79+
80+
const headers = ["Date", "File"];
81+
82+
function onUpdate(uFrom, fTo) {
83+
return [uFrom, fTo];
84+
}
85+
86+
const reactive = engine.reactive(
87+
// update function
88+
onUpdate,
89+
// arguments
90+
mb.getMetadata(mbFrom),
91+
mb.getMetadata(mbTo)
92+
);
93+
94+
const subscriptionFrom = mb.subscribeToMetadata(
95+
mbFrom,
96+
component,
97+
(newFrom) => {console.log(newFrom); reactive.refresh(newFrom, mb.getMetadata(mbTo))}
98+
);
99+
100+
const subscriptionTo = mb.subscribeToMetadata(
101+
mbTo,
102+
component,
103+
(newTo) => {console.log(newTo); reactive.refresh(mb.getMetadata(mbFrom), newTo)}
104+
);
105+
106+
return reactive;
107+
```

packages/core/src/api/API.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ import { JsViewFieldParser } from 'packages/core/src/parsers/viewFieldParser/JsV
4646
import { Signal } from 'packages/core/src/utils/Signal';
4747
import { parsePropPath } from 'packages/core/src/utils/prop/PropParser';
4848
import { type BindTargetDeclaration } from 'packages/core/src/parsers/bindTargetParser/BindTargetDeclaration';
49-
import { type MetadataSubscription } from 'packages/core/src/metadata/MetadataSubscription';
5049
import {
5150
V_BindTargetDeclaration,
5251
V_BindTargetScope,
@@ -63,6 +62,10 @@ import {
6362
import { validate } from 'packages/core/src/utils/ZodUtils';
6463
import { z } from 'zod';
6564

65+
export interface LifecycleHook {
66+
register(cb: () => void): void;
67+
}
68+
6669
export interface APIFieldOverrides {
6770
inputFieldParser?: InputFieldParser;
6871
viewFieldParser?: ViewFieldParser;
@@ -680,19 +683,23 @@ export abstract class API<Plugin extends IPlugin> {
680683
* IF YOU DON'T CALL `unsubscribe` THE SUBSCRIPTION WILL LEAK MEMORY.
681684
*
682685
* @param bindTarget
686+
* @param lifecycleHook In Obsidian this is an instance of the Component class. The subscription will be automatically unsubscribed when the component is unloaded.
683687
* @param callback
684688
*/
685689
public subscribeToMetadata(
686690
bindTarget: BindTargetDeclaration,
691+
lifecycleHook: LifecycleHook,
687692
callback: (value: unknown) => void,
688-
): MetadataSubscription {
693+
): void {
689694
validate(
690695
z.object({
691696
bindTarget: V_BindTargetDeclaration,
697+
lifecycleHook: this.plugin.internal.getLifecycleHookValidator(),
692698
callback: z.function().args(z.any()).returns(z.void()),
693699
}),
694700
{
695701
bindTarget: bindTarget,
702+
lifecycleHook: lifecycleHook,
696703
callback: callback,
697704
},
698705
);
@@ -704,8 +711,12 @@ export abstract class API<Plugin extends IPlugin> {
704711
callback: callback,
705712
});
706713

707-
return this.plugin.metadataManager.subscribe(uuid, signal, bindTarget, (): void => {
714+
const subscription = this.plugin.metadataManager.subscribe(uuid, signal, bindTarget, (): void => {
708715
signal.unregisterAllListeners();
709716
});
717+
718+
lifecycleHook.register(() => {
719+
subscription.unsubscribe();
720+
});
710721
}
711722
}

packages/core/src/api/InternalAPI.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import { SuggesterSelectModal } from 'packages/core/src/modals/selectModalConten
2828
import { type IFuzzySearch } from 'packages/core/src/utils/IFuzzySearch';
2929
import { type ContextMenuItemDefinition, type IContextMenu } from 'packages/core/src/utils/IContextMenu';
3030
import TextPromptModalContent from 'packages/core/src/modals/modalContents/TextPromptModalContent.svelte';
31+
import { type z } from 'zod';
32+
import { type LifecycleHook } from 'packages/core/src/api/API';
3133

3234
export interface ErrorIndicatorProps {
3335
errorCollection: ErrorCollection;
@@ -62,6 +64,9 @@ export abstract class InternalAPI<Plugin extends IPlugin> {
6264
this.plugin = plugin;
6365
}
6466

67+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
68+
abstract getLifecycleHookValidator(): z.ZodType<LifecycleHook, any, any>;
69+
6570
/**
6671
* Get the options for the image suggester input field.
6772
*

packages/core/src/metadata/ComputedMetadataSubscription.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { type MetadataSubscription } from 'packages/core/src/metadata/MetadataSu
55
import { type BindTargetDeclaration } from 'packages/core/src/parsers/bindTargetParser/BindTargetDeclaration';
66
import { type Signal } from 'packages/core/src/utils/Signal';
77
import { getUUID } from 'packages/core/src/utils/Utils';
8+
import { ErrorLevel, MetaBindInternalError } from 'packages/core/src/utils/errors/MetaBindErrors';
89

910
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
1011
export type ComputeFunction = (values: unknown[]) => Promise<unknown> | unknown;
@@ -73,11 +74,23 @@ export class ComputedMetadataSubscription implements IMetadataSubscription {
7374
}
7475

7576
private async computeValue(): Promise<void> {
76-
const values = this.dependencySubscriptions.map(x => x.callbackSignal.get());
77-
const value = await this.computeFunction(values);
78-
this.callbackSignal.set(value);
79-
if (this.bindTarget !== undefined) {
80-
this.metadataManager.write(value, this.bindTarget, this.uuid);
77+
try {
78+
const values = this.dependencySubscriptions.map(x => x.callbackSignal.get());
79+
const value = await this.computeFunction(values);
80+
this.callbackSignal.set(value);
81+
if (this.bindTarget !== undefined) {
82+
this.metadataManager.write(value, this.bindTarget, this.uuid);
83+
}
84+
} catch (e) {
85+
const error = e instanceof Error ? e : String(e);
86+
87+
console.warn(
88+
new MetaBindInternalError({
89+
errorLevel: ErrorLevel.ERROR,
90+
effect: 'Failed to compute value of computed subscription',
91+
cause: error,
92+
}),
93+
);
8194
}
8295
}
8396

packages/core/src/metadata/MetadataSubscription.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { type MetadataManager } from 'packages/core/src/metadata/MetadataManager
33
import { type Signal } from 'packages/core/src/utils/Signal';
44
import { type ComputedSubscriptionDependency } from 'packages/core/src/metadata/ComputedMetadataSubscription';
55
import { type BindTargetDeclaration } from 'packages/core/src/parsers/bindTargetParser/BindTargetDeclaration';
6+
import { ErrorLevel, MetaBindInternalError } from 'packages/core/src/utils/errors/MetaBindErrors';
67

78
export class MetadataSubscription implements IMetadataSubscription {
89
readonly uuid: string;
@@ -55,7 +56,19 @@ export class MetadataSubscription implements IMetadataSubscription {
5556
* @param value
5657
*/
5758
public notify(value: unknown): void {
58-
this.callbackSignal.set(value);
59+
try {
60+
this.callbackSignal.set(value);
61+
} catch (e) {
62+
const error = e instanceof Error ? e : String(e);
63+
64+
console.warn(
65+
new MetaBindInternalError({
66+
errorLevel: ErrorLevel.ERROR,
67+
effect: 'Failed to notify subscription of updated value in the cache',
68+
cause: error,
69+
}),
70+
);
71+
}
5972
}
6073

6174
public getDependencies(): ComputedSubscriptionDependency[] {

packages/core/src/utils/Signal.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getUUID } from 'packages/core/src/utils/Utils';
2+
import { ErrorLevel, MetaBindInternalError } from 'packages/core/src/utils/errors/MetaBindErrors';
23

34
export interface NotifierInterface<T, TListener extends Listener<T>> {
45
registerListener(listener: Omit<TListener, 'uuid'>): TListener;
@@ -41,7 +42,19 @@ export class Notifier<T, TListener extends Listener<T>> implements NotifierInter
4142
public notifyListeners(value: T): void {
4243
for (const listener of this.listeners) {
4344
// console.debug('meta-bind | calling listener callback', value);
44-
listener.callback(value);
45+
try {
46+
listener.callback(value);
47+
} catch (e) {
48+
const error = e instanceof Error ? e : String(e);
49+
50+
console.error(
51+
new MetaBindInternalError({
52+
errorLevel: ErrorLevel.ERROR,
53+
effect: 'error while calling listener callback',
54+
cause: error,
55+
}),
56+
);
57+
}
4558
}
4659
}
4760
}

packages/obsidian/src/ObsidianInternalAPI.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import { type IFuzzySearch } from 'packages/core/src/utils/IFuzzySearch';
3131
import { FuzzySearch } from 'packages/obsidian/src/FuzzySearch';
3232
import { type ContextMenuItemDefinition, type IContextMenu } from 'packages/core/src/utils/IContextMenu';
3333
import { ObsidianContextMenu } from 'packages/obsidian/src/ObsidianContextMenu';
34+
import { z, type ZodType } from 'zod';
35+
import { type LifecycleHook } from 'packages/core/src/api/API';
3436

3537
export class ObsidianInternalAPI extends InternalAPI<MetaBindPlugin> {
3638
readonly app: App;
@@ -41,6 +43,11 @@ export class ObsidianInternalAPI extends InternalAPI<MetaBindPlugin> {
4143
this.app = plugin.app;
4244
}
4345

46+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
47+
public getLifecycleHookValidator(): ZodType<LifecycleHook, any, any> {
48+
return z.instanceof(Component);
49+
}
50+
4451
public getImageSuggesterOptions(inputField: ImageSuggesterLikeIPF): SuggesterOption<string>[] {
4552
return getImageSuggesterOptionsForInputField(this.plugin, inputField);
4653
}

packages/publish/src/PublishInternalAPI.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,17 @@ import { type ModalContent } from 'packages/core/src/modals/ModalContent';
1212
import { type IModal } from 'packages/core/src/modals/IModal';
1313
import { type SelectModalContent } from 'packages/core/src/modals/SelectModalContent';
1414
import { type ContextMenuItemDefinition, type IContextMenu } from 'packages/core/src/utils/IContextMenu';
15-
import { Notice, parseYaml, stringifyYaml, setIcon } from 'obsidian/publish';
15+
import { Notice, parseYaml, stringifyYaml, setIcon, Component } from 'obsidian/publish';
16+
import { z, type ZodType } from 'zod';
17+
import { type LifecycleHook } from 'packages/core/src/api/API';
1618

1719
// TODO: implement
1820
export class PublishInternalAPI extends InternalAPI<MetaBindPublishPlugin> {
21+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
22+
public getLifecycleHookValidator(): ZodType<LifecycleHook, any, any> {
23+
return z.instanceof(Component);
24+
}
25+
1926
public async renderMarkdown(markdown: string, element: HTMLElement, _filePath: string): Promise<() => void> {
2027
element.innerText += markdown;
2128
return () => {};

tests/__mocks__/TestInternalAPI.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { SelectModalContent } from 'packages/core/src/modals/SelectModalContent'
1414
import { ContextMenuItemDefinition, IContextMenu } from 'packages/core/src/utils/IContextMenu';
1515
import { TestFileSystem } from 'tests/__mocks__/TestFileSystem';
1616
import YAML from 'yaml';
17+
import { z, ZodType } from 'zod';
18+
import { LifecycleHook } from 'packages/core/src/api/API';
1719

1820
export class TestInternalAPI extends InternalAPI<TestPlugin> {
1921
fileSystem: TestFileSystem;
@@ -24,6 +26,10 @@ export class TestInternalAPI extends InternalAPI<TestPlugin> {
2426
this.fileSystem = new TestFileSystem();
2527
}
2628

29+
public getLifecycleHookValidator(): ZodType<LifecycleHook, any, any> {
30+
return z.object({ register: z.function().returns(z.void()) });
31+
}
32+
2733
public async renderMarkdown(markdown: string, element: HTMLElement, _filePath: string): Promise<() => void> {
2834
element.innerText += markdown;
2935
return () => {};

0 commit comments

Comments
 (0)