Lazy load attachment preview (#4568)
Some checks are pending
CI / Build, Upload, Test (push) Waiting to run

This commit is contained in:
windingwind 2024-08-15 14:22:24 +08:00 committed by GitHub
parent ec7ffa8c0e
commit ca9508ebce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 254 additions and 45 deletions

View file

@ -32,7 +32,6 @@
content = MozXULElement.parseXULToFragment(` content = MozXULElement.parseXULToFragment(`
<collapsible-section data-l10n-id="section-attachment-info" data-pane="attachment-info"> <collapsible-section data-l10n-id="section-attachment-info" data-pane="attachment-info">
<html:div class="body"> <html:div class="body">
<attachment-preview id="attachment-preview" tabindex="0" data-l10n-id="attachment-preview"/>
<html:div style="display: grid;"> <html:div style="display: grid;">
<label id="url" is="zotero-text-link" crop="end" tabindex="0" <label id="url" is="zotero-text-link" crop="end" tabindex="0"
ondragstart="let dt = event.dataTransfer; dt.setData('text/x-moz-url', this.value); dt.setData('text/uri-list', this.value); dt.setData('text/plain', this.value);"/> ondragstart="let dt = event.dataTransfer; dt.setData('text/x-moz-url', this.value); dt.setData('text/uri-list', this.value); dt.setData('text/plain', this.value);"/>
@ -76,6 +75,16 @@
</collapsible-section> </collapsible-section>
`); `);
_body = null;
_preview = null;
_lastPreviewRenderId = "";
_discardPreviewTimeout = 60000;
_previewDiscarded = false;
constructor() { constructor() {
super(); super();
@ -91,7 +100,6 @@
this._item = null; this._item = null;
this._section = null; this._section = null;
this._preview = null;
this._asyncRendering = false; this._asyncRendering = false;
@ -212,17 +220,25 @@
if (val.isAttachment()) { if (val.isAttachment()) {
this._item = val; this._item = val;
this.hidden = false; this.hidden = false;
this._preview.disableResize = false;
} }
else { else {
this.hidden = true; this.hidden = true;
this._preview.disableResize = true;
} }
if (this._preview) this._preview.disableResize = !!this.hidden;
}
get previewElem() {
if (!this._preview) {
this._initPreview();
}
return this._preview;
} }
init() { init() {
this.initCollapsibleSection(); this.initCollapsibleSection();
this._body = this.querySelector('.body');
this._id('url').addEventListener('contextmenu', (event) => { this._id('url').addEventListener('contextmenu', (event) => {
this._id('url-menu').openPopupAtScreen(event.screenX, event.screenY, true); this._id('url-menu').openPopupAtScreen(event.screenX, event.screenY, true);
}); });
@ -236,8 +252,6 @@
this._isEditingFilename = false; this._isEditingFilename = false;
}); });
this._preview = this._id("attachment-preview");
let noteButton = this._id('note-button'); let noteButton = this._id('note-button');
noteButton.addEventListener("command", () => { noteButton.addEventListener("command", () => {
this.convertAttachmentNote(); this.convertAttachmentNote();
@ -301,7 +315,14 @@
if (!this.item) return; if (!this.item) return;
if (this._asyncRendering) return; if (this._asyncRendering) return;
if (!this._section.open) return; if (!this._section.open) return;
if (this._isAlreadyRendered("async")) return; if (this._isAlreadyRendered("async")) {
if (this._previewDiscarded) {
this._previewDiscarded = false;
this.previewElem.render();
}
this._lastPreviewRenderTime = Date.now();
return;
}
Zotero.debug('Refreshing attachment box'); Zotero.debug('Refreshing attachment box');
this._asyncRendering = true; this._asyncRendering = true;
@ -450,11 +471,24 @@
} }
if (this.usePreview) { if (this.usePreview) {
this._preview.item = this.item; this.previewElem.item = this.item;
await this._preview.render(); await this.previewElem.render();
} }
this._asyncRendering = false; this._asyncRendering = false;
this._lastPreviewRenderTime = `${Date.now()}-${Math.random()}`;
}
discard() {
if (!this._preview) return;
let lastRenderTime = this._lastPreviewRenderId;
setTimeout(() => {
if (!this._asyncRendering && this._lastPreviewRenderId === lastRenderTime) {
this._preview?.discard();
this._previewDiscarded = true;
}
}, this._discardPreviewTimeout);
} }
onViewClick(event) { onViewClick(event) {
@ -676,6 +710,14 @@
if (finished) resolve(); if (finished) resolve();
else reject(new Error("AttachmentBox#_waitForRender timeout")); else reject(new Error("AttachmentBox#_waitForRender timeout"));
} }
_initPreview() {
this._preview = document.createXULElement('attachment-preview');
this._preview.setAttribute('tabindex', '0');
this._preview.setAttribute('data-l10n-id', 'attachment-preview');
this._body.prepend(this._preview);
this._preview.disableResize = !!this.hidden;
}
} }
customElements.define("attachment-box", AttachmentBox); customElements.define("attachment-box", AttachmentBox);

