From 98fa74ed896ffaa9c92a5cac158ca38e9cae36d2 Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Mon, 7 Oct 2024 21:05:44 +0200
Subject: [PATCH] refactor: template picker
Use picker window to replace the PromptManager from toolkit
---
.../chrome/content/styles/templatePicker.css | 19 +++
addon/chrome/content/templatePicker.xhtml | 58 ++++++++
package-lock.json | 10 +-
package.json | 2 +-
src/addon.ts | 5 -
src/api.ts | 6 +
src/extras/templatePicker.ts | 133 ++++++++++++++++++
src/hooks.ts | 8 +-
src/modules/template/controller.ts | 19 +--
src/modules/template/picker.ts | 93 +++++-------
src/utils/templatePicker.ts | 29 ++++
src/utils/ztoolkit.ts | 3 -
12 files changed, 291 insertions(+), 94 deletions(-)
create mode 100644 addon/chrome/content/styles/templatePicker.css
create mode 100644 addon/chrome/content/templatePicker.xhtml
create mode 100644 src/extras/templatePicker.ts
create mode 100644 src/utils/templatePicker.ts
diff --git a/addon/chrome/content/styles/templatePicker.css b/addon/chrome/content/styles/templatePicker.css
new file mode 100644
index 0000000..44d16b1
--- /dev/null
+++ b/addon/chrome/content/styles/templatePicker.css
@@ -0,0 +1,19 @@
+dialog {
+ -moz-window-dragging: drag;
+ max-height: 700px;
+}
+
+.viewport-container {
+ -moz-window-dragging: no-drag;
+}
+
+.viewport {
+ width: 100%;
+}
+
+#table-container {
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+ background: var(--material-background);
+}
diff --git a/addon/chrome/content/templatePicker.xhtml b/addon/chrome/content/templatePicker.xhtml
new file mode 100644
index 0000000..2c9e3a5
--- /dev/null
+++ b/addon/chrome/content/templatePicker.xhtml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 8df85c0..dc05031 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -35,7 +35,7 @@
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.1",
"yamljs": "^0.3.0",
- "zotero-plugin-toolkit": "^4.0.4"
+ "zotero-plugin-toolkit": "^4.0.6"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
@@ -13588,14 +13588,14 @@
}
},
"node_modules/zotero-plugin-toolkit": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/zotero-plugin-toolkit/-/zotero-plugin-toolkit-4.0.4.tgz",
- "integrity": "sha512-FjuyVt6q5uPmQiYCkT9LjvAMpDBAvPfVGsYAhFNN6tTJbiE9YsHIl2XZIfinEAIVAqYd+YPglXaK1pw/TUNuFQ==",
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/zotero-plugin-toolkit/-/zotero-plugin-toolkit-4.0.6.tgz",
+ "integrity": "sha512-juxIrSrUYTxk+efQJAH7OpfQHSboErMd0Ygu2eZs/xKA1177XlCYhe0sdxlTxI8Y/4UGtRwLicThUddaOfArMQ==",
"dependencies": {
"zotero-types": "^2.2.0"
},
"engines": {
- "node": ">=16"
+ "node": ">=18"
}
},
"node_modules/zotero-types": {
diff --git a/package.json b/package.json
index 3d3f2f1..06c6a3f 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.1",
"yamljs": "^0.3.0",
- "zotero-plugin-toolkit": "^4.0.4"
+ "zotero-plugin-toolkit": "^4.0.6"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
diff --git a/src/addon.ts b/src/addon.ts
index fd7cd77..069deca 100644
--- a/src/addon.ts
+++ b/src/addon.ts
@@ -1,4 +1,3 @@
-import { Prompt, PromptManager } from "zotero-plugin-toolkit";
import { VirtualizedTableHelper } from "zotero-plugin-toolkit";
import { LargePrefHelper } from "zotero-plugin-toolkit";
@@ -72,7 +71,6 @@ class Addon {
worker?: Worker;
};
imageCache: Record;
- readonly prompt?: Prompt;
hint: {
silent: boolean;
};
@@ -120,9 +118,6 @@ class Addon {
},
relation: {},
imageCache: {},
- get prompt() {
- return new PromptManager().prompt;
- },
hint: {
silent: false,
},
diff --git a/src/api.ts b/src/api.ts
index 28d86e4..e7ecdcc 100644
--- a/src/api.ts
+++ b/src/api.ts
@@ -81,6 +81,7 @@ import {
updateNoteLinkRelation,
} from "./utils/relation";
import { getWorkspaceByTabID, getWorkspaceByUID } from "./utils/workspace";
+import { getString } from "./utils/locale";
const workspace = {
getWorkspaceByTabID,
@@ -178,6 +179,10 @@ const relation = {
getAnnotationByLinkTarget,
};
+const utils = {
+ getString,
+};
+
export default {
workspace,
sync,
@@ -188,4 +193,5 @@ export default {
editor,
note,
relation,
+ utils,
};
diff --git a/src/extras/templatePicker.ts b/src/extras/templatePicker.ts
new file mode 100644
index 0000000..f0afc3e
--- /dev/null
+++ b/src/extras/templatePicker.ts
@@ -0,0 +1,133 @@
+import { VirtualizedTableHelper } from "zotero-plugin-toolkit";
+import { config } from "../../package.json";
+
+document.addEventListener("DOMContentLoaded", (ev) => {
+ init();
+});
+
+document.addEventListener("dialogaccept", () => accept());
+
+const args = window.arguments[0] as any;
+const templateData = args.templates;
+const multiSelect = args.multiSelect;
+let tableHelper: VirtualizedTableHelper;
+
+function init() {
+ args._initPromise.resolve();
+ initTable();
+}
+
+function accept() {
+ const selected = tableHelper.treeInstance.selection.selected;
+ args.selected = Array.from(selected).map(
+ (index: number) => templateData[index],
+ );
+}
+
+const getString = (Zotero[config.addonRef] as typeof addon).api.utils.getString;
+
+function initTable() {
+ tableHelper = new VirtualizedTableHelper(window)
+ .setContainerId("table-container")
+ .setProp({
+ id: "templates-table",
+ // Do not use setLocale, as it modifies the Zotero.Intl.strings
+ // Set locales directly to columns
+ columns: [
+ {
+ dataKey: "type",
+ label: "templateEditor-templateType",
+ width: 60,
+ fixedWidth: true,
+ },
+ {
+ dataKey: "name",
+ label: "templateEditor-templateName",
+ fixedWidth: false,
+ },
+ ].map((column) =>
+ Object.assign(column, {
+ label: getString(column.label),
+ }),
+ ),
+ showHeader: true,
+ multiSelect: multiSelect,
+ staticColumns: true,
+ disableFontSizeScaling: true,
+ })
+ .setProp("getRowCount", () => templateData.length)
+ .setProp("getRowData", getRowData)
+ .setProp("getRowString", (index) => templateData[index] || "")
+ .setProp("renderItem", (index, selection, oldElem, columns) => {
+ let div;
+ if (oldElem) {
+ div = oldElem;
+ div.innerHTML = "";
+ } else {
+ div = document.createElement("div");
+ div.className = "row";
+ }
+
+ div.classList.toggle("selected", selection.isSelected(index));
+ div.classList.toggle("focused", selection.focused == index);
+ const rowData = getRowData(index);
+
+ for (const column of columns) {
+ const span = document.createElement("span");
+ // @ts-ignore
+ span.className = `cell ${column?.className}`;
+ const cellData = rowData[column.dataKey as keyof typeof rowData];
+ span.textContent = cellData;
+ if (column.dataKey === "type") {
+ span.style.backgroundColor = getRowLabelColor(cellData);
+ span.style.borderRadius = "4px";
+ span.style.paddingInline = "4px";
+ span.style.marginInline = "2px -2px";
+ span.style.textAlign = "center";
+ span.textContent = getString(
+ "templateEditor-templateDisplayType",
+ cellData,
+ );
+ }
+ div.append(span);
+ }
+ return div;
+ })
+ .render();
+}
+
+function getRowData(index: number) {
+ const rowData = templateData[index];
+ if (!rowData) {
+ return {
+ name: "",
+ type: "unknown",
+ };
+ }
+ let templateType = "unknown";
+ let templateDisplayName = rowData;
+ if (rowData.toLowerCase().startsWith("[item]")) {
+ templateType = "item";
+ templateDisplayName = rowData.slice(6);
+ } else if (rowData.toLowerCase().startsWith("[text]")) {
+ templateType = "text";
+ templateDisplayName = rowData.slice(6);
+ }
+ return {
+ name: templateDisplayName,
+ type: templateType,
+ };
+}
+
+function getRowLabelColor(type: string) {
+ switch (type) {
+ case "system":
+ return "var(--accent-yellow)";
+ case "item":
+ return "var(--accent-green)";
+ case "text":
+ return "var(--accent-azure)";
+ default:
+ return "var(--accent-red)";
+ }
+}
diff --git a/src/hooks.ts b/src/hooks.ts
index c1c3f83..92ef502 100644
--- a/src/hooks.ts
+++ b/src/hooks.ts
@@ -23,10 +23,7 @@ import {
syncAnnotationNoteTags,
} from "./modules/annotationNote";
import { setSyncing, callSyncing } from "./modules/sync/hooks";
-import {
- showTemplatePicker,
- updateTemplatePicker,
-} from "./modules/template/picker";
+import { showTemplatePicker } from "./modules/template/picker";
import { showImageViewer } from "./modules/imageViewer";
import { showExportNoteOptions } from "./modules/export/exportWindow";
import { showSyncDiff } from "./modules/sync/diffWindow";
@@ -241,8 +238,6 @@ const onSyncing = callSyncing;
const onShowTemplatePicker = showTemplatePicker;
-const onUpdateTemplatePicker = updateTemplatePicker;
-
const onImportTemplateFromClipboard = importTemplateFromClipboard;
const onRefreshTemplatesInNote = refreshTemplatesInNote;
@@ -280,7 +275,6 @@ export default {
onInitWorkspace,
onSyncing,
onShowTemplatePicker,
- onUpdateTemplatePicker,
onImportTemplateFromClipboard,
onRefreshTemplatesInNote,
onShowImageViewer,
diff --git a/src/modules/template/controller.ts b/src/modules/template/controller.ts
index 136f349..6ebe200 100644
--- a/src/modules/template/controller.ts
+++ b/src/modules/template/controller.ts
@@ -34,10 +34,9 @@ function initTemplates() {
const templateKeys = getTemplateKeys();
for (const defaultTemplate of addon.api.template.DEFAULT_TEMPLATES) {
if (!templateKeys.includes(defaultTemplate.name)) {
- setTemplate(defaultTemplate, false);
+ setTemplate(defaultTemplate);
}
}
- addon.hooks.onUpdateTemplatePicker();
}
function getTemplateKeys(): string[] {
@@ -52,27 +51,15 @@ function getTemplateText(keyName: string): string {
return addon.data.template.data?.getValue(keyName) || "";
}
-function setTemplate(
- template: NoteTemplate,
- updatePrompt: boolean = true,
-): void {
+function setTemplate(template: NoteTemplate): void {
addon.data.template.data?.setValue(template.name, template.text);
- if (updatePrompt) {
- addon.hooks.onUpdateTemplatePicker();
- }
}
-function removeTemplate(
- keyName: string | undefined,
- updatePrompt: boolean = true,
-): void {
+function removeTemplate(keyName: string | undefined): void {
if (!keyName) {
return;
}
addon.data.template.data?.deleteKey(keyName);
- if (updatePrompt) {
- addon.hooks.onUpdateTemplatePicker();
- }
}
function importTemplateFromClipboard(text?: string) {
diff --git a/src/modules/template/picker.ts b/src/modules/template/picker.ts
index 220c428..35299c5 100644
--- a/src/modules/template/picker.ts
+++ b/src/modules/template/picker.ts
@@ -1,78 +1,57 @@
-import { Prompt } from "zotero-plugin-toolkit";
import { addLineToNote } from "../../utils/note";
import { getString } from "../../utils/locale";
+import { openTemplatePicker } from "../../utils/templatePicker";
-export { updateTemplatePicker, showTemplatePicker };
+export { showTemplatePicker };
-function showTemplatePicker(
+async function showTemplatePicker(
mode: "insert",
data?: { noteId?: number; lineIndex?: number },
-): void;
-function showTemplatePicker(
+): Promise;
+async function showTemplatePicker(
mode: "create",
data?: {
noteType?: "standalone" | "item";
parentItemId?: number;
topItemIds?: number[];
},
-): void;
-function showTemplatePicker(mode: "export", data?: Record): void;
-function showTemplatePicker(): void;
-function showTemplatePicker(
+): Promise;
+async function showTemplatePicker(
+ mode: "export",
+ data?: Record,
+): Promise;
+async function showTemplatePicker(): Promise;
+async function showTemplatePicker(
mode: typeof addon.data.template.picker.mode = "insert",
data: Record = {},
) {
- if (addon.data.prompt) {
- addon.data.template.picker.mode = mode;
- addon.data.template.picker.data = data;
- addon.data.prompt.promptNode.style.display = "flex";
- addon.data.prompt.showCommands(
- addon.data.prompt.commands.filter(
- (cmd) => cmd.label === "BNotes Template",
- ),
- );
+ addon.data.template.picker.mode = mode;
+ addon.data.template.picker.data = data;
+ const selected = await openTemplatePicker();
+ if (!selected.length) {
+ return;
}
+ const name = selected[0];
+ await handleTemplateOperation(name);
}
-function updateTemplatePicker() {
- ztoolkit.Prompt.unregisterAll();
- const templateKeys = addon.api.template.getTemplateKeys();
- ztoolkit.Prompt.register(
- templateKeys
- .filter(
- (template) =>
- !addon.api.template.SYSTEM_TEMPLATE_NAMES.includes(template),
- )
- .map((template) => {
- return {
- name: `Template: ${template}`,
- label: "BNotes Template",
- callback: getTemplatePromptHandler(template),
- };
- }),
- );
-}
-
-function getTemplatePromptHandler(name: string) {
- return async (prompt: Prompt) => {
- ztoolkit.log(prompt, name);
- prompt.promptNode.style.display = "none";
- // TODO: add preview when command is selected
- switch (addon.data.template.picker.mode) {
- case "create":
- await createTemplateNoteCallback(name);
- break;
- case "export":
- await exportTemplateCallback(name);
- break;
- case "insert":
- default:
- await insertTemplateCallback(name);
- break;
- }
- addon.data.template.picker.mode = "insert";
- addon.data.template.picker.data = {};
- };
+async function handleTemplateOperation(name: string) {
+ ztoolkit.log(name);
+ // TODO: add preview when command is selected
+ switch (addon.data.template.picker.mode) {
+ case "create":
+ await createTemplateNoteCallback(name);
+ break;
+ case "export":
+ await exportTemplateCallback(name);
+ break;
+ case "insert":
+ default:
+ await insertTemplateCallback(name);
+ break;
+ }
+ addon.data.template.picker.mode = "insert";
+ addon.data.template.picker.data = {};
}
async function insertTemplateCallback(name: string) {
diff --git a/src/utils/templatePicker.ts b/src/utils/templatePicker.ts
new file mode 100644
index 0000000..ab38f9d
--- /dev/null
+++ b/src/utils/templatePicker.ts
@@ -0,0 +1,29 @@
+import { config } from "../../package.json";
+
+export async function openTemplatePicker(
+ options: {
+ multiSelect?: boolean;
+ } = {},
+) {
+ const { multiSelect = false } = options;
+ const templates = addon.api.template
+ .getTemplateKeys()
+ .filter(
+ (template) =>
+ !addon.api.template.SYSTEM_TEMPLATE_NAMES.includes(template),
+ );
+ const args = {
+ templates,
+ multiSelect,
+ selected: [] as string[],
+ _initPromise: Zotero.Promise.defer(),
+ };
+ Zotero.getMainWindow().openDialog(
+ `chrome://${config.addonRef}/content/templatePicker.xhtml`,
+ "_blank",
+ "chrome,modal,centerscreen,resizable=yes",
+ args,
+ );
+ await args._initPromise.promise;
+ return args.selected;
+}
diff --git a/src/utils/ztoolkit.ts b/src/utils/ztoolkit.ts
index 399e9ed..d7ebf64 100644
--- a/src/utils/ztoolkit.ts
+++ b/src/utils/ztoolkit.ts
@@ -1,7 +1,6 @@
import {
BasicTool,
UITool,
- PromptManager,
MenuManager,
ClipboardHelper,
FilePickerHelper,
@@ -43,7 +42,6 @@ function initZToolkit(_ztoolkit: ReturnType) {
class MyToolkit extends BasicTool {
UI: UITool;
- Prompt: PromptManager;
Menu: MenuManager;
Clipboard: typeof ClipboardHelper;
FilePicker: typeof FilePickerHelper;
@@ -56,7 +54,6 @@ class MyToolkit extends BasicTool {
constructor() {
super();
this.UI = new UITool(this);
- this.Prompt = new PromptManager(this);
this.Menu = new MenuManager(this);
this.Clipboard = ClipboardHelper;
this.FilePicker = FilePickerHelper;