zotero-plugin-toolkit
Version:
Toolkit for Zotero plugins
1,611 lines (1,610 loc) • 68.4 kB
TypeScript
import * as React from "react";
//#region src/typings/basic.d.ts
declare type FunctionNamesOf<T> = keyof FunctionsOf<T>;
declare type FunctionsOf<T> = { [K in keyof T as T[K] extends Function ? K : never]: T[K] };
//#endregion
//#region src/basic.d.ts
/**
* Basic APIs with Zotero 6 & newer (7) compatibility.
* See also https://www.zotero.org/support/dev/zotero_7_for_developers
*/
declare class BasicTool {
/**
* configurations.
*/
protected _basicOptions: BasicOptions;
protected _console?: Console;
/**
* @deprecated Use `patcherManager` instead.
*/
protected readonly patchSign: string;
static _version: string;
/**
* Get version - checks subclass first, then falls back to parent
*/
get _version(): string;
get basicOptions(): BasicOptions;
/**
*
* @param data Pass an BasicTool instance to copy its options.
*/
constructor(data?: BasicTool | BasicOptions);
/**
* @alpha
* @param k
*/
getGlobal(k: "Zotero" | "zotero"): _ZoteroTypes.Zotero;
/**
* @alpha
* @param k
*/
getGlobal(k: "ZoteroPane" | "ZoteroPane_Local"): _ZoteroTypes.ZoteroPane;
/**
* @alpha
* @param k
*/
getGlobal(k: "Zotero_Tabs"): _ZoteroTypes.Zotero_Tabs;
/**
* @alpha
* @param k
*/
getGlobal(k: "Zotero_File_Interface"): any;
/**
* @alpha
* @param k
*/
getGlobal(k: "Zotero_File_Exporter"): any;
/**
* @alpha
* @param k
*/
getGlobal(k: "Zotero_LocateMenu"): any;
/**
* @alpha
* @param k
*/
getGlobal(k: "Zotero_Report_Interface"): any;
/**
* @alpha
* @param k
*/
getGlobal(k: "Zotero_Timeline_Interface"): any;
/**
* @alpha
* @param k
*/
getGlobal(k: "Zotero_Tooltip"): any;
/**
* @alpha
* @param k
*/
getGlobal(k: "ZoteroContextPane"): _ZoteroTypes.ZoteroContextPane;
/**
* @alpha
* @param k
*/
getGlobal(k: "ZoteroItemPane"): any;
/**
* @alpha
* @param k
*/
getGlobal<K extends keyof typeof globalThis, GLOBAL extends typeof globalThis>(k: K): GLOBAL[K];
/**
* Get global variables.
* @param k Global variable name, `Zotero`, `ZoteroPane`, `window`, `document`, etc.
*/
getGlobal(k: string): any;
/**
* If it's an XUL element
* @param elem
*/
isXULElement(elem: Element): boolean;
/**
* Create an XUL element
*
* For Zotero 6, use `createElementNS`;
*
* For Zotero 7+, use `createXULElement`.
* @param doc
* @param type
* @example
* Create a `<menuitem>`:
* ```ts
* const compat = new ZoteroCompat();
* const doc = compat.getWindow().document;
* const elem = compat.createXULElement(doc, "menuitem");
* ```
*/
createXULElement(doc: Document, type: string): XULElement;
/**
* Output to both Zotero.debug and console.log
* @param data e.g. string, number, object, ...
*/
log(...data: any): void;
/**
* Patch a function
* @deprecated Use {@link PatchHelper} instead.
* @param object The owner of the function
* @param funcSign The signature of the function(function name)
* @param ownerSign The signature of patch owner to avoid patching again
* @param patcher The new wrapper of the patched function
*/
patch<T, K extends FunctionNamesOf<T>>(object: T, funcSign: K, ownerSign: string, patcher: (fn: T[K]) => T[K]): void;
/**
* Add a Zotero event listener callback
* @param type Event type
* @param callback Event callback
*/
addListenerCallback<T extends keyof ListenerCallbackMap>(type: T, callback: ListenerCallbackMap[T]): void;
/**
* Remove a Zotero event listener callback
* @param type Event type
* @param callback Event callback
*/
removeListenerCallback<T extends keyof ListenerCallbackMap>(type: T, callback: ListenerCallbackMap[T]): void;
/**
* Remove all Zotero event listener callbacks when the last callback is removed.
*/
protected _ensureRemoveListener(): void;
/**
* Ensure the main window listener is registered.
*/
protected _ensureMainWindowListener(): void;
/**
* Ensure the plugin listener is registered.
*/
protected _ensurePluginListener(): void;
updateOptions(source?: BasicTool | BasicOptions): this;
static getZotero(): _ZoteroTypes.Zotero;
}
interface BasicOptions {
log: {
readonly _type: "toolkitlog";
disableConsole: boolean;
disableZLog: boolean;
prefix: string;
};
debug: {
disableDebugBridgePassword: boolean;
password: string;
};
_debug?: BasicOptions["debug"];
api: {
pluginID: string;
};
listeners: {
_mainWindow?: any;
_plugin?: _ZoteroTypes.Plugins.observer;
callbacks: { [K in keyof ListenerCallbackMap]: Set<ListenerCallbackMap[K]> };
};
}
declare abstract class ManagerTool extends BasicTool {
abstract register(...data: any[]): any;
abstract unregister(...data: any[]): any;
/**
* Unregister everything
*/
abstract unregisterAll(): any;
protected _ensureAutoUnregisterAll(): void;
}
declare function unregister(tools: {
[key: string | number]: any;
}): void;
declare function makeHelperTool<T extends typeof HelperTool>(cls: T, options: BasicTool | BasicOptions): T;
declare function makeHelperTool<T>(cls: T, options: BasicTool | BasicOptions): T;
declare function _importESModule(path: string): any;
declare interface ListenerCallbackMap {
onMainWindowLoad: (win: Window) => void;
onMainWindowUnload: (win: Window) => void;
onPluginUnload: (...args: Parameters<NonNullable<_ZoteroTypes.Plugins.observer["shutdown"]>>) => void;
}
declare class HelperTool {
constructor(...args: any);
updateOptions: BasicTool["updateOptions"];
}
//#endregion
//#region src/helpers/clipboard.d.ts
/**
* Copy helper for text/richtext/image.
*
* @example
* Copy plain text
* ```ts
* new ClipboardHelper().addText("plain", "text/unicode").copy();
* ```
* @example
* Copy plain text & rich text
* ```ts
* new ClipboardHelper().addText("plain", "text/unicode")
* .addText("<h1>rich text</h1>", "text/html")
* .copy();
* ```
* @example
* Copy plain text, rich text & image
* ```ts
* new ClipboardHelper().addText("plain", "text/unicode")
* .addText("<h1>rich text</h1>", "text/html")
* .addImage("data:image/png;base64,...")
* .copy();
* ```
*/
declare class ClipboardHelper extends BasicTool {
private transferable;
private clipboardService;
private filePath;
constructor();
addText(source: string, type?: "text/html" | "text/plain" | "text/unicode"): this;
addImage(source: string): this;
addFile(path: string): this;
copy(): this;
}
//#endregion
//#region src/tools/ui.d.ts
/**
* UI APIs. Create elements and manage them.
*/
declare class UITool extends BasicTool {
/**
* UITool options
*/
protected _basicOptions: UIOptions;
get basicOptions(): UIOptions;
/**
* Store elements created with this instance
*
* @remarks
* > What is this for?
*
* In bootstrap plugins, elements must be manually maintained and removed on exiting.
*
* This API does this for you.
*/
protected elementCache: WeakRef<Element>[];
constructor(base?: BasicTool | BasicOptions);
/**
* Remove all elements created by `createElement`.
*
* @remarks
* > What is this for?
*
* In bootstrap plugins, elements must be manually maintained and removed on exiting.
*
* This API does this for you.
*/
unregisterAll(): void;
/**
* Create `DocumentFragment`.
* @param doc
* @param tagName
* @param props
* @example
* ```ts
* const frag: DocumentFragment = ui.createElement(
* document, "fragment",
* {
* children:
* [
* { tag: "h1", properties: { innerText: "Hello World!" } }
* ]
* }
* );
* ```
*/
createElement(doc: Document, tagName: "fragment", props?: FragmentElementProps): DocumentFragment;
/**
* Create `HTMLElement`.
* @param doc
* @param tagName
* @param props See {@link ElementProps }
* @example
* ```ts
* const div: HTMLDivElement = ui.createElement(document, "div");
* ```
* @example
* Attributes(for `elem.setAttribute()`), properties(for `elem.prop=`), listeners, and children.
* ```ts
* const div: HTMLDivElement = ui.createElement(
* document, "div",
* {
* id: "hi-div",
* skipIfExists: true,
* listeners:
* [
* { type: "click", listener: (e)=>ui.log("Clicked!") }
* ],
* children:
* [
* { tag: "h1", properties: { innerText: "Hello World!" } },
* { tag: "a", attributes: { href: "https://www.zotero.org" } },
* ]
* }
* );
* ```
*/
createElement<HTML_TAG extends keyof HTMLElementTagNameMap, T extends HTMLElementTagNameMap[HTML_TAG]>(doc: Document, tagName: HTML_TAG, props?: HTMLElementProps): T;
/**
* Create `XULElement`.
* @see ElementProps
* @param doc
* @param tagName
* @param props See {@link ElementProps }
* @example
* ```ts
* const menuitem: XULMenuItem = ui.createElement(document, "menuitem", { attributes: { label: "Click Me!" } });
* ```
*/
createElement<XUL_TAG extends keyof XULElementTagNameMap, T extends XULElementTagNameMap[XUL_TAG]>(doc: Document, tagName: XUL_TAG, props?: XULElementProps): T;
/**
* Create `SVGElement`
* @param doc
* @param tagName
* @param props See {@link ElementProps }
*/
createElement<SVG_TAG extends keyof SVGElementTagNameMap, T extends SVGElementTagNameMap[SVG_TAG]>(doc: Document, tagName: SVG_TAG, props?: SVGElementProps): T;
/**
* Create Element
* @param doc
* @param tagName
* @param props See {@link ElementProps }
*/
createElement(doc: Document, tagName: string, props?: ElementProps): HTMLElement | XULElement | SVGElement;
/**
* @deprecated
* @param doc target document, e.g. Zotero main window.document
* @param tagName element tag name, e.g. `hbox`, `div`
* @param namespace default "html"
* @param enableElementRecord If current element will be recorded and maintained by toolkit. If not set, use this.enableElementRecordGlobal
*/
createElement(doc: Document, tagName: string, namespace?: "html" | "svg" | "xul", enableElementRecord?: boolean): HTMLElement | XULElement | SVGElement | DocumentFragment;
/**
* Append element(s) to a node.
* @param properties See {@link ElementProps}
* @param container The parent node to append to.
* @returns A Node that is the appended child (aChild),
* except when aChild is a DocumentFragment,
* in which case the empty DocumentFragment is returned.
*/
appendElement(properties: TagElementProps, container: Element): Node;
/**
* Inserts a node before a reference node as a child of its parent node.
* @param properties See {@link ElementProps}
* @param referenceNode The node before which newNode is inserted.
* @returns Node
*/
insertElementBefore(properties: TagElementProps, referenceNode: Element): Node | undefined;
/**
* Replace oldNode with a new one.
* @param properties See {@link ElementProps}
* @param oldNode The child to be replaced.
* @returns The replaced Node. This is the same node as oldChild.
*/
replaceElement(properties: ElementProps & {
tag: string;
}, oldNode: Element): Node | undefined;
/**
* Parse XHTML to XUL fragment. For Zotero 6.
*
* To load preferences from a Zotero 7's `.xhtml`, use this method to parse it.
* @param str xhtml raw text
* @param entities dtd file list ("chrome://xxx.dtd")
* @param defaultXUL true for default XUL namespace
*/
parseXHTMLToFragment(str: string, entities?: string[], defaultXUL?: boolean): DocumentFragment;
}
interface UIOptions extends BasicOptions {
ui: {
/**
* Whether to record elements created with `createElement`.
*/
enableElementRecord: boolean;
/**
* Wether to log the `ElementProps` parameter in `createElement`.
*/
enableElementJSONLog: boolean;
/**
* Wether to log the DOM node mounted by `createElement`.
*/
enableElementDOMLog: boolean;
};
}
/**
* `props` of `UITool.createElement`. See {@link UITool}
*/
interface ElementProps {
/**
* tagName
*/
tag?: string;
/**
* id
*/
id?: string;
/**
* xul | html | svg
*/
namespace?: string;
/**
* classList
*/
classList?: Array<string>;
/**
* styles
*/
styles?: Partial<CSSStyleDeclaration>;
/**
* Set with `elem.prop =`
*/
properties?: {
[key: string]: unknown;
};
/**
* @deprecated Use `properties`
*/
directAttributes?: {
[key: string]: string | boolean | number | null | undefined;
};
/**
* Set with `elem.setAttribute()`
*/
attributes?: {
[key: string]: string | boolean | number | null | undefined;
};
/**
* Event listeners
*/
listeners?: Array<{
type: string;
listener: EventListenerOrEventListenerObject | ((e: Event) => void) | null | undefined;
options?: boolean | AddEventListenerOptions;
}>;
/**
* Child elements. Will be created and appended to this element.
*/
children?: Array<TagElementProps>;
/**
* Set true to check if the element exists using `id`. If exists, return this element and do not do anything.
*/
ignoreIfExists?: boolean;
/**
* Set true to check if the element exists using `id`. If exists, skip element creation and continue with props/attrs/children.
*/
skipIfExists?: boolean;
/**
* Set true to check if the element exists using `id`. If exists, remove and re-create it, then continue with props/attrs/children.
*/
removeIfExists?: boolean;
/**
* Existence check will be processed under this element, default `document`
*/
checkExistenceParent?: HTMLElement;
/**
* Custom check hook. If it returns false, return undefined and do not do anything.
* @param doc
* @param options
*/
customCheck?: (doc: Document, options: ElementProps) => boolean;
/**
* @deprecated Use `children`
*/
subElementOptions?: Array<TagElementProps>;
/**
* Enable elements to be recorded by the toolkit so it can be removed when calling `unregisterAll`.
*/
enableElementRecord?: boolean;
/**
* Enable elements to be printed to console & Zotero.debug.
*/
enableElementJSONLog?: boolean;
/**
* Enable elements to be printed to console & Zotero.debug.
*/
enableElementDOMLog?: boolean;
}
interface TagElementProps extends ElementProps {
tag: string;
}
interface HTMLElementProps extends Exclude<ElementProps, {
tag: any;
}> {
namespace?: "html";
}
interface SVGElementProps extends Exclude<ElementProps, {
tag: any;
}> {
namespace?: "svg";
}
interface XULElementProps extends Exclude<ElementProps, {
tag: any;
}> {
namespace?: "xul";
}
interface FragmentElementProps {
children?: Array<ElementProps>;
}
interface XULElementTagNameMap {
action: XULElement;
arrowscrollbox: XULElement;
bbox: XULBoxElement;
binding: XULElement;
bindings: XULElement;
box: XULElement;
broadcaster: XULElement;
broadcasterset: XULElement;
button: XULButtonElement;
browser: XULBrowserElement;
checkbox: XULCheckboxElement;
caption: XULElement;
colorpicker: XULColorPickerElement;
column: XULElement;
columns: XULElement;
commandset: XULElement;
command: XULCommandElement;
conditions: XULElement;
content: XULElement;
deck: XULDeckElement;
description: XULDescriptionElement;
dialog: XULElement;
dialogheader: XULElement;
editor: XULElement;
grid: XULElement;
grippy: XULGrippyElement;
groupbox: XULGroupBoxElement;
hbox: XULBoxElement;
iframe: XULElement;
image: XULElement;
key: XULElement;
keyset: XULElement;
label: XULLabelElement;
listbox: XULElement;
listcell: XULElement;
listcol: XULElement;
listcols: XULElement;
listhead: XULElement;
listheader: XULElement;
listitem: XULListItemElement;
member: XULElement;
menu: XULMenuElement;
menubar: XULMenuBarElement;
menuitem: XULMenuItemElement;
menulist: XULMenuListElement;
menupopup: XULMenuPopupElement;
menuseparator: XULMenuSeparatorElement;
observes: XULElement;
overlay: XULElement;
page: XULElement;
popup: XULPopupElement;
popupset: XULElement;
preference: XULElement;
preferences: XULElement;
prefpane: XULElement;
prefwindow: XULElement;
progressmeter: XULProgressMeterElement;
radio: XULRadioElement;
radiogroup: XULRadioGroupElement;
resizer: XULElement;
richlistbox: XULElement;
richlistitem: XULElement;
row: XULElement;
rows: XULElement;
rule: XULElement;
script: XULElement;
scrollbar: XULScrollBarElement;
scrollbox: XULElement;
scrollcorner: XULElement;
separator: XULSeparatorElement;
spacer: XULSpacerElement;
splitter: XULSplitterElement;
stack: XULElement;
statusbar: XULStatusBarElement;
statusbarpanel: XULStatusBarPanelElement;
stringbundle: XULElement;
stringbundleset: XULElement;
tab: XULTabElement;
tabbrowser: XULElement;
tabbox: XULTabBoxElement;
tabpanel: XULTabPanelElement;
tabpanels: XULTabPanelsElement;
tabs: XULTabsElement;
template: XULElement;
textnode: XULElement;
textbox: XULTextBoxElement;
titlebar: XULElement;
toolbar: XULToolBarElement;
toolbarbutton: XULToolBarButtonElement;
toolbargrippy: XULToolBarGrippyElement;
toolbaritem: XULToolBarItemElement;
toolbarpalette: XULToolBarPaletteElement;
toolbarseparator: XULToolBarSeparatorElement;
toolbarset: XULToolBarSetElement;
toolbarspacer: XULToolBarSpacerElement;
toolbarspring: XULToolBarSpringElement;
toolbox: XULToolBoxElement;
tooltip: XULTooltipElement;
tree: XULTreeElement;
treecell: XULTreeCellElement;
treechildren: XULTreeChildrenElement;
treecol: XULTreeColElement;
treecols: XULTreeColsElement;
treeitem: XULTreeItemElement;
treerow: XULTreeRowElement;
treeseparator: XULTreeSeparatorElement;
triple: XULElement;
vbox: XULBoxElement;
window: XULWindowElement;
wizard: XULElement;
wizardpage: XULElement;
}
//#endregion
//#region src/helpers/dialog.d.ts
/**
* Dialog window helper. A superset of XUL dialog.
*/
declare class DialogHelper extends UITool {
/**
* Passed to dialog window for data-binding and lifecycle controls. See {@link DialogHelper.setDialogData}
*/
dialogData: DialogData;
/**
* Dialog window instance
*/
window: Window;
protected elementProps: ElementProps & {
tag: string;
};
/**
* Create a dialog helper with row \* column grids.
* @param row
* @param column
*/
constructor(row: number, column: number);
/**
* Add a cell at (row, column). Index starts from 0.
* @param row
* @param column
* @param elementProps Cell element props. See {@link ElementProps}
* @param cellFlex If the cell is flex. Default true.
*/
addCell(row: number, column: number, elementProps: TagElementProps, cellFlex?: boolean): this;
/**
* Add a control button to the bottom of the dialog.
* @param label Button label
* @param id Button id.
* The corresponding id of the last button user clicks before window exit will be set to `dialogData._lastButtonId`.
* @param options Options
* @param [options.noClose] Don't close window when clicking this button.
* @param [options.callback] Callback of button click event.
*/
addButton(label: string, id?: string, options?: {
noClose?: boolean;
callback?: (ev: Event) => any;
}): this;
/**
* Dialog data.
* @remarks
* This object is passed to the dialog window.
*
* The control button id is in `dialogData._lastButtonId`;
*
* The data-binding values are in `dialogData`.
* ```ts
* interface DialogData {
* [key: string | number | symbol]: any;
* loadLock?: { promise: Promise<void>; resolve: () => void; isResolved: () => boolean }; // resolve after window load (auto-generated)
* loadCallback?: Function; // called after window load
* unloadLock?: { promise: Promise<void>; resolve: () => void }; // resolve after window unload (auto-generated)
* unloadCallback?: Function; // called after window unload
* beforeUnloadCallback?: Function; // called before window unload when elements are accessable.
* }
* ```
* @param dialogData
*/
setDialogData(dialogData: DialogData): this;
/**
* Open the dialog
* @param title Window title
* @param windowFeatures
* @param windowFeatures.width Ignored if fitContent is `true`.
* @param windowFeatures.height Ignored if fitContent is `true`.
* @param windowFeatures.left
* @param windowFeatures.top
* @param windowFeatures.centerscreen Open window at the center of screen.
* @param windowFeatures.resizable If window is resizable.
* @param windowFeatures.fitContent Resize the window to content size after elements are loaded.
* @param windowFeatures.noDialogMode Dialog mode window only has a close button. Set `true` to make maximize and minimize button visible.
* @param windowFeatures.alwaysRaised Is the window always at the top.
*/
open(title: string, windowFeatures?: {
width?: number;
height?: number;
left?: number;
top?: number;
centerscreen?: boolean;
resizable?: boolean;
fitContent?: boolean;
noDialogMode?: boolean;
alwaysRaised?: boolean;
}): this;
}
interface DialogData {
[key: string | number | symbol]: any;
loadLock?: {
promise: Promise<void>;
resolve: () => void;
isResolved: () => boolean;
};
loadCallback?: () => void;
unloadLock?: {
promise: Promise<void>;
resolve: () => void;
};
unloadCallback?: () => void;
beforeUnloadCallback?: () => void;
l10nFiles?: string | string[];
}
//#endregion
//#region src/helpers/filePicker.d.ts
/**
* File picker helper.
* @param title window title
* @param mode
* @param filters Array<[hint string, filter string]>
* @param suggestion default file/folder
* @param window the parent window. By default it is the main window
* @param filterMask built-in filters
* @param directory directory in which to open the file picker
* @example
* ```ts
* await new FilePickerHelper(
* `${Zotero.getString("fileInterface.import")} MarkDown Document`,
* "open",
* [["MarkDown File(*.md)", "*.md"]]
* ).open();
* ```
*/
declare class FilePickerHelper<MODE extends "open" | "save" | "folder" | "multiple"> extends BasicTool {
private title;
private mode;
private filters?;
private suggestion?;
private directory?;
private window?;
private filterMask?;
constructor(title: string, mode: MODE, filters?: [string, string][], suggestion?: string, window?: Window, filterMask?: "all" | "html" | "text" | "images" | "xml" | "apps" | "urls" | "audio" | "video", directory?: string);
open(): Promise<(MODE extends "multiple" ? string[] : string) | false>;
private getMode;
private getFilterMask;
}
//#endregion
//#region src/helpers/guide.d.ts
/**
* Helper for creating a guide.
* Designed for creating a step-by-step guide for users.
* @alpha
*/
declare class GuideHelper extends BasicTool {
_steps: GuideStep[];
constructor();
addStep(step: GuideStep): this;
addSteps(steps: GuideStep[]): this;
show(doc: Document): Promise<Guide>;
highlight(doc: Document, step: GuideStep): Promise<Guide>;
}
interface GuideStep {
element?: string | Element | (() => Element);
title?: string;
description?: string;
position?: "before_start" | "before_end" | "after_start" | "after_end" | "start_before" | "start_after" | "end_before" | "end_after" | "overlap" | "after_pointer" | "center";
showButtons?: ("next" | "prev" | "close")[];
showProgress?: boolean;
disableButtons?: ("next" | "prev" | "close")[];
progressText?: string;
closeBtnText?: string;
nextBtnText?: string;
prevBtnText?: string;
onBeforeRender?: GuideHook;
onRender?: GuideHook;
onExit?: GuideHook;
onNextClick?: GuideHook;
onPrevClick?: GuideHook;
onCloseClick?: GuideHook;
onMask?: (props: {
mask: (elem: Element) => void;
}) => void;
}
type GuideHook = ({
config,
state
}: {
config: GuideStep;
state: GuideState;
}) => any;
interface GuideState {
step: number;
steps: GuideStep[];
controller: Guide;
}
declare class Guide {
_window: Window;
_id: string;
_panel: XULPopupElement;
_header: HTMLDivElement;
_body: HTMLDivElement;
_footer: HTMLDivElement;
_progress: HTMLDivElement;
_closeButton: XUL.Button;
_prevButton: XUL.Button;
_nextButton: XUL.Button;
_steps?: GuideStep[];
_noClose: boolean;
_closed: boolean;
_autoNext: boolean;
_currentIndex: number;
initialized: boolean;
_cachedMasks: WeakRef<HTMLElement | SVGElement>[];
get content(): any;
get currentStep(): GuideStep | undefined;
get currentTarget(): Element | undefined;
get hasNext(): boolean | undefined;
get hasPrevious(): boolean | undefined;
get hookProps(): Parameters<GuideHook>[0];
get panel(): XULPopupElement;
constructor(win: Window);
show(steps?: GuideStep[]): Promise<void>;
hide(): void;
abort(): void;
moveTo(stepIndex: number): void;
moveNext(): void;
movePrevious(): void;
_handleShown(): void;
_handleHidden(): Promise<void>;
_centerPanel: () => void;
_createMask(targetElement?: Element): void;
_removeMask(): void;
}
//#endregion
//#region src/helpers/largePref.d.ts
/**
* Helper class for storing large amounts of data in Zotero preferences.
*
* @remarks
* The allowed data length for a single preference is at least 100k,
* but if this can grow infinitely, like an Array or an Object,
* there will be significant performance problems.
*
* This class stores the keys of data in a single preference as a JSON string of Array,
* and stores the values of data in separate preferences.
*
* You can either use the class as a normal object with `asObject()`,
* or use the methods to access the data.
*/
declare class LargePrefHelper extends BasicTool {
private keyPref;
private valuePrefPrefix;
private innerObj;
private hooks;
/**
*
* @param keyPref The preference name for storing the keys of the data.
* @param valuePrefPrefix The preference name prefix for storing the values of the data.
* @param hooks Hooks for parsing the values of the data.
* - `afterGetValue`: A function that takes the value of the data as input and returns the parsed value.
* - `beforeSetValue`: A function that takes the key and value of the data as input and returns the parsed key and value.
* If `hooks` is `"default"`, no parsing will be done.
* If `hooks` is `"parser"`, the values will be parsed as JSON.
* If `hooks` is an object, the values will be parsed by the hooks.
*/
constructor(keyPref: string, valuePrefPrefix: string, hooks?: Partial<typeof defaultHooks> | "default" | "parser");
/**
* Get the object that stores the data.
* @returns The object that stores the data.
*/
asObject(): ProxyObj;
/**
* Get the Map that stores the data.
* @returns The Map that stores the data.
*/
asMapLike(): Map<string, any>;
/**
* Get the keys of the data.
* @returns The keys of the data.
*/
getKeys(): string[];
/**
* Set the keys of the data.
* @param keys The keys of the data.
*/
setKeys(keys: string[]): void;
/**
* Get the value of a key.
* @param key The key of the data.
* @returns The value of the key.
*/
getValue(key: string): any;
/**
* Set the value of a key.
* @param key The key of the data.
* @param value The value of the key.
*/
setValue(key: string, value: any): void;
/**
* Check if a key exists.
* @param key The key of the data.
* @returns Whether the key exists.
*/
hasKey(key: string): boolean;
/**
* Add a key.
* @param key The key of the data.
*/
setKey(key: string): void;
/**
* Delete a key.
* @param key The key of the data.
*/
deleteKey(key: string): boolean;
private constructTempObj;
private constructTempMap;
}
type ProxyObj = Record<string | number, string | number | boolean>;
declare const defaultHooks: {
afterGetValue: ({
value
}: {
value: string;
}) => {
value: any;
};
beforeSetValue: ({
key,
value
}: {
key: string;
value: any;
}) => {
key: string;
value: any;
};
};
//#endregion
//#region src/helpers/message.d.ts
type MessageParams<T extends MessageHandlers> = { [K in keyof T]: T[K] extends ((...params: infer P) => void) ? P : never };
type MessageReturnType<T extends MessageHandlers> = { [K in keyof T]: T[K] extends ((...params: any) => infer R) ? R : never };
interface MessageHandlers {
[key: string]: (...data: any[]) => Promise<any> | any;
}
type PromisedMessageHandlers<T extends MessageHandlers> = { [K in keyof T]: (...data: Parameters<T[K]>) => Promise<Awaited<ReturnType<T[K]>>> };
interface MessageServerConfig {
name: string;
handlers: MessageHandlers;
target?: Window | Worker;
dev?: boolean;
canBeDestroyed?: boolean;
}
interface BuiltInMessageHandlers {
_start: () => Promise<void>;
_stop: () => Promise<void>;
_destroy: () => Promise<void>;
_ping: () => Promise<void>;
_call: (data: {
func: string;
args: any[];
}) => Promise<any>;
_get: (data: {
key: string;
}) => Promise<any>;
_set: (data: {
key: string;
value: any;
}) => Promise<void>;
}
/**
* Helper class to manage messages between workers/iframes and their parent
* @beta
*
* @example
* Use the `MessageHelper` to create a server that can be used to communicate between workers or iframes and their parent.
*
* In the child `worker.js`:
* ```typescript
* const handlers = {
* async test() {
* return "test";
* },
* };
* // Create a new server
* const server = new MessageHelper({
* name: "child",
* handlers,
* canBeDestroyed: true,
* });
* // Start the listener
* server.start();
* // Export the handlers for type hinting
* export { handlers };
* ```
* In the parent:
* ```typescript
* // Import the handlers
* import type { handlers } from "./worker.js";
* // Create a new worker
* const worker = new Worker("worker.js");
* // Create a new server with the type from the target handlers
* const server = new MessageHelper<typeof handlers>({
* name: "worker",
* handlers: {
* async test() {
* return "test";
* },
* },
* target: worker,
* });
* server.start();
* // Execute the handlers defined in the worker as if they were local
* ztoolkit.log(await server.proxy.test());
* // ...
* // Stop the server, can be restarted with server.start()
* server.stop();
* // Destroy the server and the worker
* server.destroy();
* ```
*
* @example
* Evaluate code in the other side of the server
* ```typescript
* await server.eval("self.firstName = 'John';");
* ```
*
* @example
* Get a property from the other side of the server, can be nested.
*
* Only works if the property is a primitive or a serializable object
* ```typescript
* ztoolkit.log(await server.get("self.firstName"));
* ```
*
* @example
* Set a property from the other side of the server, can be nested.
*
* Only works if the property is a primitive or a serializable object
* ```typescript
* await server.set("self.firstName", "Alice");
* ```
*
* @example
* Check if the target is alive
* ```typescript
* ztoolkit.log(await server.isTargetAlive());
* // Alternatively, send a ping message
* ztoolkit.log(await server.proxy._ping());
* ```
*/
declare class MessageHelper<_TargetHandlers extends MessageHandlers> {
protected config: Required<MessageServerConfig>;
protected env: "webworker" | "chromeworker" | "browser" | "content";
protected listener?: any;
running: boolean;
/**
* Proxy object to call the message handlers
*/
proxy: PromisedMessageHandlers<_TargetHandlers & BuiltInMessageHandlers>;
get target(): Window | Worker;
get privileged(): boolean;
constructor(config: MessageServerConfig);
start(): void;
stop(): void;
destroy(): void;
exec<_HandlersName extends keyof MessageParams<_HandlersType>, _HandlersType extends _TargetHandlers & BuiltInMessageHandlers>(name: _HandlersName, params?: MessageParams<_HandlersType>[_HandlersName], options?: {
timeout?: number;
}): Promise<Awaited<MessageReturnType<_HandlersType>[_HandlersName]>>;
call(func: string, args: any[]): Promise<MessageReturnType<_TargetHandlers & BuiltInMessageHandlers>["_call"]>;
get(key: string): Promise<MessageReturnType<_TargetHandlers & BuiltInMessageHandlers>["_get"]>;
set(key: string, value: any): Promise<MessageReturnType<_TargetHandlers & BuiltInMessageHandlers>["_set"]>;
eval(code: string): Promise<MessageReturnType<_TargetHandlers & BuiltInMessageHandlers>["_eval"]>;
send(options: {
name: string;
data: any;
jobID?: string;
success?: boolean;
requestReturn?: boolean;
}): Promise<string>;
isTargetAlive(): Promise<boolean>;
}
//#endregion
//#region src/helpers/patch.d.ts
declare class PatchHelper extends BasicTool {
private options?;
constructor();
setData<T, K extends FunctionNamesOf<T>>(options: PatchOptions<T, K>): this;
enable(): this;
disable(): this;
}
declare interface PatchOptions<T, K extends FunctionNamesOf<T>> {
target: T;
funcSign: K;
patcher: (origin: T[K]) => T[K];
enabled: boolean;
}
//#endregion
//#region src/helpers/progressWindow.d.ts
/**
* ProgressWindow helper.
* @example
* Show a popup with success icon
* ```ts
* const tool = new ZoteroTool();
* tool.createProgressWindow("Addon").createLine({
* type: "success",
* text: "Finish"
* progress: 100,
* }).show();
* ```
* @example
* Show a popup and change line content
* ```ts
* const compat = new ZoteroCompat();
* const tool = new ZoteroTool();
* const popupWin = tool.createProgressWindow("Addon").createLine({
* text: "Loading"
* progress: 50,
* }).show(-1);
* // Do operations
* compat.getGlobal("setTimeout")(()=>{
* popupWin.changeLine({
* text: "Finish",
* progress: 100,
* });
* }, 3000);
* ```
*/
declare class ProgressWindowHelper {
win: Zotero.ProgressWindow;
private lines;
private closeTime;
/**
*
* @param header window header
* @param options
* @param options.window
* @param options.closeOnClick
* @param options.closeTime
* @param options.closeOtherProgressWindows
*/
constructor(header: string, options?: {
window?: Window;
closeOnClick?: boolean;
closeTime?: number;
closeOtherProgressWindows?: boolean;
});
/**
* Create a new line
* @param options
* @param options.type
* @param options.icon
* @param options.text
* @param options.progress
* @param options.idx
*/
createLine(options: {
type?: string;
icon?: string;
text?: string;
progress?: number;
idx?: number;
}): this;
/**
* Change the line content
* @param options
* @param options.type
* @param options.icon
* @param options.text
* @param options.progress
* @param options.idx
*/
changeLine(options: {
type?: string;
icon?: string;
text?: string;
progress?: number;
idx?: number;
}): this;
show(closeTime?: number | undefined): this;
/**
* Set custom icon uri for progress window
* @param key
* @param uri
*/
static setIconURI(key: string, uri: string): void;
private getIcon;
private updateIcons;
changeHeadline(text: string, icon?: string, postText?: string): this;
addLines(labels: string | {
[key: string | number | symbol]: string;
}, icons: string | {
[key: string | number | symbol]: string;
}): this;
addDescription(text: string): this;
startCloseTimer(ms: number, requireMouseOver?: boolean): this;
close(): this;
}
//#endregion
//#region src/helpers/settingsDialog.d.ts
/**
* Settings dialog helper. Extends DialogHelper with setting management capabilities.
*/
declare class SettingsDialogHelper extends DialogHelper {
private settingsHandlers;
private autoSaveButtonIds;
private settingBindings;
/**
* Create a settings dialog helper.
* Uses a 2-column grid layout by default (label column + control column)
*/
constructor();
/**
* Set the setting handlers for getting and setting values.
* @param getSetting Function to get a setting value by key
* @param setSetting Function to set a setting value by key
*/
setSettingHandlers(getSetting: (key: string) => any, setSetting: (key: string, value: any) => void): this;
/**
* Add a setting row with label and form control.
* @param label Label text for the setting
* @param settingKey The key used to store/retrieve the setting
* @param controlProps Properties for the form control element
* @param options Additional options
* @param options.valueType Type of the setting value for proper conversion
* @param options.labelProps Properties for the label element
* @param options.condition Optional condition function to determine if the setting should be added
* (returns true to add, false to skip)
* @returns The SettingsDialogHelper instance for chaining
*/
addSetting(label: string, settingKey: string, controlProps: TagElementProps, options?: {
valueType?: "string" | "number" | "boolean";
labelProps?: Partial<TagElementProps>;
condition?: () => boolean;
}): this;
/**
* Add a static row (label + static element) to the settings grid. This is not a form control.
* @param label Label text for the row
* @param staticElementProps Properties for the static element (e.g., text, icon, etc.)
* @param options Additional options
* @param options.labelProps Properties for the label element
* @param options.condition Optional condition function to determine if the row should be added
* @returns The SettingsDialogHelper instance for chaining
*/
addStaticRow(label: string, staticElementProps: TagElementProps, options?: {
labelProps?: Partial<TagElementProps>;
condition?: () => boolean;
}): this;
/**
* Add a control button that will auto-save settings when clicked.
* @param label Button label
* @param id Button id
* @param options Button options
* @param options.noClose Don't close window when clicking this button
* @param options.validate Validation function for settings data
* @param options.callback Callback of button click event
*/
addAutoSaveButton(label: string, id?: string, options?: {
noClose?: boolean;
validate?: (data: any) => Promise<true | string> | true | string;
callback?: (ev: Event) => any;
}): this;
/**
* Save all settings using the setting handlers.
*/
saveAllSettings(): void;
/**
* Collect and return all current setting values from the dialog controls.
*/
getAllSettingsData(): Record<string, any>;
/**
* Load all settings from the setting handlers.
*/
loadAllSettings(): void;
/**
* Override the open method to handle setting loading after window opens.
*/
open(title: string, windowFeatures?: {
width?: number;
height?: number;
left?: number;
top?: number;
centerscreen?: boolean;
resizable?: boolean;
fitContent?: boolean;
noDialogMode?: boolean;
alwaysRaised?: boolean;
}): this;
/**
* Set control value based on element type and value type.
*/
private setControlValue;
/**
* Set control value on an actual DOM element.
*/
private setControlValueOnElement;
/**
* Get control value from a DOM element with proper type conversion.
*/
private getControlValue;
}
//#endregion
//#region src/helpers/virtualizedTable.d.ts
/**
* VirtualizedTable helper.
*/
declare class VirtualizedTableHelper extends BasicTool {
props: VirtualizedTableProps;
localeStrings: {
[name: string]: string;
};
containerId: string;
treeInstance: VirtualizedTable;
private window;
private React;
private ReactDOM;
private VirtualizedTable;
private IntlProvider;
constructor(win: Window);
/**
* Set properties by name.
* @remarks
* `id` and `getRowCount` are required.
* If `id` is not set, it's a random string.
* @param propName Property name
* @param propValue Property value
*/
setProp<K extends keyof VirtualizedTableProps, V extends VirtualizedTableProps[K]>(propName: K, propValue: V): VirtualizedTableHelper;
/**
* Set properties object.
* @remarks
* `id` and `getRowCount` are required.
* If `id` is not set, it's a random string.
* @param data property object.
* @remarks
* All available properties:
* ```ts
* interface VirtualizedTableProps {
* id: string;
* getRowCount: () => number;
* getRowData?: (index: number) => { [dataKey: string]: string };
* // Use `getRowData` instead. This property is generated automatically.
* renderItem?: (
* index: number,
* selection: TreeSelection,
* oldElem: HTMLElement,
* columns: ColumnOptions[]
* ) => Node;
* // Row height specified as lines of text per row. Defaults to 1
* linesPerRow?: number;
* // Do not adjust for Zotero-defined font scaling
* disableFontSizeScaling?: boolean;
* // An array of two elements for alternating row colors
* alternatingRowColors?: Array<string>;
* // For screen-readers
* label?: string;
* role?: string;
* showHeader?: boolean;
* // Array of column objects like the ones in itemTreeColumns.js
* columns?: Array<ColumnOptions>;
* onColumnPickerMenu?: (event: Event) => void;
* onColumnSort?: (columnIndex: number, ascending: 1 | -1) => void;
* getColumnPrefs?: () => { [dataKey: string]: any };
* storeColumnPrefs?: (prefs: { [dataKey: string]: any }) => void;
* getDefaultColumnOrder?: () => { [dataKey: string]: any };
* // Makes columns unmovable, unsortable, etc
* staticColumns?: boolean;
* // Used for initial column widths calculation
* containerWidth?: number;
* // Internal windowed-list ref
* treeboxRef?: (innerWindowedList: WindowedList) => any;
* // Render with display?: none
* hide?: boolean;
* multiSelect?: boolean;
* onSelectionChange?: (
* selection: TreeSelection,
* shouldDebounce: boolean
* ) => void;
* // The below are for arrow-key navigation
* isSelectable?: (index: number) => boolean;
* getParentIndex?: (index: number) => number;
* isContainer?: (index: number) => boolean;
* isContainerEmpty?: (index: number) => boolean;
* isContainerOpen?: (index: number) => boolean;
* toggleOpenState?: (index: number) => void;
* // A function with signature (index?:Number) => result?:String which will be used
* // for find-as-you-type navigation. Find-as-you-type is disabled if prop is undefined.
* getRowString?: (index: number) => string;
* // If you want to perform custom key handling it should be in this function
* // if it returns false then virtualized-table's own key handler won't run
* onKeyDown?: (e: Event) => boolean;
* onKeyUp?: (e: Event) => void;
* onDragOver?: (e: Event) => void;
* onDrop?: (e: Event) => void;
* // Enter, double-clicking
* onActivate?: (e: Event, items: number[]) => void;
* onFocus?: (e: Event) => void;
* onItemContextMenu?: (e: Event, x: number, y: number) => void;
* }
* ```
*/
setProp(data: Partial<VirtualizedTableProps>): VirtualizedTableHelper;
/**
* Set locale strings, which replaces the table header's label if matches. Default it's `Zotero.Intl.strings`
* @param localeStrings
*/
setLocale(localeStrings: {
[name: string]: string;
}): this;
/**
* Set container element id that the table will be rendered on.
* @param id element id
*/
setContainerId(id: string): this;
/**
* Render the table.
* @param selectId Which row to select after rendering
* @param onfulfilled callback after successfully rendered
* @param onrejected callback after rendering with error
*/
render(selectId?: number, onfulfilled?: (value: unknown) => unknown, onrejected?: (reason: any) => PromiseLike<never>): this;
}
interface ColumnOptions {
dataKey: string;
label: string;
iconLabel?: React.ReactElement;
defaultSort?: 1 | -1;
flex?: number;
width?: number;
fixedWidth?: boolean;
staticWidth?: boolean;
minWidth?: number;
ignoreInColumnPicker?: boolean;
submenu?: boolean;
}
interface VirtualizedTableProps {
id: string;
getRowCount: () => number;
getRowData?: (index: number) => {
[dataKey: string]: string;
};
/**
* Use `getRowData` instead. This property is generated automatically.
* @param index
* @param selection
* @param oldElem
* @param columns
*/
renderItem?: (index: number, selection: TreeSelection, oldElem: HTMLElement, columns: ColumnOptions[]) => Node;
linesPerRow?: number;
disableFontSizeScaling?: boolean;
alternatingRowColors?: Array<string>;
label?: string;
role?: string;
showHeader?: boolean;
columns?: Array<ColumnOptions>;
onColumnPickerMenu?: (event: Event) => void;
onColumnSort?: (columnIndex: number, ascending: 1 | -1) => void;
getColumnPrefs?: () => {
[dataKey: string]: any;
};
storeColumnPrefs?: (prefs: {
[dataKey: string]: any;
}) => void;
getDefaultColumnOrder?: () => {
[dataKey: string]: any;
};
staticColumns?: boolean;
containerWidth?: number;
treeboxRef?: (innerWindowedList: WindowedList) => any;
hide?: boolean;
multiSelect?: boolean;
onSelectionChange?: (selection: TreeSelection, shouldDebounce: boolean) => void;
isSelectable?: (index: number) => boolean;
getParentIndex?: (index: number) => number;
isContainer?: (index: number) => boolean;
isContainerEmpty?: (index: number) => boolean;
isContainerOpen?: (index: number) => boolean;
toggleOpenState?: (index: number) => void;
getRowString?: (index: number) => string;
onKeyDown?: (e: KeyboardEvent) => boolean;
onKeyUp?: (e: KeyboardEvent) => void;
onDragOver?: (e: DragEvent) => void;
onDrop?: (e: DragEvent) => void;
onActivate?: (e: MouseEvent | KeyboardEvent, items: number[]) => void;
onFocus?: (e: FocusEvent) => void;
onItemContextMenu?: (e: MouseEvent | KeyboardEvent, x: number, y: number) => void;
}
interface VirtualizedTable extends React.Component<VirtualizedTableProps, object> {
selection: TreeSelection;
invalidate: () => void;
}
/**
* Somewhat corresponds to nsITreeSelection
* https://udn.realityripple.com/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsITreeSelection
*
* @property pivot {Number} The selection "pivot". This is the first item the user selected as part of
* a ranged select (i.e. shift-select).
* @property focused {Number} The currently selected/focused item.
* @property count {Number} The number of selected items
* @property selected {Set} The set of currently selected items
* @property selectEventsSuppressed {Boolean} Controls whether select events are triggered on selection change.
*/
interface TreeSelection {
_tree: VirtualizedTable;
pivot: number;
focused: number;
selected: Set<number>;
_selectEventsSuppressed: boolean;
/**
* @param tree {VirtualizedTable} The tree where selection occurs. Will be used to issue
* updates.
*/
new (tree: VirtualizedTable): this;
/**
* Returns whether the given index is selected.
* @param index {Number} The index is 0-clamped.
* @returns {boolean}
*/
isSelected: (index: number) => boolean;
/**
* Toggles an item's selection state, updates focused item to index.
* @param index {Number} The index is 0-clamped.
* @param shouldDebounce {Boolean} Whether the update to the tree should be debounced
*/
toggleSelect: (index: number, shouldDebounce?: boolean) => void;
clearSelection: () => void;
/**
* Selects an item, updates focused item to index.
* @param index {Number} The index is 0-clamped.
* @param shouldDebounce {Boolean} Whether the update to the tree should be debounced
* @returns {boolean} False if nothing to select and select handlers won't be called
*/
select: (index: number, shouldDebounce?: boolean) => boolean;
rangedSelect: (from: number, to: number, augment: boolean, isSelectAll: boolean) => void;
/**
* Performs a shift-select from current pivot to provided index. Updates focused item to index.
* @param index {Number} The index is 0-clamped.
* @param augment {Boolean} Adds to existing selection if true
* @param shouldDebounce {Boolean} Whether the update to the tree should be debounced
*/
shiftSelect: (index: number, augment: boolean, shouldDebounce?: boolean) => void;
/**
* Calls the onSelectionChange prop on the tree
* @param shouldDebounce {Boolean} Whether the update to the tree should be debounced
* @private
*/
_updateTree: (shouldDebounce?: boolean) => void;
get count(): number;
get selectEventsSuppressed(): boolean;
set selectEventsSuppressed(val: boolean);
}
interface WindowedList {
[key: string | number | symbol]: any;
}
//#endregion
//#region src/managers/fieldHook.d.ts
/**
* Item field hooks manager.
*/
declare class FieldHookManager extends ManagerTool {
private data;
private patchHelpers;
constructor(base?: BasicTool | BasicOptions);
/**
* Register `Zotero.Item.getField` hook.
* @param type
* @param field
* @param hook ( field: string, unformatted: boolean, includeBaseMapped: boolean, item: Zotero.Item, original: Function) => string
*/
register(type: "getField", field: string, hook: typeof getFieldHookFunc): void;
/**
* Register `Zotero.Item.setField` hook.
* @param type
* @param field
* @param hook ( field: string, value: string, loadIn: boolean, item: Zotero.Item, original: Function) => void
*/
register(type: "setField", field: string, hook: typeof setFieldHookFunc): void;
/**
* Register `Zotero