diff --git a/jsEngine/api/Internal.ts b/jsEngine/api/Internal.ts index 6ac8f63..d5d4ff8 100644 --- a/jsEngine/api/Internal.ts +++ b/jsEngine/api/Internal.ts @@ -264,7 +264,7 @@ export class InternalAPI { * Runs all startup scripts defined in the plugins settings. */ public async executeStartupScripts(): Promise { - for (const script of this.apiInstance.plugin.settings.startupScripts ?? []) { + for (const script of this.apiInstance.plugin.settings.startupScripts) { await this.executeFileSimple(script); } } diff --git a/jsEngine/obsidian-ex.d.ts b/jsEngine/obsidian-ex.d.ts index 907e788..efa5e52 100644 --- a/jsEngine/obsidian-ex.d.ts +++ b/jsEngine/obsidian-ex.d.ts @@ -8,6 +8,12 @@ declare module 'obsidian' { plugins: Record; getPlugin: (plugin: string) => Plugin; }; + + /** + * Open a file or folder with the systems default app for it. + * @param path a normalized path to open + */ + openWithDefaultApp(path: string): void; } interface MenuItem { diff --git a/jsEngine/settings/Settings.ts b/jsEngine/settings/Settings.ts index b2a50c6..4f96fa6 100644 --- a/jsEngine/settings/Settings.ts +++ b/jsEngine/settings/Settings.ts @@ -1,13 +1,14 @@ import type JsEnginePlugin from 'jsEngine/main'; -import { StartupScriptsModal } from 'jsEngine/settings/StartupScriptModal'; import type { App } from 'obsidian'; -import { PluginSettingTab, Setting } from 'obsidian'; +import { normalizePath, PluginSettingTab, Setting, TFile, TFolder } from 'obsidian'; export interface JsEnginePluginSettings { - startupScripts?: string[]; + startupScriptsDirectory: string | undefined; + startupScripts: string[]; } export const JS_ENGINE_DEFAULT_SETTINGS: JsEnginePluginSettings = { + startupScriptsDirectory: undefined, startupScripts: [], }; @@ -20,18 +21,126 @@ export class JsEnginePluginSettingTab extends PluginSettingTab { } display(): void { - this.containerEl.empty(); + const containerEl = this.containerEl; + const vault = this.app.vault; + const settings = this.plugin.settings; + containerEl.empty(); - if (!this.plugin.settings) { + if (!settings) { return; } - // this.containerEl.createEl('p', { text: 'Currently Empty, but there will be stuff here later.' }); + new Setting(containerEl) + .setName('JS snippets (loaded on startup)') + .setHeading() + .addExtraButton(el => { + el.setTooltip('Reload snippets') + .setIcon('refresh-cw') + .onClick(() => this.display()); + }) + .addExtraButton(el => { + el.setTooltip('Open snippets folder') + .setIcon('folder-open') + .onClick(async () => await this.openStartupScriptsDirectory()); + }); - new Setting(this.containerEl).setName('Startup scripts').addButton(button => { - button.setButtonText('Manage').onClick(() => { - new StartupScriptsModal(this.plugin).open(); + new Setting(containerEl) + .setName('Custom JS Snippets Folder') + .setDesc('The folder to search for JavaScript files to load') + .addText(el => { + el.setPlaceholder('Folder') + .setValue(settings.startupScriptsDirectory ?? '') + .inputEl.addEventListener('focusout', ev => { + const target = ev.currentTarget as HTMLInputElement; + settings.startupScriptsDirectory = target.value ? normalizePath(target.value) : JS_ENGINE_DEFAULT_SETTINGS.startupScriptsDirectory; + void this.plugin.saveSettings(); + this.display(); + }); }); - }); + + const startupScriptsDirectory = vault.getFolderByPath(settings.startupScriptsDirectory ?? '/'); + let startupScripts: TFile[] = []; + if (startupScriptsDirectory != null) { + startupScripts = this.listJSfilesInDirectory(startupScriptsDirectory); + } + if (startupScripts.length == 0) { + new Setting(containerEl).setName('No JS snippets found').setDesc(`JS snippets are stored in "vault/${settings.startupScriptsDirectory ?? ''}"`); + } else { + for (const file of startupScripts) { + new Setting(containerEl) + .setName(file.basename) + .setDesc(`Apply JS snippet from "vault/${file.path}"`) + .addToggle(el => { + el.setValue(settings.startupScripts.contains(file.path)).onChange(async val => this.toggleStartupScript(file, val)); + }); + } + } + + const oldScripts = settings.startupScripts + .map(file => vault.getFileByPath(file)!) + .filter(file => !this.isParentDir(file, settings.startupScriptsDirectory ?? '/')); + if (oldScripts.length > 0) { + this.containerEl.createEl('div', { cls: 'callout js-engine-settings-warning', text: 'These scripts are not in the Snippets Folder' }); + for (const file of oldScripts) { + new Setting(containerEl) + .setName(file.basename) + .setDesc(`Apply JS snippet from "vault/${file.path}"`) + .addExtraButton(el => { + el.setTooltip('Move to current Snippets Folder') + .setIcon('archive-restore') + .onClick(async () => await this.moveStartupScriptToNewDirectory(file)); + }) + .addToggle(el => { + el.setValue(settings.startupScripts.contains(file.path)).onChange(async val => this.toggleStartupScript(file, val)); + }); + } + } + } + + async openStartupScriptsDirectory(): Promise { + const vault = this.app.vault; + const directory = this.plugin.settings.startupScriptsDirectory ?? '/'; + if (!(await vault.adapter.exists(directory))) { + await vault.createFolder(directory); + } + this.app.openWithDefaultApp(directory); + } + + listJSfilesInDirectory(directory: TFolder): TFile[] { + const files = directory.children.filter(el => el instanceof TFile); + const folders = directory.children.filter(el => el instanceof TFolder); + return files.filter(f => f.extension == 'js').concat(folders.flatMap(dir => this.listJSfilesInDirectory(dir))); + } + + isParentDir(pathnode: TFile | TFolder, parent: string): boolean { + if (pathnode.parent == null) return false; + if (pathnode.parent.path == parent) return true; + return this.isParentDir(pathnode.parent, parent); + } + + async toggleStartupScript(file: TFile, enable: boolean): Promise { + const settings = this.plugin.settings; + if (enable) { + settings.startupScripts.push(file.path); + void this.plugin.api.internal.executeFileSimple(file.path); + } else { + settings.startupScripts.remove(file.path); + } + await this.plugin.saveSettings(); + } + + async moveStartupScriptToNewDirectory(script: TFile): Promise { + const settings = this.plugin.settings; + const vault = this.app.vault; + const startupScriptsDirectory = settings.startupScriptsDirectory ?? '/'; + const newPath = startupScriptsDirectory.concat('/', script.name); + if ((await vault.adapter.exists(startupScriptsDirectory)) == false) { + await vault.createFolder(startupScriptsDirectory); + } + settings.startupScripts.remove(script.path); + await this.app.vault.rename(script, newPath); + settings.startupScripts.push(newPath); + await this.plugin.saveSettings(); + this.display(); } } diff --git a/jsEngine/settings/StartupScriptModal.ts b/jsEngine/settings/StartupScriptModal.ts deleted file mode 100644 index 6e9357d..0000000 --- a/jsEngine/settings/StartupScriptModal.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type JsEnginePlugin from 'jsEngine/main'; -import StartupScripts from 'jsEngine/settings/StartupScripts.svelte'; -import { Modal } from 'obsidian'; -import { mount, unmount } from 'svelte'; - -export class StartupScriptsModal extends Modal { - plugin: JsEnginePlugin; - component?: ReturnType; - - constructor(plugin: JsEnginePlugin) { - super(plugin.app); - this.plugin = plugin; - } - - onOpen(): void { - this.contentEl.empty(); - - this.component = mount(StartupScripts, { - target: this.contentEl, - props: { - modal: this, - startupScripts: this.plugin.settings.startupScripts ?? [], - }, - }); - } - - onClose(): void { - if (this.component) { - unmount(this.component); - } - } - - save(startupScripts: string[]): void { - this.plugin.settings.startupScripts = startupScripts; - void this.plugin.saveSettings(); - } -} diff --git a/jsEngine/settings/StartupScripts.svelte b/jsEngine/settings/StartupScripts.svelte deleted file mode 100644 index fc067d5..0000000 --- a/jsEngine/settings/StartupScripts.svelte +++ /dev/null @@ -1,91 +0,0 @@ - - -
-
- {#each startupScripts as script, i} -
- {script} - -
- {/each} -
- - - - - - - -
diff --git a/styles.css b/styles.css index 545aebb..22e9978 100644 --- a/styles.css +++ b/styles.css @@ -175,3 +175,12 @@ If your plugin does not need CSS, delete this file. left: 0; right: 0; } + +.js-engine-settings-warning { + --callout-color: var(--callout-warning); + color: rgb(var(--callout-color)); + margin-bottom: 0; + padding-block: 0.25em; + border-bottom-left-radius: unset; + border-bottom-right-radius: unset; +}