From 1ab8af4ad24257903b53019e24a2f0439d8facdf Mon Sep 17 00:00:00 2001
From: xiangyu <3170102889@zju.edu.cn>
Date: Fri, 16 Dec 2022 18:11:47 +0800
Subject: [PATCH] add: preference compatibility for Zotero 6 & 7
---
addon/bootstrap.js | 21 +--
addon/chrome.manifest | 1 -
.../icons}/favicon.png | Bin
.../icons}/favicon@0.5x.png | Bin
addon/chrome/content/preferences.xhtml | 10 +-
addon/chrome/locale/en-US/overlay.dtd | 1 +
addon/chrome/locale/zh-CN/overlay.dtd | 1 +
addon/install.rdf | 2 +-
addon/manifest.json | 4 +-
src/events.ts | 7 +-
src/index.ts | 7 +-
src/prefs.ts | 27 +++-
src/utils.ts | 126 +++++++++++++++++-
src/views.ts | 28 +++-
typing/global.d.ts | 22 +++
15 files changed, 220 insertions(+), 37 deletions(-)
rename addon/chrome/{skin/default/addontemplate => content/icons}/favicon.png (100%)
rename addon/chrome/{skin/default/addontemplate => content/icons}/favicon@0.5x.png (100%)
diff --git a/addon/bootstrap.js b/addon/bootstrap.js
index 39826cf..d3c07b0 100644
--- a/addon/bootstrap.js
+++ b/addon/bootstrap.js
@@ -69,11 +69,12 @@ async function startup({ id, version, resourceURI, rootURI }, reason) {
rootURI = resourceURI.spec;
}
- Services.scriptloader.loadSubScript(
- `${rootURI}/chrome/content/scripts/index.js`
- );
+ const ctx = { Zotero, rootURI };
- Zotero.AddonTemplate.rootURI = rootURI;
+ Services.scriptloader.loadSubScript(
+ `${rootURI}/chrome/content/scripts/index.js`,
+ ctx
+ );
if (Zotero.platformMajorVersion >= 102) {
var aomStartup = Components.classes[
@@ -86,12 +87,12 @@ async function startup({ id, version, resourceURI, rootURI }, reason) {
["locale", "__addonRef__", "zh-CN", rootURI + "chrome/locale/zh-CN/"],
]);
- Zotero.PreferencePanes.register({
- pluginID: "__addonID__",
- src: rootURI + "chrome/content/preferences.xhtml",
- extraDTD: [`chrome://__addonRef__/locale/overlay.dtd`],
- defaultXUL: true,
- });
+ // Zotero.PreferencePanes.register({
+ // pluginID: "__addonID__",
+ // src: rootURI + "chrome/content/preferences.xhtml",
+ // extraDTD: ["chrome://__addonRef__/locale/overlay.dtd"],
+ // defaultXUL: true,
+ // });
}
}
diff --git a/addon/chrome.manifest b/addon/chrome.manifest
index 6c33bcd..713e1b0 100644
--- a/addon/chrome.manifest
+++ b/addon/chrome.manifest
@@ -1,4 +1,3 @@
content __addonRef__ chrome/content/
-skin __addonRef__ default chrome/skin/default/__addonRef__/
locale __addonRef__ en-US chrome/locale/en-US/
locale __addonRef__ zh-CN chrome/locale/zh-CN/
diff --git a/addon/chrome/skin/default/addontemplate/favicon.png b/addon/chrome/content/icons/favicon.png
similarity index 100%
rename from addon/chrome/skin/default/addontemplate/favicon.png
rename to addon/chrome/content/icons/favicon.png
diff --git a/addon/chrome/skin/default/addontemplate/favicon@0.5x.png b/addon/chrome/content/icons/favicon@0.5x.png
similarity index 100%
rename from addon/chrome/skin/default/addontemplate/favicon@0.5x.png
rename to addon/chrome/content/icons/favicon@0.5x.png
diff --git a/addon/chrome/content/preferences.xhtml b/addon/chrome/content/preferences.xhtml
index 60607f3..ad73f90 100644
--- a/addon/chrome/content/preferences.xhtml
+++ b/addon/chrome/content/preferences.xhtml
@@ -6,8 +6,16 @@
+
+
+ &zotero.__addonRef__.pref.input.label;
+
diff --git a/addon/chrome/locale/en-US/overlay.dtd b/addon/chrome/locale/en-US/overlay.dtd
index 319c425..beffe6c 100644
--- a/addon/chrome/locale/en-US/overlay.dtd
+++ b/addon/chrome/locale/en-US/overlay.dtd
@@ -1,5 +1,6 @@
+
\ No newline at end of file
diff --git a/addon/chrome/locale/zh-CN/overlay.dtd b/addon/chrome/locale/zh-CN/overlay.dtd
index 8417667..8d0a76f 100644
--- a/addon/chrome/locale/zh-CN/overlay.dtd
+++ b/addon/chrome/locale/zh-CN/overlay.dtd
@@ -1,5 +1,6 @@
+
\ No newline at end of file
diff --git a/addon/install.rdf b/addon/install.rdf
index 15fd8bf..77666f5 100644
--- a/addon/install.rdf
+++ b/addon/install.rdf
@@ -12,7 +12,7 @@
em:creator="__author__"
em:description="__description__"
em:homepageURL="__homepage__"
- em:iconURL="chrome://__addonRef__/skin/favicon.png"
+ em:iconURL="chrome://__addonRef__/content/icons/favicon.png"
em:optionsURL="chrome://__addonRef__/content/preferences.xul"
em:updateURL="__updaterdf__"
em:multiprocessCompatible="true"
diff --git a/addon/manifest.json b/addon/manifest.json
index c7bc69b..029bf11 100644
--- a/addon/manifest.json
+++ b/addon/manifest.json
@@ -5,8 +5,8 @@
"description": "__description__",
"author": "__author__",
"icons": {
- "48": "chrome/skin/default/__addonRef__/favicon@0.5x.png",
- "96": "chrome/skin/default/__addonRef__/favicon.png"
+ "48": "chrome/content/icons/favicon@0.5x.png",
+ "96": "chrome/content/icons/favicon.png"
},
"applications": {
"zotero": {
diff --git a/src/events.ts b/src/events.ts
index 441ad37..52fd8eb 100644
--- a/src/events.ts
+++ b/src/events.ts
@@ -28,10 +28,11 @@ class AddonEvents extends AddonModule {
};
}
- public async onInit(_Zotero: _ZoteroConstructable) {
+ public async onInit(_Zotero: _ZoteroConstructable, rootURI) {
this._Addon.Zotero = _Zotero;
+ this._Addon.rootURI = rootURI;
// This function is the setup code of the addon
- Zotero.debug(`${addonName}: init called`);
+ this._Addon.Utils.Tool.log(`${addonName}: init called`);
// alert(112233);
// Reset prefs
@@ -72,7 +73,7 @@ class AddonEvents extends AddonModule {
public onUnInit(): void {
const Zotero = this._Addon.Zotero;
- Zotero.debug(`${addonName}: uninit called`);
+ this._Addon.Utils.Tool.log(`${addonName}: uninit called`);
// Remove elements and do clean up
this._Addon.views.unInitViews();
// Remove addon object
diff --git a/src/index.ts b/src/index.ts
index 845bb14..a58cf92 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,10 +1,7 @@
import Addon from "./addon";
-const Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
- Components.interfaces.nsISupports
-).wrappedJSObject;
-
if (!Zotero.AddonTemplate) {
Zotero.AddonTemplate = new Addon();
- Zotero.AddonTemplate.events.onInit(Zotero);
+ // @ts-ignore
+ Zotero.AddonTemplate.events.onInit(Zotero, rootURI);
}
diff --git a/src/prefs.ts b/src/prefs.ts
index 4f10cfc..82ad941 100644
--- a/src/prefs.ts
+++ b/src/prefs.ts
@@ -1,6 +1,6 @@
import Addon from "./addon";
import AddonModule from "./module";
-import { addonName } from "../package.json";
+import { addonName, addonRef } from "../package.json";
class AddonPrefs extends AddonModule {
private _window: Window;
@@ -11,15 +11,36 @@ class AddonPrefs extends AddonModule {
// This function is called when the prefs window is opened
// See addon/chrome/content/preferences.xul onpaneload
this._window = _window;
- Zotero.debug(`${addonName}: init preferences`);
+ this._Addon.Utils.Tool.log(`${addonName}: init preferences`);
this.updatePrefsUI();
+ this.bindPrefEvents();
}
private updatePrefsUI() {
// You can initialize some UI elements on prefs window
// with this._window.document
// Or bind some events to the elements
- Zotero.debug(`${addonName}: init preferences UI`);
+ this._Addon.Utils.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._window.alert(
+ `Successfully changed to ${(e.target as XUL.Checkbox).checked}!`
+ );
+ });
+
+ this._window.document
+ .querySelector(`#zotero-prefpane-${addonRef}-input`)
+ ?.addEventListener("change", (e) => {
+ this._Addon.Utils.Tool.log(e);
+ this._window.alert(
+ `Successfully changed to ${(e.target as HTMLInputElement).value}!`
+ );
+ });
}
}
diff --git a/src/utils.ts b/src/utils.ts
index 9de9d8a..e7304c0 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -21,12 +21,19 @@ class AddonUtils extends AddonModule {
// Check if it's running on Zotero 7 (Firefox 102)
isZotero7: () => Zotero.platformMajorVersion >= 102,
// Firefox 102 support DOMParser natively
- getDOMParser: () =>
- this.Compat.isZotero7()
- ? new DOMParser()
- : Components.classes[
- "@mozilla.org/xmlextras/domparser;1"
- ].createInstance(Components.interfaces.nsIDOMParser),
+ getDOMParser: () => {
+ if (this.Compat.isZotero7()) {
+ return new DOMParser();
+ }
+ try {
+ return new (this.Compat.getZotero().getMainWindow().DOMParser)();
+ } catch (e) {
+ return Components.classes[
+ "@mozilla.org/xmlextras/domparser;1"
+ ].createInstance(Components.interfaces.nsIDOMParser);
+ }
+ },
+
// create XUL element
createXULElement: (doc: Document, type: string) => {
if (this.Compat.isZotero7()) {
@@ -39,6 +46,111 @@ class AddonUtils extends AddonModule {
) as XUL.Element;
}
},
+ parseXHTMLToFragment: (
+ str: string,
+ entities: string[] = [],
+ defaultXUL = true
+ ) => {
+ // Adapted from MozXULElement.parseXULToFragment
+
+ /* eslint-disable indent */
+ let parser = this.Compat.getDOMParser();
+ // parser.forceEnableXULXBL();
+ const xulns =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ const htmlns = "http://www.w3.org/1999/xhtml";
+ const wrappedStr = `${
+ entities.length
+ ? ` {
+ return (
+ preamble +
+ ` %_dtd-${index}; `
+ );
+ },
+ ""
+ )}]>`
+ : ""
+ }
+
+ ${str}
+ `;
+ this.Tool.log(wrappedStr, parser);
+ let doc = parser.parseFromString(wrappedStr, "text/xml");
+ /* eslint-enable indent */
+ console.log(doc);
+
+ if (doc.documentElement.localName === "parsererror") {
+ throw new Error("not well-formed XHTML");
+ }
+
+ // We use a range here so that we don't access the inner DOM elements from
+ // JavaScript before they are imported and inserted into a document.
+ let range = doc.createRange();
+ range.selectNodeContents(doc.querySelector("div"));
+ return range.extractContents();
+ },
+ prefPaneCache: { win: undefined, listeners: [], ids: [] },
+ registerPrefPane: (options: PrefPaneOptions) => {
+ const windowListener = {
+ onOpenWindow: (xulWindow) => {
+ const win: Window = xulWindow
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindow);
+ win.addEventListener(
+ "load",
+ async () => {
+ if (
+ win.location.href ===
+ "chrome://zotero/content/preferences/preferences.xul"
+ ) {
+ this.Tool.log("registerPrefPane:detected", options);
+ const Zotero = this.Compat.getZotero();
+ options.id || (options.id = `plugin-${new Date().getTime()}`);
+ const src = `
+ ${(await Zotero.File.getContentsAsync(options.src)) as string}
+ `;
+ const frag = this.Compat.parseXHTMLToFragment(
+ src,
+ options.extraDTD,
+ options.defaultXUL
+ );
+ this.Tool.log(frag);
+ const prefWindow = win.document.querySelector("prefwindow");
+ prefWindow.appendChild(frag);
+ const prefPane = win.document.querySelector(`#${options.id}`);
+ // @ts-ignore
+ prefWindow.addPane(prefPane);
+ this.Compat.prefPaneCache.win = win;
+ this.Compat.prefPaneCache.listeners.push(windowListener);
+ this.Compat.prefPaneCache.ids.push(options.id);
+ if (options.onload) {
+ options.onload(win);
+ }
+ }
+ },
+ false
+ );
+ },
+ };
+ Services.wm.addListener(windowListener);
+ },
+ unregisterPrefPane: () => {
+ this.Compat.prefPaneCache.listeners.forEach((l) =>
+ Services.wm.removeListener(l)
+ );
+ const win = this.Compat.prefPaneCache.win;
+ if (win && !win.closed) {
+ this.Compat.prefPaneCache.ids.forEach((id) =>
+ win.document.querySelector(id)?.remove()
+ );
+ }
+ },
};
this.Tool = {
getCopyHelper: () => new CopyHelper(),
@@ -84,7 +196,7 @@ class AddonUtils extends AddonModule {
},
log: (...data: any[]) => {
try {
- this._Addon.Zotero.getMainWindow().console.log(data);
+ this._Addon.Zotero.getMainWindow().console.log(...data);
for (const d of data) {
this._Addon.Zotero.debug(d);
}
diff --git a/src/views.ts b/src/views.ts
index a1bc6bf..d8299d8 100644
--- a/src/views.ts
+++ b/src/views.ts
@@ -11,7 +11,7 @@ class AddonViews extends AddonModule {
this.progressWindowIcon = {
success: "chrome://zotero/skin/tick.png",
fail: "chrome://zotero/skin/cross.png",
- default: `chrome://${addonRef}/skin/favicon.png`,
+ default: `chrome://${addonRef}/content/icons/favicon.png`,
};
}
@@ -19,9 +19,9 @@ class AddonViews extends AddonModule {
const Zotero = this._Addon.Zotero;
// You can init the UI elements that
// cannot be initialized with overlay.xul
- Zotero.debug("Initializing UI");
+ this._Addon.Utils.Tool.log("Initializing UI");
const menuIcon =
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAAC9klEQVQ4jU1TzW/UZRB+Zub9fexXt7s0YG0RAlTAg6EqjfUkMYYDN2OiJngxXOBEBP0T9KY3LiQe1JgYP4BEw0laBSGgSSVIYhNLw5cuaaXdbnf39/G+73jo/hommcxhMk9m5nkeUlUU4RQkBJ25kwRHv358LmAcEUbKRNxJfRAb8kbIqip1Mw2mn4nOMJ4IVWUAuHE/e3m1748IAyt9bxZXbPDCWHiJGa37qzZcz9SUAsK1e+kJowBtAoAIAEJDVcNAKaCZYweHzjPRyORo8P1fS/apwGBqZiGpzN5JT9UiEkPFLAAa1FCos5Z4fdBG9cf5ZO7geGhOX+y+Hwq1XhwLPv234z8UhjgFTGLBACoFiir828+Xb/etTnVT3TpSZrPw2D7993L+7njdLN5q5Tfnl+3JekRwCpgPLvW/AXAYQD7YQgwTanHUowj2P4eSC+W3Stz7dXtdzjngQCSAV1gAht77obdJAxPQy9VZjywOUFKFEkBeCZNbcWjhUd9+Mde73CwzVKEAiIXghaABw/VyxfSYHD+8y7yqCpQMaSzkmYDMYWhXw7R3NsxnTMgGz1dWgBWAVUgtpKUddfllelwe1SOayz3YKWgo5uy7W739H8+uff7sSHCtHNAp9VAhwAzu9pmDbB+mC/90/Ru/t3R07xb58vpDNxkbqFOloZhzJuz4eTE5GzB1haGdVJmL2zOn2NOUq3dX/XPtxB/aVqE/AFUCGAoOGL3hEi+N1qS7pUwaCdLXJ+JPDAC1HjIc03okuLKS6EPrcX5nnf9slni+m+u+1cTLsanq4pv7GnvbqTaEYBVAJFhnAnzugW0VvpA5vLKW6sXU4dvL9+w7E03+yroNhkLZqNWQ1qohdWohdQwTmAjqVLGnwT/dXnavhUIsBLS6+tZIiW74jR9t6l0BKpIIMF5hYkO4+sCeth67zcBe7VRfmr1rPyobuJUEknv4Qu6F5AGAAVwhAJnHfgWiouEVgVUc8AqphHRzosnXC8aedPD/kPxathRorWcAAAAASUVORK5CYII=";
+ 'url("chrome://addontemplate/content/icons/favicon@0.5x.png")';
// item menuitem with icon
this._Addon.Utils.UI.insertMenuItem("item", {
tag: "menuitem",
@@ -62,12 +62,32 @@ class AddonViews extends AddonModule {
public initPrefs() {
const Zotero = this._Addon.Zotero;
+ this._Addon.Utils.Tool.log(this._Addon.rootURI);
+ const prefOptions = {
+ pluginID: addonID,
+ src: this._Addon.rootURI + "chrome/content/preferences.xhtml",
+ label: "Template",
+ image: `chrome://${addonRef}/content/icons/favicon.png`,
+ extraDTD: [`chrome://${addonRef}/locale/overlay.dtd`],
+ defaultXUL: true,
+ onload: (win: Window) => {
+ this._Addon.prefs.initPreferences(win);
+ },
+ };
+ if (this._Addon.Utils.Compat.isZotero7()) {
+ Zotero.PreferencePanes.register(prefOptions);
+ } else {
+ this._Addon.Utils.Compat.registerPrefPane(prefOptions);
+ }
}
public unInitViews() {
const Zotero = this._Addon.Zotero;
- Zotero.debug("Uninitializing UI");
+ this._Addon.Utils.Tool.log("Uninitializing UI");
this._Addon.Utils.UI.removeAddonElements();
+ if (!this._Addon.Utils.Compat.isZotero7()) {
+ this._Addon.Utils.Compat.unregisterPrefPane();
+ }
}
public showProgressWindow(
diff --git a/typing/global.d.ts b/typing/global.d.ts
index 415b7fe..75c2131 100644
--- a/typing/global.d.ts
+++ b/typing/global.d.ts
@@ -3,6 +3,14 @@ declare interface ZoteroCompat {
isZotero7: () => boolean;
getDOMParser: () => DOMParser;
createXULElement: (doc: Document, type: string) => XUL.Element;
+ parseXHTMLToFragment: (
+ str: string,
+ entities: string[],
+ defaultXUL?: boolean
+ ) => DocumentFragment;
+ prefPaneCache: { win: Window; listeners: any[]; ids: string[] };
+ registerPrefPane: (options: PrefPaneOptions) => void;
+ unregisterPrefPane: () => void;
}
declare interface ZoteroTool {
@@ -79,6 +87,20 @@ declare interface MenuitemOptions {
subElementOptions?: Array;
}
+declare interface PrefPaneOptions {
+ pluginID: string;
+ src: string;
+ id?: string;
+ parent?: string;
+ label?: string;
+ image?: string;
+ extraDTD?: string[];
+ scripts?: string[];
+ defaultXUL?: boolean;
+ // Only for Zotero 6
+ onload?: (win: Window) => any;
+}
+
declare class CopyHelper {
addText: (source: string, type: "text/html" | "text/unicode") => CopyHelper;
addImage: (source: string) => CopyHelper;