From 425be185c9d0bee5c8642cf516c251b5d12fb3a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CBowen=5FLiu=E2=80=9D?= Date: Wed, 29 Nov 2023 09:12:13 +0800 Subject: [PATCH] Add item menu callback. Execute local js or cmd. --- addon/chrome/content/icons/favicon.png | Bin 677 -> 0 bytes addon/chrome/content/icons/favicon.svg | 14 + addon/chrome/content/icons/favicon@0.5x.png | Bin 836 -> 0 bytes addon/chrome/content/icons/favicon@0.5x.svg | 14 + addon/chrome/content/preferences.xhtml | 36 +- addon/chrome/content/zoteroPane.css | 3 - addon/locale/en-US/addon.ftl | 2 +- addon/locale/en-US/preferences.ftl | 2 +- addon/locale/zh-CN/addon.ftl | 2 +- addon/locale/zh-CN/preferences.ftl | 2 +- addon/manifest.json | 6 +- addon/prefs.js | 6 +- src/actions/menu.ts | 16 + src/addon.ts | 3 - src/hooks.ts | 113 +-- src/modules/examples.ts | 944 +------------------- src/modules/preferenceScript.ts | 108 +-- src/tasks.ts | 27 + update.json | 6 +- 19 files changed, 116 insertions(+), 1188 deletions(-) delete mode 100644 addon/chrome/content/icons/favicon.png create mode 100644 addon/chrome/content/icons/favicon.svg delete mode 100644 addon/chrome/content/icons/favicon@0.5x.png create mode 100644 addon/chrome/content/icons/favicon@0.5x.svg create mode 100644 src/actions/menu.ts create mode 100644 src/tasks.ts diff --git a/addon/chrome/content/icons/favicon.png b/addon/chrome/content/icons/favicon.png deleted file mode 100644 index 8a57dede77060aa159836686f7e8eff9fb0f5899..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 677 zcmV;W0$TlvP)7yO1}aNMxo=Ss1!z zqRbsAPtbNC0}JI1ig*C8wpzMs$T)@ro^4n$T&0MG}3 zQ`W-2c#vt>Wf2fr5dgppA2)Qa@UYEX5PLd*_q2DzC2;8DT&7{)nSLQwC?AgwcJGMH8%3*IXAdL5D*`ueUq0sOctJB~M9vCM z4MImsRt$;M(462R zpqzEIcKTz)1DA9NjkpAq%Au6*C3zV$(%^+U0?R?WHAbgeE5-E9NkbiKuVl(eti4mu z=6PV^%hi7BQd%cOia<}x`&!*7GRrEFp-W2OAh1SN1XTC~;~p3!dC9tRxlk1WWxP)n ztV0s1v1N>nstDNfuOO1Vj4(nTd3M4^z*hYxItI~32`HnLx0oi|(N+Udl9!nKo0@fk z=J@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/addon/chrome/content/icons/favicon@0.5x.png b/addon/chrome/content/icons/favicon@0.5x.png deleted file mode 100644 index 30e803757b8e2f9722bc65b2347727f2f7a646fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 836 zcmV-K1H1f*P)OVph+c2ViQZlmo90g-EMdH_wl~(cFUaX))`weODz11o)R7FTTW!dQ5T8Lj)PHeAotlmf!Fd+7S?%;T6 zPG9W49}2Zz91A}Ex8pw)hByUGV!;3^1&S*C+s~J8_pMHKQ{6gJX0!0vKK=J{{_c3) zEZ%D6H|Lk_$$CU^3YZTr^}hjd7(hPQLJSjXanz#-_CJS`3+1;;-1~KVUCue+KqP=y zSOA9J`=Pf<0~658)me`$4p35s5W4Z zm>fU6%e<-zlniMs?7LgN{U6V+eg5o7aIGBBDfLhxzzpv8nS*Q}rVIac{Tsixbad|)`SSWhHyInK zQ>CGpb0+Ngc`-Q~Fm;8Yom82N+cT%w)0C@sSN(JC#2?yLp+5js0vHMo7`@>VFOAlu zyQNGT3DVam;!g|!wH`7Y(|RPhvXbaS>v4W(o&Ri>c)pwKzSYFbv8n3foBJAj+o?8$ zU==_lSZ4x!?tyI;zK}V*vX<(_)ZyF9_v||pP5#Qd4TeJ5sMV0xsv&i1A$1HBm&bR2ryn@Vnew4z@D}qyUoB}?Nncyy=y-z z8@QDOl6&|r1)`$i6@gV8@CvDfbaSG~*9*p;bnyR>{93jYXsu@e O0000 + + + + + + + + + + + + + \ No newline at end of file diff --git a/addon/chrome/content/preferences.xhtml b/addon/chrome/content/preferences.xhtml index af67264..873b9da 100644 --- a/addon/chrome/content/preferences.xhtml +++ b/addon/chrome/content/preferences.xhtml @@ -1,36 +1,10 @@ - + - - - - - - - - - + - - - - + \ No newline at end of file diff --git a/addon/chrome/content/zoteroPane.css b/addon/chrome/content/zoteroPane.css index 8c95c1e..e69de29 100644 --- a/addon/chrome/content/zoteroPane.css +++ b/addon/chrome/content/zoteroPane.css @@ -1,3 +0,0 @@ -.makeItRed { - background-color: tomato; -} diff --git a/addon/locale/en-US/addon.ftl b/addon/locale/en-US/addon.ftl index 892d267..25e47b0 100644 --- a/addon/locale/en-US/addon.ftl +++ b/addon/locale/en-US/addon.ftl @@ -1,6 +1,6 @@ startup-begin = Addon is loading startup-finish = Addon is ready -menuitem-label = Addon Template: Helper Examples +menuitem-label = Action-cmd: menu action menupopup-label = Addon Template: Menupopup menuitem-submenulabel = Addon Template menuitem-filemenulabel = Addon Template: File Menuitem diff --git a/addon/locale/en-US/preferences.ftl b/addon/locale/en-US/preferences.ftl index 57189e0..adfd4c9 100644 --- a/addon/locale/en-US/preferences.ftl +++ b/addon/locale/en-US/preferences.ftl @@ -1,4 +1,4 @@ -pref-title = Addon Template Example +pref-title = See settings in Settings/Advanced/Config Editor pref-enable = .label = Enable pref-input = Input diff --git a/addon/locale/zh-CN/addon.ftl b/addon/locale/zh-CN/addon.ftl index 16d97d3..7092a0a 100644 --- a/addon/locale/zh-CN/addon.ftl +++ b/addon/locale/zh-CN/addon.ftl @@ -1,6 +1,6 @@ startup-begin = 插件加载中 startup-finish = 插件已就绪 -menuitem-label = 插件模板: 帮助工具样例 +menuitem-label = Action-cmd: menu action menupopup-label = 插件模板: 弹出菜单 menuitem-submenulabel = 插件模板:子菜单 menuitem-filemenulabel = 插件模板: 文件菜单 diff --git a/addon/locale/zh-CN/preferences.ftl b/addon/locale/zh-CN/preferences.ftl index b20243b..ea853a6 100644 --- a/addon/locale/zh-CN/preferences.ftl +++ b/addon/locale/zh-CN/preferences.ftl @@ -1,4 +1,4 @@ -pref-title = 插件模板设置示例 +pref-title = 目前设置在"设置/高级/编辑器"下 pref-enable = .label = 开启 pref-input = 输入 diff --git a/addon/manifest.json b/addon/manifest.json index 0c356c6..822e108 100644 --- a/addon/manifest.json +++ b/addon/manifest.json @@ -6,8 +6,8 @@ "homepage_url": "__homepage__", "author": "__author__", "icons": { - "48": "chrome/content/icons/favicon@0.5x.png", - "96": "chrome/content/icons/favicon.png" + "48": "chrome/content/icons/favicon@0.5x.svg", + "96": "chrome/content/icons/favicon.svg" }, "applications": { "zotero": { @@ -17,4 +17,4 @@ "strict_max_version": "7.0.*" } } -} +} \ No newline at end of file diff --git a/addon/prefs.js b/addon/prefs.js index be8f47d..bfd7bae 100644 --- a/addon/prefs.js +++ b/addon/prefs.js @@ -1,3 +1,5 @@ /* eslint-disable no-undef */ -pref("__prefsPrefix__.enable", true); -pref("__prefsPrefix__.input", "This is input"); +pref('__prefsPrefix__.menu.jsOrCmd', true) +pref('__prefsPrefix__.menu.jsPath', 'D:\\Project\\Python工具\\extract_pdf_image\\test.js') +pref('__prefsPrefix__.menu.cmd', 'D:\\Python310\\pythonw.exe') +pref('__prefsPrefix__.menu.args', '"D:\\Project\\Python工具\\extract_pdf_image\\extract_pdf_image.py"') diff --git a/src/actions/menu.ts b/src/actions/menu.ts new file mode 100644 index 0000000..f8487b3 --- /dev/null +++ b/src/actions/menu.ts @@ -0,0 +1,16 @@ +import { MenuTask } from "../tasks" +import { config } from "../../package.json" + + +export async function menuAction(task: MenuTask) { + const jsOrCmd = Zotero.Prefs.get(`${config.addonRef}.menu.jsOrCmd`) as boolean + const args = (Zotero.Prefs.get(`${config.addonRef}.menu.args`) as string).split(/\s*,\s*/).map(el => el.trim().replace(/^\"|\"$/g, "")).filter(el => el) + args.push(`"${JSON.stringify(task)}"`) + if (jsOrCmd) { + const content = await OS.File.read(Zotero.Prefs.get(`${config.addonRef}.menu.jsPath`) as string, { encoding: "utf-8" }) as string; + // new Function(content)(task) + eval(content) + } else { + await Zotero.Utilities.Internal.exec(Zotero.Prefs.get(`${config.addonRef}.menu.cmd`) as string, args) + } +} \ No newline at end of file diff --git a/src/addon.ts b/src/addon.ts index a9a70af..f971315 100644 --- a/src/addon.ts +++ b/src/addon.ts @@ -14,10 +14,7 @@ class Addon { }; prefs?: { window: Window; - columns: Array; - rows: Array<{ [dataKey: string]: string }>; }; - dialog?: DialogHelper; }; // Lifecycle hooks public hooks: typeof hooks; diff --git a/src/hooks.ts b/src/hooks.ts index 7b000f9..8c46221 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -1,16 +1,16 @@ import { BasicExampleFactory, - HelperExampleFactory, - KeyExampleFactory, - PromptExampleFactory, UIExampleFactory, } from "./modules/examples"; import { config } from "../package.json"; import { getString, initLocale } from "./utils/locale"; import { registerPrefsScripts } from "./modules/preferenceScript"; import { createZToolkit } from "./utils/ztoolkit"; +import { addMenuTask, MenuTask } from "./tasks" +import { menuAction } from "./actions/menu" async function onStartup() { + Components.utils.import("resource://gre/modules/osfile.jsm"); await Promise.all([ Zotero.initializationPromise, Zotero.unlockPromise, @@ -20,77 +20,22 @@ async function onStartup() { BasicExampleFactory.registerPrefs(); - BasicExampleFactory.registerNotifier(); - await onMainWindowLoad(window); } async function onMainWindowLoad(win: Window): Promise { // Create ztoolkit for every window addon.data.ztoolkit = createZToolkit(); - - const popupWin = new ztoolkit.ProgressWindow(config.addonName, { - closeOnClick: true, - closeTime: -1, - }) - .createLine({ - text: getString("startup-begin"), - type: "default", - progress: 0, - }) - .show(); - - KeyExampleFactory.registerShortcuts(); - - await Zotero.Promise.delay(1000); - popupWin.changeLine({ - progress: 30, - text: `[30%] ${getString("startup-begin")}`, - }); - UIExampleFactory.registerStyleSheet(); - UIExampleFactory.registerRightClickMenuItem(); - - UIExampleFactory.registerRightClickMenuPopup(); - - UIExampleFactory.registerWindowMenuWithSeparator(); - - await UIExampleFactory.registerExtraColumn(); - - await UIExampleFactory.registerExtraColumnWithCustomCell(); - - await UIExampleFactory.registerCustomItemBoxRow(); - - UIExampleFactory.registerLibraryTabPanel(); - - await UIExampleFactory.registerReaderTabPanel(); - - PromptExampleFactory.registerNormalCommandExample(); - - PromptExampleFactory.registerAnonymousCommandExample(); - - PromptExampleFactory.registerConditionalCommandExample(); - - await Zotero.Promise.delay(1000); - - popupWin.changeLine({ - progress: 100, - text: `[100%] ${getString("startup-finish")}`, - }); - popupWin.startCloseTimer(5000); - - addon.hooks.onDialogEvents("dialogExample"); } async function onMainWindowUnload(win: Window): Promise { ztoolkit.unregisterAll(); - addon.data.dialog?.window?.close(); } function onShutdown(): void { ztoolkit.unregisterAll(); - addon.data.dialog?.window?.close(); // Remove addon object addon.data.alive = false; delete Zotero[config.addonInstance]; @@ -107,16 +52,7 @@ async function onNotify( extraData: { [key: string]: any }, ) { // You can add your code to the corresponding notify type - ztoolkit.log("notify", event, type, ids, extraData); - if ( - event == "select" && - type == "tab" && - extraData[ids[0]].type == "reader" - ) { - BasicExampleFactory.exampleNotifierCallback(); - } else { - return; - } + } /** @@ -136,47 +72,21 @@ async function onPrefsEvent(type: string, data: { [key: string]: any }) { } function onShortcuts(type: string) { - switch (type) { - case "larger": - KeyExampleFactory.exampleShortcutLargerCallback(); - break; - case "smaller": - KeyExampleFactory.exampleShortcutSmallerCallback(); - break; - case "confliction": - KeyExampleFactory.exampleShortcutConflictingCallback(); - break; - default: - break; - } + } function onDialogEvents(type: string) { - switch (type) { - case "dialogExample": - HelperExampleFactory.dialogExample(); - break; - case "clipboardExample": - HelperExampleFactory.clipboardExample(); - break; - case "filePickerExample": - HelperExampleFactory.filePickerExample(); - break; - case "progressWindowExample": - HelperExampleFactory.progressWindowExample(); - break; - case "vtableExample": - HelperExampleFactory.vtableExample(); - break; - default: - break; - } + } // Add your hooks here. For element click, etc. // Keep in mind hooks only do dispatch. Don't add code that does real jobs in hooks. // Otherwise the code would be hard to read and maintian. - +async function onMenuActionInBatch(tasks: Array) { + for (const task of tasks) { + await menuAction(task); + } +} export default { onStartup, onShutdown, @@ -186,4 +96,5 @@ export default { onPrefsEvent, onShortcuts, onDialogEvents, + onMenuActionInBatch }; diff --git a/src/modules/examples.ts b/src/modules/examples.ts index d493760..db2b2de 100644 --- a/src/modules/examples.ts +++ b/src/modules/examples.ts @@ -1,187 +1,21 @@ import { config } from "../../package.json"; import { getString } from "../utils/locale"; - -function example( - target: any, - propertyKey: string | symbol, - descriptor: PropertyDescriptor, -) { - const original = descriptor.value; - descriptor.value = function (...args: any) { - try { - ztoolkit.log(`Calling example ${target.name}.${String(propertyKey)}`); - return original.apply(this, args); - } catch (e) { - ztoolkit.log(`Error in example ${target.name}.${String(propertyKey)}`, e); - throw e; - } - }; - return descriptor; -} +import { addMenuTask, MenuTask } from "../tasks" export class BasicExampleFactory { - @example - static registerNotifier() { - const callback = { - notify: async ( - event: string, - type: string, - ids: number[] | string[], - extraData: { [key: string]: any }, - ) => { - if (!addon?.data.alive) { - this.unregisterNotifier(notifierID); - return; - } - addon.hooks.onNotify(event, type, ids, extraData); - }, - }; - - // Register the callback in Zotero as an item observer - const notifierID = Zotero.Notifier.registerObserver(callback, [ - "tab", - "item", - "file", - ]); - - // Unregister callback when the window closes (important to avoid a memory leak) - window.addEventListener( - "unload", - (e: Event) => { - this.unregisterNotifier(notifierID); - }, - false, - ); - } - - @example - static exampleNotifierCallback() { - new ztoolkit.ProgressWindow(config.addonName) - .createLine({ - text: "Open Tab Detected!", - type: "success", - progress: 100, - }) - .show(); - } - - @example - private static unregisterNotifier(notifierID: string) { - Zotero.Notifier.unregisterObserver(notifierID); - } - - @example static registerPrefs() { const prefOptions = { pluginID: config.addonID, src: rootURI + "chrome/content/preferences.xhtml", label: getString("prefs-title"), - image: `chrome://${config.addonRef}/content/icons/favicon.png`, + image: `chrome://${config.addonRef}/content/icons/favicon.svg`, defaultXUL: true, }; ztoolkit.PreferencePane.register(prefOptions); } } -export class KeyExampleFactory { - @example - static registerShortcuts() { - const keysetId = `${config.addonRef}-keyset`; - const cmdsetId = `${config.addonRef}-cmdset`; - const cmdSmallerId = `${config.addonRef}-cmd-smaller`; - // Register an event key for Alt+L - ztoolkit.Shortcut.register("event", { - id: `${config.addonRef}-key-larger`, - key: "L", - modifiers: "alt", - callback: (keyOptions) => { - addon.hooks.onShortcuts("larger"); - }, - }); - // Register an element key using for Alt+S - ztoolkit.Shortcut.register("element", { - id: `${config.addonRef}-key-smaller`, - key: "S", - modifiers: "alt", - xulData: { - document, - command: cmdSmallerId, - _parentId: keysetId, - _commandOptions: { - id: cmdSmallerId, - document, - _parentId: cmdsetId, - oncommand: `Zotero.${config.addonInstance}.hooks.onShortcuts('smaller')`, - }, - }, - }); - // Here we register an conflict key for Alt+S - // just to show how the confliction check works. - // This is something you should avoid in your plugin. - ztoolkit.Shortcut.register("event", { - id: `${config.addonRef}-key-smaller-conflict`, - key: "S", - modifiers: "alt", - callback: (keyOptions) => { - ztoolkit.getGlobal("alert")("Smaller! This is a conflict key."); - }, - }); - // Register an event key to check confliction - ztoolkit.Shortcut.register("event", { - id: `${config.addonRef}-key-check-conflict`, - key: "C", - modifiers: "alt", - callback: (keyOptions) => { - addon.hooks.onShortcuts("confliction"); - }, - }); - new ztoolkit.ProgressWindow(config.addonName) - .createLine({ - text: "Example Shortcuts: Alt+L/S/C", - type: "success", - }) - .show(); - } - - @example - static exampleShortcutLargerCallback() { - new ztoolkit.ProgressWindow(config.addonName) - .createLine({ - text: "Larger!", - type: "default", - }) - .show(); - } - - @example - static exampleShortcutSmallerCallback() { - new ztoolkit.ProgressWindow(config.addonName) - .createLine({ - text: "Smaller!", - type: "default", - }) - .show(); - } - - @example - static exampleShortcutConflictingCallback() { - const conflictingGroups = ztoolkit.Shortcut.checkAllKeyConflicting(); - new ztoolkit.ProgressWindow("Check Key Conflicting") - .createLine({ - text: `${conflictingGroups.length} groups of conflicting keys found. Details are in the debug output/console.`, - }) - .show(-1); - ztoolkit.log( - "Conflicting:", - conflictingGroups, - "All keys:", - ztoolkit.Shortcut.getAll(), - ); - } -} - export class UIExampleFactory { - @example static registerStyleSheet() { const styles = ztoolkit.UI.createElement(document, "link", { properties: { @@ -191,776 +25,22 @@ export class UIExampleFactory { }, }); document.documentElement.appendChild(styles); - document - .getElementById("zotero-item-pane-content") - ?.classList.add("makeItRed"); } - - @example static registerRightClickMenuItem() { - const menuIcon = `chrome://${config.addonRef}/content/icons/favicon@0.5x.png`; + const menuIcon = `chrome://${config.addonRef}/content/icons/favicon@0.5x.svg`; // item menuitem with icon ztoolkit.Menu.register("item", { tag: "menuitem", - id: "zotero-itemmenu-addontemplate-test", + id: "zotero-action-cmd-menu", label: getString("menuitem-label"), - commandListener: (ev) => addon.hooks.onDialogEvents("dialogExample"), - icon: menuIcon, - }); - } - - @example - static registerRightClickMenuPopup() { - ztoolkit.Menu.register( - "item", - { - tag: "menu", - label: getString("menupopup-label"), - children: [ - { - tag: "menuitem", - label: getString("menuitem-submenulabel"), - oncommand: "alert('Hello World! Sub Menuitem.')", - }, - ], - }, - "before", - document.querySelector( - "#zotero-itemmenu-addontemplate-test", - ) as XUL.MenuItem, - ); - } - - @example - static registerWindowMenuWithSeparator() { - ztoolkit.Menu.register("menuFile", { - tag: "menuseparator", - }); - // menu->File menuitem - ztoolkit.Menu.register("menuFile", { - tag: "menuitem", - label: getString("menuitem-filemenulabel"), - oncommand: "alert('Hello World! File Menuitem.')", - }); - } - - @example - static async registerExtraColumn() { - await ztoolkit.ItemTree.register( - "test1", - "text column", - ( - field: string, - unformatted: boolean, - includeBaseMapped: boolean, - item: Zotero.Item, - ) => { - return field + String(item.id); - }, - { - iconPath: "chrome://zotero/skin/cross.png", - }, - ); - } - - @example - static async registerExtraColumnWithCustomCell() { - await ztoolkit.ItemTree.register( - "test2", - "custom column", - ( - field: string, - unformatted: boolean, - includeBaseMapped: boolean, - item: Zotero.Item, - ) => { - return String(item.id); - }, - { - renderCell(index, data, column) { - ztoolkit.log("Custom column cell is rendered!"); - const span = document.createElementNS( - "http://www.w3.org/1999/xhtml", - "span", - ); - span.className = `cell ${column.className}`; - span.style.background = "#0dd068"; - span.innerText = "⭐" + data; - return span; - }, - }, - ); - } - - @example - static async registerCustomItemBoxRow() { - await ztoolkit.ItemBox.register( - "itemBoxFieldEditable", - "Editable Custom Field", - (field, unformatted, includeBaseMapped, item, original) => { - return ( - ztoolkit.ExtraField.getExtraField(item, "itemBoxFieldEditable") || "" + commandListener: (ev) => { + addon.hooks.onMenuActionInBatch( + ZoteroPane.getSelectedItems(true) + .map((id) => addMenuTask(id)) + .filter((task) => task) as MenuTask[] ); }, - { - editable: true, - setFieldHook: (field, value, loadIn, item, original) => { - window.alert("Custom itemBox value is changed and saved to extra!"); - ztoolkit.ExtraField.setExtraField( - item, - "itemBoxFieldEditable", - value, - ); - return true; - }, - index: 1, - }, - ); - - await ztoolkit.ItemBox.register( - "itemBoxFieldNonEditable", - "Non-Editable Custom Field", - (field, unformatted, includeBaseMapped, item, original) => { - return ( - "[CANNOT EDIT THIS]" + (item.getField("title") as string).slice(0, 10) - ); - }, - { - editable: false, - index: 2, - }, - ); - } - - @example - static registerLibraryTabPanel() { - const tabId = ztoolkit.LibraryTabPanel.register( - getString("tabpanel-lib-tab-label"), - (panel: XUL.Element, win: Window) => { - const elem = ztoolkit.UI.createElement(win.document, "vbox", { - children: [ - { - tag: "h2", - properties: { - innerText: "Hello World!", - }, - }, - { - tag: "div", - properties: { - innerText: "This is a library tab.", - }, - }, - { - tag: "button", - namespace: "html", - properties: { - innerText: "Unregister", - }, - listeners: [ - { - type: "click", - listener: () => { - ztoolkit.LibraryTabPanel.unregister(tabId); - }, - }, - ], - }, - ], - }); - panel.append(elem); - }, - { - targetIndex: 1, - }, - ); - } - - @example - static async registerReaderTabPanel() { - const tabId = await ztoolkit.ReaderTabPanel.register( - getString("tabpanel-reader-tab-label"), - ( - panel: XUL.TabPanel | undefined, - deck: XUL.Deck, - win: Window, - reader: _ZoteroTypes.ReaderInstance, - ) => { - if (!panel) { - ztoolkit.log( - "This reader do not have right-side bar. Adding reader tab skipped.", - ); - return; - } - ztoolkit.log(reader); - const elem = ztoolkit.UI.createElement(win.document, "vbox", { - id: `${config.addonRef}-${reader._instanceID}-extra-reader-tab-div`, - // This is important! Don't create content for multiple times - // ignoreIfExists: true, - removeIfExists: true, - children: [ - { - tag: "h2", - properties: { - innerText: "Hello World!", - }, - }, - { - tag: "div", - properties: { - innerText: "This is a reader tab.", - }, - }, - { - tag: "div", - properties: { - innerText: `Reader: ${reader._title.slice(0, 20)}`, - }, - }, - { - tag: "div", - properties: { - innerText: `itemID: ${reader.itemID}.`, - }, - }, - { - tag: "button", - namespace: "html", - properties: { - innerText: "Unregister", - }, - listeners: [ - { - type: "click", - listener: () => { - ztoolkit.ReaderTabPanel.unregister(tabId); - }, - }, - ], - }, - ], - }); - panel.append(elem); - }, - { - targetIndex: 1, - }, - ); - } -} - -export class PromptExampleFactory { - @example - static registerNormalCommandExample() { - ztoolkit.Prompt.register([ - { - name: "Normal Command Test", - label: "Plugin Template", - callback(prompt) { - ztoolkit.getGlobal("alert")("Command triggered!"); - }, - }, - ]); - } - - @example - static registerAnonymousCommandExample() { - ztoolkit.Prompt.register([ - { - id: "search", - callback: async (prompt) => { - // https://github.com/zotero/zotero/blob/7262465109c21919b56a7ab214f7c7a8e1e63909/chrome/content/zotero/integration/quickFormat.js#L589 - function getItemDescription(item: Zotero.Item) { - const nodes = []; - let str = ""; - let author, - authorDate = ""; - if (item.firstCreator) { - author = authorDate = item.firstCreator; - } - let date = item.getField("date", true, true) as string; - if (date && (date = date.substr(0, 4)) !== "0000") { - authorDate += " (" + parseInt(date) + ")"; - } - authorDate = authorDate.trim(); - if (authorDate) nodes.push(authorDate); - - const publicationTitle = item.getField( - "publicationTitle", - false, - true, - ); - if (publicationTitle) { - nodes.push(`${publicationTitle}`); - } - let volumeIssue = item.getField("volume"); - const issue = item.getField("issue"); - if (issue) volumeIssue += "(" + issue + ")"; - if (volumeIssue) nodes.push(volumeIssue); - - const publisherPlace = []; - let field; - if ((field = item.getField("publisher"))) - publisherPlace.push(field); - if ((field = item.getField("place"))) publisherPlace.push(field); - if (publisherPlace.length) nodes.push(publisherPlace.join(": ")); - - const pages = item.getField("pages"); - if (pages) nodes.push(pages); - - if (!nodes.length) { - const url = item.getField("url"); - if (url) nodes.push(url); - } - - // compile everything together - for (let i = 0, n = nodes.length; i < n; i++) { - const node = nodes[i]; - - if (i != 0) str += ", "; - - if (typeof node === "object") { - const label = document.createElement("label"); - label.setAttribute("value", str); - label.setAttribute("crop", "end"); - str = ""; - } else { - str += node; - } - } - str.length && (str += "."); - return str; - } - function filter(ids: number[]) { - ids = ids.filter(async (id) => { - const item = (await Zotero.Items.getAsync(id)) as Zotero.Item; - return item.isRegularItem() && !(item as any).isFeedItem; - }); - return ids; - } - const text = prompt.inputNode.value; - prompt.showTip("Searching..."); - const s = new Zotero.Search(); - s.addCondition("quicksearch-titleCreatorYear", "contains", text); - s.addCondition("itemType", "isNot", "attachment"); - let ids = await s.search(); - // prompt.exit will remove current container element. - // @ts-ignore ignore - prompt.exit(); - const container = prompt.createCommandsContainer(); - container.classList.add("suggestions"); - ids = filter(ids); - console.log(ids.length); - if (ids.length == 0) { - const s = new Zotero.Search(); - const operators = [ - "is", - "isNot", - "true", - "false", - "isInTheLast", - "isBefore", - "isAfter", - "contains", - "doesNotContain", - "beginsWith", - ]; - let hasValidCondition = false; - let joinMode = "all"; - if (/\s*\|\|\s*/.test(text)) { - joinMode = "any"; - } - text.split(/\s*(&&|\|\|)\s*/g).forEach((conditinString: string) => { - const conditions = conditinString.split(/\s+/g); - if ( - conditions.length == 3 && - operators.indexOf(conditions[1]) != -1 - ) { - hasValidCondition = true; - s.addCondition( - "joinMode", - joinMode as Zotero.Search.Operator, - "", - ); - s.addCondition( - conditions[0] as string, - conditions[1] as Zotero.Search.Operator, - conditions[2] as string, - ); - } - }); - if (hasValidCondition) { - ids = await s.search(); - } - } - ids = filter(ids); - console.log(ids.length); - if (ids.length > 0) { - ids.forEach((id: number) => { - const item = Zotero.Items.get(id); - const title = item.getField("title"); - const ele = ztoolkit.UI.createElement(document, "div", { - namespace: "html", - classList: ["command"], - listeners: [ - { - type: "mousemove", - listener: function () { - // @ts-ignore ignore - prompt.selectItem(this); - }, - }, - { - type: "click", - listener: () => { - prompt.promptNode.style.display = "none"; - Zotero_Tabs.select("zotero-pane"); - ZoteroPane.selectItem(item.id); - }, - }, - ], - styles: { - display: "flex", - flexDirection: "column", - justifyContent: "start", - }, - children: [ - { - tag: "span", - styles: { - fontWeight: "bold", - overflow: "hidden", - textOverflow: "ellipsis", - whiteSpace: "nowrap", - }, - properties: { - innerText: title, - }, - }, - { - tag: "span", - styles: { - overflow: "hidden", - textOverflow: "ellipsis", - whiteSpace: "nowrap", - }, - properties: { - innerHTML: getItemDescription(item), - }, - }, - ], - }); - container.appendChild(ele); - }); - } else { - // @ts-ignore ignore - prompt.exit(); - prompt.showTip("Not Found."); - } - }, - }, - ]); - } - - @example - static registerConditionalCommandExample() { - ztoolkit.Prompt.register([ - { - name: "Conditional Command Test", - label: "Plugin Template", - // The when function is executed when Prompt UI is woken up by `Shift + P`, and this command does not display when false is returned. - when: () => { - const items = ZoteroPane.getSelectedItems(); - return items.length > 0; - }, - callback(prompt) { - prompt.inputNode.placeholder = "Hello World!"; - const items = ZoteroPane.getSelectedItems(); - ztoolkit.getGlobal("alert")( - `You select ${items.length} items!\n\n${items - .map( - (item, index) => - String(index + 1) + ". " + item.getDisplayTitle(), - ) - .join("\n")}`, - ); - }, - }, - ]); - } -} - -export class HelperExampleFactory { - @example - static async dialogExample() { - const dialogData: { [key: string | number]: any } = { - inputValue: "test", - checkboxValue: true, - loadCallback: () => { - ztoolkit.log(dialogData, "Dialog Opened!"); - }, - unloadCallback: () => { - ztoolkit.log(dialogData, "Dialog closed!"); - }, - }; - const dialogHelper = new ztoolkit.Dialog(10, 2) - .addCell(0, 0, { - tag: "h1", - properties: { innerHTML: "Helper Examples" }, - }) - .addCell(1, 0, { - tag: "h2", - properties: { innerHTML: "Dialog Data Binding" }, - }) - .addCell(2, 0, { - tag: "p", - properties: { - innerHTML: - "Elements with attribute 'data-bind' are binded to the prop under 'dialogData' with the same name.", - }, - styles: { - width: "200px", - }, - }) - .addCell(3, 0, { - tag: "label", - namespace: "html", - attributes: { - for: "dialog-checkbox", - }, - properties: { innerHTML: "bind:checkbox" }, - }) - .addCell( - 3, - 1, - { - tag: "input", - namespace: "html", - id: "dialog-checkbox", - attributes: { - "data-bind": "checkboxValue", - "data-prop": "checked", - type: "checkbox", - }, - properties: { label: "Cell 1,0" }, - }, - false, - ) - .addCell(4, 0, { - tag: "label", - namespace: "html", - attributes: { - for: "dialog-input", - }, - properties: { innerHTML: "bind:input" }, - }) - .addCell( - 4, - 1, - { - tag: "input", - namespace: "html", - id: "dialog-input", - attributes: { - "data-bind": "inputValue", - "data-prop": "value", - type: "text", - }, - }, - false, - ) - .addCell(5, 0, { - tag: "h2", - properties: { innerHTML: "Toolkit Helper Examples" }, - }) - .addCell( - 6, - 0, - { - tag: "button", - namespace: "html", - attributes: { - type: "button", - }, - listeners: [ - { - type: "click", - listener: (e: Event) => { - addon.hooks.onDialogEvents("clipboardExample"); - }, - }, - ], - children: [ - { - tag: "div", - styles: { - padding: "2.5px 15px", - }, - properties: { - innerHTML: "example:clipboard", - }, - }, - ], - }, - false, - ) - .addCell( - 7, - 0, - { - tag: "button", - namespace: "html", - attributes: { - type: "button", - }, - listeners: [ - { - type: "click", - listener: (e: Event) => { - addon.hooks.onDialogEvents("filePickerExample"); - }, - }, - ], - children: [ - { - tag: "div", - styles: { - padding: "2.5px 15px", - }, - properties: { - innerHTML: "example:filepicker", - }, - }, - ], - }, - false, - ) - .addCell( - 8, - 0, - { - tag: "button", - namespace: "html", - attributes: { - type: "button", - }, - listeners: [ - { - type: "click", - listener: (e: Event) => { - addon.hooks.onDialogEvents("progressWindowExample"); - }, - }, - ], - children: [ - { - tag: "div", - styles: { - padding: "2.5px 15px", - }, - properties: { - innerHTML: "example:progressWindow", - }, - }, - ], - }, - false, - ) - .addCell( - 9, - 0, - { - tag: "button", - namespace: "html", - attributes: { - type: "button", - }, - listeners: [ - { - type: "click", - listener: (e: Event) => { - addon.hooks.onDialogEvents("vtableExample"); - }, - }, - ], - children: [ - { - tag: "div", - styles: { - padding: "2.5px 15px", - }, - properties: { - innerHTML: "example:virtualized-table", - }, - }, - ], - }, - false, - ) - .addButton("Confirm", "confirm") - .addButton("Cancel", "cancel") - .addButton("Help", "help", { - noClose: true, - callback: (e) => { - dialogHelper.window?.alert( - "Help Clicked! Dialog will not be closed.", - ); - }, - }) - .setDialogData(dialogData) - .open("Dialog Example"); - addon.data.dialog = dialogHelper; - await dialogData.unloadLock.promise; - addon.data.dialog = undefined; - addon.data.alive && - ztoolkit.getGlobal("alert")( - `Close dialog with ${dialogData._lastButtonId}.\nCheckbox: ${dialogData.checkboxValue}\nInput: ${dialogData.inputValue}.`, - ); - ztoolkit.log(dialogData); - } - - @example - static clipboardExample() { - new ztoolkit.Clipboard() - .addText( - "![Plugin Template](https://github.com/windingwind/zotero-plugin-template)", - "text/unicode", - ) - .addText( - 'Plugin Template', - "text/html", - ) - .copy(); - ztoolkit.getGlobal("alert")("Copied!"); - } - - @example - static async filePickerExample() { - const path = await new ztoolkit.FilePicker( - "Import File", - "open", - [ - ["PNG File(*.png)", "*.png"], - ["Any", "*.*"], - ], - "image.png", - ).open(); - ztoolkit.getGlobal("alert")(`Selected ${path}`); - } - - @example - static progressWindowExample() { - new ztoolkit.ProgressWindow(config.addonName) - .createLine({ - text: "ProgressWindow Example!", - type: "success", - progress: 100, - }) - .show(); - } - - @example - static vtableExample() { - ztoolkit.getGlobal("alert")("See src/modules/preferenceScript.ts"); + icon: menuIcon, + }); } -} +} \ No newline at end of file diff --git a/src/modules/preferenceScript.ts b/src/modules/preferenceScript.ts index ef6e5b9..3b478b2 100644 --- a/src/modules/preferenceScript.ts +++ b/src/modules/preferenceScript.ts @@ -6,33 +6,7 @@ export async function registerPrefsScripts(_window: Window) { // See addon/chrome/content/preferences.xul onpaneload if (!addon.data.prefs) { addon.data.prefs = { - window: _window, - columns: [ - { - dataKey: "title", - label: getString("prefs-table-title"), - fixedWidth: true, - width: 100, - }, - { - dataKey: "detail", - label: getString("prefs-table-detail"), - }, - ], - rows: [ - { - title: "Orange", - detail: "It's juicy", - }, - { - title: "Banana", - detail: "It's sweet", - }, - { - title: "Apple", - detail: "I mean the fruit APPLE", - }, - ], + window: _window }; } else { addon.data.prefs.window = _window; @@ -42,90 +16,12 @@ export async function registerPrefsScripts(_window: Window) { } async function updatePrefsUI() { - // You can initialize some UI elements on prefs window - // with addon.data.prefs.window.document - // Or bind some events to the elements const renderLock = ztoolkit.getGlobal("Zotero").Promise.defer(); if (addon.data.prefs?.window == undefined) return; - const tableHelper = new ztoolkit.VirtualizedTable(addon.data.prefs?.window) - .setContainerId(`${config.addonRef}-table-container`) - .setProp({ - id: `${config.addonRef}-prefs-table`, - // Do not use setLocale, as it modifies the Zotero.Intl.strings - // Set locales directly to columns - columns: addon.data.prefs?.columns, - showHeader: true, - multiSelect: true, - staticColumns: true, - disableFontSizeScaling: true, - }) - .setProp("getRowCount", () => addon.data.prefs?.rows.length || 0) - .setProp( - "getRowData", - (index) => - addon.data.prefs?.rows[index] || { - title: "no data", - detail: "no data", - }, - ) - // Show a progress window when selection changes - .setProp("onSelectionChange", (selection) => { - new ztoolkit.ProgressWindow(config.addonName) - .createLine({ - text: `Selected line: ${addon.data.prefs?.rows - .filter((v, i) => selection.isSelected(i)) - .map((row) => row.title) - .join(",")}`, - progress: 100, - }) - .show(); - }) - // When pressing delete, delete selected line and refresh table. - // Returning false to prevent default event. - .setProp("onKeyDown", (event: KeyboardEvent) => { - if (event.key == "Delete" || (Zotero.isMac && event.key == "Backspace")) { - addon.data.prefs!.rows = - addon.data.prefs?.rows.filter( - (v, i) => !tableHelper.treeInstance.selection.isSelected(i), - ) || []; - tableHelper.render(); - return false; - } - return true; - }) - // For find-as-you-type - .setProp( - "getRowString", - (index) => addon.data.prefs?.rows[index].title || "", - ) - // Render the table. - .render(-1, () => { - renderLock.resolve(); - }); + await renderLock.promise; - ztoolkit.log("Preference table rendered!"); } function bindPrefEvents() { - addon.data - .prefs!.window.document.querySelector( - `#zotero-prefpane-${config.addonRef}-enable`, - ) - ?.addEventListener("command", (e) => { - ztoolkit.log(e); - addon.data.prefs!.window.alert( - `Successfully changed to ${(e.target as XUL.Checkbox).checked}!`, - ); - }); - addon.data - .prefs!.window.document.querySelector( - `#zotero-prefpane-${config.addonRef}-input`, - ) - ?.addEventListener("change", (e) => { - ztoolkit.log(e); - addon.data.prefs!.window.alert( - `Successfully changed to ${(e.target as HTMLInputElement).value}!`, - ); - }); } diff --git a/src/tasks.ts b/src/tasks.ts new file mode 100644 index 0000000..81d451d --- /dev/null +++ b/src/tasks.ts @@ -0,0 +1,27 @@ +export interface MenuTask { + id: string, + pdfUrl: string +} + +export function addMenuTask( + itemId: number +) { + const item = Zotero.Items.get(itemId); + // item.getBestAttachment() + if (item?.isRegularItem()) { + const attachments = item.getAttachments(); + if (attachments && attachments.length) { + const attachment = Zotero.Items.get(attachments[0]); + + if (attachment.isPDFAttachment()) { + const newTask: MenuTask = { + id: item.getField("id") as string, + pdfUrl: attachment.getFilePath() as string + }; + ztoolkit.log("MenuTask -----------------------------------------------------"); + ztoolkit.log(newTask) + return newTask + } + } + } +} \ No newline at end of file diff --git a/update.json b/update.json index f5c857f..1f4339c 100644 --- a/update.json +++ b/update.json @@ -1,10 +1,10 @@ { "addons": { - "addontemplate@euclpts.com": { + "ZoteroActionCMD": { "updates": [ { - "version": "1.0.2", - "update_link": "https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi", + "version": "1.0.0", + "update_link": "https://github.com/Bowen-0x00/zotero-action-cmd/releases/latest/download/zotero-action-cmd.xpi", "applications": { "zotero": { "strict_min_version": "6.999"