add: preference value bindings for Zotero 6
add: global variables fix: bootstrap bugs
This commit is contained in:
parent
600bda7bd4
commit
7bcbd3e025
19
addon/bootstrap.js
vendored
19
addon/bootstrap.js
vendored
@ -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) {}
|
||||||
|
@ -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
|
||||||
|
12
src/index.ts
12
src/index.ts
@ -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();
|
||||||
}
|
}
|
||||||
|
156
src/utils.ts
156
src/utils.ts
@ -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);
|
||||||
}
|
}
|
||||||
|
28
src/views.ts
28
src/views.ts
@ -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
4
typing/global.d.ts
vendored
@ -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: (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user