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;