Add ItemTree column API (#3186)
This commit is contained in:
parent
676f820f87
commit
c89590c7b7
13 changed files with 586 additions and 121 deletions
|
@ -26,7 +26,7 @@
|
|||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
import ItemTree from 'zotero/itemTree';
|
||||
import { getDefaultColumnsByDataKeys } from 'zotero/itemTreeColumns';
|
||||
import { getColumnDefinitionsByDataKey } from 'zotero/itemTreeColumns'
|
||||
|
||||
|
||||
var ZoteroAdvancedSearch = new function() {
|
||||
|
@ -60,13 +60,14 @@ var ZoteroAdvancedSearch = new function() {
|
|||
id: "advanced-search",
|
||||
dragAndDrop: true,
|
||||
onActivate: this.onItemActivate.bind(this),
|
||||
columns: getDefaultColumnsByDataKeys(['title', 'firstCreator']),
|
||||
columns: getColumnDefinitionsByDataKey(["title", "firstCreator"]),
|
||||
});
|
||||
|
||||
// A minimal implementation of Zotero.CollectionTreeRow
|
||||
var collectionTreeRow = {
|
||||
view: {},
|
||||
ref: _searchBox.search,
|
||||
visibilityGroup: 'default',
|
||||
isSearchMode: () => true,
|
||||
getItems: async () => [],
|
||||
isLibrary: () => false,
|
||||
|
@ -96,6 +97,7 @@ var ZoteroAdvancedSearch = new function() {
|
|||
var collectionTreeRow = {
|
||||
view: {},
|
||||
ref: _searchBox.search,
|
||||
visibilityGroup: 'default',
|
||||
isSearchMode: () => true,
|
||||
getItems: async function () {
|
||||
await Zotero.Libraries.get(_libraryID).waitForDataLoad('item');
|
||||
|
|
|
@ -1090,9 +1090,23 @@ class VirtualizedTable extends React.Component {
|
|||
return this._getVisibleColumns().map((column, index) => {
|
||||
let columnName = formatColumnName(column);
|
||||
let label = columnName;
|
||||
// Allow custom icons to be used in column headers
|
||||
if (column.iconPath) {
|
||||
column.iconLabel = <span
|
||||
className="icon icon-bg"
|
||||
style={{backgroundImage: `url("${column.iconPath}")`}}>
|
||||
</span>;
|
||||
}
|
||||
if (column.iconLabel) {
|
||||
label = column.iconLabel;
|
||||
}
|
||||
if (column.htmlLabel) {
|
||||
if (React.isValidElement(column.htmlLabel)) {
|
||||
label = column.htmlLabel;
|
||||
} else if (typeof column.htmlLabel === "string") {
|
||||
label = <span dangerouslySetInnerHTML={{ __html: column.htmlLabel }} />;
|
||||
}
|
||||
}
|
||||
let resizer = (<Draggable
|
||||
onDragStart={this._handleResizerDragStart.bind(this, index)}
|
||||
onDrag={this._handleResizerDrag}
|
||||
|
@ -1329,6 +1343,12 @@ class VirtualizedTable extends React.Component {
|
|||
return row >= this._jsWindow.getFirstVisibleRow()
|
||||
&& row <= this._jsWindow.getLastVisibleRow();
|
||||
}
|
||||
|
||||
async _resetColumns() {
|
||||
this.invalidate();
|
||||
this._columns = new Columns(this);
|
||||
await new Promise((resolve) => {this.forceUpdate(resolve)});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1452,7 +1472,7 @@ var Columns = class {
|
|||
}
|
||||
|
||||
_getColumnPrefsToPersist(column) {
|
||||
let persistKeys = column.zoteroPersist;
|
||||
let persistKeys = new Set(column.zoteroPersist);
|
||||
if (!persistKeys) persistKeys = new Set();
|
||||
// Always persist
|
||||
['ordinal', 'hidden', 'sortDirection'].forEach(k => persistKeys.add(k));
|
||||
|
@ -1585,7 +1605,7 @@ var Columns = class {
|
|||
column.sortDirection *= -1;
|
||||
}
|
||||
else {
|
||||
column.sortDirection = column.defaultSort || 1;
|
||||
column.sortDirection = column.sortReverse ? -1 : 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -173,7 +173,7 @@
|
|||
}
|
||||
|
||||
add = async () => {
|
||||
let io = { dataIn: null, dataOut: null, deferred: Zotero.Promise.defer() };
|
||||
let io = { dataIn: null, dataOut: null, deferred: Zotero.Promise.defer(), itemTreeID: 'related-box-select-item-dialog' };
|
||||
window.openDialog('chrome://zotero/content/selectItemsDialog.xhtml', '',
|
||||
'chrome,dialog=no,centerscreen,resizable=yes', io);
|
||||
|
||||
|
|
|
@ -138,6 +138,10 @@ var Zotero_Citation_Dialog = new function () {
|
|||
i++;
|
||||
}
|
||||
menu.selectedIndex = pageLocatorIndex;
|
||||
|
||||
if (!io.itemTreeID) {
|
||||
io.itemTreeID = "add-citation-select-item-dialog";
|
||||
}
|
||||
|
||||
// load (from selectItemsDialog.js)
|
||||
yield doLoad();
|
||||
|
|
|
@ -57,6 +57,10 @@ var Zotero_Bibliography_Dialog = new function () {
|
|||
window.addEventListener('dialogcancel', () => Zotero_Bibliography_Dialog.close());
|
||||
|
||||
_editor = document.querySelector('#editor').contentWindow.editor;
|
||||
|
||||
if (!io.itemTreeID) {
|
||||
io.itemTreeID = "edit-bib-select-item-dialog";
|
||||
}
|
||||
|
||||
// load (from selectItemsDialog.js)
|
||||
await doLoad();
|
||||
|
|
|
@ -32,10 +32,14 @@ const VirtualizedTable = require('components/virtualized-table');
|
|||
const { renderCell, formatColumnName } = VirtualizedTable;
|
||||
const Icons = require('components/icons');
|
||||
const { getDOMElement } = Icons;
|
||||
const { COLUMNS } = require('./itemTreeColumns');
|
||||
const { COLUMNS } = require("zotero/itemTreeColumns");
|
||||
const { Cc, Ci, Cu } = require('chrome');
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
/**
|
||||
* @typedef {import("./itemTreeColumns.jsx").ItemTreeColumnOptions} ItemTreeColumnOptions
|
||||
*/
|
||||
|
||||
const CHILD_INDENT = 12;
|
||||
const COLORED_TAGS_RE = new RegExp("^(?:Numpad|Digit)([0-" + Zotero.Tags.MAX_COLORED_TAGS + "]{1})$");
|
||||
const COLUMN_PREFS_FILEPATH = OS.Path.join(Zotero.Profile.dir, "treePrefs.json");
|
||||
|
@ -99,7 +103,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
|
||||
this._unregisterID = Zotero.Notifier.registerObserver(
|
||||
this,
|
||||
['item', 'collection-item', 'item-tag', 'share-items', 'bucket', 'feedItem', 'search'],
|
||||
['item', 'collection-item', 'item-tag', 'share-items', 'bucket', 'feedItem', 'search', 'itemtree'],
|
||||
'itemTreeView',
|
||||
50
|
||||
);
|
||||
|
@ -107,8 +111,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
this._itemsPaneMessage = null;
|
||||
|
||||
this._columnsId = null;
|
||||
this.columns = null;
|
||||
|
||||
|
||||
if (this.collectionTreeRow) {
|
||||
this.collectionTreeRow.view.itemTreeView = this;
|
||||
}
|
||||
|
@ -142,12 +145,20 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
|
||||
|
||||
/**
|
||||
* Extension developers: use this to monkey-patch additional columns. See
|
||||
* itemTreeColumns.js for available column fields.
|
||||
* @returns {Array<Column>}
|
||||
* Get global columns from ItemTreeColumns and local columns from this.columns
|
||||
* @returns {ItemTreeColumnOptions[]}
|
||||
*/
|
||||
getColumns() {
|
||||
return Array.from(this.props.columns);
|
||||
const extraColumns = Zotero.ItemTreeManager.getCustomColumns(this.props.id);
|
||||
|
||||
/** @type {ItemTreeColumnOptions[]} */
|
||||
const currentColumns = this.props.columns.map(col => Object.assign({}, col));
|
||||
extraColumns.forEach((column) => {
|
||||
if (!currentColumns.find(c => c.dataKey === column.dataKey)) {
|
||||
currentColumns.push(column);
|
||||
}
|
||||
});
|
||||
return currentColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -349,6 +360,12 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
return;
|
||||
}
|
||||
|
||||
// Reset columns on custom column change
|
||||
if(type === "itemtree" && action === "refresh") {
|
||||
await this._resetColumns();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == 'search' && action == 'modify') {
|
||||
// TODO: Only refresh on condition change (not currently available in extraData)
|
||||
await this.refresh();
|
||||
|
@ -1300,7 +1317,8 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
return (row.ref.isFeedItem && Zotero.Feeds.get(row.ref.libraryID).name) || "";
|
||||
|
||||
default:
|
||||
return row.ref.getField(field, false, true);
|
||||
// Get from row.getField() to allow for custom fields
|
||||
return row.getField(field, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3165,6 +3183,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
|
||||
let columnsSettings = this._getColumnPrefs();
|
||||
|
||||
// Refresh columns from itemTreeColumns
|
||||
const columns = this.getColumns();
|
||||
let hasDefaultIn = columns.some(column => 'defaultIn' in column);
|
||||
for (let column of columns) {
|
||||
|
@ -3193,7 +3212,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
// Initial hidden value
|
||||
if (!("hidden" in column)) {
|
||||
if (hasDefaultIn) {
|
||||
column.hidden = !(column.defaultIn && column.defaultIn.has(visibilityGroup));
|
||||
column.hidden = !(column.defaultIn && column.defaultIn.includes(visibilityGroup));
|
||||
}
|
||||
else {
|
||||
column.hidden = false;
|
||||
|
@ -3573,7 +3592,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
let columnMenuitemElements = {};
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
const column = columns[i];
|
||||
if (column.ignoreInColumnPicker === true) continue;
|
||||
if (column.showInColumnPicker === false) continue;
|
||||
let label = formatColumnName(column);
|
||||
let menuitem = doc.createXULElement('menuitem');
|
||||
menuitem.setAttribute('type', 'checkbox');
|
||||
|
@ -3604,7 +3623,7 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
let moreItems = [];
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
const column = columns[i];
|
||||
if (column.submenu) {
|
||||
if (column.columnPickerSubMenu) {
|
||||
moreItems.push(columnMenuitemElements[column.dataKey]);
|
||||
}
|
||||
}
|
||||
|
@ -3815,6 +3834,15 @@ var ItemTree = class ItemTree extends LibraryTree {
|
|||
}
|
||||
return span;
|
||||
}
|
||||
|
||||
async _resetColumns(){
|
||||
this._columnsId = null;
|
||||
return new Promise((resolve) => this.forceUpdate(async () => {
|
||||
await this.tree._resetColumns();
|
||||
await this.refreshAndMaintainSelection();
|
||||
resolve();
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
var ItemTreeRow = function(ref, level, isOpen)
|
||||
|
@ -3827,7 +3855,10 @@ var ItemTreeRow = function(ref, level, isOpen)
|
|||
|
||||
ItemTreeRow.prototype.getField = function(field, unformatted)
|
||||
{
|
||||
return this.ref.getField(field, unformatted, true);
|
||||
if (!Zotero.ItemTreeManager.isCustomColumn(field)) {
|
||||
return this.ref.getField(field, unformatted, true);
|
||||
}
|
||||
return Zotero.ItemTreeManager.getCustomCellData(this.ref, field);
|
||||
}
|
||||
|
||||
ItemTreeRow.prototype.numNotes = function() {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2020 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
Vienna, Virginia, USA
|
||||
http://zotero.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
@ -23,318 +23,358 @@
|
|||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
(function() {
|
||||
const React = require('react');
|
||||
const Icons = require('components/icons');
|
||||
|
||||
/**
|
||||
* @type Column {
|
||||
* dataKey: string, // Required, see use in ItemTree#_getRowData()
|
||||
*
|
||||
* defaultIn: Set<string>, // Types of trees the column is default in. Can be [default, feed];
|
||||
* disabledIn: Set<string>, // Types of trees where the column is not available
|
||||
* defaultSort: number // Default: 1. -1 for descending sort
|
||||
*
|
||||
* flex: number, // Default: 1. When the column is added to the tree how much space it should occupy as a flex ratio
|
||||
* width: string, // A column width instead of flex ratio. See above.
|
||||
* fixedWidth: boolean // Default: false. Set to true to disable column resizing
|
||||
* staticWidth: boolean // Default: false. Set to true to prevent columns from changing width when
|
||||
* // the width of the tree increases or decreases
|
||||
* minWidth: number, // Override the default [20px] column min-width for resizing
|
||||
*
|
||||
* label: string, // The column label. Either a string or the id to an i18n string.
|
||||
* iconLabel: React.Component, // Set an Icon label instead of a text-based one
|
||||
*
|
||||
* ignoreInColumnPicker: boolean // Default: false. Set to true to not display in column picker.
|
||||
* submenu: boolean, // Default: false. Set to true to display the column in "More Columns" submenu of column picker.
|
||||
*
|
||||
* primary: boolean, // Should only be one column at the time. Title is the primary column
|
||||
* zoteroPersist: Set<string>, // Which column properties should be persisted between zotero close
|
||||
* }
|
||||
* @typedef ItemTreeColumnOptions
|
||||
* @type {object}
|
||||
* @property {string} dataKey - Required, see use in ItemTree#_getRowData()
|
||||
* @property {string} label - The column label. Either a string or the id to an i18n string.
|
||||
* @property {string} [pluginID] - Set plugin ID to auto remove column when plugin is removed.
|
||||
* @property {string[]} [enabledTreeIDs=[]] - Which tree ids the column should be enabled in. If undefined, enabled in main tree. If ["*"], enabled in all trees.
|
||||
* @property {string[]} [defaultIn] - Will be deprecated. Types of trees the column is default in. Can be [default, feed];
|
||||
* @property {string[]} [disabledIn] - Will be deprecated. Types of trees where the column is not available
|
||||
* @property {boolean} [sortReverse=false] - Default: false. Set to true to reverse the sort order
|
||||
* @property {number} [flex=1] - Default: 1. When the column is added to the tree how much space it should occupy as a flex ratio
|
||||
* @property {string} [width] - A column width instead of flex ratio. See above.
|
||||
* @property {boolean} [fixedWidth] - Default: false. Set to true to disable column resizing
|
||||
* @property {boolean} [staticWidth] - Default: false. Set to true to prevent columns from changing width when the width of the tree increases or decreases
|
||||
* @property {number} [minWidth] - Override the default [20px] column min-width for resizing
|
||||
* @property {React.Component} [iconLabel] - Set an Icon label instead of a text-based one
|
||||
* @property {string} [iconPath] - Set an Icon path, overrides {iconLabel}
|
||||
* @property {string | React.Component} [htmlLabel] - Set an HTML label, overrides {iconLabel} and {label}. Can be a HTML string or a React component.
|
||||
* @property {boolean} [showInColumnPicker=true] - Default: true. Set to true to show in column picker.
|
||||
* @property {boolean} [columnPickerSubMenu=false] - Default: false. Set to true to display the column in "More Columns" submenu of column picker.
|
||||
* @property {boolean} [primary] - Should only be one column at the time. Title is the primary column
|
||||
* @property {boolean} [custom] - Set automatically to true when the column is added by the user
|
||||
* @property {(item: Zotero.Item, dataKey: string) => string} [dataProvider] - Custom data provider that is called when rendering cells
|
||||
* @property {string[]} [zoteroPersist] - Which column properties should be persisted between zotero close
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {ItemTreeColumnOptions[]}
|
||||
* @constant
|
||||
*/
|
||||
const COLUMNS = [
|
||||
{
|
||||
dataKey: "title",
|
||||
primary: true,
|
||||
defaultIn: new Set(["default", "feeds", "feed"]),
|
||||
defaultIn: ["default", "feeds", "feed"],
|
||||
label: "itemFields.title",
|
||||
ignoreInColumnPicker: true,
|
||||
showInColumnPicker: false,
|
||||
flex: 4,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "firstCreator",
|
||||
defaultIn: new Set(["default", "feeds", "feed"]),
|
||||
defaultIn: ["default", "feeds", "feed"],
|
||||
label: "zotero.items.creator_column",
|
||||
showInColumnPicker: true,
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "itemType",
|
||||
label: "zotero.items.itemType",
|
||||
showInColumnPicker: true,
|
||||
width: "40",
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "date",
|
||||
defaultIn: new Set(["feeds", "feed"]),
|
||||
defaultSort: -1,
|
||||
defaultIn: ["feeds", "feed"],
|
||||
sortReverse: true,
|
||||
label: "itemFields.date",
|
||||
showInColumnPicker: true,
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "year",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
defaultSort: -1,
|
||||
sortReverse: true,
|
||||
label: "zotero.items.year_column",
|
||||
showInColumnPicker: true,
|
||||
flex: 1,
|
||||
staticWidth: true,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "publisher",
|
||||
label: "itemFields.publisher",
|
||||
showInColumnPicker: true,
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "publicationTitle",
|
||||
label: "itemFields.publicationTitle",
|
||||
showInColumnPicker: true,
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "journalAbbreviation",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.journalAbbreviation",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "language",
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.language",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "accessDate",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
defaultSort: -1,
|
||||
submenu: true,
|
||||
sortReverse: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.accessDate",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "libraryCatalog",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.libraryCatalog",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "callNumber",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.callNumber",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "rights",
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.rights",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "dateAdded",
|
||||
defaultSort: -1,
|
||||
sortReverse: true,
|
||||
disabledIn: ["feeds", "feed"],
|
||||
showInColumnPicker: true,
|
||||
label: "itemFields.dateAdded",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "dateModified",
|
||||
defaultSort: -1,
|
||||
sortReverse: true,
|
||||
disabledIn: ["feeds", "feed"],
|
||||
showInColumnPicker: true,
|
||||
label: "zotero.items.dateModified_column",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "archive",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.archive",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "archiveLocation",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.archiveLocation",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "place",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.place",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "volume",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.volume",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "edition",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.edition",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "number",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.number",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "pages",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.pages",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "issue",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.issue",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "series",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.series",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "seriesTitle",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.seriesTitle",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "court",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.court",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "medium",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.medium",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "genre",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.genre",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "system",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.system",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "shortTitle",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
submenu: true,
|
||||
showInColumnPicker: true,
|
||||
columnPickerSubMenu: true,
|
||||
label: "itemFields.shortTitle",
|
||||
flex: 2,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "extra",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
showInColumnPicker: true,
|
||||
label: "itemFields.extra",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "hasAttachment",
|
||||
defaultIn: new Set(["default"]),
|
||||
defaultIn: ["default"],
|
||||
disabledIn: ["feeds", "feed"],
|
||||
showInColumnPicker: true,
|
||||
label: "zotero.tabs.attachments.label",
|
||||
iconLabel: <Icons.IconAttachSmall />,
|
||||
fixedWidth: true,
|
||||
width: "16",
|
||||
zoteroPersist: new Set(["hidden", "sortDirection"])
|
||||
zoteroPersist: ["hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "numNotes",
|
||||
disabledIn: ["feeds", "feed"],
|
||||
showInColumnPicker: true,
|
||||
label: "zotero.tabs.notes.label",
|
||||
iconLabel: <Icons.IconTreeitemNoteSmall />,
|
||||
width: "14",
|
||||
minWidth: 14,
|
||||
staticWidth: true,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
},
|
||||
{
|
||||
dataKey: "feed",
|
||||
disabledIn: ["default", "feed"],
|
||||
showInColumnPicker: true,
|
||||
label: "itemFields.feed",
|
||||
flex: 1,
|
||||
zoteroPersist: new Set(["width", "hidden", "sortDirection"])
|
||||
zoteroPersist: ["width", "hidden", "sortDirection"]
|
||||
}
|
||||
];
|
||||
|
||||
function getDefaultColumnByDataKey(dataKey) {
|
||||
return Object.assign({}, COLUMNS.find(col => col.dataKey == dataKey), {hidden: false});
|
||||
}
|
||||
|
||||
function getDefaultColumnsByDataKeys(dataKeys) {
|
||||
return COLUMNS.filter(column => dataKeys.includes(column.dataKey)).map(column => Object.assign({}, column, {hidden: false}));
|
||||
/**
|
||||
* Returns the columns that match the given data keys from the COLUMNS constant.
|
||||
* @param {string | string[]} dataKeys - The data key(s) to match.
|
||||
* @returns {ItemTreeColumnOptions | ItemTreeColumnOptions[]} - The matching columns.
|
||||
*/
|
||||
function getColumnDefinitionsByDataKey(dataKeys) {
|
||||
const isSingle = !Array.isArray(dataKeys);
|
||||
if (isSingle) {
|
||||
dataKeys = [dataKeys];
|
||||
}
|
||||
const matches = COLUMNS.filter(column => dataKeys.includes(column.dataKey)).map(column => Object.assign({}, column, { hidden: false }));
|
||||
return isSingle ? matches[0] : matches;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
COLUMNS,
|
||||
getDefaultColumnByDataKey,
|
||||
getDefaultColumnsByDataKeys,
|
||||
getColumnDefinitionsByDataKey,
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
|
@ -341,7 +341,7 @@ const Zotero_RTFScan = { // eslint-disable-line no-unused-vars, camelcase
|
|||
}
|
||||
else { // mapped or unmapped citation, or ambiguous citation parent
|
||||
var citation = row.rtf;
|
||||
var io = { singleSelection: true };
|
||||
var io = { singleSelection: true, itemTreeID: 'rtf-scan-select-item-dialog' };
|
||||
if (this.citationItemIDs[citation] && this.citationItemIDs[citation].length == 1) { // mapped citation
|
||||
// specify that item should be selected in window
|
||||
io.select = this.citationItemIDs[citation][0];
|
||||
|
|
|
@ -60,7 +60,7 @@ var doLoad = async function () {
|
|||
onItemSelected();
|
||||
}
|
||||
},
|
||||
id: "select-items-dialog",
|
||||
id: io.itemTreeID || "select-items-dialog",
|
||||
dragAndDrop: false,
|
||||
persistColumns: true,
|
||||
columnPicker: true,
|
||||
|
|
|
@ -3181,6 +3181,7 @@ Zotero.Integration.Citation = class {
|
|||
|
||||
io.addBorder = Zotero.isWin;
|
||||
io.singleSelection = true;
|
||||
io.itemTreeID = "handle-missing-item-select-item-dialog";
|
||||
|
||||
await Zotero.Integration.displayDialog('chrome://zotero/content/selectItemsDialog.xhtml', 'resizable', io);
|
||||
|
||||
|
|
361
chrome/content/zotero/xpcom/itemTreeManager.js
Normal file
361
chrome/content/zotero/xpcom/itemTreeManager.js
Normal file
|
@ -0,0 +1,361 @@
|
|||
/*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright © 2019 Corporation for Digital Scholarship
|
||||
Vienna, Virginia, USA
|
||||
https://digitalscholar.org
|
||||
|
||||
This file is part of Zotero.
|
||||
|
||||
Zotero is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Zotero is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with Zotero. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
*/
|
||||
|
||||
const { COLUMNS: ITEMTREE_COLUMNS } = require("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
|
||||
*/
|
||||
|
||||
class ItemTreeManager {
|
||||
_observerAdded = false;
|
||||
|
||||
/** @type {Record<string, ItemTreeCustomColumnOptions}} */
|
||||
_customColumns = {};
|
||||
|
||||
/**
|
||||
* Register a custom column. All registered columns must be valid, and must have a unique dataKey.
|
||||
* Although it's async, resolving does not promise the item trees are updated.
|
||||
*
|
||||
* 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 | ItemTreeCustomColumnOptions[]} options - An option or array of options to register
|
||||
* @returns {string | string[] | false} - The dataKey(s) of the added column(s) or false if no columns were added
|
||||
* @example
|
||||
* A minimal custom column:
|
||||
* ```js
|
||||
* // You can unregister the column later with Zotero.ItemTreeManager.unregisterColumns(registeredDataKey);
|
||||
* const registeredDataKey = await Zotero.ItemTreeManager.registerColumns(
|
||||
* {
|
||||
* dataKey: 'rtitle',
|
||||
* label: 'Reversed Title',
|
||||
* pluginID: 'make-it-red@zotero.org', // Replace with your plugin ID
|
||||
* dataProvider: (item, dataKey) => {
|
||||
* return item.getField('title').split('').reverse().join('');
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* A custom column using all available options.
|
||||
* Note that the column will only be shown in the main item tree.
|
||||
* ```js
|
||||
* const registeredDataKey = await Zotero.ItemTreeManager.registerColumns(
|
||||
* {
|
||||
* dataKey: 'rtitle',
|
||||
* label: 'Reversed Title',
|
||||
* enabledTreeIDs: ['main'], // only show in the main item tree
|
||||
* sortReverse: true, // sort by increasing order
|
||||
* flex: 0, // don't take up all available space
|
||||
* width: 100, // assign fixed width in pixels
|
||||
* fixedWidth: true, // don't allow user to resize
|
||||
* staticWidth: true, // don't allow column to be resized when the tree is resized
|
||||
* minWidth: 50, // minimum width in pixels
|
||||
* iconPath: 'chrome://zotero/skin/tick.png', // icon to show in the column header
|
||||
* 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
|
||||
* dataProvider: (item, dataKey) => {
|
||||
* // item: the current item in the row
|
||||
* // dataKey: the dataKey of the column
|
||||
* // return: the data to display in the column
|
||||
* return item.getField('title').split('').reverse().join('');
|
||||
* },
|
||||
* zoteroPersist: ['width', 'hidden', 'sortDirection'], // persist the column properties
|
||||
* });
|
||||
* ```
|
||||
* @example
|
||||
* Register multiple custom columns:
|
||||
* ```js
|
||||
* const registeredDataKeys = await Zotero.ItemTreeManager.registerColumns(
|
||||
* [
|
||||
* {
|
||||
* dataKey: 'rtitle',
|
||||
* iconPath: 'chrome://zotero/skin/tick.png',
|
||||
* label: 'Reversed Title',
|
||||
* pluginID: 'make-it-red@zotero.org', // Replace with your plugin ID
|
||||
* dataProvider: (item, dataKey) => {
|
||||
* return item.getField('title').split('').reverse().join('');
|
||||
* },
|
||||
* },
|
||||
* {
|
||||
* dataKey: 'utitle',
|
||||
* label: 'Uppercase Title',
|
||||
* pluginID: 'make-it-red@zotero.org', // Replace with your plugin ID
|
||||
* dataProvider: (item, dataKey) => {
|
||||
* return item.getField('title').toUpperCase();
|
||||
* },
|
||||
* },
|
||||
* ]);
|
||||
* ```
|
||||
*/
|
||||
async registerColumns(options) {
|
||||
const registeredDataKeys = this._addColumns(options);
|
||||
if (!registeredDataKeys) {
|
||||
return false;
|
||||
}
|
||||
this._addPluginShutdownObserver();
|
||||
await this._notifyItemTrees();
|
||||
return registeredDataKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a custom column.
|
||||
* Although it's async, resolving does not promise the item trees are updated.
|
||||
* @param {string | string[]} dataKeys - The dataKey of the column to unregister
|
||||
* @returns {boolean} true if the column(s) are unregistered
|
||||
* @example
|
||||
* ```js
|
||||
* Zotero.ItemTreeManager.unregisterColumns('rtitle');
|
||||
* ```
|
||||
*/
|
||||
async unregisterColumns(dataKeys) {
|
||||
const success = this._removeColumns(dataKeys);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
await this._notifyItemTrees();
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: add cell renderer registration
|
||||
|
||||
/**
|
||||
* 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 = Object.values(this._customColumns).map(opt => Object.assign({}, opt));
|
||||
if (!filterTreeIDs && !options) {
|
||||
return allColumns;
|
||||
}
|
||||
let filteredColumns = allColumns;
|
||||
if (typeof filterTreeIDs === "string") {
|
||||
filterTreeIDs = [filterTreeIDs];
|
||||
}
|
||||
if (filterTreeIDs && !filterTreeIDs.includes("*")) {
|
||||
const filterTreeIDsSet = new Set(filterTreeIDs);
|
||||
filteredColumns = filteredColumns.filter((column) => {
|
||||
if (column.enabledTreeIDs[0] == "*") return true;
|
||||
|
||||
for (const treeID of column.enabledTreeIDs) {
|
||||
if (filterTreeIDsSet.has(treeID)) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
if (options) {
|
||||
filteredColumns = filteredColumns.filter((col) => {
|
||||
return Object.keys(options).every((key) => {
|
||||
// Ignore undefined properties
|
||||
if (options[key] === undefined) {
|
||||
return true;
|
||||
}
|
||||
return options[key] === col[key];
|
||||
});
|
||||
});
|
||||
}
|
||||
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._customColumns[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 options = this._customColumns[dataKey];
|
||||
if (options && options.dataProvider) {
|
||||
return options.dataProvider(item, dataKey);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if column options is valid.
|
||||
* All its children must be valid. Otherwise, the validation fails.
|
||||
* @param {ItemTreeCustomColumnOptions[]} options - An array of options to validate
|
||||
* @returns {boolean} true if the options are valid
|
||||
*/
|
||||
_validateColumnOption(options) {
|
||||
// Check if the input option has duplicate dataKeys
|
||||
const noInputDuplicates = !options.find((opt, i, arr) => arr.findIndex(o => o.dataKey === opt.dataKey) !== i);
|
||||
if (!noInputDuplicates) {
|
||||
Zotero.warn(`ItemTree Column options have duplicate dataKey.`);
|
||||
}
|
||||
const noDeniedProperties = options.every((option) => {
|
||||
const valid = !option.primary;
|
||||
return valid;
|
||||
});
|
||||
const requiredProperties = options.every((option) => {
|
||||
const valid = option.dataKey && option.label && option.pluginID;
|
||||
if (!valid) {
|
||||
Zotero.warn(`ItemTree Column option ${JSON.stringify(option)} must have dataKey, label, and pluginID.`);
|
||||
}
|
||||
return valid;
|
||||
});
|
||||
const noRegisteredDuplicates = options.every((option) => {
|
||||
const valid = !this._customColumns[option.dataKey] && !ITEMTREE_COLUMNS.find(col => col.dataKey === option.dataKey);
|
||||
if (!valid) {
|
||||
Zotero.warn(`ItemTree Column option ${JSON.stringify(option)} with dataKey ${option.dataKey} already exists.`);
|
||||
}
|
||||
return valid;
|
||||
});
|
||||
return noInputDuplicates && noDeniedProperties && requiredProperties && noRegisteredDuplicates;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a new column or new columns.
|
||||
* If the options is an array, all its children must be valid.
|
||||
* Otherwise, no columns are added.
|
||||
* @param {ItemTreeCustomColumnOptions | ItemTreeCItemTreeCustomColumnOptionsolumnOptions[]} options - An option or array of options to add
|
||||
* @returns {string | string[] | false} - The dataKey(s) of the added column(s) or false if no columns were added
|
||||
*/
|
||||
_addColumns(options) {
|
||||
const isSingle = !Array.isArray(options);
|
||||
if (isSingle) {
|
||||
options = [options];
|
||||
}
|
||||
options.forEach((o) => {
|
||||
o.dataKey = this._namespacedDataKey(o);
|
||||
o.enabledTreeIDs = o.enabledTreeIDs || ["main"];
|
||||
if (o.enabledTreeIDs.includes("*")) {
|
||||
o.enabledTreeIDs = ["*"];
|
||||
}
|
||||
o.showInColumnPicker = o.showInColumnPicker === undefined ? true : o.showInColumnPicker;
|
||||
});
|
||||
// If any check fails, return check results
|
||||
if (!this._validateColumnOption(options)) {
|
||||
return false;
|
||||
}
|
||||
for (const opt of options) {
|
||||
this._customColumns[opt.dataKey] = Object.assign({}, opt, { custom: true });
|
||||
}
|
||||
return isSingle ? options[0].dataKey : options.map(opt => opt.dataKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a column option
|
||||
* @param {string | string[]} dataKeys - The dataKey of the column to remove
|
||||
* @returns {boolean} - True if column(s) were removed, false if not
|
||||
*/
|
||||
_removeColumns(dataKeys) {
|
||||
if (!Array.isArray(dataKeys)) {
|
||||
dataKeys = [dataKeys];
|
||||
}
|
||||
// If any check fails, return check results and do not remove any columns
|
||||
for (const key of dataKeys) {
|
||||
if (!this._customColumns[key]) {
|
||||
Zotero.warn(`ItemTree Column option with dataKey ${key} does not exist.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const key of dataKeys) {
|
||||
delete this._customColumns[key];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the dataKey is namespaced with the plugin ID
|
||||
* @param {ItemTreeCustomColumnOptions} options
|
||||
* @returns {string}
|
||||
*/
|
||||
_namespacedDataKey(options) {
|
||||
if (options.pluginID && options.dataKey) {
|
||||
// Make sure the return value is valid as class name or element id
|
||||
return `${options.pluginID}-${options.dataKey}`.replace(/[^a-zA-Z0-9-_]/g, "-");
|
||||
}
|
||||
return options.dataKey;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset the item trees to update the columns
|
||||
*/
|
||||
async _notifyItemTrees() {
|
||||
await Zotero.DB.executeTransaction(async function () {
|
||||
Zotero.Notifier.queue(
|
||||
'refresh',
|
||||
'itemtree',
|
||||
[],
|
||||
{},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister all columns registered by a plugin
|
||||
* @param {string} pluginID - Plugin ID
|
||||
*/
|
||||
async _unregisterColumnByPluginID(pluginID) {
|
||||
const columns = this.getCustomColumns(undefined, { pluginID });
|
||||
if (columns.length === 0) {
|
||||
return;
|
||||
}
|
||||
// Remove the columns one by one
|
||||
// This is to ensure that the columns are removed and not interrupted by any non-existing columns
|
||||
columns.forEach(column => this._removeColumns(column.dataKey));
|
||||
Zotero.debug(`ItemTree columns registered by plugin ${pluginID} unregistered due to shutdown`);
|
||||
await this._notifyItemTrees();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the shutdown observer is added
|
||||
* @returns {void}
|
||||
*/
|
||||
_addPluginShutdownObserver() {
|
||||
if (this._observerAdded) {
|
||||
return;
|
||||
}
|
||||
|
||||
Zotero.Plugins.addObserver({
|
||||
shutdown: ({ id: pluginID }) => {
|
||||
this._unregisterColumnByPluginID(pluginID);
|
||||
}
|
||||
});
|
||||
this._observerAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
Zotero.ItemTreeManager = new ItemTreeManager();
|
|
@ -33,7 +33,8 @@ Zotero.Notifier = new function(){
|
|||
var _types = [
|
||||
'collection', 'search', 'share', 'share-items', 'item', 'file',
|
||||
'collection-item', 'item-tag', 'tag', 'setting', 'group', 'trash',
|
||||
'bucket', 'relation', 'feed', 'feedItem', 'sync', 'api-key', 'tab'
|
||||
'bucket', 'relation', 'feed', 'feedItem', 'sync', 'api-key', 'tab',
|
||||
'itemtree'
|
||||
];
|
||||
var _transactionID = false;
|
||||
var _queue = {};
|
||||
|
|
|
@ -154,6 +154,7 @@ const xpcomFilesLocal = [
|
|||
'connector/httpIntegrationClient',
|
||||
'connector/server_connector',
|
||||
'connector/server_connectorIntegration',
|
||||
'itemTreeManager',
|
||||
];
|
||||
|
||||
Components.utils.import("resource://gre/modules/ComponentUtils.jsm");
|
||||
|
|
Loading…
Add table
Reference in a new issue