From 1834bf5f6cdb4833d8a332abeda721deabfd5bf6 Mon Sep 17 00:00:00 2001 From: Danielo Rodriguez Date: Tue, 2 Jan 2024 09:03:02 +0100 Subject: [PATCH 1/4] feat: tags input allow any value to be selected, even if it does not exist yet --- src/FormModal.ts | 1 + src/suggesters/MultiSuggest.ts | 19 +++++++++++++++---- src/views/components/MultiSelect.svelte | 2 ++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/FormModal.ts b/src/FormModal.ts index 44df7f79..768a6ba2 100644 --- a/src/FormModal.ts +++ b/src/FormModal.ts @@ -195,6 +195,7 @@ export class FormModal extends Modal { setting: fieldBase, errors: fieldStore.errors, app: this.app, + allowUnknownValues: true, }, }), ); diff --git a/src/suggesters/MultiSuggest.ts b/src/suggesters/MultiSuggest.ts index 43e51bc0..7eb6c9a2 100644 --- a/src/suggesters/MultiSuggest.ts +++ b/src/suggesters/MultiSuggest.ts @@ -1,17 +1,27 @@ -import { AbstractInputSuggest, App } from 'obsidian' +import { AbstractInputSuggest, App } from "obsidian"; export class MultiSuggest extends AbstractInputSuggest { content: Set; - constructor(private inputEl: HTMLInputElement, content: Set, private onSelectCb: (value: string) => void, app: App) { + constructor( + private inputEl: HTMLInputElement, + content: Set, + private onSelectCb: (value: string) => void, + app: App, + private allowUnknownValues = false, + ) { super(app, inputEl); this.content = content; } getSuggestions(inputStr: string): string[] { const lowerCaseInputStr = inputStr.toLocaleLowerCase(); - return [...this.content].filter((content) => - content.toLocaleLowerCase().contains(lowerCaseInputStr) + const candidates = + this.allowUnknownValues && inputStr !== "" + ? [...this.content, inputStr] + : Array.from(this.content); + return candidates.filter((content) => + content.toLocaleLowerCase().contains(lowerCaseInputStr), ); } @@ -20,6 +30,7 @@ export class MultiSuggest extends AbstractInputSuggest { } selectSuggestion(content: string, evt: MouseEvent | KeyboardEvent): void { + console.log("selectSuggestion", content); this.onSelectCb(content); this.inputEl.value = ""; this.close(); diff --git a/src/views/components/MultiSelect.svelte b/src/views/components/MultiSelect.svelte index 3e0c534b..e407caa7 100644 --- a/src/views/components/MultiSelect.svelte +++ b/src/views/components/MultiSelect.svelte @@ -7,6 +7,7 @@ export let availableOptions: string[] = []; export let errors: Readable; export let values: Writable; + export let allowUnknownValues: boolean = false; // We take the setting to make it consistent with the other input components export let setting: Setting; export let app: App; @@ -27,6 +28,7 @@ values.update((x) => [...x, selected]); }, app, + allowUnknownValues, ); } function removeValue(value: string) { From fa9da3d5197a13a14a9ed5be819f1f611736677b Mon Sep 17 00:00:00 2001 From: Danielo Rodriguez Date: Tue, 2 Jan 2024 10:51:14 +0100 Subject: [PATCH 2/4] feat: allow any value in multi-select dataview fixes #54 --- src/FormModal.ts | 27 +++-- src/core/InputDefinitionSchema.ts | 8 +- src/core/formDefinition.ts | 10 +- src/exampleModalDefinition.ts | 6 +- src/suggesters/MultiSuggest.ts | 1 - src/views/FormBuilder.svelte | 98 ++++++------------- .../components/InputBuilderSelect.svelte | 37 ++++--- 7 files changed, 83 insertions(+), 104 deletions(-) diff --git a/src/FormModal.ts b/src/FormModal.ts index 768a6ba2..15ba8e58 100644 --- a/src/FormModal.ts +++ b/src/FormModal.ts @@ -18,7 +18,10 @@ import { FolderSuggest } from "./suggesters/suggestFolder"; export type SubmitFn = (formResult: FormResult) => void; -const notify = throttle((msg: string) => log_notice("⚠️ The form has errors ⚠️", msg, "notice-warning"), 2000); +const notify = throttle( + (msg: string) => log_notice("⚠️ The form has errors ⚠️", msg, "notice-warning"), + 2000, +); const notifyError = (title: string) => throttle((msg: string) => log_notice(`🚨 ${title} 🚨`, msg, "notice-error"), 2000); @@ -34,7 +37,10 @@ export class FormModal extends Modal { options?: FormOptions, ) { super(app); - this.initialFormValues = formDataFromFormDefaults(modalDefinition.fields, options?.values ?? {}); + this.initialFormValues = formDataFromFormDefaults( + modalDefinition.fields, + options?.values ?? {}, + ); this.formEngine = makeFormEngine((result) => { this.onSubmit(FormResult.make(result, "ok")); this.close(); @@ -46,7 +52,8 @@ export class FormModal extends Modal { const { contentEl } = this; // This class is very important for scoped styles contentEl.addClass("modal-form"); - 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) @@ -155,11 +162,13 @@ export class FormModal extends Modal { }); case "multiselect": { const source = fieldInput.source; + const allowUnknownValues = + source === "dataview" ? fieldInput.allowUnknownValues : false; const options = source == "fixed" ? fieldInput.multi_select_options : source == "notes" - ? pipe( + ? pipe( get_tfiles_from_folder(fieldInput.folder, this.app), E.map(A.map((file) => file.basename)), E.getOrElse((err) => { @@ -167,7 +176,10 @@ export class FormModal extends Modal { return [] as string[]; }), ) - : executeSandboxedDvQuery(sandboxedDvQuery(fieldInput.query), this.app); + : executeSandboxedDvQuery( + sandboxedDvQuery(fieldInput.query), + this.app, + ); fieldStore.value.set(initialValue ?? []); this.svelteComponents.push( new MultiSelect({ @@ -178,13 +190,16 @@ export class FormModal extends Modal { errors: fieldStore.errors, setting: fieldBase, app: this.app, + allowUnknownValues, }, }), ); return; } case "tag": { - const options = Object.keys(this.app.metadataCache.getTags()).map((tag) => tag.slice(1)); // remove the # + const options = Object.keys(this.app.metadataCache.getTags()).map((tag) => + tag.slice(1), + ); // remove the # fieldStore.value.set(initialValue ?? []); this.svelteComponents.push( new MultiSelect({ diff --git a/src/core/InputDefinitionSchema.ts b/src/core/InputDefinitionSchema.ts index d38b8ca1..a330ac0d 100644 --- a/src/core/InputDefinitionSchema.ts +++ b/src/core/InputDefinitionSchema.ts @@ -13,6 +13,7 @@ import { BaseSchema, enumType, Output, + boolean, } from "valibot"; /** @@ -92,7 +93,9 @@ const MultiSelectQuerySchema = object({ type: literal("multiselect"), source: literal("dataview"), query: nonEmptyString("dataview query"), + allowUnknownValues: optional(boolean(), false), }); + export const MultiselectSchema = union([ MultiSelectNotesSchema, MultiSelectFixedSchema, @@ -120,10 +123,7 @@ export const InputTypeSchema = union([ DocumentBlock, ]); -export const InputTypeToParserMap: Record< - AllFieldTypes, - ParsingFn -> = { +export const InputTypeToParserMap: Record> = { number: parseC(InputBasicSchema), text: parseC(InputBasicSchema), email: parseC(InputBasicSchema), diff --git a/src/core/formDefinition.ts b/src/core/formDefinition.ts index 4fad3a66..5f1c30c3 100644 --- a/src/core/formDefinition.ts +++ b/src/core/formDefinition.ts @@ -58,9 +58,7 @@ 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 { @@ -103,6 +101,7 @@ export type EditableInput = { options?: { value: string; label: string }[]; multi_select_options?: string[]; query?: string; + allowUnknownValues?: boolean; }; export type EditableFormDefinition = FormDefinition & { @@ -157,10 +156,7 @@ export function isValidFormDefinition(input: unknown): input is FormDefinition { return true; } -export function duplicateForm( - formName: string, - forms: (FormDefinition | MigrationError)[], -) { +export function duplicateForm(formName: string, forms: (FormDefinition | MigrationError)[]) { return pipe( forms, A.findFirstMap((f) => { diff --git a/src/exampleModalDefinition.ts b/src/exampleModalDefinition.ts index 91784776..2a0ebfc6 100644 --- a/src/exampleModalDefinition.ts +++ b/src/exampleModalDefinition.ts @@ -81,12 +81,13 @@ export const exampleModalDefinition: FormDefinition = { type: "multiselect", source: "dataview", query: 'dv.pages("#person").map(p => p.file.name)', + allowUnknownValues: true, }, }, { name: "best_fried", label: "Best friend", - description: "Pick one", + description: "Select of type note from a folder", input: { type: "select", source: "notes", @@ -96,8 +97,7 @@ export const exampleModalDefinition: FormDefinition = { { name: "dataview_example", label: "Dataview example", - description: - "Only people matching the dataview query will be shown", + 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)', diff --git a/src/suggesters/MultiSuggest.ts b/src/suggesters/MultiSuggest.ts index 7eb6c9a2..982c89bd 100644 --- a/src/suggesters/MultiSuggest.ts +++ b/src/suggesters/MultiSuggest.ts @@ -30,7 +30,6 @@ export class MultiSuggest extends AbstractInputSuggest { } selectSuggestion(content: string, evt: MouseEvent | KeyboardEvent): void { - console.log("selectSuggestion", content); this.onSelectCb(content); this.inputEl.value = ""; this.close(); diff --git a/src/views/FormBuilder.svelte b/src/views/FormBuilder.svelte index 3f612e07..45e220c3 100644 --- a/src/views/FormBuilder.svelte +++ b/src/views/FormBuilder.svelte @@ -18,10 +18,7 @@ import { pipe } from "fp-ts/lib/function"; import { A } from "@std"; import Tabs from "./components/Tabs.svelte"; - import { - ParsedTemplate, - parsedTemplateToString, - } from "src/core/template/templateParser"; + import { ParsedTemplate, parsedTemplateToString } from "src/core/template/templateParser"; import InputBuilderDocumentBlock from "./components/InputBuilderDocumentBlock.svelte"; export let definition: EditableFormDefinition = { @@ -145,9 +142,7 @@ {fieldNames} {saveTemplate} templateString={definition.template - ? parsedTemplateToString( - definition.template.parsedTemplate, - ) + ? parsedTemplateToString(definition.template.parsedTemplate) : ""} /> @@ -156,20 +151,13 @@
This name will identify this form uniquely, and will be - the value you need to provide when calling the method - openFormThis name will identify this form uniquely, and will be the value you need + to provide when calling the method openForm - + This is the title that will be shown in the modal when - the form is visibleThis is the title that will be shown in the modal when the form is visible - In case you want to add a class name to the modal form - to customize itCustom class NameIn case you want to add a class name to the modal form to customize itAdd more fields - Preview - Save and close - Cancel {#if errors.length > 0}

- Form is invalid, check - the following: + Form is invalid, check the following:

    {#each errors as error} @@ -251,8 +229,7 @@ {@const delete_id = `delete_${index}`}
    @@ -274,14 +251,8 @@
    {#if ["text", "email", "tel", "number", "note", "tag", "dataview", "multiselect"].includes(field.input.type)} - - + + {/if}
    @@ -289,8 +260,7 @@ for={delete_id} style:visibility={"hidden"} style:overflow={"hidden"} - style:white-space={"nowrap"} - >delete {index}delete {index}
    @@ -307,14 +277,9 @@
    - {#each Object.entries(InputTypeReadable) as type} - + {/each}
    @@ -328,16 +293,17 @@ bind:folder={field.input.folder} notifyChange={onChange} is_multi={false} + allowUnknownValues={false} {app} /> {:else if field.input.type === "multiselect"} - {:else if field.input.type === "document_block"} + {:else if field.input.type === "document_block"}