add: preference value bindings for Zotero 6

add: global variables
fix: bootstrap bugs
This commit is contained in:
xiangyu 2022-12-18 15:11:20 +08:00
parent 600bda7bd4
commit 7bcbd3e025
6 changed files with 212 additions and 61 deletions

19
addon/bootstrap.js vendored
View File

@ -69,7 +69,15 @@ async function startup({ id, version, resourceURI, rootURI }, reason) {
rootURI = resourceURI.spec; rootURI = resourceURI.spec;
} }
const ctx = { Zotero, rootURI }; const window = Zotero.getMainWindow();
// Global variables for plugin code
const ctx = {
Zotero,
rootURI,
window,
document: window.document,
ZoteroPane: Zotero.getActiveZoteroPane(),
};
Services.scriptloader.loadSubScript( Services.scriptloader.loadSubScript(
`${rootURI}/chrome/content/scripts/index.js`, `${rootURI}/chrome/content/scripts/index.js`,
@ -86,13 +94,6 @@ async function startup({ id, version, resourceURI, rootURI }, reason) {
["locale", "__addonRef__", "en-US", rootURI + "chrome/locale/en-US/"], ["locale", "__addonRef__", "en-US", rootURI + "chrome/locale/en-US/"],
["locale", "__addonRef__", "zh-CN", rootURI + "chrome/locale/zh-CN/"], ["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,
// });
} }
} }
@ -113,8 +114,10 @@ function shutdown({ id, version, resourceURI, rootURI }, reason) {
Cu.unload(`${rootURI}/chrome/content/scripts/index.js`); Cu.unload(`${rootURI}/chrome/content/scripts/index.js`);
if (chromeHandle) {
chromeHandle.destruct(); chromeHandle.destruct();
chromeHandle = null; chromeHandle = null;
} }
}
function uninstall(data, reason) {} function uninstall(data, reason) {}

View File

