From 0d5afbd399a6635c77534c3fb0e9b2838ee22040 Mon Sep 17 00:00:00 2001 From: Danielo Rodriguez Date: Sat, 4 Nov 2023 00:52:37 +0100 Subject: [PATCH] fix(input): if folder does not exist, the form does not fail fixes #90 --- src/FormModal.ts | 43 +++++++++++---- src/core/formDefinition.ts | 2 + src/std/index.ts | 3 +- src/suggesters/suggestFile.ts | 12 ++-- src/utils/files.ts | 101 ++++++++++++++++++++++------------ 5 files changed, 105 insertions(+), 56 deletions(-) diff --git a/src/FormModal.ts b/src/FormModal.ts index 71e6e41e..0c41f425 100644 --- a/src/FormModal.ts +++ b/src/FormModal.ts @@ -8,6 +8,9 @@ import { FileSuggest } from "./suggesters/suggestFile"; import { DataviewSuggest } from "./suggesters/suggestFromDataview"; import { SvelteComponent } from "svelte"; import { executeSandboxedDvQuery, sandboxedDvQuery } from "./suggesters/SafeDataviewQuery"; +import { pipe } from "fp-ts/lib/function"; +import { A, E } from "@std"; +import { log_error } from "./utils/Log"; export type SubmitFn = (formResult: FormResult) => void; @@ -136,7 +139,14 @@ export class FormModal extends Modal { const options = source == 'fixed' ? fieldInput.multi_select_options : source == 'notes' - ? get_tfiles_from_folder(fieldInput.folder, this.app).map((file) => file.basename) + ? pipe( + get_tfiles_from_folder(fieldInput.folder, this.app), + E.map(A.map((file) => file.basename)), + E.getOrElse((err) => { + log_error(err) + return [] as string[]; + }) + ) : executeSandboxedDvQuery(sandboxedDvQuery(fieldInput.query), this.app) this.svelteComponents.push(new MultiSelect({ target: fieldBase.controlEl, @@ -182,18 +192,27 @@ export class FormModal extends Modal { case "notes": return fieldBase.addDropdown((element) => { const files = get_tfiles_from_folder(fieldInput.folder, this.app); - const options = files.reduce( - ( - acc: Record, - option - ) => { - acc[option.basename] = - option.basename; - return acc; - }, - {} + pipe( + files, + 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(); element.onChange(async (value) => { this.formResult[definition.name] = diff --git a/src/core/formDefinition.ts b/src/core/formDefinition.ts index 00adefca..9ed0aa79 100644 --- a/src/core/formDefinition.ts +++ b/src/core/formDefinition.ts @@ -86,6 +86,8 @@ export type EditableFormDefinition = { label?: string; description: string; input: EditableInput; + folder?: string; + options?: { value: string; label: string }[]; }[]; }; diff --git a/src/std/index.ts b/src/std/index.ts index c04cbb78..6a576241 100644 --- a/src/std/index.ts +++ b/src/std/index.ts @@ -1,7 +1,7 @@ import { pipe as p, flow as f } from "fp-ts/function"; import { partitionMap, findFirst, findFirstMap, partition, map as mapArr, filter } from "fp-ts/Array"; import { map as mapO, getOrElse as getOrElseOpt, some, none } from 'fp-ts/Option' -import { isLeft, isRight, tryCatchK, map, getOrElse, right, left, mapLeft, Either, bimap, tryCatch, flatMap } from "fp-ts/Either"; +import { isLeft, isRight, tryCatchK, map, getOrElse, fromNullable, right, left, mapLeft, Either, bimap, tryCatch, flatMap } from "fp-ts/Either"; import { BaseSchema, Output, ValiError, parse as parseV } from "valibot"; import { Semigroup, concatAll } from "fp-ts/Semigroup"; import { NonEmptyArray } from "fp-ts/NonEmptyArray"; @@ -30,6 +30,7 @@ export const E = { mapLeft, bimap, flatMap, + fromNullable, } export const O = { diff --git a/src/suggesters/suggestFile.ts b/src/suggesters/suggestFile.ts index 36cfc498..19a87e31 100644 --- a/src/suggesters/suggestFile.ts +++ b/src/suggesters/suggestFile.ts @@ -1,6 +1,6 @@ import { AbstractInputSuggest, App, TAbstractFile, TFile } from "obsidian"; import { get_tfiles_from_folder } from "../utils/files"; -import { tryCatch } from "../utils/Error"; +import { E } from "@std"; // Instead of hardcoding the logic in separate and almost identical classes, // we move this little logic parts into an interface and we can use the samme @@ -10,6 +10,7 @@ export interface FileStrategy { selectSuggestion(file: TFile): string; } + export class FileSuggest extends AbstractInputSuggest { constructor( public app: App, @@ -21,17 +22,14 @@ export class FileSuggest extends AbstractInputSuggest { } getSuggestions(input_str: string): TFile[] { - const all_files = tryCatch( - () => get_tfiles_from_folder(this.folder, this.app), - "The folder does not exist" - ); - if (!all_files) { + const all_files = get_tfiles_from_folder(this.folder, this.app) + if (E.isLeft(all_files)) { return []; } const lower_input_str = input_str.toLowerCase(); - return all_files.filter((file: TAbstractFile) => { + return all_files.right.filter((file: TAbstractFile) => { return ( file instanceof TFile && file.extension === "md" && diff --git a/src/utils/files.ts b/src/utils/files.ts index 48cd5673..1632d97b 100644 --- a/src/utils/files.ts +++ b/src/utils/files.ts @@ -1,47 +1,76 @@ import { App, TAbstractFile, TFile, TFolder, Vault, normalizePath } from "obsidian"; -import { ModalFormError } from "./Error"; - -export function resolve_tfolder(folder_str: string, app: App): TFolder { - folder_str = normalizePath(folder_str); +import { E, Either, pipe } from "@std"; +export class FolderDoesNotExistError extends Error { + static readonly tag = "FolderDoesNotExistError"; +} - const folder = app.vault.getAbstractFileByPath(folder_str); - if (!folder) { - throw new ModalFormError(`Folder "${folder_str}" doesn't exist`); - } - if (!(folder instanceof TFolder)) { - throw new ModalFormError(`${folder_str} is a file, not a folder`); - } +export class NotAFolderError extends Error { + static readonly tag = "NotAFolderError"; + constructor(public file: TAbstractFile) { + super(`File ${file.path} is not a folder`); + } +} - return folder; +export class FileDoesNotExistError extends Error { + static readonly tag = "FileDoesNotExistError"; + static of(file: string) { + return new FileDoesNotExistError(`File "${file}" doesn't exist`); + } +} +export class NotAFileError extends Error { + static readonly tag = "NotAFileError"; + constructor(public file: TAbstractFile) { + super(`File ${file.path} is not a file`); + } } -export function resolve_tfile(file_str: string, app: App): TFile { - file_str = normalizePath(file_str); +type FolderError = FolderDoesNotExistError | NotAFolderError; - const file = app.vault.getAbstractFileByPath(file_str); - if (!file) { - throw new ModalFormError(`File "${file_str}" doesn't exist`); - } - if (!(file instanceof TFile)) { - throw new ModalFormError(`${file_str} is a folder, not a file`); - } +export function resolve_tfolder(folder_str: string, app: App): Either { + folder_str = normalizePath(folder_str); - return file; + return pipe( + app.vault.getAbstractFileByPath(folder_str), + E.fromNullable(new FolderDoesNotExistError(`Folder "${folder_str}" doesn't exist`)), + E.flatMap((file) => { + if (!(file instanceof TFolder)) { + return E.left(new NotAFolderError(file)); + } + return E.right(file); + }) + ); } -export function get_tfiles_from_folder(folder_str: string, app: App): Array { - const folder = resolve_tfolder(folder_str, app); - - const files: Array = []; - Vault.recurseChildren(folder, (file: TAbstractFile) => { - if (file instanceof TFile) { - files.push(file); - } - }); - - files.sort((a, b) => { - return a.basename.localeCompare(b.basename); - }); +export function resolve_tfile(file_str: string, app: App): Either { + return pipe( + normalizePath(file_str), + app.vault.getAbstractFileByPath, + E.fromNullable(FileDoesNotExistError.of(file_str)), + E.flatMap((file) => { + if (!(file instanceof TFile)) { + return E.left(new NotAFileError(file)); + } + return E.right(file); + }) + ) +} - return files; +export function get_tfiles_from_folder(folder_str: string, app: App): Either> { + return pipe( + resolve_tfolder(folder_str, app), + E.flatMap((folder) => { + const files: Array = []; + Vault.recurseChildren(folder, (file: TAbstractFile) => { + if (file instanceof TFile) { + files.push(file); + } + }); + return E.right(files); + }), + E.map((files) => { + return files.sort((a, b) => { + return a.basename.localeCompare(b.basename); + }); + } + )) }