add: preference value bindings for Zotero 6
add: global variables fix: bootstrap bugs
This commit is contained in:
parent
600bda7bd4
commit
7bcbd3e025
23
addon/bootstrap.js
vendored
23
addon/bootstrap.js
vendored
@ -69,7 +69,15 @@ async function startup({ id, version, resourceURI, rootURI }, reason) {
|
||||
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(
|
||||
`${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__", "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`);
|
||||
|
||||
chromeHandle.destruct();
|
||||
chromeHandle = null;
|
||||
if (chromeHandle) {
|
||||
chromeHandle.destruct();
|
||||
chromeHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
function uninstall(data, reason) {}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Addon from "./addon";
|
||||
import AddonModule from "./module";
|
||||
import { addonName } from "../package.json";
|
||||
import { addonName, addonID, addonRef } from "../package.json";
|
||||
|
||||
class AddonEvents extends AddonModule {
|
||||
private notifierCallback: any;
|
||||
@ -28,15 +28,12 @@ class AddonEvents extends AddonModule {
|
||||
};
|
||||
}
|
||||
|
||||
public async onInit(_Zotero: _ZoteroConstructable, rootURI) {
|
||||
this._Addon.Zotero = _Zotero;
|
||||
public async onInit() {
|
||||
this._Addon.Zotero = Zotero;
|
||||
// @ts-ignore
|
||||
this._Addon.rootURI = rootURI;
|
||||
// This function is the setup code of the addon
|
||||
this._Addon.Utils.Tool.log(`${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, [
|
||||
@ -54,26 +51,41 @@ class AddonEvents extends AddonModule {
|
||||
false
|
||||
);
|
||||
|
||||
// Initialize preference window
|
||||
this.initPrefs();
|
||||
this._Addon.views.initViews();
|
||||
this._Addon.views.initPrefs();
|
||||
}
|
||||
|
||||
private resetState(): void {
|
||||
/*
|
||||
For prefs that could be simply set to a static default value,
|
||||
Please use addon/defaults/preferences/defaults.js
|
||||
Reset other preferrences here.
|
||||
Uncomment to use the example code.
|
||||
*/
|
||||
// let testPref = Zotero.Prefs.get("addonTemplate.testPref");
|
||||
// if (typeof testPref === "undefined") {
|
||||
// Zotero.Prefs.set("addonTemplate.testPref", true);
|
||||
// }
|
||||
public initPrefs() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private unInitPrefs() {
|
||||
if (!this._Addon.Utils.Compat.isZotero7()) {
|
||||
this._Addon.Utils.Compat.unregisterPrefPane();
|
||||
}
|
||||
}
|
||||
|
||||
public onUnInit(): void {
|
||||
const Zotero = this._Addon.Zotero;
|
||||
this._Addon.Utils.Tool.log(`${addonName}: uninit called`);
|
||||
this.unInitPrefs();
|
||||
// Remove elements and do clean up
|
||||
this._Addon.views.unInitViews();
|
||||
// Remove addon object
|
||||
|
12
src/index.ts
12
src/index.ts
@ -1,7 +1,17 @@
|
||||
import Addon from "./addon";
|
||||
|
||||
/**
|
||||
* Globals: bootstrap.js > ctx
|
||||
* const ctx = {
|
||||
Zotero,
|
||||
rootURI,
|
||||
window,
|
||||
document: window.document,
|
||||
ZoteroPane: Zotero.getActiveZoteroPane(),
|
||||
};
|
||||
*/
|
||||
if (!Zotero.AddonTemplate) {
|
||||
Zotero.AddonTemplate = new Addon();
|
||||
// @ts-ignore
|
||||
Zotero.AddonTemplate.events.onInit(Zotero, rootURI);
|
||||
Zotero.AddonTemplate.events.onInit();
|
||||
}
|
||||
|
156
src/utils.ts
156
src/utils.ts
@ -18,6 +18,9 @@ class AddonUtils extends AddonModule {
|
||||
}
|
||||
return Zotero;
|
||||
},
|
||||
getWindow: () => {
|
||||
return this.Compat.getZotero().getMainWindow() as Window;
|
||||
},
|
||||
// Check if it's running on Zotero 7 (Firefox 102)
|
||||
isZotero7: () => Zotero.platformMajorVersion >= 102,
|
||||
// Firefox 102 support DOMParser natively
|
||||
@ -33,7 +36,12 @@ class AddonUtils extends AddonModule {
|
||||
].createInstance(Components.interfaces.nsIDOMParser);
|
||||
}
|
||||
},
|
||||
|
||||
isXULElement: (elem: Element) => {
|
||||
return (
|
||||
elem.namespaceURI ===
|
||||
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
);
|
||||
},
|
||||
// create XUL element
|
||||
createXULElement: (doc: Document, type: string) => {
|
||||
if (this.Compat.isZotero7()) {
|
||||
@ -93,6 +101,145 @@ class AddonUtils extends AddonModule {
|
||||
},
|
||||
prefPaneCache: { win: undefined, listeners: [], ids: [] },
|
||||
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 = {
|
||||
onOpenWindow: (xulWindow) => {
|
||||
const win: Window = xulWindow
|
||||
@ -107,7 +254,8 @@ class AddonUtils extends AddonModule {
|
||||
) {
|
||||
this.Tool.log("registerPrefPane:detected", options);
|
||||
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(
|
||||
options.src
|
||||
);
|
||||
@ -115,7 +263,7 @@ class AddonUtils extends AddonModule {
|
||||
typeof contenrOrXHR === "string"
|
||||
? contenrOrXHR
|
||||
: (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
|
||||
}" insertafter="zotero-prefpane-advanced" label="${
|
||||
options.label || options.pluginID
|
||||
@ -136,6 +284,8 @@ class AddonUtils extends AddonModule {
|
||||
this.Compat.prefPaneCache.win = win;
|
||||
this.Compat.prefPaneCache.listeners.push(windowListener);
|
||||
this.Compat.prefPaneCache.ids.push(options.id);
|
||||
// Binding preferences
|
||||
_initImportedNodesPostInsert(prefPane);
|
||||
if (options.onload) {
|
||||
options.onload(win);
|
||||
}
|
||||
|
28
src/views.ts
28
src/views.ts
@ -1,6 +1,6 @@
|
||||
import Addon from "./addon";
|
||||
import AddonModule from "./module";
|
||||
const { addonRef, addonID } = require("../package.json");
|
||||
const { addonRef } = require("../package.json");
|
||||
|
||||
class AddonViews extends AddonModule {
|
||||
// You can store some element in the object attributes
|
||||
@ -16,7 +16,6 @@ class AddonViews extends AddonModule {
|
||||
}
|
||||
|
||||
public initViews() {
|
||||
const Zotero = this._Addon.Zotero;
|
||||
// You can init the UI elements that
|
||||
// cannot be initialized with overlay.xul
|
||||
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() {
|
||||
const Zotero = this._Addon.Zotero;
|
||||
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(
|
||||
|
4
typing/global.d.ts
vendored
4
typing/global.d.ts
vendored
@ -1,7 +1,9 @@
|
||||
declare interface ZoteroCompat {
|
||||
getZotero: () => _ZoteroConstructable;
|
||||
getWindow: () => Window;
|
||||
isZotero7: () => boolean;
|
||||
getDOMParser: () => DOMParser;
|
||||
isXULElement: (elem: Element) => boolean;
|
||||
createXULElement: (doc: Document, type: string) => XUL.Element;
|
||||
parseXHTMLToFragment: (
|
||||
str: string,
|
||||
@ -29,7 +31,7 @@ declare interface ZoteroUI {
|
||||
createElement: (
|
||||
doc: Document,
|
||||
tagName: string,
|
||||
namespace: "html" | "svg" | "xul"
|
||||
namespace?: "html" | "svg" | "xul"
|
||||
) => XUL.Element | DocumentFragment | HTMLElement | SVGAElement;
|
||||
removeAddonElements: () => void;
|
||||
creatElementsFromJSON: (
|
||||
|
Loading…
x
Reference in New Issue
Block a user