Add ability to cite Zotero notes.
<span class=citation data-citation=serialized-citation-data/> elements will automatically be converted to zotero citations
This commit is contained in:
parent
2b3669afd8
commit
2e9e655479
7 changed files with 325 additions and 124 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -61,6 +61,8 @@ const DELAYED_CITATION_HTML_STYLING_END = "</div>"
|
|||
|
||||
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(`<a href="${placeholderURL}">${citationElem.textContent}</a>`);
|
||||
}
|
||||
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<n; i++) {
|
||||
var citationItem = this.citationItems[i];
|
||||
|
||||
if (!this.citationItems.length) {
|
||||
return Zotero.Integration.DELETE;
|
||||
}
|
||||
for (var i=0, n=this.citationItems.length; i<n; i++) {
|
||||
var citationItem = this.citationItems[i];
|
||||
// get Zotero item
|
||||
var zoteroItem = false;
|
||||
if (citationItem.uris) {
|
||||
let itemNeedsUpdate;
|
||||
[zoteroItem, itemNeedsUpdate] = await Zotero.Integration.currentSession.uriMap.getZoteroItemForURIs(citationItem.uris);
|
||||
needUpdate = needUpdate || itemNeedsUpdate;
|
||||
|
||||
// get Zotero item
|
||||
var zoteroItem = false;
|
||||
if (citationItem.uris) {
|
||||
let itemNeedsUpdate;
|
||||
[zoteroItem, itemNeedsUpdate] = yield Zotero.Integration.currentSession.uriMap.getZoteroItemForURIs(citationItem.uris);
|
||||
needUpdate = needUpdate || itemNeedsUpdate;
|
||||
// Unfortunately, people do weird things with their documents. One weird thing people
|
||||
// apparently like to do (http://forums.zotero.org/discussion/22262/) is to copy and
|
||||
// paste citations from other documents created with earlier versions of Zotero into
|
||||
// their documents and then not refresh the document. Usually, this isn't a problem. If
|
||||
// document is edited by the same user, it will work without incident. If the first
|
||||
// citation of a given item doesn't contain itemData, the user will get a
|
||||
// MissingItemException. However, it may also happen that the first citation contains
|
||||
// itemData, but later citations don't, because the user inserted the item properly and
|
||||
// then copied and pasted the same citation from another document. We check for that
|
||||
// possibility here.
|
||||
if (zoteroItem.cslItemData && !citationItem.itemData) {
|
||||
citationItem.itemData = zoteroItem.cslItemData;
|
||||
needUpdate = true;
|
||||
}
|
||||
} else {
|
||||
if (citationItem.key && citationItem.libraryID) {
|
||||
// DEBUG: why no library id?
|
||||
zoteroItem = Zotero.Items.getByLibraryAndKey(citationItem.libraryID, citationItem.key);
|
||||
} else if (citationItem.itemID) {
|
||||
zoteroItem = Zotero.Items.get(citationItem.itemID);
|
||||
} else if (citationItem.id) {
|
||||
zoteroItem = Zotero.Items.get(citationItem.id);
|
||||
}
|
||||
if (zoteroItem) needUpdate = true;
|
||||
}
|
||||
|
||||
// Item no longer in library
|
||||
if (!zoteroItem) {
|
||||
// Use embedded item
|
||||
if (citationItem.itemData) {
|
||||
Zotero.debug(`Item ${JSON.stringify(citationItem.uris)} not in library. Using embedded data`);
|
||||
// add new embedded item
|
||||
var itemData = Zotero.Utilities.deepCopy(citationItem.itemData);
|
||||
|
||||
// Unfortunately, people do weird things with their documents. One weird thing people
|
||||
// apparently like to do (http://forums.zotero.org/discussion/22262/) is to copy and
|
||||
// paste citations from other documents created with earlier versions of Zotero into
|
||||
// their documents and then not refresh the document. Usually, this isn't a problem. If
|
||||
// document is edited by the same user, it will work without incident. If the first
|
||||
// citation of a given item doesn't contain itemData, the user will get a
|
||||
// MissingItemException. However, it may also happen that the first citation contains
|
||||
// itemData, but later citations don't, because the user inserted the item properly and
|
||||
// then copied and pasted the same citation from another document. We check for that
|
||||
// possibility here.
|
||||
if (zoteroItem.cslItemData && !citationItem.itemData) {
|
||||
citationItem.itemData = zoteroItem.cslItemData;
|
||||
needUpdate = true;
|
||||
}
|
||||
} else {
|
||||
if (citationItem.key && citationItem.libraryID) {
|
||||
// DEBUG: why no library id?
|
||||
zoteroItem = Zotero.Items.getByLibraryAndKey(citationItem.libraryID, citationItem.key);
|
||||
} else if (citationItem.itemID) {
|
||||
zoteroItem = Zotero.Items.get(citationItem.itemID);
|
||||
} else if (citationItem.id) {
|
||||
zoteroItem = Zotero.Items.get(citationItem.id);
|
||||
// assign a random string as an item ID
|
||||
var anonymousID = Zotero.randomString();
|
||||
var globalID = itemData.id = citationItem.id = Zotero.Integration.currentSession.data.sessionID+"/"+anonymousID;
|
||||
Zotero.Integration.currentSession.embeddedItems[anonymousID] = itemData;
|
||||
|
||||
// assign a Zotero item
|
||||
var surrogateItem = Zotero.Integration.currentSession.embeddedZoteroItems[anonymousID] = new Zotero.Item();
|
||||
Zotero.Utilities.itemFromCSLJSON(surrogateItem, itemData);
|
||||
surrogateItem.cslItemID = globalID;
|
||||
surrogateItem.cslURIs = citationItem.uris;
|
||||
surrogateItem.cslItemData = itemData;
|
||||
|
||||
for(var j=0, m=citationItem.uris.length; j<m; j++) {
|
||||
Zotero.Integration.currentSession.embeddedItemsByURI[citationItem.uris[j]] = surrogateItem;
|
||||
}
|
||||
} else if (promptToReselect) {
|
||||
zoteroItem = await this.handleMissingItem(i);
|
||||
if (zoteroItem) needUpdate = true;
|
||||
}
|
||||
|
||||
// Item no longer in library
|
||||
if (!zoteroItem) {
|
||||
// Use embedded item
|
||||
if (citationItem.itemData) {
|
||||
Zotero.debug(`Item ${JSON.stringify(citationItem.uris)} not in library. Using embedded data`);
|
||||
// add new embedded item
|
||||
var itemData = Zotero.Utilities.deepCopy(citationItem.itemData);
|
||||
|
||||
// assign a random string as an item ID
|
||||
var anonymousID = Zotero.randomString();
|
||||
var globalID = itemData.id = citationItem.id = Zotero.Integration.currentSession.data.sessionID+"/"+anonymousID;
|
||||
Zotero.Integration.currentSession.embeddedItems[anonymousID] = itemData;
|
||||
|
||||
// assign a Zotero item
|
||||
var surrogateItem = Zotero.Integration.currentSession.embeddedZoteroItems[anonymousID] = new Zotero.Item();
|
||||
Zotero.Utilities.itemFromCSLJSON(surrogateItem, itemData);
|
||||
surrogateItem.cslItemID = globalID;
|
||||
surrogateItem.cslURIs = citationItem.uris;
|
||||
surrogateItem.cslItemData = itemData;
|
||||
|
||||
for(var j=0, m=citationItem.uris.length; j<m; j++) {
|
||||
Zotero.Integration.currentSession.embeddedItemsByURI[citationItem.uris[j]] = surrogateItem;
|
||||
}
|
||||
} else if (promptToReselect) {
|
||||
zoteroItem = yield this.handleMissingItem(i);
|
||||
if (zoteroItem) needUpdate = true;
|
||||
else return Zotero.Integration.REMOVE_CODE;
|
||||
} else {
|
||||
// throw a MissingItemException
|
||||
throw (new Zotero.Integration.MissingItemException(this, this.citationItems[i]));
|
||||
}
|
||||
}
|
||||
|
||||
if (zoteroItem) {
|
||||
if (zoteroItem.cslItemID) {
|
||||
citationItem.id = zoteroItem.cslItemID;
|
||||
}
|
||||
else {
|
||||
citationItem.id = zoteroItem.id;
|
||||
items.push(zoteroItem);
|
||||
}
|
||||
else return Zotero.Integration.REMOVE_CODE;
|
||||
} else {
|
||||
// throw a MissingItemException
|
||||
throw (new Zotero.Integration.MissingItemException(this, this.citationItems[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Items may be in libraries that haven't been loaded, and retrieveItem() is synchronous, so load
|
||||
// all data (as required by toJSON(), which is used by itemToExportFormat(), which is used by
|
||||
// itemToCSLJSON()) now
|
||||
if (items.length) {
|
||||
yield Zotero.Items.loadDataTypes(items);
|
||||
if (zoteroItem) {
|
||||
if (zoteroItem.cslItemID) {
|
||||
citationItem.id = zoteroItem.cslItemID;
|
||||
}
|
||||
else {
|
||||
citationItem.id = zoteroItem.id;
|
||||
items.push(zoteroItem);
|
||||
}
|
||||
}
|
||||
return needUpdate ? Zotero.Integration.UPDATE : Zotero.Integration.NO_ACTION;
|
||||
}).apply(this, arguments);
|
||||
}
|
||||
|
||||
// Items may be in libraries that haven't been loaded, and retrieveItem() is synchronous, so load
|
||||
// all data (as required by toJSON(), which is used by itemToExportFormat(), which is used by
|
||||
// itemToCSLJSON()) now
|
||||
if (items.length) {
|
||||
await Zotero.Items.loadDataTypes(items);
|
||||
}
|
||||
return needUpdate ? Zotero.Integration.UPDATE : Zotero.Integration.NO_ACTION;
|
||||
}
|
||||
|
||||
async handleMissingItem(idx) {
|
||||
|
|
|
@ -932,6 +932,8 @@ integration.exportDocument.title = Prepare Citations for Transfer
|
|||
integration.exportDocument.description1 = Zotero will convert citations in the document to a format that can be safely transferred to another supported word processor.
|
||||
integration.exportDocument.description2 = You should make a backup of the document before proceeding.
|
||||
integration.importInstructions = The Zotero citations in this document have been converted to a format that can be safely transferred between word processors. Open this document in a supported word processor and press Refresh in the Zotero plugin to continue working with the citations.
|
||||
integration.noteCiteItemsRemoved = Notes cannot be cited with other Zotero items. Selecting this note item will remove other items in this citation.\n\nDo you want to proceed?
|
||||
integration.cannotInsertItemWithNote = Notes cannot be cited with other Zotero items. Please remove the cited note item before proceeding.
|
||||
|
||||
styles.install.title = Install Style
|
||||
styles.install.unexpectedError = An unexpected error occurred while installing "%1$S"
|
||||
|
|
|
@ -21,7 +21,10 @@ describe("Zotero.Integration", function () {
|
|||
this.primaryFieldType = "Field";
|
||||
this.secondaryFieldType = "Bookmark";
|
||||
this.supportedNotes = ['footnotes', 'endnotes'];
|
||||
// Will display an option to switch word processors in the Doc Prefs
|
||||
this.supportsImportExport = true;
|
||||
// Will allow inserting notes
|
||||
this.supportsTextInsertion = true;
|
||||
this.fields = [];
|
||||
};
|
||||
DocumentPluginDummy.Application.prototype = {
|
||||
|
@ -82,25 +85,48 @@ describe("Zotero.Integration", function () {
|
|||
*/
|
||||
setDocumentData: function(data) {this.data = data},
|
||||
/**
|
||||
* Inserts a field at the given position and initializes the field object.
|
||||
* Inserts a field at cursor position and initializes the field object.
|
||||
* If Document.insertText() was called previously inserts the field
|
||||
* directly after the inserted text.
|
||||
* @param {String} fieldType
|
||||
* @param {Integer} noteType
|
||||
* @returns {DocumentPluginDummy.Field}
|
||||
*/
|
||||
insertField: function(fieldType, noteType) {
|
||||
insertField: function(fieldType, noteType) {
|
||||
if (typeof noteType != "number") {
|
||||
throw new Error("noteType must be an integer");
|
||||
}
|
||||
var field = new DocumentPluginDummy.Field(this);
|
||||
var field = new DocumentPluginDummy.Field(this);
|
||||
this.fields.push(field);
|
||||
return field;
|
||||
},
|
||||
/**
|
||||
* Inserts rich text at cursor position. If Document.insertField() was called
|
||||
* previously inserts the text directly after the inserted field.
|
||||
* @param {String} text
|
||||
*/
|
||||
insertText: function (text) { return; },
|
||||
/**
|
||||
* Converts placeholders (which are text with links to https://www.zotero.org/?[placeholderID])
|
||||
* to fields and sets their field codes to strings in `codes` in the reverse order of their appearance
|
||||
* @param {String[]} codes
|
||||
* @param {Number} noteType - controls whether citations should be in-text or in footnotes/endnotes
|
||||
* @return {Field[]}
|
||||
*/
|
||||
convertPlaceholdersToFields: function (codes, noteType) {
|
||||
return codes.map(code => {
|
||||
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
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue