mirror of
https://github.com/zotero/zotero.git
synced 2026-08-28 05:25:31 +00:00
295 lines
8.8 KiB
JavaScript
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);
|
|
}
|