Merge branch 'bootstrap-zo7' into bootstrap
This commit is contained in:
		
						commit
						2cd31e414e
					
				
							
								
								
									
										135
									
								
								addon/bootstrap.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										135
									
								
								addon/bootstrap.js
									
									
									
									
										vendored
									
									
								
							@ -3,69 +3,118 @@
 | 
			
		||||
 * 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/. */
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
/* global Components, Services */
 | 
			
		||||
/* global addon, APP_SHUTDOWN */
 | 
			
		||||
const { classes: Cc, utils: Cu } = Components;
 | 
			
		||||
 | 
			
		||||
Cu.import("resource://gre/modules/Services.jsm");
 | 
			
		||||
 | 
			
		||||
function install(data, reason) {}
 | 
			
		||||
 | 
			
		||||
function startup(data, reason) {
 | 
			
		||||
  // Load the addon to Zotero if window is ready
 | 
			
		||||
  const loadAddon = (window) => {
 | 
			
		||||
    console.log(window);
 | 
			
		||||
    if (window.document.readyState === "complete" && window.Zotero) {
 | 
			
		||||
      Services.scriptloader.loadSubScript(
 | 
			
		||||
        "chrome://__addonRef__/content/scripts/index.js"
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      window.addEventListener("load", (e) => {
 | 
			
		||||
        if (window.Zotero) {
 | 
			
		||||
          Services.scriptloader.loadSubScript(
 | 
			
		||||
            "chrome://__addonRef__/content/scripts/index.js"
 | 
			
		||||
          );
 | 
			
		||||
if (typeof Zotero == "undefined") {
 | 
			
		||||
  var Zotero;
 | 
			
		||||
}
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // Listen to windows
 | 
			
		||||
  var WindowListener = {
 | 
			
		||||
    onOpenWindow: function (xulWindow) {
 | 
			
		||||
      loadAddon(
 | 
			
		||||
        xulWindow
 | 
			
		||||
          .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
 | 
			
		||||
          .getInterface(Components.interfaces.nsIDOMWindow)
 | 
			
		||||
var chromeHandle;
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
// Zotero window to open and get the Zotero object from there.
 | 
			
		||||
//
 | 
			
		||||
// In Zotero 7, bootstrap methods are not called until Zotero is initialized, and the 'Zotero' is
 | 
			
		||||
// automatically made available.
 | 
			
		||||
async function waitForZotero() {
 | 
			
		||||
  if (typeof Zotero != "undefined") {
 | 
			
		||||
    await Zotero.initializationPromise;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | 
			
		||||
  var windows = Services.wm.getEnumerator("navigator:browser");
 | 
			
		||||
  var found = false;
 | 
			
		||||
  while (windows.hasMoreElements()) {
 | 
			
		||||
    let win = windows.getNext();
 | 
			
		||||
    if (win.Zotero) {
 | 
			
		||||
      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
 | 
			
		||||
  const windows = Services.wm.getEnumerator("navigator:browser");
 | 
			
		||||
  while (windows.hasMoreElements()) {
 | 
			
		||||
    loadAddon(
 | 
			
		||||
      windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow)
 | 
			
		||||
function install(data, reason) {}
 | 
			
		||||
 | 
			
		||||
async function startup({ id, version, resourceURI, rootURI }, reason) {
 | 
			
		||||
  await waitForZotero();
 | 
			
		||||
 | 
			
		||||
  // 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) {
 | 
			
		||||
    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
 | 
			
		||||
    ).wrappedJSObject;
 | 
			
		||||
  _Zotero.AddonTemplate.events.onUnInit(_Zotero);
 | 
			
		||||
  }
 | 
			
		||||
  Zotero.AddonTemplate.events.onUnInit(Zotero);
 | 
			
		||||
 | 
			
		||||
  Cc["@mozilla.org/intl/stringbundle;1"]
 | 
			
		||||
    .getService(Components.interfaces.nsIStringBundleService)
 | 
			
		||||
    .flushBundles();
 | 
			
		||||
 | 
			
		||||
  Cu.unload("chrome://_addonRef__/scripts/index.js");
 | 
			
		||||
  Cu.unload(`${rootURI}/chrome/content/scripts/index.js`);
 | 
			
		||||
 | 
			
		||||
  chromeHandle.destruct();
 | 
			
		||||
  chromeHandle = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function uninstall(data, reason) {}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
content __addonRef__ chrome/content/
 | 
			
		||||
skin __addonRef__ default	chrome/skin/default/__addonRef__/
 | 
			
		||||
locale __addonRef__ en-US chrome/locale/en-US/
 | 
			
		||||
locale __addonRef__ zh-CN chrome/locale/zh-CN/
 | 
			
		||||
 | 
			
		||||
| 
		 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__.pref.enable.label "Enable">
 | 
			
		||||
<!ENTITY zotero.__addonRef__.pref.input.label "Input">
 | 
			
		||||
 | 
			
		||||
<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ VERSION __buildVersion__">
 | 
			
		||||
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
<!ENTITY zotero.__addonRef__.itemmenu.test.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.releasetime.label "Build __buildTime__">
 | 
			
		||||
@ -12,7 +12,7 @@
 | 
			
		||||
        em:creator="__author__"
 | 
			
		||||
        em:description="__description__"
 | 
			
		||||
        em:homepageURL="__homepage__"
 | 
			
		||||
        em:iconURL="chrome://__addonRef__/skin/favicon.png"
 | 
			
		||||
        em:iconURL="chrome://__addonRef__/content/icons/favicon.png"
 | 
			
		||||
        em:optionsURL="chrome://__addonRef__/content/preferences.xul"
 | 
			
		||||
        em:updateURL="__updaterdf__"
 | 
			
		||||
        em:multiprocessCompatible="true"
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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);
 | 
			
		||||
 | 
			
		||||
  copyFileSync("update-template.json", "update.json");
 | 
			
		||||
  copyFileSync("update-template.rdf", "update.rdf");
 | 
			
		||||
 | 
			
		||||
  await esbuild
 | 
			
		||||
    .build({
 | 
			
		||||
      entryPoints: ["src/index.ts"],
 | 
			
		||||
@ -113,9 +116,13 @@ async function main() {
 | 
			
		||||
      path.join(buildDir, "**/*.rdf"),
 | 
			
		||||
      path.join(buildDir, "**/*.dtd"),
 | 
			
		||||
      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/chrome.manifest"),
 | 
			
		||||
      path.join(buildDir, "addon/manifest.json"),
 | 
			
		||||
      path.join(buildDir, "addon/bootstrap.js"),
 | 
			
		||||
      "update.json",
 | 
			
		||||
      "update.rdf",
 | 
			
		||||
    ],
 | 
			
		||||
    from: [
 | 
			
		||||
@ -129,7 +136,6 @@ async function main() {
 | 
			
		||||
      /__addonRef__/g,
 | 
			
		||||
      /__buildVersion__/g,
 | 
			
		||||
      /__buildTime__/g,
 | 
			
		||||
      /<em:version>\S*<\/em:version>/g,
 | 
			
		||||
    ],
 | 
			
		||||
    to: [
 | 
			
		||||
      author,
 | 
			
		||||
@ -142,7 +148,6 @@ async function main() {
 | 
			
		||||
      addonRef,
 | 
			
		||||
      version,
 | 
			
		||||
      buildTime,
 | 
			
		||||
      `<em:version>${version}</em:version>`,
 | 
			
		||||
    ],
 | 
			
		||||
    countMatches: true,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@ -26,14 +26,15 @@
 | 
			
		||||
  },
 | 
			
		||||
  "homepage": "https://github.com/windingwind/zotero-addon-template#readme",
 | 
			
		||||
  "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": {
 | 
			
		||||
    "compressing": "^1.5.1",
 | 
			
		||||
    "esbuild": "^0.15.16",
 | 
			
		||||
    "replace-in-file": "^6.3.2"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "release-it": "^15.5.0",
 | 
			
		||||
    "zotero-types": "^0.0.6"
 | 
			
		||||
    "@types/node": "^18.7.20",
 | 
			
		||||
    "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 AddonPrefs from "./prefs";
 | 
			
		||||
import AddonUtils from "./utils";
 | 
			
		||||
import AddonViews from "./views";
 | 
			
		||||
 | 
			
		||||
const { addonName } = require("../package.json");
 | 
			
		||||
 | 
			
		||||
class Addon {
 | 
			
		||||
  public Zotero: _ZoteroConstructable;
 | 
			
		||||
  public events: AddonEvents;
 | 
			
		||||
  public views: AddonViews;
 | 
			
		||||
  public prefs: AddonPrefs;
 | 
			
		||||
  public Utils: AddonUtils;
 | 
			
		||||
  // root path to access the resources
 | 
			
		||||
  public rootURI: string;
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.events = new AddonEvents(this);
 | 
			
		||||
    this.views = new AddonViews(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 { addonName } from "../package.json";
 | 
			
		||||
 | 
			
		||||
class AddonEvents extends AddonModule {
 | 
			
		||||
  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
 | 
			
		||||
    console.log(`${addonName}: init called`);
 | 
			
		||||
    _Zotero.debug(`${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
 | 
			
		||||
    let notifierID = _Zotero.Notifier.registerObserver(this.notifierCallback, [
 | 
			
		||||
    let notifierID = Zotero.Notifier.registerObserver(this.notifierCallback, [
 | 
			
		||||
      "tab",
 | 
			
		||||
      "item",
 | 
			
		||||
      "file",
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    // Unregister callback when the window closes (important to avoid a memory leak)
 | 
			
		||||
    _Zotero.getMainWindow().addEventListener(
 | 
			
		||||
    Zotero.getMainWindow().addEventListener(
 | 
			
		||||
      "unload",
 | 
			
		||||
      function (e) {
 | 
			
		||||
        _Zotero.Notifier.unregisterObserver(notifierID);
 | 
			
		||||
        Zotero.Notifier.unregisterObserver(notifierID);
 | 
			
		||||
      },
 | 
			
		||||
      false
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    this._Addon.views.initViews(_Zotero);
 | 
			
		||||
    this._Addon.views.initViews();
 | 
			
		||||
    this._Addon.views.initPrefs();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private resetState(): void {
 | 
			
		||||
@ -68,13 +71,13 @@ class AddonEvents extends AddonModule {
 | 
			
		||||
    // }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public onUnInit(_Zotero): void {
 | 
			
		||||
    console.log(`${addonName}: uninit called`);
 | 
			
		||||
    _Zotero.debug(`${addonName}: uninit called`);
 | 
			
		||||
  public onUnInit(): void {
 | 
			
		||||
    const Zotero = this._Addon.Zotero;
 | 
			
		||||
    this._Addon.Utils.Tool.log(`${addonName}: uninit called`);
 | 
			
		||||
    //  Remove elements and do clean up
 | 
			
		||||
    this._Addon.views.unInitViews(_Zotero);
 | 
			
		||||
    this._Addon.views.unInitViews();
 | 
			
		||||
    // 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(
 | 
			
		||||
  Components.interfaces.nsISupports
 | 
			
		||||
).wrappedJSObject;
 | 
			
		||||
if (!_Zotero.AddonTemplate) {
 | 
			
		||||
  _Zotero.AddonTemplate = new Addon();
 | 
			
		||||
  _Zotero.AddonTemplate.events.onInit(_Zotero);
 | 
			
		||||
if (!Zotero.AddonTemplate) {
 | 
			
		||||
  Zotero.AddonTemplate = new Addon();
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  Zotero.AddonTemplate.events.onInit(Zotero, rootURI);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
import Addon from "./addon";
 | 
			
		||||
 | 
			
		||||
class AddonModule {
 | 
			
		||||
  protected _Addon: any;
 | 
			
		||||
  constructor(parent: any) {
 | 
			
		||||
  protected _Addon: Addon;
 | 
			
		||||
  constructor(parent: Addon) {
 | 
			
		||||
    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 { addonName, addonRef } from "../package.json";
 | 
			
		||||
 | 
			
		||||
class AddonPrefs extends AddonModule {
 | 
			
		||||
  private _window: Window;
 | 
			
		||||
@ -10,15 +11,36 @@ class AddonPrefs extends AddonModule {
 | 
			
		||||
    // This function is called when the prefs window is opened
 | 
			
		||||
    // See addon/chrome/content/preferences.xul onpaneload
 | 
			
		||||
    this._window = _window;
 | 
			
		||||
    Zotero.debug(`${addonName}: init preferences`);
 | 
			
		||||
    this._Addon.Utils.Tool.log(`${addonName}: init preferences`);
 | 
			
		||||
    this.updatePrefsUI();
 | 
			
		||||
    this.bindPrefEvents();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private updatePrefsUI() {
 | 
			
		||||
    // You can initialize some UI elements on prefs window
 | 
			
		||||
    // with this._window.document
 | 
			
		||||
    // Or bind some events to the elements
 | 
			
		||||
    Zotero.debug(`${addonName}: init preferences UI`);
 | 
			
		||||
    this._Addon.Utils.Tool.log(`${addonName}: init preferences UI`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private bindPrefEvents() {
 | 
			
		||||
    this._window.document
 | 
			
		||||
      .querySelector(`#zotero-prefpane-${addonRef}-enable`)
 | 
			
		||||
      ?.addEventListener("command", (e) => {
 | 
			
		||||
        this._Addon.Utils.Tool.log(e);
 | 
			
		||||
        this._window.alert(
 | 
			
		||||
          `Successfully changed to ${(e.target as XUL.Checkbox).checked}!`
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    this._window.document
 | 
			
		||||
      .querySelector(`#zotero-prefpane-${addonRef}-input`)
 | 
			
		||||
      ?.addEventListener("change", (e) => {
 | 
			
		||||
        this._Addon.Utils.Tool.log(e);
 | 
			
		||||
        this._window.alert(
 | 
			
		||||
          `Successfully changed to ${(e.target as HTMLInputElement).value}!`
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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";
 | 
			
		||||
const { addonRef } = require("../package.json");
 | 
			
		||||
const { addonRef, addonID } = require("../package.json");
 | 
			
		||||
 | 
			
		||||
class AddonViews extends AddonModule {
 | 
			
		||||
  // You can store some element in the object attributes
 | 
			
		||||
  private testButton: XUL.Button;
 | 
			
		||||
  private progressWindowIcon: object;
 | 
			
		||||
 | 
			
		||||
  constructor(parent: Addon) {
 | 
			
		||||
@ -12,31 +11,83 @@ class AddonViews extends AddonModule {
 | 
			
		||||
    this.progressWindowIcon = {
 | 
			
		||||
      success: "chrome://zotero/skin/tick.png",
 | 
			
		||||
      fail: "chrome://zotero/skin/cross.png",
 | 
			
		||||
      default: `chrome://${addonRef}/skin/favicon.png`,
 | 
			
		||||
      default: `chrome://${addonRef}/content/icons/favicon.png`,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public initViews(_Zotero) {
 | 
			
		||||
  public initViews() {
 | 
			
		||||
    const Zotero = this._Addon.Zotero;
 | 
			
		||||
    // You can init the UI elements that
 | 
			
		||||
    // cannot be initialized with overlay.xul
 | 
			
		||||
    console.log("Initializing UI");
 | 
			
		||||
    const _window: Window = _Zotero.getMainWindow();
 | 
			
		||||
    const menuitem = _window.document.createElement("menuitem");
 | 
			
		||||
    menuitem.id = "zotero-itemmenu-addontemplate-test";
 | 
			
		||||
    menuitem.setAttribute("label", "Addon Template");
 | 
			
		||||
    menuitem.setAttribute("oncommand", "alert('Hello World!')");
 | 
			
		||||
    menuitem.className = "menuitem-iconic";
 | 
			
		||||
    menuitem.style["list-style-image"] =
 | 
			
		||||
      "url('chrome://addontemplate/skin/favicon@0.5x.png')";
 | 
			
		||||
    _window.document.querySelector("#zotero-itemmenu").appendChild(menuitem);
 | 
			
		||||
    this._Addon.Utils.Tool.log("Initializing UI");
 | 
			
		||||
    const menuIcon =
 | 
			
		||||
      'url("chrome://addontemplate/content/icons/favicon@0.5x.png")';
 | 
			
		||||
    // item menuitem with icon
 | 
			
		||||
    this._Addon.Utils.UI.insertMenuItem("item", {
 | 
			
		||||
      tag: "menuitem",
 | 
			
		||||
      id: "zotero-itemmenu-addontemplate-test",
 | 
			
		||||
      label: "Addon Template: Menuitem",
 | 
			
		||||
      oncommand: "alert('Hello World! Default 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) {
 | 
			
		||||
    console.log("Uninitializing UI");
 | 
			
		||||
    const _window: Window = _Zotero.getMainWindow();
 | 
			
		||||
    _window.document
 | 
			
		||||
      .querySelector("#zotero-itemmenu-addontemplate-test")
 | 
			
		||||
      ?.remove();
 | 
			
		||||
  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(
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "module": "commonjs",
 | 
			
		||||
    "target": "es6",
 | 
			
		||||
    "resolveJsonModule": true
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "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"?>
 | 
			
		||||
<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>
 | 
			
		||||
      <rdf:Seq>
 | 
			
		||||
        <rdf:li>
 | 
			
		||||
@ -9,17 +9,17 @@
 | 
			
		||||
            <em:targetApplication>
 | 
			
		||||
              <rdf:Description>
 | 
			
		||||
                <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:updateLink>__releasepage__</em:updateLink>
 | 
			
		||||
                <em:updateLink>https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi</em:updateLink>
 | 
			
		||||
              </rdf:Description>
 | 
			
		||||
            </em:targetApplication>
 | 
			
		||||
            <em:targetApplication>
 | 
			
		||||
              <rdf:Description>
 | 
			
		||||
                <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:updateLink>__releasepage__</em:updateLink>
 | 
			
		||||
                <em:updateLink>https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi</em:updateLink>
 | 
			
		||||
              </rdf:Description>
 | 
			
		||||
            </em:targetApplication>
 | 
			
		||||
          </rdf:Description>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user