diff --git a/src/FormModal.ts b/src/FormModal.ts index ff39980b..54d52489 100644 --- a/src/FormModal.ts +++ b/src/FormModal.ts @@ -1,13 +1,19 @@ import { App, Modal, Platform, Setting } from "obsidian"; import MultiSelect from "./views/components/MultiSelect.svelte"; -import FormResult, { formDataFromFormOptions, type ModalFormData } from "./core/FormResult"; +import FormResult, { + formDataFromFormOptions, + type ModalFormData, +} from "./core/FormResult"; import { exhaustiveGuard } from "./safety"; import { get_tfiles_from_folder } from "./utils/files"; import type { FormDefinition, FormOptions } from "./core/formDefinition"; import { FileSuggest } from "./suggesters/suggestFile"; import { DataviewSuggest } from "./suggesters/suggestFromDataview"; import { SvelteComponent } from "svelte"; -import { executeSandboxedDvQuery, sandboxedDvQuery } from "./suggesters/SafeDataviewQuery"; +import { + executeSandboxedDvQuery, + sandboxedDvQuery, +} from "./suggesters/SafeDataviewQuery"; import { A, E, pipe } from "@std"; import { log_error } from "./utils/Log"; @@ -16,33 +22,41 @@ export type SubmitFn = (formResult: FormResult) => void; export class FormModal extends Modal { formResult: ModalFormData; svelteComponents: SvelteComponent[] = []; - constructor(app: App, private modalDefinition: FormDefinition, private onSubmit: SubmitFn, options?: FormOptions) { + constructor( + app: App, + private modalDefinition: FormDefinition, + private onSubmit: SubmitFn, + options?: FormOptions, + ) { super(app); this.formResult = {}; if (options?.values) { - this.formResult = formDataFromFormOptions(options.values) + this.formResult = formDataFromFormOptions(options.values); } } onOpen() { const { contentEl } = this; - if (this.modalDefinition.customClassname) contentEl.addClass(this.modalDefinition.customClassname); + if (this.modalDefinition.customClassname) + contentEl.addClass(this.modalDefinition.customClassname); contentEl.createEl("h1", { text: this.modalDefinition.title }); this.modalDefinition.fields.forEach((definition) => { const fieldBase = new Setting(contentEl) .setName(definition.label || definition.name) .setDesc(definition.description); // This intermediary constants are necessary so typescript can narrow down the proper types. - // without them, you will have to use the whole access path (definition.input.folder), + // without them, you will have to use the whole access path (definition.input.folder), // and it is no specific enough when you use it in a switch statement. const fieldInput = definition.input; const type = fieldInput.type; const initialValue = this.formResult[definition.name]; switch (type) { case "textarea": - fieldBase.setClass('modal-form-textarea') + fieldBase.setClass("modal-form-textarea"); return fieldBase.addTextArea((textEl) => { - if (typeof initialValue === 'string') { textEl.setValue(initialValue); } + if (typeof initialValue === "string") { + textEl.setValue(initialValue); + } textEl.onChange((value) => { this.formResult[definition.name] = value; }); @@ -52,22 +66,23 @@ export class FormModal extends Modal { else if (Platform.isDesktopApp) { textEl.inputEl.rows = 10; } - }) + }); case "email": case "tel": case "text": return fieldBase.addText((text) => { text.inputEl.type = type; - initialValue !== undefined && text.setValue(String(initialValue)); + initialValue !== undefined && + text.setValue(String(initialValue)); return text.onChange(async (value) => { this.formResult[definition.name] = value; }); - } - ); + }); case "number": return fieldBase.addText((text) => { - text.inputEl.type = 'number'; - initialValue !== undefined && text.setValue(String(initialValue)); + text.inputEl.type = "number"; + initialValue !== undefined && + text.setValue(String(initialValue)); text.onChange(async (value) => { if (value !== "") { this.formResult[definition.name] = @@ -78,7 +93,8 @@ export class FormModal extends Modal { case "date": return fieldBase.addText((text) => { text.inputEl.type = "date"; - initialValue !== undefined && text.setValue(String(initialValue)); + initialValue !== undefined && + text.setValue(String(initialValue)); text.onChange(async (value) => { this.formResult[definition.name] = value; }); @@ -86,7 +102,8 @@ export class FormModal extends Modal { case "time": return fieldBase.addText((text) => { text.inputEl.type = "time"; - initialValue !== undefined && text.setValue(String(initialValue)); + initialValue !== undefined && + text.setValue(String(initialValue)); text.onChange(async (value) => { this.formResult[definition.name] = value; }); @@ -94,7 +111,8 @@ export class FormModal extends Modal { case "datetime": return fieldBase.addText((text) => { text.inputEl.type = "datetime-local"; - initialValue !== undefined && text.setValue(String(initialValue)); + initialValue !== undefined && + text.setValue(String(initialValue)); text.onChange(async (value) => { this.formResult[definition.name] = value; }); @@ -106,18 +124,22 @@ export class FormModal extends Modal { return toggle.onChange(async (value) => { this.formResult[definition.name] = value; }); - } - ); + }); case "note": return fieldBase.addText((element) => { - new FileSuggest(this.app, element.inputEl, { - renderSuggestion(file) { - return file.basename; - }, - selectSuggestion(file) { - return file.basename; + new FileSuggest( + this.app, + element.inputEl, + { + renderSuggestion(file) { + return file.basename; + }, + selectSuggestion(file) { + return file.basename; + }, }, - }, fieldInput.folder); + fieldInput.folder, + ); element.onChange(async (value) => { this.formResult[definition.name] = value; }); @@ -126,8 +148,8 @@ export class FormModal extends Modal { return fieldBase.addSlider((slider) => { slider.setLimits(fieldInput.min, fieldInput.max, 1); slider.setDynamicTooltip(); - if (typeof initialValue === 'number') { - slider.setValue(initialValue) + if (typeof initialValue === "number") { + slider.setValue(initialValue); } else { slider.setValue(fieldInput.min); } @@ -135,57 +157,87 @@ export class FormModal extends Modal { this.formResult[definition.name] = value; }); }); - case 'multiselect': - { - this.formResult[definition.name] = this.formResult[definition.name] || [] - const source = fieldInput.source; - const options = source == 'fixed' + case "multiselect": { + this.formResult[definition.name] = + this.formResult[definition.name] || []; + const source = fieldInput.source; + const options = + source == "fixed" ? fieldInput.multi_select_options - : source == 'notes' + : source == "notes" ? pipe( - get_tfiles_from_folder(fieldInput.folder, this.app), + get_tfiles_from_folder( + fieldInput.folder, + this.app, + ), E.map(A.map((file) => file.basename)), E.getOrElse((err) => { - log_error(err) + log_error(err); return [] as string[]; - }) + }), ) - : executeSandboxedDvQuery(sandboxedDvQuery(fieldInput.query), this.app) - this.svelteComponents.push(new MultiSelect({ + : executeSandboxedDvQuery( + sandboxedDvQuery(fieldInput.query), + this.app, + ); + this.svelteComponents.push( + new MultiSelect({ target: fieldBase.controlEl, props: { - selectedVales: this.formResult[definition.name] as string[], + selectedVales: this.formResult[ + definition.name + ] as string[], availableOptions: options, setting: fieldBase, app: this.app, - } - })) - return; - } - case "dataview": - { - const query = fieldInput.query; - return fieldBase.addText((element) => { - new DataviewSuggest(element.inputEl, query, this.app); - element.onChange(async (value) => { - this.formResult[definition.name] = value; - }); + }, + }), + ); + return; + } + case "tag": { + const options = Object.keys( + this.app.metadataCache.getTags(), + ); + this.formResult[definition.name] = this.formResult[definition.name] || []; + this.svelteComponents.push( + new MultiSelect({ + target: fieldBase.controlEl, + props: { + selectedVales: this.formResult[ + definition.name + ] as string[], + availableOptions: options, + setting: fieldBase, + app: this.app, + }, + }), + ); + return; + } + case "dataview": { + const query = fieldInput.query; + return fieldBase.addText((element) => { + new DataviewSuggest(element.inputEl, query, this.app); + element.onChange(async (value) => { + this.formResult[definition.name] = value; }); - } + }); + } case "select": { const source = fieldInput.source; switch (source) { case "fixed": return fieldBase.addDropdown((element) => { - fieldInput.options.forEach( - ( - option - ) => { - element.addOption(option.value, option.label); - }, - ); - this.formResult[definition.name] = element.getValue(); + fieldInput.options.forEach((option) => { + element.addOption( + option.value, + option.label, + ); + }); + this.formResult[definition.name] = + element.getValue(); element.onChange(async (value) => { this.formResult[definition.name] = value; @@ -194,29 +246,35 @@ export class FormModal extends Modal { case "notes": return fieldBase.addDropdown((element) => { - const files = get_tfiles_from_folder(fieldInput.folder, this.app); + const files = get_tfiles_from_folder( + fieldInput.folder, + this.app, + ); pipe( files, - E.map((files) => files.reduce( - ( - acc: Record, - option - ) => { - acc[option.basename] = - option.basename; - return acc; - }, - {} - )), + E.map((files) => + files.reduce( + ( + acc: Record, + option, + ) => { + acc[option.basename] = + option.basename; + return acc; + }, + {}, + ), + ), E.mapLeft((err) => { log_error(err); return err; }), E.map((options) => { - element.addOptions(options) - }) + element.addOptions(options); + }), ); - this.formResult[definition.name] = element.getValue(); + this.formResult[definition.name] = + element.getValue(); element.onChange(async (value) => { this.formResult[definition.name] = value; @@ -235,14 +293,11 @@ export class FormModal extends Modal { const submit = () => { this.onSubmit(new FormResult(this.formResult, "ok")); this.close(); - } + }; new Setting(contentEl).addButton((btn) => - btn - .setButtonText("Submit") - .setCta() - .onClick(submit) - ) + btn.setButtonText("Submit").setCta().onClick(submit), + ); const submitEnterCallback = (evt: KeyboardEvent) => { if ((evt.ctrlKey || evt.metaKey) && evt.key === "Enter") { @@ -251,12 +306,12 @@ export class FormModal extends Modal { } }; - contentEl.addEventListener("keydown", submitEnterCallback) + contentEl.addEventListener("keydown", submitEnterCallback); } onClose() { const { contentEl } = this; - this.svelteComponents.forEach((component) => component.$destroy()) + this.svelteComponents.forEach((component) => component.$destroy()); contentEl.empty(); this.formResult = {}; } diff --git a/src/core/formDefinition.ts b/src/core/formDefinition.ts index ce2e7b03..2bf4efef 100644 --- a/src/core/formDefinition.ts +++ b/src/core/formDefinition.ts @@ -1,5 +1,19 @@ import { type Output, is, safeParse } from "valibot"; -import { SelectFromNotesSchema, InputSliderSchema, InputNoteFromFolderSchema, InputDataviewSourceSchema, InputSelectFixedSchema, InputBasicSchema, MultiselectSchema, InputTypeSchema, FieldDefinitionSchema, FormDefinitionLatestSchema, FieldListSchema, FormDefinitionBasicSchema, MigrationError } from "./formDefinitionSchema"; +import { + SelectFromNotesSchema, + InputSliderSchema, + InputNoteFromFolderSchema, + InputDataviewSourceSchema, + InputSelectFixedSchema, + InputBasicSchema, + MultiselectSchema, + InputTypeSchema, + FieldDefinitionSchema, + FormDefinitionLatestSchema, + FieldListSchema, + FormDefinitionBasicSchema, + MigrationError, +} from "./formDefinitionSchema"; import { A, O, pipe } from "@std"; //=========== Types derived from schemas type selectFromNotes = Output; @@ -14,6 +28,7 @@ type inputType = Output; export const FieldTypeReadable: Record = { text: "Text", number: "Number", + tag: "Tags", email: "Email", tel: "Phone", date: "Date", @@ -21,11 +36,11 @@ export const FieldTypeReadable: Record = { datetime: "DateTime", textarea: "Text area", toggle: "Toggle", - "note": "Note", - "slider": "Slider", - "select": "Select", - "dataview": "Dataview", - "multiselect": "Multiselect", + note: "Note", + slider: "Slider", + select: "Select", + dataview: "Dataview", + multiselect: "Multiselect", } as const; export function isDataViewSource(input: unknown): input is inputDataviewSource { @@ -39,14 +54,16 @@ export function isSelectFromNotes(input: unknown): input is selectFromNotes { return is(SelectFromNotesSchema, input); } -export function isInputNoteFromFolder(input: unknown): input is inputNoteFromFolder { +export function isInputNoteFromFolder( + input: unknown, +): input is inputNoteFromFolder { return is(InputNoteFromFolderSchema, input); } export function isInputSelectFixed(input: unknown): input is inputSelectFixed { return is(InputSelectFixedSchema, input); } -export type AllFieldTypes = inputType['type'] +export type AllFieldTypes = inputType["type"]; export type FieldDefinition = Output; /** @@ -56,15 +73,16 @@ export type FormDefinition = Output; export type FormOptions = { values?: Record; -} +}; -type KeyOfUnion = T extends unknown ? keyof T : never -type PickUnion> = - T extends unknown - ? K & keyof T extends never ? never : Pick - : never +type KeyOfUnion = T extends unknown ? keyof T : never; +type PickUnion> = T extends unknown + ? K & keyof T extends never + ? never + : Pick + : never; -export type AllSources = PickUnion['source'] +export type AllSources = PickUnion["source"]; // When an input is in edit state, it is represented by this type. // It has all the possible values, and then you need to narrow it down @@ -105,36 +123,37 @@ export function isInputTypeValid(input: unknown): input is inputType { return is(InputTypeSchema, input); } - export function validateFields(fields: unknown) { const result = safeParse(FieldListSchema, fields); if (result.success) { - return [] + return []; } - console.error('Fields issues', result.issues) - return result.issues.map((issue) => - ({ - message: issue.message, path: issue.path?.map((item) => item.key).join('.'), - index: issue.path?.[0]?.key ?? 0 - }) - ); + console.error("Fields issues", result.issues); + return result.issues.map((issue) => ({ + message: issue.message, + path: issue.path?.map((item) => item.key).join("."), + index: issue.path?.[0]?.key ?? 0, + })); } export function isValidFormDefinition(input: unknown): input is FormDefinition { if (!is(FormDefinitionBasicSchema, input)) { return false; } - console.log('basic is valid'); + console.log("basic is valid"); const fieldsAreValid = is(FieldListSchema, input.fields); if (!fieldsAreValid) { return false; } - console.log('fields are valid'); + console.log("fields are valid"); return true; } -export function duplicateForm(formName: string, forms: (FormDefinition | MigrationError)[]) { +export function duplicateForm( + formName: string, + forms: (FormDefinition | MigrationError)[], +) { return pipe( forms, A.findFirstMap((f) => { @@ -147,10 +166,10 @@ export function duplicateForm(formName: string, forms: (FormDefinition | Migrati return O.none; }), O.map((f) => { - let newName = f.name + '-copy'; + let newName = f.name + "-copy"; let i = 1; while (forms.some((f) => f.name === newName)) { - newName = f.name + '-copy-' + i; + newName = f.name + "-copy-" + i; i++; } return { ...f, name: newName }; @@ -158,6 +177,6 @@ export function duplicateForm(formName: string, forms: (FormDefinition | Migrati O.map((f) => { return [...forms, f]; }), - O.getOrElse(() => forms) - ) + O.getOrElse(() => forms), + ); } diff --git a/src/core/formDefinitionSchema.ts b/src/core/formDefinitionSchema.ts index dea26910..2b7efb7f 100644 --- a/src/core/formDefinitionSchema.ts +++ b/src/core/formDefinitionSchema.ts @@ -1,6 +1,24 @@ import * as E from "fp-ts/Either"; import { pipe, parse, trySchemas, ParsingFn, parseC } from "@std"; -import { object, number, literal, type Output, is, array, string, union, optional, minLength, toTrimmed, merge, unknown, ValiError, BaseSchema, enumType, passthrough } from "valibot"; +import { + object, + number, + literal, + type Output, + is, + array, + string, + union, + optional, + minLength, + toTrimmed, + merge, + unknown, + ValiError, + BaseSchema, + enumType, + passthrough, +} from "valibot"; import { AllFieldTypes, FormDefinition } from "./formDefinition"; import { findFieldErrors } from "./findInputDefinitionSchema"; @@ -10,43 +28,90 @@ import { findFieldErrors } from "./findInputDefinitionSchema"; * Here are the types, validators, rules etc. */ function nonEmptyString(name: string) { - return string(`${name} should be a string`, [toTrimmed(), minLength(1, `${name} should not be empty`)]); + return string(`${name} should be a string`, [ + toTrimmed(), + minLength(1, `${name} should not be empty`), + ]); } -const InputBasicTypeSchema = enumType(["text", "number", "date", "time", "datetime", "textarea", "toggle", "email", "tel"]); +const InputBasicTypeSchema = enumType([ + "text", + "number", + "date", + "time", + "datetime", + "textarea", + "toggle", + "email", + "tel", +]); //=========== Schema definitions -export const SelectFromNotesSchema = object({ type: literal("select"), source: literal("notes"), folder: nonEmptyString('folder name') }); -export const InputSliderSchema = object({ type: literal("slider"), min: number(), max: number() }); -export const InputNoteFromFolderSchema = object({ type: literal("note"), folder: nonEmptyString('folder name') }); -export const InputDataviewSourceSchema = object({ type: literal("dataview"), query: nonEmptyString('dataview query') }); +export const SelectFromNotesSchema = object({ + type: literal("select"), + source: literal("notes"), + folder: nonEmptyString("folder name"), +}); +export const InputTagSchema = object({ + type: literal("tag"), + exclude: optional(string()), // This should be a regex string +}); +export const InputSliderSchema = object({ + type: literal("slider"), + min: number(), + max: number(), +}); +export const InputNoteFromFolderSchema = object({ + type: literal("note"), + folder: nonEmptyString("folder name"), +}); +export const InputDataviewSourceSchema = object({ + type: literal("dataview"), + query: nonEmptyString("dataview query"), +}); export const InputBasicSchema = object({ type: InputBasicTypeSchema }); export const InputSelectFixedSchema = object({ type: literal("select"), source: literal("fixed"), - options: array(object({ - value: string([toTrimmed()]), label: string() - })) + options: array( + object({ + value: string([toTrimmed()]), + label: string(), + }), + ), }); const MultiSelectNotesSchema = object({ - type: literal("multiselect"), source: literal("notes"), - folder: nonEmptyString('multi select source folder') + type: literal("multiselect"), + source: literal("notes"), + folder: nonEmptyString("multi select source folder"), +}); +const MultiSelectFixedSchema = object({ + type: literal("multiselect"), + source: literal("fixed"), + multi_select_options: array(string()), }); -const MultiSelectFixedSchema = object({ type: literal("multiselect"), source: literal("fixed"), multi_select_options: array(string()) }); const MultiSelectQuerySchema = object({ type: literal("multiselect"), source: literal("dataview"), - query: nonEmptyString('dataview query') + query: nonEmptyString("dataview query"), }); -export const MultiselectSchema = union([MultiSelectNotesSchema, MultiSelectFixedSchema, MultiSelectQuerySchema]); +export const MultiselectSchema = union([ + MultiSelectNotesSchema, + MultiSelectFixedSchema, + MultiSelectQuerySchema, +]); export const InputTypeSchema = union([ InputBasicSchema, InputNoteFromFolderSchema, InputSliderSchema, + InputTagSchema, SelectFromNotesSchema, InputDataviewSourceSchema, InputSelectFixedSchema, - MultiselectSchema + MultiselectSchema, ]); -export const InputTypeToParserMap: Record> = { +export const InputTypeToParserMap: Record< + AllFieldTypes, + ParsingFn +> = { number: parseC(InputBasicSchema), text: parseC(InputBasicSchema), email: parseC(InputBasicSchema), @@ -58,28 +123,30 @@ export const InputTypeToParserMap: Record> toggle: parseC(InputBasicSchema), note: parseC(InputNoteFromFolderSchema), slider: parseC(InputSliderSchema), + tag: parseC(InputTagSchema), select: trySchemas([SelectFromNotesSchema, InputSelectFixedSchema]), dataview: parseC(InputDataviewSourceSchema), multiselect: parseC(MultiselectSchema), }; export const FieldDefinitionSchema = object({ - name: nonEmptyString('field name'), + name: nonEmptyString("field name"), label: optional(string()), description: string(), - input: InputTypeSchema + input: InputTypeSchema, }); /** * Only for error reporting purposes */ -export const FieldMinimalSchema = passthrough(merge([ - FieldDefinitionSchema, - object({ input: passthrough(object({ type: string() })) }) -])); +export const FieldMinimalSchema = passthrough( + merge([ + FieldDefinitionSchema, + object({ input: passthrough(object({ type: string() })) }), + ]), +); export type FieldMinimal = Output; - export const FieldListSchema = array(FieldDefinitionSchema); /** * This is the most basic representation of a form definition. @@ -88,18 +155,21 @@ export const FieldListSchema = array(FieldDefinitionSchema); * This is the V0 schema. */ export const FormDefinitionBasicSchema = object({ - title: nonEmptyString('form title'), - name: nonEmptyString('form name'), + title: nonEmptyString("form title"), + name: nonEmptyString("form name"), customClassname: optional(string()), fields: array(unknown()), }); /** * This is the V1 schema. */ -const FormDefinitionV1Schema = merge([FormDefinitionBasicSchema, object({ - version: literal("1"), - fields: FieldListSchema, -})]); +const FormDefinitionV1Schema = merge([ + FormDefinitionBasicSchema, + object({ + version: literal("1"), + fields: FieldListSchema, + }), +]); // This is the latest schema. // Make sure to update this when you add a new version. export const FormDefinitionLatestSchema = FormDefinitionV1Schema; @@ -114,15 +184,18 @@ type FormDefinitionBasic = Output; export class MigrationError { static readonly _tag = "MigrationError" as const; public readonly name: string; - constructor(public form: FormDefinitionBasic, readonly error: ValiError) { + constructor( + public form: FormDefinitionBasic, + readonly error: ValiError, + ) { this.name = form.name; } toString(): string { return `MigrationError: ${this.error.message} - ${this.error.issues.map((issue) => issue.message).join(', ')}`; + ${this.error.issues.map((issue) => issue.message).join(", ")}`; } - // This allows to store the error in the settings, along with the rest of the forms and + // This allows to store the error in the settings, along with the rest of the forms and // have save all the data in one go transparently. // This is required so we don't lose the form, even if it is invalid toJSON() { @@ -137,16 +210,23 @@ export class MigrationError { */ export class InvalidData { static readonly _tag = "InvalidData" as const; - constructor(public data: unknown, readonly error: ValiError) { } + constructor( + public data: unknown, + readonly error: ValiError, + ) { } toString(): string { - return `InvalidData: ${this.error.issues.map((issue) => issue.message).join(', ')}`; + return `InvalidData: ${this.error.issues + .map((issue) => issue.message) + .join(", ")}`; } } //=========== Migration logic -function fromV0toV1(data: FormDefinitionBasic): MigrationError | FormDefinitionV1 { +function fromV0toV1( + data: FormDefinitionBasic, +): MigrationError | FormDefinitionV1 { return pipe( parse(FormDefinitionV1Schema, { ...data, version: "1" }), - E.getOrElseW((error) => (new MigrationError(data, error))) + E.getOrElseW((error) => new MigrationError(data, error)), ); } /** @@ -154,15 +234,19 @@ function fromV0toV1(data: FormDefinitionBasic): MigrationError | FormDefinitionV * Parses the form definition and migrates it to the latest version in one operation. */ -export function migrateToLatest(data: unknown): E.Either { +export function migrateToLatest( + data: unknown, +): E.Either { return pipe( // first try a quick one with the latest schema parse(FormDefinitionLatestSchema, data, { abortEarly: true }), - E.orElse(() => pipe( - parse(FormDefinitionBasicSchema, data), - E.mapLeft((error) => new InvalidData(data, error)), - E.map(fromV0toV1) - )) + E.orElse(() => + pipe( + parse(FormDefinitionBasicSchema, data), + E.mapLeft((error) => new InvalidData(data, error)), + E.map(fromV0toV1), + ), + ), ); } diff --git a/src/exampleModalDefinition.ts b/src/exampleModalDefinition.ts index 11a9a994..dc4662ad 100644 --- a/src/exampleModalDefinition.ts +++ b/src/exampleModalDefinition.ts @@ -50,49 +50,68 @@ export const exampleModalDefinition: FormDefinition = { name: "multi_example_2", label: "Multi select fixed", description: "Allows to pick many notes from a fixed list", - input: { type: "multiselect", source: "fixed", multi_select_options: ['Android', 'iOS', 'Windows', 'MacOS', 'Linux', 'Solaris', 'MS2'] }, + input: { + type: "multiselect", + source: "fixed", + multi_select_options: [ + "Android", + "iOS", + "Windows", + "MacOS", + "Linux", + "Solaris", + "MS2", + ], + }, }, { name: "multi_select_dataview", label: "Multi select dataview", description: "Allows to pick several values from a dv query", - input: { type: "multiselect", source: "dataview", query: 'dv.pages("#person").map(p => p.file.name)' }, + input: { + type: "multiselect", + source: "dataview", + query: 'dv.pages("#person").map(p => p.file.name)', + }, }, { name: "best_fried", label: "Best friend", description: "Pick one", input: { - type: 'select', - source: 'notes', - folder: 'People' - } + type: "select", + source: "notes", + folder: "People", + }, }, { - name: 'dataview_example', - label: 'Dataview example', - description: 'Only people matching the dataview query will be shown', + name: "dataview_example", + label: "Dataview example", + description: + "Only people matching the dataview query will be shown", input: { - type: 'dataview', - query: 'dv.pages("#person").filter(p => p.age < 30).map(p => p.file.name)' - } + type: "dataview", + query: 'dv.pages("#person").filter(p => p.age < 30).map(p => p.file.name)', + }, }, { name: "friendship_level", label: "Friendship level", description: "How good friends are you?", input: { - type: 'slider', + type: "slider", min: 0, - max: 10 - } + max: 10, + }, }, { name: "favorite_meal", label: "Favorite meal", description: "Pick one option", input: { - type: "select", source: "fixed", options: [ + type: "select", + source: "fixed", + options: [ { value: "pizza", label: "🍕 Pizza" }, { value: "pasta", label: "🍝 Pasta" }, { value: "burger", label: "🍔 Burger" }, @@ -102,8 +121,8 @@ export const exampleModalDefinition: FormDefinition = { { value: "ramen", label: "🍜 Ramen" }, { value: "tacos", label: "🌮 Tacos" }, { value: "fish", label: "🐟 Fish" }, - { value: "chicken", label: "🍗 Chicken" } - ] + { value: "chicken", label: "🍗 Chicken" }, + ], }, }, { @@ -112,7 +131,12 @@ export const exampleModalDefinition: FormDefinition = { description: "Put your thouhts here", input: { type: "textarea", - } - } + }, + }, + { + name: "Tags", + description: "Tags input example", + input: { type: "tag" }, + }, ], }; diff --git a/src/typings/obsidian-ex.d.ts b/src/typings/obsidian-ex.d.ts index 72ba5837..3f6d143b 100644 --- a/src/typings/obsidian-ex.d.ts +++ b/src/typings/obsidian-ex.d.ts @@ -1,33 +1,38 @@ -// https://github.com/blacksmithgu/obsidian-dataview/blob/bb594a27ba1eed130d7c2ab7eff0990578e93f62/src/typings/obsidian-ex.d.ts +// https://github.com/blacksmithgu/obsidian-dataview/blob/bb594a27ba1eed130d7c2ab7eff0990578e93f62/src/typings/obsidian-ex.d.ts import type { DataviewApi } from "api/plugin-api"; import "obsidian"; declare module "obsidian" { - interface MetadataCache { - trigger(...args: Parameters): void; - trigger(name: string, ...data: any[]): void; - } + interface MetadataCache { + trigger(...args: Parameters): void; + trigger(name: string, ...data: unknown[]): void; + getTags(): Record; + } - interface App { - appId?: string; - plugins: { - enabledPlugins: Set; - plugins: { - dataview?: { - api: DataviewApi; - }; - }; - }; - } + interface App { + appId?: string; + plugins: { + enabledPlugins: Set; + plugins: { + dataview?: { + api: DataviewApi; + }; + }; + }; + } - interface Workspace { - /** Sent to rendered dataview components to tell them to possibly refresh */ - on(name: "dataview:refresh-views", callback: () => void, ctx?: any): EventRef; - } + interface Workspace { + /** Sent to rendered dataview components to tell them to possibly refresh */ + on( + name: "dataview:refresh-views", + callback: () => void, + ctx?: unknown, + ): EventRef; + } } declare global { - interface Window { - DataviewAPI?: DataviewApi; - } + interface Window { + DataviewAPI?: DataviewApi; + } }