View file

@ -30,7 +30,6 @@
content = MozXULElement.parseXULToFragment(` content = MozXULElement.parseXULToFragment(`
<collapsible-section data-l10n-id="section-attachments" data-pane="attachments" extra-buttons="add"> <collapsible-section data-l10n-id="section-attachments" data-pane="attachments" extra-buttons="add">
<html:div class="body"> <html:div class="body">
<attachment-preview tabindex="0" data-l10n-id="attachment-preview"/>
<html:div class="attachments-container"></html:div> <html:div class="attachments-container"></html:div>
</html:div> </html:div>
</collapsible-section> </collapsible-section>
@ -45,8 +44,16 @@
_attachmentIDs = []; _attachmentIDs = [];
_body = null;
_preview = null; _preview = null;
_lastPreviewRenderTime = "";
_discardPreviewTimeout = 60000;
_previewDiscarded = false;
get item() { get item() {
return this._item; return this._item;
} }
@ -59,7 +66,7 @@
super.item = item; super.item = item;
let hidden = !item?.isRegularItem() || item?.isFeedItem; let hidden = !item?.isRegularItem() || item?.isFeedItem;
this.hidden = hidden; this.hidden = hidden;
this._preview.disableResize = !!hidden; if (this._preview) this._preview.disableResize = !!hidden;
} }
get inTrash() { get inTrash() {
@ -81,10 +88,19 @@
} }
} }
get previewElem() {
if (!this._preview) {
this._initPreview();
}
return this._preview;
}
init() { init() {
this.initCollapsibleSection(); this.initCollapsibleSection();
this._section.addEventListener('add', this._handleAdd); this._section.addEventListener('add', this._handleAdd);
this._body = this.querySelector('.body');
this._attachments = this.querySelector('.attachments-container'); this._attachments = this.querySelector('.attachments-container');
this._addPopup = this.querySelector('.add-popup'); this._addPopup = this.querySelector('.add-popup');
@ -106,7 +122,6 @@
}); });
this.usePreview = Zotero.Prefs.get('showAttachmentPreview'); this.usePreview = Zotero.Prefs.get('showAttachmentPreview');
this._preview = this.querySelector('attachment-preview');
this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'attachmentsBox'); this._notifierID = Zotero.Notifier.registerObserver(this, ['item'], 'attachmentsBox');
@ -169,7 +184,14 @@
async asyncRender() { async asyncRender() {
if (!this._item) return; if (!this._item) return;
if (this._isAlreadyRendered("async")) return; if (this._isAlreadyRendered("async")) {
if (this._previewDiscarded) {
this._previewDiscarded = false;
this.previewElem.render();
}
this._lastPreviewRenderTime = `${Date.now()}-${Math.random()}`;
return;
}
this._renderStage = "final"; this._renderStage = "final";
this._asyncRendering = true; this._asyncRendering = true;
@ -184,6 +206,17 @@
await this.updatePreview(); await this.updatePreview();
this._asyncRendering = false; this._asyncRendering = false;
} }
discard() {
if (!this._preview) return;
let lastRenderTime = this._lastPreviewRenderTime;
setTimeout(() => {
if (!this._asyncRendering && this._lastPreviewRenderTime === lastRenderTime) {
this._preview?.discard();
this._previewDiscarded = true;
}
}, this._discardPreviewTimeout);
}
updateCount() { updateCount() {
if (!this._item?.isRegularItem()) { if (!this._item?.isRegularItem()) {
@ -211,8 +244,9 @@
|| (this._attachmentIDs.length && !this._section.open)) { || (this._attachmentIDs.length && !this._section.open)) {
return; return;
} }
this._preview.item = attachment; this.previewElem.item = attachment;
await this._preview.render(); await this.previewElem.render();
this._lastPreviewRenderTime = `${Date.now()}-${Math.random()}`;
} }
async _getPreviewAttachment() { async _getPreviewAttachment() {
@ -225,6 +259,14 @@
return attachment; return attachment;
} }
_initPreview() {
this._preview = document.createXULElement('attachment-preview');
this._preview.setAttribute('tabindex', '0');
this._preview.setAttribute('data-l10n-id', 'attachment-preview');
this._body.prepend(this._preview);
this._preview.disableResize = !!this.hidden;
}
_handleAdd = (event) => { _handleAdd = (event) => {
this._section.open = true; this._section.open = true;
this._addPopup.openPopup(event.detail.button, 'after_end'); this._addPopup.openPopup(event.detail.button, 'after_end');
@ -238,7 +280,7 @@
menu.dataset.l10nArgs = `{ "type": "${this.usePreview ? "open" : "collapsed"}" }`; menu.dataset.l10nArgs = `{ "type": "${this.usePreview ? "open" : "collapsed"}" }`;
if (toOpen) { if (toOpen) {
this._preview.render(); this.previewElem.render();
} }
}; };

