Skip to content

Commit 7571d87

Browse files
committed
new feature: buttons
1 parent 8785834 commit 7571d87

23 files changed

+480
-66
lines changed

bun.lockb

335 Bytes
Binary file not shown.

exampleVault/Button Example.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Meta Bind is getting Buttons
2+
3+
```meta-bind-button
4+
style: primary
5+
label: Open Meta Bind FAQ
6+
action:
7+
type: command
8+
command: obsidian-meta-bind-plugin:mb-open-faq
9+
```
10+
11+
And custom JS buttons as well
12+
13+
```meta-bind-button
14+
style: default
15+
label: Run Custom JS
16+
action:
17+
type: js
18+
jsFile: testJsFile.js
19+
```
20+

exampleVault/testJsFile.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
console.log(`Hello World from ${context.file.path}!`);
2+
throw new Error('This is an error');

extraTypes/JsEngine.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// js engine 0.0.7 types
1+
// js engine 0.0.8 types
22

33
declare module 'jsEngine/ArgumentManager' {
44
import { type CachedMetadata, type TFile } from 'obsidian';
@@ -484,8 +484,9 @@ declare module 'jsEngine/api/Internal' {
484484
export class InternalAPI {
485485
private readonly apiInstance;
486486
constructor(apiInstance: API);
487-
excute(params: EngineExecutionParams): Promise<JsExecution>;
487+
execute(params: EngineExecutionParams): Promise<JsExecution>;
488488
createRenderer(container: HTMLElement, sourcePath: string, component: Component): ResultRenderer;
489+
executeFile(path: string, params: Omit<EngineExecutionParams, 'code'>): Promise<JsExecution>;
489490
}
490491
}
491492
declare module 'jsEngine/api/API' {

extraTypes/obsidian-ex.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ declare module 'obsidian' {
88
plugins: Record<string, Plugin>;
99
getPlugin: (plugin: string) => Plugin;
1010
};
11+
12+
commands: {
13+
executeCommandById: (id: string) => boolean;
14+
};
1115
}
1216
}
1317

package.json

