Skip to content

Commit c87d7b4

Browse files
committed
implement #414
1 parent 47049fc commit c87d7b4

File tree

5 files changed

+80
-50
lines changed

5 files changed

+80
-50
lines changed

packages/core/src/config/ButtonConfig.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { LinePosition } from 'packages/core/src/config/APIConfigs';
2+
13
export enum ButtonStyleType {
24
DEFAULT = 'default',
35
PRIMARY = 'primary',
@@ -129,3 +131,9 @@ export interface ButtonConfig {
129131
action?: ButtonAction;
130132
actions?: ButtonAction[];
131133
}
134+
135+
export interface ButtonContext {
136+
position: LinePosition | undefined;
137+
isInGroup: boolean;
138+
isInline: boolean;
139+
}

packages/core/src/fields/button/ButtonActionRunner.ts

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { NotePosition } from 'packages/core/src/config/APIConfigs';
21
import type {
32
ButtonAction,
43
ButtonConfig,
4+
ButtonContext,
55
CommandButtonAction,
66
CreateNoteButtonAction,
77
InlineJsButtonAction,
@@ -66,18 +66,13 @@ export class ButtonActionRunner {
6666
* @param inline whether the button is inline
6767
* @param position the position of the button in the note
6868
*/
69-
async runButtonAction(
70-
config: ButtonConfig,
71-
filePath: string,
72-
inline: boolean,
73-
position: NotePosition | undefined,
74-
): Promise<void> {
69+
async runButtonActions(config: ButtonConfig, filePath: string, context: ButtonContext): Promise<void> {
7570
try {
7671
if (config.action) {
77-
await this.plugin.api.buttonActionRunner.runAction(config, config.action, filePath, inline, position);
72+
await this.plugin.api.buttonActionRunner.runAction(config, config.action, filePath, context);
7873
} else if (config.actions) {
7974
for (const action of config.actions) {
80-
await this.plugin.api.buttonActionRunner.runAction(config, action, filePath, inline, position);
75+
await this.plugin.api.buttonActionRunner.runAction(config, action, filePath, context);
8176
}
8277
} else {
8378
console.warn('meta-bind | ButtonMDRC >> no action defined');
@@ -178,14 +173,13 @@ export class ButtonActionRunner {
178173
config: ButtonConfig | undefined,
179174
action: ButtonAction,
180175
filePath: string,
181-
inline: boolean,
182-
position: NotePosition | undefined,
176+
buttonContext: ButtonContext,
183177
): Promise<void> {
184178
if (action.type === ButtonActionType.COMMAND) {
185179
await this.runCommandAction(action);
186180
return;
187181
} else if (action.type === ButtonActionType.JS) {
188-
await this.runJSAction(config, action, filePath);
182+
await this.runJSAction(config, action, filePath, buttonContext);
189183
return;
190184
} else if (action.type === ButtonActionType.OPEN) {
191185
await this.runOpenAction(action, filePath);
@@ -209,16 +203,16 @@ export class ButtonActionRunner {
209203
await this.runReplaceInNoteAction(action, filePath);
210204
return;
211205
} else if (action.type === ButtonActionType.REPLACE_SELF) {
212-
await this.runReplaceSelfAction(action, filePath, inline, position);
206+
await this.runReplaceSelfAction(action, filePath, buttonContext);
213207
return;
214208
} else if (action.type === ButtonActionType.REGEXP_REPLACE_IN_NOTE) {
215-
await this.runRegexpReplaceInNotAction(action, filePath);
209+
await this.runRegexpReplaceInNoteAction(action, filePath);
216210
return;
217211
} else if (action.type === ButtonActionType.INSERT_INTO_NOTE) {
218212
await this.runInsertIntoNoteAction(action, filePath);
219213
return;
220214
} else if (action.type === ButtonActionType.INLINE_JS) {
221-
await this.runInlineJsAction(config, action, filePath);
215+
await this.runInlineJsAction(config, action, filePath, buttonContext);
222216
return;
223217
}
224218

@@ -231,7 +225,12 @@ export class ButtonActionRunner {
231225
this.plugin.internal.executeCommandById(action.command);
232226
}
233227

234-
async runJSAction(config: ButtonConfig | undefined, action: JSButtonAction, filePath: string): Promise<void> {
228+
async runJSAction(
229+
config: ButtonConfig | undefined,
230+
action: JSButtonAction,
231+
filePath: string,
232+
buttonContext: ButtonContext,
233+
): Promise<void> {
235234
if (!this.plugin.settings.enableJs) {
236235
throw new MetaBindJsError({
237236
errorLevel: ErrorLevel.CRITICAL,
@@ -243,6 +242,7 @@ export class ButtonActionRunner {
243242
const configOverrides: Record<string, unknown> = {
244243
buttonConfig: structuredClone(config),
245244
args: structuredClone(action.args),
245+
buttonContext: structuredClone(buttonContext),
246246
};
247247
const unloadCallback = await this.plugin.internal.jsEngineRunFile(action.file, filePath, configOverrides);
248248
unloadCallback();
@@ -334,28 +334,25 @@ export class ButtonActionRunner {
334334
async runReplaceSelfAction(
335335
action: ReplaceSelfButtonAction,
336336
filePath: string,
337-
inline: boolean,
338-
position: NotePosition | undefined,
337+
buttonContext: ButtonContext,
339338
): Promise<void> {
340-
if (inline) {
339+
if (buttonContext.isInline) {
341340
throw new Error('Replace self action not supported for inline buttons');
342341
}
343342

344-
const linePosition = position?.getPosition();
345-
346-
if (linePosition === undefined) {
343+
if (buttonContext.position === undefined) {
347344
throw new Error('Position of the button in the note is unknown');
348345
}
349346

350-
if (linePosition.lineStart > linePosition.lineEnd) {
347+
if (buttonContext.position.lineStart > buttonContext.position.lineEnd) {
351348
throw new Error('Position of the button in the note is invalid');
352349
}
353350

354351
const content = await this.plugin.internal.readFilePath(filePath);
355352

356353
let splitContent = content.split('\n');
357354

358-
if (linePosition.lineStart < 0 || linePosition.lineEnd > splitContent.length + 1) {
355+
if (buttonContext.position.lineStart < 0 || buttonContext.position.lineEnd > splitContent.length + 1) {
359356
throw new Error('Position of the button in the note is out of bounds');
360357
}
361358

@@ -364,15 +361,15 @@ export class ButtonActionRunner {
364361
: action.replacement;
365362

366363
splitContent = [
367-
...splitContent.slice(0, linePosition.lineStart),
364+
...splitContent.slice(0, buttonContext.position.lineStart),
368365
replacement,
369-
...splitContent.slice(linePosition.lineEnd + 1),
366+
...splitContent.slice(buttonContext.position.lineEnd + 1),
370367
];
371368

372369
await this.plugin.internal.writeFilePath(filePath, splitContent.join('\n'));
373370
}
374371

375-
async runRegexpReplaceInNotAction(action: RegexpReplaceInNoteButtonAction, filePath: string): Promise<void> {
372+
async runRegexpReplaceInNoteAction(action: RegexpReplaceInNoteButtonAction, filePath: string): Promise<void> {
376373
if (action.regexp === '') {
377374
throw new Error('Regexp cannot be empty');
378375
}
@@ -410,6 +407,7 @@ export class ButtonActionRunner {
410407
config: ButtonConfig | undefined,
411408
action: InlineJsButtonAction,
412409
filePath: string,
410+
buttonContext: ButtonContext,
413411
): Promise<void> {
414412
if (!this.plugin.settings.enableJs) {
415413
throw new MetaBindJsError({
@@ -421,6 +419,7 @@ export class ButtonActionRunner {
421419

422420
const configOverrides: Record<string, unknown> = {
423421
buttonConfig: structuredClone(config),
422+
buttonContext: structuredClone(buttonContext),
424423
};
425424
const unloadCallback = await this.plugin.internal.jsEngineRunCode(action.code, filePath, configOverrides);
426425
unloadCallback();

packages/core/src/fields/button/ButtonField.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { NotePosition } from 'packages/core/src/config/APIConfigs';
22
import { RenderChildType } from 'packages/core/src/config/APIConfigs';
3-
import type { ButtonConfig } from 'packages/core/src/config/ButtonConfig';
3+
import type { ButtonConfig, ButtonContext } from 'packages/core/src/config/ButtonConfig';
44
import type { IPlugin } from 'packages/core/src/IPlugin';
55
import ButtonComponent from 'packages/core/src/utils/components/ButtonComponent.svelte';
66
import { Mountable } from 'packages/core/src/utils/Mountable';
@@ -12,7 +12,7 @@ export class ButtonField extends Mountable {
1212
plugin: IPlugin;
1313
config: ButtonConfig;
1414
filePath: string;
15-
inline: boolean;
15+
isInline: boolean;
1616
position: NotePosition | undefined;
1717
buttonComponent?: ReturnType<SvelteComponent>;
1818
isInGroup: boolean;
@@ -32,7 +32,7 @@ export class ButtonField extends Mountable {
3232
this.plugin = plugin;
3333
this.config = config;
3434
this.filePath = filePath;
35-
this.inline = renderChildType === RenderChildType.INLINE;
35+
this.isInline = renderChildType === RenderChildType.INLINE;
3636
this.position = position;
3737
this.isInGroup = isInGroup;
3838
this.isPreview = isPreview;
@@ -41,9 +41,9 @@ export class ButtonField extends Mountable {
4141
protected onMount(targetEl: HTMLElement): void {
4242
DomHelpers.empty(targetEl);
4343
DomHelpers.removeAllClasses(targetEl);
44-
DomHelpers.addClasses(targetEl, ['mb-button', this.inline ? 'mb-button-inline' : 'mb-button-block']);
44+
DomHelpers.addClasses(targetEl, ['mb-button', this.isInline ? 'mb-button-inline' : 'mb-button-block']);
4545

46-
if (!this.inline && !this.isPreview && !this.isInGroup) {
46+
if (!this.isInline && !this.isPreview && !this.isInGroup) {
4747
if (this.config.id) {
4848
this.plugin.api.buttonManager.addButton(this.filePath, this.config);
4949
}
@@ -68,23 +68,30 @@ export class ButtonField extends Mountable {
6868
label: this.config.label,
6969
tooltip: isTruthy(this.config.tooltip) ? this.config.tooltip : this.config.label,
7070
onclick: async (): Promise<void> => {
71-
await this.plugin.api.buttonActionRunner.runButtonAction(
71+
await this.plugin.api.buttonActionRunner.runButtonActions(
7272
this.config,
7373
this.filePath,
74-
this.inline,
75-
this.position,
74+
this.getContext(),
7675
);
7776
},
7877
},
7978
});
8079
}
8180

81+
public getContext(): ButtonContext {
82+
return {
83+
position: this.position?.getPosition(),
84+
isInGroup: this.isInGroup,
85+
isInline: this.isInline,
86+
};
87+
}
88+
8289
protected onUnmount(): void {
8390
if (this.buttonComponent) {
8491
unmount(this.buttonComponent);
8592
}
8693

87-
if (!this.inline && !this.isPreview) {
94+
if (!this.isInline && !this.isPreview) {
8895
if (this.config?.id) {
8996
this.plugin.api.buttonManager.removeButton(this.filePath, this.config.id);
9097
}

packages/core/src/utils/Utils.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,13 @@ export function isFalsy(value: unknown): boolean {
138138
return !value;
139139
}
140140

141-
export function deepFreeze<T extends object>(object: T): Readonly<T> {
141+
export function deepFreeze<T extends object>(object: T): Readonly<T>;
142+
export function deepFreeze<T extends object>(object: T | undefined): Readonly<T | undefined>;
143+
export function deepFreeze<T extends object>(object: T | undefined): Readonly<T | undefined> {
144+
if (object === undefined) {
145+
return object;
146+
}
147+
142148
// Retrieve the property names defined on object
143149
const propNames: (string | symbol)[] = Reflect.ownKeys(object);
144150

tests/fields/Button.test.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ let testPlugin: TestPlugin;
88
const testFilePath = 'test/file.md';
99

1010
async function simplifiedRunAction(action: ButtonAction): Promise<void> {
11-
await testPlugin.api.buttonActionRunner.runAction(undefined, action, testFilePath, false, undefined);
11+
await testPlugin.api.buttonActionRunner.runAction(undefined, action, testFilePath, {
12+
position: undefined,
13+
isInline: false,
14+
isInGroup: false,
15+
});
1216
}
1317

1418
const buttonActionTests: Record<ButtonActionType, () => void> = {
@@ -176,12 +180,15 @@ const buttonActionTests: Record<ButtonActionType, () => void> = {
176180
replacement: 'no button',
177181
},
178182
testFilePath,
179-
false,
180-
new NotePosition({
181-
// these line numbers start at 0
182-
lineStart: 1,
183-
lineEnd: 1,
184-
}),
183+
{
184+
position: {
185+
// these line numbers start at 0
186+
lineStart: 1,
187+
lineEnd: 1,
188+
},
189+
isInline: false,
190+
isInGroup: false,
191+
},
185192
);
186193

187194
expect(testPlugin.internal.fileSystem.readFile('test/file.md')).toBe('line1\nno button\nline3\n');
@@ -197,12 +204,15 @@ const buttonActionTests: Record<ButtonActionType, () => void> = {
197204
replacement: 'no button',
198205
},
199206
testFilePath,
200-
false,
201-
new NotePosition({
202-
// these line numbers start at 0
203-
lineStart: 1,
204-
lineEnd: 3,
205-
}),
207+
{
208+
position: {
209+
// these line numbers start at 0
210+
lineStart: 1,
211+
lineEnd: 3,
212+
},
213+
isInline: false,
214+
isInGroup: false,
215+
},
206216
);
207217

208218
expect(testPlugin.internal.fileSystem.readFile('test/file.md')).toBe('line1\nno button\nline3\n');

0 commit comments

Comments
 (0)