From f5b653e7fd4f6a2a82a47eef3c2b8626f74ff815 Mon Sep 17 00:00:00 2001 From: windingwind <33902321+windingwind@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:35:56 +0100 Subject: [PATCH] Add item tree and info box refresh API (#4850) For APIs that have lifecycle control, e.g. item pane section, the `update` is passed in the init hook. For APIs without lifecycle, we can't pass a value. Instead, we provide a method to refresh in the API instance. --- chrome/content/zotero/elements/itemBox.js | 31 +++++++++++++------ .../zotero/xpcom/pluginAPI/itemPaneManager.js | 11 +++++++ .../zotero/xpcom/pluginAPI/itemTreeManager.js | 8 +++++ .../zotero/xpcom/pluginAPI/pluginAPIBase.mjs | 12 +++---- test/tests/pluginAPITest.js | 28 +++++++++++++++++ 5 files changed, 75 insertions(+), 15 deletions(-) diff --git a/chrome/content/zotero/elements/itemBox.js b/chrome/content/zotero/elements/itemBox.js index 172b7c557c..fa93aab8c2 100644 --- a/chrome/content/zotero/elements/itemBox.js +++ b/chrome/content/zotero/elements/itemBox.js @@ -488,7 +488,7 @@ // notify(event, type, ids) { if (event == 'refresh' && type == 'infobox' && this.item?.id) { - this.renderCustomRows(); + this.renderCustomRows(ids); return; } if (event == 'modify' && this.item?.id && ids.includes(this.item.id)) { @@ -863,11 +863,6 @@ this.restoreCustomRowElements(); - // Update custom row data - for (let rowElem of this._infoTable.querySelectorAll('.meta-row[data-custom-row-id]')) { - this.updateCustomRowData(rowElem); - } - // Set focus on the last focused field this._restoreFieldFocus(); // Make sure that any opened popup closes @@ -876,8 +871,18 @@ }); } - renderCustomRows() { + renderCustomRows(rowIDs) { let { options: targetRows, updateID } = Zotero.ItemPaneManager.customInfoRowData; + + // If rowIDs are provided, always update them + if (rowIDs?.length > 0) { + for (let rowID of rowIDs) { + let rowElem = this._infoTable.querySelector(`[data-custom-row-id="${rowID}"]`); + if (!rowElem) continue; + this.updateCustomRowData(rowElem); + } + } + if (this._lastUpdateCustomRows == updateID) return; this._lastUpdateCustomRows = updateID; @@ -890,8 +895,15 @@ // Add rows that are in the target rows but not in the current rows for (let row of targetRows) { - if (this._infoTable.querySelector(`[data-custom-row-id="${row.rowID}"]`)) continue; - let rowElem = document.createElement("div"); + let rowElem = this._infoTable.querySelector(`[data-custom-row-id="${row.rowID}"]`); + if (rowElem) { + // If the row is already in the table, and not already updated, update it + if (!rowIDs?.includes(row.rowID)) { + this.updateCustomRowData(rowElem); + } + continue; + } + rowElem = document.createElement("div"); rowElem.dataset.customRowId = row.rowID; let position = row.position || "end"; rowElem.dataset.position = position; @@ -933,6 +945,7 @@ this.updateCustomRowProperty(rowElem); + // The row is new, always update the data this.updateCustomRowData(rowElem); } } diff --git a/chrome/content/zotero/xpcom/pluginAPI/itemPaneManager.js b/chrome/content/zotero/xpcom/pluginAPI/itemPaneManager.js index a60d99c9eb..708dbaee42 100644 --- a/chrome/content/zotero/xpcom/pluginAPI/itemPaneManager.js +++ b/chrome/content/zotero/xpcom/pluginAPI/itemPaneManager.js @@ -301,6 +301,13 @@ }, }; } + + refresh(rowID) { + if (!this._optionsCache[rowID]) { + return; + } + this._refresh([rowID]); + } } @@ -329,6 +336,10 @@ return this._infoRowManager.unregister(rowID); } + refreshInfoRow(rowID) { + return this._infoRowManager.refresh(rowID); + } + get customInfoRowData() { return this._infoRowManager.data; } diff --git a/chrome/content/zotero/xpcom/pluginAPI/itemTreeManager.js b/chrome/content/zotero/xpcom/pluginAPI/itemTreeManager.js index 8e9fb61bd4..6cf0a35058 100644 --- a/chrome/content/zotero/xpcom/pluginAPI/itemTreeManager.js +++ b/chrome/content/zotero/xpcom/pluginAPI/itemTreeManager.js @@ -158,6 +158,10 @@ import { COLUMNS } from 'zotero/itemTreeColumns'; return super._validate(option); } + + refresh() { + this._refresh(); + } } @@ -332,6 +336,10 @@ import { COLUMNS } from 'zotero/itemTreeColumns'; } return ""; } + + refreshColumns() { + this._columnManager.refresh(); + } } diff --git a/chrome/content/zotero/xpcom/pluginAPI/pluginAPIBase.mjs b/chrome/content/zotero/xpcom/pluginAPI/pluginAPIBase.mjs index 8e69b3f68f..a4198e0642 100644 --- a/chrome/content/zotero/xpcom/pluginAPI/pluginAPIBase.mjs +++ b/chrome/content/zotero/xpcom/pluginAPI/pluginAPIBase.mjs @@ -121,7 +121,7 @@ class PluginAPIBase { return false; } this._addPluginShutdownObserver(); - this._update(); + this._refresh(); return mainKey; } @@ -135,7 +135,7 @@ class PluginAPIBase { if (!success) { return false; } - this._update(); + this._refresh(); return true; } @@ -322,14 +322,14 @@ class PluginAPIBase { /** * Notify the receiver to update */ - async _update() { + async _refresh(ids = [], extraData = {}) { this._lastUpdateID = `${new Date().getTime()}-${lazy.Zotero.Utilities.randomString()}`; await lazy.Zotero.DB.executeTransaction(async () => { lazy.Zotero.Notifier.queue( this._config.notifyAction, this._config.notifyType, - [], - {}, + ids, + extraData, ); }); } @@ -347,7 +347,7 @@ class PluginAPIBase { // Remove the registrations one by one paneIDs.forEach(id => this._remove(id)); this._log(`Registrations for plugin ${pluginID} are unregistered due to shutdown`); - await this._update(); + await this._refresh(); } /** diff --git a/test/tests/pluginAPITest.js b/test/tests/pluginAPITest.js index dcbc0e14bf..61da08da7f 100644 --- a/test/tests/pluginAPITest.js +++ b/test/tests/pluginAPITest.js @@ -359,6 +359,34 @@ describe("Plugin API", function () { await waitForUnregister(rowID); }); + + it("should refresh custom row value", async function () { + let item = new Zotero.Item('book'); + await item.saveTx(); + await ZoteroPane.selectItem(item.id); + + let rowID = await waitForRegister(defaultOption); + + let rowElem = infoSection.querySelector(`[data-custom-row-id="${rowID}"]`); + let valueElem = rowElem.querySelector(".value"); + + let oldValue = valueElem.value; + + // Since this row does not have `onSetData`, changing value does not do anything + // We just want to test if the value can be refreshed by calling `updateInfoRow` + let newValue = "TEST CUSTOM ROW EDITED"; + valueElem.value = newValue; + + let notifyPromise = waitForNotifierEvent("refresh", "infobox"); + + // Manually refresh the row + Zotero.ItemPaneManager.refreshInfoRow(rowID); + await notifyPromise; + + assert.equal(oldValue, valueElem.value); + + await waitForUnregister(rowID); + }); }); describe("Item tree custom column", function () {