@ -1,6 +1,6 @@
import Addon from "./addon"; import Addon from "./addon";
import AddonModule from "./module"; import AddonModule from "./module";
import { addonName } from "../package.json"; import { addonName, addonID, addonRef } from "../package.json";
class AddonEvents extends AddonModule { class AddonEvents extends AddonModule {
private notifierCallback: any; private notifierCallback: any;
@ -28,15 +28,12 @@ class AddonEvents extends AddonModule {
}; };
} }
public async onInit(_Zotero: _ZoteroConstructable, rootURI) { public async onInit() {
this._Addon.Zotero = _Zotero; this._Addon.Zotero = Zotero;
// @ts-ignore
this._Addon.rootURI = rootURI; this._Addon.rootURI = rootURI;
// This function is the setup code of the addon // This function is the setup code of the addon
this._Addon.Utils.Tool.log(`${addonName}: init called`); this._Addon.Utils.Tool.log(`${addonName}: init called`);
// alert(112233);
// Reset prefs
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, [
@ -54,26 +51,41 @@ class AddonEvents extends AddonModule {
false false
); );
// Initialize preference window
this.initPrefs();
this._Addon.views.initViews(); this._Addon.views.initViews();
this._Addon.views.initPrefs();
} }
private resetState(): void { public initPrefs() {
/* this._Addon.Utils.Tool.log(this._Addon.rootURI);
For prefs that could be simply set to a static default value, const prefOptions = {
Please use addon/defaults/preferences/defaults.js pluginID: addonID,
Reset other preferrences here. src: this._Addon.rootURI + "chrome/content/preferences.xhtml",
Uncomment to use the example code. label: "Template",
*/ image: `chrome://${addonRef}/content/icons/favicon.png`,
// let testPref = Zotero.Prefs.get("addonTemplate.testPref"); extraDTD: [`chrome://${addonRef}/locale/overlay.dtd`],
// if (typeof testPref === "undefined") { defaultXUL: true,
// Zotero.Prefs.set("addonTemplate.testPref", 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);
}
}
private unInitPrefs() {
if (!this._Addon.Utils.Compat.isZotero7()) {
this._Addon.Utils.Compat.unregisterPrefPane();
}
} }
public onUnInit(): void { public onUnInit(): void {
const Zotero = this._Addon.Zotero; const Zotero = this._Addon.Zotero;
this._Addon.Utils.Tool.log(`${addonName}: uninit called`); this._Addon.Utils.Tool.log(`${addonName}: uninit called`);
this.unInitPrefs();
// Remove elements and do clean up // Remove elements and do clean up
this._Addon.views.unInitViews(); this._Addon.views.unInitViews();
// Remove addon object // Remove addon object

View File

@ -1,7 +1,17 @@
import Addon from "./addon"; import Addon from "./addon";
/**
* Globals: bootstrap.js > ctx
* const ctx = {
Zotero,
rootURI,
window,
document: window.document,
ZoteroPane: Zotero.getActiveZoteroPane(),
};
*/
if (!Zotero.AddonTemplate) { if (!Zotero.AddonTemplate) {
Zotero.AddonTemplate = new Addon(); Zotero.AddonTemplate = new Addon();
// @ts-ignore // @ts-ignore
Zotero.AddonTemplate.events.onInit(Zotero, rootURI); Zotero.AddonTemplate.events.onInit();
} }

View File

@ -18,6 +18,9 @@ class AddonUtils extends AddonModule {
} }
return Zotero; return Zotero;
}, },
getWindow: () => {
return this.Compat.getZotero().getMainWindow() as Window;
},
// Check if it's running on Zotero 7 (Firefox 102) // Check if it's running on Zotero 7 (Firefox 102)
isZotero7: () => Zotero.platformMajorVersion >= 102, isZotero7: () => Zotero.platformMajorVersion >= 102,
// Firefox 102 support DOMParser natively // Firefox 102 support DOMParser natively
@ -33,7 +36,12 @@ class AddonUtils extends AddonModule {
].createInstance(Components.interfaces.nsIDOMParser); ].createInstance(Components.interfaces.nsIDOMParser);
} }
}, },
isXULElement: (elem: Element) => {
return (
elem.namespaceURI ===
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
);
},
// create XUL element // create XUL element
createXULElement: (doc: Document, type: string) => { createXULElement: (doc: Document, type: string) => {
if (this.Compat.isZotero7()) { if (this.Compat.isZotero7()) {
@ -93,6 +101,145 @@ class AddonUtils extends AddonModule {
}, },
prefPaneCache: { win: undefined, listeners: [], ids: [] }, prefPaneCache: { win: undefined, listeners: [], ids: [] },
registerPrefPane: (options: PrefPaneOptions) => { registerPrefPane: (options: PrefPaneOptions) => {
const _initImportedNodesPostInsert = (container) => {
const _observerSymbols = new Map();
const Zotero = this.Compat.getZotero();
const window = container.ownerGlobal;
let useChecked = (elem) =>
(elem instanceof window.HTMLInputElement &&
elem.type == "checkbox") ||
elem.tagName == "checkbox";
let syncFromPref = (elem, preference) => {
let value = Zotero.Prefs.get(preference, true);
if (useChecked(elem)) {
elem.checked = value;
} else {
elem.value = value;
}
elem.dispatchEvent(new window.Event("syncfrompreference"));
};
// We use a single listener function shared between all elements so we can easily detach it later
let syncToPrefOnModify = (event) => {
if (event.currentTarget.getAttribute("preference")) {
let value = useChecked(event.currentTarget)
? event.currentTarget.checked
: event.currentTarget.value;
Zotero.Prefs.set(
event.currentTarget.getAttribute("preference"),
value,
true
);
event.currentTarget.dispatchEvent(
new window.Event("synctopreference")
);
}
};
let attachToPreference = (elem, preference) => {
Zotero.debug(
`Attaching <${elem.tagName}> element to ${preference}`
);
// @ts-ignore
let symbol = Zotero.Prefs.registerObserver(
preference,
() => syncFromPref(elem, preference),
true
);
_observerSymbols.set(elem, symbol);
};
let detachFromPreference = (elem) => {
if (_observerSymbols.has(elem)) {
Zotero.debug(
`Detaching <${elem.tagName}> element from preference`
);
// @ts-ignore
Zotero.Prefs.unregisterObserver(this._observerSymbols.get(elem));
_observerSymbols.delete(elem);
}
};
// Activate `preference` attributes
for (let elem of container.querySelectorAll("[preference]")) {
let preference = elem.getAttribute("preference");
if (
container.querySelector("preferences > preference#" + preference)
) {
Zotero.warn(
"<preference> is deprecated -- `preference` attribute values " +
"should be full preference keys, not <preference> IDs"
);
preference = container
.querySelector("preferences > preference#" + preference)
.getAttribute("name");
}
attachToPreference(elem, preference);
elem.addEventListener(
this.Compat.isXULElement(elem) ? "command" : "input",
syncToPrefOnModify
);
// Set timeout before populating the value so the pane can add listeners first
window.setTimeout(() => {
syncFromPref(elem, preference);
});
}
new window.MutationObserver((mutations) => {
for (let mutation of mutations) {
if (mutation.type == "attributes") {
let target = mutation.target as Element;
detachFromPreference(target);
if (target.hasAttribute("preference")) {
attachToPreference(target, target.getAttribute("preference"));
target.addEventListener(
this.Compat.isXULElement(target) ? "command" : "input",
syncToPrefOnModify
);
}
} else if (mutation.type == "childList") {
for (let node of mutation.removedNodes) {
detachFromPreference(node);
}
for (let node of mutation.addedNodes) {
if (
node.nodeType == Node.ELEMENT_NODE &&
(node as Element).hasAttribute("preference")
) {
attachToPreference(
node,
(node as Element).getAttribute("preference")
);
node.addEventListener(
this.Compat.isXULElement(node as Element)
? "command"
: "input",
syncToPrefOnModify
);
}
}
}
}
}).observe(container, {
childList: true,
subtree: true,
attributeFilter: ["preference"],
});
// parseXULToFragment() doesn't convert oncommand attributes into actual
// listeners, so we'll do it here
for (let elem of container.querySelectorAll("[oncommand]")) {
elem.oncommand = elem.getAttribute("oncommand");
}
for (let child of container.children) {
child.dispatchEvent(new window.Event("load"));
}
};
const windowListener = { const windowListener = {
onOpenWindow: (xulWindow) => { onOpenWindow: (xulWindow) => {
const win: Window = xulWindow const win: Window = xulWindow
@ -107,7 +254,8 @@ class AddonUtils extends AddonModule {
) { ) {
this.Tool.log("registerPrefPane:detected", options); this.Tool.log("registerPrefPane:detected", options);
const Zotero = this.Compat.getZotero(); const Zotero = this.Compat.getZotero();
options.id || (options.id = `plugin-${new Date().getTime()}`); options.id ||
(options.id = `plugin-${Zotero.Utilities.randomString()}-${new Date().getTime()}`);
const contenrOrXHR = await Zotero.File.getContentsAsync( const contenrOrXHR = await Zotero.File.getContentsAsync(
options.src options.src
); );
@ -115,7 +263,7 @@ class AddonUtils extends AddonModule {
typeof contenrOrXHR === "string" typeof contenrOrXHR === "string"
? contenrOrXHR ? contenrOrXHR
: (contenrOrXHR as any as XMLHttpRequest).response; : (contenrOrXHR as any as XMLHttpRequest).response;
const src = `<prefpane id="${ const src = `<prefpane xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="${
options.id options.id
}" insertafter="zotero-prefpane-advanced" label="${ }" insertafter="zotero-prefpane-advanced" label="${
options.label || options.pluginID options.label || options.pluginID
@ -136,6 +284,8 @@ class AddonUtils extends AddonModule {
this.Compat.prefPaneCache.win = win; this.Compat.prefPaneCache.win = win;
this.Compat.prefPaneCache.listeners.push(windowListener); this.Compat.prefPaneCache.listeners.push(windowListener);
this.Compat.prefPaneCache.ids.push(options.id); this.Compat.prefPaneCache.ids.push(options.id);
// Binding preferences
_initImportedNodesPostInsert(prefPane);
if (options.onload) { if (options.onload) {
options.onload(win); options.onload(win);
} }

View File

@ -1,6 +1,6 @@
import Addon from "./addon"; import Addon from "./addon";
import AddonModule from "./module"; import AddonModule from "./module";
const { addonRef, addonID } = require("../package.json"); const { addonRef } = 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,7 +16,6 @@ class AddonViews extends AddonModule {
} }
public initViews() { public initViews() {
const Zotero = this._Addon.Zotero;
// 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
this._Addon.Utils.Tool.log("Initializing UI"); this._Addon.Utils.Tool.log("Initializing UI");
@ -59,34 +58,9 @@ 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() { public unInitViews() {
const Zotero = this._Addon.Zotero;
this._Addon.Utils.Tool.log("Uninitializing UI"); this._Addon.Utils.Tool.log("Uninitializing UI");
this._Addon.Utils.UI.removeAddonElements(); this._Addon.Utils.UI.removeAddonElements();
if (!this._Addon.Utils.Compat.isZotero7()) {
this._Addon.Utils.Compat.unregisterPrefPane();
}
} }
public showProgressWindow( public showProgressWindow(

4
typing/global.d.ts vendored
View File

@ -1,7 +1,9 @@
declare interface ZoteroCompat { declare interface ZoteroCompat {
getZotero: () => _ZoteroConstructable; getZotero: () => _ZoteroConstructable;
getWindow: () => Window;
isZotero7: () => boolean; isZotero7: () => boolean;
getDOMParser: () => DOMParser; getDOMParser: () => DOMParser;
isXULElement: (elem: Element) => boolean;
createXULElement: (doc: Document, type: string) => XUL.Element; createXULElement: (doc: Document, type: string) => XUL.Element;
parseXHTMLToFragment: ( parseXHTMLToFragment: (
str: string, str: string,
@ -29,7 +31,7 @@ declare interface ZoteroUI {
createElement: ( createElement: (
doc: Document, doc: Document,
tagName: string, tagName: string,
namespace: "html" | "svg" | "xul" namespace?: "html" | "svg" | "xul"
) => XUL.Element | DocumentFragment | HTMLElement | SVGAElement; ) => XUL.Element | DocumentFragment | HTMLElement | SVGAElement;
removeAddonElements: () => void; removeAddonElements: () => void;
creatElementsFromJSON: ( creatElementsFromJSON: (