Lines changed: 60 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,62 @@
11
{
2-
"name": "obsidian-meta-bind-plugin",
3-
"version": "0.8.0",
4-
"description": "This plugin can create input fields inside your notes and bind them to metadata fields.",
5-
"main": "main.js",
6-
"scripts": {
7-
"dev": "bun run esbuild.dev.config.mjs",
8-
"build": "tsc -noEmit -skipLibCheck && bun run esbuild.config.mjs production",
9-
"dev-publish": "bun run esbuild.publish.config.mjs",
10-
"build-publish": "tsc -noEmit -skipLibCheck && node esbuild.publish.config.mjs production",
11-
"tsc": "tsc -noEmit -skipLibCheck",
12-
"test": "bun test",
13-
"test:log": "LOG_TESTS=true bun test",
14-
"format": "prettier --write --plugin prettier-plugin-svelte .",
15-
"format:check": "prettier --check --plugin prettier-plugin-svelte .",
16-
"lint": "eslint --max-warnings=0 src/**",
17-
"lint:fix": "eslint --max-warnings=0 --fix src/**",
18-
"types": "tsc -p \"./tsconfig.types.json\"",
19-
"check": "bun run format:check && bun run lint && bun run tsc && bun run test",
20-
"check:fix": "bun run format && bun run lint:fix && bun run tsc && bun run test",
21-
"release": "bun run automation/release.ts"
22-
},
23-
"keywords": [],
24-
"author": "Moritz Jung",
25-
"license": "GPL-3.0",
26-
"devDependencies": {
27-
"@codemirror/lang-javascript": "^6.2.1",
28-
"@codemirror/language": "^6.9.2",
29-
"@codemirror/state": "^6.3.1",
30-
"@codemirror/view": "^6.22.0",
31-
"@happy-dom/global-registrator": "^12.10.3",
32-
"@tsconfig/svelte": "^5.0.0",
33-
"@types/ungap__structured-clone": "^0.3.0",
34-
"@types/web": "^0.0.119",
35-
"@typescript-eslint/eslint-plugin": "^6.0.0",
36-
"@typescript-eslint/parser": "^6.0.0",
37-
"builtin-modules": "^3.3.0",
38-
"bun-types": "1.0.11",
39-
"esbuild": "^0.19.5",
40-
"esbuild-plugin-copy-watch": "^2.0.0",
41-
"esbuild-svelte": "^0.8.0",
42-
"eslint": "^8.52.0",
43-
"eslint-plugin-import": "^2.28.1",
44-
"eslint-plugin-isaacscript": "^3.5.6",
45-
"eslint-plugin-only-warn": "^1.1.0",
46-
"obsidian": "latest",
47-
"obsidian-dataview": "0.5.56",
48-
"prettier": "^3.0.3",
49-
"prettier-plugin-svelte": "^3.0.3",
50-
"string-argv": "^0.3.2",
51-
"svelte": "^4.2.0",
52-
"svelte-preprocess": "^5.0.4",
53-
"tslib": "2.6.2",
54-
"typescript": "^5.2.2"
55-
},
56-
"dependencies": {
57-
"@lemons_dev/parsinom": "^0.0.11",
58-
"itertools-ts": "^1.27.0",
59-
"mathjs": "^12.0.0"
60-
}
2+
"name": "obsidian-meta-bind-plugin",
3+
"version": "0.8.0",
4+
"description": "This plugin can create input fields inside your notes and bind them to metadata fields.",
5+
"main": "main.js",
6+
"scripts": {
7+
"dev": "bun run esbuild.dev.config.mjs",
8+
"build": "tsc -noEmit -skipLibCheck && bun run esbuild.config.mjs production",
9+
"dev-publish": "bun run esbuild.publish.config.mjs",
10+
"build-publish": "tsc -noEmit -skipLibCheck && node esbuild.publish.config.mjs production",
11+
"tsc": "tsc -noEmit -skipLibCheck",
12+
"test": "bun test",
13+
"test:log": "LOG_TESTS=true bun test",
14+
"format": "prettier --write --plugin prettier-plugin-svelte .",
15+
"format:check": "prettier --check --plugin prettier-plugin-svelte .",
16+
"lint": "eslint --max-warnings=0 src/**",
17+
"lint:fix": "eslint --max-warnings=0 --fix src/**",
18+
"types": "tsc -p \"./tsconfig.types.json\"",
19+
"check": "bun run format:check && bun run lint && bun run tsc && bun run test",
20+
"check:fix": "bun run format && bun run lint:fix && bun run tsc && bun run test",
21+
"release": "bun run automation/release.ts"
22+
},
23+
"keywords": [],
24+
"author": "Moritz Jung",
25+
"license": "GPL-3.0",
26+
"devDependencies": {
27+
"@codemirror/lang-javascript": "^6.2.1",
28+
"@codemirror/language": "^6.9.2",
29+
"@codemirror/state": "^6.3.1",
30+
"@codemirror/view": "^6.22.0",
31+
"@happy-dom/global-registrator": "^12.10.3",
32+
"@tsconfig/svelte": "^5.0.0",
33+
"@types/ungap__structured-clone": "^0.3.0",
34+
"@types/web": "^0.0.119",
35+
"@typescript-eslint/eslint-plugin": "^6.0.0",
36+
"@typescript-eslint/parser": "^6.0.0",
37+
"builtin-modules": "^3.3.0",
38+
"bun-types": "1.0.11",
39+
"esbuild": "^0.19.5",
40+
"esbuild-plugin-copy-watch": "^2.0.0",
41+
"esbuild-svelte": "^0.8.0",
42+
"eslint": "^8.52.0",
43+
"eslint-plugin-import": "^2.28.1",
44+
"eslint-plugin-isaacscript": "^3.5.6",
45+
"eslint-plugin-only-warn": "^1.1.0",
46+
"obsidian": "latest",
47+
"obsidian-dataview": "0.5.56",
48+
"prettier": "^3.0.3",
49+
"prettier-plugin-svelte": "^3.0.3",
50+
"string-argv": "^0.3.2",
51+
"svelte": "^4.2.0",
52+
"svelte-preprocess": "^5.0.4",
53+
"tslib": "2.6.2",
54+
"typescript": "^5.2.2"
55+
},
56+
"dependencies": {
57+
"@lemons_dev/parsinom": "^0.0.11",
58+
"itertools-ts": "^1.27.0",
59+
"mathjs": "^12.0.0",
60+
"zod": "^3.22.4"
61+
}
6162
}

src/api/API.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { ViewFieldFactory } from '../fields/viewFields/ViewFieldFactory';
2727
import { getUUID } from '../utils/Utils';
2828
import { parsePropPath } from '../utils/prop/PropParser';
2929
import { RenderChildType } from '../config/FieldConfigs';
30+
import { ButtonActionRunner } from '../button/ButtonActionRunner';
3031

