diff --git a/CHANGELOG.md b/CHANGELOG.md index a98f0029..426628cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Obsidian Meta Bind Changelog +# Unreleased + +- Improved validation errors for buttons and the API +- Removed deprecated input fields that had names in snake_case. Use the camelCase variants instead. The snake_case variants were deprecated since version `0.6.0`. + # 0.11.0 New Features diff --git a/bun.lockb b/bun.lockb index 1d5305d4..eda0c582 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/exampleVault/Advanced Examples/Activity Tracker.md b/exampleVault/Advanced Examples/Activity Tracker.md index a7018108..f636f1fd 100644 --- a/exampleVault/Advanced Examples/Activity Tracker.md +++ b/exampleVault/Advanced Examples/Activity Tracker.md @@ -1,9 +1,7 @@ --- activities: - - activity: sudying - status: 0 - from: 02:02 - to: 03:02 + - {} + - {} --- ```js-engine @@ -19,5 +17,21 @@ const columns = [ ]; +mb.createTable(container, context.file.path, component, bindTarget, tableHead, columns); +``` + +```js-engine +const mb = engine.getPlugin('obsidian-meta-bind-plugin').api; + +const bindTarget = mb.createBindTarget('activities'); +const tableHead = ['From', 'To', 'Activity', 'Status']; +const columns = [ + mb.inputField.createInputFieldDeclarationFromString('INPUT[time:scope^from]'), + mb.inputField.createInputFieldDeclarationFromString('INPUT[time:scope^to]'), + mb.inputField.createInputFieldDeclarationFromString('INPUT[inlineSelect(option(youtube), option(sudying), option(linch)):scope^activity]'), + mb.inputField.createInputFieldDeclarationFromString('INPUT[inlineSelect(option(-, unproductive), option(0, normal), option(+, productive)):scope^status]') +]; + + mb.createTable(container, context.file.path, component, bindTarget, tableHead, columns); ``` \ No newline at end of file diff --git a/exampleVault/Button Example.md b/exampleVault/Button Example.md index 2600a0ec..b6a214c2 100644 --- a/exampleVault/Button Example.md +++ b/exampleVault/Button Example.md @@ -207,9 +207,20 @@ hidden: false id: "" style: default actions: - - type: sl - ms: 1000 + - type: sleep - type: command command: obsidian-meta-bind-plugin:open-help ``` + + +```meta-bind-button +label: Test +hidden: asdasd +id: "" +style: default +actions: + - type: command + command: obsidian-meta-bind-plugin:open-help + +``` \ No newline at end of file diff --git a/exampleVault/Input Fields/Progress Bar.md b/exampleVault/Input Fields/Progress Bar.md index d61b60c9..f80d9ca8 100644 --- a/exampleVault/Input Fields/Progress Bar.md +++ b/exampleVault/Input Fields/Progress Bar.md @@ -1,7 +1,8 @@ --- progress1: -6 progress2: 0.2 -progress3: 4 +progress3: 2 +progress4: 3.6 --- ```meta-bind @@ -16,4 +17,7 @@ INPUT[progressBar(showcase, minValue(0), maxValue(1), stepSize(0.1)):progress2] INPUT[progressBar(showcase, minValue(0), maxValue(10), stepSize(-1)):progress3] ``` +```meta-bind +INPUT[progressBar(showcase, minValue(0), maxValue(10), stepSize(0.1)):progress4] +``` diff --git a/exampleVault/Input Fields/Slider.md b/exampleVault/Input Fields/Slider.md index 9d0a5aab..714f4488 100644 --- a/exampleVault/Input Fields/Slider.md +++ b/exampleVault/Input Fields/Slider.md @@ -2,6 +2,7 @@ slider1: 51 slider2: 2 slider3: 233 +slider4: 0.1 --- ### Simple Slider @@ -16,7 +17,7 @@ INPUT[slider(showcase):slider1] INPUT[slider(addLabels, showcase):slider1] ``` -### Slider with custom min max values +### Slider with Custom Min Max Values ```meta-bind INPUT[slider(addLabels, minValue(-20), maxValue(20), showcase):slider2] @@ -25,3 +26,10 @@ INPUT[slider(addLabels, minValue(-20), maxValue(20), showcase):slider2] ```meta-bind INPUT[slider(addLabels, minValue(0), maxValue(1000), showcase):slider3] ``` + +### Slider with Custom Step Size + +```meta-bind +INPUT[slider(addLabels, minValue(0), maxValue(10), stepSize(0.1), showcase):slider4] +``` + diff --git a/exampleVault/Input Fields/Toggle.md b/exampleVault/Input Fields/Toggle.md index 05626591..7526527d 100644 --- a/exampleVault/Input Fields/Toggle.md +++ b/exampleVault/Input Fields/Toggle.md @@ -9,4 +9,5 @@ INPUT[toggle(showcase):toggle1] ```meta-bind INPUT[toggle(showcase, onValue(1), offValue(0), defaultValue(1)):toggle2] -``` \ No newline at end of file +``` + diff --git a/extraTypes/obsidian-ex.d.ts b/extraTypes/obsidian-ex.d.ts index 58228632..f2a9dd3c 100644 --- a/extraTypes/obsidian-ex.d.ts +++ b/extraTypes/obsidian-ex.d.ts @@ -57,6 +57,19 @@ declare module 'obsidian' { removeCommand: (commandId: string) => void; }; } + + interface Menu { + dom: HTMLElement; + items: MenuItem[]; + onMouseOver: (evt: MouseEvent) => void; + } + + interface MenuItem { + callback: () => void; + dom: HTMLElement; + setSubmenu: () => Menu; + disabled: boolean; + } } declare global { diff --git a/package.json b/package.json index 7f343921..99d9a324 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@lemons_dev/parsinom": "^0.0.12", "itertools-ts": "^1.27.0", "mathjs": "^12.0.0", - "zod": "^3.22.4" + "zod": "^3.22.4", + "zod-validation-error": "^2.1.0" } } diff --git a/src/EditorMenu.ts b/src/EditorMenu.ts new file mode 100644 index 00000000..14aadc34 --- /dev/null +++ b/src/EditorMenu.ts @@ -0,0 +1,66 @@ +import { type Editor, type Menu, stringifyYaml } from 'obsidian'; +import type MetaBindPlugin from './main'; +import { createInputFieldInsertExamples, createViewFieldInsertExamples } from './faq/InputFieldExamples'; +import { ButtonBuilderModal } from './fields/button/ButtonBuilderModal'; + +export function createEditorMenu(menu: Menu, editor: Editor, plugin: MetaBindPlugin): void { + const inputFieldExamples = createInputFieldInsertExamples(plugin); + const viewFieldExamples = createViewFieldInsertExamples(plugin); + + menu.addItem(mbItem => { + mbItem.setTitle('Meta Bind'); + mbItem.setIcon('blocks'); + + const mbSubmenu = mbItem.setSubmenu(); + + mbSubmenu.addItem(ipfItem => { + ipfItem.setTitle('Input Field'); + + const ipfSubmenu = ipfItem.setSubmenu(); + + for (const [type, declaration] of inputFieldExamples) { + ipfSubmenu.addItem(item => { + item.setTitle(type); + item.onClick(() => insetAtCursor(editor, declaration)); + }); + } + }); + + mbSubmenu.addItem(vfItem => { + vfItem.setTitle('View Field'); + + const vfSubmenu = vfItem.setSubmenu(); + + for (const [type, declaration] of viewFieldExamples) { + vfSubmenu.addItem(item => { + item.setTitle(type); + item.onClick(() => insetAtCursor(editor, declaration)); + }); + } + }); + + mbSubmenu.addItem(inlineButtonItem => { + inlineButtonItem.setTitle('Inline Button'); + inlineButtonItem.onClick(() => { + insetAtCursor(editor, '`BUTTON[example-id]`'); + }); + }); + + mbSubmenu.addItem(buttonItem => { + buttonItem.setTitle('Button'); + buttonItem.onClick(() => { + new ButtonBuilderModal( + plugin, + config => { + insetAtCursor(editor, `\`\`\`meta-bind-button\n${stringifyYaml(config)}\n\`\`\``); + }, + 'Insert', + ).open(); + }); + }); + }); +} + +function insetAtCursor(editor: Editor, text: string): void { + editor.replaceSelection(text); +} diff --git a/src/api/API.ts b/src/api/API.ts index 4ec4e115..c1ce8a3b 100644 --- a/src/api/API.ts +++ b/src/api/API.ts @@ -34,18 +34,18 @@ import { ButtonMDRC } from '../renderChildren/ButtonMDRC'; import { InlineButtonMDRC } from '../renderChildren/InlineButtonMDRC'; import { SyntaxHighlightingAPI } from './SyntaxHighlightingAPI'; import { - V_BindTargetDeclaration, - V_BindTargetScope, - V_ComponentLike, - V_FilePath, - V_HTMLElement, - V_RenderChildType, - V_Signal, - V_UnvalidatedInputFieldDeclaration, - V_UnvalidatedViewFieldDeclaration, - V_VoidFunction, + V_API_createBindTarget, + V_API_createButtonFromString, + V_API_createExcludedField, + V_API_createInlineButtonFromString, + V_API_createInputField, + V_API_createInputFieldFromString, + V_API_createJsViewFieldFromString, + V_API_createTable, + V_API_createViewFieldFromString, + V_API_listenToMetadata, } from './APIValidators'; -import { z } from 'zod'; +import { validateArgs } from '../utils/ZodUtils'; export interface ComponentLike { addChild(child: Component): void; @@ -103,12 +103,14 @@ export class API implements IAPI { component: ComponentLike, scope?: BindTargetScope | undefined, ): InputFieldMDRC | ExcludedMDRC { - V_UnvalidatedInputFieldDeclaration.parse(unvalidatedDeclaration); - V_RenderChildType.parse(renderType); - V_FilePath.parse(filePath); - V_HTMLElement.parse(containerEl); - V_ComponentLike.parse(component); - V_BindTargetScope.optional().parse(scope); + validateArgs(V_API_createInputField, [ + unvalidatedDeclaration, + renderType, + filePath, + containerEl, + component, + scope, + ]); if (this.plugin.isFilePathExcluded(filePath)) { return this.createExcludedField(containerEl, filePath, component); @@ -141,12 +143,14 @@ export class API implements IAPI { component: ComponentLike, scope?: BindTargetScope | undefined, ): InputFieldMDRC | ExcludedMDRC { - z.string().parse(fullDeclaration); - V_RenderChildType.parse(renderType); - V_FilePath.parse(filePath); - V_HTMLElement.parse(containerEl); - V_ComponentLike.parse(component); - V_BindTargetScope.optional().parse(scope); + validateArgs(V_API_createInputFieldFromString, [ + fullDeclaration, + renderType, + filePath, + containerEl, + component, + scope, + ]); if (this.plugin.isFilePathExcluded(filePath)) { return this.createExcludedField(containerEl, filePath, component); @@ -179,12 +183,14 @@ export class API implements IAPI { component: ComponentLike, scope?: BindTargetScope | undefined, ): ViewFieldMDRC | ExcludedMDRC { - z.string().parse(fullDeclaration); - V_RenderChildType.parse(renderType); - V_FilePath.parse(filePath); - V_HTMLElement.parse(containerEl); - V_ComponentLike.parse(component); - V_BindTargetScope.optional().parse(scope); + validateArgs(V_API_createViewFieldFromString, [ + fullDeclaration, + renderType, + filePath, + containerEl, + component, + scope, + ]); if (this.plugin.isFilePathExcluded(filePath)) { return this.createExcludedField(containerEl, filePath, component); @@ -215,11 +221,13 @@ export class API implements IAPI { containerEl: HTMLElement, component: ComponentLike, ): JsViewFieldMDRC | ExcludedMDRC { - z.string().parse(fullDeclaration); - V_RenderChildType.parse(renderType); - V_FilePath.parse(filePath); - V_HTMLElement.parse(containerEl); - V_ComponentLike.parse(component); + validateArgs(V_API_createJsViewFieldFromString, [ + fullDeclaration, + renderType, + filePath, + containerEl, + component, + ]); if (this.plugin.isFilePathExcluded(filePath)) { return this.createExcludedField(containerEl, filePath, component); @@ -241,6 +249,8 @@ export class API implements IAPI { * @param component component for lifecycle management */ public createExcludedField(containerEl: HTMLElement, filePath: string, component: ComponentLike): ExcludedMDRC { + validateArgs(V_API_createExcludedField, [containerEl, filePath, component]); + const excludedField = new ExcludedMDRC(containerEl, RenderChildType.INLINE, this.plugin, filePath, getUUID()); component.addChild(excludedField); @@ -267,11 +277,7 @@ export class API implements IAPI { listenToChildren: boolean = false, onDelete?: () => void, ): () => void { - V_Signal.parse(signal); - V_FilePath.parse(filePath); - z.array(z.string()).parse(metadataPath); - z.boolean().parse(listenToChildren); - V_VoidFunction.optional().parse(onDelete); + validateArgs(V_API_listenToMetadata, [signal, filePath, metadataPath, listenToChildren, onDelete]); const uuid = getUUID(); @@ -310,12 +316,7 @@ export class API implements IAPI { tableHead: string[], columns: (UnvalidatedInputFieldDeclaration | UnvalidatedViewFieldDeclaration)[], ): MetaBindTable { - V_HTMLElement.parse(containerEl); - V_FilePath.parse(filePath); - V_ComponentLike.parse(component); - V_BindTargetDeclaration.parse(bindTarget); - z.array(z.string()).parse(tableHead); - z.array(z.union([V_UnvalidatedInputFieldDeclaration, V_UnvalidatedViewFieldDeclaration])).parse(columns); + validateArgs(V_API_createTable, [containerEl, filePath, component, bindTarget, tableHead, columns]); const table = new MetaBindTable( containerEl, @@ -333,8 +334,7 @@ export class API implements IAPI { } public createBindTarget(fullDeclaration: string, currentFilePath: string): BindTargetDeclaration { - z.string().parse(fullDeclaration); - V_FilePath.parse(currentFilePath); + validateArgs(V_API_createBindTarget, [fullDeclaration, currentFilePath]); return this.bindTargetParser.parseAndValidateBindTarget(fullDeclaration, currentFilePath); } @@ -345,10 +345,7 @@ export class API implements IAPI { containerEl: HTMLElement, component: ComponentLike, ): ButtonMDRC | ExcludedMDRC { - z.string().parse(fullDeclaration); - V_FilePath.parse(filePath); - V_HTMLElement.parse(containerEl); - V_ComponentLike.parse(component); + validateArgs(V_API_createButtonFromString, [fullDeclaration, filePath, containerEl, component]); if (this.plugin.isFilePathExcluded(filePath)) { return this.createExcludedField(containerEl, filePath, component); @@ -366,10 +363,7 @@ export class API implements IAPI { containerEl: HTMLElement, component: ComponentLike, ): InlineButtonMDRC | ExcludedMDRC { - z.string().parse(fullDeclaration); - V_FilePath.parse(filePath); - V_HTMLElement.parse(containerEl); - V_ComponentLike.parse(component); + validateArgs(V_API_createInlineButtonFromString, [fullDeclaration, filePath, containerEl, component]); if (this.plugin.isFilePathExcluded(filePath)) { return this.createExcludedField(containerEl, filePath, component); diff --git a/src/api/APIValidators.ts b/src/api/APIValidators.ts index 78d0dabc..4e59a5a1 100644 --- a/src/api/APIValidators.ts +++ b/src/api/APIValidators.ts @@ -1,6 +1,6 @@ import { z } from 'zod'; import { schemaForType } from '../utils/ZodUtils'; -import { type ComponentLike } from './API'; +import { type API, type ComponentLike } from './API'; import { RenderChildType } from '../config/FieldConfigs'; import { type UnvalidatedFieldArgument, @@ -114,3 +114,57 @@ export const V_ComponentLike = schemaForType()( addChild: z.function().args(z.instanceof(Component)).returns(z.void()), }), ); + +export const V_API_createInputField = schemaForType['createInputField']>>()( + z.tuple([ + V_UnvalidatedInputFieldDeclaration, + V_RenderChildType, + V_FilePath, + V_HTMLElement, + V_ComponentLike, + V_BindTargetScope.optional(), + ]), +); + +export const V_API_createInputFieldFromString = schemaForType< + Parameters['createInputFieldFromString']> +>()(z.tuple([z.string(), V_RenderChildType, V_FilePath, V_HTMLElement, V_ComponentLike, V_BindTargetScope.optional()])); + +export const V_API_createViewFieldFromString = schemaForType< + Parameters['createViewFieldFromString']> +>()(z.tuple([z.string(), V_RenderChildType, V_FilePath, V_HTMLElement, V_ComponentLike, V_BindTargetScope.optional()])); + +export const V_API_createJsViewFieldFromString = schemaForType< + Parameters['createJsViewFieldFromString']> +>()(z.tuple([z.string(), V_RenderChildType, V_FilePath, V_HTMLElement, V_ComponentLike])); + +export const V_API_createExcludedField = schemaForType['createExcludedField']>>()( + z.tuple([V_HTMLElement, V_FilePath, V_ComponentLike]), +); + +export const V_API_listenToMetadata = schemaForType['listenToMetadata']>>()( + z.tuple([V_Signal, V_FilePath, z.array(z.string()), z.boolean(), V_VoidFunction.optional()]), +); + +export const V_API_createTable = schemaForType['createTable']>>()( + z.tuple([ + V_HTMLElement, + V_FilePath, + V_ComponentLike, + V_BindTargetDeclaration, + z.array(z.string()), + z.array(z.union([V_UnvalidatedInputFieldDeclaration, V_UnvalidatedViewFieldDeclaration])), + ]), +); + +export const V_API_createBindTarget = schemaForType['createBindTarget']>>()( + z.tuple([z.string(), V_FilePath]), +); + +export const V_API_createButtonFromString = schemaForType< + Parameters['createButtonFromString']> +>()(z.tuple([z.string(), V_FilePath, V_HTMLElement, V_ComponentLike])); + +export const V_API_createInlineButtonFromString = schemaForType< + Parameters['createInlineButtonFromString']> +>()(z.tuple([z.string(), V_FilePath, V_HTMLElement, V_ComponentLike])); diff --git a/src/config/FieldConfigs.ts b/src/config/FieldConfigs.ts index 1339ef0c..6451a1c2 100644 --- a/src/config/FieldConfigs.ts +++ b/src/config/FieldConfigs.ts @@ -18,14 +18,11 @@ export enum InputFieldType { TOGGLE = 'toggle', SLIDER = 'slider', TEXT = 'text', - TEXT_AREA_DEPRECATED = 'text_area', TEXT_AREA = 'textArea', SELECT = 'select', - MULTI_SELECT_DEPRECATED = 'multi_select', MULTI_SELECT = 'multiSelect', DATE = 'date', TIME = 'time', - DATE_PICKER_DEPRECATED = 'date_picker', DATE_PICKER = 'datePicker', NUMBER = 'number', SUGGESTER = 'suggester', @@ -83,11 +80,6 @@ export const InputFieldConfigs: Record = { allowInBlock: true, allowInline: true, }, - [InputFieldType.TEXT_AREA_DEPRECATED]: { - type: InputFieldType.TEXT_AREA_DEPRECATED, - allowInBlock: true, - allowInline: true, - }, [InputFieldType.TEXT_AREA]: { type: InputFieldType.TEXT_AREA, allowInBlock: true, @@ -98,11 +90,6 @@ export const InputFieldConfigs: Record = { allowInBlock: true, allowInline: false, }, - [InputFieldType.MULTI_SELECT_DEPRECATED]: { - type: InputFieldType.MULTI_SELECT_DEPRECATED, - allowInBlock: true, - allowInline: false, - }, [InputFieldType.MULTI_SELECT]: { type: InputFieldType.MULTI_SELECT, allowInBlock: true, @@ -118,11 +105,6 @@ export const InputFieldConfigs: Record = { allowInBlock: true, allowInline: true, }, - [InputFieldType.DATE_PICKER_DEPRECATED]: { - type: InputFieldType.DATE_PICKER_DEPRECATED, - allowInBlock: true, - allowInline: true, - }, [InputFieldType.DATE_PICKER]: { type: InputFieldType.DATE_PICKER, allowInBlock: true, @@ -311,7 +293,6 @@ export const InputFieldArgumentConfigs: Record - {#each createInputFieldExamples(plugin) as example} + {#each createInputFieldFAQExamples(plugin) as example} {/each} diff --git a/src/faq/InputFieldExamples.ts b/src/faq/InputFieldExamples.ts index 2a8df017..9a831a1f 100644 --- a/src/faq/InputFieldExamples.ts +++ b/src/faq/InputFieldExamples.ts @@ -1,6 +1,11 @@ -import { InputFieldArgumentType, type InputFieldType } from '../config/FieldConfigs'; +import { + InputFieldArgumentType, + InputFieldConfigs, + type InputFieldType, + type ViewFieldType, +} from '../config/FieldConfigs'; import { type UnvalidatedInputFieldDeclaration } from '../parsers/inputFieldParser/InputFieldDeclaration'; -import type MetaBindPlugin from '../main'; +import { type IPlugin } from '../IPlugin'; export const INPUT_FIELD_EXAMPLE_DECLARATIONS: Record = { date: 'date', @@ -23,13 +28,18 @@ export const INPUT_FIELD_EXAMPLE_DECLARATIONS: Record = time: 'time', toggle: 'toggle', - date_picker: '', - multi_select: '', - text_area: '', invalid: '', }; -export function createInputFieldExamples(plugin: MetaBindPlugin): [InputFieldType, UnvalidatedInputFieldDeclaration][] { +export const VIEW_FIELD_EXAMPLE_DECLARATIONS: Record = { + math: 'VIEW[{exampleProperty} + 2][math]', + text: 'VIEW[some text {exampleProperty}][text]', + link: 'VIEW[{exampleProperty}][link]', + + invalid: '', +}; + +export function createInputFieldFAQExamples(plugin: IPlugin): [InputFieldType, UnvalidatedInputFieldDeclaration][] { const ret: [InputFieldType, UnvalidatedInputFieldDeclaration][] = []; for (const [type, declaration] of Object.entries(INPUT_FIELD_EXAMPLE_DECLARATIONS)) { if (declaration === '') { @@ -50,3 +60,47 @@ export function createInputFieldExamples(plugin: MetaBindPlugin): [InputFieldTyp } return ret; } + +export function createInputFieldInsertExamples(_plugin: IPlugin): [string, string][] { + const ret: [string, string][] = []; + for (const [type, declaration] of Object.entries(INPUT_FIELD_EXAMPLE_DECLARATIONS)) { + if (declaration === '') { + continue; + } + const ipfType = type as InputFieldType; + + let fullDeclaration = ''; + + if (InputFieldConfigs[ipfType].allowInline) { + fullDeclaration = `\`INPUT[${declaration}:exampleProperty]\``; + } else { + fullDeclaration = `\n\`\`\`meta-bind\nINPUT[${declaration}:exampleProperty]\n\`\`\`\n`; + } + + ret.push([ipfType, fullDeclaration]); + } + + ret.sort((a, b) => a[0].localeCompare(b[0])); + + return ret; +} + +export function createViewFieldInsertExamples(_plugin: IPlugin): [string, string][] { + const ret: [string, string][] = []; + for (const [type, declaration] of Object.entries(VIEW_FIELD_EXAMPLE_DECLARATIONS)) { + if (declaration === '') { + continue; + } + const vfType = type as ViewFieldType; + + const fullDeclaration = `\`${declaration}\``; + + ret.push([vfType, fullDeclaration]); + } + + ret.push(['markdown', `\`VIEW[**some markdown** {exampleProperty}][text(renderMarkdown)]\``]); + + ret.sort((a, b) => a[0].localeCompare(b[0])); + + return ret; +} diff --git a/src/fields/button/ButtonBuilderModal.ts b/src/fields/button/ButtonBuilderModal.ts index 93588708..daa4921f 100644 --- a/src/fields/button/ButtonBuilderModal.ts +++ b/src/fields/button/ButtonBuilderModal.ts @@ -7,11 +7,13 @@ export class ButtonBuilderModal extends Modal { plugin: MetaBindPlugin; component?: ButtonBuilderModalComponent; onOkay: (config: ButtonConfig) => void; + submitText: string; - constructor(plugin: MetaBindPlugin, onOkay: (config: ButtonConfig) => void) { + constructor(plugin: MetaBindPlugin, onOkay: (config: ButtonConfig) => void, submitText: string) { super(plugin.app); this.plugin = plugin; this.onOkay = onOkay; + this.submitText = submitText; } public onOpen(): void { diff --git a/src/fields/button/ButtonBuilderModalComponent.svelte b/src/fields/button/ButtonBuilderModalComponent.svelte index 6ac1d87b..e5eff542 100644 --- a/src/fields/button/ButtonBuilderModalComponent.svelte +++ b/src/fields/button/ButtonBuilderModalComponent.svelte @@ -253,6 +253,6 @@ Add action of type
- + diff --git a/src/fields/button/ButtonField.ts b/src/fields/button/ButtonField.ts index db749baf..85406920 100644 --- a/src/fields/button/ButtonField.ts +++ b/src/fields/button/ButtonField.ts @@ -5,6 +5,7 @@ import { V_ButtonConfig } from '../../config/ButtonConfigValidators'; import { ErrorLevel, MetaBindButtonError } from '../../utils/errors/MetaBindErrors'; import { DocsUtils } from '../../utils/DocsUtils'; import { isTruthy } from '../../utils/Utils'; +import { fromZodError } from 'zod-validation-error'; export class ButtonField { plugin: IPlugin; @@ -48,11 +49,17 @@ export class ButtonField { const validationResult = V_ButtonConfig.safeParse(this.unvalidatedConfig); if (!validationResult.success) { + const niceError = fromZodError(validationResult.error, { + unionSeparator: '\nOR ', + issueSeparator: ' AND ', + prefix: null, + }); + throw new MetaBindButtonError({ errorLevel: ErrorLevel.ERROR, effect: 'can not parse button config', cause: 'zod validation failed. Check your button syntax', - positionContext: validationResult.error.message, + positionContext: niceError.message, docs: [DocsUtils.linkToButtonConfig()], }); } diff --git a/src/fields/inputFields/InputFieldFactory.ts b/src/fields/inputFields/InputFieldFactory.ts index 0fdaa11d..cc1750df 100644 --- a/src/fields/inputFields/InputFieldFactory.ts +++ b/src/fields/inputFields/InputFieldFactory.ts @@ -70,18 +70,12 @@ export class InputFieldFactory { return new TextIPF(base); } else if (type === InputFieldType.TEXT_AREA) { return new TextAreaIPF(base); - } else if (type === InputFieldType.TEXT_AREA_DEPRECATED) { - return new TextAreaIPF(base); } else if (type === InputFieldType.SELECT) { return new SelectIPF(base); } else if (type === InputFieldType.MULTI_SELECT) { return new MultiSelectIPF(base); - } else if (type === InputFieldType.MULTI_SELECT_DEPRECATED) { - return new MultiSelectIPF(base); } else if (type === InputFieldType.DATE_PICKER) { return new DatePickerIPF(base); - } else if (type === InputFieldType.DATE_PICKER_DEPRECATED) { - return new DatePickerIPF(base); } else if (type === InputFieldType.NUMBER) { return new NumberIPF(base); } else if (type === InputFieldType.SUGGESTER) { diff --git a/src/fields/inputFields/fields/ProgressBar/ProgressBarComponent.svelte b/src/fields/inputFields/fields/ProgressBar/ProgressBarComponent.svelte index 795fc960..677907e9 100644 --- a/src/fields/inputFields/fields/ProgressBar/ProgressBarComponent.svelte +++ b/src/fields/inputFields/fields/ProgressBar/ProgressBarComponent.svelte @@ -71,7 +71,7 @@ function round(number: number, increment: number) { // the parsing is done to fix floating point errors - return Number.parseFloat((Math.round(number / increment) * increment).toFixed(15)); + return Number.parseFloat((Math.round(number / increment) * increment).toFixed(10)); } function onKeyPress(e: KeyboardEvent) { diff --git a/src/main.ts b/src/main.ts index 3856b589..7b289518 100644 --- a/src/main.ts +++ b/src/main.ts @@ -20,6 +20,7 @@ import { InlineMDRCType, InlineMDRCUtils } from './utils/InlineMDRCUtils'; import { registerCm5HLModes } from './cm6/Cm5_Modes'; import { DependencyManager } from './utils/dependencies/DependencyManager'; import { Version } from './utils/dependencies/Version'; +import { createEditorMenu } from './EditorMenu'; export enum MetaBindBuild { DEV = 'dev', @@ -95,6 +96,14 @@ export default class MetaBindPlugin extends Plugin implements IPlugin { // misc this.registerView(MB_FAQ_VIEW_TYPE, leaf => new FaqView(leaf, this)); this.addStatusBarBuildIndicator(); + + if (this.settings.enableEditorRightClickMenu) { + this.registerEvent( + this.app.workspace.on('editor-menu', (menu, editor) => { + createEditorMenu(menu, editor, this); + }), + ); + } } onunload(): void { @@ -209,11 +218,15 @@ export default class MetaBindPlugin extends Plugin implements IPlugin { id: 'open-button-builder', name: 'Open Button Builder', callback: () => { - new ButtonBuilderModal(this, config => { - void window.navigator.clipboard.writeText( - `\`\`\`meta-bind-button\n${stringifyYaml(config)}\n\`\`\``, - ); - }).open(); + new ButtonBuilderModal( + this, + config => { + void window.navigator.clipboard.writeText( + `\`\`\`meta-bind-button\n${stringifyYaml(config)}\n\`\`\``, + ); + }, + 'Copy to Clipboard', + ).open(); }, }); } diff --git a/src/parsers/inputFieldParser/InputFieldDeclarationValidator.ts b/src/parsers/inputFieldParser/InputFieldDeclarationValidator.ts index 80878709..e92c0a7b 100644 --- a/src/parsers/inputFieldParser/InputFieldDeclarationValidator.ts +++ b/src/parsers/inputFieldParser/InputFieldDeclarationValidator.ts @@ -66,22 +66,8 @@ export class InputFieldDeclarationValidator { return InputFieldType.INVALID; } - private checkForDeprecation(declaration: InputFieldDeclaration): void { - if ( - declaration.inputFieldType === InputFieldType.DATE_PICKER_DEPRECATED || - declaration.inputFieldType === InputFieldType.TEXT_AREA_DEPRECATED || - declaration.inputFieldType === InputFieldType.MULTI_SELECT_DEPRECATED - ) { - this.errorCollection.add( - new ParsingValidationError( - ErrorLevel.WARNING, - 'Declaration Validator', - `'${declaration.inputFieldType}' is deprecated, as it has been renamed to be in camel case ('input_field_type' => 'inputFieldType').`, - this.unvalidatedDeclaration.fullDeclaration, - this.unvalidatedDeclaration.inputFieldType?.position, - ), - ); - } + private checkForDeprecation(_declaration: InputFieldDeclaration): void { + // nothing to check atm } private validateBindTarget(scope: BindTargetScope | undefined): BindTargetDeclaration | undefined { diff --git a/src/publish/PublishUtils.ts b/src/publish/PublishUtils.ts index 4064e5c9..1662b2cf 100644 --- a/src/publish/PublishUtils.ts +++ b/src/publish/PublishUtils.ts @@ -19,28 +19,19 @@ export function getPublishDefaultValue(declaration: InputFieldDeclaration): unkn return minArgument ? minArgument.value : 0; } else if (declaration.inputFieldType === InputFieldType.TEXT) { return placeholderString; - } else if ( - declaration.inputFieldType === InputFieldType.TEXT_AREA_DEPRECATED || - declaration.inputFieldType === InputFieldType.TEXT_AREA - ) { + } else if (declaration.inputFieldType === InputFieldType.TEXT_AREA) { return placeholderString; } else if (declaration.inputFieldType === InputFieldType.SELECT) { const firstOptionArgument = declaration.argumentContainer.get(InputFieldArgumentType.OPTION); return firstOptionArgument ? firstOptionArgument.value : placeholderString; - } else if ( - declaration.inputFieldType === InputFieldType.MULTI_SELECT_DEPRECATED || - declaration.inputFieldType === InputFieldType.MULTI_SELECT - ) { + } else if (declaration.inputFieldType === InputFieldType.MULTI_SELECT) { const firstOptionArgument = declaration.argumentContainer.get(InputFieldArgumentType.OPTION); return firstOptionArgument ? firstOptionArgument.value : placeholderString; } else if (declaration.inputFieldType === InputFieldType.DATE) { return '1970-01-01'; } else if (declaration.inputFieldType === InputFieldType.TIME) { return '00:00'; - } else if ( - declaration.inputFieldType === InputFieldType.DATE_PICKER_DEPRECATED || - declaration.inputFieldType === InputFieldType.DATE_PICKER - ) { + } else if (declaration.inputFieldType === InputFieldType.DATE_PICKER) { return '1970-01-01'; } else if (declaration.inputFieldType === InputFieldType.NUMBER) { return 0; diff --git a/src/renderChildren/InputFieldMDRC.ts b/src/renderChildren/InputFieldMDRC.ts index 7e1a883c..a904d4ba 100644 --- a/src/renderChildren/InputFieldMDRC.ts +++ b/src/renderChildren/InputFieldMDRC.ts @@ -67,7 +67,6 @@ export class InputFieldMDRC extends AbstractMDRC implements IInputFieldBase { private shouldAddCardContainer(): boolean { const containerInputFieldType = this.inputFieldDeclaration.inputFieldType === InputFieldType.SELECT || - this.inputFieldDeclaration.inputFieldType === InputFieldType.MULTI_SELECT_DEPRECATED || this.inputFieldDeclaration.inputFieldType === InputFieldType.MULTI_SELECT || this.inputFieldDeclaration.inputFieldType === InputFieldType.LIST; diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 01ca3754..b44521be 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -69,6 +69,7 @@ export interface MetaBindPluginSettings { enableJs: boolean; viewFieldDisplayNullAsEmpty: boolean; enableSyntaxHighlighting: boolean; + enableEditorRightClickMenu: boolean; inputFieldTemplates: InputFieldTemplate[]; excludedFolders: string[]; @@ -94,6 +95,7 @@ export const DEFAULT_SETTINGS: MetaBindPluginSettings = { enableJs: false, viewFieldDisplayNullAsEmpty: false, enableSyntaxHighlighting: true, + enableEditorRightClickMenu: true, inputFieldTemplates: [], excludedFolders: ['templates'], diff --git a/src/settings/SettingsTab.ts b/src/settings/SettingsTab.ts index 67634c5b..25723128 100644 --- a/src/settings/SettingsTab.ts +++ b/src/settings/SettingsTab.ts @@ -74,6 +74,17 @@ export class MetaBindSettingTab extends PluginSettingTab { }); }); + new Setting(containerEl) + .setName('Enable Editor Right Click Menu') + .setDesc(`Enable a meta bind menu section in the editor right click menu. RESTART REQUIRED.`) + .addToggle(cb => { + cb.setValue(this.plugin.settings.enableEditorRightClickMenu); + cb.onChange(data => { + this.plugin.settings.enableEditorRightClickMenu = data; + void this.plugin.saveSettings(); + }); + }); + new Setting(containerEl) .setName('Input Field Templates') .setDesc( diff --git a/src/utils/ZodUtils.ts b/src/utils/ZodUtils.ts index 0d709b99..886b5e79 100644 --- a/src/utils/ZodUtils.ts +++ b/src/utils/ZodUtils.ts @@ -1,4 +1,7 @@ import { type RefinementCtx, z } from 'zod'; +import { type Tuple } from './Utils'; +import { ErrorLevel, MetaBindInternalError } from './errors/MetaBindErrors'; +import { fromZodError } from 'zod-validation-error'; export function oneOf< A, @@ -26,3 +29,17 @@ export function schemaForType(): >(arg: S) = return arg; }; } + +export type ZodifyTuple> = { [P in keyof T]: z.ZodType }; + +export function validateArgs>(validator: z.ZodTuple>, args: T): void { + const result = validator.safeParse(args); + + if (!result.success) { + throw new MetaBindInternalError({ + errorLevel: ErrorLevel.CRITICAL, + effect: 'invalid arguments supplied to function', + cause: fromZodError(result.error), + }); + } +} diff --git a/src/utils/errors/ErrorCollectionComponent.svelte b/src/utils/errors/ErrorCollectionComponent.svelte index 3adcef8f..73bb2123 100644 --- a/src/utils/errors/ErrorCollectionComponent.svelte +++ b/src/utils/errors/ErrorCollectionComponent.svelte @@ -10,7 +10,7 @@ {/if} {#if settings.code} -

{settings.code}

+
{settings.code}
{/if} {#if settings.errorCollection.hasErrors()} diff --git a/src/utils/errors/ErrorCollectionViewModal.ts b/src/utils/errors/ErrorCollectionViewModal.ts index d89a705d..840b28ae 100644 --- a/src/utils/errors/ErrorCollectionViewModal.ts +++ b/src/utils/errors/ErrorCollectionViewModal.ts @@ -20,7 +20,7 @@ export class ErrorCollectionViewModal extends Modal { } public onOpen(): void { - this.modalEl.addClass('mb-error-collection-modal'); + this.modalEl.addClass('mb-error-collection-modal', 'markdown-rendered'); this.titleEl.innerText = 'Meta Bind Error Overview'; this.component = new ErrorCollectionComponent({ diff --git a/src/utils/errors/ErrorIndicatorComponent.svelte b/src/utils/errors/ErrorIndicatorComponent.svelte index b7fa5559..b1666e33 100644 --- a/src/utils/errors/ErrorIndicatorComponent.svelte +++ b/src/utils/errors/ErrorIndicatorComponent.svelte @@ -5,12 +5,11 @@ export let app: App; export let errorCollection: ErrorCollection; - export let declaration: string; + export let declaration: string | undefined; function openModal() { const modal = new ErrorCollectionViewModal(app, { errorCollection: errorCollection, - declaration: declaration, errorText: 'Errors caused the creation of the field to fail. Sometimes one error only occurs because of another.', warningText: diff --git a/tests/parserTests/InputFieldParser.test.ts b/tests/parserTests/InputFieldParser.test.ts index eae62dec..68345218 100644 --- a/tests/parserTests/InputFieldParser.test.ts +++ b/tests/parserTests/InputFieldParser.test.ts @@ -105,31 +105,9 @@ describe('should not error or warn cases', () => { }); }); -describe('should warn on deprecation', () => { - test('INPUT[multi_select]', () => { - const input = 'INPUT[multi_select]'; - const res = parser.parseString(input, TEST_FILE, undefined); - - expect(res.errorCollection.hasWarnings()).toBe(true); - expect(res.errorCollection.hasErrors()).toBe(false); - }); - - test('INPUT[date_picker]', () => { - const input = 'INPUT[date_picker]'; - const res = parser.parseString(input, TEST_FILE, undefined); - - expect(res.errorCollection.hasWarnings()).toBe(true); - expect(res.errorCollection.hasErrors()).toBe(false); - }); - - test('INPUT[text_area]', () => { - const input = 'INPUT[text_area]'; - const res = parser.parseString(input, TEST_FILE, undefined); - - expect(res.errorCollection.hasWarnings()).toBe(true); - expect(res.errorCollection.hasErrors()).toBe(false); - }); -}); +// describe('should warn on deprecation', () => { +// +// }); describe('should warn on invalid argument', () => { test('INPUT[text(invalidArgument)]', () => {