Fix attachments & annotation box refresh bugs and add tests (#4031)
Fixes #3993 Fixes #3995 Closes #4082
This commit is contained in:
parent
07e8f0a01d
commit
91c0c28b5d
10 changed files with 806 additions and 49 deletions
|
@ -46,53 +46,95 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
set item(item) {
|
set item(item) {
|
||||||
super.item = item;
|
super.item = (item instanceof Zotero.Item && item.isFileAttachment()) ? item : null;
|
||||||
this._updateHidden();
|
this._updateHidden();
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.initCollapsibleSection();
|
this.initCollapsibleSection();
|
||||||
|
|
||||||
|
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'attachmentAnnotationsBox');
|
||||||
|
|
||||||
this._body = this.querySelector('.body');
|
this._body = this.querySelector('.body');
|
||||||
|
|
||||||
|
this._annotationItems = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {}
|
destroy() {
|
||||||
|
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||||
|
}
|
||||||
|
|
||||||
notify(action, type, ids) {
|
notify(event, _type, ids, _extraData) {
|
||||||
if (action == 'modify' && this.item && ids.includes(this.item.id)) {
|
if (!this.item) return;
|
||||||
this._forceRenderAll();
|
|
||||||
|
this._annotationItems = this.item.getAnnotations();
|
||||||
|
let annotations = this._annotationItems.filter(
|
||||||
|
annotation => ids.includes(annotation.id));
|
||||||
|
|
||||||
|
if (["add", "modify"].includes(event)) {
|
||||||
|
for (let annotation of annotations) {
|
||||||
|
let row = this.querySelector(`annotation-row[annotation-id="${annotation.id}"]`);
|
||||||
|
row?.remove();
|
||||||
|
this.addRow(annotation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if (event == 'delete') {
|
||||||
|
for (let id of ids) {
|
||||||
|
let row = this.querySelector(`annotation-row[annotation-id="${id}"]`);
|
||||||
|
row?.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
this._annotationItems = this.item.getAnnotations();
|
||||||
|
this.updateCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
async asyncRender() {
|
||||||
if (!this.initialized || !this.item?.isFileAttachment()) return;
|
if (!this.initialized || !this.item?.isFileAttachment()) return;
|
||||||
if (this._isAlreadyRendered()) return;
|
if (this._isAlreadyRendered()) return;
|
||||||
|
|
||||||
let annotations = this.item.getAnnotations();
|
await Zotero.PDFWorker.renderAttachmentAnnotations(this.item.id);
|
||||||
this._section.setCount(annotations.length);
|
|
||||||
|
|
||||||
this._body.replaceChildren();
|
this._body.replaceChildren();
|
||||||
|
|
||||||
if (!this._section.open) {
|
if (!this._section.open || this._annotationItems.length === 0) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = annotations.length;
|
|
||||||
if (count === 0) {
|
|
||||||
this.hidden = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hidden = false;
|
this.hidden = false;
|
||||||
for (let annotation of annotations) {
|
for (let annotation of this._annotationItems) {
|
||||||
let row = document.createXULElement('annotation-row');
|
this.addRow(annotation);
|
||||||
row.annotation = annotation;
|
|
||||||
this._body.append(row);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addRow(annotation) {
|
||||||
|
let row = document.createXULElement('annotation-row');
|
||||||
|
row.annotation = annotation;
|
||||||
|
|
||||||
|
let index = this._annotationItems.findIndex(item => item.id == annotation.id);
|
||||||
|
if (index < 0 || index >= this._body.children.length) {
|
||||||
|
this._body.append(row);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._body.insertBefore(row, this._body.children[index]);
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCount() {
|
||||||
|
let count = this._annotationItems.length;
|
||||||
|
this._section.setCount(count);
|
||||||
|
if (count === 0) {
|
||||||
|
this.hidden = true;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
_updateHidden() {
|
_updateHidden() {
|
||||||
this.hidden = !this.item?.isFileAttachment() || this.tabType == "reader";
|
this.hidden = !this.item || this.tabType == "reader";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define("attachment-annotations-box", AttachmentAnnotationsBox);
|
customElements.define("attachment-annotations-box", AttachmentAnnotationsBox);
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
this._section = null;
|
this._section = null;
|
||||||
this._preview = null;
|
this._preview = null;
|
||||||
|
|
||||||
this._isRendering = false;
|
this._asyncRendering = false;
|
||||||
|
|
||||||
this._isEditingFilename = false;
|
this._isEditingFilename = false;
|
||||||
}
|
}
|
||||||
|
@ -304,12 +304,12 @@
|
||||||
|
|
||||||
async asyncRender() {
|
async asyncRender() {
|
||||||
if (!this.item) return;
|
if (!this.item) return;
|
||||||
if (this._isRendering) return;
|
if (this._asyncRendering) return;
|
||||||
if (!this._section.open) return;
|
if (!this._section.open) return;
|
||||||
if (this._isAlreadyRendered("async")) return;
|
if (this._isAlreadyRendered("async")) return;
|
||||||
|
|
||||||
Zotero.debug('Refreshing attachment box');
|
Zotero.debug('Refreshing attachment box');
|
||||||
this._isRendering = true;
|
this._asyncRendering = true;
|
||||||
// Cancel editing filename when refreshing
|
// Cancel editing filename when refreshing
|
||||||
this._isEditingFilename = false;
|
this._isEditingFilename = false;
|
||||||
|
|
||||||
|
@ -457,7 +457,7 @@
|
||||||
else {
|
else {
|
||||||
selectButton.hidden = true;
|
selectButton.hidden = true;
|
||||||
}
|
}
|
||||||
this._isRendering = false;
|
this._asyncRendering = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onViewClick(event) {
|
onViewClick(event) {
|
||||||
|
|
|
@ -165,6 +165,7 @@
|
||||||
this.addEventListener("focusin", this._handleFocusIn);
|
this.addEventListener("focusin", this._handleFocusIn);
|
||||||
this.addEventListener("keypress", this._handleKeypress);
|
this.addEventListener("keypress", this._handleKeypress);
|
||||||
this.setAttribute("data-preview-type", "unknown");
|
this.setAttribute("data-preview-type", "unknown");
|
||||||
|
this._notifierID = Zotero.Notifier.registerObserver(this, ["item"], "attachmentPreview");
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
@ -177,6 +178,38 @@
|
||||||
this.removeEventListener("click", this._handleFocusIn);
|
this.removeEventListener("click", this._handleFocusIn);
|
||||||
this.removeEventListener("focusin", this._handleFocusIn);
|
this.removeEventListener("focusin", this._handleFocusIn);
|
||||||
this.removeEventListener("keypress", this._handleKeypress);
|
this.removeEventListener("keypress", this._handleKeypress);
|
||||||
|
Zotero.Notifier.unregisterObserver(this._notifierID);
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(event, type, ids, extraData) {
|
||||||
|
if (!this.item) return;
|
||||||
|
if (this.isReaderType && this._reader) {
|
||||||
|
// Following chrome/content/zotero/xpcom/reader.js
|
||||||
|
if (event === "delete") {
|
||||||
|
let disappearedIDs = this._reader.annotationItemIDs.filter(x => ids.includes(x));
|
||||||
|
if (disappearedIDs.length) {
|
||||||
|
let keys = disappearedIDs.map(id => extraData[id].key);
|
||||||
|
this._reader.unsetAnnotations(keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (["add", "modify"].includes(event)) {
|
||||||
|
let annotationItems = this.item.getAnnotations();
|
||||||
|
this._reader.annotationItemIDs = annotationItems.map(x => x.id);
|
||||||
|
let affectedAnnotations = annotationItems.filter(({ id }) => (
|
||||||
|
ids.includes(id)
|
||||||
|
&& !(extraData && extraData[id] && extraData[id].instanceID === this._reader._instanceID)
|
||||||
|
));
|
||||||
|
if (affectedAnnotations.length) {
|
||||||
|
this._reader.setAnnotations(affectedAnnotations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isMediaType) {
|
||||||
|
if (["refresh", "modify"].includes(event) && ids.includes(this.item.id)) {
|
||||||
|
this.discard().then(() => this.render());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async render() {
|
async render() {
|
||||||
|
@ -184,6 +217,14 @@
|
||||||
if (!this.initialized && itemID === this._renderingItemID) {
|
if (!this.initialized && itemID === this._renderingItemID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// For tests
|
||||||
|
let resolve;
|
||||||
|
if (Zotero.test) {
|
||||||
|
this._renderPromise = new Promise(r => resolve = r);
|
||||||
|
// Expose `resolve` for `this.discard`
|
||||||
|
this._renderPromise.resolve = resolve;
|
||||||
|
}
|
||||||
|
|
||||||
this._renderingItemID = itemID;
|
this._renderingItemID = itemID;
|
||||||
let success = false;
|
let success = false;
|
||||||
if (this.isValidType && await this._item.fileExists()) {
|
if (this.isValidType && await this._item.fileExists()) {
|
||||||
|
@ -203,6 +244,9 @@
|
||||||
if (this._renderingItemID === itemID) {
|
if (this._renderingItemID === itemID) {
|
||||||
this._renderingItemID = null;
|
this._renderingItemID = null;
|
||||||
}
|
}
|
||||||
|
if (Zotero.test) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async discard(force = false) {
|
async discard(force = false) {
|
||||||
|
@ -213,7 +257,7 @@
|
||||||
if (this._isDiscarding) {
|
if (this._isDiscarding) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!force && this.isVisible) {
|
if (!force && (this.isVisible || !this._reader)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._isDiscarding = true;
|
this._isDiscarding = true;
|
||||||
|
@ -237,6 +281,7 @@
|
||||||
this._id("preview")?.after(this.nextPreview);
|
this._id("preview")?.after(this.nextPreview);
|
||||||
this.setPreviewStatus("loading");
|
this.setPreviewStatus("loading");
|
||||||
this._isDiscarding = false;
|
this._isDiscarding = false;
|
||||||
|
this._renderPromise?.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
async openAttachment(event) {
|
async openAttachment(event) {
|
||||||
|
|
|
@ -70,7 +70,9 @@
|
||||||
|
|
||||||
set usePreview(val) {
|
set usePreview(val) {
|
||||||
this.toggleAttribute('data-use-preview', val);
|
this.toggleAttribute('data-use-preview', val);
|
||||||
this.updatePreview();
|
if (this.item) {
|
||||||
|
this.updatePreview();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
@ -83,11 +85,17 @@
|
||||||
this._addPopup.id = '';
|
this._addPopup.id = '';
|
||||||
this.querySelector('popupset').append(this._addPopup);
|
this.querySelector('popupset').append(this._addPopup);
|
||||||
|
|
||||||
|
this.usePreview = Zotero.Prefs.get('showAttachmentPreview');
|
||||||
this._preview = this.querySelector('attachment-preview');
|
this._preview = this.querySelector('attachment-preview');
|
||||||
|
|
||||||
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'attachmentsBox');
|
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'attachmentsBox');
|
||||||
|
|
||||||
this._section._contextMenu.addEventListener('popupshowing', this._handleContextMenu, { once: true });
|
this._section._contextMenu.addEventListener('popupshowing', this._handleContextMenu, { once: true });
|
||||||
|
|
||||||
|
// For tests
|
||||||
|
this._asyncRendering = false;
|
||||||
|
// Indicate if the preview should update, can be none | initial | final
|
||||||
|
this._renderStage = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
@ -96,16 +104,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
notify(action, type, ids) {
|
notify(action, type, ids) {
|
||||||
if (ids.includes(this._item?.id)) {
|
|
||||||
this._resetRenderedFlags();
|
|
||||||
}
|
|
||||||
if (!this._item?.isRegularItem()) return;
|
if (!this._item?.isRegularItem()) return;
|
||||||
|
|
||||||
this._updateAttachmentIDs().then(() => {
|
this._updateAttachmentIDs().then(() => {
|
||||||
this.updatePreview();
|
this.updatePreview();
|
||||||
|
|
||||||
let attachments = Zotero.Items.get((this._attachmentIDs).filter(id => ids.includes(id)));
|
let attachments = Zotero.Items.get((this._attachmentIDs).filter(id => ids.includes(id)));
|
||||||
if (attachments.length === 0) {
|
if (attachments.length === 0 && action !== "delete") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (action == 'add') {
|
if (action == 'add') {
|
||||||
|
@ -113,20 +118,20 @@
|
||||||
this.addRow(attachment);
|
this.addRow(attachment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (action == 'modify') {
|
// When annotation added to attachment, action=modify
|
||||||
|
// When annotation deleted from attachment, action=refresh
|
||||||
|
else if (action == 'modify' || action == 'refresh') {
|
||||||
for (let attachment of attachments) {
|
for (let attachment of attachments) {
|
||||||
let row = this.querySelector(`attachment-row[attachment-id="${attachment.id}"]`);
|
let row = this.querySelector(`attachment-row[attachment-id="${attachment.id}"]`);
|
||||||
let open = false;
|
|
||||||
if (row) {
|
if (row) {
|
||||||
open = row.open;
|
|
||||||
row.remove();
|
row.remove();
|
||||||
}
|
}
|
||||||
this.addRow(attachment).open = open;
|
this.addRow(attachment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (action == 'delete') {
|
else if (action == 'delete') {
|
||||||
for (let attachment of attachments) {
|
for (let id of ids) {
|
||||||
let row = this.querySelector(`attachment-row[attachment-id="${attachment.id}"]`);
|
let row = this.querySelector(`attachment-row[attachment-id="${id}"]`);
|
||||||
if (row) {
|
if (row) {
|
||||||
row.remove();
|
row.remove();
|
||||||
}
|
}
|
||||||
|
@ -137,11 +142,9 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addRow(attachment, open = false) {
|
addRow(attachment) {
|
||||||
let row = document.createXULElement('attachment-row');
|
let row = document.createXULElement('attachment-row');
|
||||||
this._updateRowAttributes(row, attachment);
|
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);
|
let index = this._attachmentIDs.indexOf(attachment.id);
|
||||||
if (index < 0 || index >= this._attachments.children.length) {
|
if (index < 0 || index >= this._attachments.children.length) {
|
||||||
|
@ -156,12 +159,15 @@
|
||||||
render() {
|
render() {
|
||||||
if (!this._item) return;
|
if (!this._item) return;
|
||||||
if (this._isAlreadyRendered()) return;
|
if (this._isAlreadyRendered()) return;
|
||||||
|
this._renderStage = "initial";
|
||||||
this.updateCount();
|
this.updateCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
async asyncRender() {
|
async asyncRender() {
|
||||||
if (!this._item) return;
|
if (!this._item) return;
|
||||||
if (this._isAlreadyRendered("async")) return;
|
if (this._isAlreadyRendered("async")) return;
|
||||||
|
this._renderStage = "final";
|
||||||
|
this._asyncRendering = true;
|
||||||
|
|
||||||
await this._updateAttachmentIDs();
|
await this._updateAttachmentIDs();
|
||||||
|
|
||||||
|
@ -171,15 +177,29 @@
|
||||||
for (let attachment of itemAttachments) {
|
for (let attachment of itemAttachments) {
|
||||||
this.addRow(attachment);
|
this.addRow(attachment);
|
||||||
}
|
}
|
||||||
this.usePreview = Zotero.Prefs.get('showAttachmentPreview');
|
await this.updatePreview();
|
||||||
|
this._asyncRendering = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCount() {
|
updateCount() {
|
||||||
|
if (!this._item?.isRegularItem()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let count = this._item.numAttachments(this.inTrash);
|
let count = this._item.numAttachments(this.inTrash);
|
||||||
this._section.setCount(count);
|
this._section.setCount(count);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updatePreview() {
|
async updatePreview() {
|
||||||
|
// Skip if asyncRender is not finished/executed, which means the box is invisible
|
||||||
|
// The box will be rendered when it becomes visible
|
||||||
|
if (this._renderStage !== "final") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let attachment = await this._getPreviewAttachment();
|
||||||
|
this.toggleAttribute('data-use-preview', !!attachment && Zotero.Prefs.get('showAttachmentPreview'));
|
||||||
|
if (!attachment) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.usePreview
|
if (!this.usePreview
|
||||||
// Skip only when the section is manually collapsed (when there's attachment),
|
// Skip only when the section is manually collapsed (when there's attachment),
|
||||||
// This is necessary to ensure the rendering of the first added attachment
|
// This is necessary to ensure the rendering of the first added attachment
|
||||||
|
@ -187,11 +207,6 @@
|
||||||
|| (this._attachmentIDs.length && !this._section.open)) {
|
|| (this._attachmentIDs.length && !this._section.open)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let attachment = await this._getPreviewAttachment();
|
|
||||||
if (!attachment) {
|
|
||||||
this.toggleAttribute('data-use-preview', false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._preview.item = attachment;
|
this._preview.item = attachment;
|
||||||
await this._preview.render();
|
await this._preview.render();
|
||||||
}
|
}
|
||||||
|
@ -199,7 +214,7 @@
|
||||||
async _getPreviewAttachment() {
|
async _getPreviewAttachment() {
|
||||||
let attachment = await this._item.getBestAttachment();
|
let attachment = await this._item.getBestAttachment();
|
||||||
if (this.tabType === "reader"
|
if (this.tabType === "reader"
|
||||||
&& Zotero_Tabs._getTab(Zotero_Tabs.selectedID)?.tab?.data?.itemID == attachment.id) {
|
&& Zotero_Tabs._getTab(this.tabID)?.tab?.data?.itemID == attachment.id) {
|
||||||
// In the reader, only show the preview when viewing a secondary attachment
|
// In the reader, only show the preview when viewing a secondary attachment
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -295,6 +295,7 @@
|
||||||
this._itemPaneDeck.appendChild(itemDetails);
|
this._itemPaneDeck.appendChild(itemDetails);
|
||||||
|
|
||||||
itemDetails.editable = editable;
|
itemDetails.editable = editable;
|
||||||
|
itemDetails.tabID = tabID;
|
||||||
itemDetails.tabType = "reader";
|
itemDetails.tabType = "reader";
|
||||||
itemDetails.item = targetItem;
|
itemDetails.item = targetItem;
|
||||||
// Manually cache parentID
|
// Manually cache parentID
|
||||||
|
|
|
@ -100,6 +100,14 @@
|
||||||
this.toggleAttribute('readonly', !editable);
|
this.toggleAttribute('readonly', !editable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get tabID() {
|
||||||
|
return this._tabID;
|
||||||
|
}
|
||||||
|
|
||||||
|
set tabID(tabID) {
|
||||||
|
this._tabID = tabID;
|
||||||
|
}
|
||||||
|
|
||||||
get tabType() {
|
get tabType() {
|
||||||
return this._tabType;
|
return this._tabType;
|
||||||
}
|
}
|
||||||
|
@ -215,12 +223,18 @@
|
||||||
let item = this.item;
|
let item = this.item;
|
||||||
Zotero.debug('Viewing item');
|
Zotero.debug('Viewing item');
|
||||||
this._isRendering = true;
|
this._isRendering = true;
|
||||||
|
// For tests
|
||||||
|
let resolve;
|
||||||
|
if (Zotero.test) {
|
||||||
|
this._renderPromise = new Promise(r => resolve = r);
|
||||||
|
}
|
||||||
|
|
||||||
this.renderCustomSections();
|
this.renderCustomSections();
|
||||||
|
|
||||||
let panes = this.getPanes();
|
let panes = this.getPanes();
|
||||||
for (let box of [this._header, ...panes]) {
|
for (let box of [this._header, ...panes]) {
|
||||||
box.editable = this.editable;
|
box.editable = this.editable;
|
||||||
|
box.tabID = this.tabID;
|
||||||
box.tabType = this.tabType;
|
box.tabType = this.tabType;
|
||||||
box.item = item;
|
box.item = item;
|
||||||
// Execute sync render immediately
|
// Execute sync render immediately
|
||||||
|
@ -260,6 +274,9 @@
|
||||||
if (this.item.id == item.id) {
|
if (this.item.id == item.id) {
|
||||||
this._isRendering = false;
|
this._isRendering = false;
|
||||||
}
|
}
|
||||||
|
if (Zotero.test) {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCustomSections() {
|
renderCustomSections() {
|
||||||
|
|
|
@ -151,6 +151,7 @@
|
||||||
this.mode = "item";
|
this.mode = "item";
|
||||||
|
|
||||||
this._itemDetails.editable = this.editable;
|
this._itemDetails.editable = this.editable;
|
||||||
|
this._itemDetails.tabID = "zotero-pane";
|
||||||
this._itemDetails.tabType = "library";
|
this._itemDetails.tabType = "library";
|
||||||
this._itemDetails.item = item;
|
this._itemDetails.item = item;
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,14 @@ class ItemPaneSectionElementBase extends XULElementBase {
|
||||||
this.toggleAttribute('readonly', !editable);
|
this.toggleAttribute('readonly', !editable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get tabID() {
|
||||||
|
return this._tabID;
|
||||||
|
}
|
||||||
|
|
||||||
|
set tabID(tabID) {
|
||||||
|
this._tabID = tabID;
|
||||||
|
}
|
||||||
|
|
||||||
get tabType() {
|
get tabType() {
|
||||||
return this._tabType;
|
return this._tabType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -384,6 +384,31 @@ async function delay(ms) {
|
||||||
return Zotero.Promise.delay(ms);
|
return Zotero.Promise.delay(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitForFrame() {
|
||||||
|
return waitNoLongerThan(new Promise((resolve) => {
|
||||||
|
requestAnimationFrame(resolve);
|
||||||
|
}), 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForFrames(n) {
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
await waitForFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitNoLongerThan(promise, ms = 1000) {
|
||||||
|
return Promise.race([
|
||||||
|
promise,
|
||||||
|
Zotero.Promise.delay(ms)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForScrollToPane(itemDetails, paneID) {
|
||||||
|
await itemDetails._renderPromise;
|
||||||
|
itemDetails.scrollToPane(paneID, "instant");
|
||||||
|
// Wait for some frames or up to 150ms to ensure the pane is visible
|
||||||
|
await waitForFrames(5);
|
||||||
|
}
|
||||||
|
|
||||||
function clickOnItemsRow(win, itemsView, row) {
|
function clickOnItemsRow(win, itemsView, row) {
|
||||||
itemsView._treebox.scrollToRow(row);
|
itemsView._treebox.scrollToRow(row);
|
||||||
|
@ -1115,3 +1140,4 @@ async function startHTTPServer(port = null) {
|
||||||
var baseURL = `http://localhost:${port}/`
|
var baseURL = `http://localhost:${port}/`
|
||||||
return { httpd, port, baseURL };
|
return { httpd, port, baseURL };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,38 @@
|
||||||
describe("Item pane", function () {
|
describe("Item pane", function () {
|
||||||
var win, doc, itemsView;
|
var win, doc, ZoteroPane, Zotero_Tabs, ZoteroContextPane, itemsView;
|
||||||
|
|
||||||
|
async function waitForPreviewBoxRender(box) {
|
||||||
|
let success = await waitForCallback(
|
||||||
|
() => box._asyncRenderItemID && !box._asyncRendering);
|
||||||
|
if (!success) {
|
||||||
|
throw new Error("Wait for box render time out");
|
||||||
|
}
|
||||||
|
await box._preview._renderPromise;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForPreviewBoxReader(box, itemID) {
|
||||||
|
await waitForPreviewBoxRender(box);
|
||||||
|
let success = await waitForCallback(
|
||||||
|
() => box._preview._reader?.itemID == itemID, 100, 3000);
|
||||||
|
if (!success) {
|
||||||
|
throw new Error("Wait for box preview reader time out");
|
||||||
|
}
|
||||||
|
await box._preview._reader._initPromise;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPreviewDisplayed(box) {
|
||||||
|
return !!(box._preview.hasPreview
|
||||||
|
&& win.getComputedStyle(box._preview).display !== "none");
|
||||||
|
}
|
||||||
|
|
||||||
before(function* () {
|
before(function* () {
|
||||||
win = yield loadZoteroPane();
|
win = yield loadZoteroPane();
|
||||||
doc = win.document;
|
doc = win.document;
|
||||||
|
ZoteroPane = win.ZoteroPane;
|
||||||
|
Zotero_Tabs = win.Zotero_Tabs;
|
||||||
|
ZoteroContextPane = win.ZoteroContextPane;
|
||||||
itemsView = win.ZoteroPane.itemsView;
|
itemsView = win.ZoteroPane.itemsView;
|
||||||
});
|
});
|
||||||
after(function () {
|
after(function () {
|
||||||
|
@ -168,7 +197,7 @@ describe("Item pane", function () {
|
||||||
assert.equal(label.value, 'Test');
|
assert.equal(label.value, 'Test');
|
||||||
|
|
||||||
yield Zotero.Items.erase(id);
|
yield Zotero.Items.erase(id);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
it("should swap creator names", async function () {
|
it("should swap creator names", async function () {
|
||||||
|
@ -344,7 +373,479 @@ describe("Item pane", function () {
|
||||||
'1'
|
'1'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
describe("Attachments pane", function () {
|
||||||
|
let paneID = "attachments";
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Zotero.Prefs.set("panes.attachments.open", true);
|
||||||
|
Zotero.Prefs.set("showAttachmentPreview", true);
|
||||||
|
Zotero_Tabs.select("zotero-pane");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
Zotero_Tabs.select("zotero-pane");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show attachments pane in library for regular item", async function () {
|
||||||
|
// Regular item: show
|
||||||
|
let attachmentsBox = ZoteroPane.itemPane._itemDetails.getPane(paneID);
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
await item.saveTx();
|
||||||
|
await ZoteroPane.selectItem(item.id);
|
||||||
|
assert.isFalse(attachmentsBox.hidden);
|
||||||
|
|
||||||
|
// Child attachment: hide
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
let attachment = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
await ZoteroPane.selectItem(attachment.id);
|
||||||
|
assert.isTrue(attachmentsBox.hidden);
|
||||||
|
|
||||||
|
// Standalone attachment: hide
|
||||||
|
let attachment1 = await importFileAttachment('test.pdf');
|
||||||
|
await ZoteroPane.selectItem(attachment1.id);
|
||||||
|
assert.isTrue(attachmentsBox.hidden);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show attachments pane preview in reader best-matched attachment item", async function () {
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
await item.saveTx();
|
||||||
|
let attachment = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
await ZoteroPane.viewItems([attachment]);
|
||||||
|
let tabID = Zotero_Tabs.selectedID;
|
||||||
|
let itemDetails = ZoteroContextPane.context._getItemContext(tabID);
|
||||||
|
let attachmentsBox = itemDetails.getPane(paneID);
|
||||||
|
assert.isFalse(attachmentsBox.hidden);
|
||||||
|
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
|
||||||
|
assert.isFalse(isPreviewDisplayed(attachmentsBox));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show attachments pane in reader standalone attachment item", async function () {
|
||||||
|
let attachment = await importFileAttachment('test.pdf');
|
||||||
|
await ZoteroPane.viewItems([attachment]);
|
||||||
|
let tabID = Zotero_Tabs.selectedID;
|
||||||
|
let itemDetails = ZoteroContextPane.context._getItemContext(tabID);
|
||||||
|
let attachmentsBox = itemDetails.getPane(paneID);
|
||||||
|
assert.isTrue(attachmentsBox.hidden);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show attachments pane preview in reader non-best-matched attachment item", async function () {
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
await item.saveTx();
|
||||||
|
await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
|
||||||
|
let bestAttachments = await item.getBestAttachments();
|
||||||
|
await ZoteroPane.viewItems([bestAttachments[1]]);
|
||||||
|
// Ensure context pane is open
|
||||||
|
ZoteroContextPane.splitter.setAttribute("state", "open");
|
||||||
|
await waitForFrame();
|
||||||
|
let tabID = Zotero_Tabs.selectedID;
|
||||||
|
let itemDetails = ZoteroContextPane.context._getItemContext(tabID);
|
||||||
|
let attachmentsBox = itemDetails.getPane(paneID);
|
||||||
|
assert.isFalse(attachmentsBox.hidden);
|
||||||
|
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
await waitForPreviewBoxRender(attachmentsBox);
|
||||||
|
assert.isTrue(isPreviewDisplayed(attachmentsBox));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not render attachments pane preview when show preview is disabled", async function () {
|
||||||
|
Zotero.Prefs.set("showAttachmentPreview", false);
|
||||||
|
|
||||||
|
let itemDetails = ZoteroPane.itemPane._itemDetails;
|
||||||
|
let attachmentsBox = itemDetails.getPane(paneID);
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
await item.saveTx();
|
||||||
|
await ZoteroPane.selectItem(item.id);
|
||||||
|
assert.isFalse(attachmentsBox.hidden);
|
||||||
|
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
|
||||||
|
assert.isFalse(isPreviewDisplayed(attachmentsBox));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should only render after attachments pane becomes visible", async function () {
|
||||||
|
// Resize to very small height to ensure the attachment box is not in view
|
||||||
|
let height = doc.documentElement.clientHeight;
|
||||||
|
win.resizeTo(null, 100);
|
||||||
|
|
||||||
|
let itemDetails = ZoteroPane.itemPane._itemDetails;
|
||||||
|
let attachmentsBox = itemDetails.getPane(paneID);
|
||||||
|
let preview = attachmentsBox._preview;
|
||||||
|
// Force discard previous preview
|
||||||
|
await preview.discard(true);
|
||||||
|
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
await item.saveTx();
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await ZoteroPane.selectItem(item.id);
|
||||||
|
assert.isFalse(itemDetails.isPaneVisible(paneID));
|
||||||
|
// Do not use _isAlreadyRendered, since that changes the render flag state
|
||||||
|
assert.equal(attachmentsBox._syncRenderItemID, item.id);
|
||||||
|
assert.notEqual(attachmentsBox._asyncRenderItemID, item.id);
|
||||||
|
assert.isFalse(isPreviewDisplayed(attachmentsBox));
|
||||||
|
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
await waitForPreviewBoxRender(attachmentsBox);
|
||||||
|
assert.isTrue(itemDetails.isPaneVisible(paneID));
|
||||||
|
assert.equal(attachmentsBox._syncRenderItemID, item.id);
|
||||||
|
assert.equal(attachmentsBox._asyncRenderItemID, item.id);
|
||||||
|
|
||||||
|
assert.isTrue(isPreviewDisplayed(attachmentsBox));
|
||||||
|
assert.isTrue(preview.hasPreview);
|
||||||
|
win.resizeTo(null, height);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update attachments pane when attachments changed", async function () {
|
||||||
|
// https://forums.zotero.org/discussion/113632/zotero-7-beta-pdf-attachment-preview-and-annotations-not-refreshed-after-adding-annotations
|
||||||
|
|
||||||
|
let itemDetails = ZoteroPane.itemPane._itemDetails;
|
||||||
|
let attachmentsBox = itemDetails.getPane(paneID);
|
||||||
|
let preview = attachmentsBox._preview;
|
||||||
|
// Force discard previous preview
|
||||||
|
await preview.discard(true);
|
||||||
|
|
||||||
|
// Pin the pane to ensure it's rendered
|
||||||
|
itemDetails.pinnedPane = paneID;
|
||||||
|
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
await item.saveTx();
|
||||||
|
|
||||||
|
await ZoteroPane.selectItem(item.id);
|
||||||
|
assert.isTrue(await waitForPreviewBoxRender(attachmentsBox));
|
||||||
|
// No preview
|
||||||
|
assert.isFalse(isPreviewDisplayed(attachmentsBox));
|
||||||
|
// No row
|
||||||
|
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 0);
|
||||||
|
|
||||||
|
// Add an attachment
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.png');
|
||||||
|
let _attachment1 = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
await ZoteroPane.selectItem(item.id);
|
||||||
|
await itemDetails._renderPromise;
|
||||||
|
await waitForPreviewBoxRender(attachmentsBox);
|
||||||
|
// Image preview for item with image attachment
|
||||||
|
assert.isTrue(isPreviewDisplayed(attachmentsBox));
|
||||||
|
assert.equal(preview.previewType, "image");
|
||||||
|
// 1 row
|
||||||
|
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 1);
|
||||||
|
|
||||||
|
// Add an PDF attachment, which will be best match and update the preview
|
||||||
|
file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
let attachment2 = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
await waitForPreviewBoxReader(attachmentsBox, attachment2.id);
|
||||||
|
await Zotero.Promise.delay(100);
|
||||||
|
// PDF preview
|
||||||
|
assert.isTrue(isPreviewDisplayed(attachmentsBox));
|
||||||
|
assert.equal(preview.previewType, "pdf");
|
||||||
|
// 2 rows
|
||||||
|
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 2);
|
||||||
|
|
||||||
|
// Created annotations should be update in preview and attachment row
|
||||||
|
let annotation = await createAnnotation('highlight', attachment2);
|
||||||
|
await Zotero.Promise.delay(100);
|
||||||
|
// Annotation updated in preview reader
|
||||||
|
let readerAnnotation
|
||||||
|
= preview._reader._internalReader._annotationManager._annotations.find(
|
||||||
|
a => a.libraryID === annotation.libraryID && a.id === annotation.key
|
||||||
|
);
|
||||||
|
assert.exists(readerAnnotation);
|
||||||
|
|
||||||
|
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 2);
|
||||||
|
let attachmentRow = attachmentsBox.querySelector(`attachment-row[attachment-id="${attachment2.id}"]`);
|
||||||
|
assert.isFalse(attachmentRow._annotationButton.hidden);
|
||||||
|
// 1 annotation
|
||||||
|
assert.equal(attachmentRow._annotationButton.querySelector('.label').textContent, "1");
|
||||||
|
|
||||||
|
// Deleted annotations should be removed from preview and attachment row
|
||||||
|
await annotation.eraseTx();
|
||||||
|
await Zotero.Promise.delay(100);
|
||||||
|
// Annotation removed from preview reader
|
||||||
|
readerAnnotation
|
||||||
|
= preview._reader._internalReader._annotationManager._annotations.find(
|
||||||
|
a => a.libraryID === annotation.libraryID && a.id === annotation.key
|
||||||
|
);
|
||||||
|
assert.notExists(readerAnnotation);
|
||||||
|
// Row might be recreated
|
||||||
|
attachmentRow = attachmentsBox.querySelector(`attachment-row[attachment-id="${attachment2.id}"]`);
|
||||||
|
assert.isTrue(attachmentRow._annotationButton.hidden);
|
||||||
|
// 0 annotation
|
||||||
|
assert.equal(attachmentRow._annotationButton.querySelector('.label').textContent, "0");
|
||||||
|
|
||||||
|
// Delete attachment
|
||||||
|
await attachment2.eraseTx();
|
||||||
|
await Zotero.Promise.delay(100);
|
||||||
|
// Image preview for item with image attachment
|
||||||
|
assert.isTrue(isPreviewDisplayed(attachmentsBox));
|
||||||
|
assert.equal(preview.previewType, "image");
|
||||||
|
// 1 row
|
||||||
|
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 1);
|
||||||
|
// The corresponding row should be removed
|
||||||
|
attachmentRow = attachmentsBox.querySelector(`attachment-row[attachment-id="${attachment2.id}"]`);
|
||||||
|
assert.notExists(attachmentRow);
|
||||||
|
|
||||||
|
// Unpin
|
||||||
|
itemDetails.pinnedPane = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should keep attachments pane preview status after switching tab", async function () {
|
||||||
|
// https://forums.zotero.org/discussion/113658/zotero-7-beta-preview-appearing-in-the-item-pane-of-the-pdf-tab
|
||||||
|
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
await item.saveTx();
|
||||||
|
let attachment = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open reader
|
||||||
|
await ZoteroPane.viewItems([attachment]);
|
||||||
|
let tabID = Zotero_Tabs.selectedID;
|
||||||
|
await Zotero.Reader.getByTabID(tabID)._waitForReader();
|
||||||
|
// Ensure context pane is open
|
||||||
|
ZoteroContextPane.splitter.setAttribute("state", "open");
|
||||||
|
await waitForFrame();
|
||||||
|
|
||||||
|
let itemDetails = ZoteroContextPane.context._getItemContext(tabID);
|
||||||
|
let attachmentsBox = itemDetails.getPane(paneID);
|
||||||
|
assert.isFalse(attachmentsBox.hidden);
|
||||||
|
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
assert.isFalse(isPreviewDisplayed(attachmentsBox));
|
||||||
|
|
||||||
|
// Select library tab
|
||||||
|
Zotero_Tabs.select("zotero-pane");
|
||||||
|
let libraryItemDetails = ZoteroPane.itemPane._itemDetails;
|
||||||
|
let libraryAttachmentsBox = libraryItemDetails.getPane(paneID);
|
||||||
|
await ZoteroPane.selectItem(item.id);
|
||||||
|
await waitForScrollToPane(libraryItemDetails, paneID);
|
||||||
|
// Collapse section
|
||||||
|
libraryAttachmentsBox.querySelector('collapsible-section > .head').click();
|
||||||
|
await Zotero.Promise.delay(50);
|
||||||
|
// Open section
|
||||||
|
libraryAttachmentsBox.querySelector('collapsible-section > .head').click();
|
||||||
|
await Zotero.Promise.delay(50);
|
||||||
|
|
||||||
|
// Select reader tab
|
||||||
|
Zotero_Tabs.select(tabID);
|
||||||
|
|
||||||
|
// Make sure the preview status is not changed in reader
|
||||||
|
assert.isFalse(isPreviewDisplayed(attachmentsBox));
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test is essential to ensure the proper functioning of the sync/async rendering,
|
||||||
|
* scrolling handler, and pinning mechanism of ItemDetails.
|
||||||
|
* AttachmentsBox serves as a good example since it involves both sync and async rendering.
|
||||||
|
* If this test fails, it is not recommended to add timeouts as a quick fix.
|
||||||
|
*/
|
||||||
|
it("should keep attachments pane status after changing selection", async function () {
|
||||||
|
let itemDetails = ZoteroPane.itemPane._itemDetails;
|
||||||
|
let attachmentsBox = itemDetails.getPane(paneID);
|
||||||
|
let preview = attachmentsBox._preview;
|
||||||
|
|
||||||
|
// Pin the pane to avoid always scrolling to the section
|
||||||
|
itemDetails.pinnedPane = paneID;
|
||||||
|
|
||||||
|
// item with attachment (1 annotation)
|
||||||
|
let item1 = new Zotero.Item('book');
|
||||||
|
await item1.saveTx();
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
let attachment1 = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item1.id
|
||||||
|
});
|
||||||
|
let annotation = await createAnnotation('highlight', attachment1);
|
||||||
|
|
||||||
|
await itemDetails._renderPromise;
|
||||||
|
await waitForPreviewBoxReader(attachmentsBox, attachment1.id);
|
||||||
|
|
||||||
|
assert.isFalse(attachmentsBox.hidden);
|
||||||
|
let readerAnnotation
|
||||||
|
= preview._reader._internalReader._annotationManager._annotations.find(
|
||||||
|
a => a.libraryID === annotation.libraryID && a.id === annotation.key
|
||||||
|
);
|
||||||
|
assert.exists(readerAnnotation);
|
||||||
|
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 1);
|
||||||
|
let attachmentRow = attachmentsBox.querySelector(`attachment-row[attachment-id="${attachment1.id}"]`);
|
||||||
|
assert.isFalse(attachmentRow._annotationButton.hidden);
|
||||||
|
// 1 annotation
|
||||||
|
assert.equal(attachmentRow._annotationButton.querySelector('.label').textContent, "1");
|
||||||
|
|
||||||
|
// item with attachment (no annotation)
|
||||||
|
let item2 = new Zotero.Item('book');
|
||||||
|
await item2.saveTx();
|
||||||
|
file = getTestDataDirectory();
|
||||||
|
file.append('wonderland_short.pdf');
|
||||||
|
let attachment2 = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item2.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Select item with attachment (no annotation)
|
||||||
|
await itemDetails._renderPromise;
|
||||||
|
await waitForPreviewBoxReader(attachmentsBox, attachment2.id);
|
||||||
|
|
||||||
|
assert.isFalse(attachmentsBox.hidden);
|
||||||
|
readerAnnotation
|
||||||
|
= preview._reader._internalReader._annotationManager._annotations.find(
|
||||||
|
a => a.libraryID === annotation.libraryID && a.id === annotation.key
|
||||||
|
);
|
||||||
|
assert.notExists(readerAnnotation);
|
||||||
|
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 1);
|
||||||
|
attachmentRow = attachmentsBox.querySelector(`attachment-row[attachment-id="${attachment2.id}"]`);
|
||||||
|
assert.isTrue(attachmentRow._annotationButton.hidden);
|
||||||
|
// 0 annotation
|
||||||
|
assert.equal(attachmentRow._annotationButton.querySelector('.label').textContent, "0");
|
||||||
|
|
||||||
|
let item3 = new Zotero.Item('book');
|
||||||
|
await item3.saveTx();
|
||||||
|
|
||||||
|
// Select item without attachment
|
||||||
|
await itemDetails._renderPromise;
|
||||||
|
|
||||||
|
assert.isFalse(attachmentsBox.hidden);
|
||||||
|
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 0);
|
||||||
|
|
||||||
|
// Again, select item with attachment (1 annotation)
|
||||||
|
await ZoteroPane.selectItem(item1.id);
|
||||||
|
await itemDetails._renderPromise;
|
||||||
|
await waitForPreviewBoxReader(attachmentsBox, attachment1.id);
|
||||||
|
|
||||||
|
assert.isFalse(attachmentsBox.hidden);
|
||||||
|
readerAnnotation
|
||||||
|
= preview._reader._internalReader._annotationManager._annotations.find(
|
||||||
|
a => a.libraryID === annotation.libraryID && a.id === annotation.key
|
||||||
|
);
|
||||||
|
assert.exists(readerAnnotation);
|
||||||
|
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 1);
|
||||||
|
attachmentRow = attachmentsBox.querySelector(`attachment-row[attachment-id="${attachment1.id}"]`);
|
||||||
|
assert.isFalse(attachmentRow._annotationButton.hidden);
|
||||||
|
// 1 annotation
|
||||||
|
assert.equal(attachmentRow._annotationButton.querySelector('.label').textContent, "1");
|
||||||
|
|
||||||
|
// Unpin
|
||||||
|
itemDetails.pinnedPane = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should open attachment on clicking attachment row", async function () {
|
||||||
|
let itemDetails = ZoteroPane.itemPane._itemDetails;
|
||||||
|
let attachmentsBox = itemDetails.getPane(paneID);
|
||||||
|
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
await item.saveTx();
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
let attachment = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await ZoteroPane.selectItem(item.id);
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
await waitForPreviewBoxRender(attachmentsBox);
|
||||||
|
|
||||||
|
let attachmentRow = attachmentsBox.querySelector(`attachment-row[attachment-id="${attachment.id}"]`);
|
||||||
|
attachmentRow._attachmentButton.click();
|
||||||
|
await Zotero.Promise.delay(100);
|
||||||
|
let reader = await Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||||
|
// Should open attachment
|
||||||
|
assert.equal(reader.itemID, attachment.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should select attachment on clicking annotation button of attachment row", async function () {
|
||||||
|
let itemDetails = ZoteroPane.itemPane._itemDetails;
|
||||||
|
let attachmentsBox = itemDetails.getPane(paneID);
|
||||||
|
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
await item.saveTx();
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
let attachment = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
let _annotation = await createAnnotation('highlight', attachment);
|
||||||
|
|
||||||
|
await ZoteroPane.selectItem(item.id);
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
await waitForPreviewBoxRender(attachmentsBox);
|
||||||
|
|
||||||
|
let attachmentRow = attachmentsBox.querySelector(`attachment-row[attachment-id="${attachment.id}"]`);
|
||||||
|
attachmentRow._annotationButton.click();
|
||||||
|
await Zotero.Promise.delay(100);
|
||||||
|
// Should select attachment
|
||||||
|
assert.equal(ZoteroPane.getSelectedItems(true)[0], attachment.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should open attachment on double-clicking attachments pane preview", async function () {
|
||||||
|
let itemDetails = ZoteroPane.itemPane._itemDetails;
|
||||||
|
let attachmentsBox = itemDetails.getPane(paneID);
|
||||||
|
let preview = attachmentsBox._preview;
|
||||||
|
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
await item.saveTx();
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
let attachment = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await ZoteroPane.selectItem(item.id);
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
await waitForPreviewBoxRender(attachmentsBox);
|
||||||
|
|
||||||
|
let event = new MouseEvent('dblclick', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
view: window
|
||||||
|
});
|
||||||
|
preview.dispatchEvent(event);
|
||||||
|
await Zotero.Promise.delay(100);
|
||||||
|
let reader = await Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
|
||||||
|
// Should open attachment
|
||||||
|
assert.equal(reader.itemID, attachment.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
describe("Notes pane", function () {
|
describe("Notes pane", function () {
|
||||||
|
@ -461,6 +962,18 @@ describe("Item pane", function () {
|
||||||
|
|
||||||
|
|
||||||
describe("Attachment pane", function () {
|
describe("Attachment pane", function () {
|
||||||
|
let paneID = "attachment-info";
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
Zotero.Prefs.set("panes.attachment-info.open", true);
|
||||||
|
Zotero.Prefs.set("showAttachmentPreview", true);
|
||||||
|
Zotero_Tabs.select("zotero-pane");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
Zotero_Tabs.select("zotero-pane");
|
||||||
|
});
|
||||||
|
|
||||||
it("should refresh on file rename", async function () {
|
it("should refresh on file rename", async function () {
|
||||||
let file = getTestDataDirectory();
|
let file = getTestDataDirectory();
|
||||||
file.append('test.png');
|
file.append('test.png');
|
||||||
|
@ -499,6 +1012,95 @@ describe("Item pane", function () {
|
||||||
await promise;
|
await promise;
|
||||||
assert.equal(label.value, newTitle);
|
assert.equal(label.value, newTitle);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should show attachment pane in library for attachment item", async function () {
|
||||||
|
// Regular item: hide
|
||||||
|
let itemDetails = ZoteroPane.itemPane._itemDetails;
|
||||||
|
let box = itemDetails.getPane(paneID);
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
await item.saveTx();
|
||||||
|
await ZoteroPane.selectItem(item.id);
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
assert.isTrue(box.hidden);
|
||||||
|
|
||||||
|
// Child attachment: show
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
let attachment = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
await ZoteroPane.selectItem(attachment.id);
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
await waitForPreviewBoxReader(box, attachment.id);
|
||||||
|
assert.isFalse(box.hidden);
|
||||||
|
await Zotero.Promise.delay(100);
|
||||||
|
assert.isTrue(isPreviewDisplayed(box));
|
||||||
|
|
||||||
|
// Standalone attachment: show
|
||||||
|
let attachment1 = await importFileAttachment('test.pdf');
|
||||||
|
await ZoteroPane.selectItem(attachment1.id);
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
await waitForPreviewBoxReader(box, attachment1.id);
|
||||||
|
assert.isFalse(box.hidden);
|
||||||
|
await Zotero.Promise.delay(100);
|
||||||
|
assert.isTrue(isPreviewDisplayed(box));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show attachment pane without preview in reader for standalone attachment item", async function () {
|
||||||
|
// Attachment item with parent item: hide
|
||||||
|
let item = new Zotero.Item('book');
|
||||||
|
let file = getTestDataDirectory();
|
||||||
|
file.append('test.pdf');
|
||||||
|
await item.saveTx();
|
||||||
|
let attachment = await Zotero.Attachments.importFromFile({
|
||||||
|
file,
|
||||||
|
parentItemID: item.id
|
||||||
|
});
|
||||||
|
await ZoteroPane.viewItems([attachment]);
|
||||||
|
let tabID = Zotero_Tabs.selectedID;
|
||||||
|
let itemDetails = ZoteroContextPane.context._getItemContext(tabID);
|
||||||
|
let box = itemDetails.getPane(paneID);
|
||||||
|
assert.isTrue(box.hidden);
|
||||||
|
|
||||||
|
// Standalone attachment item: show
|
||||||
|
attachment = await importFileAttachment('test.pdf');
|
||||||
|
await ZoteroPane.viewItems([attachment]);
|
||||||
|
tabID = Zotero_Tabs.selectedID;
|
||||||
|
itemDetails = ZoteroContextPane.context._getItemContext(tabID);
|
||||||
|
box = itemDetails.getPane(paneID);
|
||||||
|
assert.isFalse(box.hidden);
|
||||||
|
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
// No preview
|
||||||
|
assert.isFalse(isPreviewDisplayed(box));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should only show attachment note container when exists", async function () {
|
||||||
|
let itemDetails = ZoteroPane.itemPane._itemDetails;
|
||||||
|
let box = itemDetails.getPane(paneID);
|
||||||
|
let noteContainer = box._id("note-container");
|
||||||
|
let noteEditor = box._id('attachment-note-editor');
|
||||||
|
|
||||||
|
// Hide note container by default
|
||||||
|
let attachment = await importFileAttachment('test.pdf');
|
||||||
|
await ZoteroPane.selectItem(attachment.id);
|
||||||
|
await itemDetails._renderPromise;
|
||||||
|
await waitForScrollToPane(itemDetails, paneID);
|
||||||
|
await waitForPreviewBoxRender(box);
|
||||||
|
assert.isTrue(noteContainer.hidden);
|
||||||
|
|
||||||
|
// Add attachment note
|
||||||
|
let itemModifyPromise = waitForItemEvent("modify");
|
||||||
|
attachment.setNote("<h1>TEST</h1>");
|
||||||
|
await attachment.saveTx();
|
||||||
|
await itemModifyPromise;
|
||||||
|
await waitForPreviewBoxRender(box);
|
||||||
|
// Should show note container
|
||||||
|
assert.isFalse(noteContainer.hidden);
|
||||||
|
// Should be readonly
|
||||||
|
assert.equal(noteEditor.mode, "view");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -602,4 +1204,4 @@ describe("Item pane", function () {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
Loading…
Reference in a new issue