change: use zotero-plugin-toolkit
change: use strict ts mode change: dependencies
This commit is contained in:
		
							parent
							
								
									c3db612775
								
							
						
					
					
						commit
						969a5274c6
					
				
							
								
								
									
										115
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								README.md
									
									
									
									
									
								
							@ -16,7 +16,7 @@ This is an addon/plugin template for [Zotero](https://www.zotero.org/).
 | 
				
			|||||||
- Release to GitHub automatically(using [release-it](https://github.com/release-it/release-it));
 | 
					- Release to GitHub automatically(using [release-it](https://github.com/release-it/release-it));
 | 
				
			||||||
- Extensive skeleton;
 | 
					- Extensive skeleton;
 | 
				
			||||||
- Some sample code of UI and lifecycle.
 | 
					- Some sample code of UI and lifecycle.
 | 
				
			||||||
- ⭐Compatibilities for Zotero 6 & Zotero 7.
 | 
					- ⭐Compatibilities for Zotero 6 & Zotero 7.(using [zotero-plugin-toolkit](https://github.com/windingwind/zotero-plugin-toolkit))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Quick Start Guide
 | 
					## Quick Start Guide
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -26,6 +26,7 @@ This is an addon/plugin template for [Zotero](https://www.zotero.org/).
 | 
				
			|||||||
- Modify the settings in `./package.json`, including:
 | 
					- Modify the settings in `./package.json`, including:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					  version,
 | 
				
			||||||
  author,
 | 
					  author,
 | 
				
			||||||
  description,
 | 
					  description,
 | 
				
			||||||
  homepage,
 | 
					  homepage,
 | 
				
			||||||
@ -56,6 +57,8 @@ This is an addon/plugin template for [Zotero](https://www.zotero.org/).
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Examples
 | 
					### Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See https://github.com/windingwind/zotero-plugin-toolkit for more detailed API documentations.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Menu (file, edit, view, ...) & Right-click Menu (item, collection/library)
 | 
					#### Menu (file, edit, view, ...) & Right-click Menu (item, collection/library)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**File Menu**
 | 
					**File Menu**
 | 
				
			||||||
@ -70,61 +73,15 @@ https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062d
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L23-L51
 | 
					https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L23-L51
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`Utils.UI.insertMenuItem` resolved the input object and inject the menu items.
 | 
					`insertMenuItem` resolved the input object and inject the menu items.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Available types `menuFile`, `menuEdit`, ...:
 | 
					You can choose an anchor element and insert before/after it using `insertPosition` and `anchorElement`. Default the insert position is the end of the menu.
 | 
				
			||||||
 | 
					 | 
				
			||||||
```ts
 | 
					 | 
				
			||||||
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",
 | 
					 | 
				
			||||||
},
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
You can choose an anchor element and insert before/after it using `insertPosition` and `anchorElement`. Default the insert position is the end of menu.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```ts
 | 
					 | 
				
			||||||
insertMenuItem: (
 | 
					 | 
				
			||||||
  menuPopup: XUL.Menupopup | string,
 | 
					 | 
				
			||||||
  options: MenuitemOptions,
 | 
					 | 
				
			||||||
  insertPosition?: "before" | "after",
 | 
					 | 
				
			||||||
  anchorElement?: XUL.Element
 | 
					 | 
				
			||||||
) => boolean;
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Full options you can use:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```ts
 | 
					 | 
				
			||||||
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>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Preference, for both Zotero 6 and Zotero 7 (all in bootstrap)
 | 
					#### Preference, for both Zotero 6 and Zotero 7 (all in bootstrap)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Zotero 6 doesn't support preference pane injection in bootstrap mode, thus I write a register for Zotero 6 or lower.
 | 
					Zotero 6 doesn't support preference pane injection in bootstrap mode, thus I write a register for Zotero 6 or lower.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You only need to maintain one `preferences.xhtml` which runs natively on Zotero 7 and let the plugin template handle when it is running on Zotero 6.
 | 
					You only need to maintain one `preferences.xhtml` which runs natively on Zotero 7 and let the plugin template handle it when it is running on Zotero 6.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<table style="margin-left: auto; margin-right: auto;">
 | 
					<table style="margin-left: auto; margin-right: auto;">
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
@ -141,9 +98,9 @@ You only need to maintain one `preferences.xhtml` which runs natively on Zotero
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L63-L82
 | 
					https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L63-L82
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Call `Utils.Compat.registerPrefPane` when it's on Zotero 6.
 | 
					Call `registerPrefPane` when it's on Zotero 6.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Note that `<preferences>` element is deprecated. Please use the full pref-key in elements' `preference` attribute. Like:
 | 
					Note that `<preferences>` element is deprecated. Please use the full pref-key in the elements' `preference` attribute. Like:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```xml
 | 
					```xml
 | 
				
			||||||
<checkbox label="&zotero.__addonRef__.pref.enable.label;" preference="extensions.zotero.__addonRef__.enable"
 | 
					<checkbox label="&zotero.__addonRef__.pref.enable.label;" preference="extensions.zotero.__addonRef__.enable"
 | 
				
			||||||
@ -152,7 +109,7 @@ Note that `<preferences>` element is deprecated. Please use the full pref-key in
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
The elements with `preference` attributes will bind to Zotero preferences.
 | 
					The elements with `preference` attributes will bind to Zotero preferences.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Remember to call `Utils.Compat.unregisterPrefPane()` on plugin unload.
 | 
					Remember to call `unregisterPrefPane()` on plugin unload.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L88-L90
 | 
					https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062db51cf16e99dda35288/src/views.ts#L88-L90
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -160,55 +117,10 @@ https://github.com/windingwind/zotero-addon-template/blob/574ce88b9fd3535a9d062d
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
The plugin template provides new APIs for bootstrap plugins. We have two reasons to use these APIs, instead of the `createElement/createElementNS`:
 | 
					The plugin template provides new APIs for bootstrap plugins. We have two reasons to use these APIs, instead of the `createElement/createElementNS`:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- In bootstrap mode, plugins have to clean up all UI elements on exit (disable or uninstall), which is very annoying. Using the `Utils.UI.createElement`, the plugin template will maintain these elements. Just `Utils.UI.removeAddonElements` on exit.
 | 
					- In bootstrap mode, plugins have to clean up all UI elements on exit (disable or uninstall), which is very annoying. Using the `createElement`, the plugin template will maintain these elements. Just `removeAddonElements` on exit.
 | 
				
			||||||
- Zotero 7 requires createElement()/createElementNS() → createXULElement() for remaining XUL elements, while on Zotero 6 doesn't support `createXULElement`. Using `Utils.UI.createElement`, it switches API depending on the current platform automatically.
 | 
					- Zotero 7 requires createElement()/createElementNS() → createXULElement() for remaining XUL elements, while Zotero 6 doesn't support `createXULElement`. Using `createElement`, it switches API depending on the current platform automatically.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Definition:
 | 
					There are more advanced APIs for creating elements in batch: `creatElementsFromJSON`. Input an element tree in JSON and return a fragment/element. These elements are also maintained by this plugin template.
 | 
				
			||||||
 | 
					 | 
				
			||||||
```ts
 | 
					 | 
				
			||||||
function createElement (
 | 
					 | 
				
			||||||
  doc: Document,
 | 
					 | 
				
			||||||
  tagName: string,
 | 
					 | 
				
			||||||
  namespace: "html" | "svg" | "xul"
 | 
					 | 
				
			||||||
) => XUL.Element | DocumentFragment | HTMLElement | SVGAElement;
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
There are more advanced APIs for creating elements in batch: `Utils.UI.creatElementsFromJSON`. Input an element tree in JSON and return a fragment/element. These elements are also maintained by this plugin template.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Definition:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```ts
 | 
					 | 
				
			||||||
function creatElementsFromJSON (
 | 
					 | 
				
			||||||
  doc: Document,
 | 
					 | 
				
			||||||
  options: ElementOptions
 | 
					 | 
				
			||||||
) => XUL.Element | DocumentFragment | HTMLElement | SVGAElement;
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Available options:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```ts
 | 
					 | 
				
			||||||
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>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Directory Structure
 | 
					### Directory Structure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -263,7 +175,6 @@ This section shows the directory structure of a template.
 | 
				
			|||||||
    │  module.ts    # module class
 | 
					    │  module.ts    # module class
 | 
				
			||||||
    │  addon.ts     # base class
 | 
					    │  addon.ts     # base class
 | 
				
			||||||
    │  events.ts    # events class
 | 
					    │  events.ts    # events class
 | 
				
			||||||
    │  utils.ts     # Utils class
 | 
					 | 
				
			||||||
    │  views.ts     # UI class
 | 
					    │  views.ts     # UI class
 | 
				
			||||||
    └─ prefs.ts     # preferences class
 | 
					    └─ prefs.ts     # preferences class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -28,12 +28,13 @@
 | 
				
			|||||||
  "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.json",
 | 
					  "updaterdf": "https://raw.githubusercontent.com/windingwind/zotero-addon-template/master/update.json",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "compressing": "^1.5.1",
 | 
					    "zotero-plugin-toolkit": "^0.0.1"
 | 
				
			||||||
    "esbuild": "^0.15.16",
 | 
					 | 
				
			||||||
    "replace-in-file": "^6.3.2"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@types/node": "^18.7.20",
 | 
					    "@types/node": "^18.7.20",
 | 
				
			||||||
 | 
					    "compressing": "^1.5.1",
 | 
				
			||||||
 | 
					    "esbuild": "^0.15.16",
 | 
				
			||||||
 | 
					    "replace-in-file": "^6.3.2",
 | 
				
			||||||
    "release-it": "^14.14.0",
 | 
					    "release-it": "^14.14.0",
 | 
				
			||||||
    "zotero-types": "^0.0.8"
 | 
					    "zotero-types": "^0.0.8"
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										11
									
								
								src/addon.ts
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/addon.ts
									
									
									
									
									
								
							@ -1,22 +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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ZoteroToolkit from "zotero-plugin-toolkit";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Addon {
 | 
					class Addon {
 | 
				
			||||||
  public Zotero: _ZoteroConstructable;
 | 
					  public Zotero!: _ZoteroConstructable;
 | 
				
			||||||
  public events: AddonEvents;
 | 
					  public events: AddonEvents;
 | 
				
			||||||
  public views: AddonViews;
 | 
					  public views: AddonViews;
 | 
				
			||||||
  public prefs: AddonPrefs;
 | 
					  public prefs: AddonPrefs;
 | 
				
			||||||
  public Utils: AddonUtils;
 | 
					  public toolkit: ZoteroToolkit;
 | 
				
			||||||
  // root path to access the resources
 | 
					  // root path to access the resources
 | 
				
			||||||
  public rootURI: string;
 | 
					  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);
 | 
					    this.toolkit = new ZoteroToolkit();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@ class AddonEvents extends AddonModule {
 | 
				
			|||||||
        event: string,
 | 
					        event: string,
 | 
				
			||||||
        type: string,
 | 
					        type: string,
 | 
				
			||||||
        ids: Array<string>,
 | 
					        ids: Array<string>,
 | 
				
			||||||
        extraData: object
 | 
					        extraData: { [key: string]: any }
 | 
				
			||||||
      ) => {
 | 
					      ) => {
 | 
				
			||||||
        // You can add your code to the corresponding notify type
 | 
					        // You can add your code to the corresponding notify type
 | 
				
			||||||
        if (
 | 
					        if (
 | 
				
			||||||
@ -33,7 +33,7 @@ class AddonEvents extends AddonModule {
 | 
				
			|||||||
    // @ts-ignore
 | 
					    // @ts-ignore
 | 
				
			||||||
    this._Addon.rootURI = rootURI;
 | 
					    this._Addon.rootURI = rootURI;
 | 
				
			||||||
    // This function is the setup code of the addon
 | 
					    // This function is the setup code of the addon
 | 
				
			||||||
    this._Addon.Utils.Tool.log(`${addonName}: init called`);
 | 
					    this._Addon.toolkit.Tool.log(`${addonName}: init called`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 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, [
 | 
				
			||||||
@ -45,7 +45,7 @@ class AddonEvents extends AddonModule {
 | 
				
			|||||||
    // 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: Event) {
 | 
				
			||||||
        Zotero.Notifier.unregisterObserver(notifierID);
 | 
					        Zotero.Notifier.unregisterObserver(notifierID);
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      false
 | 
					      false
 | 
				
			||||||
@ -57,7 +57,7 @@ class AddonEvents extends AddonModule {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public initPrefs() {
 | 
					  public initPrefs() {
 | 
				
			||||||
    this._Addon.Utils.Tool.log(this._Addon.rootURI);
 | 
					    this._Addon.toolkit.Tool.log(this._Addon.rootURI);
 | 
				
			||||||
    const prefOptions = {
 | 
					    const prefOptions = {
 | 
				
			||||||
      pluginID: addonID,
 | 
					      pluginID: addonID,
 | 
				
			||||||
      src: this._Addon.rootURI + "chrome/content/preferences.xhtml",
 | 
					      src: this._Addon.rootURI + "chrome/content/preferences.xhtml",
 | 
				
			||||||
@ -69,22 +69,22 @@ class AddonEvents extends AddonModule {
 | 
				
			|||||||
        this._Addon.prefs.initPreferences(win);
 | 
					        this._Addon.prefs.initPreferences(win);
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    if (this._Addon.Utils.Compat.isZotero7()) {
 | 
					    if (this._Addon.toolkit.Compat.isZotero7()) {
 | 
				
			||||||
      Zotero.PreferencePanes.register(prefOptions);
 | 
					      Zotero.PreferencePanes.register(prefOptions);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      this._Addon.Utils.Compat.registerPrefPane(prefOptions);
 | 
					      this._Addon.toolkit.Compat.registerPrefPane(prefOptions);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private unInitPrefs() {
 | 
					  private unInitPrefs() {
 | 
				
			||||||
    if (!this._Addon.Utils.Compat.isZotero7()) {
 | 
					    if (!this._Addon.toolkit.Compat.isZotero7()) {
 | 
				
			||||||
      this._Addon.Utils.Compat.unregisterPrefPane();
 | 
					      this._Addon.toolkit.Compat.unregisterPrefPane();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public onUnInit(): void {
 | 
					  public onUnInit(): void {
 | 
				
			||||||
    const Zotero = this._Addon.Zotero;
 | 
					    const Zotero = this._Addon.Zotero;
 | 
				
			||||||
    this._Addon.Utils.Tool.log(`${addonName}: uninit called`);
 | 
					    this._Addon.toolkit.Tool.log(`${addonName}: uninit called`);
 | 
				
			||||||
    this.unInitPrefs();
 | 
					    this.unInitPrefs();
 | 
				
			||||||
    //  Remove elements and do clean up
 | 
					    //  Remove elements and do clean up
 | 
				
			||||||
    this._Addon.views.unInitViews();
 | 
					    this._Addon.views.unInitViews();
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								src/prefs.ts
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/prefs.ts
									
									
									
									
									
								
							@ -3,7 +3,7 @@ import AddonModule from "./module";
 | 
				
			|||||||
import { addonName, addonRef } from "../package.json";
 | 
					import { addonName, addonRef } from "../package.json";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AddonPrefs extends AddonModule {
 | 
					class AddonPrefs extends AddonModule {
 | 
				
			||||||
  private _window: Window;
 | 
					  private _window!: Window;
 | 
				
			||||||
  constructor(parent: Addon) {
 | 
					  constructor(parent: Addon) {
 | 
				
			||||||
    super(parent);
 | 
					    super(parent);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -11,7 +11,7 @@ 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;
 | 
				
			||||||
    this._Addon.Utils.Tool.log(`${addonName}: init preferences`);
 | 
					    this._Addon.toolkit.Tool.log(`${addonName}: init preferences`);
 | 
				
			||||||
    this.updatePrefsUI();
 | 
					    this.updatePrefsUI();
 | 
				
			||||||
    this.bindPrefEvents();
 | 
					    this.bindPrefEvents();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -20,14 +20,14 @@ class AddonPrefs extends AddonModule {
 | 
				
			|||||||
    // 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
 | 
				
			||||||
    this._Addon.Utils.Tool.log(`${addonName}: init preferences UI`);
 | 
					    this._Addon.toolkit.Tool.log(`${addonName}: init preferences UI`);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private bindPrefEvents() {
 | 
					  private bindPrefEvents() {
 | 
				
			||||||
    this._window.document
 | 
					    this._window.document
 | 
				
			||||||
      .querySelector(`#zotero-prefpane-${addonRef}-enable`)
 | 
					      .querySelector(`#zotero-prefpane-${addonRef}-enable`)
 | 
				
			||||||
      ?.addEventListener("command", (e) => {
 | 
					      ?.addEventListener("command", (e) => {
 | 
				
			||||||
        this._Addon.Utils.Tool.log(e);
 | 
					        this._Addon.toolkit.Tool.log(e);
 | 
				
			||||||
        this._window.alert(
 | 
					        this._window.alert(
 | 
				
			||||||
          `Successfully changed to ${(e.target as XUL.Checkbox).checked}!`
 | 
					          `Successfully changed to ${(e.target as XUL.Checkbox).checked}!`
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@ -36,7 +36,7 @@ class AddonPrefs extends AddonModule {
 | 
				
			|||||||
    this._window.document
 | 
					    this._window.document
 | 
				
			||||||
      .querySelector(`#zotero-prefpane-${addonRef}-input`)
 | 
					      .querySelector(`#zotero-prefpane-${addonRef}-input`)
 | 
				
			||||||
      ?.addEventListener("change", (e) => {
 | 
					      ?.addEventListener("change", (e) => {
 | 
				
			||||||
        this._Addon.Utils.Tool.log(e);
 | 
					        this._Addon.toolkit.Tool.log(e);
 | 
				
			||||||
        this._window.alert(
 | 
					        this._window.alert(
 | 
				
			||||||
          `Successfully changed to ${(e.target as HTMLInputElement).value}!`
 | 
					          `Successfully changed to ${(e.target as HTMLInputElement).value}!`
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										619
									
								
								src/utils.ts
									
									
									
									
									
								
							
							
						
						
									
										619
									
								
								src/utils.ts
									
									
									
									
									
								
							@ -1,619 +0,0 @@
 | 
				
			|||||||
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;
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      getWindow: () => {
 | 
					 | 
				
			||||||
        return this.Compat.getZotero().getMainWindow() as Window;
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      // Check if it's running on Zotero 7 (Firefox 102)
 | 
					 | 
				
			||||||
      isZotero7: () => Zotero.platformMajorVersion >= 102,
 | 
					 | 
				
			||||||
      // Firefox 102 support DOMParser natively
 | 
					 | 
				
			||||||
      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);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      isXULElement: (elem: Element) => {
 | 
					 | 
				
			||||||
        return (
 | 
					 | 
				
			||||||
          elem.namespaceURI ===
 | 
					 | 
				
			||||||
          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      // create XUL element
 | 
					 | 
				
			||||||
      createXULElement: (doc: Document, type: string) => {
 | 
					 | 
				
			||||||
        if (this.Compat.isZotero7()) {
 | 
					 | 
				
			||||||
          // @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 _initImportedNodesPostInsert = (container) => {
 | 
					 | 
				
			||||||
          const _observerSymbols = new Map();
 | 
					 | 
				
			||||||
          const Zotero = this.Compat.getZotero();
 | 
					 | 
				
			||||||
          const window = container.ownerGlobal;
 | 
					 | 
				
			||||||
          let useChecked = (elem) =>
 | 
					 | 
				
			||||||
            (elem instanceof window.HTMLInputElement &&
 | 
					 | 
				
			||||||
              elem.type == "checkbox") ||
 | 
					 | 
				
			||||||
            elem.tagName == "checkbox";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          let syncFromPref = (elem, preference) => {
 | 
					 | 
				
			||||||
            let value = Zotero.Prefs.get(preference, true);
 | 
					 | 
				
			||||||
            if (useChecked(elem)) {
 | 
					 | 
				
			||||||
              elem.checked = value;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
              elem.value = value;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            elem.dispatchEvent(new window.Event("syncfrompreference"));
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          // We use a single listener function shared between all elements so we can easily detach it later
 | 
					 | 
				
			||||||
          let syncToPrefOnModify = (event) => {
 | 
					 | 
				
			||||||
            if (event.currentTarget.getAttribute("preference")) {
 | 
					 | 
				
			||||||
              let value = useChecked(event.currentTarget)
 | 
					 | 
				
			||||||
                ? event.currentTarget.checked
 | 
					 | 
				
			||||||
                : event.currentTarget.value;
 | 
					 | 
				
			||||||
              Zotero.Prefs.set(
 | 
					 | 
				
			||||||
                event.currentTarget.getAttribute("preference"),
 | 
					 | 
				
			||||||
                value,
 | 
					 | 
				
			||||||
                true
 | 
					 | 
				
			||||||
              );
 | 
					 | 
				
			||||||
              event.currentTarget.dispatchEvent(
 | 
					 | 
				
			||||||
                new window.Event("synctopreference")
 | 
					 | 
				
			||||||
              );
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          let attachToPreference = (elem, preference) => {
 | 
					 | 
				
			||||||
            Zotero.debug(
 | 
					 | 
				
			||||||
              `Attaching <${elem.tagName}> element to ${preference}`
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            // @ts-ignore
 | 
					 | 
				
			||||||
            let symbol = Zotero.Prefs.registerObserver(
 | 
					 | 
				
			||||||
              preference,
 | 
					 | 
				
			||||||
              () => syncFromPref(elem, preference),
 | 
					 | 
				
			||||||
              true
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            _observerSymbols.set(elem, symbol);
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          let detachFromPreference = (elem) => {
 | 
					 | 
				
			||||||
            if (_observerSymbols.has(elem)) {
 | 
					 | 
				
			||||||
              Zotero.debug(
 | 
					 | 
				
			||||||
                `Detaching <${elem.tagName}> element from preference`
 | 
					 | 
				
			||||||
              );
 | 
					 | 
				
			||||||
              // @ts-ignore
 | 
					 | 
				
			||||||
              Zotero.Prefs.unregisterObserver(this._observerSymbols.get(elem));
 | 
					 | 
				
			||||||
              _observerSymbols.delete(elem);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          // Activate `preference` attributes
 | 
					 | 
				
			||||||
          for (let elem of container.querySelectorAll("[preference]")) {
 | 
					 | 
				
			||||||
            let preference = elem.getAttribute("preference");
 | 
					 | 
				
			||||||
            if (
 | 
					 | 
				
			||||||
              container.querySelector("preferences > preference#" + preference)
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
              Zotero.warn(
 | 
					 | 
				
			||||||
                "<preference> is deprecated -- `preference` attribute values " +
 | 
					 | 
				
			||||||
                  "should be full preference keys, not <preference> IDs"
 | 
					 | 
				
			||||||
              );
 | 
					 | 
				
			||||||
              preference = container
 | 
					 | 
				
			||||||
                .querySelector("preferences > preference#" + preference)
 | 
					 | 
				
			||||||
                .getAttribute("name");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            attachToPreference(elem, preference);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            elem.addEventListener(
 | 
					 | 
				
			||||||
              this.Compat.isXULElement(elem) ? "command" : "input",
 | 
					 | 
				
			||||||
              syncToPrefOnModify
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Set timeout before populating the value so the pane can add listeners first
 | 
					 | 
				
			||||||
            window.setTimeout(() => {
 | 
					 | 
				
			||||||
              syncFromPref(elem, preference);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          new window.MutationObserver((mutations) => {
 | 
					 | 
				
			||||||
            for (let mutation of mutations) {
 | 
					 | 
				
			||||||
              if (mutation.type == "attributes") {
 | 
					 | 
				
			||||||
                let target = mutation.target as Element;
 | 
					 | 
				
			||||||
                detachFromPreference(target);
 | 
					 | 
				
			||||||
                if (target.hasAttribute("preference")) {
 | 
					 | 
				
			||||||
                  attachToPreference(target, target.getAttribute("preference"));
 | 
					 | 
				
			||||||
                  target.addEventListener(
 | 
					 | 
				
			||||||
                    this.Compat.isXULElement(target) ? "command" : "input",
 | 
					 | 
				
			||||||
                    syncToPrefOnModify
 | 
					 | 
				
			||||||
                  );
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              } else if (mutation.type == "childList") {
 | 
					 | 
				
			||||||
                for (let node of mutation.removedNodes) {
 | 
					 | 
				
			||||||
                  detachFromPreference(node);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                for (let node of mutation.addedNodes) {
 | 
					 | 
				
			||||||
                  if (
 | 
					 | 
				
			||||||
                    node.nodeType == Node.ELEMENT_NODE &&
 | 
					 | 
				
			||||||
                    (node as Element).hasAttribute("preference")
 | 
					 | 
				
			||||||
                  ) {
 | 
					 | 
				
			||||||
                    attachToPreference(
 | 
					 | 
				
			||||||
                      node,
 | 
					 | 
				
			||||||
                      (node as Element).getAttribute("preference")
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                    node.addEventListener(
 | 
					 | 
				
			||||||
                      this.Compat.isXULElement(node as Element)
 | 
					 | 
				
			||||||
                        ? "command"
 | 
					 | 
				
			||||||
                        : "input",
 | 
					 | 
				
			||||||
                      syncToPrefOnModify
 | 
					 | 
				
			||||||
                    );
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }).observe(container, {
 | 
					 | 
				
			||||||
            childList: true,
 | 
					 | 
				
			||||||
            subtree: true,
 | 
					 | 
				
			||||||
            attributeFilter: ["preference"],
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          // parseXULToFragment() doesn't convert oncommand attributes into actual
 | 
					 | 
				
			||||||
          // listeners, so we'll do it here
 | 
					 | 
				
			||||||
          for (let elem of container.querySelectorAll("[oncommand]")) {
 | 
					 | 
				
			||||||
            elem.oncommand = elem.getAttribute("oncommand");
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          for (let child of container.children) {
 | 
					 | 
				
			||||||
            child.dispatchEvent(new window.Event("load"));
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        const windowListener = {
 | 
					 | 
				
			||||||
          onOpenWindow: (xulWindow) => {
 | 
					 | 
				
			||||||
            const win: Window = xulWindow
 | 
					 | 
				
			||||||
              .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-${Zotero.Utilities.randomString()}-${new Date().getTime()}`);
 | 
					 | 
				
			||||||
                  const contenrOrXHR = await Zotero.File.getContentsAsync(
 | 
					 | 
				
			||||||
                    options.src
 | 
					 | 
				
			||||||
                  );
 | 
					 | 
				
			||||||
                  const content =
 | 
					 | 
				
			||||||
                    typeof contenrOrXHR === "string"
 | 
					 | 
				
			||||||
                      ? contenrOrXHR
 | 
					 | 
				
			||||||
                      : (contenrOrXHR as any as XMLHttpRequest).response;
 | 
					 | 
				
			||||||
                  const src = `<prefpane xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="${
 | 
					 | 
				
			||||||
                    options.id
 | 
					 | 
				
			||||||
                  }" insertafter="zotero-prefpane-advanced" label="${
 | 
					 | 
				
			||||||
                    options.label || options.pluginID
 | 
					 | 
				
			||||||
                  }" image="${options.image || ""}">
 | 
					 | 
				
			||||||
                  ${content}
 | 
					 | 
				
			||||||
                  </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);
 | 
					 | 
				
			||||||
                  // Binding preferences
 | 
					 | 
				
			||||||
                  _initImportedNodesPostInsert(prefPane);
 | 
					 | 
				
			||||||
                  if (options.onload) {
 | 
					 | 
				
			||||||
                    options.onload(win);
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              false
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        Services.wm.addListener(windowListener);
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      unregisterPrefPane: () => {
 | 
					 | 
				
			||||||
        this.Compat.prefPaneCache.listeners.forEach((l) => {
 | 
					 | 
				
			||||||
          Services.wm.removeListener(l);
 | 
					 | 
				
			||||||
          l.onOpenWindow = undefined;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        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;
 | 
					 | 
				
			||||||
							
								
								
									
										16
									
								
								src/views.ts
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								src/views.ts
									
									
									
									
									
								
							@ -4,7 +4,7 @@ const { addonRef } = require("../package.json");
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class AddonViews extends AddonModule {
 | 
					class AddonViews extends AddonModule {
 | 
				
			||||||
  // You can store some element in the object attributes
 | 
					  // You can store some element in the object attributes
 | 
				
			||||||
  private progressWindowIcon: object;
 | 
					  private progressWindowIcon: { [key: string]: string };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(parent: Addon) {
 | 
					  constructor(parent: Addon) {
 | 
				
			||||||
    super(parent);
 | 
					    super(parent);
 | 
				
			||||||
@ -18,10 +18,10 @@ class AddonViews extends AddonModule {
 | 
				
			|||||||
  public initViews() {
 | 
					  public initViews() {
 | 
				
			||||||
    // You can init the UI elements that
 | 
					    // You can init the UI elements that
 | 
				
			||||||
    // cannot be initialized with overlay.xul
 | 
					    // cannot be initialized with overlay.xul
 | 
				
			||||||
    this._Addon.Utils.Tool.log("Initializing UI");
 | 
					    this._Addon.toolkit.Tool.log("Initializing UI");
 | 
				
			||||||
    const menuIcon = "chrome://addontemplate/content/icons/favicon@0.5x.png";
 | 
					    const menuIcon = "chrome://addontemplate/content/icons/favicon@0.5x.png";
 | 
				
			||||||
    // item menuitem with icon
 | 
					    // item menuitem with icon
 | 
				
			||||||
    this._Addon.Utils.UI.insertMenuItem("item", {
 | 
					    this._Addon.toolkit.UI.insertMenuItem("item", {
 | 
				
			||||||
      tag: "menuitem",
 | 
					      tag: "menuitem",
 | 
				
			||||||
      id: "zotero-itemmenu-addontemplate-test",
 | 
					      id: "zotero-itemmenu-addontemplate-test",
 | 
				
			||||||
      label: "Addon Template: Menuitem",
 | 
					      label: "Addon Template: Menuitem",
 | 
				
			||||||
@ -29,7 +29,7 @@ class AddonViews extends AddonModule {
 | 
				
			|||||||
      icon: menuIcon,
 | 
					      icon: menuIcon,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    // item menupopup with sub-menuitems
 | 
					    // item menupopup with sub-menuitems
 | 
				
			||||||
    this._Addon.Utils.UI.insertMenuItem(
 | 
					    this._Addon.toolkit.UI.insertMenuItem(
 | 
				
			||||||
      "item",
 | 
					      "item",
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        tag: "menu",
 | 
					        tag: "menu",
 | 
				
			||||||
@ -47,11 +47,11 @@ class AddonViews extends AddonModule {
 | 
				
			|||||||
        "#zotero-itemmenu-addontemplate-test"
 | 
					        "#zotero-itemmenu-addontemplate-test"
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    this._Addon.Utils.UI.insertMenuItem("menuFile", {
 | 
					    this._Addon.toolkit.UI.insertMenuItem("menuFile", {
 | 
				
			||||||
      tag: "menuseparator",
 | 
					      tag: "menuseparator",
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    // menu->File menuitem
 | 
					    // menu->File menuitem
 | 
				
			||||||
    this._Addon.Utils.UI.insertMenuItem("menuFile", {
 | 
					    this._Addon.toolkit.UI.insertMenuItem("menuFile", {
 | 
				
			||||||
      tag: "menuitem",
 | 
					      tag: "menuitem",
 | 
				
			||||||
      label: "Addon Template: File Menuitem",
 | 
					      label: "Addon Template: File Menuitem",
 | 
				
			||||||
      oncommand: "alert('Hello World! File Menuitem.')",
 | 
					      oncommand: "alert('Hello World! File Menuitem.')",
 | 
				
			||||||
@ -59,8 +59,8 @@ class AddonViews extends AddonModule {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public unInitViews() {
 | 
					  public unInitViews() {
 | 
				
			||||||
    this._Addon.Utils.Tool.log("Uninitializing UI");
 | 
					    this._Addon.toolkit.Tool.log("Uninitializing UI");
 | 
				
			||||||
    this._Addon.Utils.UI.removeAddonElements();
 | 
					    this._Addon.toolkit.UI.removeAddonElements();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public showProgressWindow(
 | 
					  public showProgressWindow(
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,9 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "compilerOptions": {
 | 
					  "compilerOptions": {
 | 
				
			||||||
    "module": "commonjs",
 | 
					    "module": "commonjs",
 | 
				
			||||||
    "target": "es6",
 | 
					    "target": "ES2016",
 | 
				
			||||||
    "resolveJsonModule": true
 | 
					    "resolveJsonModule": true,
 | 
				
			||||||
 | 
					    "strict": true
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "include": [
 | 
					  "include": [
 | 
				
			||||||
    "src",
 | 
					    "src",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										110
									
								
								typing/global.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										110
									
								
								typing/global.d.ts
									
									
									
									
										vendored
									
									
								
							@ -1,110 +0,0 @@
 | 
				
			|||||||
declare interface ZoteroCompat {
 | 
					 | 
				
			||||||
  getZotero: () => _ZoteroConstructable;
 | 
					 | 
				
			||||||
  getWindow: () => Window;
 | 
					 | 
				
			||||||
  isZotero7: () => boolean;
 | 
					 | 
				
			||||||
  getDOMParser: () => DOMParser;
 | 
					 | 
				
			||||||
  isXULElement: (elem: Element) => boolean;
 | 
					 | 
				
			||||||
  createXULElement: (doc: Document, type: string) => XUL.Element;
 | 
					 | 
				
			||||||
  parseXHTMLToFragment: (
 | 
					 | 
				
			||||||
    str: string,
 | 
					 | 
				
			||||||
    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;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user