From d50a80b0e98ea9c85c300248003f47441857b4af Mon Sep 17 00:00:00 2001 From: FelisDiligens <47528453+FelisDiligens@users.noreply.github.com> Date: Fri, 10 Feb 2023 08:43:40 +0100 Subject: [PATCH] Added the option to pick between panel and dialog --- plugin.config.json | 2 + src/dialog.ts | 35 +++++++++++++ src/index.ts | 114 +++++++++++++++++++++++++++++++++++++---- src/panel.ts | 6 +-- src/settings.ts | 40 +++++++++++++++ src/utils/dialogs.ts | 35 ++++++++++++- src/utils/panels.ts | 1 + src/webview_dialog.css | 16 ++++++ 8 files changed, 235 insertions(+), 14 deletions(-) create mode 100644 src/dialog.ts create mode 100644 src/settings.ts diff --git a/plugin.config.json b/plugin.config.json index c29dd3a..5e56d5a 100644 --- a/plugin.config.json +++ b/plugin.config.json @@ -2,6 +2,8 @@ "extraScripts": [ "cmPlugin.ts", "panel.ts", + "dialog.ts", + "settings.ts", "utils/utils.ts", "utils/joplinUtils.ts", "utils/dialogs.ts", diff --git a/src/dialog.ts b/src/dialog.ts new file mode 100644 index 0000000..bb95f3e --- /dev/null +++ b/src/dialog.ts @@ -0,0 +1,35 @@ +export function getDialogHTML() { + return ` +
+

Search and replace

+
+ + + + + + + +
+ + + + + + +
Options: +
+
+
+ +
+
+
+ +
+
+ +
`; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 42e7bd5..1547765 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,24 @@ import joplin from "api"; import { ContentScriptType, MenuItemLocation } from "api/types"; +import { getDialogHTML } from "./dialog"; import { getPanelHTML } from "./panel"; +import { getSettings, registerAllSettings } from "./settings"; import { Dialog } from "./utils/dialogs"; import { getEditorState } from "./utils/joplinUtils"; import { Panel } from "./utils/panels"; import { prepareRegex, sanitizeHTML } from "./utils/utils"; let dialogAlert: Dialog; +let dialogSAR: Dialog; +let dialogSARLastFormData; let panel: Panel; let selectedText: string = ""; joplin.plugins.register({ onStart: async function () { + // Register this plugin's settings, so the user can access them: + await registerAllSettings(); + /* Create 'Search and Replace' panel */ @@ -56,11 +63,39 @@ joplin.plugins.register({ } }); + /* + Create "Search & replace" dialog + */ + dialogSAR = new Dialog(); + await dialogSAR.create(); + await dialogSAR.addScript("./webview_dialog.css"); + await dialogSAR.setButtons([ + {id: "replaceNext", title: "Replace next"}, + {id: "replaceAll", title: "Replace all"}, + {id: "cancel"} + ]); + dialogSAR.addPositiveIds("replaceNext", "replaceAll"); + dialogSAR.template = getDialogHTML(); + dialogSAR.setDefaultFormData({ + "pattern-txt": "", + "replacement-txt": "", + "wrap-chk": "off", + "matchcase-chk": "off", + "matchwholeword-chk": "off", + "preservecase-chk": "off", + "matchmethod": "literal" + }); + dialogSARLastFormData = { + ...dialogSAR.getDefaultFormData(), + "matchcase-chk": "on" + }; + /* Create alert dialog */ dialogAlert = new Dialog(); await dialogAlert.create(); + await dialogAlert.addScript("./webview_dialog.css"); await dialogAlert.setButtons([{ id: "ok" }]); dialogAlert.template = `
@@ -81,18 +116,77 @@ joplin.plugins.register({ // Save 'selectedText' for later: selectedText = await joplin.commands.execute("selectedText"); - // If panel not visible: - let isVisible = await panel.visible(); - if (!isVisible) { - panel.setHtml(await getPanelHTML()); + // Get the user preference (panel or dialog) + let guiPreference = (await getSettings()).SARGUIPreference; - // Open panel: - await panel.show(); - } + /* + Open a panel: + */ + if (guiPreference == "panel") { + // If panel not visible: + let isVisible = await panel.visible(); + if (!isVisible) { + panel.setHtml(await getPanelHTML()); + + // Open panel: + await panel.show(); + } + + // Set the text in the panel to the selectedText: + if (selectedText.length > 0) { + panel.postMessage({ name: "SARPanel.setText", value: selectedText }); + } + + /* + Open a dialog: + */ + } else if (guiPreference == "dialog") { + // "Recall" last form data: + dialogSAR.useTemplate({ + "pattern": selectedText.length > 0 ? sanitizeHTML(selectedText) : sanitizeHTML(dialogSARLastFormData["pattern-txt"]), + "replacement": sanitizeHTML(dialogSARLastFormData["replacement-txt"]), + "wrap": dialogSARLastFormData["wrap-chk"] == "on" ? "checked" : "", + "matchcase": dialogSARLastFormData["matchcase-chk"] == "on" ? "checked" : "", + "matchwholeword": dialogSARLastFormData["matchwholeword-chk"] == "on" ? "checked" : "", + "preservecase": dialogSARLastFormData["preservecase-chk"] == "on" ? "checked" : "", + "matchmethod-literal": dialogSARLastFormData["matchmethod"] == "literal" ? "checked" : "", + "matchmethod-wildcards": dialogSARLastFormData["matchmethod"] == "wildcards" ? "checked" : "", + "matchmethod-regex": dialogSARLastFormData["matchmethod"] == "regex" ? "checked" : "" + }); - // Set the text in the panel to the selectedText: - if (selectedText.length > 0) { - panel.postMessage({ name: "SARPanel.setText", value: selectedText }); + // Open the dialog: + await dialogSAR.open(); + let result = dialogSAR.getPreparedDialogResult(); + + // "Memorize" form data: + dialogSARLastFormData = result.formData; + + if (result.confirm) { + let form = { + searchPattern: result.formData["pattern-txt"], + replacement: result.formData["replacement-txt"], + options: { + wrapAround: result.formData["wrap-chk"] == "on", + matchCase: result.formData["matchcase-chk"] == "on", + matchWholeWord: result.formData["matchwholeword-chk"] == "on", + matchMethod: result.formData["matchmethod"], + preserveCase: result.formData["preservecase-chk"] == "on" + }, + } + + switch (result.id) { + case "replaceNext": + return await joplin.commands.execute('editor.execCommand', { + name: "SARPlugin.replace", + args: [form], + }); + case "replaceAll": + return await joplin.commands.execute('editor.execCommand', { + name: "SARPlugin.replaceAll", + args: [form], + }); + } + } } }, }); diff --git a/src/panel.ts b/src/panel.ts index b71dbbc..c72494a 100644 --- a/src/panel.ts +++ b/src/panel.ts @@ -59,9 +59,9 @@ export async function getPanelHTML() { -
-
- +
+
+ diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..5d5404e --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,40 @@ +import joplin from "api"; +import { SettingItemType } from "api/types"; + +/** + * Returns all settings the user can/has set. +*/ +export async function getSettings() { + return { + "SARGUIPreference": await joplin.settings.value('SARGUIPreference') + } +} + +/** + * Register this plugin"s settings to Joplin. + */ +export async function registerAllSettings() { + const section = "SAROptions"; + + await joplin.settings.registerSection(section, { + label: "Search & Replace", + description: "Search & Replace", + iconName: "fas fa-search" + }); + + await joplin.settings.registerSettings({ + ["SARGUIPreference"]: { + public: true, + section: section, + type: SettingItemType.String, + isEnum: true, + value: "panel", + label: "GUI Preference", + description: "If you don't like the panel, you can switch to a popup dialog instead.", + options: { + "panel": "Open a panel (default, recommended)", + "dialog": "Open a dialog (like in previous versions)" + }, + } + }); +} \ No newline at end of file diff --git a/src/utils/dialogs.ts b/src/utils/dialogs.ts index e08fed1..056be29 100644 --- a/src/utils/dialogs.ts +++ b/src/utils/dialogs.ts @@ -20,7 +20,16 @@ export class Dialog { public async create() { this.viewHandle = await joplin.views.dialogs.create(this.id); - await joplin.views.dialogs.addScript(this.viewHandle, './webview_dialog.css'); + // await joplin.views.dialogs.addScript(this.viewHandle, './webview_dialog.css'); + } + + /** + * Adds and loads new JS or CSS files into the dialog. + * @see {@link joplin.views.dialogs.addScript} + * @param script + */ + public async addScript(script: string) { + await joplin.views.dialogs.addScript(this.viewHandle, script); } /** @@ -32,6 +41,9 @@ export class Dialog { return await joplin.views.dialogs.setHtml(this.viewHandle, html); } + /** + * Uses a template for the HTML content and inserts variables. + */ public async useTemplate(obj: {} = {}) { let html = this.template; for (var key of Object.keys(obj)) { @@ -51,6 +63,9 @@ export class Dialog { return await joplin.views.dialogs.setButtons(this.viewHandle, buttons); } + /** + * Adds IDs of dialog buttons that should be interpreted as 'true'. + */ public addPositiveIds(...ids: string[]) { this.positiveIds.push(...ids); } @@ -64,10 +79,16 @@ export class Dialog { return this.dialogResult; } + /** + * Returns the last (raw) dialog result. + */ public getDialogResult(): DialogResult { return this.dialogResult; } + /** + * Returns the last dialog result that has been prepared. + */ public getPreparedDialogResult(defaultFormData: Object = this.defaultFormData): {id: string, confirm: boolean, formData: Object} { return { "id": this.getPressedButton(), @@ -84,6 +105,11 @@ export class Dialog { this.defaultFormData = defaultFormData; } + /** + * By default, Joplin only returns form data that has been changed. This function returns a complete form data with defaults filled in. + * @see setDefaultFormData + * @see getDefaultFormData + */ public getFormData(defaultFormData: Object = this.defaultFormData): Object { let formData = Object.assign({}, defaultFormData); if (this.dialogResult.formData) { @@ -92,10 +118,17 @@ export class Dialog { return formData; } + /** + * Returns the ID of the last pressed button. + */ public getPressedButton(): string { return this.dialogResult.id; } + /** + * Returns a boolean depending on whether the ID of the last pressed button is in `Dialog.positiveIds`. + * @see {addPositiveIds} to add IDs that should be interpreted as `true` + */ public getAnswer(): boolean { return this.positiveIds.includes(this.getPressedButton()); } diff --git a/src/utils/panels.ts b/src/utils/panels.ts index c5aba44..4465e73 100644 --- a/src/utils/panels.ts +++ b/src/utils/panels.ts @@ -35,6 +35,7 @@ export class Panel { /** * Adds (and loads when created) new JS or CSS files into the panel. + * @see {@link joplin.views.panels.addScript} * @param script */ public async addScript(script: string) { diff --git a/src/webview_dialog.css b/src/webview_dialog.css index 92e9417..1236855 100644 --- a/src/webview_dialog.css +++ b/src/webview_dialog.css @@ -1,4 +1,20 @@ #joplin-plugin-content { min-width: 400px; font-size: 10pt; + font-family: Roboto; +} + +input[type="text"] { + width: 380px; + display: inline-block; + box-sizing: border-box; + color: var(--joplin-color); + background-color: var(--joplin-background-color); + border: 1px solid rgba(136, 136, 136, 0.3); + padding: 4px 6px; + border-radius: 3px; +} + +td { + vertical-align: top; } \ No newline at end of file