Merge branch 'bootstrap-zo7' into bootstrap
This commit is contained in:
commit
2cd31e414e
131
addon/bootstrap.js
vendored
131
addon/bootstrap.js
vendored
@ -3,69 +3,118 @@
|
|||||||
* 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");
|
var chromeHandle;
|
||||||
|
|
||||||
function install(data, reason) {}
|
// 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 startup(data, reason) {
|
// Zotero window to open and get the Zotero object from there.
|
||||||
// Load the addon to Zotero if window is ready
|
//
|
||||||
const loadAddon = (window) => {
|
// In Zotero 7, bootstrap methods are not called until Zotero is initialized, and the 'Zotero' is
|
||||||
console.log(window);
|
// automatically made available.
|
||||||
if (window.document.readyState === "complete" && window.Zotero) {
|
async function waitForZotero() {
|
||||||
Services.scriptloader.loadSubScript(
|
if (typeof Zotero != "undefined") {
|
||||||
"chrome://__addonRef__/content/scripts/index.js"
|
await Zotero.initializationPromise;
|
||||||
);
|
|
||||||
} 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = { Zotero, rootURI };
|
||||||
|
|
||||||
|
Services.scriptloader.loadSubScript(
|
||||||
|
`${rootURI}/chrome/content/scripts/index.js`,
|
||||||
|
ctx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (Zotero.platformMajorVersion >= 102) {
|
||||||
|
var aomStartup = Components.classes[
|
||||||
|
"@mozilla.org/addons/addon-manager-startup;1"
|
||||||
|
].getService(Components.interfaces.amIAddonManagerStartup);
|
||||||
|
var manifestURI = Services.io.newURI(rootURI + "manifest.json");
|
||||||
|
chromeHandle = aomStartup.registerChrome(manifestURI, [
|
||||||
|
["content", "__addonRef__", rootURI + "chrome/content/"],
|
||||||
|
["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,
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
content __addonRef__ chrome/content/
|
content __addonRef__ chrome/content/
|
||||||
skin __addonRef__ default chrome/skin/default/__addonRef__/
|
|
||||||
locale __addonRef__ en-US chrome/locale/en-US/
|
locale __addonRef__ en-US chrome/locale/en-US/
|
||||||
locale __addonRef__ zh-CN chrome/locale/zh-CN/
|
locale __addonRef__ zh-CN chrome/locale/zh-CN/
|
||||||
|
Before Width: | Height: | Size: 677 B After Width: | Height: | Size: 677 B |
Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 836 B |
21
addon/chrome/content/preferences.xhtml
Normal file
21
addon/chrome/content/preferences.xhtml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<vbox
|
||||||
|
id="zotero-prefpane-__addonRef__"
|
||||||
|
onload="Zotero.AddonTemplate.prefs.initPreferences(window)"
|
||||||
|
>
|
||||||
|
<groupbox>
|
||||||
|
<label><html:h2>Addon Template Example</html:h2></label>
|
||||||
|
<checkbox
|
||||||
|
id="zotero-prefpane-__addonRef__-enable"
|
||||||
|
label="&zotero.__addonRef__.pref.enable.label;"
|
||||||
|
/>
|
||||||
|
<hbox>
|
||||||
|
<html:input
|
||||||
|
type="text"
|
||||||
|
id="zotero-prefpane-__addonRef__-input"
|
||||||
|
></html:input>
|
||||||
|
<html:label for="zotero-prefpane-__addonRef__-input"
|
||||||
|
>&zotero.__addonRef__.pref.input.label;</html:label
|
||||||
|
>
|
||||||
|
</hbox>
|
||||||
|
</groupbox>
|
||||||
|
</vbox>
|
@ -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>
|
|
@ -1,5 +1,6 @@
|
|||||||
<!ENTITY zotero.__addonRef__.itemmenu.test.label "addon template">
|
<!ENTITY zotero.__addonRef__.itemmenu.test.label "addon template">
|
||||||
<!ENTITY zotero.__addonRef__.pref.enable.label "Enable">
|
<!ENTITY zotero.__addonRef__.pref.enable.label "Enable">
|
||||||
|
<!ENTITY zotero.__addonRef__.pref.input.label "Input">
|
||||||
|
|
||||||
<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ VERSION __buildVersion__">
|
<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ VERSION __buildVersion__">
|
||||||
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">
|
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">
|
@ -1,5 +1,6 @@
|
|||||||
<!ENTITY zotero.__addonRef__.itemmenu.test.label "插件模板">
|
<!ENTITY zotero.__addonRef__.itemmenu.test.label "插件模板">
|
||||||
<!ENTITY zotero.__addonRef__.pref.enable.label "开启">
|
<!ENTITY zotero.__addonRef__.pref.enable.label "开启">
|
||||||
|
<!ENTITY zotero.__addonRef__.pref.input.label "输入">
|
||||||
|
|
||||||
<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ 版本 __buildVersion__">
|
<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ 版本 __buildVersion__">
|
||||||
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">
|
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">
|
@ -12,7 +12,7 @@
|
|||||||
em:creator="__author__"
|
em:creator="__author__"
|
||||||
em:description="__description__"
|
em:description="__description__"
|
||||||
em:homepageURL="__homepage__"
|
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:optionsURL="chrome://__addonRef__/content/preferences.xul"
|
||||||
em:updateURL="__updaterdf__"
|
em:updateURL="__updaterdf__"
|
||||||
em:multiprocessCompatible="true"
|
em:multiprocessCompatible="true"
|
||||||
|
19
addon/manifest.json
Normal file
19
addon/manifest.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "__addonName__",
|
||||||
|
"version": "__buildVersion__",
|
||||||
|
"description": "__description__",
|
||||||
|
"author": "__author__",
|
||||||
|
"icons": {
|
||||||
|
"48": "chrome/content/icons/favicon@0.5x.png",
|
||||||
|
"96": "chrome/content/icons/favicon.png"
|
||||||
|
},
|
||||||
|
"applications": {
|
||||||
|
"zotero": {
|
||||||
|
"id": "__addonID__",
|
||||||
|
"update_url": "__updaterdf__",
|
||||||
|
"strict_min_version": "6.999",
|
||||||
|
"strict_max_version": "7.0.*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
build.js
11
build.js
@ -96,6 +96,9 @@ async function main() {
|
|||||||
|
|
||||||
copyFolderRecursiveSync("addon", buildDir);
|
copyFolderRecursiveSync("addon", buildDir);
|
||||||
|
|
||||||
|
copyFileSync("update-template.json", "update.json");
|
||||||
|
copyFileSync("update-template.rdf", "update.rdf");
|
||||||
|
|
||||||
await esbuild
|
await esbuild
|
||||||
.build({
|
.build({
|
||||||
entryPoints: ["src/index.ts"],
|
entryPoints: ["src/index.ts"],
|
||||||
@ -113,9 +116,13 @@ 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/manifest.json"),
|
||||||
path.join(buildDir, "addon/bootstrap.js"),
|
path.join(buildDir, "addon/bootstrap.js"),
|
||||||
|
"update.json",
|
||||||
"update.rdf",
|
"update.rdf",
|
||||||
],
|
],
|
||||||
from: [
|
from: [
|
||||||
@ -129,7 +136,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 +148,6 @@ async function main() {
|
|||||||
addonRef,
|
addonRef,
|
||||||
version,
|
version,
|
||||||
buildTime,
|
buildTime,
|
||||||
`<em:version>${version}</em:version>`,
|
|
||||||
],
|
],
|
||||||
countMatches: true,
|
countMatches: true,
|
||||||
};
|
};
|
||||||
|
@ -26,14 +26,15 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/windingwind/zotero-addon-template#readme",
|
"homepage": "https://github.com/windingwind/zotero-addon-template#readme",
|
||||||
"releasepage": "https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi",
|
"releasepage": "https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi",
|
||||||
"updaterdf": "https://raw.githubusercontent.com/windingwind/zotero-addon-template/master/update.rdf",
|
"updaterdf": "https://raw.githubusercontent.com/windingwind/zotero-addon-template/master/update.json",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"compressing": "^1.5.1",
|
"compressing": "^1.5.1",
|
||||||
"esbuild": "^0.15.16",
|
"esbuild": "^0.15.16",
|
||||||
"replace-in-file": "^6.3.2"
|
"replace-in-file": "^6.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"release-it": "^15.5.0",
|
"@types/node": "^18.7.20",
|
||||||
"zotero-types": "^0.0.6"
|
"release-it": "^14.14.0",
|
||||||
|
"zotero-types": "^0.0.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
src/addon.ts
10
src/addon.ts
@ -1,19 +1,23 @@
|
|||||||
import AddonEvents from "./events";
|
import AddonEvents from "./events";
|
||||||
import AddonPrefs from "./prefs";
|
import AddonPrefs from "./prefs";
|
||||||
|
import AddonUtils from "./utils";
|
||||||
import AddonViews from "./views";
|
import AddonViews from "./views";
|
||||||
|
|
||||||
const { addonName } = require("../package.json");
|
|
||||||
|
|
||||||
class Addon {
|
class Addon {
|
||||||
|
public Zotero: _ZoteroConstructable;
|
||||||
public events: AddonEvents;
|
public events: AddonEvents;
|
||||||
public views: AddonViews;
|
public views: AddonViews;
|
||||||
public prefs: AddonPrefs;
|
public prefs: AddonPrefs;
|
||||||
|
public Utils: AddonUtils;
|
||||||
|
// root path to access the resources
|
||||||
|
public rootURI: string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.events = new AddonEvents(this);
|
this.events = new AddonEvents(this);
|
||||||
this.views = new AddonViews(this);
|
this.views = new AddonViews(this);
|
||||||
this.prefs = new AddonPrefs(this);
|
this.prefs = new AddonPrefs(this);
|
||||||
|
this.Utils = new AddonUtils(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { addonName, Addon };
|
export default Addon;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Addon, addonName } from "./addon";
|
import Addon from "./addon";
|
||||||
import AddonModule from "./module";
|
import AddonModule from "./module";
|
||||||
|
import { addonName } from "../package.json";
|
||||||
|
|
||||||
class AddonEvents extends AddonModule {
|
class AddonEvents extends AddonModule {
|
||||||
private notifierCallback: any;
|
private notifierCallback: any;
|
||||||
@ -27,32 +28,34 @@ class AddonEvents extends AddonModule {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onInit(_Zotero) {
|
public async onInit(_Zotero: _ZoteroConstructable, rootURI) {
|
||||||
|
this._Addon.Zotero = _Zotero;
|
||||||
|
this._Addon.rootURI = rootURI;
|
||||||
// This function is the setup code of the addon
|
// This function is the setup code of the addon
|
||||||
console.log(`${addonName}: init called`);
|
this._Addon.Utils.Tool.log(`${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 +71,13 @@ class AddonEvents extends AddonModule {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
public onUnInit(_Zotero): void {
|
public onUnInit(): void {
|
||||||
console.log(`${addonName}: uninit called`);
|
const Zotero = this._Addon.Zotero;
|
||||||
_Zotero.debug(`${addonName}: uninit called`);
|
this._Addon.Utils.Tool.log(`${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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
src/index.ts
12
src/index.ts
@ -1,9 +1,7 @@
|
|||||||
import { Addon } from "./addon";
|
import Addon from "./addon";
|
||||||
|
|
||||||
var _Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
|
if (!Zotero.AddonTemplate) {
|
||||||
Components.interfaces.nsISupports
|
Zotero.AddonTemplate = new Addon();
|
||||||
).wrappedJSObject;
|
// @ts-ignore
|
||||||
if (!_Zotero.AddonTemplate) {
|
Zotero.AddonTemplate.events.onInit(Zotero, rootURI);
|
||||||
_Zotero.AddonTemplate = new Addon();
|
|
||||||
_Zotero.AddonTemplate.events.onInit(_Zotero);
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import Addon from "./addon";
|
||||||
|
|
||||||
class AddonModule {
|
class AddonModule {
|
||||||
protected _Addon: any;
|
protected _Addon: Addon;
|
||||||
constructor(parent: any) {
|
constructor(parent: Addon) {
|
||||||
this._Addon = parent;
|
this._Addon = parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
src/prefs.ts
28
src/prefs.ts
@ -1,5 +1,6 @@
|
|||||||
import { Addon, addonName } from "./addon";
|
import Addon from "./addon";
|
||||||
import AddonModule from "./module";
|
import AddonModule from "./module";
|
||||||
|
import { addonName, addonRef } from "../package.json";
|
||||||
|
|
||||||
class AddonPrefs extends AddonModule {
|
class AddonPrefs extends AddonModule {
|
||||||
private _window: Window;
|
private _window: Window;
|
||||||
@ -10,15 +11,36 @@ class AddonPrefs extends AddonModule {
|
|||||||
// This function is called when the prefs window is opened
|
// This function is called when the prefs window is opened
|
||||||
// See addon/chrome/content/preferences.xul onpaneload
|
// See addon/chrome/content/preferences.xul onpaneload
|
||||||
this._window = _window;
|
this._window = _window;
|
||||||
Zotero.debug(`${addonName}: init preferences`);
|
this._Addon.Utils.Tool.log(`${addonName}: init preferences`);
|
||||||
this.updatePrefsUI();
|
this.updatePrefsUI();
|
||||||
|
this.bindPrefEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updatePrefsUI() {
|
private updatePrefsUI() {
|
||||||
// You can initialize some UI elements on prefs window
|
// You can initialize some UI elements on prefs window
|
||||||
// with this._window.document
|
// with this._window.document
|
||||||
// Or bind some events to the elements
|
// 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}!`
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
461
src/utils.ts
Normal file
461
src/utils.ts
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
import Addon from "./addon";
|
||||||
|
import AddonModule from "./module";
|
||||||
|
|
||||||
|
class AddonUtils extends AddonModule {
|
||||||
|
public Compat: ZoteroCompat;
|
||||||
|
public Tool: ZoteroTool;
|
||||||
|
public UI: ZoteroUI;
|
||||||
|
|
||||||
|
constructor(parent: Addon) {
|
||||||
|
super(parent);
|
||||||
|
this.Compat = {
|
||||||
|
// Get Zotero instance
|
||||||
|
getZotero: () => {
|
||||||
|
if (typeof Zotero === "undefined") {
|
||||||
|
return Components.classes["@zotero.org/Zotero;1"].getService(
|
||||||
|
Components.interfaces.nsISupports
|
||||||
|
).wrappedJSObject;
|
||||||
|
}
|
||||||
|
return Zotero;
|
||||||
|
},
|
||||||
|
// Check if it's running on Zotero 7 (Firefox 102)
|
||||||
|
isZotero7: () => Zotero.platformMajorVersion >= 102,
|
||||||
|
// Firefox 102 support DOMParser natively
|
||||||
|
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()) {
|
||||||
|
// @ts-ignore
|
||||||
|
return doc.createXULElement(type);
|
||||||
|
} else {
|
||||||
|
return doc.createElementNS(
|
||||||
|
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
||||||
|
type
|
||||||
|
) 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
|
||||||
|
? `<!DOCTYPE bindings [ ${entities.reduce(
|
||||||
|
(preamble, url, index) => {
|
||||||
|
return (
|
||||||
|
preamble +
|
||||||
|
`<!ENTITY % _dtd-${index} SYSTEM "${url}"> %_dtd-${index}; `
|
||||||
|
);
|
||||||
|
},
|
||||||
|
""
|
||||||
|
)}]>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
<html:div xmlns="${defaultXUL ? xulns : htmlns}"
|
||||||
|
xmlns:xul="${xulns}" xmlns:html="${htmlns}">
|
||||||
|
${str}
|
||||||
|
</html:div>`;
|
||||||
|
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 = `<prefpane id="${
|
||||||
|
options.id
|
||||||
|
}" insertafter="zotero-prefpane-advanced" label="${
|
||||||
|
options.label || options.pluginID
|
||||||
|
}" image="${options.image || ""}">
|
||||||
|
${(await Zotero.File.getContentsAsync(options.src)) as string}
|
||||||
|
</prefpane>`;
|
||||||
|
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(),
|
||||||
|
openFilePicker: (
|
||||||
|
title: string,
|
||||||
|
mode: "open" | "save" | "folder",
|
||||||
|
filters?: [string, string][],
|
||||||
|
suggestion?: string
|
||||||
|
) => {
|
||||||
|
const fp = Components.classes[
|
||||||
|
"@mozilla.org/filepicker;1"
|
||||||
|
].createInstance(Components.interfaces.nsIFilePicker);
|
||||||
|
|
||||||
|
if (suggestion) fp.defaultString = suggestion;
|
||||||
|
|
||||||
|
mode = {
|
||||||
|
open: Components.interfaces.nsIFilePicker.modeOpen,
|
||||||
|
save: Components.interfaces.nsIFilePicker.modeSave,
|
||||||
|
folder: Components.interfaces.nsIFilePicker.modeGetFolder,
|
||||||
|
}[mode];
|
||||||
|
|
||||||
|
fp.init(window, title, mode);
|
||||||
|
|
||||||
|
for (const [label, ext] of filters || []) {
|
||||||
|
fp.appendFilter(label, ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
fp.open((userChoice) => {
|
||||||
|
switch (userChoice) {
|
||||||
|
case Components.interfaces.nsIFilePicker.returnOK:
|
||||||
|
case Components.interfaces.nsIFilePicker.returnReplace:
|
||||||
|
resolve(fp.file.path);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // aka returnCancel
|
||||||
|
resolve("");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
log: (...data: any[]) => {
|
||||||
|
try {
|
||||||
|
this._Addon.Zotero.getMainWindow().console.log(...data);
|
||||||
|
for (const d of data) {
|
||||||
|
this._Addon.Zotero.debug(d);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this._Addon.Zotero.debug(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.UI = {
|
||||||
|
addonElements: [],
|
||||||
|
createElement: (
|
||||||
|
doc: Document,
|
||||||
|
tagName: string,
|
||||||
|
namespace: "html" | "svg" | "xul" = "html"
|
||||||
|
) => {
|
||||||
|
namespace = namespace || "html";
|
||||||
|
const namespaces = {
|
||||||
|
html: "http://www.w3.org/1999/xhtml",
|
||||||
|
svg: "http://www.w3.org/2000/svg",
|
||||||
|
};
|
||||||
|
if (tagName === "fragment") {
|
||||||
|
return doc.createDocumentFragment();
|
||||||
|
} else if (namespace === "xul") {
|
||||||
|
const e = this.Compat.createXULElement(doc, tagName);
|
||||||
|
this.UI.addonElements.push(e);
|
||||||
|
return e;
|
||||||
|
} else {
|
||||||
|
const e = doc.createElementNS(namespaces[namespace], tagName) as
|
||||||
|
| HTMLElement
|
||||||
|
| SVGAElement;
|
||||||
|
this.UI.addonElements.push(e);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeAddonElements: () => {
|
||||||
|
this.UI.addonElements.forEach((e) => {
|
||||||
|
try {
|
||||||
|
e?.remove();
|
||||||
|
} catch (e) {
|
||||||
|
this._Addon.Utils.Tool.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
creatElementsFromJSON: (doc: Document, options: ElementOptions) => {
|
||||||
|
this.Tool.log(options);
|
||||||
|
if (
|
||||||
|
options.id &&
|
||||||
|
(options.checkExistanceParent
|
||||||
|
? options.checkExistanceParent
|
||||||
|
: doc
|
||||||
|
).querySelector(`#${options.id}`)
|
||||||
|
) {
|
||||||
|
if (options.ignoreIfExists) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (options.removeIfExists) {
|
||||||
|
doc.querySelector(`#${options.id}`).remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.customCheck && !options.customCheck()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const element = this.UI.createElement(
|
||||||
|
doc,
|
||||||
|
options.tag,
|
||||||
|
options.namespace
|
||||||
|
);
|
||||||
|
|
||||||
|
let _DocumentFragment: typeof DocumentFragment;
|
||||||
|
if (typeof DocumentFragment === "undefined") {
|
||||||
|
_DocumentFragment = (doc as any).ownerGlobal.DocumentFragment;
|
||||||
|
} else {
|
||||||
|
_DocumentFragment = DocumentFragment;
|
||||||
|
}
|
||||||
|
if (!(element instanceof _DocumentFragment)) {
|
||||||
|
if (options.id) {
|
||||||
|
element.id = options.id;
|
||||||
|
}
|
||||||
|
if (options.styles && Object.keys(options.styles).length) {
|
||||||
|
Object.keys(options.styles).forEach((k) => {
|
||||||
|
const v = options.styles[k];
|
||||||
|
typeof v !== "undefined" && (element.style[k] = v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
options.directAttributes &&
|
||||||
|
Object.keys(options.directAttributes).length
|
||||||
|
) {
|
||||||
|
Object.keys(options.directAttributes).forEach((k) => {
|
||||||
|
const v = options.directAttributes[k];
|
||||||
|
typeof v !== "undefined" && (element[k] = v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (options.attributes && Object.keys(options.attributes).length) {
|
||||||
|
Object.keys(options.attributes).forEach((k) => {
|
||||||
|
const v = options.attributes[k];
|
||||||
|
typeof v !== "undefined" && element.setAttribute(k, String(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (options.listeners?.length) {
|
||||||
|
options.listeners.forEach(([type, cbk, option]) => {
|
||||||
|
typeof cbk !== "undefined" &&
|
||||||
|
element.addEventListener(type, cbk, option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.subElementOptions?.length) {
|
||||||
|
const subElements = options.subElementOptions
|
||||||
|
.map((_options) => this.UI.creatElementsFromJSON(doc, _options))
|
||||||
|
.filter((e) => e);
|
||||||
|
element.append(...subElements);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
},
|
||||||
|
defaultMenuPopupSelectors: {
|
||||||
|
menuFile: "#menu_FilePopup",
|
||||||
|
menuEdit: "#menu_EditPopup",
|
||||||
|
menuView: "#menu_viewPopup",
|
||||||
|
menuGo: "#menu_goPopup",
|
||||||
|
menuTools: "#menu_ToolsPopup",
|
||||||
|
menuHelp: "#menu_HelpPopup",
|
||||||
|
collection: "#zotero-collectionmenu",
|
||||||
|
item: "#zotero-itemmenu",
|
||||||
|
},
|
||||||
|
insertMenuItem: (
|
||||||
|
menuPopup: XUL.Menupopup | string,
|
||||||
|
options: MenuitemOptions,
|
||||||
|
insertPosition: "before" | "after" = "after",
|
||||||
|
anchorElement: XUL.Element = undefined
|
||||||
|
) => {
|
||||||
|
const Zotero = this.Compat.getZotero();
|
||||||
|
let popup: XUL.Menupopup;
|
||||||
|
if (typeof menuPopup === "string") {
|
||||||
|
if (
|
||||||
|
!Object.keys(this.UI.defaultMenuPopupSelectors).includes(menuPopup)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
popup = (Zotero.getMainWindow() as Window).document.querySelector(
|
||||||
|
this.UI.defaultMenuPopupSelectors[menuPopup]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
popup = menuPopup;
|
||||||
|
}
|
||||||
|
if (!popup) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const doc: Document = popup.ownerDocument;
|
||||||
|
const generateElementOptions = (
|
||||||
|
menuitemOption: MenuitemOptions
|
||||||
|
): ElementOptions => {
|
||||||
|
let elementOption: ElementOptions = {
|
||||||
|
tag: menuitemOption.tag,
|
||||||
|
id: menuitemOption.id,
|
||||||
|
namespace: "xul",
|
||||||
|
attributes: {
|
||||||
|
label: menuitemOption.label,
|
||||||
|
hidden: Boolean(menuitemOption.hidden),
|
||||||
|
disaled: Boolean(menuitemOption.disabled),
|
||||||
|
class: menuitemOption.class || "",
|
||||||
|
oncommand: menuitemOption.oncommand,
|
||||||
|
},
|
||||||
|
styles: menuitemOption.styles || {},
|
||||||
|
listeners: [["command", menuitemOption.commandListener]],
|
||||||
|
subElementOptions: [],
|
||||||
|
};
|
||||||
|
if (menuitemOption.icon) {
|
||||||
|
elementOption.attributes["class"] += " menuitem-iconic";
|
||||||
|
elementOption.styles[
|
||||||
|
"list-style-image"
|
||||||
|
] = `url(${menuitemOption.icon})`;
|
||||||
|
}
|
||||||
|
if (menuitemOption.tag === "menu") {
|
||||||
|
elementOption.subElementOptions.push({
|
||||||
|
tag: "menupopup",
|
||||||
|
id: menuitemOption.popupId,
|
||||||
|
namespace: "xul",
|
||||||
|
attributes: { onpopupshowing: menuitemOption.onpopupshowing },
|
||||||
|
subElementOptions: menuitemOption.subElementOptions.map(
|
||||||
|
generateElementOptions
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return elementOption;
|
||||||
|
};
|
||||||
|
const menuItem = this.UI.creatElementsFromJSON(
|
||||||
|
doc,
|
||||||
|
generateElementOptions(options)
|
||||||
|
);
|
||||||
|
if (!anchorElement) {
|
||||||
|
anchorElement = (
|
||||||
|
insertPosition === "after"
|
||||||
|
? popup.lastElementChild
|
||||||
|
: popup.firstElementChild
|
||||||
|
) as XUL.Element;
|
||||||
|
}
|
||||||
|
anchorElement[insertPosition](menuItem);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CopyHelper {
|
||||||
|
private transferable: any;
|
||||||
|
private clipboardService: any;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.transferable = Components.classes[
|
||||||
|
"@mozilla.org/widget/transferable;1"
|
||||||
|
].createInstance(Components.interfaces.nsITransferable);
|
||||||
|
this.clipboardService = Components.classes[
|
||||||
|
"@mozilla.org/widget/clipboard;1"
|
||||||
|
].getService(Components.interfaces.nsIClipboard);
|
||||||
|
this.transferable.init(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addText(source: string, type: "text/html" | "text/unicode") {
|
||||||
|
const str = Components.classes[
|
||||||
|
"@mozilla.org/supports-string;1"
|
||||||
|
].createInstance(Components.interfaces.nsISupportsString);
|
||||||
|
str.data = source;
|
||||||
|
this.transferable.addDataFlavor(type);
|
||||||
|
this.transferable.setTransferData(type, str, source.length * 2);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addImage(source: string) {
|
||||||
|
let parts = source.split(",");
|
||||||
|
if (!parts[0].includes("base64")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mime = parts[0].match(/:(.*?);/)[1];
|
||||||
|
let bstr = atob(parts[1]);
|
||||||
|
let n = bstr.length;
|
||||||
|
let u8arr = new Uint8Array(n);
|
||||||
|
while (n--) {
|
||||||
|
u8arr[n] = bstr.charCodeAt(n);
|
||||||
|
}
|
||||||
|
let imgTools = Components.classes["@mozilla.org/image/tools;1"].getService(
|
||||||
|
Components.interfaces.imgITools
|
||||||
|
);
|
||||||
|
let imgPtr = Components.classes[
|
||||||
|
"@mozilla.org/supports-interface-pointer;1"
|
||||||
|
].createInstance(Components.interfaces.nsISupportsInterfacePointer);
|
||||||
|
imgPtr.data = imgTools.decodeImageFromArrayBuffer(u8arr.buffer, mime);
|
||||||
|
this.transferable.addDataFlavor(mime);
|
||||||
|
this.transferable.setTransferData(mime, imgPtr, 0);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public copy() {
|
||||||
|
this.clipboardService.setData(
|
||||||
|
this.transferable,
|
||||||
|
null,
|
||||||
|
Components.interfaces.nsIClipboard.kGlobalClipboard
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddonUtils;
|
93
src/views.ts
93
src/views.ts
@ -1,10 +1,9 @@
|
|||||||
import { Addon } from "./addon";
|
import Addon 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
|
||||||
private testButton: XUL.Button;
|
|
||||||
private progressWindowIcon: object;
|
private progressWindowIcon: object;
|
||||||
|
|
||||||
constructor(parent: Addon) {
|
constructor(parent: Addon) {
|
||||||
@ -12,31 +11,83 @@ class AddonViews extends AddonModule {
|
|||||||
this.progressWindowIcon = {
|
this.progressWindowIcon = {
|
||||||
success: "chrome://zotero/skin/tick.png",
|
success: "chrome://zotero/skin/tick.png",
|
||||||
fail: "chrome://zotero/skin/cross.png",
|
fail: "chrome://zotero/skin/cross.png",
|
||||||
default: `chrome://${addonRef}/skin/favicon.png`,
|
default: `chrome://${addonRef}/content/icons/favicon.png`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public initViews(_Zotero) {
|
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
|
||||||
console.log("Initializing UI");
|
this._Addon.Utils.Tool.log("Initializing UI");
|
||||||
const _window: Window = _Zotero.getMainWindow();
|
const menuIcon =
|
||||||
const menuitem = _window.document.createElement("menuitem");
|
'url("chrome://addontemplate/content/icons/favicon@0.5x.png")';
|
||||||
menuitem.id = "zotero-itemmenu-addontemplate-test";
|
// item menuitem with icon
|
||||||
menuitem.setAttribute("label", "Addon Template");
|
this._Addon.Utils.UI.insertMenuItem("item", {
|
||||||
menuitem.setAttribute("oncommand", "alert('Hello World!')");
|
tag: "menuitem",
|
||||||
menuitem.className = "menuitem-iconic";
|
id: "zotero-itemmenu-addontemplate-test",
|
||||||
menuitem.style["list-style-image"] =
|
label: "Addon Template: Menuitem",
|
||||||
"url('chrome://addontemplate/skin/favicon@0.5x.png')";
|
oncommand: "alert('Hello World! Default Menuitem.')",
|
||||||
_window.document.querySelector("#zotero-itemmenu").appendChild(menuitem);
|
icon: menuIcon,
|
||||||
|
});
|
||||||
|
// item menupopup with sub-menuitems
|
||||||
|
this._Addon.Utils.UI.insertMenuItem(
|
||||||
|
"item",
|
||||||
|
{
|
||||||
|
tag: "menu",
|
||||||
|
label: "Addon Template: Menupopup",
|
||||||
|
subElementOptions: [
|
||||||
|
{
|
||||||
|
tag: "menuitem",
|
||||||
|
label: "Addon Template",
|
||||||
|
oncommand: "alert('Hello World! Sub Menuitem.')",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"before",
|
||||||
|
this._Addon.Zotero.getMainWindow().document.querySelector(
|
||||||
|
"#zotero-itemmenu-addontemplate-test"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this._Addon.Utils.UI.insertMenuItem("menuFile", {
|
||||||
|
tag: "menuseparator",
|
||||||
|
});
|
||||||
|
// menu->File menuitem
|
||||||
|
this._Addon.Utils.UI.insertMenuItem("menuFile", {
|
||||||
|
tag: "menuitem",
|
||||||
|
label: "Addon Template: File Menuitem",
|
||||||
|
oncommand: "alert('Hello World! File Menuitem.')",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public unInitViews(_Zotero) {
|
public initPrefs() {
|
||||||
console.log("Uninitializing UI");
|
const Zotero = this._Addon.Zotero;
|
||||||
const _window: Window = _Zotero.getMainWindow();
|
this._Addon.Utils.Tool.log(this._Addon.rootURI);
|
||||||
_window.document
|
const prefOptions = {
|
||||||
.querySelector("#zotero-itemmenu-addontemplate-test")
|
pluginID: addonID,
|
||||||
?.remove();
|
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(
|
public showProgressWindow(
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src",
|
"src",
|
||||||
|
108
typing/global.d.ts
vendored
Normal file
108
typing/global.d.ts
vendored
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
declare interface ZoteroCompat {
|
||||||
|
getZotero: () => _ZoteroConstructable;
|
||||||
|
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 {
|
||||||
|
getCopyHelper: () => CopyHelper;
|
||||||
|
openFilePicker: (
|
||||||
|
title: string,
|
||||||
|
mode: "open" | "save" | "folder",
|
||||||
|
filters?: [string, string][],
|
||||||
|
suggestion?: string
|
||||||
|
) => Promise<string>;
|
||||||
|
log: (...data: any[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface ZoteroUI {
|
||||||
|
addonElements: Element[];
|
||||||
|
createElement: (
|
||||||
|
doc: Document,
|
||||||
|
tagName: string,
|
||||||
|
namespace: "html" | "svg" | "xul"
|
||||||
|
) => XUL.Element | DocumentFragment | HTMLElement | SVGAElement;
|
||||||
|
removeAddonElements: () => void;
|
||||||
|
creatElementsFromJSON: (
|
||||||
|
doc: Document,
|
||||||
|
options: ElementOptions
|
||||||
|
) => XUL.Element | DocumentFragment | HTMLElement | SVGAElement;
|
||||||
|
defaultMenuPopupSelectors: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
insertMenuItem: (
|
||||||
|
menuPopup: XUL.Menupopup | string,
|
||||||
|
options: MenuitemOptions,
|
||||||
|
insertPosition?: "before" | "after",
|
||||||
|
anchorElement?: XUL.Element
|
||||||
|
) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface ElementOptions {
|
||||||
|
tag: string;
|
||||||
|
id?: string;
|
||||||
|
namespace?: "html" | "svg" | "xul";
|
||||||
|
styles?: { [key: string]: string };
|
||||||
|
directAttributes?: { [key: string]: string | boolean | number };
|
||||||
|
attributes?: { [key: string]: string | boolean | number };
|
||||||
|
listeners?: Array<
|
||||||
|
| [
|
||||||
|
string,
|
||||||
|
EventListenerOrEventListenerObject,
|
||||||
|
boolean | AddEventListenerOptions
|
||||||
|
]
|
||||||
|
| [string, EventListenerOrEventListenerObject]
|
||||||
|
>;
|
||||||
|
checkExistanceParent?: HTMLElement;
|
||||||
|
ignoreIfExists?: boolean;
|
||||||
|
removeIfExists?: boolean;
|
||||||
|
customCheck?: () => boolean;
|
||||||
|
subElementOptions?: Array<ElementOptions>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare interface MenuitemOptions {
|
||||||
|
tag: "menuitem" | "menu" | "menuseparator";
|
||||||
|
id?: string;
|
||||||
|
label?: string;
|
||||||
|
// data url (chrome://xxx.png) or base64 url (data:image/png;base64,xxx)
|
||||||
|
icon?: string;
|
||||||
|
class?: string;
|
||||||
|
styles?: { [key: string]: string };
|
||||||
|
hidden?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
oncommand?: string;
|
||||||
|
commandListener?: EventListenerOrEventListenerObject;
|
||||||
|
// Attributes below are used when type === "menu"
|
||||||
|
popupId?: string;
|
||||||
|
onpopupshowing?: string;
|
||||||
|
subElementOptions?: Array<MenuitemOptions>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
copy: () => void;
|
||||||
|
}
|
26
update-template.json
Normal file
26
update-template.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
update-template.rdf
Normal file
30
update-template.rdf
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||||
|
<rdf:Description rdf:about="urn:mozilla:extension:__addonID__">
|
||||||
|
<em:updates>
|
||||||
|
<rdf:Seq>
|
||||||
|
<rdf:li>
|
||||||
|
<rdf:Description>
|
||||||
|
<em:version>__buildVersion__</em:version>
|
||||||
|
<em:targetApplication>
|
||||||
|
<rdf:Description>
|
||||||
|
<em:id>zotero@chnm.gmu.edu</em:id>
|
||||||
|
<em:minVersion>6.999</em:minVersion>
|
||||||
|
<em:maxVersion>*</em:maxVersion>
|
||||||
|
<em:updateLink>__releasepage__</em:updateLink>
|
||||||
|
</rdf:Description>
|
||||||
|
</em:targetApplication>
|
||||||
|
<em:targetApplication>
|
||||||
|
<rdf:Description>
|
||||||
|
<em:id>juris-m@juris-m.github.io</em:id>
|
||||||
|
<em:minVersion>6.999</em:minVersion>
|
||||||
|
<em:maxVersion>*</em:maxVersion>
|
||||||
|
<em:updateLink>__releasepage__</em:updateLink>
|
||||||
|
</rdf:Description>
|
||||||
|
</em:targetApplication>
|
||||||
|
</rdf:Description>
|
||||||
|
</rdf:li>
|
||||||
|
</rdf:Seq>
|
||||||
|
</em:updates>
|
||||||
|
</rdf:Description>
|
||||||
|
</rdf:RDF>
|
26
update.json
Normal file
26
update.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
update.rdf
10
update.rdf
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
|
||||||
<rdf:Description rdf:about="urn:mozilla:extension:plugintemplate@euclpts.com">
|
<rdf:Description rdf:about="urn:mozilla:extension:addontemplate@euclpts.com">
|
||||||
<em:updates>
|
<em:updates>
|
||||||
<rdf:Seq>
|
<rdf:Seq>
|
||||||
<rdf:li>
|
<rdf:li>
|
||||||
@ -9,17 +9,17 @@
|
|||||||
<em:targetApplication>
|
<em:targetApplication>
|
||||||
<rdf:Description>
|
<rdf:Description>
|
||||||
<em:id>zotero@chnm.gmu.edu</em:id>
|
<em:id>zotero@chnm.gmu.edu</em:id>
|
||||||
<em:minVersion>5.0</em:minVersion>
|
<em:minVersion>6.999</em:minVersion>
|
||||||
<em:maxVersion>*</em:maxVersion>
|
<em:maxVersion>*</em:maxVersion>
|
||||||
<em:updateLink>__releasepage__</em:updateLink>
|
<em:updateLink>https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi</em:updateLink>
|
||||||
</rdf:Description>
|
</rdf:Description>
|
||||||
</em:targetApplication>
|
</em:targetApplication>
|
||||||
<em:targetApplication>
|
<em:targetApplication>
|
||||||
<rdf:Description>
|
<rdf:Description>
|
||||||
<em:id>juris-m@juris-m.github.io</em:id>
|
<em:id>juris-m@juris-m.github.io</em:id>
|
||||||
<em:minVersion>5.0</em:minVersion>
|
<em:minVersion>6.999</em:minVersion>
|
||||||
<em:maxVersion>*</em:maxVersion>
|
<em:maxVersion>*</em:maxVersion>
|
||||||
<em:updateLink>__releasepage__</em:updateLink>
|
<em:updateLink>https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi</em:updateLink>
|
||||||
</rdf:Description>
|
</rdf:Description>
|
||||||
</em:targetApplication>
|
</em:targetApplication>
|
||||||
</rdf:Description>
|
</rdf:Description>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user