diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index c80df3c129..721b795312 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -794,7 +794,30 @@ Zotero.Item.prototype.setField = function (field, value, loadIn) { if (!itemTypeID) { throw new Error('Item type must be set before setting field data'); } - + + // Loading calls this for every stored value, so use cached field lookups. + // Same result as the general path below. + if (loadIn && typeof field == 'number') { + let info = Zotero.ItemFields._getLoadInfo(itemTypeID, field); + if (info && !info.isISBN) { + if (field == info.titleID && this.isNote()) { + this._noteTitle = value ? value : ""; + return true; + } + if (value !== false && !info.valid) { + Zotero.debug("'" + field + "' is not a valid field for type '" + + Zotero.ItemTypes.getName(itemTypeID) + "'" + " -- ignoring value '" + value + "'", 2); + return false; + } + if (typeof value == 'string' && !info.multiline + && (value.includes('\n') || value.includes('\r'))) { + value = value.replace(/[\r\n]+/g, " "); + } + this._itemData[info.fieldID] = value; + return true; + } + } + var fieldID = Zotero.ItemFields.getID(field); if (!fieldID) { throw new Error('"' + field + '" is not a valid itemData field'); diff --git a/chrome/content/zotero/xpcom/data/itemFields.js b/chrome/content/zotero/xpcom/data/itemFields.js index a9e16e1675..a884fe6dbe 100644 --- a/chrome/content/zotero/xpcom/data/itemFields.js +++ b/chrome/content/zotero/xpcom/data/itemFields.js @@ -39,7 +39,9 @@ Zotero.ItemFields = new function () { var _typeFieldNamesByBase = {}; var _baseFieldIDsByTypeAndField = {}; var _autocompleteFields = null; - + var _loadInfo = []; + var _loadInfoMethods = {}; + // Privileged methods this.getName = getName; this.getID = getID; @@ -59,6 +61,7 @@ Zotero.ItemFields = new function () { this.init = async function () { _fields = {}; _fieldsFormats = []; + _loadInfo = []; var result = await Zotero.DB.queryAsync('SELECT * FROM fieldFormats'); @@ -123,6 +126,57 @@ Zotero.ItemFields = new function () { } + /** + * The lookups Zotero.Item#setField() does for every value loaded from the + * database, cached per item type and field + * + * The cache is dropped if any method it depends on is replaced, so an + * overridden method is still consulted. + * + * @param {Integer} itemTypeID + * @param {Integer} fieldID + * @return {Object|false} - false for an unknown field + */ + this._getLoadInfo = function (itemTypeID, fieldID) { + if (_loadInfoMethods.getID !== this.getID + || _loadInfoMethods.getFieldIDFromTypeAndBase !== this.getFieldIDFromTypeAndBase + || _loadInfoMethods.isValidForType !== this.isValidForType + || _loadInfoMethods.isMultiline !== this.isMultiline + || _loadInfoMethods.getItemTypeID !== Zotero.ItemTypes.getID) { + _loadInfo = []; + _loadInfoMethods = { + getID: this.getID, + getFieldIDFromTypeAndBase: this.getFieldIDFromTypeAndBase, + isValidForType: this.isValidForType, + isMultiline: this.isMultiline, + getItemTypeID: Zotero.ItemTypes.getID + }; + } + var byField = _loadInfo[itemTypeID]; + if (!byField) { + byField = _loadInfo[itemTypeID] = new Map(); + } + var info = byField.get(fieldID); + if (info === undefined) { + if (!this.getID(fieldID)) { + info = false; + } + else { + let resolvedID = this.getFieldIDFromTypeAndBase(itemTypeID, fieldID) || fieldID; + info = { + fieldID: resolvedID, + titleID: this.getID('title'), + valid: this.isValidForType(resolvedID, itemTypeID), + multiline: this.isMultiline(resolvedID), + isISBN: resolvedID == this.getID('ISBN') + }; + } + byField.set(fieldID, info); + } + return info; + }; + + /* * Return the fieldName for a passed fieldID or fieldName */ diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js index dd35b89a53..f98b5eadcc 100644 --- a/chrome/content/zotero/xpcom/data/items.js +++ b/chrome/content/zotero/xpcom/data/items.js @@ -298,6 +298,7 @@ Zotero.Items = function () { var sql = "SELECT itemID FROM items WHERE libraryID=?" + idSQL; var params = [libraryID]; var allItemIDs = []; + var fieldIDsByType = new Map(); await Zotero.DB.queryAsync( sql, params, @@ -306,9 +307,13 @@ Zotero.Items = function () { onRow: function (row) { let itemID = row.getResultByIndex(0); let item = this._objectCache[itemID]; - + // Set nonexistent fields in the cache list to false (instead of null) - let fieldIDs = Zotero.ItemFields.getItemTypeFields(item.itemTypeID); + let fieldIDs = fieldIDsByType.get(item.itemTypeID); + if (!fieldIDs) { + fieldIDs = Zotero.ItemFields.getItemTypeFields(item.itemTypeID); + fieldIDsByType.set(item.itemTypeID, fieldIDs); + } for (let j=0; j