Skip to content

Commit

Permalink
feat: create new note from form using templates
Browse files Browse the repository at this point in the history
  • Loading branch information
danielo515 committed Dec 10, 2023
1 parent f685533 commit 64a5adc
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/core/formDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
MigrationError,
} from "./formDefinitionSchema";
import { A, O, pipe } from "@std";
import { ParsedTemplate } from "./template/templateParser";
//=========== Types derived from schemas
type selectFromNotes = Output<typeof SelectFromNotesSchema>;
type inputSlider = Output<typeof InputSliderSchema>;
Expand Down Expand Up @@ -71,6 +72,7 @@ export type FieldDefinition = Output<typeof FieldDefinitionSchema>;
* FormDefinition is an already valid form, ready to be used in the form modal.
*/
export type FormDefinition = Output<typeof FormDefinitionLatestSchema>;
export type FormWithTemplate = FormDefinition & { template: ParsedTemplate }

export type FormOptions = {
values?: Record<string, unknown>;
Expand Down
31 changes: 31 additions & 0 deletions src/core/template/templateParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import * as E from 'fp-ts/Either';
import * as St from 'fp-ts/String'
import * as P from 'parser-ts/Parser'
import { run } from 'parser-ts/code-frame'
import * as J from 'fp-ts/Json'

Check failure on line 5 in src/core/template/templateParser.ts

View workflow job for this annotation

GitHub Actions / build

'J' is defined but never used
import * as C from 'parser-ts/char'
import * as S from 'parser-ts/string'
import * as A from 'fp-ts/Array'
import { Either, O, pipe } from '@std';
import { TemplateText, TemplateVariable } from './templateSchema';
import { absurd } from 'fp-ts/function';
import { ModalFormData } from '../FormResult';
type Token = TemplateText | TemplateVariable
export type ParsedTemplate = Token[];

Expand Down Expand Up @@ -97,9 +99,38 @@ function tokenToString(token: Token): string {
}
}

function matchToken<T>(onText: (value: string) => T, onVariable: (variable: string) => T) {
return (token: Token): T => {
switch (token._tag) {
case 'text': return onText(token.value)
case 'variable': return onVariable(token.value)
default:
return absurd(token)
}
}
}

/**
* Given a correctly parsed template, convert it back into a string
* with the right format: variables are surrounded by double curly braces, etc.
* If a template is correct you should be able to parse it and then convert it back
* to a string without losing any information.
* @param parsedTemplate the template in it's already parsed form
* @returns string
*/
export function parsedTemplateToString(parsedTemplate: ParsedTemplate): string {
return pipe(
parsedTemplate,
A.foldMap(St.Monoid)(tokenToString)
)
}

export function executeTemplate(parsedTemplate: ParsedTemplate, formData: ModalFormData) {
return pipe(
parsedTemplate,
A.filterMap(
matchToken(O.some, (key) => O.fromNullable(formData[key]))
),
A.foldMap(St.Monoid)(String)
)
}
50 changes: 49 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import { API } from "src/API";
import { EDIT_FORM_VIEW, EditFormView } from "src/views/EditFormView";
import { MANAGE_FORMS_VIEW, ManageFormsView } from "src/views/ManageFormsView";
import { ModalFormError } from "src/utils/ModalFormError";
import { type FormDefinition } from "src/core/formDefinition";
import { FormWithTemplate, type FormDefinition } from "src/core/formDefinition";
import { formNeedsMigration, migrateToLatest, MigrationError, InvalidData } from "./core/formDefinitionSchema";
import { parseSettings, type ModalFormSettings, type OpenPosition, getDefaultSettings } from "src/core/settings";
import { log_notice } from "./utils/Log";
import * as E from "fp-ts/Either";
import { pipe } from "fp-ts/function";
import * as A from "fp-ts/Array"
import { settingsStore } from "./store/store";
import { FormPickerModal } from "./suggesters/FormPickerModal";
import { O } from "@std";
import { executeTemplate } from "./core/template/templateParser";

type ViewType = typeof EDIT_FORM_VIEW | typeof MANAGE_FORMS_VIEW;

Expand Down Expand Up @@ -182,9 +185,54 @@ export default class ModalFormPlugin extends Plugin {
this.manageForms();
},
});
this.addCommand({
id: 'create-note-from-form',
name: 'Create new note from a form',
callback: () => {
this.createNoteFromForm();
}
})

// This adds a settings tab so the user can configure various aspects of the plugin
this.addSettingTab(new ModalFormSettingTab(this.app, this));
}

getUniqueNoteName(name: string): string {
const defaultNotesFolder = this.app.fileManager.getNewFileParent('', 'note.md')
let destinationPath = `${defaultNotesFolder.path}/${name}.md`
let i = 1;
while (this.app.vault.getAbstractFileByPath(destinationPath)) {
destinationPath = `${defaultNotesFolder.path}/${name}-${i}.md`
i++;
}
return destinationPath;
}

/**
* Checks if there are forms with templates, and presents a prompt
* to select a form, then opens the forms, and creates a new note
* with the template and the form values
*/
createNoteFromForm() {
const formsWithTemplates = pipe(
this.settings!.formDefinitions,
A.filterMap((form) => {
if (form instanceof MigrationError) {
return O.none;
}
if (form.template !== undefined) {
return O.some(form as FormWithTemplate);
}
return O.none;
})
)
const onFormSelected = async (form: FormWithTemplate) => {
const formData = await this.api.openForm(form);
const newNoteFullPath = this.getUniqueNoteName(form.name);
this.app.vault.create(newNoteFullPath, executeTemplate(form.template, formData.getData()))
}
const picker = new FormPickerModal(this.app, formsWithTemplates, onFormSelected);
picker.open();
}

}
21 changes: 21 additions & 0 deletions src/suggesters/FormPickerModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { App, FuzzySuggestModal } from "obsidian";
import { FormDefinition } from "src/core/formDefinition";

export class FormPickerModal<Definition extends FormDefinition> extends FuzzySuggestModal<Definition> {
constructor(app: App, protected forms: Definition[], protected onSelected: (form: Definition) => void) {
super(app);
}

getItems(): Definition[] {
return this.forms;
}

getItemText(item: Definition): string {
return item.title;
}

onChooseItem(item: Definition, _: MouseEvent | KeyboardEvent): void {
this.close();
this.onSelected(item);
}
}

0 comments on commit 64a5adc

Please sign in to comment.