Update JSDoc for itemPaneManager and itemTreeManager

This commit is contained in:
windingwind 2024-11-22 09:42:38 +01:00 • committed by Dan Stillman
parent 51dae17314
commit 8a76aac83f
4 changed files with 524 additions and 172 deletions

View file

@ -29,109 +29,167 @@
/**
* @typedef SectionIcon
* @type {object}
* @property {string} icon - Icon URI
* @property {string} [darkIcon] - Icon URI in dark mode. If not set, use `icon`
* @typedef SectionL10n
* @type {object}
* @property {string} l10nID - data-l10n-id for localization of section header label
* @property {string} [l10nArgs] - data-l10n-args for localization
* @typedef SectionButton
* @type {object}
* @property {string} type - Button type, must be valid DOMString and without ","
* @property {string} icon - Icon URI
* @property {string} [darkIcon] - Icon URI in dark mode. If not set, use `icon`
* @property {string} [l10nID] - data-l10n-id for localization of button tooltiptext
* @property {(props: SectionEventHookArgs) => void} onClick - Button click callback
* @typedef SectionBasicHookArgs
* @type {object}
* @property {string} paneID - Registered pane id
* @property {Document} doc - Document of section
* @property {HTMLDivElement} body - Section body
* @typedef SectionUIHookArgs
* @type {object}
* @property {Zotero.Item} item - Current item
* @property {string} tabType - Current tab type
* @property {boolean} editable - Whether the section is in edit mode
* @property {(l10nArgs: string) => void} setL10nArgs - Set l10n args for section header
* @property {(l10nArgs: string) => void} setEnabled - Set pane enabled state
* @property {(summary: string) => void} setSectionSummary - Set pane section summary,
* the text shown in the section header when the section is collapsed.
*
* See the Abstract section as an example
* @property {(buttonType: string, options: {disabled?: boolean; hidden?: boolean}) => void} setSectionButtonStatus - Set pane section button status
* @typedef SectionHookArgs
* @type {SectionBasicHookArgs & SectionUIHookArgs}
* @typedef {SectionHookArgs & { refresh: () => Promise<void> }} SectionInitHookArgs
* @namespace Zotero
*/
/**
* @typedef {Object} SectionIcon
* @property {string} icon - Icon URI.
* @property {string} [darkIcon] - Icon URI in dark mode. If not set, use `icon`.
*/
/**
* @typedef {Object} SectionL10n
* @property {string} l10nID - data-l10n-id for localization of section header label.
* @property {string} [l10nArgs] - data-l10n-args for localization.
*/
/**
* @typedef {Object} SectionButton
* @property {string} type - Button type, must be valid DOMString and without ",".
* @property {string} icon - Icon URI.
* @property {string} [darkIcon] - Icon URI in dark mode. If not set, use `icon`.
* @property {string} [l10nID] - data-l10n-id for localization of button tooltiptext.
* @property {SectionEventHook} onClick - Button click callback.
*/
/**
* @typedef {Object} SectionBasicHookArgs
* @property {string} paneID - Registered pane id.
* @property {Document} doc - Document of section.
* @property {HTMLDivElement} body - Section body.
*/
/**
* @typedef {Object} SectionUIHookArgs
* @property {Zotero.Item} item - Current item.
* @property {string} tabType - Current tab type.
* @property {boolean} editable - Whether the section is in edit mode.
* @property {SetSectionL10nArgs} setL10nArgs - Set l10n args for section header.
* @property {SetEnabled} setEnabled - Set pane enabled state.
* @property {SetSectionSummary} setSectionSummary - Set pane section summary, shown in collapsed header.
* @property {SetSectionButtonStatus} setSectionButtonStatus - Set pane section button status.
*/
/**
* @typedef {Object} SectionHookArgs
* @property {string} paneID - Registered pane id.
* @property {Document} doc - Document of section.
* @property {HTMLDivElement} body - Section body.
* @property {Zotero.Item} item - Current item.
* @property {string} tabType - Current tab type.
* @property {boolean} editable - Whether the section is in edit mode.
* @property {SetSectionL10nArgs} setL10nArgs - Set l10n args for section header.
* @property {SetEnabled} setEnabled - Set pane enabled state.
* @property {SetSectionSummary} setSectionSummary - Set pane section summary, shown in collapsed header.
* @property {SetSectionButtonStatus} setSectionButtonStatus - Set pane section button status.
*/
/**
* @typedef {Object} SectionInitHookArgs
* @property {string} paneID - Registered pane id.
* @property {Document} doc - Document of section.
* @property {HTMLDivElement} body - Section body.
* @property {Zotero.Item} item - Current item.
* @property {string} tabType - Current tab type.
* @property {boolean} editable - Whether the section is in edit mode.
* @property {SetSectionL10nArgs} setL10nArgs - Set l10n args for section header.
* @property {SetEnabled} setEnabled - Set pane enabled state.
* @property {SetSectionSummary} setSectionSummary - Set pane section summary, shown in collapsed header.
* @property {SetSectionButtonStatus} setSectionButtonStatus - Set pane section button status.
* @property {SectionRefresh} refresh - Refresh the section.
* A `refresh` is exposed to plugins to allows plugins to refresh the section when necessary,
* e.g. item modify notifier callback. Note that calling `refresh` during initialization
* have no effect.
* @typedef {SectionHookArgs & { event: Event }} SectionEventHookArgs
* @typedef ItemDetailsSectionOptions
* @type {object}
* @property {string} paneID - Unique pane ID
* @property {string} pluginID - Set plugin ID to auto remove section when plugin is disabled/removed
* @property {SectionL10n & SectionIcon} header - Header options. Icon should be 16*16 and `label` need to be localized
* @property {SectionL10n & SectionIcon} sidenav - Sidenav options. Icon should be 20*20 and `tooltiptext` need to be localized
* @property {string} [bodyXHTML] - Pane body's innerHTML, default to XUL namespace
* @property {(props: SectionInitHookArgs) => void} [onInit]
* Lifecycle hook called when section is initialized.
* You can use destructuring assignment to get the props:
* ```js
* onInit({ paneID, doc, body, item, editable, tabType, setL10nArgs, setEnabled,
* setSectionSummary, setSectionButtonStatus, refresh }) {
* // Your code here
* }
* ```
*
* Do:
* 1. Initialize data if necessary
* 2. Set up hooks, e.g. notifier callback
*
* Don't:
* 1. Render/refresh UI
* @property {(props: SectionBasicHookArgs) => void} [onDestroy]
* Lifecycle hook called when section is destroyed
*
* Do:
* 1. Remove data and release resource
* 2. Remove hooks, e.g. notifier callback
*
* Don't:
* 1. Render/refresh UI
* @property {(props: SectionHookArgs) => boolean} [onItemChange]
* Lifecycle hook called when section's item change received
*
* Do:
* 1. Update data (no need to render or refresh);
* 2. Update the section enabled state with `props.setEnabled`. For example, if the section
* is only enabled in the readers, you can use:
* ```js
* onItemChange({ setEnabled }) {
* setEnabled(newData.value === "reader");
* }
* ```
*
* Don't:
* 1. Render/refresh UI
* @property {(props: SectionHookArgs) => void} onRender
* Lifecycle hook called when section should do initial render. Cannot be async.
*
* Create elements and append them to `props.body`.
*
* If the rendering is slow, you should make the bottleneck async and move it to `onAsyncRender`.
*
* > Note that the rendering of section is fully controlled by Zotero to minimize resource usage.
* > Only render UI things when you are told to.
* @property {(props: SectionHookArgs) => void | Promise<void>} [onAsyncRender]
* [Optional] Lifecycle hook called when section should do async render
*
* The best practice to time-consuming rendering with runtime decided section height is:
* 1. Compute height and create a box in sync `onRender`;
* 2. Render actual contents in async `onAsyncRender`.
* @property {(props: SectionEventHookArgs) => void} [onToggle] - Called when section is toggled
* @property {SectionButton[]} [sectionButtons] - Section button options
*/
/**
* @typedef {Object} SectionEventHookArgs
* @property {string} paneID - Registered pane id.
* @property {Document} doc - Document of section.
* @property {HTMLDivElement} body - Section body.
* @property {Zotero.Item} item - Current item.
* @property {string} tabType - Current tab type.
* @property {boolean} editable - Whether the section is in edit mode.
* @property {SetSectionL10nArgs} setL10nArgs - Set l10n args for section header.
* @property {SetEnabled} setEnabled - Set pane enabled state.
* @property {SetSectionSummary} setSectionSummary - Set pane section summary, shown in collapsed header.
* @property {SetSectionButtonStatus} setSectionButtonStatus - Set pane section button status.
* @property {Event} event - Event object.
*/
/**
* @callback SectionInitHook
* @param {SectionInitHookArgs} props - Props provided during section initialization.
* @returns {void}
*/
/**
* @callback SectionBasicHook
* @param {SectionBasicHookArgs} props - Basic hook arguments.
*/
/**
* @callback SectionItemChangeHook
* @param {SectionHookArgs} props - Hook arguments for item changes.
* @returns {boolean}
*/
/**
* @callback SectionRenderHook
* @param {SectionHookArgs} props - Hook arguments for rendering.
* @returns {void}
*/
/**
* @callback SectionAsyncRenderHook
* @param {SectionHookArgs} props - Hook arguments for asynchronous rendering.
* @returns {void | Promise<void>}
*/
/**
* @callback SectionToggleHook
* @param {SectionEventHookArgs} props - Event hook arguments.
* @returns {void}
*/
/**
* @callback SectionEventHook
* @param {SectionEventHookArgs} props - Event hook arguments.
* @returns {void}
*/
/**
* @callback SetSectionL10nArgs
* @param {string} l10nArgs - Localization arguments.
* @returns {void}
*/
/**
* @callback SetEnabled
* @param {boolean} enabled - Enabled state.
* @returns {void}
*/
/**
* @callback SetSectionSummary
* @param {string} summary - The summary for the section header.
* @returns {void}
*/
/**
* @callback SetSectionButtonStatus
* @param {string} buttonType - The button type.
* @param {Object} options - Options for the button status.
* @param {boolean} [options.disabled] - Whether the button is disabled.
* @param {boolean} [options.hidden] - Whether the button is hidden.
* @returns {void}
*/
/**
* @callback SectionRefresh
* @returns {Promise<void>}
*/
@ -250,6 +308,73 @@
}
/**
* @typedef {Object} InfoRowL10n
* @property {string} l10nID - data-l10n-id for localization of row label.
*/
/**
* @typedef {"start" | "afterCreators" | "end"} InfoRowPosition - Position of the row.
*/
/**
* @typedef {Object} InfoRowGetDataHookArgs
* @property {string} rowID - Row ID.
* @property {Zotero.Item} item - Current item.
* @property {string} tabType - Current tab type.
* @property {boolean} editable - Whether the row is in edit mode.
*/
/**
* @typedef {Object} InfoRowSetDataHookArgs
* @property {string} rowID - Row ID.
* @property {Zotero.Item} item - Current item.
* @property {string} tabType - Current tab type.
* @property {string} editable - Whether the row is in edit mode.
* @property {string} value - New value.
*/
/**
* @typedef {Object} InfoRowItemChangeHookArgs
* @property {string} rowID - Row ID.
* @property {Zotero.Item} item - Current item.
* @property {string} tabType - Current tab type.
* @property {boolean} editable - Whether the row is in edit mode.
* @property {SetInfoRowEnabled} setEnabled - Set row enabled state.
* @property {SetInfoRowEditable} setEditable - Set row editable state.
*/
/**
* @typedef {function} InfoRowGetDataHook
* @param {InfoRowGetDataHookArgs} props - Hook arguments for getting data.
* @returns {string} - Row data.
*/
/**
* @typedef {function} InfoRowSetDataHook
* @param {InfoRowSetDataHookArgs} props - Hook arguments for setting data.
* @returns {void}
*/
/**
* @typedef {function} InfoRowItemChangeHook
* @param {InfoRowItemChangeHookArgs} props - Hook arguments for item changes.
* @returns {void}
*/
/**
* @typedef {function} SetInfoRowEnabled
* @param {boolean} enabled - Enabled state.
* @returns {void}
*/
/**
* @typedef {function} SetInfoRowEditable
* @param {boolean} editable - Editable state.
* @returns {void}
*/
class ItemPaneInfoRowManagerInternal extends PluginAPIBase {
constructor() {
super();
@ -315,22 +440,127 @@
}
class ItemPaneManager {
_sectionManager = new ItemPaneSectionManagerInternal();
/**
* Manages item pane APIs.
*
* @memberof Zotero
*/
Zotero.ItemPaneManager = {
_sectionManager: new ItemPaneSectionManagerInternal(),
_infoRowManager = new ItemPaneInfoRowManagerInternal();
_infoRowManager: new ItemPaneInfoRowManagerInternal(),
/**
* Register a custom item pane section.
* @param {Object} options - Section options.
* @param {string} options.paneID - Unique pane ID.
* @param {string} options.pluginID - Set plugin ID to auto-remove section when the plugin is disabled or removed.
* @param {SectionL10n | SectionIcon} options.header - Header options. Icon should be 16*16 and `label` need to be localized.
* @param {SectionL10n | SectionIcon} options.sidenav - Sidenav options. Icon should be 20*20 and `tooltiptext` need to be localized.
* @param {string} [options.bodyXHTML] - Pane body's innerHTML, defaults to XUL namespace.
* @param {SectionInitHook} [options.onInit] - Lifecycle hook called when section is initialized.
*
* Do:
* 1. Initialize data if necessary
* 2. Set up hooks, e.g. notifier callback
*
* Don't:
* 1. Render/refresh UI
* @param {SectionBasicHook} [options.onDestroy] - Lifecycle hook called when section is destroyed.
*
* Do:
* 1. Remove data and release resource
* 2. Remove hooks, e.g. notifier callback
*
* Don't:
* 1. Render/refresh UI
* @param {SectionItemChangeHook} [options.onItemChange] - Lifecycle hook called when the section's target item is changed.
*
* Do:
* 1. Update data (no need to render or refresh);
* 2. Update the section enabled state with `props.setEnabled`.
*
* Don't:
* 1. Render/refresh UI
* @param {SectionRenderHook} options.onRender - Lifecycle hook called for initial render.
*
* Cannot be async.
*
* Create elements and append them to `props.body`.
*
* If the rendering is slow, you should make the bottleneck async and move it to `onAsyncRender`.
* @param {SectionAsyncRenderHook} [options.onAsyncRender] - Lifecycle hook for asynchronous rendering.
*
* The best practice to time-consuming rendering with runtime decided section height is:
* 1. Compute height and create a box in sync `onRender`;
* 2. Render actual contents in async `onAsyncRender`.
* @param {SectionToggleHook} [options.onToggle] - Called when section is toggled.
* @param {SectionButton[]} [options.sectionButtons] - Section button options.
* @returns {string | false} - The registered pane ID or false if failed.
*
* @example
* ```javascript
* Zotero.ItemPaneManager.registerSection({
* paneID: 'my-plugin-pane',
* pluginID: 'my-plugin@my-namespace.com',
* header: {
* l10nID: 'my-plugin-pane-header', // Must inject the corresponding `ftl` file
* icon: 'chrome://my-plugin/content/icon16.svg',
* },
* sidenav: {
* l10nID: 'my-plugin-pane-sidenav', // Must inject the corresponding `ftl` file
* icon: 'chrome://my-plugin/content/icon20.svg',
* },
* onInit: ({paneID, doc, body}) => {
* // Initialize data
* Zotero.debug('Section initialized');
* },
* onDestroy: ({paneID, doc, body}) => {
* // Release resource
* Zotero.debug('Section destroyed');
* },
* onItemChange: ({paneID, doc, body, item, tabType, editable, setEnabled}) => {
* // In this example, the section is enabled only for regular items
* setEnabled(item.isRegularItem());
* },
* onRender: ({doc, body, item}) => {
* // Create elements and append them to `body`
* const div = doc.createElement('div');
* div.classList.add('my-plugin-section');
* div.textContent = item.getField('title');
* body.appendChild(div);
* },
* onAsyncRender: async ({body}) => {
* // Put time-consuming rendering here
* await new Promise(resolve => setTimeout(resolve, 1000));
* body.querySelector('.my-plugin-section')?.style.setProperty('color', 'red');
* },
* onToggle: ({paneID, doc, body, item, tabType, editable, setEnabled}) => {
* // Handle section toggle
* Zotero.debug('Section toggled');
* },
* sectionButtons: [
* // Section button will appear in the header
* ],
* });
* ```
*/
registerSection(options) {
return this._sectionManager.register(options);
}
},
/**
* Unregister a custom item pane section.
* @param {string} paneID - Pane ID to unregister. This is the value returned by `registerSection`.
* @returns {boolean} - True if the section is successfully unregistered, false if the paneID is not found.
*/
unregisterSection(paneID) {
return this._sectionManager.unregister(paneID);
}
},
get customSectionData() {
return this._sectionManager.data;
}
},
isSectionOrderable(paneID) {
let option = this._sectionManager._optionsCache[paneID];
@ -338,23 +568,88 @@
return false;
}
return option.sidenav.orderable ?? true;
}
},
/**
* Register a custom item pane info section row.
* @param {Object} options - Row options.
* @param {string} options.rowID - Unique row ID.
* @param {string} options.pluginID - Set plugin ID to auto-remove row when the plugin is disabled or removed.
* @param {InfoRowL10n} options.label - Label options. `label` need to be localized.
* @param {InfoRowPosition} [options.position] - Position of the row.
* @param {boolean} [options.multiline] - Whether the row is multiline.
* @param {boolean} [options.nowrap] - Whether the row is nowrap.
* @param {boolean} [options.editable] - Whether the row is editable.
* @param {InfoRowGetDataHook} options.onGetData - Lifecycle hook for getting row data for rendering.
*
* This is called when the row is rendered or refreshed.
* @param {InfoRowSetDataHook} [options.onSetData] - Lifecycle hook for saving row data changes after editing.
*
* Do:
* 1. Save the new value of the row
*
* Don't:
* 1. Render/refresh UI
* 2. Change the value in this hook
* @param {InfoRowItemChangeHook} [options.onItemChange] - Lifecycle hook for target item changes.
*
* Do:
* 1. Update the row attribute, e.g. enabled, editable
*
* Don't:
* 1. Render/refresh UI
* @returns {string | false} - The registered row ID or false if failed.
*
* @example
* ```javascript
* Zotero.ItemPaneManager.registerInfoRow({
* rowID: 'my-plugin-row',
* pluginID: 'my-plugin@my-namespace.com',
* label: {
* l10nID: 'my-plugin-row-label', // Must inject the corresponding `ftl` file
* },
* position: 'afterCreators',
* multiline: true,
* nowrap: false,
* editable: true,
* onGetData: ({rowID, item, tabType, editable}) => {
* return item.getField('title').toUpperCase();
* },
* onSetData: ({rowID, item, tabType, editable, value}) => {
* Zotero.debug('Info row data changed:', value);
* },
* onItemChange: ({rowID, item, tabType, editable, setEnabled, setEditable}) => {
* // In this example, the row is enabled only for library tab
* setEnabled(tabType === 'library');
* },
* });
* ```
*/
registerInfoRow(options) {
return this._infoRowManager.register(options);
}
},
/**
* Unregister a custom item pane info section row.
* @param {string} rowID - Row ID to unregister. This is the value returned by `registerInfoRow`.
* @returns {boolean} - True if the row is successfully unregistered, false if the rowID is not found.
*/
unregisterInfoRow(rowID) {
return this._infoRowManager.unregister(rowID);
}
},
/**
* Refresh a custom item pane info section row.
* @param {string} rowID - Row ID to refresh. This is the value returned by `registerInfoRow`.
* @returns {void}
*/
refreshInfoRow(rowID) {
return this._infoRowManager.refresh(rowID);
}
},
get customInfoRowData() {
return this._infoRowManager.data;
}
},
getInfoRowHook(rowID, type) {
let option = this._infoRowManager._optionsCache[rowID];
@ -362,9 +657,6 @@
return undefined;
}
return option[type];
}
}
Zotero.ItemPaneManager = new ItemPaneManager();
},
};
}

