update: bootstrap for zotero 7

This commit is contained in:
xiangyu 2022-11-28 15:28:11 +08:00
parent 51bbc58590
commit 56ac4d5320
12 changed files with 254 additions and 99 deletions

137
addon/bootstrap.js vendored
View File

@ -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) {}

View File

@ -0,0 +1,13 @@
<xul:vbox
id="zotero-prefpane-__addonRef__"
onload="Zotero.AddonTemplate.prefs.initPreferences(window)"
>
<xul:groupbox>
<label><h2>Addon Template Example</h2></label>
<xul:checkbox
id="zotero-prefpane-__addonRef__-enable"
preference="pref-__addonRef__-enable"
label="&zotero.__addonRef__.pref.enable.label;"
/>
</xul:groupbox>
</xul:vbox>

View File

@ -1,15 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE prefwindow SYSTEM "chrome://__addonRef__/locale/overlay.dtd">
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://zotero-platform/content/preferences.css"?>
<prefwindow id="__addonRef__-prefs" title="&zotero.__addonRef__.pref.title;" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script src="chrome://zotero/content/include.js" />
<prefpane id=" zotero-prefpane-__addonRef__" insertafter=" zotero-prefpane-advanced" label=" __addonName__" image=" chrome: __addonRef__ skin favicon.png" onpaneload=" Zotero.AddonTemplate.prefs.initPreferences(window)">
<preferences id=" zotero-preferences-__addonRef__">
<preference id=" pref-__addonRef__-enable" name=" extensions.zotero.__addonRef__.enable" type=" bool" />
</preferences>
<checkbox id=" zotero-prefpane-__addonRef__-enable" preference=" pref-__addonRef__-enable" label=" &zotero.__addonRef__.pref.enable.label;" />
</prefpane>
</prefwindow>

19
addon/manifest.json Normal file
View File

@ -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.*"
}
}
}

View File

@ -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,
/<em:version>\S*<\/em:version>/g,
],
to: [
author,
@ -142,7 +145,6 @@ async function main() {
addonRef,
version,
buildTime,
`<em:version>${version}</em:version>`,
],
countMatches: true,
};

View File

@ -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"
}
}

View File

@ -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 };

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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();

26
update-template.json Normal file
View File

@ -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"
}
}
}
]
}
}
}

26
update.json Normal file
View File

@ -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"
}
}
}
]
}
}
}