diff --git a/chrome/content/zotero/integration/quickFormat.js b/chrome/content/zotero/integration/quickFormat.js
index fd3bffbab8..4220a5b086 100644
--- a/chrome/content/zotero/integration/quickFormat.js
+++ b/chrome/content/zotero/integration/quickFormat.js
@@ -311,12 +311,16 @@ var Zotero_QuickFormat = new function () {
// Exclude feeds
Zotero.Feeds.getAll()
.forEach(feed => s.addCondition("libraryID", "isNot", feed.libraryID));
- s.addCondition("quicksearch-titleCreatorYear", "contains", str);
- s.addCondition("itemType", "isNot", "attachment");
- if (io.filterLibraryIDs) {
- io.filterLibraryIDs.forEach(id => s.addCondition("libraryID", "is", id));
+ if (io.allowCitingNotes) {
+ s.addCondition("quicksearch-titleCreatorYearNote", "contains", str);
+ }
+ else {
+ s.addCondition("quicksearch-titleCreatorYear", "contains", str);
+ s.addCondition("itemType", "isNot", "attachment");
+ if (io.filterLibraryIDs) {
+ io.filterLibraryIDs.forEach(id => s.addCondition("libraryID", "is", id));
+ }
}
-
haveConditions = true;
}
}
@@ -671,8 +675,12 @@ var Zotero_QuickFormat = new function () {
var str = item.getField("firstCreator");
// Title, if no creator (getDisplayTitle in order to get case, e-mail, statute which don't have a title field)
- if(!str) {
- str = Zotero.getString("punctuation.openingQMark") + item.getDisplayTitle() + Zotero.getString("punctuation.closingQMark");
+ title = item.getDisplayTitle();
+ if (item.isNote()) {
+ title = title.substr(0, 24) + '…';
+ }
+ if (!str) {
+ str = Zotero.getString("punctuation.openingQMark") + title + Zotero.getString("punctuation.closingQMark");
}
// Date
@@ -754,10 +762,27 @@ var Zotero_QuickFormat = new function () {
*/
var _bubbleizeSelected = Zotero.Promise.coroutine(function* () {
if(!referenceBox.hasChildNodes() || !referenceBox.selectedItem) return false;
-
+
+ var ps = Services.prompt;
var citationItem = {"id":referenceBox.selectedItem.getAttribute("zotero-item")};
+ var item = Zotero.Cite.getItem(citationItem.id);
+ var nodes = Array.from(qfe.childNodes).filter(node => node.tagName == 'span');
+ if (nodes[0] && nodes[0].dataset && JSON.parse(nodes[0].dataset.citationItem).isNote) {
+ ps.alert(null,
+ Zotero.getString('general.warning'),
+ Zotero.getString('integration.cannotInsertItemWithNote'));
+ return false;
+ }
+ if (item && item.isNote() && nodes.length) {
+ if (!ps.confirm(null,
+ Zotero.getString('general.warning'),
+ Zotero.getString('integration.noteCiteItemsRemoved'))) {
+ return false;
+ }
+ _clearCitation();
+ citationItem.isNote = true;
+ }
if (typeof citationItem.id === "string" && citationItem.id.indexOf("/") !== -1) {
- var item = Zotero.Cite.getItem(citationItem.id);
citationItem.uris = item.cslURIs;
citationItem.itemData = item.cslItemData;
}
diff --git a/chrome/content/zotero/xpcom/connector/httpIntegrationClient.js b/chrome/content/zotero/xpcom/connector/httpIntegrationClient.js
index 83e5725df2..080aac5a05 100644
--- a/chrome/content/zotero/xpcom/connector/httpIntegrationClient.js
+++ b/chrome/content/zotero/xpcom/connector/httpIntegrationClient.js
@@ -60,6 +60,7 @@ Zotero.HTTPIntegrationClient.Application = function() {
this.outputFormat = 'html';
this.supportedNotes = ['footnotes'];
this.supportsImportExport = false;
+ this.supportsTextInsertion = false;
this.processorName = "HTTP Integration";
};
Zotero.HTTPIntegrationClient.Application.prototype = {
@@ -68,6 +69,7 @@ Zotero.HTTPIntegrationClient.Application.prototype = {
this.outputFormat = result.outputFormat || this.outputFormat;
this.supportedNotes = result.supportedNotes || this.supportedNotes;
this.supportsImportExport = result.supportsImportExport || this.supportsImportExport;
+ this.supportsTextInsertion = result.supportsTextInsertion || this.supportsTextInsertion;
this.processorName = result.processorName || this.processorName;
return new Zotero.HTTPIntegrationClient.Document(result.documentID);
}
@@ -80,7 +82,8 @@ Zotero.HTTPIntegrationClient.Document = function(documentID) {
this._documentID = documentID;
};
for (let method of ["activate", "canInsertField", "displayAlert", "getDocumentData",
- "setDocumentData", "setBibliographyStyle", "importDocument", "exportDocument"]) {
+ "setDocumentData", "setBibliographyStyle", "importDocument", "exportDocument",
+ "insertText"]) {
Zotero.HTTPIntegrationClient.Document.prototype[method] = async function() {
return Zotero.HTTPIntegrationClient.sendCommand("Document."+method,
[this._documentID].concat(Array.prototype.slice.call(arguments)));
@@ -146,6 +149,10 @@ Zotero.HTTPIntegrationClient.Document.prototype.convert = async function(fields,
fields = fields.map((f) => f._id);
await Zotero.HTTPIntegrationClient.sendCommand("Document.convert", [this._documentID, fields, fieldType, noteTypes]);
};
+Zotero.HTTPIntegrationClient.Document.prototype.convertPlaceholdersToFields = async function(codes, placeholderIDs, noteType) {
+ var retVal = await Zotero.HTTPIntegrationClient.sendCommand("Document.convertPlaceholdersToFields", [this._documentID, codes, placeholderIDs, noteType]);
+ return retVal.map(field => new Zotero.HTTPIntegrationClient.Field(this._documentID, field));
+}
Zotero.HTTPIntegrationClient.Document.prototype.complete = async function() {
Zotero.HTTPIntegrationClient.inProgress = false;
Zotero.HTTPIntegrationClient.sendCommand("Document.complete", [this._documentID]);
diff --git a/chrome/content/zotero/xpcom/data/search.js b/chrome/content/zotero/xpcom/data/search.js
index 0470b2d495..f8e2d99379 100644
--- a/chrome/content/zotero/xpcom/data/search.js
+++ b/chrome/content/zotero/xpcom/data/search.js
@@ -313,12 +313,16 @@ Zotero.Search.prototype.addCondition = function (condition, operator, value, req
this.addCondition('key', 'is', part.text, false);
}
- if (condition == 'quicksearch-titleCreatorYear') {
+ if (condition.startsWith('quicksearch-titleCreatorYear')) {
this.addCondition('title', operator, part.text, false);
this.addCondition('publicationTitle', operator, part.text, false);
this.addCondition('shortTitle', operator, part.text, false);
this.addCondition('court', operator, part.text, false);
this.addCondition('year', operator, part.text, false);
+
+ if (condition == 'quicksearch-titleCreatorYearNote') {
+ this.addCondition('note', operator, part.text, false);
+ }
}
else {
this.addCondition('field', operator, part.text, false);
diff --git a/chrome/content/zotero/xpcom/data/searchConditions.js b/chrome/content/zotero/xpcom/data/searchConditions.js
index 11fd2c53a3..e24b369913 100644
--- a/chrome/content/zotero/xpcom/data/searchConditions.js
+++ b/chrome/content/zotero/xpcom/data/searchConditions.js
@@ -168,6 +168,17 @@ Zotero.SearchConditions = new function(){
noLoad: true
},
+ {
+ name: 'quicksearch-titleCreatorYearNote',
+ operators: {
+ is: true,
+ isNot: true,
+ contains: true,
+ doesNotContain: true
+ },
+ noLoad: true
+ },
+
{
name: 'quicksearch-fields',
operators: {
diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js
index 51ba061bf2..dd601f379e 100644
--- a/chrome/content/zotero/xpcom/integration.js
+++ b/chrome/content/zotero/xpcom/integration.js
@@ -61,6 +61,8 @@ const DELAYED_CITATION_HTML_STYLING_END = ""
const EXPORTED_DOCUMENT_MARKER = "ZOTERO_TRANSFER_DOCUMENT";
+const NOTE_CITATION_PLACEHOLDER_LINK = 'https://www.zotero.org/?';
+
Zotero.Integration = new function() {
Components.utils.import("resource://gre/modules/Services.jsm");
@@ -584,10 +586,14 @@ Zotero.Integration.Interface.prototype.addEditCitation = async function (docFiel
await this._session.init(false, false);
docField = docField || await this._doc.cursorInField(this._session.data.prefs['fieldType']);
- let [idx, field, citation] = await this._session.cite(docField);
- await this._session.addCitation(idx, await field.getNoteIndex(), citation);
+ let citations = await this._session.cite(docField);
+ for (let citation of citations) {
+ await this._session.addCitation(citation._fieldIndex, await citation._field.getNoteIndex(), citation);
+ }
if (this._session.data.prefs.delayCitationUpdates) {
- return this._session.writeDelayedCitation(field, citation);
+ for (let citation of citations) {
+ await this._session.writeDelayedCitation(citation._field, citation);
+ }
} else {
return this._session.updateDocument(FORCE_CITATIONS_FALSE, false, false);
}
@@ -843,7 +849,7 @@ Zotero.Integration.Session = function(doc, app) {
* Checks that it is appropriate to add fields to the current document at the current
* position, then adds one.
*/
-Zotero.Integration.Session.prototype.addField = async function(note) {
+Zotero.Integration.Session.prototype.addField = async function(note, fieldIndex=-1) {
// Get citation types if necessary
if (!await this._doc.canInsertField(this.data.prefs['fieldType'])) {
return Zotero.Promise.reject(new Zotero.Exception.Alert("integration.error.cannotInsertHere",
@@ -867,12 +873,17 @@ Zotero.Integration.Session.prototype.addField = async function(note) {
field.setCode('TEMP');
}
// If fields already retrieved, further this.getFields() calls will returned the cached version
- // So we append this field to that list
+ // So add this field to the cache
if (this._fields) {
- this._fields.push(field);
+ if (fieldIndex == -1) {
+ this._fields.push(field);
+ }
+ else {
+ this._fields.splice(fieldIndex, 0, field);
+ }
}
- return Zotero.Promise.resolve(field);
+ return field;
}
/**
@@ -1241,9 +1252,9 @@ Zotero.Integration.Session.prototype._updateDocument = async function(forceCitat
await this._fields[removeCodeFields[i]].removeCode();
}
- var deleteFields = Object.keys(this._deleteFields).sort();
- for (var i=(deleteFields.length-1); i>=0; i--) {
- this._fields[deleteFields[i]].delete();
+ var deleteFields = Object.keys(this._deleteFields).sort((a, b) => b - a);
+ for (let fieldIndex of deleteFields) {
+ this._fields[fieldIndex].delete();
}
this.processIndices = {}
}
@@ -1328,7 +1339,8 @@ Zotero.Integration.Session.prototype.cite = async function (field) {
var io = new Zotero.Integration.CitationEditInterface(
citation, this.style.opt.sort_citations,
- fieldIndexPromise, citationsByItemIDPromise, previewFn
+ fieldIndexPromise, citationsByItemIDPromise, previewFn,
+ this._app.supportsTextInsertion
);
Zotero.debug(`Editing citation:`);
Zotero.debug(JSON.stringify(citation.toJSON()));
@@ -1360,18 +1372,127 @@ Zotero.Integration.Session.prototype.cite = async function (field) {
var fieldIndex = await fieldIndexPromise;
// Make sure session is updated
await citationsByItemIDPromise;
- return [fieldIndex, field, io.citation];
+
+ let citations = await this._insertCitingResult(fieldIndex, field, io.citation);
+ // We need to re-update from document because we've inserted multiple fields.
+ // Don't worry, the field list and info is cached so this triggers no calls to the doc.
+ await this.updateFromDocument(FORCE_CITATIONS_FALSE);
+ for (let citation of citations) {
+ await this.addCitation(citation._fieldIndex, await citation._field.getNoteIndex(), citation);
+ }
+ return citations;
+};
+
+/**
+ * Inserts a citing result, where a citing result is either multiple Items or a Note item.
+ * Notes may contain Items in them, which means that
+ * a single citing result insert can produce multiple Citations.
+ *
+ * Returns an array of Citation objects which correspond to inserted citations. At least 1 Citation
+ * is always returned.
+ *
+ * @param fieldIndex
+ * @param field
+ * @param citation
+ * @returns {Promise<[]>}
+ * @private
+ */
+Zotero.Integration.Session.prototype._insertCitingResult = async function (fieldIndex, field, citation) {
+ await citation.loadItemData();
+
+ let firstItem = Zotero.Cite.getItem(citation.citationItems[0].id);
+ if (firstItem && firstItem.isNote()) {
+ return this._insertNoteIntoDocument(fieldIndex, field, firstItem);
+ }
+ else {
+ return [await this._insertItemsIntoDocument(fieldIndex++, field, citation)];
+ }
+};
+
+/**
+ * Splits out cited items from the note text and converts them to placeholder links.
+ *
+ * Returns the modified note text and an array of citation objects and their corresponding
+ * placeholder IDs
+ * @param item {Zotero.Item}
+ */
+Zotero.Integration.Session.prototype._processNote = function (item) {
+ let text = item.getNote();
+ let parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Components.interfaces.nsIDOMParser);
+ let doc = parser.parseFromString(text, "text/html");
+ let citationsElems = doc.querySelectorAll('.citation[data-citation]');
+ let citations = [];
+ let placeholderIDs = [];
+ for (let citationElem of citationsElems) {
+ try {
+ // Add the citation code object to citations array
+ let citation = JSON.parse(decodeURIComponent(citationElem.dataset.citation));
+ delete citation.properties;
+ citations.push(citation);
+ let placeholderID = Zotero.Utilities.randomString(6);
+ // Add the placeholder we'll be using for the link to placeholder array
+ placeholderIDs.push(placeholderID);
+ let placeholderURL = NOTE_CITATION_PLACEHOLDER_LINK + placeholderID;
+ // Split out the citation element and replace with a placeholder link
+ text = text.split(citationElem.outerHTML)
+ .join(`${citationElem.textContent}`);
+ }
+ catch (e) {
+ e.message = `Failed to parse a citation from a note: ${decodeURIComponent(citationElem.dataset.citation)}`;
+ Zotero.debug(e, 1);
+ Zotero.logError(e);
+ }
+ }
+ // TODO: Later we'll need to convert note HTML to RDF.
+ // if (Zotero.Integration.currentSession._app.outputFormat == 'rtf') {
+ // text = return Zotero.RTFConverter.HTMLToRTF(text);
+ // });
+ // }
+ return [text, citations, placeholderIDs];
+};
+
+Zotero.Integration.Session.prototype._insertNoteIntoDocument = async function (fieldIndex, field, noteItem) {
+ let [text, citations, placeholderIDs] = this._processNote(noteItem);
+ await field.delete();
+ await this._doc.insertText(text);
+ if (!citations.length) return [];
+
+ // Do these in reverse order to ensure we don't get messy document edits
+ citations.reverse();
+ placeholderIDs.reverse();
+ let fields = await this._doc.convertPlaceholdersToFields(citations.map(() => 'TEMP'),
+ placeholderIDs, this.data.prefs.noteType);
+
+ let insertedCitations = await Promise.all(fields.map(async (field, index) => {
+ let citation = new Zotero.Integration.Citation(new Zotero.Integration.CitationField(field, 'TEMP'),
+ citations[index]);
+ citation._fieldIndex = fieldIndex + fields.length - 1 - index;
+ return citation;
+ }));
+ return insertedCitations;
+};
+
+Zotero.Integration.Session.prototype._insertItemsIntoDocument = async function (fieldIndex, field, citation) {
+ if (!field) {
+ field = new Zotero.Integration.CitationField(await this.addField(true, fieldIndex));
+ }
+ citation._field = field;
+ citation._fieldIndex = fieldIndex;
+ return citation;
};
/**
* Citation editing functions and propertiesaccessible to quickFormat.js and addCitationDialog.js
*/
-Zotero.Integration.CitationEditInterface = function(citation, sortable, fieldIndexPromise, citationsByItemIDPromise, previewFn) {
- this.citation = citation;
+Zotero.Integration.CitationEditInterface = function(items, sortable, fieldIndexPromise,
+ citationsByItemIDPromise, previewFn, allowCitingNotes=false){
+ this.citation = items;
this.sortable = sortable;
this.previewFn = previewFn;
this._fieldIndexPromise = fieldIndexPromise;
this._citationsByItemIDPromise = citationsByItemIDPromise;
+ this.allowCitingNotes = allowCitingNotes;
// Not available in quickFormat.js if this unspecified
this.wrappedJSObject = this;
@@ -1717,10 +1838,10 @@ Zotero.Integration.Session.prototype.importDocument = async function() {
/**
* Adds a citation to the arrays representing the document
*/
-Zotero.Integration.Session.prototype.addCitation = Zotero.Promise.coroutine(function* (index, noteIndex, citation) {
- var index = parseInt(index, 10);
+Zotero.Integration.Session.prototype.addCitation = async function (index, noteIndex, citation) {
+ index = parseInt(index, 10);
- var action = yield citation.loadItemData();
+ var action = await citation.loadItemData();
if (action == Zotero.Integration.REMOVE_CODE) {
// Mark for removal and return
@@ -1784,7 +1905,7 @@ Zotero.Integration.Session.prototype.addCitation = Zotero.Promise.coroutine(func
}
Zotero.debug("Integration: Adding citationID "+citation.citationID);
this.documentCitationIDs[citation.citationID] = index;
-});
+};
Zotero.Integration.Session.prototype.getCiteprocLists = function() {
var citations = [];
@@ -2340,7 +2461,7 @@ Zotero.Integration.Field = class {
}
this._field = field;
this._code = rawCode;
- this.type = INTEGRATION_TYPE_TEMP;
+ this.type = INTEGRATION_TYPE_TEMP;
}
async setCode(code) {
@@ -2369,8 +2490,17 @@ Zotero.Integration.Field = class {
async clearCode() {
return await this.setCode('{}');
}
+
+ async getText() {
+ if (this._text) {
+ return this._text;
+ }
+ this._text = await this._field.getText();
+ return this._text;
+ }
async setText(text) {
+ this._text = null;
var isRich = false;
// If RTF wrap with RTF tags
if (Zotero.Integration.currentSession.outputFormat == "rtf" && text.includes("\\")) {
@@ -2606,9 +2736,7 @@ Zotero.Integration.BibliographyField = class extends Zotero.Integration.Field {
Zotero.Integration.Citation = class {
constructor(citationField, data, noteIndex) {
- if (!data) {
- data = {citationItems: [], properties: {}};
- }
+ data = Object.assign({ citationItems: [], properties: {} }, data)
this.citationID = data.citationID;
this.citationItems = data.citationItems;
this.properties = data.properties;
@@ -2626,102 +2754,100 @@ Zotero.Integration.Citation = class {
* - Zotero.Integration.REMOVE_CODE
* - Zotero.Integration.DELETE
*/
- loadItemData() {
- return Zotero.Promise.coroutine(function *(promptToReselect=true){
- let items = [];
- var needUpdate = false;
+ async loadItemData(promptToReselect=true) {
+ let items = [];
+ var needUpdate = false;
+
+ if (!this.citationItems.length) {
+ return Zotero.Integration.DELETE;
+ }
+ for (var i=0, n=this.citationItems.length; i {
+ let field = new DocumentPluginDummy.Field(this);
+ field.code = code;
+ this.fields.push(field);
+ return field;
+ });
+ },
/**
* Gets all fields present in the document.
* @param {String} fieldType
* @returns {DocumentPluginDummy.Field[]}
*/
- getFields: function(fieldType) {return Array.from(this.fields)},
+ getFields: function (fieldType) {return Array.from(this.fields)},
/**
* Sets the bibliography style, overwriting the current values for this document
*/