Skip to content

Commit

Permalink
Added the option to pick between panel and dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
FelisDiligens committed Feb 10, 2023
1 parent 0e5f769 commit d50a80b
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 14 deletions.
2 changes: 2 additions & 0 deletions plugin.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"extraScripts": [
"cmPlugin.ts",
"panel.ts",
"dialog.ts",
"settings.ts",
"utils/utils.ts",
"utils/joplinUtils.ts",
"utils/dialogs.ts",
Expand Down
35 changes: 35 additions & 0 deletions src/dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export function getDialogHTML() {
return `
<div>
<h3>Search and replace</h3>
<form>
<table>
<tr>
<td><input class="expand" type="text" name="pattern-txt" value="{pattern}" placeholder="Find"></td>
</tr>
<tr>
<td><input class="expand" type="text" name="replacement-txt" value="{replacement}" placeholder="Replace"></td>
</tr>
</table>
<table>
<tr>
<td>Options:</td>
<td>
<input type="checkbox" id="wrap-chk" name="wrap-chk" {wrap}><label for="wrap-chk">Wrap around</label><br>
<input type="checkbox" id="matchcase-chk" name="matchcase-chk" {matchcase}><label for="matchcase-chk">Match case</label><br>
<input type="checkbox" id="matchwholeword-chk" name="matchwholeword-chk" {matchwholeword}><label for="matchwholeword-chk">Match whole words only</label><br>
<input type="checkbox" id="preservecase-chk" name="preservecase-chk" {preservecase}><label for="preservecase-chk">Preserve case</label>
</td>
<td>
<input type="radio" id="useliteralsearch-rad" name="matchmethod" value="literal" {matchmethod-literal}><label for="useliteralsearch-rad" checked>Literal search</label><br>
<input type="radio" id="usewildcards-rad" name="matchmethod" value="wildcards" {matchmethod-wildcards}><label for="usewildcards-rad">Use Wildcards</label><br>
<input type="radio" id="useregex-rad" name="matchmethod" value="regex" {matchmethod-regex}><label for="useregex-rad">Use Regular Expression</label>
</td>
</tr>
</table>
</form>
<!--<p class="small-text">
If you enable regular expressions, it's going to use JavaScript regex. See MDN docs to learn more.
</p>-->
</div>`;
}
114 changes: 104 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -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
*/
Expand Down Expand Up @@ -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 = `
<div>
Expand All @@ -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],
});
}
}
}
},
});
Expand Down
6 changes: 3 additions & 3 deletions src/panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ export async function getPanelHTML() {
<input type="checkbox" id="preservecase-chk"><label for="preservecase-chk">Preserve case</label>
</td>
<td>
<input type="radio" id="useliteralsearch-rad" name="matchoptions" checked><label for="useliteralsearch-rad" checked>Literal search</label><br>
<input type="radio" id="usewildcards-rad" name="matchoptions"><label for="usewildcards-rad">Use Wildcards<sup>2</sup></label><br>
<input type="radio" id="useregex-rad" name="matchoptions"><label for="useregex-rad">Use Regular Expression<sup>3</sup></label>
<input type="radio" id="useliteralsearch-rad" name="matchmethod" checked><label for="useliteralsearch-rad" checked>Literal search</label><br>
<input type="radio" id="usewildcards-rad" name="matchmethod"><label for="usewildcards-rad">Use Wildcards<sup>2</sup></label><br>
<input type="radio" id="useregex-rad" name="matchmethod"><label for="useregex-rad">Use Regular Expression<sup>3</sup></label>
</td>
</tr>
</table>
Expand Down
40 changes: 40 additions & 0 deletions src/settings.ts
Original file line number Diff line number Diff line change
@@ -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)"
},
}
});
}
35 changes: 34 additions & 1 deletion src/utils/dialogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand All @@ -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)) {
Expand All @@ -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);
}
Expand All @@ -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(),
Expand All @@ -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) {
Expand All @@ -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());
}
Expand Down
1 change: 1 addition & 0 deletions src/utils/panels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
16 changes: 16 additions & 0 deletions src/webview_dialog.css
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit d50a80b

Please sign in to comment.