View file

@ -275,11 +275,13 @@
box.tabType = this.tabType; box.tabType = this.tabType;
box.item = item; box.item = item;
box.collectionTreeRow = this.collectionTreeRow; box.collectionTreeRow = this.collectionTreeRow;
// Discard hidden panes
if (box.hidden && box.discard) {
box.discard();
}
// Execute sync render immediately // Execute sync render immediately
if (!box.hidden && box.render) { if (!box.hidden && box.render) {
if (box.render) { box.render();
box.render();
}
} }
} }
@ -604,7 +606,8 @@
let needsDiscard = []; let needsDiscard = [];
entries.forEach((entry) => { entries.forEach((entry) => {
let targetPaneElem = entry.target; let targetPaneElem = entry.target;
if (entry.isIntersecting && targetPaneElem.render) { if (entry.isIntersecting
&& (targetPaneElem.render || targetPaneElem.asyncRender)) {
needsRefresh.push(targetPaneElem); needsRefresh.push(targetPaneElem);
} }
else if (targetPaneElem.discard) { else if (targetPaneElem.discard) {

View file

@ -12,7 +12,8 @@ describe("Item pane", function () {
} }
async function waitForPreviewBoxReader(box, itemID) { async function waitForPreviewBoxReader(box, itemID) {
let preview = box._preview; let preview = await getBoxPreview(box);
if (!preview) return false;
await waitForPreviewBoxRender(box); await waitForPreviewBoxRender(box);
let res = await waitForCallback( let res = await waitForCallback(
() => preview._reader?.itemID == itemID () => preview._reader?.itemID == itemID
@ -25,9 +26,26 @@ describe("Item pane", function () {
return true; return true;
} }
function isPreviewDisplayed(box) { async function isPreviewDisplayed(box) {
return !!(box._preview.hasPreview let preview = await getBoxPreview(box);
&& win.getComputedStyle(box._preview).display !== "none"); if (!preview) return false;
return !!(preview.hasPreview
&& win.getComputedStyle(preview).display !== "none");
}
async function getBoxPreview(box) {
try {
// Since we are lazy loading the preview, should wait for the preview to be initialized
await waitForCallback(
() => !!box._preview
, 10, 0.5);
}
catch (e) {
Zotero.logError(e);
// Return false if waitForCallback fails
return false;
}
return box._preview;
} }
before(function* () { before(function* () {
@ -497,13 +515,14 @@ describe("Item pane", function () {
}); });
await ZoteroPane.viewItems([attachment]); await ZoteroPane.viewItems([attachment]);
let tabID = Zotero_Tabs.selectedID; let tabID = Zotero_Tabs.selectedID;
ZoteroContextPane.splitter.setAttribute("state", "open");
let itemDetails = ZoteroContextPane.context._getItemContext(tabID); let itemDetails = ZoteroContextPane.context._getItemContext(tabID);
let attachmentsBox = itemDetails.getPane(paneID); let attachmentsBox = itemDetails.getPane(paneID);
assert.isFalse(attachmentsBox.hidden); assert.isFalse(attachmentsBox.hidden);
await waitForScrollToPane(itemDetails, paneID); await waitForScrollToPane(itemDetails, paneID);
assert.isFalse(isPreviewDisplayed(attachmentsBox)); assert.isFalse(await isPreviewDisplayed(attachmentsBox));
}); });
it("should not show attachments pane in reader standalone attachment item", async function () { it("should not show attachments pane in reader standalone attachment item", async function () {
@ -541,7 +560,7 @@ describe("Item pane", function () {
await waitForScrollToPane(itemDetails, paneID); await waitForScrollToPane(itemDetails, paneID);
await waitForPreviewBoxRender(attachmentsBox); await waitForPreviewBoxRender(attachmentsBox);
assert.isTrue(isPreviewDisplayed(attachmentsBox)); assert.isTrue(await isPreviewDisplayed(attachmentsBox));
}); });
it("should not render attachments pane preview when show preview is disabled", async function () { it("should not render attachments pane preview when show preview is disabled", async function () {
@ -556,7 +575,7 @@ describe("Item pane", function () {
await waitForScrollToPane(itemDetails, paneID); await waitForScrollToPane(itemDetails, paneID);
assert.isFalse(isPreviewDisplayed(attachmentsBox)); assert.isFalse(await isPreviewDisplayed(attachmentsBox));
}); });
it("should only render after attachments pane becomes visible", async function () { it("should only render after attachments pane becomes visible", async function () {
@ -566,7 +585,7 @@ describe("Item pane", function () {
let itemDetails = ZoteroPane.itemPane._itemDetails; let itemDetails = ZoteroPane.itemPane._itemDetails;
let attachmentsBox = itemDetails.getPane(paneID); let attachmentsBox = itemDetails.getPane(paneID);
let preview = attachmentsBox._preview; let preview = attachmentsBox.previewElem;
// Force discard previous preview // Force discard previous preview
await preview.discard(true); await preview.discard(true);
@ -584,7 +603,7 @@ describe("Item pane", function () {
// Do not use _isAlreadyRendered, since that changes the render flag state // Do not use _isAlreadyRendered, since that changes the render flag state
assert.equal(attachmentsBox._syncRenderItemID, item.id); assert.equal(attachmentsBox._syncRenderItemID, item.id);
assert.notEqual(attachmentsBox._asyncRenderItemID, item.id); assert.notEqual(attachmentsBox._asyncRenderItemID, item.id);
assert.isFalse(isPreviewDisplayed(attachmentsBox)); assert.isFalse(await isPreviewDisplayed(attachmentsBox));
await waitForScrollToPane(itemDetails, paneID); await waitForScrollToPane(itemDetails, paneID);
await waitForPreviewBoxRender(attachmentsBox); await waitForPreviewBoxRender(attachmentsBox);
@ -594,7 +613,7 @@ describe("Item pane", function () {
assert.equal(attachmentsBox._syncRenderItemID, item.id); assert.equal(attachmentsBox._syncRenderItemID, item.id);
assert.equal(attachmentsBox._asyncRenderItemID, item.id); assert.equal(attachmentsBox._asyncRenderItemID, item.id);
assert.isTrue(isPreviewDisplayed(attachmentsBox)); assert.isTrue(await isPreviewDisplayed(attachmentsBox));
assert.isTrue(preview.hasPreview); assert.isTrue(preview.hasPreview);
win.resizeTo(null, height); win.resizeTo(null, height);
}); });
@ -604,7 +623,7 @@ describe("Item pane", function () {
let itemDetails = ZoteroPane.itemPane._itemDetails; let itemDetails = ZoteroPane.itemPane._itemDetails;
let attachmentsBox = itemDetails.getPane(paneID); let attachmentsBox = itemDetails.getPane(paneID);
let preview = attachmentsBox._preview; let preview = attachmentsBox.previewElem;
// Force discard previous preview // Force discard previous preview
await preview.discard(true); await preview.discard(true);
@ -617,7 +636,7 @@ describe("Item pane", function () {
await ZoteroPane.selectItem(item.id); await ZoteroPane.selectItem(item.id);
assert.isTrue(await waitForPreviewBoxRender(attachmentsBox)); assert.isTrue(await waitForPreviewBoxRender(attachmentsBox));
// No preview // No preview
assert.isFalse(isPreviewDisplayed(attachmentsBox)); assert.isFalse(await isPreviewDisplayed(attachmentsBox));
// No row // No row
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 0); assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 0);
@ -632,7 +651,7 @@ describe("Item pane", function () {
await itemDetails._renderPromise; await itemDetails._renderPromise;
await waitForPreviewBoxRender(attachmentsBox); await waitForPreviewBoxRender(attachmentsBox);
// Image preview for item with image attachment // Image preview for item with image attachment
assert.isTrue(isPreviewDisplayed(attachmentsBox)); assert.isTrue(await isPreviewDisplayed(attachmentsBox));
assert.equal(preview.previewType, "image"); assert.equal(preview.previewType, "image");
// 1 row // 1 row
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 1); assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 1);
@ -647,7 +666,7 @@ describe("Item pane", function () {
await waitForPreviewBoxReader(attachmentsBox, attachment2.id); await waitForPreviewBoxReader(attachmentsBox, attachment2.id);
await Zotero.Promise.delay(100); await Zotero.Promise.delay(100);
// PDF preview // PDF preview
assert.isTrue(isPreviewDisplayed(attachmentsBox)); assert.isTrue(await isPreviewDisplayed(attachmentsBox));
assert.equal(preview.previewType, "pdf"); assert.equal(preview.previewType, "pdf");
// 2 rows // 2 rows
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 2); assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 2);
@ -690,7 +709,7 @@ describe("Item pane", function () {
await attachment2.eraseTx(); await attachment2.eraseTx();
await Zotero.Promise.delay(100); await Zotero.Promise.delay(100);
// Image preview for item with image attachment // Image preview for item with image attachment
assert.isTrue(isPreviewDisplayed(attachmentsBox)); assert.isTrue(await isPreviewDisplayed(attachmentsBox));
assert.equal(preview.previewType, "image"); assert.equal(preview.previewType, "image");
// 1 row // 1 row
assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 1); assert.equal(attachmentsBox.querySelectorAll("attachment-row").length, 1);
@ -727,7 +746,7 @@ describe("Item pane", function () {
assert.isFalse(attachmentsBox.hidden); assert.isFalse(attachmentsBox.hidden);
await waitForScrollToPane(itemDetails, paneID); await waitForScrollToPane(itemDetails, paneID);
assert.isFalse(isPreviewDisplayed(attachmentsBox)); assert.isFalse(await isPreviewDisplayed(attachmentsBox));
// Select library tab // Select library tab
Zotero_Tabs.select("zotero-pane"); Zotero_Tabs.select("zotero-pane");
@ -746,7 +765,7 @@ describe("Item pane", function () {
Zotero_Tabs.select(tabID); Zotero_Tabs.select(tabID);
// Make sure the preview status is not changed in reader // Make sure the preview status is not changed in reader
assert.isFalse(isPreviewDisplayed(attachmentsBox)); assert.isFalse(await isPreviewDisplayed(attachmentsBox));
}); });
/** /**
@ -758,7 +777,7 @@ describe("Item pane", function () {
it("should keep attachments pane status after changing selection", async function () { it("should keep attachments pane status after changing selection", async function () {
let itemDetails = ZoteroPane.itemPane._itemDetails; let itemDetails = ZoteroPane.itemPane._itemDetails;
let attachmentsBox = itemDetails.getPane(paneID); let attachmentsBox = itemDetails.getPane(paneID);
let preview = attachmentsBox._preview; let preview = attachmentsBox.previewElem;
// Pin the pane to avoid always scrolling to the section // Pin the pane to avoid always scrolling to the section
itemDetails.pinnedPane = paneID; itemDetails.pinnedPane = paneID;
@ -898,7 +917,7 @@ describe("Item pane", function () {
it("should open attachment on double-clicking attachments pane preview", async function () { it("should open attachment on double-clicking attachments pane preview", async function () {
let itemDetails = ZoteroPane.itemPane._itemDetails; let itemDetails = ZoteroPane.itemPane._itemDetails;
let attachmentsBox = itemDetails.getPane(paneID); let attachmentsBox = itemDetails.getPane(paneID);
let preview = attachmentsBox._preview; let preview = attachmentsBox.previewElem;
let item = new Zotero.Item('book'); let item = new Zotero.Item('book');
await item.saveTx(); await item.saveTx();
@ -928,7 +947,7 @@ describe("Item pane", function () {
it("should render preview robustly after making dense calls to render and discard", async function () { it("should render preview robustly after making dense calls to render and discard", async function () {
let itemDetails = ZoteroPane.itemPane._itemDetails; let itemDetails = ZoteroPane.itemPane._itemDetails;
let attachmentsBox = itemDetails.getPane(paneID); let attachmentsBox = itemDetails.getPane(paneID);
let preview = attachmentsBox._preview; let preview = attachmentsBox.previewElem;
// Pin the pane to avoid always scrolling to the section // Pin the pane to avoid always scrolling to the section
itemDetails.pinnedPane = paneID; itemDetails.pinnedPane = paneID;
@ -970,14 +989,77 @@ describe("Item pane", function () {
// Should be able to render the correct preview // Should be able to render the correct preview
await ZoteroPane.selectItem(item1.id); await ZoteroPane.selectItem(item1.id);
await waitForPreviewBoxReader(attachmentsBox, attachment1.id); await waitForPreviewBoxReader(attachmentsBox, attachment1.id);
assert.isTrue(isPreviewDisplayed(attachmentsBox)); assert.isTrue(await isPreviewDisplayed(attachmentsBox));
await ZoteroPane.selectItem(item2.id); await ZoteroPane.selectItem(item2.id);
await waitForPreviewBoxReader(attachmentsBox, attachment2.id); await waitForPreviewBoxReader(attachmentsBox, attachment2.id);
assert.isTrue(isPreviewDisplayed(attachmentsBox)); assert.isTrue(await isPreviewDisplayed(attachmentsBox));
itemDetails.pinnedPane = ""; itemDetails.pinnedPane = "";
}); });
it("should not load preview iframe before becoming visible", async function () {
let itemDetails = ZoteroPane.itemPane._itemDetails;
let attachmentsBox = itemDetails.getPane(paneID);
// Resize to very small height to ensure the attachment box is not in view
let height = doc.documentElement.clientHeight;
win.resizeTo(null, 100);
// Remove any existing preview to ensure the test is valid
attachmentsBox._preview?.remove();
attachmentsBox._preview = null;
let item = await createDataObject('item');
await importFileAttachment('test.pdf', { parentID: item.id });
await ZoteroPane.selectItem(item.id);
assert.notExists(attachmentsBox._preview);
assert.notExists(attachmentsBox.querySelector("#preview"));
await waitForScrollToPane(itemDetails, paneID);
await waitForPreviewBoxRender(attachmentsBox);
assert.exists(await getBoxPreview(attachmentsBox));
win.resizeTo(null, height);
});
it("should discard attachments pane preview after becoming invisible", async function () {
let itemDetails = ZoteroPane.itemPane._itemDetails;
let attachmentsBox = itemDetails.getPane(paneID);
// Resize to very small height to ensure the attachment box is not in view
let height = doc.documentElement.clientHeight;
win.resizeTo(null, 100);
const discardTimeout = 50;
// Temporarily set discard timeout to 100ms for testing
let currentDiscardTimeout = attachmentsBox._discardPreviewTimeout;
attachmentsBox._discardPreviewTimeout = discardTimeout;
let item = await createDataObject('item');
await importFileAttachment('test.pdf', { parentID: item.id });
await ZoteroPane.selectItem(item.id);
await waitForScrollToPane(itemDetails, paneID);
await waitForPreviewBoxRender(attachmentsBox);
assert.isTrue(attachmentsBox._preview._isReaderInitialized);
// Scroll the attachments pane out of view
await waitForScrollToPane(itemDetails, 'info');
// Wait a bit for the preview to be discarded
await Zotero.Promise.delay(discardTimeout + 100);
assert.isFalse(attachmentsBox._preview._isReaderInitialized);
win.resizeTo(null, height);
attachmentsBox._discardPreviewTimeout = currentDiscardTimeout;
});
}); });
@ -1147,6 +1229,11 @@ describe("Item pane", function () {
await item.saveTx(); await item.saveTx();
await promise; await promise;
// Wait for section to finish rendering
let box = ZoteroPane.itemPane._itemDetails.getPane(paneID);
await waitForPreviewBoxRender(box);
assert.equal(label.value, newTitle); assert.equal(label.value, newTitle);
}); });
@ -1154,6 +1241,11 @@ describe("Item pane", function () {
// Regular item: hide // Regular item: hide
let itemDetails = ZoteroPane.itemPane._itemDetails; let itemDetails = ZoteroPane.itemPane._itemDetails;
let box = itemDetails.getPane(paneID); let box = itemDetails.getPane(paneID);
// TEMP: Force abort any pending renders
box._preview?.remove();
box._preview = null;
let item = new Zotero.Item('book'); let item = new Zotero.Item('book');
await item.saveTx(); await item.saveTx();
await ZoteroPane.selectItem(item.id); await ZoteroPane.selectItem(item.id);
@ -1172,7 +1264,7 @@ describe("Item pane", function () {
await waitForPreviewBoxReader(box, attachment.id); await waitForPreviewBoxReader(box, attachment.id);
assert.isFalse(box.hidden); assert.isFalse(box.hidden);
await Zotero.Promise.delay(100); await Zotero.Promise.delay(100);
assert.isTrue(isPreviewDisplayed(box)); assert.isTrue(await isPreviewDisplayed(box));
// Standalone attachment: show // Standalone attachment: show
let attachment1 = await importFileAttachment('test.pdf'); let attachment1 = await importFileAttachment('test.pdf');
@ -1181,7 +1273,7 @@ describe("Item pane", function () {
await waitForPreviewBoxReader(box, attachment1.id); await waitForPreviewBoxReader(box, attachment1.id);
assert.isFalse(box.hidden); assert.isFalse(box.hidden);
await Zotero.Promise.delay(100); await Zotero.Promise.delay(100);
assert.isTrue(isPreviewDisplayed(box)); assert.isTrue(await isPreviewDisplayed(box));
}); });
it("should show attachment pane without preview in reader for standalone attachment item", async function () { it("should show attachment pane without preview in reader for standalone attachment item", async function () {
@ -1210,7 +1302,7 @@ describe("Item pane", function () {
await waitForScrollToPane(itemDetails, paneID); await waitForScrollToPane(itemDetails, paneID);
// No preview // No preview
assert.isFalse(isPreviewDisplayed(box)); assert.isFalse(await isPreviewDisplayed(box));
}); });
it("should only show attachment note container when exists", async function () { it("should only show attachment note container when exists", async function () {
@ -1238,6 +1330,36 @@ describe("Item pane", function () {
// Should be readonly // Should be readonly
assert.equal(noteEditor.mode, "view"); assert.equal(noteEditor.mode, "view");
}); });
it("should discard attachment pane preview after becoming invisible", async function () {
let itemDetails = ZoteroPane.itemPane._itemDetails;
let attachmentBox = itemDetails.getPane(paneID);
const discardTimeout = 50;
// Temporarily set discard timeout to 100ms for testing
let currentDiscardTimeout = attachmentBox._discardPreviewTimeout;
attachmentBox._discardPreviewTimeout = discardTimeout;
let item = await createDataObject('item');
let attachment = await importFileAttachment('test.pdf', { parentID: item.id });
await ZoteroPane.selectItem(attachment.id);
await waitForScrollToPane(itemDetails, paneID);
await waitForPreviewBoxRender(attachmentBox);
assert.isTrue(attachmentBox._preview._isReaderInitialized);
// Select a regular item to hide the attachment pane
await ZoteroPane.selectItem(item.id);
// Wait a bit for the preview to be discarded
await Zotero.Promise.delay(discardTimeout + 100);
assert.isFalse(attachmentBox._preview._isReaderInitialized);
attachmentBox._discardPreviewTimeout = currentDiscardTimeout;
});
}); });