Skip to content

Commit

Permalink
Macros Upgrade (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
chhoumann authored Jun 21, 2021
1 parent a718927 commit 3cf2a7e
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 71 deletions.
61 changes: 57 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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:<variable name>}}`.

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 `<parametername>.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'.

Expand All @@ -107,13 +160,13 @@ Multi-choices are pretty simple. They're like folders for other choices. Here ar
| `{{TEMPLATE:<TEMPLATEPATH>}}` | 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
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
56 changes: 48 additions & 8 deletions src/engine/MacroChoiceEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
}
}

Expand Down
27 changes: 25 additions & 2 deletions src/engine/SingleMacroEngine.ts
Original file line number Diff line number Diff line change
@@ -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<string, string>) {
super(app, null, macros);

variables.forEach(((value, key) => {
this.params.variables[key] = value;
}));
}

public async runAndGetOutput(macroName: string): Promise<string> {
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<void> {
if (!this.memberAccess) return await super.onExportIsObject(obj);
let newObj = obj;
this.memberAccess.forEach(key => {
newObj = newObj[key];
});

await this.userScriptDelegator(newObj);
}
}
14 changes: 11 additions & 3 deletions src/engine/TemplateChoiceEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,41 @@ export class TemplateChoiceEngine extends TemplateEngine {
public async run(): Promise<void> {
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);
}
}
Expand Down
21 changes: 14 additions & 7 deletions src/engine/TemplateEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<string> {
Expand Down
14 changes: 10 additions & 4 deletions src/formatters/completeFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ export class CompleteFormatter extends Formatter {
protected async format(input: string): Promise<string> {
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;
}
Expand Down Expand Up @@ -77,8 +77,14 @@ export class CompleteFormatter extends Formatter {
}

protected async getMacroValue(macroName: string): Promise<string> {
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<string> {
Expand Down
2 changes: 1 addition & 1 deletion src/formatters/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 3cf2a7e

Please sign in to comment.