Skip to content

Commit

Permalink
change: use zotero-plugin-toolkit
Browse files Browse the repository at this point in the history
change: use strict ts mode
change: dependencies
  • Loading branch information
xiangyu committed Dec 21, 2022
1 parent c3db612 commit 969a527
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 865 deletions.
117 changes: 14 additions & 103 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This is an addon/plugin template for [Zotero](https://www.zotero.org/).
- Release to GitHub automatically(using [release-it](https://github.com/release-it/release-it));
- Extensive skeleton;
- Some sample code of UI and lifecycle.
- ⭐Compatibilities for Zotero 6 & Zotero 7.
- ⭐Compatibilities for Zotero 6 & Zotero 7.(using [zotero-plugin-toolkit](https://github.com/windingwind/zotero-plugin-toolkit))

## Quick Start Guide

Expand All @@ -26,6 +26,7 @@ This is an addon/plugin template for [Zotero](https://www.zotero.org/).
- Modify the settings in `./package.json`, including:

```
version,
author,
description,
homepage,
Expand All @@ -38,7 +39,7 @@ This is an addon/plugin template for [Zotero](https://www.zotero.org/).

> Be careful to set the addonID and addonRef to avoid confliction.
- Run `npm install` to setup the plugin and install dependencies. If you don't have NodeJS installed, please download it [here](https://nodejs.org/en/);
- Run `npm install` to set up the plugin and install dependencies. If you don't have NodeJS installed, please download it [here](https://nodejs.org/en/);
- Run `npm run build` to build the plugin. The xpi for installation and the built code is under builds folder.

### Plugin Life Cycle
Expand All @@ -56,6 +57,8 @@ This is an addon/plugin template for [Zotero](https://www.zotero.org/).

### 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**
Expand All @@ -70,61 +73,15 @@ https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062d

https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L23-L51

`Utils.UI.insertMenuItem` resolved the input object and inject the menu items.

Available types `menuFile`, `menuEdit`, ...:

```ts
defaultMenuPopupSelectors: {
menuFile: "#menu_FilePopup",
menuEdit: "#menu_EditPopup",
menuView: "#menu_viewPopup",
menuGo: "#menu_goPopup",
menuTools: "#menu_ToolsPopup",
menuHelp: "#menu_HelpPopup",
collection: "#zotero-collectionmenu",
item: "#zotero-itemmenu",
},
```

You can choose an anchor element and insert before/after it using `insertPosition` and `anchorElement`. Default the insert position is the end of menu.
`insertMenuItem` resolved the input object and inject the menu items.

```ts
insertMenuItem: (
menuPopup: XUL.Menupopup | string,
options: MenuitemOptions,
insertPosition?: "before" | "after",
anchorElement?: XUL.Element
) => boolean;
```

Full options you can use:

```ts
declare interface MenuitemOptions {
tag: "menuitem" | "menu" | "menuseparator";
id?: string;
label?: string;
// data url (chrome://xxx.png) or base64 url (data:image/png;base64,xxx)
icon?: string;
class?: string;
styles?: { [key: string]: string };
hidden?: boolean;
disabled?: boolean;
oncommand?: string;
commandListener?: EventListenerOrEventListenerObject;
// Attributes below are used when type === "menu"
popupId?: string;
onpopupshowing?: string;
subElementOptions?: Array<MenuitemOptions>;
}
```
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)

Zotero 6 doesn't support preference pane injection in bootstrap mode, thus I write a register for Zotero 6 or lower.

You only need to maintain one `preferences.xhtml` which runs natively on Zotero 7 and let the plugin template handle when it is running on Zotero 6.
You only need to maintain one `preferences.xhtml` which runs natively on Zotero 7 and let the plugin template handle it when it is running on Zotero 6.

<table style="margin-left: auto; margin-right: auto;">
<tr>
Expand All @@ -141,9 +98,9 @@ You only need to maintain one `preferences.xhtml` which runs natively on Zotero

https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L63-L82

Call `Utils.Compat.registerPrefPane` when it's on Zotero 6.
Call `registerPrefPane` when it's on Zotero 6.

Note that `<preferences>` element is deprecated. Please use the full pref-key in elements' `preference` attribute. Like:
Note that `<preferences>` element is deprecated. Please use the full pref-key in the elements' `preference` attribute. Like:

```xml
<checkbox label="&zotero.__addonRef__.pref.enable.label;" preference="extensions.zotero.__addonRef__.enable"
Expand All @@ -152,63 +109,18 @@ Note that `<preferences>` element is deprecated. Please use the full pref-key in

The elements with `preference` attributes will bind to Zotero preferences.

Remember to call `Utils.Compat.unregisterPrefPane()` on plugin unload.
Remember to call `unregisterPrefPane()` on plugin unload.

https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L88-L90

#### 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`:

- In bootstrap mode, plugins have to clean up all UI elements on exit (disable or uninstall), which is very annoying. Using the `Utils.UI.createElement`, the plugin template will maintain these elements. Just `Utils.UI.removeAddonElements` on exit.
- Zotero 7 requires createElement()/createElementNS() → createXULElement() for remaining XUL elements, while on Zotero 6 doesn't support `createXULElement`. Using `Utils.UI.createElement`, it switches API depending on the current platform automatically.

Definition:
- In bootstrap mode, plugins have to clean up all UI elements on exit (disable or uninstall), which is very annoying. Using the `createElement`, the plugin template will maintain these elements. Just `removeAddonElements` on exit.
- Zotero 7 requires createElement()/createElementNS() → createXULElement() for remaining XUL elements, while Zotero 6 doesn't support `createXULElement`. Using `createElement`, it switches API depending on the current platform automatically.

```ts
function createElement (
doc: Document,
tagName: string,
namespace: "html" | "svg" | "xul"
) => XUL.Element | DocumentFragment | HTMLElement | SVGAElement;
```

There are more advanced APIs for creating elements in batch: `Utils.UI.creatElementsFromJSON`. Input an element tree in JSON and return a fragment/element. These elements are also maintained by this plugin template.

Definition:

```ts
function creatElementsFromJSON (
doc: Document,
options: ElementOptions
) => XUL.Element | DocumentFragment | HTMLElement | SVGAElement;
```

Available options:

```ts
declare interface ElementOptions {
tag: string;
id?: string;
namespace?: "html" | "svg" | "xul";
styles?: { [key: string]: string };
directAttributes?: { [key: string]: string | boolean | number };
attributes?: { [key: string]: string | boolean | number };
listeners?: Array<
| [
string,
EventListenerOrEventListenerObject,
boolean | AddEventListenerOptions
]
| [string, EventListenerOrEventListenerObject]
>;
checkExistanceParent?: HTMLElement;
ignoreIfExists?: boolean;
removeIfExists?: boolean;
customCheck?: () => boolean;
subElementOptions?: Array<ElementOptions>;
}
```
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.

### Directory Structure

Expand Down Expand Up @@ -263,7 +175,6 @@ This section shows the directory structure of a template.
│ module.ts # module class
│ addon.ts # base class
│ events.ts # events class
│ utils.ts # Utils class
│ views.ts # UI class
└─ prefs.ts # preferences class

Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@
"releasepage": "https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi",
"updaterdf": "https://raw.githubusercontent.com/windingwind/zotero-addon-template/master/update.json",
"dependencies": {
"compressing": "^1.5.1",
"esbuild": "^0.15.16",
"replace-in-file": "^6.3.2"
"zotero-plugin-toolkit": "^0.0.1"
},
"devDependencies": {
"@types/node": "^18.7.20",
"compressing": "^1.5.1",
"esbuild": "^0.15.16",
"replace-in-file": "^6.3.2",
"release-it": "^14.14.0",
"zotero-types": "^0.0.8"
}
}
}
11 changes: 6 additions & 5 deletions src/addon.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import AddonEvents from "./events";
import AddonPrefs from "./prefs";
import AddonUtils from "./utils";
import AddonViews from "./views";

import ZoteroToolkit from "zotero-plugin-toolkit";

class Addon {
public Zotero: _ZoteroConstructable;
public Zotero!: _ZoteroConstructable;
public events: AddonEvents;
public views: AddonViews;
public prefs: AddonPrefs;
public Utils: AddonUtils;
public toolkit: ZoteroToolkit;
// root path to access the resources
public rootURI: string;
public rootURI!: string;

constructor() {
this.events = new AddonEvents(this);
this.views = new AddonViews(this);
this.prefs = new AddonPrefs(this);
this.Utils = new AddonUtils(this);
this.toolkit = new ZoteroToolkit();
}
}

Expand Down
18 changes: 9 additions & 9 deletions src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class AddonEvents extends AddonModule {
event: string,
type: string,
ids: Array<string>,
extraData: object
extraData: { [key: string]: any }
) => {
// You can add your code to the corresponding notify type
if (
Expand All @@ -33,7 +33,7 @@ class AddonEvents extends AddonModule {
// @ts-ignore
this._Addon.rootURI = rootURI;
// This function is the setup code of the addon
this._Addon.Utils.Tool.log(`${addonName}: init called`);
this._Addon.toolkit.Tool.log(`${addonName}: init called`);

// Register the callback in Zotero as an item observer
let notifierID = Zotero.Notifier.registerObserver(this.notifierCallback, [
Expand All @@ -45,7 +45,7 @@ class AddonEvents extends AddonModule {
// Unregister callback when the window closes (important to avoid a memory leak)
Zotero.getMainWindow().addEventListener(
"unload",
function (e) {
function (e: Event) {
Zotero.Notifier.unregisterObserver(notifierID);
},
false
Expand All @@ -57,7 +57,7 @@ class AddonEvents extends AddonModule {
}

public initPrefs() {
this._Addon.Utils.Tool.log(this._Addon.rootURI);
this._Addon.toolkit.Tool.log(this._Addon.rootURI);
const prefOptions = {
pluginID: addonID,
src: this._Addon.rootURI + "chrome/content/preferences.xhtml",
Expand All @@ -69,22 +69,22 @@ class AddonEvents extends AddonModule {
this._Addon.prefs.initPreferences(win);
},
};
if (this._Addon.Utils.Compat.isZotero7()) {
if (this._Addon.toolkit.Compat.isZotero7()) {
Zotero.PreferencePanes.register(prefOptions);
} else {
this._Addon.Utils.Compat.registerPrefPane(prefOptions);
this._Addon.toolkit.Compat.registerPrefPane(prefOptions);
}
}

private unInitPrefs() {
if (!this._Addon.Utils.Compat.isZotero7()) {
this._Addon.Utils.Compat.unregisterPrefPane();
if (!this._Addon.toolkit.Compat.isZotero7()) {
this._Addon.toolkit.Compat.unregisterPrefPane();
}
}

public onUnInit(): void {
const Zotero = this._Addon.Zotero;
this._Addon.Utils.Tool.log(`${addonName}: uninit called`);
this._Addon.toolkit.Tool.log(`${addonName}: uninit called`);
this.unInitPrefs();
// Remove elements and do clean up
this._Addon.views.unInitViews();
Expand Down
10 changes: 5 additions & 5 deletions src/prefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import AddonModule from "./module";
import { addonName, addonRef } from "../package.json";

class AddonPrefs extends AddonModule {
private _window: Window;
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;
this._Addon.Utils.Tool.log(`${addonName}: init preferences`);
this._Addon.toolkit.Tool.log(`${addonName}: init preferences`);
this.updatePrefsUI();
this.bindPrefEvents();
}
Expand All @@ -20,14 +20,14 @@ class AddonPrefs extends AddonModule {
// You can initialize some UI elements on prefs window
// with this._window.document
// Or bind some events to the elements
this._Addon.Utils.Tool.log(`${addonName}: init preferences UI`);
this._Addon.toolkit.Tool.log(`${addonName}: init preferences UI`);
}

private bindPrefEvents() {
this._window.document
.querySelector(`#zotero-prefpane-${addonRef}-enable`)
?.addEventListener("command", (e) => {
this._Addon.Utils.Tool.log(e);
this._Addon.toolkit.Tool.log(e);
this._window.alert(
`Successfully changed to ${(e.target as XUL.Checkbox).checked}!`
);
Expand All @@ -36,7 +36,7 @@ class AddonPrefs extends AddonModule {
this._window.document
.querySelector(`#zotero-prefpane-${addonRef}-input`)
?.addEventListener("change", (e) => {
this._Addon.Utils.Tool.log(e);
this._Addon.toolkit.Tool.log(e);
this._window.alert(
`Successfully changed to ${(e.target as HTMLInputElement).value}!`
);
Expand Down
Loading

0 comments on commit 969a527

Please sign in to comment.