View file

@ -32,12 +32,25 @@ import { COLUMNS } from 'zotero/itemTreeColumns';
/**
* @typedef {import("../../itemTreeColumns.jsx").ItemTreeColumnOptions} ItemTreeColumnOptions
* @typedef {"dataKey" | "label" | "pluginID"} RequiredCustomColumnOptionKeys
* @typedef {Required<Pick<ItemTreeColumnOptions, RequiredCustomColumnOptionKeys>>} RequiredCustomColumnOptionsPartial
* @typedef {Omit<ItemTreeColumnOptions, RequiredCustomColumnOptionKeys>} CustomColumnOptionsPartial
* @typedef {RequiredCustomColumnOptionsPartial & CustomColumnOptionsPartial} ItemTreeCustomColumnOptions
* @typedef {Partial<Omit<ItemTreeCustomColumnOptions, "enabledTreeIDs">>} ItemTreeCustomColumnFilters
* @namespace Zotero
*/
/**
* @typedef {function} ItemTreeColumnDataProvider
* @param {Zotero.Item} item - The item to get data from
* @param {string} dataKey - The dataKey of the column
* @returns {string} - The data to display in the column
*/
/**
* @typedef {function} ItemTreeColumnRenderCell
* @param {number} index - The index of the row
* @param {string} data - The data to display in the column
* @param {ItemTreeColumnOptions | {className: string}} column - The column options
* @param {boolean} isFirstColumn - true if this is the first column
* @param {Document} doc - The document of the item tree
* @returns {HTMLElement} - The HTML to display in the cell
*/
@ -165,25 +178,53 @@ import { COLUMNS } from 'zotero/itemTreeColumns';
}
class ItemTreeManager {
_columnManager = new ItemTreeColumnManagerInternal();
/**
* Manages item tree APIs.
*
* @memberof Zotero
*/
Zotero.ItemTreeManager = {
_columnManager: new ItemTreeColumnManagerInternal(),
/**
* Register a custom column, must be valid with a unique dataKey.
*
* Note that the `dataKey` you use here may be different from the one returned by the function.
* This is because the `dataKey` is prefixed with the `pluginID` to avoid conflicts after the column is registered.
* @param {ItemTreeCustomColumnOptions} option - An option or array of options to register
* @param {Object} option - An option or array of options to register
* @param {string} option.dataKey - Required, see use in ItemTree#_getRowData()
* @param {string} option.label - The column label. Either a string or the id to an i18n string.
* @param {string} option.pluginID - Set plugin ID to auto remove column when plugin is removed.
* @param {string[]} [option.enabledTreeIDs=[]] - Which tree ids the column should be enabled in. If undefined, enabled in main tree. If ["*"], enabled in all trees.
* @param {string[]} [option.defaultIn] - Will be deprecated. Types of trees the column is default in. Can be [default, feed];
* @param {string[]} [option.disabledIn] - Will be deprecated. Types of trees where the column is not available
* @param {boolean} [option.sortReverse=false] - Default: false. Set to true to reverse the sort order
* @param {number} [option.flex=1] - Default: 1. When the column is added to the tree how much space it should occupy as a flex ratio
* @param {string} [option.width] - A column width instead of flex ratio. See above.
* @param {boolean} [option.fixedWidth] - Default: false. Set to true to disable column resizing
* @param {boolean} [option.staticWidth] - Default: false. Set to true to prevent columns from changing width when the width of the tree increases or decreases
* @param {boolean} [option.noPadding] - Set to true for columns with padding disabled in stylesheet
* @param {number} [option.minWidth] - Override the default [20px] column min-width for resizing
* @param {React.Component} [option.iconLabel] - Set an Icon label instead of a text-based one
* @param {string} [option.iconPath] - Set an Icon path, overrides {iconLabel}
* @param {string | React.Component} [option.htmlLabel] - Set an HTML label, overrides {iconLabel} and {label}. Can be a HTML string or a React component.
* @param {boolean} [option.showInColumnPicker=true] - Default: true. Set to true to show in column picker.
* @param {boolean} [option.columnPickerSubMenu=false] - Default: false. Set to true to display the column in "More Columns" submenu of column picker.
* @param {boolean} [option.primary] - Should only be one column at the time. Title is the primary column
* @param {ItemTreeColumnDataProvider} [option.dataProvider] - Custom data provider that is called when rendering cells
* @param {ItemTreeColumnRenderCell} [option.renderCell] - The cell renderer function
* @param {string[]} [option.zoteroPersist] - Which column properties should be persisted between zotero close
* @returns {string | false} - The dataKey of the added column or false if no column is added
*
* @example
* A minimal custom column:
* ```js
* ```javascript
* // You can unregister the column later with Zotero.ItemTreeManager.unregisterColumn(registeredDataKey);
* const registeredDataKey = Zotero.ItemTreeManager.registerColumn(
* {
* dataKey: 'rtitle',
* label: 'Reversed Title',
* pluginID: 'make-it-red@zotero.org', // Replace with your plugin ID
* pluginID: 'my-plugin@my-namespace.com', // Replace with your plugin ID
* dataProvider: (item, dataKey) => {
* return item.getField('title').split('').reverse().join('');
* },
@ -192,7 +233,7 @@ import { COLUMNS } from 'zotero/itemTreeColumns';
* @example
* A custom column using all available options.
* Note that the column will only be shown in the main item tree.
* ```js
* ```javascript
* const registeredDataKey = Zotero.ItemTreeManager.registerColumn(
* {
* dataKey: 'rtitle',
@ -208,7 +249,7 @@ import { COLUMNS } from 'zotero/itemTreeColumns';
* htmlLabel: '<span style="color: red;">reversed title</span>', // use HTML in the label. This will override the label and iconPath property
* showInColumnPicker: true, // show in the column picker
* columnPickerSubMenu: true, // show in the column picker submenu
* pluginID: 'make-it-red@zotero.org', // plugin ID, which will be used to unregister the column when the plugin is unloaded
* pluginID: 'my-plugin@my-namespace.com', // plugin ID
* dataProvider: (item, dataKey) => {
* // item: the current item in the row
* // dataKey: the dataKey of the column
@ -234,7 +275,7 @@ import { COLUMNS } from 'zotero/itemTreeColumns';
*/
registerColumn(option) {
return this._columnManager.register(option);
}
},
/**
* @deprecated Use `registerColumn` instead.
@ -244,21 +285,16 @@ import { COLUMNS } from 'zotero/itemTreeColumns';
options = [options];
}
return options.map(option => this.registerColumn(option));
}
},
/**
* Unregister a custom column.
* @param {string} dataKey - The dataKey of the column to unregister
* @returns {boolean} true if the column is unregistered
* @example
* The `registeredDataKey` is returned by the `registerColumn` function.
* ```js
* Zotero.ItemTreeManager.unregisterColumn(registeredDataKey);
* ```
* @returns {boolean} - true if the column was unregistered, false if the column was not found
*/
unregisterColumn(dataKey) {
return this._columnManager.unregister(dataKey);
}
},
/**
* @deprecated Use `unregisterColumn` instead.
@ -268,18 +304,20 @@ import { COLUMNS } from 'zotero/itemTreeColumns';
dataKeys = [dataKeys];
}
return dataKeys.map(dataKey => this.unregisterColumn(dataKey));
}
},
/**
* Refresh the columns in the item tree
* @returns {void}
*/
refreshColumns() {
this._columnManager.refresh();
},
get customColumnUpdateID() {
return this._columnManager.updateID;
}
},
/**
* Get column(s) that matches the properties of option
* @param {string | string[]} [filterTreeIDs] - The tree IDs to match
* @param {ItemTreeCustomColumnFilters} [options] - An option or array of options to match
* @returns {ItemTreeCustomColumnOptions[]}
*/
getCustomColumns(filterTreeIDs, options) {
const allColumns = this._columnManager.options;
if (!filterTreeIDs && !options) {
@ -312,36 +350,18 @@ import { COLUMNS } from 'zotero/itemTreeColumns';
});
}
return filteredColumns;
}
},
/**
* Check if a column is registered as a custom column
* @param {string} dataKey - The dataKey of the column
* @returns {boolean} true if the column is registered as a custom column
*/
isCustomColumn(dataKey) {
return !!this._columnManager._optionsCache[dataKey];
}
},
/**
* A centralized data source for custom columns. This is used by the ItemTreeRow to get data.
* @param {Zotero.Item} item - The item to get data from
* @param {string} dataKey - The dataKey of the column
* @returns {string}
*/
getCustomCellData(item, dataKey) {
const option = this._columnManager._optionsCache[dataKey];
if (option && option.dataProvider) {
return option.dataProvider(item, dataKey);
}
return "";
}
refreshColumns() {
this._columnManager.refresh();
}
}
Zotero.ItemTreeManager = new ItemTreeManager();
},
};
}

