diff --git a/README.md b/README.md index 276acdc..ca1c422 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,6 @@ Search `@example` in `src/examples.ts`. The examples are called in `src/hooks.ts 1. When install/enable/startup triggered from Zotero, `bootstrap.js` > `startup` is called - Wait for Zotero ready - - Prepare global variables `ctx`. They are available globally in the plugin scope - Load `index.js` (the main entrance of plugin code, built from `index.ts`) - Register resources if Zotero 7+ 2. In the main entrance `index.js`, the plugin object is injected under `Zotero` and `hooks.ts` > `onStartup` is called. @@ -194,7 +193,7 @@ This section shows the directory structure of a template. │ │ │ └─zh-CN │ | overlay.dtd -│ │ addon.properties +│ └─ addon.properties │ ├─builds # build dir │ └─.xpi @@ -203,10 +202,12 @@ This section shows the directory structure of a template. │ index.ts # main entry │ addon.ts # base class │ hooks.ts # lifecycle hooks - │ examples.ts # examples factory - │ locale.ts # Locale class for properties files - └─ prefs.ts # preferences class - + | + └─modules # sub modules + │ examples.ts # examples factory + │ locale.ts # locale .properties + │ preferenceScript.ts # script runs in preferences.xhtml + │ progressWindow.ts # progressWindow tool ``` ### Build diff --git a/addon/chrome/content/preferences.xhtml b/addon/chrome/content/preferences.xhtml index 86ad3f4..c928e36 100644 --- a/addon/chrome/content/preferences.xhtml +++ b/addon/chrome/content/preferences.xhtml @@ -1,6 +1,6 @@ @@ -22,5 +22,7 @@ - + diff --git a/src/addon.ts b/src/addon.ts index 76445ed..f1b6fda 100644 --- a/src/addon.ts +++ b/src/addon.ts @@ -1,24 +1,26 @@ -import AddonHooks from "./hooks"; -import AddonPrefs from "./prefs"; -import AddonLocale from "./locale"; +import hooks from "./hooks"; class Addon { - // Env type, see build.js - public env!: "development" | "production"; - // If addon is disabled/removed, set it false - public alive: boolean; - // Lifecycle events - public hooks: AddonHooks; - // Scripts for prefpane window - public prefs: AddonPrefs; - // Runtime locale with .properties - public locale: AddonLocale; + public data: { + alive: boolean; + // Env type, see build.js + env: "development" | "production"; + locale?: { + stringBundle: any; + }; + prefs?: { + window: Window; + }; + }; + // Lifecycle hooks + public hooks: typeof hooks; constructor() { - this.alive = true; - this.hooks = new AddonHooks(); - this.prefs = new AddonPrefs(); - this.locale = new AddonLocale(); + this.data = { + alive: true, + env: __env__, + }; + this.hooks = hooks; } } diff --git a/src/hooks.ts b/src/hooks.ts index 580d92d..57c4f1a 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -1,62 +1,85 @@ -import { BasicExampleFactory, UIExampleFactory } from "./examples"; -import { changeProgressWindowLine, showProgressWindow } from "./tools/progress"; +import { BasicExampleFactory, UIExampleFactory } from "./modules/examples"; +import { + changeProgressWindowLine, + showProgressWindow, +} from "./modules/progressWindow"; import { config } from "../package.json"; +import { getString, initLocale } from "./modules/locale"; +import { registerPrefsScripts } from "./modules/preferenceScript"; -class AddonHooks { - public async onStartup() { - addon.locale.initLocale(); +async function onStartup() { + initLocale(); - const w = showProgressWindow( - config.addonName, - addon.locale.getString("startup.begin"), - "default", - -1 - ); - changeProgressWindowLine(w, { newProgress: 0 }); + const w = showProgressWindow( + config.addonName, + getString("startup.begin"), + "default", + -1 + ); + changeProgressWindowLine(w, { newProgress: 0 }); - BasicExampleFactory.registerPrefs(); + BasicExampleFactory.registerPrefs(); - BasicExampleFactory.registerNotifier(); + BasicExampleFactory.registerNotifier(); - await Zotero.Promise.delay(1000); - changeProgressWindowLine(w, { - newProgress: 30, - newText: `[30%] ${addon.locale.getString("startup.begin")}`, - }); + await Zotero.Promise.delay(1000); + changeProgressWindowLine(w, { + newProgress: 30, + newText: `[30%] ${getString("startup.begin")}`, + }); - UIExampleFactory.registerStyleSheet(); + UIExampleFactory.registerStyleSheet(); - UIExampleFactory.registerRightClickMenuItem(); + UIExampleFactory.registerRightClickMenuItem(); - UIExampleFactory.registerRightClickMenuPopup(); + UIExampleFactory.registerRightClickMenuPopup(); - UIExampleFactory.registerWindowMenuWithSeprator(); + UIExampleFactory.registerWindowMenuWithSeprator(); - await UIExampleFactory.registerExtraColumn(); + await UIExampleFactory.registerExtraColumn(); - await UIExampleFactory.registerExtraColumnWithCustomCell(); + await UIExampleFactory.registerExtraColumnWithCustomCell(); - await UIExampleFactory.registerCustomCellRenderer(); + await UIExampleFactory.registerCustomCellRenderer(); - UIExampleFactory.registerLibraryTabPanel(); + UIExampleFactory.registerLibraryTabPanel(); - await UIExampleFactory.registerReaderTabPanel(); + await UIExampleFactory.registerReaderTabPanel(); - await Zotero.Promise.delay(1000); - changeProgressWindowLine(w, { - newProgress: 100, - newText: `[100%] ${addon.locale.getString("startup.finish")}`, - }); - w.startCloseTimer(5000); - } + await Zotero.Promise.delay(1000); + changeProgressWindowLine(w, { + newProgress: 100, + newText: `[100%] ${getString("startup.finish")}`, + }); + w.startCloseTimer(5000); +} - public onShutdown(): void { - BasicExampleFactory.unregisterPrefs(); - UIExampleFactory.unregisterUIExamples(); - // Remove addon object - addon.alive = false; - delete Zotero.AddonTemplate; +function onShutdown(): void { + BasicExampleFactory.unregisterPrefs(); + UIExampleFactory.unregisterUIExamples(); + // Remove addon object + addon.data.alive = false; + delete Zotero.AddonTemplate; +} + +/** + * This function is just a dispatcher for UI events. + * Any operations should be placed in a function to keep this funcion clear + * @param type event type + * @param data event data + */ +function onCustomEvent(type: string, data: { [key: string]: any }) { + switch (type) { + case "prefLoad": + registerPrefsScripts(data.window); + break; + default: + return; } } -export default AddonHooks; +export default { + onStartup, + onShutdown, + onCustomEvent, +}; diff --git a/src/index.ts b/src/index.ts index 16ed824..8a8dc19 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,10 +12,9 @@ if (!getGlobal("Zotero").AddonTemplate) { _globalThis.document = getGlobal("document"); _globalThis.ztoolkit = new ZoteroToolkit(); _globalThis.addon = new Addon(); - // The env will be replaced after esbuild - addon.env = __env__; ztoolkit.Tool.logOptionsGlobal.prefix = `[${config.addonName}]`; - ztoolkit.Tool.logOptionsGlobal.disableConsole = addon.env === "production"; + ztoolkit.Tool.logOptionsGlobal.disableConsole = + addon.data.env === "production"; Zotero.AddonTemplate = addon; // Trigger addon hook for initialization addon.hooks.onStartup(); diff --git a/src/locale.ts b/src/locale.ts deleted file mode 100644 index bc2ee83..0000000 --- a/src/locale.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { config } from "../package.json"; - -class AddonLocale { - private stringBundle: any; - - public initLocale() { - this.stringBundle = Components.classes["@mozilla.org/intl/stringbundle;1"] - .getService(Components.interfaces.nsIStringBundleService) - .createBundle(`chrome://${config.addonRef}/locale/addon.properties`); - } - - public getString(localString: string): string { - return this.stringBundle.GetStringFromName(localString); - } -} - -export default AddonLocale; diff --git a/src/examples.ts b/src/modules/examples.ts similarity index 87% rename from src/examples.ts rename to src/modules/examples.ts index 434fb9f..0dd8a2b 100644 --- a/src/examples.ts +++ b/src/modules/examples.ts @@ -1,19 +1,31 @@ -import { log } from "zotero-plugin-toolkit/dist/utils"; -import { config } from "../package.json"; +import { config } from "../../package.json"; +import { getString } from "./locale"; -export function example(type?: string): MethodDecorator { - return function ( - target: Object, - propertyKey: string | symbol, - descriptor: PropertyDescriptor - ) { - log("Calling example", target, type, propertyKey, descriptor); - return descriptor; +function example( + target: any, + propertyKey: string | symbol, + descriptor: PropertyDescriptor +) { + const original = descriptor.value; + descriptor.value = function (...args: any) { + try { + ztoolkit.Tool.log( + `Calling example ${target.name}.${String(propertyKey)}` + ); + return original.apply(this, args); + } catch (e) { + ztoolkit.Tool.log( + `Error in example ${target.name}.${String(propertyKey)}`, + e + ); + throw e; + } }; + return descriptor; } export class BasicExampleFactory { - @example() + @example static registerNotifier() { const callback = { notify: async ( @@ -22,7 +34,7 @@ export class BasicExampleFactory { ids: Array, extraData: { [key: string]: any } ) => { - if (!addon.alive) { + if (!addon.data.alive) { this.unregisterNotifier(notifierID); return; } @@ -58,23 +70,20 @@ export class BasicExampleFactory { ); } - @example() + @example private static unregisterNotifier(notifierID: string) { Zotero.Notifier.unregisterObserver(notifierID); } - @example() + @example static registerPrefs() { const prefOptions = { pluginID: config.addonID, src: rootURI + "chrome/content/preferences.xhtml", - label: addon.locale.getString("prefs.title"), + label: getString("prefs.title"), image: `chrome://${config.addonRef}/content/icons/favicon.png`, extraDTD: [`chrome://${config.addonRef}/locale/overlay.dtd`], defaultXUL: true, - onload: (win: Window) => { - addon.prefs.initPreferences(win); - }, }; if (ztoolkit.Compat.isZotero7()) { Zotero.PreferencePanes.register(prefOptions); @@ -83,7 +92,7 @@ export class BasicExampleFactory { } } - @example() + @example static unregisterPrefs() { if (!ztoolkit.Compat.isZotero7()) { ztoolkit.Compat.unregisterPrefPane(); @@ -92,7 +101,7 @@ export class BasicExampleFactory { } export class UIExampleFactory { - @example() + @example static registerStyleSheet() { const styles = ztoolkit.UI.creatElementsFromJSON(document, { tag: "link", @@ -108,30 +117,30 @@ export class UIExampleFactory { ?.classList.add("makeItRed"); } - @example() + @example static registerRightClickMenuItem() { const menuIcon = `chrome://${config.addonRef}/content/icons/favicon@0.5x.png`; // item menuitem with icon ztoolkit.UI.insertMenuItem("item", { tag: "menuitem", id: "zotero-itemmenu-addontemplate-test", - label: addon.locale.getString("menuitem.label"), + label: getString("menuitem.label"), oncommand: "alert('Hello World! Default Menuitem.')", icon: menuIcon, }); } - @example() + @example static registerRightClickMenuPopup() { ztoolkit.UI.insertMenuItem( "item", { tag: "menu", - label: addon.locale.getString("menupopup.label"), + label: getString("menupopup.label"), subElementOptions: [ { tag: "menuitem", - label: addon.locale.getString("menuitem.submenulabel"), + label: getString("menuitem.submenulabel"), oncommand: "alert('Hello World! Sub Menuitem.')", }, ], @@ -143,7 +152,7 @@ export class UIExampleFactory { ); } - @example() + @example static registerWindowMenuWithSeprator() { ztoolkit.UI.insertMenuItem("menuFile", { tag: "menuseparator", @@ -151,12 +160,12 @@ export class UIExampleFactory { // menu->File menuitem ztoolkit.UI.insertMenuItem("menuFile", { tag: "menuitem", - label: addon.locale.getString("menuitem.filemenulabel"), + label: getString("menuitem.filemenulabel"), oncommand: "alert('Hello World! File Menuitem.')", }); } - @example() + @example static async registerExtraColumn() { await ztoolkit.ItemTree.register( "test1", @@ -175,7 +184,7 @@ export class UIExampleFactory { ); } - @example() + @example static async registerExtraColumnWithCustomCell() { await ztoolkit.ItemTree.register( "test2", @@ -202,7 +211,7 @@ export class UIExampleFactory { ); } - @example() + @example static async registerCustomCellRenderer() { await ztoolkit.ItemTree.addRenderCellHook( "title", @@ -218,10 +227,10 @@ export class UIExampleFactory { await ztoolkit.ItemTree.refresh(); } - @example() + @example static registerLibraryTabPanel() { const tabId = ztoolkit.UI.registerLibraryTabPanel( - addon.locale.getString("tabpanel.lib.tab.label"), + getString("tabpanel.lib.tab.label"), (panel: XUL.Element, win: Window) => { const elem = ztoolkit.UI.creatElementsFromJSON(win.document, { tag: "vbox", @@ -266,10 +275,10 @@ export class UIExampleFactory { ); } - @example() + @example static async registerReaderTabPanel() { const tabId = await ztoolkit.UI.registerReaderTabPanel( - addon.locale.getString("tabpanel.reader.tab.label"), + getString("tabpanel.reader.tab.label"), ( panel: XUL.TabPanel | undefined, deck: XUL.Deck, @@ -344,8 +353,11 @@ export class UIExampleFactory { ); } - @example() + @example static unregisterUIExamples() { ztoolkit.unregisterAll(); } } +function initPreferences(win: Window) { + throw new Error("Function not implemented."); +} diff --git a/src/modules/locale.ts b/src/modules/locale.ts new file mode 100644 index 0000000..88fd966 --- /dev/null +++ b/src/modules/locale.ts @@ -0,0 +1,13 @@ +import { config } from "../../package.json"; + +export function initLocale() { + addon.data.locale = { + stringBundle: Components.classes["@mozilla.org/intl/stringbundle;1"] + .getService(Components.interfaces.nsIStringBundleService) + .createBundle(`chrome://${config.addonRef}/locale/addon.properties`), + }; +} + +export function getString(localString: string): string { + return addon.data.locale?.stringBundle.GetStringFromName(localString); +} diff --git a/src/modules/preferenceScript.ts b/src/modules/preferenceScript.ts new file mode 100644 index 0000000..98526a4 --- /dev/null +++ b/src/modules/preferenceScript.ts @@ -0,0 +1,41 @@ +import { config } from "../../package.json"; + +export function registerPrefsScripts(_window: Window) { + // This function is called when the prefs window is opened + // See addon/chrome/content/preferences.xul onpaneload + addon.data.prefs = { + window: _window, + }; + updatePrefsUI(); + bindPrefEvents(); +} + +function updatePrefsUI() { + // You can initialize some UI elements on prefs window + // with addon.data.prefs.window.document + // Or bind some events to the elements +} + +function bindPrefEvents() { + addon.data + .prefs!.window.document.querySelector( + `#zotero-prefpane-${config.addonRef}-enable` + ) + ?.addEventListener("command", (e) => { + ztoolkit.Tool.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.Tool.log(e); + addon.data.prefs!.window.alert( + `Successfully changed to ${(e.target as HTMLInputElement).value}!` + ); + }); +} diff --git a/src/tools/progress.ts b/src/modules/progressWindow.ts similarity index 100% rename from src/tools/progress.ts rename to src/modules/progressWindow.ts diff --git a/src/prefs.ts b/src/prefs.ts deleted file mode 100644 index e954a03..0000000 --- a/src/prefs.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { config } from "../package.json"; - -class AddonPrefs { - private _window!: Window; - - public initPreferences(_window: Window) { - // This function is called when the prefs window is opened - // See addon/chrome/content/preferences.xul onpaneload - this._window = _window; - this.updatePrefsUI(); - this.bindPrefEvents(); - } - - private updatePrefsUI() { - // You can initialize some UI elements on prefs window - // with this._window.document - // Or bind some events to the elements - } - - private bindPrefEvents() { - this._window.document - .querySelector(`#zotero-prefpane-${config.addonRef}-enable`) - ?.addEventListener("command", (e) => { - ztoolkit.Tool.log(e); - this._window.alert( - `Successfully changed to ${(e.target as XUL.Checkbox).checked}!` - ); - }); - - this._window.document - .querySelector(`#zotero-prefpane-${config.addonRef}-input`) - ?.addEventListener("change", (e) => { - ztoolkit.Tool.log(e); - this._window.alert( - `Successfully changed to ${(e.target as HTMLInputElement).value}!` - ); - }); - } -} - -export default AddonPrefs;