From e5ba649dcaf2126b48e73f691664199f2fedaaf4 Mon Sep 17 00:00:00 2001 From: Danielo Rodriguez Date: Sat, 20 Jan 2024 13:44:41 +0100 Subject: [PATCH 01/10] chore: J import from fp-ts --- tsconfig.json | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 7a041f12..0091189f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,21 +15,10 @@ "verbatimModuleSyntax": false, "strict": true, "noUncheckedIndexedAccess": true, - "types": [ - "jest", - "node", - "svelte", - ], - "lib": [ - "DOM", - "ES5", - "ES6", - "ES7" - ], + "types": ["jest", "node", "svelte"], + "lib": ["DOM", "ES5", "ES6", "ES7"], "paths": { - "@std": [ - "src/std" - ] + "@std": ["src/std"] }, "plugins": [ { @@ -38,6 +27,9 @@ "O": { "importPath": "fp-ts/Option" }, + "J": { + "importPath": "fp-ts/Json" + }, "NEA": { "importPath": "fp-ts/NonEmptyArray" }, @@ -82,12 +74,10 @@ }, "TU": { "importPath": "fp-ts/Tuple" - }, + } } } ] }, - "include": [ - "src/**/*" - ], -} \ No newline at end of file + "include": ["src/**/*"] +} From d33dbb658c67024f72a13da79fd074e272f62873 Mon Sep 17 00:00:00 2001 From: Danielo Rodriguez Date: Sat, 20 Jan 2024 16:56:40 +0100 Subject: [PATCH 02/10] WIP: form import --- src/core/ResultValue.ts | 14 ++++-- src/core/findInputDefinitionSchema.ts | 28 +++++------ src/core/formDefinitionSchema.ts | 28 ++++++----- src/main.ts | 10 ++++ src/std/index.ts | 8 ++- src/views/FormImport.svelte | 70 +++++++++++++++++++++++++++ src/views/FormImport.ts | 46 ++++++++++++++++++ src/views/FormImportView.ts | 23 +++++++++ 8 files changed, 190 insertions(+), 37 deletions(-) create mode 100644 src/views/FormImport.svelte create mode 100644 src/views/FormImport.ts create mode 100644 src/views/FormImportView.ts diff --git a/src/core/ResultValue.ts b/src/core/ResultValue.ts index dde1cec4..3e3d3218 100644 --- a/src/core/ResultValue.ts +++ b/src/core/ResultValue.ts @@ -9,6 +9,7 @@ function _toBulletList(value: Record | unknown[]) { .map(([key, value]) => `- ${key}: ${value}`) .join("\n"); } + function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } @@ -37,7 +38,7 @@ export class ResultValue { protected value: T, protected name: string, private notify: Reporter = notify, - ) { } + ) {} static from(value: U, name: string, notify = notifyError) { return new ResultValue(value, name, notify); } @@ -159,7 +160,9 @@ export class ResultValue { * If the value is an array, it will return an array with all the strings uppercased. */ get upper() { - return this.map((v) => deepMap(v, (it) => typeof it === "string" ? it.toLocaleUpperCase() : it)); + return this.map((v) => + deepMap(v, (it) => (typeof it === "string" ? it.toLocaleUpperCase() : it)), + ); } /** * getter that returns all the string values lowercased. @@ -168,13 +171,14 @@ export class ResultValue { * @returns FormValue */ get lower() { - return this.map((v) => deepMap(v, (it) => typeof it === "string" ? it.toLocaleLowerCase() : it)); + return this.map((v) => + deepMap(v, (it) => (typeof it === "string" ? it.toLocaleLowerCase() : it)), + ); } /** * getter that returns all the string values trimmed. * */ get trimmed() { - return this.map((v) => deepMap(v, (it) => typeof it === "string" ? it.trim() : it)); + return this.map((v) => deepMap(v, (it) => (typeof it === "string" ? it.trim() : it))); } - } diff --git a/src/core/findInputDefinitionSchema.ts b/src/core/findInputDefinitionSchema.ts index f7b973df..4e1f9daf 100644 --- a/src/core/findInputDefinitionSchema.ts +++ b/src/core/findInputDefinitionSchema.ts @@ -1,16 +1,14 @@ import { A, NonEmptyArray, ParsingFn, parse, pipe } from "@std"; +import * as Separated from "fp-ts/Separated"; import * as E from "fp-ts/Either"; import { ValiError, BaseSchema } from "valibot"; import { FieldMinimal, FieldMinimalSchema } from "./formDefinitionSchema"; import { AllFieldTypes } from "./formDefinition"; import { InputTypeToParserMap } from "./InputDefinitionSchema"; -function stringifyIssues(error: ValiError): NonEmptyArray { +export function stringifyIssues(error: ValiError): NonEmptyArray { return error.issues.map( - (issue) => - `${issue.path?.map((i) => i.key)}: ${issue.message} got ${ - issue.input - }`, + (issue) => `${issue.path?.map((i) => i.key)}: ${issue.message} got ${issue.input}`, ) as NonEmptyArray; } export class InvalidInputTypeError { @@ -24,9 +22,7 @@ export class InvalidInputTypeError { return `InvalidInputTypeError: ${this.getFieldErrors()[0]}`; } getFieldErrors(): NonEmptyArray { - return [ - `"input.type" is invalid, got: ${JSON.stringify(this.inputType)}`, - ]; + return [`"input.type" is invalid, got: ${JSON.stringify(this.inputType)}`]; } } export class InvalidInputError { @@ -59,6 +55,9 @@ export class InvalidFieldError { toString(): string { return `InvalidFieldError: ${stringifyIssues(this.error).join(", ")}`; } + toArrayOfStrings(): string[] { + return this.getFieldErrors(); + } getFieldErrors(): string[] { return stringifyIssues(this.error); } @@ -82,17 +81,13 @@ function isValidInputType(input: unknown): input is AllFieldTypes { */ export function findInputDefinitionSchema( fieldDefinition: unknown, -): E.Either< - InvalidFieldError | InvalidInputTypeError, - [FieldMinimal, ParsingFn] -> { +): E.Either]> { return pipe( parse(FieldMinimalSchema, fieldDefinition), E.mapLeft(InvalidFieldError.of(fieldDefinition)), E.chainW((field) => { const type = field.input.type; - if (isValidInputType(type)) - return E.right([field, InputTypeToParserMap[type]]); + if (isValidInputType(type)) return E.right([field, InputTypeToParserMap[type]]); else return E.left(new InvalidInputTypeError(field, type)); }), ); @@ -107,7 +102,7 @@ export function findInputDefinitionSchema( export function findFieldErrors(fields: unknown[]) { return pipe( fields, - A.map((fieldUnparsed) => { + A.partitionMap((fieldUnparsed) => { return pipe( findInputDefinitionSchema(fieldUnparsed), E.chainW(([field, parser]) => @@ -121,7 +116,6 @@ export function findFieldErrors(fields: unknown[]) { ), ); }), - // A.partition(E.isLeft), - // Separated.right, + Separated.left, ); } diff --git a/src/core/formDefinitionSchema.ts b/src/core/formDefinitionSchema.ts index 68ca74ad..ff94adbf 100644 --- a/src/core/formDefinitionSchema.ts +++ b/src/core/formDefinitionSchema.ts @@ -15,7 +15,7 @@ import { boolean, } from "valibot"; import { FormDefinition } from "./formDefinition"; -import { findFieldErrors } from "./findInputDefinitionSchema"; +import { findFieldErrors, stringifyIssues } from "./findInputDefinitionSchema"; import { ParsedTemplateSchema } from "./template/templateSchema"; import { InputTypeSchema, nonEmptyString } from "./InputDefinitionSchema"; @@ -36,10 +36,7 @@ export const FieldDefinitionSchema = object({ * Only for error reporting purposes */ export const FieldMinimalSchema = passthrough( - merge([ - FieldDefinitionSchema, - object({ input: passthrough(object({ type: string() })) }), - ]), + merge([FieldDefinitionSchema, object({ input: passthrough(object({ type: string() })) })]), ); export type FieldMinimal = Output; @@ -98,6 +95,12 @@ export class MigrationError { ${this.error.message} ${this.error.issues.map((issue) => issue.message).join(", ")}`; } + toArrayOfStrings(): string[] { + return [ + ...stringifyIssues(this.error), + ...this.fieldErrors.map((issue) => issue.toString()), + ]; + } // 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 @@ -118,25 +121,24 @@ export class InvalidData { readonly error: ValiError, ) {} toString(): string { - return `InvalidData: ${this.error.issues - .map((issue) => issue.message) - .join(", ")}`; + return `InvalidData: ${stringifyIssues(this.error).join(", ")}`; + } + toArrayOfStrings(): string[] { + return stringifyIssues(this.error); } } //=========== 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)), ); } + /** * * Parses the form definition and migrates it to the latest version in one operation. */ - export function migrateToLatest( data: unknown, ): E.Either { @@ -145,7 +147,7 @@ export function migrateToLatest( parse(FormDefinitionLatestSchema, data, { abortEarly: true }), E.orElse(() => pipe( - parse(FormDefinitionBasicSchema, data), + parse(FormDefinitionBasicSchema, data, { abortEarly: false }), E.mapLeft((error) => new InvalidData(data, error)), E.map(fromV0toV1), ), diff --git a/src/main.ts b/src/main.ts index e9f38a4c..df9c7bff 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,6 +26,7 @@ import { executeTemplate } from "./core/template/templateParser"; import { NewNoteModal } from "./suggesters/NewNoteModal"; import { file_exists } from "./utils/files"; import { FormPickerModal } from "./suggesters/FormPickerModal"; +import { FormImportModal } from "./views/FormImportView"; type ViewType = typeof EDIT_FORM_VIEW | typeof MANAGE_FORMS_VIEW; @@ -241,6 +242,15 @@ export default class ModalFormPlugin extends Plugin { }, }); + this.addCommand({ + id: "import-form", + name: "Import form", + callback: () => { + const importModal = new FormImportModal(this.app); + importModal.open(); + }, + }); + // This adds a settings tab so the user can configure various aspects of the plugin this.addSettingTab(new ModalFormSettingTab(this.app, this)); } diff --git a/src/std/index.ts b/src/std/index.ts index 817a012d..ed24d79e 100644 --- a/src/std/index.ts +++ b/src/std/index.ts @@ -109,9 +109,13 @@ type ParseOpts = Parameters[2]; * and returns the result of parsing the input with the schema and options. */ export function parseC(schema: S, options?: ParseOpts) { - return (input: unknown) => parse(schema, input, options); + return (input: unknown, moreOptions?: ParseOpts) => + parse(schema, input, { ...options, ...moreOptions }); } -export type ParsingFn = (input: unknown) => Either>; +export type ParsingFn = ( + input: unknown, + options?: ParseOpts, +) => Either>; /** * Concatenates two parsing functions that return Either into one. * If the first function returns a Right, the second function is not called. diff --git a/src/views/FormImport.svelte b/src/views/FormImport.svelte new file mode 100644 index 00000000..fd16c4b2 --- /dev/null +++ b/src/views/FormImport.svelte @@ -0,0 +1,70 @@ + + +
+

+ Import a form by pasting the JSON definition into the box below. You can export a form from + the Form Builder. Any errors in the JSON will be displayed below. You will only be able to + import the form if there are no errors. +

+
+