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

129
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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict"; if (typeof Zotero == "undefined") {
/* global Components, Services */ var Zotero;
/* global addon, APP_SHUTDOWN */ }
const { classes: Cc, utils: Cu } = Components;
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
function install(data, reason) {} // Zotero window to open and get the Zotero object from there.
//
function startup(data, reason) { // In Zotero 7, bootstrap methods are not called until Zotero is initialized, and the 'Zotero' is
// Load the addon to Zotero if window is ready // automatically made available.
const loadAddon = (window) => { async function waitForZotero() {
console.log(window); if (typeof Zotero != "undefined") {
if (window.document.readyState === "complete" && window.Zotero) { await Zotero.initializationPromise;
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"
);
} }
});
}
};
// Listen to windows var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var WindowListener = { var windows = Services.wm.getEnumerator("navigator:browser");
onOpenWindow: function (xulWindow) { var found = false;
loadAddon( while (windows.hasMoreElements()) {
xulWindow let win = windows.getNext();
.QueryInterface(Components.interfaces.nsIInterfaceRequestor) if (win.Zotero) {
.getInterface(Components.interfaces.nsIDOMWindow) 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(WindowListener); Services.wm.addListener(listener);
});
}
await Zotero.initializationPromise;
}
// Scan current windows function install(data, reason) {}
const windows = Services.wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements()) { async function startup({ id, version, resourceURI, rootURI }, reason) {
loadAddon( await waitForZotero();
windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow)
// String 'rootURI' introduced in Zotero 7
if (!rootURI) {
rootURI = resourceURI.spec;
}
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) { if (reason === APP_SHUTDOWN) {
return; return;
} }
var _Zotero = Components.classes["@zotero.org/Zotero;1"].getService( if (typeof Zotero === "undefined") {
Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
Components.interfaces.nsISupports Components.interfaces.nsISupports
).wrappedJSObject; ).wrappedJSObject;
_Zotero.AddonTemplate.events.onUnInit(_Zotero); }
Zotero.AddonTemplate.events.onUnInit(Zotero);
Cc["@mozilla.org/intl/stringbundle;1"] Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService) .getService(Components.interfaces.nsIStringBundleService)
.flushBundles(); .flushBundles();
Cu.unload("chrome://_addonRef__/scripts/index.js"); Cu.unload(`${rootURI}/chrome/content/scripts/index.js`);
chromeHandle.destruct();
chromeHandle = null;
} }
function uninstall(data, reason) {} 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); copyFolderRecursiveSync("addon", buildDir);
copyFileSync("update-template.json", "update.json");
await esbuild await esbuild
.build({ .build({
entryPoints: ["src/index.ts"], entryPoints: ["src/index.ts"],
@ -113,10 +115,12 @@ async function main() {
path.join(buildDir, "**/*.rdf"), path.join(buildDir, "**/*.rdf"),
path.join(buildDir, "**/*.dtd"), path.join(buildDir, "**/*.dtd"),
path.join(buildDir, "**/*.xul"), 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/defaults", "**/*.js"),
path.join(buildDir, "addon/chrome.manifest"),
path.join(buildDir, "addon/bootstrap.js"), path.join(buildDir, "addon/bootstrap.js"),
"update.rdf", "update.json",
], ],
from: [ from: [
/__author__/g, /__author__/g,
@ -129,7 +133,6 @@ async function main() {
/__addonRef__/g, /__addonRef__/g,
/__buildVersion__/g, /__buildVersion__/g,
/__buildTime__/g, /__buildTime__/g,
/<em:version>\S*<\/em:version>/g,
], ],
to: [ to: [
author, author,
@ -142,7 +145,6 @@ async function main() {
addonRef, addonRef,
version, version,
buildTime, buildTime,
`<em:version>${version}</em:version>`,
], ],
countMatches: true, countMatches: true,
}; };

View File

@ -33,7 +33,8 @@
"replace-in-file": "^6.3.2" "replace-in-file": "^6.3.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.7.20",
"release-it": "^14.14.0", "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 events: AddonEvents;
public views: AddonViews; public views: AddonViews;
public prefs: AddonPrefs; public prefs: AddonPrefs;
// root path to access the resources
public rootURI: string;
constructor() { constructor() {
this.events = new AddonEvents(this); 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"; import AddonModule from "./module";
class AddonEvents extends AddonModule { 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 // 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); // alert(112233);
// Reset prefs // Reset prefs
this.resetState(); this.resetState();
// Register the callback in Zotero as an item observer // Register the callback in Zotero as an item observer
let notifierID = _Zotero.Notifier.registerObserver(this.notifierCallback, [ let notifierID = Zotero.Notifier.registerObserver(this.notifierCallback, [
"tab", "tab",
"item", "item",
"file", "file",
]); ]);
// Unregister callback when the window closes (important to avoid a memory leak) // Unregister callback when the window closes (important to avoid a memory leak)
_Zotero.getMainWindow().addEventListener( Zotero.getMainWindow().addEventListener(
"unload", "unload",
function (e) { function (e) {
_Zotero.Notifier.unregisterObserver(notifierID); Zotero.Notifier.unregisterObserver(notifierID);
}, },
false false
); );
this._Addon.views.initViews(_Zotero); this._Addon.views.initViews();
this._Addon.views.initPrefs();
} }
private resetState(): void { private resetState(): void {
@ -68,13 +69,13 @@ class AddonEvents extends AddonModule {
// } // }
} }
public onUnInit(_Zotero): void { public onUnInit(): void {
console.log(`${addonName}: uninit called`); const Zotero = getZotero();
_Zotero.debug(`${addonName}: uninit called`); Zotero.debug(`${addonName}: uninit called`);
// Remove elements and do clean up // Remove elements and do clean up
this._Addon.views.unInitViews(_Zotero); this._Addon.views.unInitViews();
// Remove addon object // 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( const Zotero = getZotero();
Components.interfaces.nsISupports
).wrappedJSObject; if (!Zotero.AddonTemplate) {
if (!_Zotero.AddonTemplate) { Zotero.AddonTemplate = new Addon();
_Zotero.AddonTemplate = new Addon(); Zotero.AddonTemplate.events.onInit();
_Zotero.AddonTemplate.events.onInit(_Zotero);
} }

View File

@ -1,6 +1,6 @@
import { Addon } from "./addon"; import { Addon, getZotero, createXULElement } from "./addon";
import AddonModule from "./module"; import AddonModule from "./module";
const { addonRef } = require("../package.json"); const { addonRef, addonID } = require("../package.json");
class AddonViews extends AddonModule { class AddonViews extends AddonModule {
// You can store some element in the object attributes // 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 // You can init the UI elements that
// cannot be initialized with overlay.xul // cannot be initialized with overlay.xul
console.log("Initializing UI"); Zotero.debug("Initializing UI");
const _window: Window = _Zotero.getMainWindow(); const _window: Window = Zotero.getMainWindow();
const menuitem = _window.document.createElement("menuitem"); const menuitem = createXULElement(_window.document, "menuitem");
menuitem.id = "zotero-itemmenu-addontemplate-test"; menuitem.id = "zotero-itemmenu-addontemplate-test";
menuitem.setAttribute("label", "Addon Template"); menuitem.setAttribute("label", "Addon Template");
menuitem.setAttribute("oncommand", "alert('Hello World!')"); menuitem.setAttribute("oncommand", "alert('Hello World!')");
menuitem.className = "menuitem-iconic"; // menuitem.className = "menuitem-iconic";
menuitem.style["list-style-image"] = // menuitem.style["list-style-image"] =
"url('chrome://addontemplate/skin/favicon@0.5x.png')"; // "url('chrome/skin/default/addontemplate/favicon@0.5x.png')";
_window.document.querySelector("#zotero-itemmenu").appendChild(menuitem); _window.document.querySelector("#zotero-itemmenu").appendChild(menuitem);
} }
public unInitViews(_Zotero) { public initPrefs() {
console.log("Uninitializing UI"); const Zotero = getZotero();
const _window: Window = _Zotero.getMainWindow(); 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 _window.document
.querySelector("#zotero-itemmenu-addontemplate-test") .querySelector("#zotero-itemmenu-addontemplate-test")
?.remove(); ?.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"
}
}
}
]
}
}
}