diff --git a/README.md b/README.md index 59440c5..d6def04 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ This plugin is not in the community plugin browser in Obsidian (yet). The first thing you'll want to do is add a new choice. A choice can be one of four types. ### Template +The template choice type is not meant to be a replacement for Templater or core Templates. It's meant to augment them. You can use both QuickAdd format syntax in a Templater template - and both will work. +That's one thing. But there's also all the other options: dynamic template names, adding to folders, appending links, incrementing file names, opening the file when it's created (or not), etc. + You first need to specify a _template path_. This is a path to the template you wish to insert. The remaining settings are useful, but optional. You can specify a format for the file name, which is based on the format syntax - which you can see further down this page. @@ -68,7 +71,7 @@ This is what my `logBook` macro looks like. It's pretty plain - it just executes ![image](https://user-images.githubusercontent.com/29108628/121774245-cfa74e00-cb81-11eb-9977-3ddac04dc8bd.png) The `logBook` user script simply updates the book in my daily page to something I specify in a prompt. -Here it is - with some comments that explain the code. +Here it is - with some comments that explain the code. [How-to-install guide](https://github.com/chhoumann/quickadd/issues/15#issuecomment-864553251). ```js // You have to export the function you wish to run. @@ -89,6 +92,56 @@ module.exports = async (params) => { } ``` +Any function executed by QuickAdd will be passed an object as the first (and only) parameter. +The object contains +- A reference to the Obsidian `app`. +- A reference to the QuickAddApi - which allows you to use the functions below. +- A reference to `variables`, an object which, if you assign values to it, you can use in your format syntax. + +Let's talk a bit more about `variables`. If you assign a value to a key in `variables`, you can access that variable by its key name. +This can be accessed both in subsequent macros, and the format syntax `{{VALUE:}}`. + +For example, say you assign `myVar` to `variables`. +Then you can access the value of `myVar` in subsequent macros, as well as through the format syntax`{{VALUE:myVar}}`. +You can also access it through `.variables["myVar"]`. + +````js +// MACRO 1 +module.exports = (params) => { + params.variables["myVar"] = "test"; +} + +// MACRO 2 +module.exports = (params) => { + console.log(params.variables["myVar"]); +} +```` + +You can use variables to pass parameters between user scripts. + +In your user scripts for your macros, you can have use ``module.exports`` to export a function, which will be executed as expected. + +You can, however, export much more than functions. You can also export variables - and more functions! + +````js +// Macro called 'MyMacro' +module.exports = { + myVar: "test", + plus: (params) => params.variables["a"] + params.variables["b"], + start +} + +async function start(params) { + params.app.vault.doSomething(); + const input = await params.quickAddApi.suggester(["DisplayValue1", "DisplayValue2", "DisplayValue3"], ["ActualValue1", "ActualValue2", "ActualValue3"]); + return input; +} +```` + +If you select the macro that contains a user script with the above code, you'll be prompted to choose between one of the three exported items. + +However, if you want to skip that, you can access nested members using this syntax: `{{MACRO:MyMacro::Start}}`. This will just instantly execute `start`. + ### Multi Multi-choices are pretty simple. They're like folders for other choices. Here are mine. They're the ones which you can 'open' and 'close'. @@ -107,13 +160,13 @@ Multi-choices are pretty simple. They're like folders for other choices. Here ar | `{{TEMPLATE:}}` | Include templates in your `format`. Supports Templater syntax. | ## QuickAdd API -#### `inputPrompt(header: string, placeholder?: string, value?: string): string` +### `inputPrompt(header: string, placeholder?: string, value?: string): string` Opens a prompt that asks for an input. Returns a string with the input. -#### `yesNoPrompt: (header: string, text?: string): boolean` +### `yesNoPrompt: (header: string, text?: string): boolean` Opens a prompt asking for confirmation. Returns `true` or `false` based on answer. -#### `suggester: (displayItems: string[], actualItems: string[])` +### `suggester: (displayItems: string[], actualItems: string[])` Opens a suggester. Displays the `displayItems`, but you map these the other values with `actualItems`. ## Examples diff --git a/manifest.json b/manifest.json index eadffdf..5e79a0a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "quickadd", "name": "QuickAdd", - "version": "0.1.15", + "version": "0.1.16", "minAppVersion": "0.12.00", "description": "Quickly add new pages or content to your vault.", "author": "Christian B. B. Houmann", diff --git a/package.json b/package.json index b65c2d2..58163fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "quickadd", - "version": "0.1.15", + "version": "0.1.16", "description": "Quickly add new pages or content to your vault.", "main": "main.js", "scripts": { diff --git a/src/engine/MacroChoiceEngine.ts b/src/engine/MacroChoiceEngine.ts index d3d7162..a142e1f 100644 --- a/src/engine/MacroChoiceEngine.ts +++ b/src/engine/MacroChoiceEngine.ts @@ -9,10 +9,12 @@ import {QuickAddApi} from "../quickAddApi"; import type {ICommand} from "../types/macros/ICommand"; import {QuickAddChoiceEngine} from "./QuickAddChoiceEngine"; import type {IMacro} from "../types/macros/IMacro"; +import GenericSuggester from "../gui/GenericSuggester/genericSuggester"; export class MacroChoiceEngine extends QuickAddChoiceEngine { - choice: IMacroChoice; + public choice: IMacroChoice; protected output: string; + protected readonly params = {app: this.app, quickAddApi: QuickAddApi.GetApi(this.app)}; constructor(app: App, choice: IMacroChoice, protected macros: IMacro[]) { super(app); @@ -38,6 +40,50 @@ export class MacroChoiceEngine extends QuickAddChoiceEngine { // Slightly modified from Templater's user script engine: // https://github.com/SilentVoid13/Templater/blob/master/src/UserTemplates/UserTemplateParser.ts protected async executeUserScript(command: IUserScript) { + const userScript = await this.getUserScript(command); + if (!userScript || !userScript.default) { + log.logError(`failed to load user script ${command.path}.`); + return; + } + + await this.userScriptDelegator(userScript.default); + } + + protected async userScriptDelegator(userScript: any) { + switch (typeof userScript) { + case "function": + await this.onExportIsFunction(userScript); + break; + case "object": + await this.onExportIsObject(userScript); + break; + case "bigint": + case "boolean": + case "number": + case "string": + this.output = userScript.toString(); + break; + default: + log.logError(`user script in macro for '${this.choice.name}' is invalid`); + } + } + + private async onExportIsFunction(userScript: any) { + this.output = await userScript(this.params); + } + + protected async onExportIsObject(obj: object) { + try { + const keys = Object.keys(obj); + const selected: string = await GenericSuggester.Suggest(this.app, keys, keys); + + await this.userScriptDelegator(obj[selected]); + } catch (e) { + log.logMessage(e); + } + } + + protected async getUserScript(command: IUserScript) { // @ts-ignore const vaultPath = this.app.vault.adapter.getBasePath(); const file: TAbstractFile = this.app.vault.getAbstractFileByPath(command.path); @@ -54,13 +100,7 @@ export class MacroChoiceEngine extends QuickAddChoiceEngine { } // @ts-ignore - const userScript = await import(filePath); - if (!userScript.default || !(userScript.default instanceof Function)) { - log.logError(`failed to load user script ${filePath}.`); - return; - } - - this.output = await userScript.default({app: this.app, quickAddApi: QuickAddApi.GetApi(this.app)}); + return await import(filePath); } } diff --git a/src/engine/SingleMacroEngine.ts b/src/engine/SingleMacroEngine.ts index c30e7dc..c985e5a 100644 --- a/src/engine/SingleMacroEngine.ts +++ b/src/engine/SingleMacroEngine.ts @@ -1,17 +1,40 @@ import type {App} from "obsidian"; import type {IMacro} from "../types/macros/IMacro"; import {MacroChoiceEngine} from "./MacroChoiceEngine"; +import {QuickAddApi} from "../quickAddApi"; export class SingleMacroEngine extends MacroChoiceEngine { - constructor(app: App, macros: IMacro[]) { + public readonly params = {app: this.app, quickAddApi: QuickAddApi.GetApi(this.app), variables: {}}; + private memberAccess: string[]; + + constructor(app: App, macros: IMacro[], private variables: Map) { super(app, null, macros); + + variables.forEach(((value, key) => { + this.params.variables[key] = value; + })); } public async runAndGetOutput(macroName: string): Promise { - const macro = this.macros.find(macro => macro.name === macroName); + const splitName: string[] = macroName.split('::'); + const macro = this.macros.find(macro => macro.name === splitName[0]); if (!macro) return; + if (splitName.length > 1) { + this.memberAccess = splitName.slice(1); + } + await this.executeCommands(macro.commands) return this.output; } + + protected override async onExportIsObject(obj: any): Promise { + if (!this.memberAccess) return await super.onExportIsObject(obj); + let newObj = obj; + this.memberAccess.forEach(key => { + newObj = newObj[key]; + }); + + await this.userScriptDelegator(newObj); + } } \ No newline at end of file diff --git a/src/engine/TemplateChoiceEngine.ts b/src/engine/TemplateChoiceEngine.ts index 9bb2453..de7c847 100644 --- a/src/engine/TemplateChoiceEngine.ts +++ b/src/engine/TemplateChoiceEngine.ts @@ -17,33 +17,41 @@ export class TemplateChoiceEngine extends TemplateEngine { public async run(): Promise { try { const folderPath = await this.getOrCreateFolder(this.choice.folder.folders); + let filePath; + if (this.choice.fileNameFormat.enabled) { filePath = await this.getFormattedFilePath(folderPath, this.choice.fileNameFormat.format, this.choice.name); } else { const fileNameValueFormat: string = "{{VALUE}}"; filePath = await this.getFormattedFilePath(folderPath, fileNameValueFormat, this.choice.name); } + if (this.choice.incrementFileName) filePath = await this.incrementFileName(filePath); + const createdFile: TFile = await this.createFileWithTemplate(filePath, this.choice.templatePath); if (!createdFile) { - log.logError(`Could not create file '${filePath}'.`); + log.logWarning(`Could not create file '${filePath}'.`); return; } + if (this.choice.appendLink) { const linkString = `[[${createdFile.path.replace(MARKDOWN_FILE_EXTENSION_REGEX, '')}]]`; appendToCurrentLine(linkString, this.app); } + if (this.choice.openFile) { if (!this.choice.openFileInNewTab.enabled) { await this.app.workspace.activeLeaf.openFile(createdFile); } else { - await this.app.workspace.splitActiveLeaf(this.choice.openFileInNewTab.direction) + await this.app.workspace + .splitActiveLeaf(this.choice.openFileInNewTab.direction) .openFile(createdFile); } } - } catch (e) { + } + catch (e) { log.logError(e.message); } } diff --git a/src/engine/TemplateEngine.ts b/src/engine/TemplateEngine.ts index 2302656..51e9773 100644 --- a/src/engine/TemplateEngine.ts +++ b/src/engine/TemplateEngine.ts @@ -5,6 +5,7 @@ import type QuickAdd from "../main"; import {getTemplater} from "../utility"; import GenericSuggester from "../gui/GenericSuggester/genericSuggester"; import {FILE_NUMBER_REGEX} from "../constants"; +import {log} from "../logger/logManager"; export abstract class TemplateEngine extends QuickAddEngine { protected formatter: CompleteFormatter; @@ -63,16 +64,22 @@ export abstract class TemplateEngine extends QuickAddEngine { } protected async createFileWithTemplate(filePath: string, templatePath: string) { - const templateContent: string = await this.getTemplateContent(templatePath); - const formattedTemplateContent: string = await this.formatter.formatFileContent(templateContent); + try { + const templateContent: string = await this.getTemplateContent(templatePath); - const createdFile: TFile = await this.app.vault.create(filePath, formattedTemplateContent); + const formattedTemplateContent: string = await this.formatter.formatFileContent(templateContent); + const createdFile: TFile = await this.app.vault.create(filePath, formattedTemplateContent); - if (this.templater && !this.templater.settings["trigger_on_file_creation"]) { - await this.templater.templater.overwrite_file_templates(createdFile); - } + if (this.templater) { + await this.templater.templater.overwrite_file_templates(createdFile); + } - return createdFile; + return createdFile; + } + catch (e) { + log.logError(e); + return null; + } } protected async getTemplateContent(templatePath: string): Promise { diff --git a/src/formatters/completeFormatter.ts b/src/formatters/completeFormatter.ts index e7eb732..5258f88 100644 --- a/src/formatters/completeFormatter.ts +++ b/src/formatters/completeFormatter.ts @@ -19,12 +19,12 @@ export class CompleteFormatter extends Formatter { protected async format(input: string): Promise { let output: string = input; + output = await this.replaceMacrosInString(output); + output = await this.replaceTemplateInString(output); output = this.replaceDateInString(output); output = await this.replaceValueInString(output); output = await this.replaceDateVariableInString(output); output = await this.replaceVariableInString(output); - output = await this.replaceMacrosInString(output); - output = await this.replaceTemplateInString(output); return output; } @@ -77,8 +77,14 @@ export class CompleteFormatter extends Formatter { } protected async getMacroValue(macroName: string): Promise { - const macroEngine: SingleMacroEngine = new SingleMacroEngine(this.app, this.plugin.settings.macros); - return await macroEngine.runAndGetOutput(macroName); + const macroEngine: SingleMacroEngine = new SingleMacroEngine(this.app, this.plugin.settings.macros, this.variables); + const macroOutput = await macroEngine.runAndGetOutput(macroName) ?? ""; + + Object.keys(macroEngine.params.variables).forEach(key => { + this.variables.set(key, macroEngine.params.variables[key]); + }) + + return macroOutput; } protected async getTemplateContent(templatePath: string): Promise { diff --git a/src/formatters/formatter.ts b/src/formatters/formatter.ts index 69f5073..4ab326f 100644 --- a/src/formatters/formatter.ts +++ b/src/formatters/formatter.ts @@ -100,7 +100,7 @@ export abstract class Formatter { const macroName = MACRO_REGEX.exec(output)[1]; const macroOutput = await this.getMacroValue(macroName); - output = output.replace(MACRO_REGEX, macroOutput.toString()); + output = output.replace(MACRO_REGEX, macroOutput ? macroOutput.toString() : ""); } return output; diff --git a/src/gui/GenericSuggester/genericSuggester.ts b/src/gui/GenericSuggester/genericSuggester.ts index c052ba2..eac2bcb 100644 --- a/src/gui/GenericSuggester/genericSuggester.ts +++ b/src/gui/GenericSuggester/genericSuggester.ts @@ -1,19 +1,21 @@ -import {App, FuzzySuggestModal} from "obsidian"; +import {App, FuzzyMatch, FuzzySuggestModal} from "obsidian"; export default class GenericSuggester extends FuzzySuggestModal{ private resolvePromise: (value: string) => void; - private promise: Promise; + private rejectPromise: (reason?: any) => void; + public promise: Promise; + private resolved: boolean; public static Suggest(app: App, displayItems: string[], items: string[]) { const newSuggester = new GenericSuggester(app, displayItems, items); return newSuggester.promise; } - private constructor(app: App, private displayItems: string[], private items: string[]) { + public constructor(app: App, private displayItems: string[], private items: string[]) { super(app); this.promise = new Promise( - (resolve) => (this.resolvePromise = resolve) + (resolve, reject) => {(this.resolvePromise = resolve); (this.rejectPromise = reject)} ); this.open(); @@ -27,8 +29,20 @@ export default class GenericSuggester extends FuzzySuggestModal{ return this.items; } + selectSuggestion(value: FuzzyMatch, evt: MouseEvent | KeyboardEvent) { + this.resolved = true; + super.selectSuggestion(value, evt); + } + onChooseItem(item: string, evt: MouseEvent | KeyboardEvent): void { + this.resolved = true; this.resolvePromise(item); } + onClose() { + super.onClose(); + + if (!this.resolved) + this.rejectPromise("no input given."); + } } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 67d8624..a03de1c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,5 @@ import {Plugin} from 'obsidian'; import {DEFAULT_SETTINGS, QuickAddSettings, QuickAddSettingsTab} from "./quickAddSettingsTab"; -import {TemplateChoice} from "./types/choices/TemplateChoice"; -import {MultiChoice} from "./types/choices/MultiChoice"; -import {CaptureChoice} from "./types/choices/CaptureChoice"; -import {MacroChoice} from "./types/choices/MacroChoice"; import ChoiceSuggester from "./gui/choiceSuggester"; import {log} from "./logger/logManager"; import {ConsoleErrorLogger} from "./logger/consoleErrorLogger"; @@ -43,32 +39,6 @@ export default class QuickAdd extends Plugin { }); /*END.DEVCMD*/ - /*START.DEVCMD*/ - this.addCommand({ - id: 'giveDivChoices', - name: 'Give Dev Choices', - callback: () => { - this.settings.choices = [ - new TemplateChoice("πŸšΆβ€β™‚οΈ Journal"), - new TemplateChoice('πŸ“– Log Book to Daily Journal'), - new MultiChoice('πŸ“₯ Add...') - .addChoice(new CaptureChoice('πŸ’­ Add a Thought')) - .addChoice(new CaptureChoice('πŸ“₯ Add an Inbox Item')) - .addChoice(new TemplateChoice('πŸ“• Add Book Notes')), - new CaptureChoice("✍ Quick Capture"), - new TemplateChoice('πŸ’¬ Add Quote Page'), - new MultiChoice('πŸŒ€ Task Manager') - .addChoice(new MacroChoice('βœ” Add a Task')) - .addChoice(new CaptureChoice('βœ” Quick Capture Task')) - .addChoice(new CaptureChoice('βœ” Add MetaEdit Backlog Task')), - new CaptureChoice('πŸ’Έ Add Purchase'), - ]; - - this.saveSettings(); - } - }) - /*END.DEVCMD*/ - log.register(new ConsoleErrorLogger()) .register(new GuiLogger(this)); @@ -140,7 +110,5 @@ export default class QuickAdd extends Plugin { await this.saveSettings(); } - - } diff --git a/src/quickAddApi.ts b/src/quickAddApi.ts index 421dfd9..e3d5f48 100644 --- a/src/quickAddApi.ts +++ b/src/quickAddApi.ts @@ -13,14 +13,26 @@ export class QuickAddApi { } public static async inputPrompt(app: App, header: string, placeholder?: string, value?: string) { - return await GenericInputPrompt.Prompt(app, header, placeholder, value); + try { + return await GenericInputPrompt.Prompt(app, header, placeholder, value); + } catch { + return undefined; + } } public static async yesNoPrompt(app: App, header: string, text?: string) { - return await GenericYesNoPrompt.Prompt(app, header, text); + try { + return await GenericYesNoPrompt.Prompt(app, header, text); + } catch { + return undefined; + } } public static async suggester(app: App, displayItems: string[], actualItems: string[]) { - return await GenericSuggester.Suggest(app, displayItems, actualItems); + try { + return await GenericSuggester.Suggest(app, displayItems, actualItems); + } catch { + return undefined; + } } } \ No newline at end of file diff --git a/versions.json b/versions.json index 9069533..c5a248c 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "0.1.15": "0.12.4" + "0.1.16": "0.12.4" }