View file

@ -118,7 +118,19 @@ Zotero.PreferencePanes = {
* relative to the plugin's root
* @param {String} [options.helpURL] If provided, a help button will be displayed under the pane
* and the provided URL will open when it is clicked
* @return {Promise<String>} Resolves to the ID of the pane if successfully added
* @return {Promise<string>} Resolves to the ID of the pane if successfully added
*
* @example
* Register a pane with a script and stylesheet:
* ```javascript
* Zotero.PreferencePanes.register({
* pluginID: 'my-plugin@my-namespace.com',
* src: rootURI + 'my-pane.xhtml',
* id: 'my-plugin-pane',
* scripts: [rootURI + 'my-pane.js'],
* stylesheets: [rootURI + 'my-pane.css']
* });
* ```
*/
register: async function (options) {
if (!options.pluginID || !options.src) {

View file

@ -2087,6 +2087,32 @@ class Reader {
}
/**
* @typedef {"renderTextSelectionPopup" | "renderSidebarAnnotationHeader" | "renderToolbar" |
* "createColorContextMenu" | "createViewContextMenu" | "createAnnotationContextMenu" |
* "createThumbnailContextMenu" | "createSelectorContextMenu"} ReaderEventType
*/
/**
* @typedef {Object} ReaderEvent
* @property {ReaderInstance} reader - Reader instance
* @property {Document} doc - Document
* @property {Object} params - Event parameters
* @property {function(...Element): void} append - Append function
*/
/**
* @typedef {function} ReaderEventHandler
* @param {ReaderEvent} event - Event
* @returns {void}
*/
/**
* @param {ReaderEventType} type - Event type
* @param {ReaderEventHandler} handler - Event handler
* @param {string} [pluginID] - Plugin ID
* @returns {void}
*
* @example
* Inject DOM nodes to reader UI parts:
* - renderTextSelectionPopup
* - renderSidebarAnnotationHeader
@ -2098,9 +2124,10 @@ class Reader {
* container.append('Loading…');
* append(container);
* setTimeout(() => container.replaceChildren('Translated text: ' + params.annotation.text), 1000);
* });
*
* }, 'my-plugin@my-namespace.com');
* ```
*
* @example
* Add options to context menus:
* - createColorContextMenu
* - createViewContextMenu
@ -2114,7 +2141,8 @@ class Reader {
* label: 'Test',
* onCommand(){ reader._iframeWindow.alert('Selected annotations: ' + params.ids.join(', ')); }
* });
* });
* }, 'my-plugin@my-namespace.com');
* ```
*/
registerEventListener(type, handler, pluginID = undefined) {
this._registeredListeners.push({ pluginID, type, handler });