Skip to content

Commit

Permalink
Merge pull request #4 from danielo515/feat-svelte-form-builder
Browse files Browse the repository at this point in the history
Feat svelte form builder
  • Loading branch information
danielo515 authored Sep 11, 2023
2 parents e0244a9 + eb6c405 commit d28a5ba
Show file tree
Hide file tree
Showing 21 changed files with 1,332 additions and 155 deletions.
81 changes: 78 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<a href="https://www.buymeacoffee.com/danielo515" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>

# Obsidian Modal Form Plugin

This plugin for [Obsidian](https://obsidian.md) allows to define forms that can be opened from anywhere you can run JavaScript, so you can combine it with other plugins like [Templater](https://github.com/SilentVoid13/Templater) or [QuickAdd](https://github.com/chhoumann/quickadd).
Expand All @@ -18,19 +20,27 @@ https://github.com/danielo515/obsidian-modal-form/assets/2270425/542974aa-c58b-4
- Many input types
- number
- date
- time
- slider
- toggle (true/false)
- free text
- text with autocompletion for note names
- text with autocompletion for note names (from a folder or root)
- select from a list
- list of fixed values
- list of notes from a folder


![example form](media/example.png)
## Why this plugin?

Obsidian is a great tool for taking notes, but it is also a nice for managing data.
However, when it's time to capture structured data it doesn't offer many conveniences.
Some plugins like [Templater](https://github.com/SilentVoid13/Templater) or [QuickAdd](https://github.com/chhoumann/quickadd) alleviate this problem with templates/automation that ease the creation of notes with a predefined structure, but then you have to fill the data manually.
This plugins have some little convenience inputs, but they are limited to a single value at a time, and they don't even have labels.
The mentioned plugins (templater, quickAdd) have some little convenience inputs, but they have certain tradeoffs/problems:

- they are limited to input a single value at a time
- they don't have labels, or detailed descriptions about the field you are filling
- you can't skip fields, you will always be prompted for all of them one by one

All of the mentioned tools are great at their job and unleash super convenient workflows.
For that reason, rather than offering an alternative, this plugin is designed as a complement to them, offering some basic building blocks that you can integrate with your existing templates and workflows.

Expand Down Expand Up @@ -122,6 +132,71 @@ tR += result.asString('{{Name}} is {{age}} years old and his/her favorite food i
### Define a form
#### Create a new form
Creating a new form is easy, you just need to open the manage forms view, either by clicking in the ribbon icon or by using the command palette (`Obsidian modal form: New form`).
Once there, click on the `+` button and you will be presented with a form to create a named form definition.
The form is self-explanatory, but here is some key points you need to keep in mind:
- The name must be unique, and it will be used to identify the form when you open it from JavaScript, case sensitive
- The title is what you will see as header in the modal window when you open the form
- You will not be able to save the form unless all the fields are valid (which means they have a name and a type)
![form editor/creator](media/editor.png)
#### Inline forms
The plugin also supports inline forms, which are forms that are defined when you call the openForm method. This is useful when you want to create a form that is only used in one place and it is simple enough. However, note the format is a bit verbose for typing it manually and it is error prone, so unless it is a very small form, you will most likely prefer to use a named form.
Here is an example of how to use it:
```javascript
const modalForm = app.plugins.plugins.obsidianModalForm.api;
const result = await modalForm.openForm({
title: 'Example form',
fields: [
{
name: 'name',
label: 'Name',
description: 'Your name',
input: { type: 'text'} ,
},
{
name: 'age',
label: 'Age',
description: 'Your age',
input: { type: 'number'} ,
},
{
name: 'favorite_meal',
label: 'Favorite meal',
description: 'Your favorite meal',
input: { type: 'text'} ,
},
{
name: 'is_family',
label: 'Is family',
type: 'toggle',
description: 'Are you family?',
required: true,
input: { type: 'toggle'} ,
},
],
});
```

You can make it smaller by removing some of the optional fields like description or label, but I really encourage you to define them all.

## Installing the plugin

Until the plugin is accepted in the community plugins list, you will need to install it either manually or through [BRAT](obsidian://show-plugin?id=obsidian42-brat)

### Installing with BRAT
1. Install the [BRAT](obsidian://show-plugin?id=obsidian42-brat) plugin (GitHub page) and enable it.
2. Open command palette and run the command BRAT: Add a beta plugin for testing.
3. Enter `https://github.com/danielo515/obsidian-modal-form` into the modal and press the Add Plugin button.
4. Return to the settings and navigate to Community plugins tab.
5. Enable the plugin.

## Manually installing the plugin

- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/modalForm/`.
Expand Down
Empty file added ViewType.ts
Empty file.
8 changes: 8 additions & 0 deletions esbuild.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
import esbuildSvelte from "esbuild-svelte";
import sveltePreprocess from "svelte-preprocess";

const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
Expand All @@ -16,6 +18,12 @@ const context = await esbuild.context({
},
entryPoints: ["main.ts"],
bundle: true,
plugins: [
esbuildSvelte({
compilerOptions: { css: "injected" },
preprocess: sveltePreprocess(),
}),
],
external: [
"obsidian",
"electron",
Expand Down
3 changes: 2 additions & 1 deletion examples/format with template example.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<%*
const modalForm = app.plugins.plugins.obsidianModalForm.api;
const result = await modalForm.openForm('example-form');
tR += result.asString('{{Name}} is {{age}} years old and his/her favourite food is {{favorite_meal}}. Family status: {{is_family}}');
tR += result.asString('{{Name}} is {{age}} years old and his/her favourite food is {{favorite_meal}}. Family status: {{is_family}}');
%>
100 changes: 76 additions & 24 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { FormDefinition } from "src/FormModal";
import { Notice, Plugin } from "obsidian";
import { Notice, Platform, Plugin } from "obsidian";
import FormResult from "src/FormResult";
import { exampleModalDefinition } from "src/exampleModalDefinition";
import { ModalFormSettingTab } from "ModalFormSettingTab";
import { API } from "src/API";
import { EDIT_FORM_VIEW, EditFormView } from "src/EditFormView";
import { MANAGE_FORMS_VIEW, ManageFormsView } from "src/views/ManageFormsView";
import { ModalFormError } from "src/utils/Error";
import type { FormDefinition } from "src/core/formDefinition";

// Remember to rename these classes and interfaces!

type ViewType = typeof EDIT_FORM_VIEW | typeof MANAGE_FORMS_VIEW;

interface ModalFormSettings {
formDefinitions: FormDefinition[];
}
Expand All @@ -27,20 +31,59 @@ export default class ModalFormPlugin extends Plugin {
// This things will be setup in the onload function rather than constructor
public api!: PublicAPI;

manageForms() {
return this.activateView(MANAGE_FORMS_VIEW);
}

createNewForm() {
return this.activateView(EDIT_FORM_VIEW);
}

async editForm(formName: string) {
const formDefinition = this.settings?.formDefinitions.find(form => form.name === formName);
if (!formDefinition) {
throw new ModalFormError(`Form ${formName} not found`)
}
await this.activateView(EDIT_FORM_VIEW, formDefinition);

}

async saveForm(formDefinition: FormDefinition) {
const index = this.settings?.formDefinitions.findIndex(form => form.name === formDefinition.name);
if (index === undefined || index === -1) {
this.settings?.formDefinitions.push(formDefinition);
} else {
this.settings?.formDefinitions.splice(index, 1, formDefinition);
}
console.log(this.settings, index)
await this.saveSettings();
// go back to manage forms and refresh it
await this.activateView(MANAGE_FORMS_VIEW);
}
async deleteForm(formName: string) {
// This should never happen, but because obsidian plugin life-cycle we can not guarantee that the settings are loaded
if (!this.settings) {
throw new ModalFormError('Settings not found')
}
this.settings.formDefinitions = this.settings.formDefinitions.filter(form => form.name !== formName);
await this.saveSettings();
}

async onload() {
this.settings = await this.getSettings();
if (this.settings.formDefinitions.length === 0) {
this.settings.formDefinitions.push(exampleModalDefinition);
}
this.api = new API(this.app, this);
this.registerView(EDIT_FORM_VIEW, (leaf) => new EditFormView(leaf, this));
this.registerView(MANAGE_FORMS_VIEW, (leaf) => new ManageFormsView(leaf, this));

// This creates an icon in the left ribbon.
const ribbonIconEl = this.addRibbonIcon(
"form",
"documents",
"Edit forms",
(evt: MouseEvent) => {
new Notice("This is a notice!");
this.manageForms()
}
);
// Perform additional things with the ribbon
Expand All @@ -49,38 +92,47 @@ export default class ModalFormPlugin extends Plugin {
this.addCommand({
id: "modal-form-new-form",
name: "New form",
callback: async () => {
const leaf = await this.activateView();
leaf.setViewState({ type: 'new-form', state: { title: '', name: '', fields: [] } })

callback: () => {
this.createNewForm();
},
});
this.addCommand({
id: "modal-form-manage-forms",
name: "Manage forms",
callback: () => {
this.manageForms();
},
});

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

async editForm(formName: string) {
const formDefinition = this.settings?.formDefinitions.find(form => form.name === formName);
if (!formDefinition) {
throw new Error(`Form ${formName} not found`)
}
const leaf = await this.activateView();
leaf.setViewState({ type: 'edit-form', state: formDefinition });

closeEditForm() {
this.app.workspace.detachLeavesOfType(EDIT_FORM_VIEW);
}

onunload() { }

async activateView() {
this.app.workspace.detachLeavesOfType(EDIT_FORM_VIEW);
onunload() { }

await this.app.workspace.getRightLeaf(false).setViewState({
type: EDIT_FORM_VIEW,
active: true,
});
async activateView(viewType: ViewType, state?: FormDefinition) {
this.app.workspace.detachLeavesOfType(viewType);

if (Platform.isMobile) {
await this.app.workspace.getLeaf(false).setViewState({
type: viewType,
active: true,
state,
});
} else {
await this.app.workspace.getRightLeaf(false).setViewState({
type: viewType,
active: true,
state,
});
}

const leave = this.app.workspace.getLeavesOfType(EDIT_FORM_VIEW)[0]
const leave = this.app.workspace.getLeavesOfType(viewType)[0]
this.app.workspace.revealLeaf(
leave
);
Expand Down
4 changes: 2 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"description": "Define forms for filling data that you will be able to open from anywhere you can run JS",
"author": "Danielo Rodriguez",
"authorUrl": "https://danielo.es",
"fundingUrl": "https://obsidian.md/pricing",
"fundingUrl": "https://www.buymeacoffee.com/danielo515",
"isDesktopOnly": false,
"version": "1.1.0"
}
}
Binary file added media/editor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit d28a5ba

Please sign in to comment.