refactor: make examples more friendly

This commit is contained in:
xiangyu 2023-01-05 17:22:17 +08:00
parent 6426ae5198
commit 4469cbc82d
17 changed files with 351 additions and 325 deletions

101
README.md
View File

@ -19,6 +19,32 @@ This is an addon/plugin template for [Zotero](https://www.zotero.org/).
- Some sample code of UI and lifecycle.
- ⭐Compatibilities for Zotero 6 & Zotero 7.(using [zotero-plugin-toolkit](https://github.com/windingwind/zotero-plugin-toolkit))
## Examples
This repo provides examples for [zotero-plugin-toolkit](https://github.com/windingwind/zotero-plugin-toolkit) APIs.
Search `@example` in `src/examples.ts`. The examples are called in `src/hooks.ts`.
### Basic Examples
- registerNotifier
- registerPrefs, unregisterPrefs
### UI Examples
![image](https://user-images.githubusercontent.com/33902321/209274492-7aa94912-af38-4154-af46-dc8f59640de3.png)
- registerStyleSheet(the official make-it-red example)
- registerRightClickMenuItem
- registerRightClickMenuPopup
- registerWindowMenuWithSeprator
- registerExtraColumn
- registerExtraColumnWithCustomCell
- registerCustomCellRenderer
- registerLibraryTabPanel
- registerReaderTabPanel
- unregisterUIExamples
## Quick Start Guide
- Fork this repo;
@ -46,58 +72,38 @@ This is an addon/plugin template for [Zotero](https://www.zotero.org/).
- Run `npm run build` to build the plugin in production mode. Run `npm run build-dev` to build the plugin in development mode. The xpi for installation and the built code is under builds folder.
> What the difference between dev & prod?
>
> - This environment variable is stored in `Zotero.AddonTemplate.env`. The outputs to console is disabled in prod mode.
> - You can decide what users cannot see/use based on this variable.
### About Life Cycle
### About Hooks
> See also `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 `events.ts` > `onInit` is called.
- Initialize anything you want, including notify listeners, preference panes(`initPrefs`), and UI elements(`initViews`).
2. In the main entrance `index.js`, the plugin object is injected under `Zotero` and `hooks.ts` > `onStartup` is called.
- Initialize anything you want, including notify listeners, preference panes, and UI elements.
3. When uninstall/disabled triggered from Zotero, `bootstrap.js` > `shutdown` is called.
- `events.ts` > `onUninit` is called. Remove UI elements(`unInitViews`), preference panes(`uninitPrefs`), or anything created by the plugin.
- `events.ts` > `onShutdown` is called. Remove UI elements, preference panes, or anything created by the plugin.
- Remove scripts and release resources.
### About Global Variables
> See also `src/index.ts`
The bootstrapped plugin runs in a sandbox, which does not have default global variables like `Zotero` or `window`, which we used to have in the overlay plugins' window environment.
This template registers the following variables to the global scope:
```ts
Zotero, ZoteroPane, Zotero_Tabs, window, document, rootURI, ZToolkit
Zotero, ZoteroPane, Zotero_Tabs, window, document, rootURI, ztoolkit, addon;
```
See `src/events.ts` > `initGlobalVariables` for more details.
### Examples
See https://github.com/windingwind/zotero-plugin-toolkit for more detailed API documentations.
#### Menu (file, edit, view, ...) & Right-click Menu (item, collection/library)
**File Menu**
![image](https://user-images.githubusercontent.com/33902321/208077117-e9ae3ca8-f9c7-4549-8835-1de5ea8c665f.png)
https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L52-L60
**Item Menu**
![image](https://user-images.githubusercontent.com/33902321/208078502-75d547b7-1cff-4538-802a-3d5127a8b617.png)
https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L23-L51
`insertMenuItem` resolved the input object and inject the menu items.
You can choose an anchor element and insert before/after it using `insertPosition` and `anchorElement`. Default the insert position is the end of the menu.
#### Preference, for both Zotero 6 and Zotero 7 (all in bootstrap)
### About Preference
Zotero 6 doesn't support preference pane injection in bootstrap mode, thus I write a register for Zotero 6 or lower.
@ -133,7 +139,7 @@ Remember to call `unregisterPrefPane()` on plugin unload.
https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L88-L90
#### Create Elements API
### Create Elements API
The plugin template provides new APIs for bootstrap plugins. We have two reasons to use these APIs, instead of the `createElement/createElementNS`:
@ -142,30 +148,13 @@ The plugin template provides new APIs for bootstrap plugins. We have two reasons
There are more advanced APIs for creating elements in batch: `creatElementsFromJSON`. Input an element tree in JSON and return a fragment/element. These elements are also maintained by this plugin template.
#### Extra Column in Library
Using [Zotero Plugin Toolkit:ItemTreeTool](https://github.com/windingwind/zotero-plugin-toolkit/blob/HEAD/docs/zotero-plugin-toolkit.itemtreetool.md) to register an extra column in `src/views.ts`.
```ts
ZToolkit.ItemTree.registerExample();
```
This will register a column with dataKey `test`. Looks like:
![image](https://user-images.githubusercontent.com/33902321/209274492-7aa94912-af38-4154-af46-dc8f59640de3.png)
Remember to unregister it when exiting.
```ts
ZToolkit.ItemTree.unregister("test");
```
### Directory Structure
This section shows the directory structure of a template.
- All `.js/.ts` code files are in `./src`;
- Addon config files: `./addon/chrome.manifest`, `./addon/install.rdf`;
- UI files: `./addon/chrome/content/*.xul`. The `overlay.xul` also defines the main entrance;
- Addon config files: `./addon/chrome.manifest`, `./addon/install.rdf`, and `./addon/manifest.json`;
- UI files: `./addon/chrome/content/*.xhtml`.
- Locale files: `./addon/chrome/locale/[*.dtd, *.properties]`;
- Resource files: `./addon/chrome/skin/default/__addonRef__/*.dtd`;
- Preferences file: `./addon/chrome/defaults/preferences/defaults.js`;
@ -184,8 +173,9 @@ This section shows the directory structure of a template.
├─.github # github conf
├─addon # addon dir
│ │ chrome.manifest #addon conf
│ │ install.rdf # addon install conf
│ │ chrome.manifest # for Zotero 6
│ │ manifest.json # for Zotero 7
│ │ install.rdf # addon install conf, for Zotero 6
│ │ bootstrap.js # addon load/unload script, like a main.c
│ │
│ └─chrome
@ -211,10 +201,9 @@ This section shows the directory structure of a template.
└─src # source code
│ index.ts # main entry
│ module.ts # module class
│ addon.ts # base class
events.ts # events class
views.ts # UI class
hooks.ts # lifecycle hooks
examples.ts # examples factory
│ locale.ts # Locale class for properties files
└─ prefs.ts # preferences class

2
addon/bootstrap.js vendored
View File

@ -102,7 +102,7 @@ function shutdown({ id, version, resourceURI, rootURI }, reason) {
Components.interfaces.nsISupports
).wrappedJSObject;
}
Zotero.AddonTemplate.events.onUnInit(Zotero);
Zotero.AddonTemplate.hooks.onShutdown();
Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService)

View File

@ -1,3 +1,5 @@
startup.begin=Addon is loading
startup.finish=Addon is ready
menuitem.label=Addon Template: Menuitem
menupopup.label=Addon Template: Menupopup
menuitem.submenulabel=Addon Template

View File

@ -1,7 +1,9 @@
menuitem.label=Addon Template: 菜单
menupopup.label=Addon Template: 弹出菜单
menuitem.submenulabel=Addon Template
menuitem.filemenulabel=Addon Template: 文件菜单
startup.begin=插件加载中
startup.finish=插件已就绪
menuitem.label=插件模板: 菜单
menupopup.label=插件模板: 弹出菜单
menuitem.submenulabel=插件模板:子菜单
menuitem.filemenulabel=插件模板: 文件菜单
prefs.title=插件模板
tabpanel.lib.tab.label=库标签
tabpanel.reader.tab.label=阅读器标签

View File

@ -101,7 +101,7 @@ async function main() {
.build({
entryPoints: ["src/index.ts"],
define: {
__env__: process.env.NODE_ENV,
__env__: `"${process.env.NODE_ENV}"`,
},
bundle: true,
// Entry should be the same as addon/chrome/content/overlay.xul

View File

@ -40,6 +40,6 @@
"esbuild": "^0.16.10",
"release-it": "^14.14.3",
"replace-in-file": "^6.3.5",
"zotero-types": "^0.1.2"
"zotero-types": "^0.1.4"
}
}

View File

@ -1,25 +1,24 @@
import AddonEvents from "./events";
import AddonHooks from "./hooks";
import AddonPrefs from "./prefs";
import AddonViews from "./views";
import AddonLocale from "./locale";
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 events: AddonEvents;
// UI operations
public views: AddonViews;
public hooks: AddonHooks;
// Scripts for prefpane window
public prefs: AddonPrefs;
// Runtime locale with .properties
public locale: AddonLocale;
constructor() {
this.events = new AddonEvents(this);
this.views = new AddonViews(this);
this.prefs = new AddonPrefs(this);
this.locale = new AddonLocale(this);
this.alive = true;
this.hooks = new AddonHooks();
this.prefs = new AddonPrefs();
this.locale = new AddonLocale();
}
}

View File

@ -1,119 +0,0 @@
import Addon from "./addon";
import AddonModule from "./module";
import { config } from "../package.json";
import ZoteroToolkit from "zotero-plugin-toolkit";
class AddonEvents extends AddonModule {
constructor(parent: Addon) {
super(parent);
}
// This function is the setup code of the addon
public async onInit() {
this.initGlobalVariables();
// @ts-ignore
const development = "development";
const production = "production";
// The env will be replaced after esbuild
// @ts-ignore
this._Addon.env = __env__;
ZToolkit.Tool.logOptionsGlobal.disableConsole =
this._Addon.env === "production";
ZToolkit.Tool.log("init called");
// Initialize locale provider
this._Addon.locale.initLocale();
// Initialize preference window
this.initPrefs();
// Initialize notifier callback
this.initNotifier();
// Initialize UI elements
this._Addon.views.initViews();
}
public onUnInit(): void {
ZToolkit.Tool.log("uninit called");
this.unInitPrefs();
// Remove elements and do clean up
this._Addon.views.unInitViews();
// Remove addon object
Zotero.AddonTemplate = undefined;
}
private initGlobalVariables() {
_globalThis.ZToolkit = new ZoteroToolkit();
ZToolkit.Tool.logOptionsGlobal.prefix = `[${config.addonName}]`;
_globalThis.Zotero = ZToolkit.Compat.getGlobal("Zotero");
_globalThis.ZoteroPane = ZToolkit.Compat.getGlobal("ZoteroPane");
_globalThis.Zotero_Tabs = ZToolkit.Compat.getGlobal("Zotero_Tabs");
_globalThis.window = ZToolkit.Compat.getGlobal("window");
_globalThis.document = ZToolkit.Compat.getGlobal("document");
ZToolkit.Tool.log("initializeing global variables");
}
private initNotifier() {
const callback = {
notify: async (
event: string,
type: string,
ids: Array<string>,
extraData: { [key: string]: any }
) => {
// You can add your code to the corresponding notify type
if (
event == "select" &&
type == "tab" &&
extraData[ids[0]].type == "reader"
) {
// Select a reader tab
}
if (event == "add" && type == "item") {
// Add an item
}
},
};
// Register the callback in Zotero as an item observer
let notifierID = Zotero.Notifier.registerObserver(callback, [
"tab",
"item",
"file",
]);
// Unregister callback when the window closes (important to avoid a memory leak)
Zotero.getMainWindow().addEventListener(
"unload",
function (e: Event) {
Zotero.Notifier.unregisterObserver(notifierID);
},
false
);
}
private initPrefs() {
const prefOptions = {
pluginID: config.addonID,
src: rootURI + "chrome/content/preferences.xhtml",
label: this._Addon.locale.getString("prefs.title"),
image: `chrome://${config.addonRef}/content/icons/favicon.png`,
extraDTD: [`chrome://${config.addonRef}/locale/overlay.dtd`],
defaultXUL: true,
onload: (win: Window) => {
this._Addon.prefs.initPreferences(win);
},
};
if (ZToolkit.Compat.isZotero7()) {
Zotero.PreferencePanes.register(prefOptions);
} else {
ZToolkit.Compat.registerPrefPane(prefOptions);
}
}
private unInitPrefs() {
if (!ZToolkit.Compat.isZotero7()) {
ZToolkit.Compat.unregisterPrefPane();
}
}
}
export default AddonEvents;

View File

@ -1,27 +1,100 @@
import Addon from "./addon";
import AddonModule from "./module";
import { log } from "zotero-plugin-toolkit/dist/utils";
import { config } from "../package.json";
class AddonViews extends AddonModule {
// You can store some element in the object attributes
private progressWindowIcon: { [key: string]: string };
constructor(parent: Addon) {
super(parent);
this.progressWindowIcon = {
success: "chrome://zotero/skin/tick.png",
fail: "chrome://zotero/skin/cross.png",
default: `chrome://${config.addonRef}/content/icons/favicon.png`,
export function example(type?: string): MethodDecorator {
return function (
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) {
log("Calling example", target, type, propertyKey, descriptor);
return descriptor;
};
}
public initViews() {
// You can init the UI elements that
// cannot be initialized with overlay.xul
ZToolkit.Tool.log("Initializing UI");
export class BasicExampleFactory {
@example()
static registerNotifier() {
const callback = {
notify: async (
event: string,
type: string,
ids: Array<string>,
extraData: { [key: string]: any }
) => {
if (!addon.alive) {
this.unregisterNotifier(notifierID);
return;
}
ztoolkit.Tool.log("notify", event, type, ids, extraData);
// You can add your code to the corresponding notify type
if (
event == "select" &&
type == "tab" &&
extraData[ids[0]].type == "reader"
) {
// Select a reader tab
}
if (event == "add" && type == "item") {
// Add an item
}
},
};
// register style sheet
const styles = ZToolkit.UI.creatElementsFromJSON(document, {
// 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()
private static unregisterNotifier(notifierID: string) {
Zotero.Notifier.unregisterObserver(notifierID);
}
@example()
static registerPrefs() {
const prefOptions = {
pluginID: config.addonID,
src: rootURI + "chrome/content/preferences.xhtml",
label: addon.locale.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);
} else {
ztoolkit.Compat.registerPrefPane(prefOptions);
}
}
@example()
static unregisterPrefs() {
if (!ztoolkit.Compat.isZotero7()) {
ztoolkit.Compat.unregisterPrefPane();
}
}
}
export class UIExampleFactory {
@example()
static registerStyleSheet() {
const styles = ztoolkit.UI.creatElementsFromJSON(document, {
tag: "link",
directAttributes: {
type: "text/css",
@ -33,26 +106,32 @@ class AddonViews extends AddonModule {
document
.getElementById("zotero-item-pane-content")
?.classList.add("makeItRed");
}
@example()
static registerRightClickMenuItem() {
const menuIcon = `chrome://${config.addonRef}/content/icons/favicon@0.5x.png`;
// item menuitem with icon
ZToolkit.UI.insertMenuItem("item", {
ztoolkit.UI.insertMenuItem("item", {
tag: "menuitem",
id: "zotero-itemmenu-addontemplate-test",
label: this._Addon.locale.getString("menuitem.label"),
label: addon.locale.getString("menuitem.label"),
oncommand: "alert('Hello World! Default Menuitem.')",
icon: menuIcon,
});
// item menupopup with sub-menuitems
ZToolkit.UI.insertMenuItem(
}
@example()
static registerRightClickMenuPopup() {
ztoolkit.UI.insertMenuItem(
"item",
{
tag: "menu",
label: this._Addon.locale.getString("menupopup.label"),
label: addon.locale.getString("menupopup.label"),
subElementOptions: [
{
tag: "menuitem",
label: this._Addon.locale.getString("menuitem.submenulabel"),
label: addon.locale.getString("menuitem.submenulabel"),
oncommand: "alert('Hello World! Sub Menuitem.')",
},
],
@ -62,24 +141,24 @@ class AddonViews extends AddonModule {
"#zotero-itemmenu-addontemplate-test"
) as XUL.MenuItem
);
ZToolkit.UI.insertMenuItem("menuFile", {
}
@example()
static registerWindowMenuWithSeprator() {
ztoolkit.UI.insertMenuItem("menuFile", {
tag: "menuseparator",
});
// menu->File menuitem
ZToolkit.UI.insertMenuItem("menuFile", {
ztoolkit.UI.insertMenuItem("menuFile", {
tag: "menuitem",
label: this._Addon.locale.getString("menuitem.filemenulabel"),
label: addon.locale.getString("menuitem.filemenulabel"),
oncommand: "alert('Hello World! File Menuitem.')",
});
/**
* Example: menu items ends
*/
}
/**
* Example: extra column starts
*/
// Initialize extra columns
ZToolkit.ItemTree.register(
@example()
static async registerExtraColumn() {
await ztoolkit.ItemTree.register(
"test1",
"text column",
(
@ -94,7 +173,11 @@ class AddonViews extends AddonModule {
iconPath: "chrome://zotero/skin/cross.png",
}
);
ZToolkit.ItemTree.register(
}
@example()
static async registerExtraColumnWithCustomCell() {
await ztoolkit.ItemTree.register(
"test2",
"custom column",
(
@ -117,15 +200,11 @@ class AddonViews extends AddonModule {
},
}
);
/**
* Example: extra column ends
*/
}
/**
* Example: custom cell starts
*/
// Customize cells
ZToolkit.ItemTree.addRenderCellHook(
@example()
static async registerCustomCellRenderer() {
await ztoolkit.ItemTree.addRenderCellHook(
"title",
(index: number, data: string, column: any, original: Function) => {
const span = original(index, data, column) as HTMLSpanElement;
@ -134,17 +213,17 @@ class AddonViews extends AddonModule {
return span;
}
);
/**
* Example: custom cell ends
*/
// @ts-ignore
// This is a private method. Make it public in toolkit.
await ztoolkit.ItemTree.refresh();
}
/**
* Example: extra library tab starts
*/
const libTabId = ZToolkit.UI.registerLibraryTabPanel(
this._Addon.locale.getString("tabpanel.lib.tab.label"),
@example()
static registerLibraryTabPanel() {
const tabId = ztoolkit.UI.registerLibraryTabPanel(
addon.locale.getString("tabpanel.lib.tab.label"),
(panel: XUL.Element, win: Window) => {
const elem = ZToolkit.UI.creatElementsFromJSON(win.document, {
const elem = ztoolkit.UI.creatElementsFromJSON(win.document, {
tag: "vbox",
namespace: "xul",
subElementOptions: [
@ -172,7 +251,7 @@ class AddonViews extends AddonModule {
{
type: "click",
listener: () => {
ZToolkit.UI.unregisterLibraryTabPanel(libTabId);
ztoolkit.UI.unregisterLibraryTabPanel(tabId);
},
},
],
@ -185,30 +264,26 @@ class AddonViews extends AddonModule {
targetIndex: 1,
}
);
/**
* Example: extra library tab ends
*/
}
/**
* Example: extra reader tab starts
*/
const readerTabId = `${config.addonRef}-extra-reader-tab`;
ZToolkit.UI.registerReaderTabPanel(
this._Addon.locale.getString("tabpanel.reader.tab.label"),
@example()
static async registerReaderTabPanel() {
const tabId = await ztoolkit.UI.registerReaderTabPanel(
addon.locale.getString("tabpanel.reader.tab.label"),
(
panel: XUL.Element,
panel: XUL.TabPanel | undefined,
deck: XUL.Deck,
win: Window,
reader: _ZoteroReaderInstance
) => {
if (!panel) {
ZToolkit.Tool.log(
ztoolkit.Tool.log(
"This reader do not have right-side bar. Adding reader tab skipped."
);
return;
}
ZToolkit.Tool.log(reader);
const elem = ZToolkit.UI.creatElementsFromJSON(win.document, {
ztoolkit.Tool.log(reader);
const elem = ztoolkit.UI.creatElementsFromJSON(win.document, {
tag: "vbox",
id: `${config.addonRef}-${reader._instanceID}-extra-reader-tab-div`,
namespace: "xul",
@ -254,7 +329,7 @@ class AddonViews extends AddonModule {
{
type: "click",
listener: () => {
ZToolkit.UI.unregisterReaderTabPanel(readerTabId);
ztoolkit.UI.unregisterReaderTabPanel(tabId);
},
},
],
@ -264,49 +339,13 @@ class AddonViews extends AddonModule {
panel.append(elem);
},
{
tabId: readerTabId,
targetIndex: 1,
}
);
/**
* Example: extra reader tab ends
*/
}
public unInitViews() {
ZToolkit.Tool.log("Uninitializing UI");
ZToolkit.unregisterAll();
// toolkit.UI.removeAddonElements();
// // Remove extra columns
// toolkit.ItemTree.unregister("test1");
// toolkit.ItemTree.unregister("test2");
// // Remove title cell patch
// toolkit.ItemTree.removeRenderCellHook("title");
// toolkit.UI.unregisterReaderTabPanel(
// `${config.addonRef}-extra-reader-tab`
// );
}
public showProgressWindow(
header: string,
context: string,
type: string = "default",
t: number = 5000
) {
// A simple wrapper of the Zotero ProgressWindow
let progressWindow = new Zotero.ProgressWindow({ closeOnClick: true });
progressWindow.changeHeadline(header);
progressWindow.progress = new progressWindow.ItemProgress(
this.progressWindowIcon[type],
context
);
progressWindow.show();
if (t > 0) {
progressWindow.startCloseTimer(t);
@example()
static unregisterUIExamples() {
ztoolkit.unregisterAll();
}
}
}
export default AddonViews;

62
src/hooks.ts Normal file
View File

@ -0,0 +1,62 @@
import { BasicExampleFactory, UIExampleFactory } from "./examples";
import { changeProgressWindowLine, showProgressWindow } from "./tools/progress";
import { config } from "../package.json";
class AddonHooks {
public async onStartup() {
addon.locale.initLocale();
const w = showProgressWindow(
config.addonName,
addon.locale.getString("startup.begin"),
"default",
-1
);
changeProgressWindowLine(w, { newProgress: 0 });
BasicExampleFactory.registerPrefs();
BasicExampleFactory.registerNotifier();
await Zotero.Promise.delay(1000);
changeProgressWindowLine(w, {
newProgress: 30,
newText: `[30%] ${addon.locale.getString("startup.begin")}`,
});
UIExampleFactory.registerStyleSheet();
UIExampleFactory.registerRightClickMenuItem();
UIExampleFactory.registerRightClickMenuPopup();
UIExampleFactory.registerWindowMenuWithSeprator();
await UIExampleFactory.registerExtraColumn();
await UIExampleFactory.registerExtraColumnWithCustomCell();
await UIExampleFactory.registerCustomCellRenderer();
UIExampleFactory.registerLibraryTabPanel();
await UIExampleFactory.registerReaderTabPanel();
await Zotero.Promise.delay(1000);
changeProgressWindowLine(w, {
newProgress: 100,
newText: `[100%] ${addon.locale.getString("startup.finish")}`,
});
w.startCloseTimer(5000);
}
public onShutdown(): void {
BasicExampleFactory.unregisterPrefs();
UIExampleFactory.unregisterUIExamples();
// Remove addon object
addon.alive = false;
delete Zotero.AddonTemplate;
}
}
export default AddonHooks;

View File

@ -1,7 +1,22 @@
import ZoteroToolkit from "zotero-plugin-toolkit";
import { getGlobal } from "zotero-plugin-toolkit/dist/utils";
import Addon from "./addon";
import { config } from "../package.json";
if (!Zotero.AddonTemplate) {
Zotero.AddonTemplate = new Addon();
// @ts-ignore
Zotero.AddonTemplate.events.onInit();
if (!getGlobal("Zotero").AddonTemplate) {
// Set global variables
_globalThis.Zotero = getGlobal("Zotero");
_globalThis.ZoteroPane = getGlobal("ZoteroPane");
_globalThis.Zotero_Tabs = getGlobal("Zotero_Tabs");
_globalThis.window = getGlobal("window");
_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";
Zotero.AddonTemplate = addon;
// Trigger addon hook for initialization
addon.hooks.onStartup();
}

View File

@ -1,7 +1,6 @@
import AddonModule from "./module";
import { config } from "../package.json";
class AddonLocale extends AddonModule {
class AddonLocale {
private stringBundle: any;
public initLocale() {

View File

@ -1,10 +0,0 @@
import Addon from "./addon";
class AddonModule {
protected _Addon: Addon;
constructor(parent: Addon) {
this._Addon = parent;
}
}
export default AddonModule;

View File

@ -1,17 +1,12 @@
import Addon from "./addon";
import AddonModule from "./module";
import { config } from "../package.json";
class AddonPrefs extends AddonModule {
class AddonPrefs {
private _window!: Window;
constructor(parent: Addon) {
super(parent);
}
public initPreferences(_window: Window) {
// This function is called when the prefs window is opened
// See addon/chrome/content/preferences.xul onpaneload
this._window = _window;
ZToolkit.Tool.log("init preferences");
this.updatePrefsUI();
this.bindPrefEvents();
}
@ -20,14 +15,13 @@ class AddonPrefs extends AddonModule {
// You can initialize some UI elements on prefs window
// with this._window.document
// Or bind some events to the elements
ZToolkit.Tool.log("init preferences UI");
}
private bindPrefEvents() {
this._window.document
.querySelector(`#zotero-prefpane-${config.addonRef}-enable`)
?.addEventListener("command", (e) => {
ZToolkit.Tool.log(e);
ztoolkit.Tool.log(e);
this._window.alert(
`Successfully changed to ${(e.target as XUL.Checkbox).checked}!`
);
@ -36,7 +30,7 @@ class AddonPrefs extends AddonModule {
this._window.document
.querySelector(`#zotero-prefpane-${config.addonRef}-input`)
?.addEventListener("change", (e) => {
ZToolkit.Tool.log(e);
ztoolkit.Tool.log(e);
this._window.alert(
`Successfully changed to ${(e.target as HTMLInputElement).value}!`
);

48
src/tools/progress.ts Normal file
View File

@ -0,0 +1,48 @@
import { config } from "../../package.json";
const progressWindowIcon = {
success: "chrome://zotero/skin/tick.png",
fail: "chrome://zotero/skin/cross.png",
default: `chrome://${config.addonRef}/content/icons/favicon.png`,
};
export function showProgressWindow(
header: string,
context: string,
type: "success" | "fail" | "default" = "default",
t: number = 5000
): _ZoteroProgressWindow {
// A simple wrapper of the Zotero ProgressWindow
let progressWindow = new Zotero.ProgressWindow({
closeOnClick: true,
}) as _ZoteroProgressWindow;
progressWindow.changeHeadline(header);
// @ts-ignore
progressWindow.progress = new progressWindow.ItemProgress(
progressWindowIcon[type],
context
);
progressWindow.show();
if (t > 0) {
progressWindow.startCloseTimer(t);
}
return progressWindow;
}
export function changeProgressWindowLine(
progressWindow: _ZoteroProgressWindow,
options: {
newText?: string;
newIcon?: string;
newProgress?: number;
}
) {
// @ts-ignore
const progress = progressWindow.progress as _ZoteroItemProgress;
if (!progress) {
return;
}
options.newText && progress.setText(options.newText);
options.newIcon && progress.setIcon(options.newIcon);
options.newProgress && progress.setProgress(options.newProgress);
}

View File

@ -1,5 +1,6 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"module": "commonjs",
"target": "ES2016",
"resolveJsonModule": true,

9
typing/global.d.ts vendored
View File

@ -5,9 +5,14 @@ declare const _globalThis: {
Zotero_Tabs: typeof Zotero_Tabs;
window: Window;
document: Document;
ZToolkit: typeof ZToolkit;
ztoolkit: typeof ztoolkit;
addon: typeof addon;
};
declare const ZToolkit: import("zotero-plugin-toolkit").ZoteroToolkit;
declare const ztoolkit: import("zotero-plugin-toolkit").ZoteroToolkit;
declare const rootURI: string;
declare const addon: import("../src/addon").default;
declare const __env__: "production" | "development";