Skip to content

Commit

Permalink
UX improvements (#20)
Browse files Browse the repository at this point in the history
### New features
- When adding a new choice, it now empties the 'name' input
- Change choice names by clicking on their header
- Added an 'Add' button for the folder list in Template choices (as per #7)
- Selecting macros is now done through a dropdown
- Only searches for template files in your template folders (auto-detects Templater/core Templates settings)
- Capture choices can now create the file for you, if it doesn't already exist. You can specify a template to create the file with

### Fixes
- Fix modal sizing (now has scrollbar)
- Refactor event listening to activate startup macros
  • Loading branch information
chhoumann authored Jun 21, 2021
1 parent dc0744c commit e61ae33
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 63 deletions.
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.16",
"version": "0.2.0",
"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.16",
"version": "0.2.0",
"description": "Quickly add new pages or content to your vault.",
"main": "main.js",
"scripts": {
Expand Down
17 changes: 16 additions & 1 deletion src/engine/CaptureChoiceEngine.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import type ICaptureChoice from "../types/choices/ICaptureChoice";
import type {App, TFile} from "obsidian";
import {log} from "../logger/logManager";
import GenericInputPrompt from "../gui/GenericInputPrompt/genericInputPrompt";
import {CaptureChoiceFormatter} from "../formatters/captureChoiceFormatter";
import {appendToCurrentLine} from "../utility";
import {MARKDOWN_FILE_EXTENSION_REGEX, VALUE_SYNTAX} from "../constants";
import type QuickAdd from "../main";
import {QuickAddChoiceEngine} from "./QuickAddChoiceEngine";
import {SingleTemplateEngine} from "./SingleTemplateEngine";

export class CaptureChoiceEngine extends QuickAddChoiceEngine {
choice: ICaptureChoice;
private formatter: CaptureChoiceFormatter;
private readonly plugin: QuickAdd;

constructor(app: App, plugin: QuickAdd, choice: ICaptureChoice) {
super(app);
this.choice = choice;
this.plugin = plugin;
this.formatter = new CaptureChoiceFormatter(app, plugin);
}

Expand Down Expand Up @@ -42,6 +44,19 @@ export class CaptureChoiceEngine extends QuickAddChoiceEngine {
const newFileContent: string = await this.formatter.formatContentWithFile(content, this.choice, fileContent, file);

await this.app.vault.modify(file, newFileContent);
} else if (this.choice?.createFileIfItDoesntExist?.enabled) {
const singleTemplateEngine: SingleTemplateEngine =
new SingleTemplateEngine(this.app, this.plugin, this.choice.createFileIfItDoesntExist.template);

const fileContent: string = await singleTemplateEngine.run();
const createdFile: TFile = await this.createFileWithInput(filePath, fileContent);
if (!createdFile) {
log.logError(`could not create '${filePath}.'`);
return;
}

const newFileContent: string = await this.formatter.formatContentWithFile(content, this.choice, fileContent, createdFile);
await this.app.vault.modify(createdFile, newFileContent);
} else {
const formattedContent = await this.formatter.formatContent(content, this.choice);
if (!formattedContent) return;
Expand Down
4 changes: 3 additions & 1 deletion src/engine/SingleTemplateEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ export class SingleTemplateEngine extends TemplateEngine {
throw new Error(`Template ${this.templatePath} not found.`);
}

templateContent = await this.formatter.formatFileContent(templateContent);

if (this.templater) {
templateContent = this.templater.templater.parser.parseTemplates(templateContent);
}

return await this.formatter.formatFileContent(templateContent);
return templateContent;
}
}
54 changes: 52 additions & 2 deletions src/gui/ChoiceBuilder/captureChoiceBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {FormatDisplayFormatter} from "../../formatters/formatDisplayFormatter";
import type QuickAdd from "../../main";
import {FileNameDisplayFormatter} from "../../formatters/fileNameDisplayFormatter";
import {GenericTextSuggester} from "../genericTextSuggester";
import {getTemplatePaths} from "../../utility";

export class CaptureChoiceBuilder extends ChoiceBuilder {
choice: ICaptureChoice;
Expand All @@ -22,8 +23,14 @@ export class CaptureChoiceBuilder extends ChoiceBuilder {
protected display() {
this.contentEl.empty();

this.addCenteredHeader(this.choice.name);
this.addCenteredChoiceNameHeader(this.choice);
this.addCapturedToSetting();
if (!this.choice?.captureToActiveFile) {
this.addCreateIfNotExistsSetting();
if (this.choice?.createFileIfItDoesntExist?.enabled)
this.addCreateWithTemplateSetting();
}

this.addTaskSetting();

if (!this.choice.captureToActiveFile) {
Expand Down Expand Up @@ -51,7 +58,7 @@ export class CaptureChoiceBuilder extends ChoiceBuilder {
captureToActiveFileToggle.onChange(value => {
this.choice.captureToActiveFile = value;

this.display();
this.reload();
});

if (!this.choice?.captureToActiveFile) {
Expand Down Expand Up @@ -156,4 +163,47 @@ export class CaptureChoiceBuilder extends ChoiceBuilder {
const displayFormatter: FormatDisplayFormatter = new FormatDisplayFormatter(this.app, this.plugin);
(async () => formatDisplay.innerText = await displayFormatter.format(this.choice.format.format))();
}

private addCreateIfNotExistsSetting() {
if (!this.choice.createFileIfItDoesntExist)
this.choice.createFileIfItDoesntExist = {enabled: false, createWithTemplate: false, template: ""};

const createFileIfItDoesntExist: Setting = new Setting(this.contentEl);
createFileIfItDoesntExist
.setName("Create file if it doesn't exist")
.addToggle(toggle => toggle
.setValue(this.choice?.createFileIfItDoesntExist?.enabled)
.setTooltip("Create file if it doesn't exist")
.onChange(value => {
this.choice.createFileIfItDoesntExist.enabled = value
this.reload();
})
);
}

private addCreateWithTemplateSetting() {
let templateSelector: TextComponent;
const createWithTemplateSetting = new Setting(this.contentEl);
createWithTemplateSetting.setName("Create file with given template.")
.addToggle(toggle => toggle.setValue(this.choice.createFileIfItDoesntExist?.createWithTemplate)
.onChange(value => {
this.choice.createFileIfItDoesntExist.createWithTemplate = value
templateSelector.setDisabled(!value);
}));

templateSelector = new TextComponent(this.contentEl);
templateSelector.setValue(this.choice?.createFileIfItDoesntExist?.template ?? "")
.setPlaceholder("Template path")
.setDisabled(!this.choice?.createFileIfItDoesntExist?.createWithTemplate);

templateSelector.inputEl.style.width = "100%";
templateSelector.inputEl.style.marginBottom = "8px";

const markdownFiles: string[] = getTemplatePaths(this.app);
new GenericTextSuggester(this.app, templateSelector.inputEl, markdownFiles);

templateSelector.onChange(value => {
this.choice.createFileIfItDoesntExist.template = value;
});
}
}
23 changes: 19 additions & 4 deletions src/gui/ChoiceBuilder/choiceBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {App, Modal, SearchComponent, Setting} from "obsidian";
import type IChoice from "../../types/choices/IChoice";
import type {SvelteComponent} from "svelte";
import {GenericTextSuggester} from "../genericTextSuggester";
import GenericInputPrompt from "../GenericInputPrompt/genericInputPrompt";
import {log} from "../../logger/logManager";

export abstract class ChoiceBuilder extends Modal {
private resolvePromise: (input: IChoice) => void;
Expand All @@ -22,6 +24,7 @@ export abstract class ChoiceBuilder extends Modal {
}
);

this.containerEl.addClass('choiceBuilderModal');
this.open();
}

Expand Down Expand Up @@ -49,10 +52,22 @@ export abstract class ChoiceBuilder extends Modal {
return component;
}

protected addCenteredHeader(header: string): void {
const headerEl = this.contentEl.createEl('h2');
headerEl.style.textAlign = "center";
headerEl.setText(header);
protected addCenteredChoiceNameHeader(choice: IChoice): void {
const headerEl: HTMLHeadingElement = this.contentEl.createEl('h2', {cls: "choiceNameHeader"});
headerEl.setText(choice.name);

headerEl.addEventListener('click', async ev => {
try {
const newName: string = await GenericInputPrompt.Prompt(this.app, choice.name, "Choice name", choice.name);
if (newName !== choice.name) {
choice.name = newName;
headerEl.setText(newName);
}
}
catch (e) {
log.logMessage(`No new name given for ${choice.name}`);
}
});
}

onClose() {
Expand Down
52 changes: 17 additions & 35 deletions src/gui/ChoiceBuilder/macroChoiceBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import {ChoiceBuilder} from "./choiceBuilder";
import type IMacroChoice from "../../types/choices/IMacroChoice";
import type {App} from "obsidian";
import {Setting} from "obsidian";
import {DropdownComponent, Setting} from "obsidian";
import {GenericTextSuggester} from "../genericTextSuggester";
import type {IMacro} from "../../types/macros/IMacro";

export class MacroChoiceBuilder extends ChoiceBuilder {
choice: IMacroChoice;
selectedMacro: IMacro;
private updateSelectedMacro: () => void;

constructor(app: App, choice: IMacroChoice, private macros: IMacro[]) {
super(app);
Expand All @@ -18,45 +16,29 @@ export class MacroChoiceBuilder extends ChoiceBuilder {
}

protected display() {
this.addCenteredHeader(this.choice.name);
this.addSelectedMacroElement();
this.addCenteredChoiceNameHeader(this.choice);
this.addSelectMacroSearch();
}

private addSelectedMacroElement() {
const selectedMacroEl = this.contentEl.createEl('h3');
selectedMacroEl.style.textAlign = "center";
private addSelectMacroSearch() {
const selectMacroDropdownContainer: HTMLDivElement = this.contentEl.createDiv('selectMacroDropdownContainer');
const dropdown: DropdownComponent = new DropdownComponent(selectMacroDropdownContainer);

this.updateSelectedMacro = (() => {
this.selectedMacro = this.macros.find(m => m.id === this.choice.macroId);
let macroOptions: Record<string, string> = {};

if (this.selectedMacro)
selectedMacroEl.textContent = `Selected macro: ${this.selectedMacro.name}`;
this.macros.forEach(macro => {
macroOptions[macro.name] = macro.name;
});

this.updateSelectedMacro();
}
dropdown.addOptions(macroOptions);
dropdown.onChange(value => {
const targetMacro: IMacro = this.macros.find(m => m.name === value);
if (!targetMacro) return;

private addSelectMacroSearch() {
new Setting(this.contentEl)
.setName("Select macro")
.addSearch(searchComponent => {
searchComponent.setPlaceholder("Macro name");
new GenericTextSuggester(this.app, searchComponent.inputEl, this.macros.map(m => m.name));

searchComponent.inputEl.addEventListener('keypress', (e: KeyboardEvent) => {
if (e.key === 'Enter') {
const value: string = searchComponent.getValue();
const macro = this.macros.find(m => m.name === value);
if (!macro) return;

this.choice.macroId = macro.id;
this.updateSelectedMacro();

searchComponent.setValue("");
}
})
})
}
this.choice.macroId = targetMacro.id;
});

const selectedMacro: IMacro = this.macros.find(m => m.id === this.choice.macroId);
if (selectedMacro) dropdown.setValue(selectedMacro.name);
}
}
57 changes: 41 additions & 16 deletions src/gui/ChoiceBuilder/templateChoiceBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import {ChoiceBuilder} from "./choiceBuilder";
import {App, Setting, TextComponent, TFolder} from "obsidian";
import {App, ButtonComponent, Setting, TextComponent, TFolder} from "obsidian";
import type ITemplateChoice from "../../types/choices/ITemplateChoice";
import {FormatSyntaxSuggester} from "../formatSyntaxSuggester";
import {FILE_NAME_FORMAT_SYNTAX} from "../../constants";
import {NewTabDirection} from "../../types/newTabDirection";
import FolderList from "./FolderList.svelte";
import {FileNameDisplayFormatter} from "../../formatters/fileNameDisplayFormatter";
import {ExclusiveSuggester} from "../exclusiveSuggester";
import {log} from "../../logger/logManager";
import {getTemplatePaths} from "../../utility";
import {GenericTextSuggester} from "../genericTextSuggester";

export class TemplateChoiceBuilder extends ChoiceBuilder {
choice: ITemplateChoice;
Expand All @@ -19,7 +22,7 @@ export class TemplateChoiceBuilder extends ChoiceBuilder {
}

protected display() {
this.addCenteredHeader(this.choice.name);
this.addCenteredChoiceNameHeader(this.choice);
this.addTemplatePathSetting();
this.addFileNameFormatSetting();
this.addFolderSetting();
Expand All @@ -33,11 +36,18 @@ export class TemplateChoiceBuilder extends ChoiceBuilder {
private addTemplatePathSetting(): void {
const templatePathSetting = new Setting(this.contentEl)
.setName('Template Path')
.setDesc('Path to the Template.');
.setDesc('Path to the Template.')
.addSearch(search => {
const templates: string[] = getTemplatePaths(this.app);
search.setValue(this.choice.templatePath);
search.setPlaceholder("Template path");

this.addFileSearchInputToSetting(templatePathSetting, this.choice.templatePath, value => {
this.choice.templatePath = value;
});
new GenericTextSuggester(this.app, search.inputEl, templates);

search.onChange(value => {
this.choice.templatePath = value;
})
})
}

private addFileNameFormatSetting(): void {
Expand Down Expand Up @@ -97,26 +107,41 @@ export class TemplateChoiceBuilder extends ChoiceBuilder {

this.svelteElements.push(folderListEl);

const folderInput = new TextComponent(this.contentEl);
folderInput.setPlaceholder("Folder path");
const inputContainer = this.contentEl.createDiv('folderInputContainer');
const folderInput = new TextComponent(inputContainer);
folderInput.inputEl.style.width = "100%";
folderInput.inputEl.style.marginBottom = "8px";
folderInput.setPlaceholder("Folder path");
const folders: string[] = this.app.vault.getAllLoadedFiles()
.filter(f => f instanceof TFolder)
.map(folder => folder.path);

const suggester = new ExclusiveSuggester(this.app, folderInput.inputEl, folders, this.choice.folder.folders);

folderInput.inputEl.addEventListener('keypress', (e: KeyboardEvent) => {
const addFolder = () => {
const input = folderInput.inputEl.value.trim();
if (e.key === 'Enter' && !this.choice.folder.folders.some(folder => folder === input)) {
this.choice.folder.folders.push(input);
folderListEl.updateFolders(this.choice.folder.folders);
folderInput.inputEl.value = "";

suggester.updateCurrentItems(this.choice.folder.folders);
if (this.choice.folder.folders.some(folder => folder === input)) {
log.logWarning("cannot add same folder twice.");
return;
}

this.choice.folder.folders.push(input);
folderListEl.updateFolders(this.choice.folder.folders);
folderInput.inputEl.value = "";

suggester.updateCurrentItems(this.choice.folder.folders);
}

folderInput.inputEl.addEventListener('keypress', (e: KeyboardEvent) => {
if (e.key === 'Enter') {
addFolder();
}
})
});

const addButton: ButtonComponent = new ButtonComponent(inputContainer);
addButton.setCta().setButtonText("Add").onClick(evt => {
addFolder();
});
}

private addAppendLinkSetting(): void {
Expand Down
1 change: 1 addition & 0 deletions src/gui/choiceList/AddChoiceBox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
}
dispatch('addChoice', {name, type});
name = "";
}
Expand Down
Loading

0 comments on commit e61ae33

Please sign in to comment.