3132
export class API implements IAPI {
3233
public plugin: MetaBindPlugin;
@@ -39,6 +40,8 @@ export class API implements IAPI {
3940
public readonly inputFieldFactory: InputFieldFactory;
4041
public readonly viewFieldFactory: ViewFieldFactory;
4142

43+
public readonly buttonActionRunner: ButtonActionRunner;
44+
4245
constructor(plugin: MetaBindPlugin) {
4346
this.plugin = plugin;
4447
this.inputField = new InputFieldAPI(this);
@@ -50,6 +53,8 @@ export class API implements IAPI {
5053

5154
this.inputFieldFactory = new InputFieldFactory(this.plugin);
5255
this.viewFieldFactory = new ViewFieldFactory(this.plugin);
56+
57+
this.buttonActionRunner = new ButtonActionRunner(this.plugin);
5358
}
5459

5560
/**

src/api/IAPI.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { type BindTargetParser } from '../parsers/BindTargetParser';
44
import { type IPlugin } from '../IPlugin';
55
import { type InputFieldAPI } from './InputFieldAPI';
66
import { type InputFieldFactory } from '../fields/inputFields/InputFieldFactory';
7+
import { type ButtonActionRunner } from '../button/ButtonActionRunner';
78

89
export interface IAPI {
910
readonly plugin: IPlugin;
@@ -26,4 +27,6 @@ export interface IAPI {
2627
readonly bindTargetParser: BindTargetParser;
2728

2829
readonly inputFieldFactory: InputFieldFactory;
30+
31+
readonly buttonActionRunner: ButtonActionRunner;
2932
}

src/button/ButtonActionRunner.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { type IPlugin } from '../IPlugin';
2+
import {
3+
type ButtonAction,
4+
ButtonActionType,
5+
type CommandButtonAction,
6+
type JSButtonAction,
7+
} from '../config/ButtonConfig';
8+
9+
export class ButtonActionRunner {
10+
plugin: IPlugin;
11+
12+
constructor(plugin: IPlugin) {
13+
this.plugin = plugin;
14+
}
15+
16+
async runAction(action: ButtonAction, filePath: string): Promise<void> {
17+
if (action.type === ButtonActionType.COMMAND) {
18+
await this.runCommandAction(action);
19+
} else if (action.type === ButtonActionType.JS) {
20+
await this.runJSAction(action, filePath);
21+
}
22+
}
23+
24+
async runCommandAction(action: CommandButtonAction): Promise<void> {
25+
this.plugin.internal.executeCommandById(action.command);
26+
}
27+
28+
async runJSAction(action: JSButtonAction, filePath: string): Promise<void> {
29+
const unloadCallback = await this.plugin.internal.jsEngineRunFile(action.jsFile, filePath);
30+
unloadCallback();
31+
}
32+
}

src/config/ButtonConfig.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { type RefinementCtx, z } from 'zod';
2+
3+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4+
function schemaForType<T>(): <S extends z.ZodType<T, any, any>>(arg: S) => S {
5+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6+
return function <S extends z.ZodType<T, any, any>>(arg: S): S {
7+
return arg;
8+
};
9+
}
10+
11+
export enum ButtonStyleType {
12+
DEFUALT = 'default',
13+
PRIMARY = 'primary',
14+
DESTRUCTIVE = 'destructive',
15+
PLAIN = 'plain',
16+
}
17+
18+
export enum ButtonActionType {
19+
COMMAND = 'command',
20+
JS = 'js',
21+
}
22+
23+
export interface CommandButtonAction {
24+
type: ButtonActionType.COMMAND;
25+
command: string;
26+
}
27+
28+
export const CommandButtonActionValidator = schemaForType<CommandButtonAction>()(
29+
z.object({
30+
type: z.literal(ButtonActionType.COMMAND),
31+
command: z.string(),
32+
}),
33+
);
34+
35+
export interface JSButtonAction {
36+
type: ButtonActionType.JS;
37+
jsFile: string;
38+
}
39+
40+
export const JSButtonActionValidator = schemaForType<JSButtonAction>()(
41+
z.object({
42+
type: z.literal(ButtonActionType.JS),
43+
jsFile: z.string(),
44+
}),
45+
);
46+
47+
export type ButtonAction = CommandButtonAction | JSButtonAction;
48+
49+
export const ButtonActionValidator = schemaForType<ButtonAction>()(
50+
z.union([CommandButtonActionValidator, JSButtonActionValidator]),
51+
);
52+
53+
export interface ButtonConfig {
54+
label: string;
55+
style: ButtonStyleType;
56+
action?: ButtonAction;
57+
actions?: ButtonAction[];
58+
}
59+
60+
type Tuple<T> = [T, ...T[]];
61+
export const ButtonStyleValidator = z.enum(Object.values(ButtonStyleType) as Tuple<ButtonStyleType>);
62+
63+
function oneOf<
64+
A,
65+
K1 extends Extract<keyof A, string>,
66+
K2 extends Extract<keyof A, string>,
67+
R extends A &
68+
((Required<Pick<A, K1>> & { [P in K2]: undefined }) | (Required<Pick<A, K2>> & { [P in K1]: undefined })),
69+
>(key1: K1, key2: K2): (arg: A, ctx: RefinementCtx) => arg is R {
70+
return (arg, ctx): arg is R => {
71+
if ((arg[key1] === undefined) === (arg[key2] === undefined)) {
72+
ctx.addIssue({
73+
code: z.ZodIssueCode.custom,
74+
message: `Either ${key1} or ${key2} must be filled, but not both`,
75+
});
76+
return false;
77+
}
78+
return true;
79+
};
80+
}
81+
82+
export const ButtonConfigValidator = schemaForType<ButtonConfig>()(
83+
z
84+
.object({
85+
label: z.string(),
86+
style: ButtonStyleValidator,
87+
action: ButtonActionValidator.optional(),
88+
actions: ButtonActionValidator.array().optional(),
89+
})
90+
.superRefine(oneOf('action', 'actions')),
91+
);

0 commit comments

Comments
 (0)