UNPKG

zotero-plugin-toolkit

Version:
1,611 lines (1,610 loc) 68.4 kB
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