From ee67c848d796cb1ed80e140cd42139d883885ee8 Mon Sep 17 00:00:00 2001 From: Adomas Ven Date: Fri, 21 Oct 2022 10:17:35 +0300 Subject: [PATCH] Adds support for merging adjacent citations (#2875) --- .../xpcom/connector/httpIntegrationClient.js | 9 ++++ chrome/content/zotero/xpcom/integration.js | 49 ++++++++++++++++--- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/chrome/content/zotero/xpcom/connector/httpIntegrationClient.js b/chrome/content/zotero/xpcom/connector/httpIntegrationClient.js index fdba7b921f..87dd791292 100644 --- a/chrome/content/zotero/xpcom/connector/httpIntegrationClient.js +++ b/chrome/content/zotero/xpcom/connector/httpIntegrationClient.js @@ -61,6 +61,7 @@ Zotero.HTTPIntegrationClient.Application = function() { this.supportedNotes = ['footnotes']; this.supportsImportExport = false; this.supportsTextInsertion = false; + this.supportsCitationMerging = false; this.processorName = "HTTP Integration"; }; Zotero.HTTPIntegrationClient.Application.prototype = { @@ -70,6 +71,7 @@ Zotero.HTTPIntegrationClient.Application.prototype = { this.supportedNotes = result.supportedNotes || this.supportedNotes; this.supportsImportExport = result.supportsImportExport || this.supportsImportExport; this.supportsTextInsertion = result.supportsTextInsertion || this.supportsTextInsertion; + this.supportsCitationMerging = result.supportsCitationMerging || this.supportsCitationMerging; this.processorName = result.processorName || this.processorName; return new Zotero.HTTPIntegrationClient.Document(result.documentID, this.processorName); } @@ -168,6 +170,10 @@ Zotero.HTTPIntegrationClient.Field = function(documentID, json) { this._code = json.code; this._text = json.text; this._noteIndex = json.noteIndex; + this._adjacent = json.adjacent; + if (this._adjacent !== 'undefined') { + this.isAdjacentToNextField = this._isAdjacentToNextField; + } }; Zotero.HTTPIntegrationClient.Field.prototype = {}; @@ -201,3 +207,6 @@ Zotero.HTTPIntegrationClient.Field.prototype.getNoteIndex = async function() { Zotero.HTTPIntegrationClient.Field.prototype.equals = async function(arg) { return this._id === arg._id; }; +Zotero.HTTPIntegrationClient.Field.prototype._isAdjacentToNextField = async function() { + return this._adjacent; +} diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js index d02892a9a0..91640f45b6 100644 --- a/chrome/content/zotero/xpcom/integration.js +++ b/chrome/content/zotero/xpcom/integration.js @@ -819,7 +819,8 @@ Zotero.Integration.Interface.prototype.addEditBibliography = Zotero.Promise.coro * @return {Promise} */ Zotero.Integration.Interface.prototype.refresh = async function() { - await this._session.init(true, false) + await this._session.init(true, false); + this._session.shouldMerge = true; this._session.reload = this._session.reload || this._session.data.prefs.delayCitationUpdates; await this._session.updateFromDocument(FORCE_CITATIONS_REGENERATE); @@ -1088,15 +1089,24 @@ Zotero.Integration.Session.prototype._processFields = async function () { if (!this._fields) { throw new Error("_processFields called without fetching fields first"); } - + + let adjacentCitations = []; for (var i = 0; i < this._fields.length; i++) { let field = await Zotero.Integration.Field.loadExisting(this._fields[i]); if (field.type === INTEGRATION_TYPE_ITEM) { var noteIndex = await field.getNoteIndex(), data = await field.unserialize(), citation = new Zotero.Integration.Citation(field, data, noteIndex); - - await this.addCitation(i, noteIndex, citation); + + if (this.shouldMerge && typeof field.isAdjacentToNextField === 'function' && await field.isAdjacentToNextField()) { + adjacentCitations.push(citation); + this._deleteFields[i] = true; + continue; + } + await this.addCitation(i, noteIndex, citation, adjacentCitations); + if (adjacentCitations.length) { + adjacentCitations = []; + } } else if (field.type === INTEGRATION_TYPE_BIBLIOGRAPHY) { if (this.ignoreEmptyBibliography && (await field.getText()).trim() === "") { this._removeCodeFields[i] = true; @@ -1977,9 +1987,17 @@ Zotero.Integration.Session.prototype.importDocument = async function() { /** * Adds a citation to the arrays representing the document */ -Zotero.Integration.Session.prototype.addCitation = async function (index, noteIndex, citation) { +Zotero.Integration.Session.prototype.addCitation = async function (index, noteIndex, citation, adjacentCitations=[]) { index = parseInt(index, 10); + if (adjacentCitations.length) { + Zotero.debug(`Merging adjacent citations ${adjacentCitations.map(c => c.citationID)} to citation ${citation.citationID}`); + for (let adjacentCitation of adjacentCitations) { + citation.mergeCitation(adjacentCitation); + } + this.updateIndices[index] = true; + } + var action = await citation.loadItemData(); if (action == Zotero.Integration.REMOVE_CODE) { @@ -2646,7 +2664,7 @@ Zotero.Integration.Field = class { throw new Error("Trying to instantiate Integration.Field with Integration.Field, not doc field"); } for (let func of Zotero.Integration.Field.INTERFACE) { - if (!(func in this)) { + if (!(func in this) && (func in field)) { this[func] = field[func].bind(field); } } @@ -2705,7 +2723,7 @@ Zotero.Integration.Field = class { } }; Zotero.Integration.Field.INTERFACE = ['delete', 'removeCode', 'select', 'setText', - 'getText', 'setCode', 'getCode', 'equals', 'getNoteIndex']; + 'getText', 'setCode', 'getCode', 'equals', 'getNoteIndex', 'isAdjacentToNextField']; /** * Load existing field in document and return correct instance of field type @@ -2944,6 +2962,23 @@ Zotero.Integration.Citation = class { this._field = citationField; } + /** + * Merge citation items and remove duplicates, unless the items have different + * @param citation {Citation} + */ + mergeCitation(citation) { + let items = this.citationItems.concat(citation.citationItems); + let addedItems = new Set(); + this.citationItems = [] + for (let item of items) { + if (addedItems.has(item.id)) { + continue; + } + addedItems.add(item.id); + this.citationItems.push(item); + } + } + /** * Load citation item data * @param {Boolean} [promptToReselect=true] - will throw a MissingItemException if false