From 56ac4d532049756c5f32bc2e465dc7146dab9ab5 Mon Sep 17 00:00:00 2001
From: xiangyu <3170102889@zju.edu.cn>
Date: Mon, 28 Nov 2022 15:28:11 +0800
Subject: [PATCH] update: bootstrap for zotero 7
---
addon/bootstrap.js | 137 ++++++++++++++++---------
addon/chrome/content/preferences.xhtml | 13 +++
addon/chrome/content/preferences.xul | 15 ---
addon/manifest.json | 19 ++++
build.js | 10 +-
package.json | 3 +-
src/addon.ts | 29 +++++-
src/events.ts | 27 ++---
src/index.ts | 13 ++-
src/views.ts | 35 ++++---
update-template.json | 26 +++++
update.json | 26 +++++
12 files changed, 254 insertions(+), 99 deletions(-)
create mode 100644 addon/chrome/content/preferences.xhtml
delete mode 100644 addon/chrome/content/preferences.xul
create mode 100644 addon/manifest.json
create mode 100644 update-template.json
create mode 100644 update.json
diff --git a/addon/bootstrap.js b/addon/bootstrap.js
index a22fe5b..b4a8752 100644
--- a/addon/bootstrap.js
+++ b/addon/bootstrap.js
@@ -3,69 +3,114 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-/* global Components, Services */
-/* global addon, APP_SHUTDOWN */
-const { classes: Cc, utils: Cu } = Components;
+if (typeof Zotero == "undefined") {
+ var Zotero;
+}
-Cu.import("resource://gre/modules/Services.jsm");
+// In Zotero 6, bootstrap methods are called before Zotero is initialized, and using include.js
+// to get the Zotero XPCOM service would risk breaking Zotero startup. Instead, wait for the main
+// Zotero window to open and get the Zotero object from there.
+//
+// In Zotero 7, bootstrap methods are not called until Zotero is initialized, and the 'Zotero' is
+// automatically made available.
+async function waitForZotero() {
+ if (typeof Zotero != "undefined") {
+ await Zotero.initializationPromise;
+ }
+
+ var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+ var windows = Services.wm.getEnumerator("navigator:browser");
+ var found = false;
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ if (win.Zotero) {
+ Zotero = win.Zotero;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ await new Promise((resolve) => {
+ var listener = {
+ onOpenWindow: function (aWindow) {
+ // Wait for the window to finish loading
+ let domWindow = aWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
+ domWindow.addEventListener(
+ "load",
+ function () {
+ domWindow.removeEventListener("load", arguments.callee, false);
+ if (domWindow.Zotero) {
+ Services.wm.removeListener(listener);
+ Zotero = domWindow.Zotero;
+ resolve();
+ }
+ },
+ false
+ );
+ },
+ };
+ Services.wm.addListener(listener);
+ });
+ }
+ await Zotero.initializationPromise;
+}
function install(data, reason) {}
-function startup(data, reason) {
- // Load the addon to Zotero if window is ready
- const loadAddon = (window) => {
- console.log(window);
- if (window.document.readyState === "complete" && window.Zotero) {
- Services.scriptloader.loadSubScript(
- "chrome://__addonRef__/content/scripts/index.js"
- );
- } else {
- window.addEventListener("load", (e) => {
- if (window.Zotero) {
- Services.scriptloader.loadSubScript(
- "chrome://__addonRef__/content/scripts/index.js"
- );
- }
- });
- }
- };
+async function startup({ id, version, resourceURI, rootURI }, reason) {
+ await waitForZotero();
- // Listen to windows
- var WindowListener = {
- onOpenWindow: function (xulWindow) {
- loadAddon(
- xulWindow
- .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
- .getInterface(Components.interfaces.nsIDOMWindow)
- );
- },
- };
- Services.wm.addListener(WindowListener);
+ // String 'rootURI' introduced in Zotero 7
+ if (!rootURI) {
+ rootURI = resourceURI.spec;
+ }
- // Scan current windows
- const windows = Services.wm.getEnumerator("navigator:browser");
- while (windows.hasMoreElements()) {
- loadAddon(
- windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow)
- );
+ Services.scriptloader.loadSubScript(
+ `${rootURI}/chrome/content/scripts/index.js`
+ );
+
+ Zotero.AddonTemplate.rootURI = rootURI;
+
+ if (Zotero.platformMajorVersion >= 102) {
+ var aomStartup = Components.Cc[
+ "@mozilla.org/addons/addon-manager-startup;1"
+ ].getService(Components.Ci.amIAddonManagerStartup);
+ var manifestURI = Services.io.newURI(rootURI + "manifest.json");
+ var chromeHandle = aomStartup.registerChrome(manifestURI, [
+ ["content", "__addonRef__", "chrome/content/"],
+ ["locale", "__addonRef__", "en-US", "chrome/locale/en-US/"],
+ ["locale", "__addonRef__", "zh-CN", "chrome/locale/zh-CN/"],
+ ]);
+
+ Zotero.PreferencePanes.register({
+ pluginID: "__addonID__",
+ src: `${rootURI}/chrome/content/preferences.xhtml`,
+ extraDTD: [`chrome://__addonRef__/locale/overlay.dtd`],
+ });
}
}
-function shutdown(data, reason) {
+function shutdown({ id, version, resourceURI, rootURI }, reason) {
if (reason === APP_SHUTDOWN) {
return;
}
- var _Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
- Components.interfaces.nsISupports
- ).wrappedJSObject;
- _Zotero.AddonTemplate.events.onUnInit(_Zotero);
+ if (typeof Zotero === "undefined") {
+ Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
+ Components.interfaces.nsISupports
+ ).wrappedJSObject;
+ }
+ Zotero.AddonTemplate.events.onUnInit(Zotero);
Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService)
.flushBundles();
- Cu.unload("chrome://_addonRef__/scripts/index.js");
+ Cu.unload(`${rootURI}/chrome/content/scripts/index.js`);
+
+ chromeHandle.destruct();
+ chromeHandle = null;
}
function uninstall(data, reason) {}
diff --git a/addon/chrome/content/preferences.xhtml b/addon/chrome/content/preferences.xhtml
new file mode 100644
index 0000000..a526bd6
--- /dev/null
+++ b/addon/chrome/content/preferences.xhtml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/addon/chrome/content/preferences.xul b/addon/chrome/content/preferences.xul
deleted file mode 100644
index 7f75ee3..0000000
--- a/addon/chrome/content/preferences.xul
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/addon/manifest.json b/addon/manifest.json
new file mode 100644
index 0000000..c7bc69b
--- /dev/null
+++ b/addon/manifest.json
@@ -0,0 +1,19 @@
+{
+ "manifest_version": 2,
+ "name": "__addonName__",
+ "version": "__buildVersion__",
+ "description": "__description__",
+ "author": "__author__",
+ "icons": {
+ "48": "chrome/skin/default/__addonRef__/favicon@0.5x.png",
+ "96": "chrome/skin/default/__addonRef__/favicon.png"
+ },
+ "applications": {
+ "zotero": {
+ "id": "__addonID__",
+ "update_url": "__updaterdf__",
+ "strict_min_version": "6.999",
+ "strict_max_version": "7.0.*"
+ }
+ }
+}
diff --git a/build.js b/build.js
index 71578ae..35b35f8 100644
--- a/build.js
+++ b/build.js
@@ -96,6 +96,8 @@ async function main() {
copyFolderRecursiveSync("addon", buildDir);
+ copyFileSync("update-template.json", "update.json");
+
await esbuild
.build({
entryPoints: ["src/index.ts"],
@@ -113,10 +115,12 @@ async function main() {
path.join(buildDir, "**/*.rdf"),
path.join(buildDir, "**/*.dtd"),
path.join(buildDir, "**/*.xul"),
- path.join(buildDir, "**/*.manifest"),
+ path.join(buildDir, "**/*.xhtml"),
+ path.join(buildDir, "**/*.json"),
path.join(buildDir, "addon/defaults", "**/*.js"),
+ path.join(buildDir, "addon/chrome.manifest"),
path.join(buildDir, "addon/bootstrap.js"),
- "update.rdf",
+ "update.json",
],
from: [
/__author__/g,
@@ -129,7 +133,6 @@ async function main() {
/__addonRef__/g,
/__buildVersion__/g,
/__buildTime__/g,
- /\S*<\/em:version>/g,
],
to: [
author,
@@ -142,7 +145,6 @@ async function main() {
addonRef,
version,
buildTime,
- `${version}`,
],
countMatches: true,
};
diff --git a/package.json b/package.json
index 1097aaf..d1c583a 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,8 @@
"replace-in-file": "^6.3.2"
},
"devDependencies": {
+ "@types/node": "^18.7.20",
"release-it": "^14.14.0",
- "zotero-types": "^0.0.4"
+ "zotero-types": "^0.0.7"
}
}
diff --git a/src/addon.ts b/src/addon.ts
index 3945f91..e0d2628 100644
--- a/src/addon.ts
+++ b/src/addon.ts
@@ -8,6 +8,8 @@ class Addon {
public events: AddonEvents;
public views: AddonViews;
public prefs: AddonPrefs;
+ // root path to access the resources
+ public rootURI: string;
constructor() {
this.events = new AddonEvents(this);
@@ -16,4 +18,29 @@ class Addon {
}
}
-export { addonName, Addon };
+function getZotero(): _ZoteroConstructable {
+ if (typeof Zotero === "undefined") {
+ return Components.classes["@zotero.org/Zotero;1"].getService(
+ Components.interfaces.nsISupports
+ ).wrappedJSObject;
+ }
+ return Zotero;
+}
+
+function isZotero7(): boolean {
+ return Zotero.platformMajorVersion >= 102;
+}
+
+function createXULElement(doc: Document, type: string): XUL.Element {
+ if (isZotero7()) {
+ // @ts-ignore
+ return doc.createXULElement(type);
+ } else {
+ return doc.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ type
+ ) as XUL.Element;
+ }
+}
+
+export { addonName, Addon, getZotero, isZotero7, createXULElement };
diff --git a/src/events.ts b/src/events.ts
index 322b174..8a05870 100644
--- a/src/events.ts
+++ b/src/events.ts
@@ -1,4 +1,4 @@
-import { Addon, addonName } from "./addon";
+import { Addon, addonName, getZotero } from "./addon";
import AddonModule from "./module";
class AddonEvents extends AddonModule {
@@ -27,32 +27,33 @@ class AddonEvents extends AddonModule {
};
}
- public async onInit(_Zotero) {
+ public async onInit() {
+ const Zotero = getZotero();
// This function is the setup code of the addon
- console.log(`${addonName}: init called`);
- _Zotero.debug(`${addonName}: init called`);
+ Zotero.debug(`${addonName}: init called`);
// alert(112233);
// Reset prefs
this.resetState();
// Register the callback in Zotero as an item observer
- let notifierID = _Zotero.Notifier.registerObserver(this.notifierCallback, [
+ let notifierID = Zotero.Notifier.registerObserver(this.notifierCallback, [
"tab",
"item",
"file",
]);
// Unregister callback when the window closes (important to avoid a memory leak)
- _Zotero.getMainWindow().addEventListener(
+ Zotero.getMainWindow().addEventListener(
"unload",
function (e) {
- _Zotero.Notifier.unregisterObserver(notifierID);
+ Zotero.Notifier.unregisterObserver(notifierID);
},
false
);
- this._Addon.views.initViews(_Zotero);
+ this._Addon.views.initViews();
+ this._Addon.views.initPrefs();
}
private resetState(): void {
@@ -68,13 +69,13 @@ class AddonEvents extends AddonModule {
// }
}
- public onUnInit(_Zotero): void {
- console.log(`${addonName}: uninit called`);
- _Zotero.debug(`${addonName}: uninit called`);
+ public onUnInit(): void {
+ const Zotero = getZotero();
+ Zotero.debug(`${addonName}: uninit called`);
// Remove elements and do clean up
- this._Addon.views.unInitViews(_Zotero);
+ this._Addon.views.unInitViews();
// Remove addon object
- _Zotero.AddonTemplate = undefined;
+ Zotero.AddonTemplate = undefined;
}
}
diff --git a/src/index.ts b/src/index.ts
index 87f6363..1dd8c80 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,9 +1,8 @@
-import { Addon } from "./addon";
+import { Addon, getZotero } from "./addon";
-var _Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
- Components.interfaces.nsISupports
-).wrappedJSObject;
-if (!_Zotero.AddonTemplate) {
- _Zotero.AddonTemplate = new Addon();
- _Zotero.AddonTemplate.events.onInit(_Zotero);
+const Zotero = getZotero();
+
+if (!Zotero.AddonTemplate) {
+ Zotero.AddonTemplate = new Addon();
+ Zotero.AddonTemplate.events.onInit();
}
diff --git a/src/views.ts b/src/views.ts
index df36171..7672b16 100644
--- a/src/views.ts
+++ b/src/views.ts
@@ -1,6 +1,6 @@
-import { Addon } from "./addon";
+import { Addon, getZotero, createXULElement } from "./addon";
import AddonModule from "./module";
-const { addonRef } = require("../package.json");
+const { addonRef, addonID } = require("../package.json");
class AddonViews extends AddonModule {
// You can store some element in the object attributes
@@ -16,24 +16,35 @@ class AddonViews extends AddonModule {
};
}
- public initViews(_Zotero) {
+ public initViews() {
+ const Zotero = getZotero();
// You can init the UI elements that
// cannot be initialized with overlay.xul
- console.log("Initializing UI");
- const _window: Window = _Zotero.getMainWindow();
- const menuitem = _window.document.createElement("menuitem");
+ Zotero.debug("Initializing UI");
+ const _window: Window = Zotero.getMainWindow();
+ const menuitem = createXULElement(_window.document, "menuitem");
menuitem.id = "zotero-itemmenu-addontemplate-test";
menuitem.setAttribute("label", "Addon Template");
menuitem.setAttribute("oncommand", "alert('Hello World!')");
- menuitem.className = "menuitem-iconic";
- menuitem.style["list-style-image"] =
- "url('chrome://addontemplate/skin/favicon@0.5x.png')";
+ // menuitem.className = "menuitem-iconic";
+ // menuitem.style["list-style-image"] =
+ // "url('chrome/skin/default/addontemplate/favicon@0.5x.png')";
_window.document.querySelector("#zotero-itemmenu").appendChild(menuitem);
}
- public unInitViews(_Zotero) {
- console.log("Uninitializing UI");
- const _window: Window = _Zotero.getMainWindow();
+ public initPrefs() {
+ const Zotero = getZotero();
+ Zotero.PreferencePanes.register({
+ pluginID: addonID,
+ src: `${this._Addon.rootURI}/chrome/content/preferences.xhtml`,
+ extraDTD: [`chrome://${addonRef}/locale/overlay.dtd`],
+ });
+ }
+
+ public unInitViews() {
+ const Zotero = getZotero();
+ Zotero.debug("Uninitializing UI");
+ const _window: Window = Zotero.getMainWindow();
_window.document
.querySelector("#zotero-itemmenu-addontemplate-test")
?.remove();
diff --git a/update-template.json b/update-template.json
new file mode 100644
index 0000000..7dca98e
--- /dev/null
+++ b/update-template.json
@@ -0,0 +1,26 @@
+{
+ "addons": {
+ "__addonID__": {
+ "updates": [
+ {
+ "version": "__buildVersion__",
+ "update_link": "__releasepage__",
+ "applications": {
+ "gecko": {
+ "strict_min_version": "60.0"
+ }
+ }
+ },
+ {
+ "version": "__buildVersion__",
+ "update_link": "__releasepage__",
+ "applications": {
+ "zotero": {
+ "strict_min_version": "6.999"
+ }
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/update.json b/update.json
new file mode 100644
index 0000000..8613ac6
--- /dev/null
+++ b/update.json
@@ -0,0 +1,26 @@
+{
+ "addons": {
+ "addontemplate@euclpts.com": {
+ "updates": [
+ {
+ "version": "0.0.0",
+ "update_link": "https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi",
+ "applications": {
+ "gecko": {
+ "strict_min_version": "60.0"
+ }
+ }
+ },
+ {
+ "version": "0.0.0",
+ "update_link": "https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi",
+ "applications": {
+ "zotero": {
+ "strict_min_version": "6.999"
+ }
+ }
+ }
+ ]
+ }
+ }
+}