zotero/chrome/content/zotero/elements/attachmentsBox.js
abaevbog 91bc1a3e2d
vpat 6: aria labels for buttons in item/context panes (#3957)
Added aria labels and button roles for buttons in the
item/Collection pane. This includes buttons in the header
as well as rows from notes, related, collections, and
attachments section that act as buttons. Also added
missing aria-labelledby for mini-itembox rows below
attachment preview to have filename, dateModified
and etc. properly labelled.

This also covers most of vpat 16.

This does not affect how tags/header/itemBox entries are announced.
There is a separate issue with NVDA and JAWS sometimes struggling
with announcing text inside of `editable-text` component,
so this will be handled separately.
2024-04-12 04:53:35 -04:00

254 lines
7.2 KiB
JavaScript

/*
***** BEGIN LICENSE BLOCK *****
Copyright © 2023 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";
{
class AttachmentsBox extends ItemPaneSectionElementBase {
content = MozXULElement.parseXULToFragment(`
<collapsible-section data-l10n-id="section-attachments" data-pane="attachments" extra-buttons="add">
<html:div class="body">
<attachment-preview tabindex="0" data-l10n-id="attachment-preview"/>
<html:div class="attachments-container"></html:div>
</html:div>
</collapsible-section>
<popupset/>
`);
_attachmentIDs = [];
_preview = null;
get item() {
return this._item;
}
set item(item) {
if (this._item === item) {
return;
}
super.item = item;
let hidden = !item?.isRegularItem() || item?.isFeedItem;
this.hidden = hidden;
this._preview.disableResize = !!hidden;
}
get inTrash() {
if (this.tabType != "library") {
return false;
}
return ZoteroPane.collectionsView.selectedTreeRow
&& ZoteroPane.collectionsView.selectedTreeRow.isTrash();
}
get usePreview() {
return this.hasAttribute('data-use-preview');
}
set usePreview(val) {
this.toggleAttribute('data-use-preview', val);
this.updatePreview();
}
init() {
this.initCollapsibleSection();
this._section.addEventListener('add', this._handleAdd);
this._attachments = this.querySelector('.attachments-container');
this._addPopup = document.getElementById('zotero-add-attachment-popup').cloneNode(true);
this._addPopup.id = '';
this.querySelector('popupset').append(this._addPopup);
this._preview = this.querySelector('attachment-preview');
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'attachmentsBox');
this._section._contextMenu.addEventListener('popupshowing', this._handleContextMenu, { once: true });
}
destroy() {
this._section?.removeEventListener('add', this._handleAdd);
Zotero.Notifier.unregisterObserver(this._notifierID);
}
notify(action, type, ids) {
if (!this._item?.isRegularItem()) return;
this.updatePreview();
this._updateAttachmentIDs().then(() => {
let attachments = Zotero.Items.get((this._attachmentIDs).filter(id => ids.includes(id)));
if (attachments.length === 0) {
return;
}
if (action == 'add') {
for (let attachment of attachments) {
this.addRow(attachment);
}
}
else if (action == 'modify') {
for (let attachment of attachments) {
let row = this.querySelector(`attachment-row[attachment-id="${attachment.id}"]`);
let open = false;
if (row) {
open = row.open;
row.remove();
}
this.addRow(attachment).open = open;
}
}
else if (action == 'delete') {
for (let attachment of attachments) {
let row = this.querySelector(`attachment-row[attachment-id="${attachment.id}"]`);
if (row) {
row.remove();
}
}
}
this.updateCount();
});
}
addRow(attachment, open = false) {
let row = document.createXULElement('attachment-row');
this._updateRowAttributes(row, attachment);
// Set open state before adding to dom to prevent animation
row.toggleAttribute("open", open);
let index = this._attachmentIDs.indexOf(attachment.id);
if (index < 0 || index >= this._attachments.children.length) {
this._attachments.append(row);
}
else {
this._attachments.insertBefore(row, this._attachments.children[index]);
}
return row;
}
render() {
if (!this._item) return;
if (this._isAlreadyRendered()) return;
this.updateCount();
}
async asyncRender() {
if (!this._item) return;
if (this._isAlreadyRendered("async")) return;
await this._updateAttachmentIDs();
let itemAttachments = Zotero.Items.get(this._attachmentIDs);
this._attachments.querySelectorAll("attachment-row").forEach(e => e.remove());
for (let attachment of itemAttachments) {
this.addRow(attachment);
}
this.usePreview = Zotero.Prefs.get('showAttachmentPreview');
}
updateCount() {
let count = this._item.numAttachments(this.inTrash);
this._section.setCount(count);
}
async updatePreview() {
if (!this.usePreview || !this._section.open) {
return;
}
let attachment = await this._getPreviewAttachment();
if (!attachment) {
this.toggleAttribute('data-use-preview', false);
return;
}
this._preview.item = attachment;
await this._preview.render();
}
async _getPreviewAttachment() {
let attachment = await this._item.getBestAttachment();
if (this.tabType === "reader"
&& Zotero_Tabs._getTab(Zotero_Tabs.selectedID)?.tab?.data?.itemID == attachment.id) {
// In the reader, only show the preview when viewing a secondary attachment
return null;
}
return attachment;
}
_handleAdd = (event) => {
this._section.open = true;
ZoteroPane.updateAddAttachmentMenu(this._addPopup);
this._addPopup.openPopup(event.detail.button, 'after_end');
};
_handleTogglePreview = () => {
let toOpen = !Zotero.Prefs.get('showAttachmentPreview');
Zotero.Prefs.set('showAttachmentPreview', toOpen);
this.usePreview = toOpen;
let menu = this._section._contextMenu.querySelector('.zotero-menuitem-toggle-preview');
menu.dataset.l10nArgs = `{ "type": "${this.usePreview ? "open" : "collapsed"}" }`;
if (toOpen) {
this._preview.render();
}
};
_handleContextMenu = async () => {
if (!await this._getPreviewAttachment()) return;
let contextMenu = this._section._contextMenu;
let menu = document.createXULElement("menuitem");
menu.classList.add('menuitem-iconic', 'zotero-menuitem-toggle-preview');
menu.setAttribute('data-l10n-id', 'toggle-preview');
menu.addEventListener('command', this._handleTogglePreview);
menu.dataset.l10nArgs = `{ "type": "${this.usePreview ? "open" : "collapsed"}" }`;
contextMenu.append(menu);
};
_updateRowAttributes(row, attachment) {
let hidden = !this.inTrash && attachment.deleted;
row.attachment = attachment;
row.hidden = hidden;
}
async _updateAttachmentIDs() {
let sortedAttachmentIDs = [];
let allAttachmentIDs = this._item.getAttachments(true);
let bestAttachment = await this._item.getBestAttachment();
if (bestAttachment) {
sortedAttachmentIDs.push(
bestAttachment.id,
...allAttachmentIDs.filter(id => id && id !== bestAttachment.id)
);
}
else {
sortedAttachmentIDs = allAttachmentIDs;
}
this._attachmentIDs = sortedAttachmentIDs;
}
}
customElements.define("attachments-box", AttachmentsBox);
}