zotero/chrome/content/zotero/elements/librariesCollectionsBox.js
Abe Jellinek b7437faf55
Some checks are pending
CI / Build, Upload, Test (push) Waiting to run
Convert ItemPane{SectionElement,Container}Base to ES modules
2026-05-18 15:48:31 -04:00

295 lines
8.8 KiB
JavaScript

/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2020 Corporation for Digital Scholarship
Vienna, Virginia, USA
https://www.zotero.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 *****
*/
"use strict";
import { getCSSIcon } from 'components/icons';
{
const { ItemPaneSectionElementBase } = ChromeUtils.importESModule(
"chrome://zotero/content/elements/itemPaneSectionElementBase.mjs",
{ global: "current" }
);
class LibrariesCollectionsBox extends ItemPaneSectionElementBase {
content = MozXULElement.parseXULToFragment(`
<collapsible-section data-l10n-id="section-libraries-collections" data-pane="libraries-collections" extra-buttons="add">
<html:div class="body"/>
</collapsible-section>
<popupset>
<menupopup class="add-popup"/>
</popupset>
`, ['chrome://zotero/locale/zotero.dtd']);
_linkedItems = [];
get item() {
return this._item;
}
set item(item) {
let isRegularItem = item?.isRegularItem() && !item?.isFeedItem;
let isStandaloneAttachment = item?.isAttachment() && !item.parentItemID;
let isStandaloneNote = item?.isNote() && !item.parentItemID;
if (isRegularItem || isStandaloneAttachment || isStandaloneNote) {
this.hidden = false;
}
else {
this.hidden = true;
return;
}
this._item = item;
this._linkedItems = [];
}
get _renderDependencies() {
return [...super._renderDependencies, this.collectionTreeRow?.id];
}
init() {
this._notifierID = Zotero.Notifier.registerObserver(this, ['item', 'collection'], 'librariesCollectionsBox');
this._body = this.querySelector('.body');
this.initCollapsibleSection();
this._addPopup = this.querySelector('.add-popup');
this._addPopup.addEventListener('popupshowing', this._handleAddPopupShowing);
this._section.addEventListener('add', this._handleAdd);
}
destroy() {
Zotero.Notifier.unregisterObserver(this._notifierID);
this._section?.removeEventListener('add', this._handleAdd);
this._addPopup?.removeEventListener('popupshowing', this._handleAddPopupShowing);
}
notify(action, type, ids) {
if (!this._item) return;
if (action == 'modify'
&& type == "item"
&& (ids.includes(this._item.id) || this._linkedItems.some(item => ids.includes(item.id)))) {
this._forceRenderAll();
}
if (["modify", "trash"].includes(action) && type == "collection") {
let isRelevantCollection = ids.some(id => Zotero.Collections.get(id).hasItem(this._item));
if (isRelevantCollection) {
this._forceRenderAll();
}
}
}
_buildRow(obj, level, contextItem) {
let isContext = obj instanceof Zotero.Collection && !contextItem.inCollection(obj.id);
let row = document.createElement('div');
row.classList.add('row');
row.dataset.id = obj.treeViewID;
row.dataset.level = level;
row.style.setProperty('--level', level);
row.classList.toggle('context', isContext);
let box = document.createElement('div');
box.className = 'box keyboard-clickable';
box.setAttribute("tabindex", "0");
box.setAttribute('aria-label', obj.name);
box.setAttribute('role', "button");
let iconName;
if (obj instanceof Zotero.Group) {
iconName = 'library-group';
}
else if (obj instanceof Zotero.Library) {
iconName = 'library';
}
else {
iconName = 'collection';
}
let icon = getCSSIcon(iconName);
box.append(icon);
let text = document.createElement('span');
text.classList.add('label');
text.textContent = obj.name;
box.append(text);
row.append(box);
if (this.editable && obj instanceof Zotero.Collection && !isContext) {
let remove = document.createXULElement('toolbarbutton');
remove.className = 'zotero-clicky zotero-clicky-minus';
remove.setAttribute("tabindex", "0");
remove.setAttribute("data-l10n-id", 'section-button-remove');
remove.addEventListener('command', () => {
if (Services.prompt.confirm(
window,
Zotero.getString('pane.items.remove.title'),
Zotero.getString('pane.items.removeFromOther', [obj.name])
)) {
contextItem.removeFromCollection(obj.id);
contextItem.saveTx();
}
});
row.append(remove);
}
let isCurrent = this.tabType === 'library'
&& this.collectionTreeRow?.id == obj.treeViewID;
box.classList.toggle('current', isCurrent);
// Disable clicky if this is a context row or we're already in the library/collection it points to
let disableClicky = isContext || isCurrent;
box.toggleAttribute('disabled', disableClicky);
if (!disableClicky) {
box.addEventListener('click', async () => {
await ZoteroPane.collectionsView.selectByID(obj.treeViewID);
// Do not try to view deleted items in a collection.
// They do not appear outside of trash, and selecting a deleted item
// will re-open trash in collectionTree.
if (!contextItem.deleted) {
await ZoteroPane.selectItem(contextItem.id);
}
});
}
return row;
}
_findRow(obj) {
return this._body.querySelector(`.row[data-id="${obj.treeViewID}"]`);
}
_getChildren(row = null, deep = false) {
let rows = Array.from(this._body.querySelectorAll('.row'));
let startIndex = row ? rows.indexOf(row) + 1 : 0;
let level = row ? parseInt(row.dataset.level) + 1 : 0;
let children = [];
for (let i = startIndex; i < rows.length; i++) {
let childLevel = parseInt(rows[i].dataset.level);
if (childLevel == level || deep && childLevel > level) {
children.push(rows[i]);
}
else if (childLevel < level) {
break;
}
}
return children;
}
_addObject(obj, contextItem) {
let existingRow = this._findRow(obj);
if (existingRow) {
return existingRow;
}
let parent = obj instanceof Zotero.Library
? null
: (obj.parentID ? Zotero.Collections.get(obj.parentID) : Zotero.Libraries.get(obj.libraryID));
let parentRow = parent && this._findRow(parent);
if (parent && !parentRow) {
parentRow = this._addObject(parent, contextItem);
}
let row = this._buildRow(obj, parentRow ? parseInt(parentRow.dataset.level) + 1 : 0, contextItem);
let siblings = this._getChildren(parentRow);
let added = false;
for (let sibling of siblings) {
if (Zotero.localeCompare(sibling.querySelector('.label').textContent, obj.name) > 0) {
sibling.before(row);
added = true;
break;
}
}
if (!added) {
if (siblings.length) {
let lastSibling = siblings[siblings.length - 1];
let childrenOfLastSibling = this._getChildren(lastSibling, true);
if (childrenOfLastSibling.length) {
childrenOfLastSibling[childrenOfLastSibling.length - 1].after(row);
}
else {
lastSibling.after(row);
}
}
else if (parentRow) {
parentRow.after(row);
}
else {
this._body.append(row);
}
}
return row;
}
render() {
if (!this._item) return;
if (!this._section.open) return;
if (this._isAlreadyRendered()) return;
this._body.replaceChildren();
for (let item of [this._item, ...this._linkedItems]) {
this._addObject(Zotero.Libraries.get(item.libraryID), item);
for (let collection of Zotero.Collections.get(item.getCollections())) {
this._addObject(collection, item);
}
}
}
async asyncRender() {
if (!this._item) {
return;
}
if (this._isAlreadyRendered("async")) return;
// Skip if already rendered
if (this._linkedItems.length > 0) {
return;
}
this._linkedItems = (await Promise.all(Zotero.Libraries.getAll()
.filter(lib => lib.libraryID !== this._item.libraryID)
.map(lib => this._item.getLinkedItem(lib.libraryID, true))))
.filter(Boolean);
for (let item of this._linkedItems) {
this._addObject(Zotero.Libraries.get(item.libraryID), item);
for (let collection of Zotero.Collections.get(item.getCollections())) {
this._addObject(collection, item);
}
}
}
_handleAdd = (event) => {
this._addPopup.openPopupAtScreen(
event.detail.button.screenX,
event.detail.button.screenY,
true
);
this._section.open = true;
};
_handleAddPopupShowing = (event) => {
ZoteroPane.buildAddItemToCollectionMenu(event, [this._item]);
};
}
customElements.define("libraries-collections-box", LibrariesCollectionsBox);
}