Skip to content

Commit

Permalink
implement #414
Browse files Browse the repository at this point in the history
  • Loading branch information
mProjectsCode committed Sep 21, 2024
1 parent 47049fc commit c87d7b4
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 50 deletions.
8 changes: 8 additions & 0 deletions packages/core/src/config/ButtonConfig.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { LinePosition } from 'packages/core/src/config/APIConfigs';

export enum ButtonStyleType {
DEFAULT = 'default',
PRIMARY = 'primary',
Expand Down Expand Up @@ -129,3 +131,9 @@ export interface ButtonConfig {
action?: ButtonAction;
actions?: ButtonAction[];
}

export interface ButtonContext {
position: LinePosition | undefined;
isInGroup: boolean;
isInline: boolean;
}
53 changes: 26 additions & 27 deletions packages/core/src/fields/button/ButtonActionRunner.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NotePosition } from 'packages/core/src/config/APIConfigs';
import type {
ButtonAction,
ButtonConfig,
ButtonContext,
CommandButtonAction,
CreateNoteButtonAction,
InlineJsButtonAction,
Expand Down Expand Up @@ -66,18 +66,13 @@ export class ButtonActionRunner {
* @param inline whether the button is inline
* @param position the position of the button in the note
*/
async runButtonAction(
config: ButtonConfig,
filePath: string,
inline: boolean,
position: NotePosition | undefined,
): Promise<void> {
async runButtonActions(config: ButtonConfig, filePath: string, context: ButtonContext): Promise<void> {
try {
if (config.action) {
await this.plugin.api.buttonActionRunner.runAction(config, config.action, filePath, inline, position);
await this.plugin.api.buttonActionRunner.runAction(config, config.action, filePath, context);
} else if (config.actions) {
for (const action of config.actions) {
await this.plugin.api.buttonActionRunner.runAction(config, action, filePath, inline, position);
await this.plugin.api.buttonActionRunner.runAction(config, action, filePath, context);
}
} else {
console.warn('meta-bind | ButtonMDRC >> no action defined');
Expand Down Expand Up @@ -178,14 +173,13 @@ export class ButtonActionRunner {
config: ButtonConfig | undefined,
action: ButtonAction,
filePath: string,
inline: boolean,
position: NotePosition | undefined,
buttonContext: ButtonContext,
): Promise<void> {
if (action.type === ButtonActionType.COMMAND) {
await this.runCommandAction(action);
return;
} else if (action.type === ButtonActionType.JS) {
await this.runJSAction(config, action, filePath);
await this.runJSAction(config, action, filePath, buttonContext);
return;
} else if (action.type === ButtonActionType.OPEN) {
await this.runOpenAction(action, filePath);
Expand All @@ -209,16 +203,16 @@ export class ButtonActionRunner {
await this.runReplaceInNoteAction(action, filePath);
return;
} else if (action.type === ButtonActionType.REPLACE_SELF) {
await this.runReplaceSelfAction(action, filePath, inline, position);
await this.runReplaceSelfAction(action, filePath, buttonContext);
return;
} else if (action.type === ButtonActionType.REGEXP_REPLACE_IN_NOTE) {
await this.runRegexpReplaceInNotAction(action, filePath);
await this.runRegexpReplaceInNoteAction(action, filePath);
return;
} else if (action.type === ButtonActionType.INSERT_INTO_NOTE) {
await this.runInsertIntoNoteAction(action, filePath);
return;
} else if (action.type === ButtonActionType.INLINE_JS) {
await this.runInlineJsAction(config, action, filePath);
await this.runInlineJsAction(config, action, filePath, buttonContext);
return;
}

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

async runJSAction(config: ButtonConfig | undefined, action: JSButtonAction, filePath: string): Promise<void> {
async runJSAction(
config: ButtonConfig | undefined,
action: JSButtonAction,
filePath: string,
buttonContext: ButtonContext,
): Promise<void> {
if (!this.plugin.settings.enableJs) {
throw new MetaBindJsError({
errorLevel: ErrorLevel.CRITICAL,
Expand All @@ -243,6 +242,7 @@ export class ButtonActionRunner {
const configOverrides: Record<string, unknown> = {
buttonConfig: structuredClone(config),
args: structuredClone(action.args),
buttonContext: structuredClone(buttonContext),
};
const unloadCallback = await this.plugin.internal.jsEngineRunFile(action.file, filePath, configOverrides);
unloadCallback();
Expand Down Expand Up @@ -334,28 +334,25 @@ export class ButtonActionRunner {
async runReplaceSelfAction(
action: ReplaceSelfButtonAction,
filePath: string,
inline: boolean,
position: NotePosition | undefined,
buttonContext: ButtonContext,
): Promise<void> {
if (inline) {
if (buttonContext.isInline) {
throw new Error('Replace self action not supported for inline buttons');
}

const linePosition = position?.getPosition();

if (linePosition === undefined) {
if (buttonContext.position === undefined) {
throw new Error('Position of the button in the note is unknown');
}

if (linePosition.lineStart > linePosition.lineEnd) {
if (buttonContext.position.lineStart > buttonContext.position.lineEnd) {
throw new Error('Position of the button in the note is invalid');
}

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

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

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

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

splitContent = [
...splitContent.slice(0, linePosition.lineStart),
...splitContent.slice(0, buttonContext.position.lineStart),
replacement,
...splitContent.slice(linePosition.lineEnd + 1),
...splitContent.slice(buttonContext.position.lineEnd + 1),
];

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

async runRegexpReplaceInNotAction(action: RegexpReplaceInNoteButtonAction, filePath: string): Promise<void> {
async runRegexpReplaceInNoteAction(action: RegexpReplaceInNoteButtonAction, filePath: string): Promise<void> {
if (action.regexp === '') {
throw new Error('Regexp cannot be empty');
}
Expand Down Expand Up @@ -410,6 +407,7 @@ export class ButtonActionRunner {
config: ButtonConfig | undefined,
action: InlineJsButtonAction,
filePath: string,
buttonContext: ButtonContext,
): Promise<void> {
if (!this.plugin.settings.enableJs) {
throw new MetaBindJsError({
Expand All @@ -421,6 +419,7 @@ export class ButtonActionRunner {

const configOverrides: Record<string, unknown> = {
buttonConfig: structuredClone(config),
buttonContext: structuredClone(buttonContext),
};
const unloadCallback = await this.plugin.internal.jsEngineRunCode(action.code, filePath, configOverrides);
unloadCallback();
Expand Down
25 changes: 16 additions & 9 deletions packages/core/src/fields/button/ButtonField.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { NotePosition } from 'packages/core/src/config/APIConfigs';
import { RenderChildType } from 'packages/core/src/config/APIConfigs';
import type { ButtonConfig } from 'packages/core/src/config/ButtonConfig';
import type { ButtonConfig, ButtonContext } from 'packages/core/src/config/ButtonConfig';
import type { IPlugin } from 'packages/core/src/IPlugin';
import ButtonComponent from 'packages/core/src/utils/components/ButtonComponent.svelte';
import { Mountable } from 'packages/core/src/utils/Mountable';
Expand All @@ -12,7 +12,7 @@ export class ButtonField extends Mountable {
plugin: IPlugin;
config: ButtonConfig;
filePath: string;
inline: boolean;
isInline: boolean;
position: NotePosition | undefined;
buttonComponent?: ReturnType<SvelteComponent>;
isInGroup: boolean;
Expand All @@ -32,7 +32,7 @@ export class ButtonField extends Mountable {
this.plugin = plugin;
this.config = config;
this.filePath = filePath;
this.inline = renderChildType === RenderChildType.INLINE;
this.isInline = renderChildType === RenderChildType.INLINE;
this.position = position;
this.isInGroup = isInGroup;
this.isPreview = isPreview;
Expand All @@ -41,9 +41,9 @@ export class ButtonField extends Mountable {
protected onMount(targetEl: HTMLElement): void {
DomHelpers.empty(targetEl);
DomHelpers.removeAllClasses(targetEl);
DomHelpers.addClasses(targetEl, ['mb-button', this.inline ? 'mb-button-inline' : 'mb-button-block']);
DomHelpers.addClasses(targetEl, ['mb-button', this.isInline ? 'mb-button-inline' : 'mb-button-block']);

if (!this.inline && !this.isPreview && !this.isInGroup) {
if (!this.isInline && !this.isPreview && !this.isInGroup) {
if (this.config.id) {
this.plugin.api.buttonManager.addButton(this.filePath, this.config);
}
Expand All @@ -68,23 +68,30 @@ export class ButtonField extends Mountable {
label: this.config.label,
tooltip: isTruthy(this.config.tooltip) ? this.config.tooltip : this.config.label,
onclick: async (): Promise<void> => {
await this.plugin.api.buttonActionRunner.runButtonAction(
await this.plugin.api.buttonActionRunner.runButtonActions(
this.config,
this.filePath,
this.inline,
this.position,
this.getContext(),
);
},
},
});
}

public getContext(): ButtonContext {
return {
position: this.position?.getPosition(),
isInGroup: this.isInGroup,
isInline: this.isInline,
};
}

protected onUnmount(): void {
if (this.buttonComponent) {
unmount(this.buttonComponent);
}

if (!this.inline && !this.isPreview) {
if (!this.isInline && !this.isPreview) {
if (this.config?.id) {
this.plugin.api.buttonManager.removeButton(this.filePath, this.config.id);
}
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/utils/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,13 @@ export function isFalsy(value: unknown): boolean {
return !value;
}

export function deepFreeze<T extends object>(object: T): Readonly<T> {
export function deepFreeze<T extends object>(object: T): Readonly<T>;
export function deepFreeze<T extends object>(object: T | undefined): Readonly<T | undefined>;
export function deepFreeze<T extends object>(object: T | undefined): Readonly<T | undefined> {
if (object === undefined) {
return object;
}

// Retrieve the property names defined on object
const propNames: (string | symbol)[] = Reflect.ownKeys(object);

Expand Down
36 changes: 23 additions & 13 deletions tests/fields/Button.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ let testPlugin: TestPlugin;
const testFilePath = 'test/file.md';

async function simplifiedRunAction(action: ButtonAction): Promise<void> {
await testPlugin.api.buttonActionRunner.runAction(undefined, action, testFilePath, false, undefined);
await testPlugin.api.buttonActionRunner.runAction(undefined, action, testFilePath, {
position: undefined,
isInline: false,
isInGroup: false,
});
}

const buttonActionTests: Record<ButtonActionType, () => void> = {
Expand Down Expand Up @@ -176,12 +180,15 @@ const buttonActionTests: Record<ButtonActionType, () => void> = {
replacement: 'no button',
},
testFilePath,
false,
new NotePosition({
// these line numbers start at 0
lineStart: 1,
lineEnd: 1,
}),
{
position: {
// these line numbers start at 0
lineStart: 1,
lineEnd: 1,
},
isInline: false,
isInGroup: false,
},
);

expect(testPlugin.internal.fileSystem.readFile('test/file.md')).toBe('line1\nno button\nline3\n');
Expand All @@ -197,12 +204,15 @@ const buttonActionTests: Record<ButtonActionType, () => void> = {
replacement: 'no button',
},
testFilePath,
false,
new NotePosition({
// these line numbers start at 0
lineStart: 1,
lineEnd: 3,
}),
{
position: {
// these line numbers start at 0
lineStart: 1,
lineEnd: 3,
},
isInline: false,
isInGroup: false,
},
);

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

0 comments on commit c87d7b4

Please sign in to comment.