Merge branch 'deasyncification'

This commit is contained in:
Dan Stillman 2016-03-16 02:02:41 -04:00
commit a949d6bf8d
77 changed files with 2622 additions and 2125 deletions

View File

@ -1569,7 +1569,7 @@
this._lastTabIndex = parseInt(textbox.getAttribute('ztabindex')) - 1;
this._tabDirection = 1;
var creator = yield Zotero.Creators.getAsync(creatorID);
var creator = Zotero.Creators.get(creatorID);
var otherField = creatorField == 'lastName' ? 'firstName' : 'lastName';

View File

@ -417,48 +417,41 @@
</method>
<method name="updateTagsSummary">
<body><![CDATA[
Zotero.spawn(function* () {
var v = yield this.id('tags').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
}
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
+ Zotero.getString('punctuation.colon');
this.id('tagsClick').value = v;
}, this);
var v = this.id('tags').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
}
this.id('tagsLabel').value = Zotero.getString('itemFields.tags')
+ Zotero.getString('punctuation.colon');
this.id('tagsClick').value = v;
]]></body>
</method>
<method name="relatedClick">
<body><![CDATA[
Zotero.spawn(function* () {
yield this.item.loadRelations();
var relatedList = this.item.relatedItems;
if (relatedList.length > 0) {
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
this.id('relatedPopup').openPopupAtScreen(x, y, false);
}
else {
this.id('related').add();
}
}, this);
var relatedList = this.item.relatedItems;
if (relatedList.length > 0) {
var x = this.boxObject.screenX;
var y = this.boxObject.screenY;
this.id('relatedPopup').openPopupAtScreen(x, y, false);
}
else {
this.id('related').add();
}
]]></body>
</method>
<method name="updateRelatedSummary">
<body><![CDATA[
Zotero.spawn(function* () {
var v = yield this.id('related').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
}
this.id('relatedLabel').value = Zotero.getString('itemFields.related')
+ Zotero.getString('punctuation.colon');
this.id('relatedClick').value = v;
}, this)
var v = this.id('related').summary;
if (!v || v == "") {
v = "[" + Zotero.getString('pane.item.noteEditor.clickHere') + "]";
}
this.id('relatedLabel').value = Zotero.getString('itemFields.related')
+ Zotero.getString('punctuation.colon');
this.id('relatedClick').value = v;
]]></body>
</method>
<method name="parentClick">

View File

@ -74,25 +74,20 @@
<property name="summary">
<getter>
<![CDATA[
return Zotero.spawn(function* () {
var r = "";
if (this.item) {
yield this.item.loadRelations()
.tap(() => Zotero.Promise.check(this.item));
var keys = this.item.relatedItems;
if (keys.length) {
let items = yield Zotero.Items.getAsync(keys)
.tap(() => Zotero.Promise.check(this.item));
for (let item of items) {
r = r + item.getDisplayTitle() + ", ";
}
r = r.substr(0,r.length-2);
var r = "";
if (this.item) {
var keys = this.item.relatedItems;
if (keys.length) {
for (let key of keys) {
let item = Zotero.Items.getByLibraryAndKey(this.item.libraryID, key);
r = r + item.getDisplayTitle() + ", ";
}
r = r.substr(0,r.length-2);
}
return r;
}, this);
}
return r;
]]>
</getter>
</property>
@ -129,89 +124,79 @@
</method>
<method name="refresh">
<body>
<![CDATA[
return Zotero.spawn(function* () {
var addButton = this.id('addButton');
addButton.hidden = !this.editable;
var rows = this.id('relatedRows');
while(rows.hasChildNodes())
rows.removeChild(rows.firstChild);
if (this.item) {
yield this.item.loadRelations()
.tap(() => Zotero.Promise.check(this.item));
var relatedKeys = this.item.relatedItems;
for (var i = 0; i < relatedKeys.length; i++) {
let key = relatedKeys[i];
let relatedItem =
yield Zotero.Items.getByLibraryAndKeyAsync(
this.item.libraryID, key
)
.tap(() => Zotero.Promise.check(this.item));
let id = relatedItem.id;
yield relatedItem.loadItemData()
.tap(() => Zotero.Promise.check(this.item));
let icon = document.createElement("image");
icon.className = "zotero-box-icon";
let type = Zotero.ItemTypes.getName(relatedItem.itemTypeID);
if (type=='attachment')
{
switch (relatedItem.attaachmentLinkMode) {
case Zotero.Attachments.LINK_MODE_LINKED_URL:
type += '-web-link';
break;
case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
type += '-snapshot';
break;
case Zotero.Attachments.LINK_MODE_LINKED_FILE:
type += '-link';
break;
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
type += '-file';
break;
}
<body><![CDATA[
var addButton = this.id('addButton');
addButton.hidden = !this.editable;
var rows = this.id('relatedRows');
while(rows.hasChildNodes())
rows.removeChild(rows.firstChild);
if (this.item) {
var relatedKeys = this.item.relatedItems;
for (var i = 0; i < relatedKeys.length; i++) {
let key = relatedKeys[i];
let relatedItem = Zotero.Items.getByLibraryAndKey(
this.item.libraryID, key
);
let id = relatedItem.id;
let icon = document.createElement("image");
icon.className = "zotero-box-icon";
let type = Zotero.ItemTypes.getName(relatedItem.itemTypeID);
if (type=='attachment')
{
switch (relatedItem.attaachmentLinkMode) {
case Zotero.Attachments.LINK_MODE_LINKED_URL:
type += '-web-link';
break;
case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
type += '-snapshot';
break;
case Zotero.Attachments.LINK_MODE_LINKED_FILE:
type += '-link';
break;
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
type += '-file';
break;
}
icon.setAttribute('src','chrome://zotero/skin/treeitem-' + type + '.png');
var label = document.createElement("label");
label.className = "zotero-box-label";
label.setAttribute('value', relatedItem.getDisplayTitle());
label.setAttribute('crop','end');
label.setAttribute('flex','1');
var box = document.createElement('box');
box.setAttribute('onclick',
"document.getBindingParent(this).showItem('" + id + "')");
box.setAttribute('class','zotero-clicky');
box.setAttribute('flex','1');
box.appendChild(icon);
box.appendChild(label);
if (this.editable) {
var remove = document.createElement("label");
remove.setAttribute('value','-');
remove.setAttribute('onclick',
"document.getBindingParent(this).remove('" + id + "');");
remove.setAttribute('class','zotero-clicky zotero-clicky-minus');
}
var row = document.createElement("row");
row.appendChild(box);
if (this.editable) {
row.appendChild(remove);
}
rows.appendChild(row);
}
this.updateCount(relatedKeys.length);
icon.setAttribute('src','chrome://zotero/skin/treeitem-' + type + '.png');
var label = document.createElement("label");
label.className = "zotero-box-label";
label.setAttribute('value', relatedItem.getDisplayTitle());
label.setAttribute('crop','end');
label.setAttribute('flex','1');
var box = document.createElement('box');
box.setAttribute('onclick',
"document.getBindingParent(this).showItem('" + id + "')");
box.setAttribute('class','zotero-clicky');
box.setAttribute('flex','1');
box.appendChild(icon);
box.appendChild(label);
if (this.editable) {
var remove = document.createElement("label");
remove.setAttribute('value','-');
remove.setAttribute('onclick',
"document.getBindingParent(this).remove('" + id + "');");
remove.setAttribute('class','zotero-clicky zotero-clicky-minus');
}
var row = document.createElement("row");
row.appendChild(box);
if (this.editable) {
row.appendChild(remove);
}
rows.appendChild(row);
}
}, this);
]]>
</body>
this.updateCount(relatedKeys.length);
}
]]></body>
</method>
<method name="add">
<body><![CDATA[
@ -238,13 +223,11 @@
}
yield Zotero.DB.executeTransaction(function* () {
for (let relItem of relItems) {
yield this.item.loadRelations();
if (this.item.addRelatedItem(relItem)) {
yield this.item.save({
skipDateModifiedUpdate: true
});
}
yield relItem.loadRelations();
if (relItem.addRelatedItem(this.item)) {
yield relItem.save({
skipDateModifiedUpdate: true
@ -267,7 +250,6 @@
skipDateModifiedUpdate: true
});
}
yield item.loadRelations();
if (item.removeRelatedItem(this.item)) {
yield item.save({
skipDateModifiedUpdate: true

View File

@ -91,24 +91,20 @@
<property name="summary">
<getter><![CDATA[
return Zotero.spawn(function* () {
var r = "";
if (this.item) {
yield this.item.loadTags()
.tap(() => Zotero.Promise.check(this.mode));
var tags = this.item.getTags();
if (tags) {
for(var i = 0; i < tags.length; i++)
{
r = r + tags[i].tag + ", ";
}
r = r.substr(0,r.length-2);
}
}
var r = "";
return r;
}, this);
if (this.item) {
var tags = this.item.getTags();
if (tags) {
for(var i = 0; i < tags.length; i++)
{
r = r + tags[i].tag + ", ";
}
r = r.substr(0,r.length-2);
}
}
return r;
]]></getter>
</property>
@ -141,7 +137,8 @@
return Zotero.spawn(function* () {
if (type == 'setting') {
if (ids.some(function (val) val.split("/")[1] == 'tagColors') && this.item) {
return this.reload();
this.reload();
return;
}
}
else if (type == 'item-tag') {
@ -198,7 +195,8 @@
}
else if (type == 'tag') {
if (event == 'modify') {
return this.reload();
this.reload();
return;
}
}
}.bind(this));
@ -208,41 +206,32 @@
<method name="reload">
<body><![CDATA[
return Zotero.spawn(function* () {
Zotero.debug('Reloading tags box');
yield this.item.loadTags()
.tap(() => Zotero.Promise.check(this.mode));
// Cancel field focusing while we're updating
this._reloading = true;
this.id('addButton').hidden = !this.editable;
this._tagColors = yield Zotero.Tags.getColors(this.item.libraryID)
.tap(() => Zotero.Promise.check(this.mode));
var rows = this.id('tagRows');
while(rows.hasChildNodes()) {
rows.removeChild(rows.firstChild);
}
var tags = this.item.getTags();
// Sort tags alphabetically
var collation = Zotero.getLocaleCollation();
tags.sort(function (a, b) collation.compareString(1, a.tag, b.tag));
for (let i=0; i<tags.length; i++) {
this.addDynamicRow(tags[i], i+1);
}
this.updateCount(tags.length);
this._reloading = false;
this._focusField();
var event = new Event('refresh');
this.dispatchEvent(event);
}, this);
Zotero.debug('Reloading tags box');
// Cancel field focusing while we're updating
this._reloading = true;
this.id('addButton').hidden = !this.editable;
this._tagColors = Zotero.Tags.getColors(this.item.libraryID);
var rows = this.id('tagRows');
while(rows.hasChildNodes()) {
rows.removeChild(rows.firstChild);
}
var tags = this.item.getTags();
// Sort tags alphabetically
var collation = Zotero.getLocaleCollation();
tags.sort(function (a, b) collation.compareString(1, a.tag, b.tag));
for (let i=0; i<tags.length; i++) {
this.addDynamicRow(tags[i], i+1);
}
this.updateCount(tags.length);
this._reloading = false;
this._focusField();
]]></body>
</method>
@ -718,7 +707,7 @@
this._lastTabIndex = this.item.getTags().length;
}
yield this.reload();
this.reload();
}
// Single tag at end
else {

View File

@ -236,8 +236,7 @@
var emptyRegular = true;
var tagsBox = this.id('tags-box');
var tagColors = yield Zotero.Tags.getColors(this.libraryID)
.tap(() => Zotero.Promise.check(this.mode));
var tagColors = Zotero.Tags.getColors(this.libraryID);
// If new data, rebuild boxes
if (fetch || this._dirty) {
@ -245,6 +244,13 @@
.tap(() => Zotero.Promise.check(this.mode));
tagsBox.textContent = "";
// Add colored tags that aren't already real tags
let regularTags = new Set(this._tags.map(tag => tag.tag));
let coloredTags = new Set(tagColors.keys());
[for (x of coloredTags) if (!regularTags.has(x)) x].forEach(x =>
this._tags.push(Zotero.Tags.cleanData({ tag: x }))
);
// Sort by name
let collation = Zotero.getLocaleCollation();
this._tags.sort(function (a, b) {
@ -375,45 +381,43 @@
<method name="insertSorted">
<parameter name="tagObjs"/>
<body><![CDATA[
return Zotero.spawn(function* () {
var tagColors = yield Zotero.Tags.getColors(this._libraryID);
var collation = Zotero.getLocaleCollation();
tagObjs.sort(function (a, b) {
return collation.compareString(1, a.tag, b.tag);
});
// Create tag elements in sorted order
var tagsBox = this.id('tags-box');
var tagElems = tagsBox.childNodes;
var j = 0;
loop:
for (let i = 0; i < tagObjs.length; i++) {
let tagObj = tagObjs[i];
while (j < tagElems.length) {
let elem = tagElems[j];
let comp = collation.compareString(
1, tagObj.tag, elem.textContent
);
// If tag already exists, update type if new one is lower
if (comp == 0) {
let tagType = elem.getAttribute('tagType');
if (parseInt(tagObj.type) < parseInt(tagType)) {
elem.setAttribute('tagType', tagObj.type);
}
continue loop;
}
if (comp < 0) {
break;
}
j++;
}
this._insertClickableTag(tagsBox, tagObj, tagElems[j]);
this._updateClickableTag(
tagElems[j], tagElems[j].textContent, tagColors
var tagColors = Zotero.Tags.getColors(this._libraryID);
var collation = Zotero.getLocaleCollation();
tagObjs.sort(function (a, b) {
return collation.compareString(1, a.tag, b.tag);
});
// Create tag elements in sorted order
var tagsBox = this.id('tags-box');
var tagElems = tagsBox.childNodes;
var j = 0;
loop:
for (let i = 0; i < tagObjs.length; i++) {
let tagObj = tagObjs[i];
while (j < tagElems.length) {
let elem = tagElems[j];
let comp = collation.compareString(
1, tagObj.tag, elem.textContent
);
// If tag already exists, update type if new one is lower
if (comp == 0) {
let tagType = elem.getAttribute('tagType');
if (parseInt(tagObj.type) < parseInt(tagType)) {
elem.setAttribute('tagType', tagObj.type);
}
continue loop;
}
if (comp < 0) {
break;
}
j++;
}
}, this);
this._insertClickableTag(tagsBox, tagObj, tagElems[j]);
this._updateClickableTag(
tagElems[j], tagElems[j].textContent, tagColors
);
}
]]></body>
</method>
@ -512,7 +516,7 @@
}.bind(this));
if (tagObjs.length) {
yield this.insertSorted(tagObjs);
this.insertSorted(tagObjs);
}
}
// Don't add anything for item or collection-item; just update scope
@ -671,7 +675,7 @@
// Colored tags don't need to exist, so in that case
// just rename the color setting
else {
let color = yield Zotero.Tags.getColor(this.libraryID, oldName);
let color = Zotero.Tags.getColor(this.libraryID, oldName);
if (!color) {
throw new Error("Can't rename missing tag");
}
@ -715,18 +719,6 @@
]]></body>
</method>
<method name="getColor">
<parameter name="tagIDs"/>
<body><![CDATA[
return Zotero.spawn(function* () {
tagIDs = tagIDs.split('-');
var name = yield Zotero.Tags.getName(tagIDs[0]);
var colorData = yield Zotero.Tags.getColor(this.libraryID, name);
return colorData ? colorData.color : '#000000';
}.bind(this));
]]></body>
</method>
<method name="_insertClickableTag">
<parameter name="tagsBox"/>
@ -877,7 +869,7 @@
name: name
};
var tagColors = yield Zotero.Tags.getColors(this.libraryID);
var tagColors = Zotero.Tags.getColors(this.libraryID);
if (tagColors.size >= Zotero.Tags.MAX_COLORED_TAGS && !tagColors.has(io.name)) {
var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);

View File

@ -23,10 +23,13 @@
***** END LICENSE BLOCK *****
*/
"use strict";
var Zotero_Duplicates_Pane = new function () {
_items = [];
_otherItems = [];
_ignoreFields = ['dateAdded', 'dateModified', 'accessDate'];
var _masterItem;
var _items = [];
var _otherItems = [];
var _ignoreFields = ['dateAdded', 'dateModified', 'accessDate'];
this.setItems = function (items, displayNumItemsOnTypeError) {
var itemTypeID, oldestItem, otherItems = [];
@ -77,15 +80,13 @@ var Zotero_Duplicates_Pane = new function () {
// Update the UI
//
var diff = oldestItem.multiDiff(otherItems, _ignoreFields);
var button = document.getElementById('zotero-duplicates-merge-button');
var versionSelect = document.getElementById('zotero-duplicates-merge-version-select');
var itembox = document.getElementById('zotero-duplicates-merge-item-box');
var fieldSelect = document.getElementById('zotero-duplicates-merge-field-select');
versionSelect.hidden = !diff;
if (diff) {
var alternatives = oldestItem.multiDiff(otherItems, _ignoreFields);
if (alternatives) {
// Populate menulist with Date Added values from all items
var dateList = document.getElementById('zotero-duplicates-merge-original-date');
@ -111,8 +112,8 @@ var Zotero_Duplicates_Pane = new function () {
}
button.label = Zotero.getString('pane.item.duplicates.mergeItems', (otherItems.length + 1));
itembox.hiddenFields = diff ? [] : ['dateAdded', 'dateModified'];
fieldSelect.hidden = !diff;
versionSelect.hidden = fieldSelect.hidden = !alternatives;
itembox.hiddenFields = alternatives ? [] : ['dateAdded', 'dateModified'];
this.setMaster(0);
@ -130,27 +131,25 @@ var Zotero_Duplicates_Pane = new function () {
// Add master item's values to the beginning of each set of
// alternative values so that they're still available if the item box
// modifies the item
Zotero.spawn(function* () {
var diff = yield item.multiDiff(_otherItems, _ignoreFields);
if (diff) {
let itemValues = yield item.toJSON();
for (let i in diff) {
diff[i].unshift(itemValues[i] !== undefined ? itemValues[i] : '');
}
itembox.fieldAlternatives = diff;
var alternatives = item.multiDiff(_otherItems, _ignoreFields);
if (alternatives) {
let itemValues = item.toJSON();
for (let i in alternatives) {
alternatives[i].unshift(itemValues[i] !== undefined ? itemValues[i] : '');
}
var newItem = yield item.copy();
yield newItem.loadItemData();
yield newItem.loadCreators();
itembox.item = newItem;
});
itembox.fieldAlternatives = alternatives;
}
_masterItem = item;
itembox.item = item.clone();
}
this.merge = function () {
this.merge = Zotero.Promise.coroutine(function* () {
var itembox = document.getElementById('zotero-duplicates-merge-item-box');
Zotero.CollectionTreeCache.clear();
Zotero.Items.merge(itembox.item, _otherItems);
}
// Update master item with any field alternatives from the item box
_masterItem.fromJSON(itembox.item.toJSON());
Zotero.Items.merge(_masterItem, _otherItems);
});
}

View File

@ -126,6 +126,7 @@ var Zotero_File_Interface = new function() {
this.exportItems = exportItems;
this.bibliographyFromCollection = bibliographyFromCollection;
this.bibliographyFromItems = bibliographyFromItems;
this.copyItemsToClipboard = copyItemsToClipboard;
this.copyCitationToClipboard = copyCitationToClipboard;
/**
@ -407,7 +408,7 @@ var Zotero_File_Interface = new function() {
*
* Does not check that items are actual references (and not notes or attachments)
*/
this.copyItemsToClipboard = Zotero.Promise.coroutine(function* (items, style, locale, asHTML, asCitations) {
function copyItemsToClipboard(items, style, locale, asHTML, asCitations) {
// copy to clipboard
var transferable = Components.classes["@mozilla.org/widget/transferable;1"].
createInstance(Components.interfaces.nsITransferable);
@ -417,7 +418,7 @@ var Zotero_File_Interface = new function() {
var cslEngine = style.getCiteProc(locale);
// add HTML
var bibliography = yield Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "html", asCitations);
var bibliography = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "html", asCitations);
var str = Components.classes["@mozilla.org/supports-string;1"].
createInstance(Components.interfaces.nsISupportsString);
str.data = bibliography;
@ -427,7 +428,7 @@ var Zotero_File_Interface = new function() {
// add text (or HTML source)
if(!asHTML) {
cslEngine = style.getCiteProc(locale);
var bibliography = yield Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "text", asCitations);
var bibliography = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine, items, "text", asCitations);
}
var str = Components.classes["@mozilla.org/supports-string;1"].
createInstance(Components.interfaces.nsISupportsString);
@ -436,7 +437,7 @@ var Zotero_File_Interface = new function() {
transferable.setTransferData("text/unicode", str, bibliography.length*2);
clipboardService.setData(transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard);
});
}
/*
@ -483,7 +484,7 @@ var Zotero_File_Interface = new function() {
/*
* Shows bibliography options and creates a bibliography
*/
let _doBibliographyOptions = Zotero.Promise.coroutine(function* (name, items) {
function _doBibliographyOptions(name, items) {
// make sure at least one item is not a standalone note or attachment
var haveRegularItem = false;
for each(var item in items) {
@ -515,12 +516,12 @@ var Zotero_File_Interface = new function() {
// generate bibliography
try {
if(io.method == 'copy-to-clipboard') {
yield Zotero_File_Interface.copyItemsToClipboard(items, io.style, locale, false, io.mode === "citations");
Zotero_File_Interface.copyItemsToClipboard(items, io.style, locale, false, io.mode === "citations");
}
else {
var style = Zotero.Styles.get(io.style);
var cslEngine = style.getCiteProc(locale);
var bibliography = yield Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine,
var bibliography = Zotero.Cite.makeFormattedBibliographyOrCitationList(cslEngine,
items, format, io.mode === "citations");
}
} catch(e) {
@ -598,7 +599,7 @@ var Zotero_File_Interface = new function() {
fStream.close();
}
}
});
}
function _saveBibliography(name, format) {

View File

@ -94,13 +94,11 @@ var ZoteroItemPane = new function() {
_notesList.removeChild(_notesList.firstChild);
}
yield item.loadChildItems();
let notes = yield Zotero.Items.getAsync(item.getNotes());
if (notes.length) {
for (var i = 0; i < notes.length; i++) {
let note = notes[i];
let id = notes[i].id;
yield note.loadItemData();
var icon = document.createElement('image');
icon.className = "zotero-box-icon";
@ -148,7 +146,6 @@ var ZoteroItemPane = new function() {
box.mode = 'edit';
}
yield Zotero.Promise.all([item.loadItemData(), item.loadCreators()]);
box.item = item;
});

View File

@ -400,7 +400,6 @@ var Zotero_LocateMenu = new function() {
}
if(item.isRegularItem()) {
yield item.loadChildItems();
var attachments = item.getAttachments();
if(attachments) {
// look through url fields for non-file:/// attachments

View File

@ -395,7 +395,6 @@ var Zotero_RecognizePDF = new function() {
}
// put new item in same collections as the old one
yield item.loadCollections();
let itemCollections = item.getCollections();
for (let i = 0; i < itemCollections.length; i++) {
let collection = yield Zotero.Collections.getAsync(itemCollections[i]);

View File

@ -56,7 +56,6 @@ Zotero.API = {
if (!col) {
throw new Error('Invalid collection ID or key');
}
yield col.loadChildItems();
results = col.getChildItems();
break;

View File

@ -1090,8 +1090,7 @@ Zotero.Attachments = new function(){
Zotero.DB.requireTransaction();
attachment.loadItemData();
var newAttachment = yield attachment.clone(libraryID);
var newAttachment = attachment.clone(libraryID);
if (attachment.isImportedAttachment()) {
// Attachment path isn't copied over by clone() if libraryID is different
newAttachment.attachmentPath = attachment.attachmentPath;

View File

@ -71,9 +71,9 @@ Zotero.Cite = {
* @param {String} format The format of the output (html, text, or rtf)
* @return {String} Bibliography or item list in specified format
*/
"makeFormattedBibliographyOrCitationList":Zotero.Promise.coroutine(function* (cslEngine, items, format, asCitationList) {
"makeFormattedBibliographyOrCitationList":function(cslEngine, items, format, asCitationList) {
cslEngine.setOutputFormat(format);
yield cslEngine.updateItems(items.map(item => item.id));
cslEngine.updateItems(items.map(item => item.id));
if(!asCitationList) {
var bibliography = Zotero.Cite.makeFormattedBibliography(cslEngine, format);
@ -84,7 +84,7 @@ Zotero.Cite = {
var citations=[];
for (var i=0, ilen=items.length; i<ilen; i++) {
var item = items[i];
var outList = yield cslEngine.appendCitationCluster({"citationItems":[{"id":item.id}], "properties":{}}, true);
var outList = cslEngine.appendCitationCluster({"citationItems":[{"id":item.id}], "properties":{}}, true);
for (var j=0, jlen=outList.length; j<jlen; j++) {
var citationPos = outList[j][0];
citations[citationPos] = outList[j][1];
@ -124,7 +124,7 @@ Zotero.Cite = {
return "<\\rtf \n"+citations.join("\\\n")+"\n}";
}
}
}),
},
/**
* Makes a formatted bibliography
@ -492,45 +492,9 @@ Zotero.Cite.System = function(automaticJournalAbbreviations) {
if(automaticJournalAbbreviations) {
this.getAbbreviation = Zotero.Cite.getAbbreviation;
}
this.items = {};
}
Zotero.Cite.System.prototype = {
/**
* Asynchronously fetch item and convert to CSL JSON
*/
"addItem":Zotero.Promise.coroutine(function* (zoteroItem) {
if (typeof(zoteroItem) != "object") {
if (this.items.hasOwnProperty(zoteroItem)) return;
zoteroItem = yield Zotero.Items.getAsync(zoteroItem);
}
if (this.items.hasOwnProperty(zoteroItem.id)) return;
let item = yield Zotero.Utilities.itemToCSLJSON(zoteroItem);
item.id = zoteroItem.id;
if (!Zotero.Prefs.get("export.citePaperJournalArticleURL")) {
var itemType = Zotero.ItemTypes.getName(zoteroItem.itemTypeID);
// don't return URL or accessed information for journal articles if a
// pages field exists
if (["journalArticle", "newspaperArticle", "magazineArticle"].indexOf(itemType) !== -1
&& item.pages
) {
delete item.URL;
delete item.accessed;
}
}
this.items[item.id] = item;
}),
/**
* Asynchronously fetch items and convert them to CSL JSON
*/
"addItems":Zotero.Promise.coroutine(function* (items) {
for (let item of items) {
yield this.addItem(item);
}
}),
/**
* citeproc-js system function for getting items
* See http://gsl-nagoya-u.net/http/pub/citeproc-doc.html#retrieveitem
@ -538,8 +502,11 @@ Zotero.Cite.System.prototype = {
* @return {Object} citeproc-js item
*/
"retrieveItem":function retrieveItem(item) {
let slashIndex;
if(typeof item === "string" && (slashIndex = item.indexOf("/")) !== -1) {
var zoteroItem, slashIndex;
if(typeof item === "object" && item !== null && item instanceof Zotero.Item) {
//if(this._cache[item.id]) return this._cache[item.id];
zoteroItem = item;
} else if(typeof item === "string" && (slashIndex = item.indexOf("/")) !== -1) {
// is an embedded item
var sessionID = item.substr(0, slashIndex);
var session = Zotero.Integration.sessions[sessionID]
@ -550,8 +517,36 @@ Zotero.Cite.System.prototype = {
return embeddedCitation;
}
}
} else {
// is an item ID
//if(this._cache[item]) return this._cache[item];
try {
zoteroItem = Zotero.Items.get(item);
} catch(e) {}
}
return this.items[item];
if(!zoteroItem) {
throw "Zotero.Cite.System.retrieveItem called on non-item "+item;
}
var cslItem = Zotero.Utilities.itemToCSLJSON(zoteroItem);
// TEMP: citeproc-js currently expects the id property to be the item DB id
cslItem.id = zoteroItem.id;
if (!Zotero.Prefs.get("export.citePaperJournalArticleURL")) {
var itemType = Zotero.ItemTypes.getName(zoteroItem.itemTypeID);
// don't return URL or accessed information for journal articles if a
// pages field exists
if (["journalArticle", "newspaperArticle", "magazineArticle"].indexOf(itemType) !== -1
&& zoteroItem.getField("pages")
) {
delete cslItem.URL;
delete cslItem.accessed;
}
}
return cslItem;
},
/**
@ -579,20 +574,3 @@ Zotero.Cite.System.prototype = {
return str.value;
}
};
Zotero.Cite.AsyncCiteProc = function() {
Zotero.CiteProc.CSL.Engine.apply(this, arguments);
}
Zotero.Cite.AsyncCiteProc.prototype = Object.create(Zotero.CiteProc.CSL.Engine.prototype);
Zotero.Cite.AsyncCiteProc.prototype.updateItems = Zotero.Promise.coroutine(function*(items) {
yield this.sys.addItems(items);
Zotero.CiteProc.CSL.Engine.prototype.updateItems.call(this, items);
});
Zotero.Cite.AsyncCiteProc.prototype.appendCitationCluster = Zotero.Promise.coroutine(function*(citation, isRegistered) {
if (!isRegistered) {
for (let citationItem of citation.citationItems) {
yield this.sys.addItem(citationItem.id);
}
}
return Zotero.CiteProc.CSL.Engine.prototype.appendCitationCluster.call(this, citation, isRegistered);
});

View File

@ -295,6 +295,7 @@ Zotero.CollectionTreeRow.prototype.getSearchObject = Zotero.Promise.coroutine(fu
// Create the outer (filter) search
var s2 = new Zotero.Search();
if (this.isTrash()) {
s2.addCondition('deleted', 'true');
}

View File

@ -67,6 +67,9 @@ Zotero.CollectionTreeView.prototype.type = 'collection';
Object.defineProperty(Zotero.CollectionTreeView.prototype, "selectedTreeRow", {
get: function () {
if (!this.selection || !this.selection.count) {
return false;
}
return this.getRow(this.selection.currentIndex);
}
});
@ -156,25 +159,28 @@ Zotero.CollectionTreeView.prototype.refresh = Zotero.Promise.coroutine(function*
this._containerState = {};
}
if (this.hideSources.indexOf('duplicates') == -1) {
try {
this._duplicateLibraries = Zotero.Prefs.get('duplicateLibraries').split(',').map(function (val) parseInt(val));
}
catch (e) {
// Add to personal library by default
Zotero.Prefs.set('duplicateLibraries', '0');
this._duplicateLibraries = [0];
}
}
var userLibraryID = Zotero.Libraries.userLibraryID;
try {
this._unfiledLibraries = Zotero.Prefs.get('unfiledLibraries').split(',').map(function (val) parseInt(val));
}
catch (e) {
// Add to personal library by default
Zotero.Prefs.set('unfiledLibraries', '0');
this._unfiledLibraries = [0];
var readPref = function (pref) {
let ids = Zotero.Prefs.get(pref);
if (ids === "") {
this["_" + pref] = [];
}
else {
if (ids === undefined || typeof ids != 'string') {
ids = "" + userLibraryID;
Zotero.Prefs.set(pref, "" + userLibraryID);
}
this["_" + pref] = ids.split(',')
// Convert old id and convert to int
.map(id => id === "0" ? userLibraryID : parseInt(id));
}
}.bind(this);
if (this.hideSources.indexOf('duplicates') == -1) {
readPref('duplicateLibraries');
}
readPref('unfiledLibraries');
var oldCount = this.rowCount || 0;
var newRows = [];
@ -526,7 +532,7 @@ Zotero.CollectionTreeView.prototype._addSortedRow = Zotero.Promise.coroutine(fun
);
}
else if (objectType == 'search') {
let search = yield Zotero.Searches.getAsync(id);
let search = Zotero.Searches.get(id);
let libraryID = search.libraryID;
let startRow = this._rowMap['L' + libraryID];
@ -545,7 +551,6 @@ Zotero.CollectionTreeView.prototype._addSortedRow = Zotero.Promise.coroutine(fun
var inSearches = false;
for (let i = startRow; i < this.rowCount; i++) {
let treeRow = this.getRow(i);
Zotero.debug(treeRow.id);
beforeRow = i;
// If we've reached something other than collections, stop
@ -900,8 +905,7 @@ Zotero.CollectionTreeView.prototype.selectByID = Zotero.Promise.coroutine(functi
switch (type) {
case 'L':
var found = yield this.selectLibrary(id);
break;
return yield this.selectLibrary(id);
case 'C':
var found = yield this.expandToCollection(id);
@ -913,14 +917,13 @@ Zotero.CollectionTreeView.prototype.selectByID = Zotero.Promise.coroutine(functi
break;
case 'T':
var found = yield this.selectTrash(id);
break;
return yield this.selectTrash(id);
}
if (!found) {
var row = this._rowMap[type + id];
if (!row) {
return false;
}
var row = this._rowMap[type + id];
this._treebox.ensureRowIsVisible(row);
yield this.selectWait(row);
@ -940,7 +943,7 @@ Zotero.CollectionTreeView.prototype.selectLibrary = Zotero.Promise.coroutine(fun
}
// Check if library is already selected
if (this.selection.currentIndex != -1) {
if (this.selection && this.selection.count && this.selection.currentIndex != -1) {
var treeRow = this.getRow(this.selection.currentIndex);
if (treeRow.isLibrary(true) && treeRow.ref.libraryID == libraryID) {
this._treebox.ensureRowIsVisible(this.selection.currentIndex);
@ -972,7 +975,7 @@ Zotero.CollectionTreeView.prototype.selectSearch = function (id) {
Zotero.CollectionTreeView.prototype.selectTrash = Zotero.Promise.coroutine(function* (libraryID) {
// Check if trash is already selected
if (this.selection.currentIndex != -1) {
if (this.selection && this.selection.count && this.selection.currentIndex != -1) {
let itemGroup = this.getRow(this.selection.currentIndex);
if (itemGroup.isTrash() && itemGroup.ref.libraryID == libraryID) {
this._treebox.ensureRowIsVisible(this.selection.currentIndex);
@ -1196,6 +1199,8 @@ Zotero.CollectionTreeView.prototype._expandRow = Zotero.Promise.coroutine(functi
* Returns libraryID or FALSE if not a library
*/
Zotero.CollectionTreeView.prototype.getSelectedLibraryID = function() {
if (!this.selection || !this.selection.count || this.selection.currentIndex == -1) return false;
var treeRow = this.getRow(this.selection.currentIndex);
return treeRow && treeRow.ref && treeRow.ref.libraryID !== undefined
&& treeRow.ref.libraryID;
@ -1504,10 +1509,6 @@ Zotero.CollectionTreeView.prototype.canDropCheckAsync = Zotero.Promise.coroutine
}
if (dataType == 'zotero/item') {
if (treeRow.isCollection()) {
yield treeRow.ref.loadChildItems();
}
var ids = data;
var items = Zotero.Items.get(ids);
var skip = true;
@ -1627,7 +1628,6 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
// If linked item is in the trash, undelete it and remove it from collections
// (since it shouldn't be restored to previous collections)
if (linkedItem.deleted) {
yield linkedItem.loadCollections();
linkedItem.setCollections();
linkedItem.deleted = false;
yield linkedItem.save({
@ -1693,7 +1693,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
}
// Create new clone item in target library
var newItem = yield item.clone(targetLibraryID, false, !options.tags);
var newItem = item.clone(targetLibraryID, false, !options.tags);
// Set Rights field for My Publications
if (options.license) {
@ -1717,11 +1717,10 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
// Child notes
if (options.childNotes) {
yield item.loadChildItems();
var noteIDs = item.getNotes();
var notes = yield Zotero.Items.getAsync(noteIDs);
var notes = Zotero.Items.get(noteIDs);
for each(var note in notes) {
let newNote = yield note.clone(targetLibraryID);
let newNote = note.clone(targetLibraryID);
newNote.parentID = newItemID;
yield newNote.save({
skipSelect: true
@ -1733,9 +1732,8 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
// Child attachments
if (options.childLinks || options.childFileAttachments) {
yield item.loadChildItems();
var attachmentIDs = item.getAttachments();
var attachments = yield Zotero.Items.getAsync(attachmentIDs);
var attachments = Zotero.Items.get(attachmentIDs);
for each(var attachment in attachments) {
var linkMode = attachment.attachmentLinkMode;
@ -1864,8 +1862,8 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
if (targetTreeRow.isPublications()) {
let items = yield Zotero.Items.getAsync(ids);
let io = yield this._treebox.treeBody.ownerDocument.defaultView.ZoteroPane
.showPublicationsWizard(items);
let io = this._treebox.treeBody.ownerDocument.defaultView
.ZoteroPane.showPublicationsWizard(items);
if (!io) {
return;
}

View File

@ -28,11 +28,8 @@ Zotero.Collection = function(params = {}) {
this._name = null;
this._hasChildCollections = null;
this._childCollections = [];
this._hasChildItems = false;
this._childItems = [];
this._childCollections = new Set();
this._childItems = new Set();
Zotero.Utilities.assignProps(this, params, ['name', 'libraryID', 'parentID',
'parentKey', 'lastSync']);
@ -162,19 +159,13 @@ Zotero.Collection.prototype.loadFromRow = function(row) {
Zotero.Collection.prototype.hasChildCollections = function() {
if (this._hasChildCollections !== null) {
return this._hasChildCollections;
}
this._requireData('primaryData');
return false;
this._requireData('childCollections');
return this._childCollections.size > 0;
}
Zotero.Collection.prototype.hasChildItems = function() {
if (this._hasChildItems !== null) {
return this._hasChildItems;
}
this._requireData('primaryData');
return false;
this._requireData('childItems');
return this._childItems.size > 0;
}
@ -189,19 +180,11 @@ Zotero.Collection.prototype.getChildCollections = function (asIDs) {
// Return collectionIDs
if (asIDs) {
var ids = [];
for each(var col in this._childCollections) {
ids.push(col.id);
}
return ids;
return this._childCollections.values();
}
// Return Zotero.Collection objects
var objs = [];
for each(var col in this._childCollections) {
objs.push(col);
}
return objs;
return Array.from(this._childCollections).map(id => this.ObjectsClass.get(id));
}
@ -215,13 +198,14 @@ Zotero.Collection.prototype.getChildCollections = function (asIDs) {
Zotero.Collection.prototype.getChildItems = function (asIDs, includeDeleted) {
this._requireData('childItems');
if (this._childItems.length == 0) {
if (this._childItems.size == 0) {
return [];
}
// Remove deleted items if necessary
var childItems = [];
for each(var item in this._childItems) {
for (let itemID of this._childItems) {
let item = this.ChildObjects.get(itemID);
if (includeDeleted || !item.deleted) {
childItems.push(item);
}
@ -229,19 +213,11 @@ Zotero.Collection.prototype.getChildItems = function (asIDs, includeDeleted) {
// Return itemIDs
if (asIDs) {
var ids = [];
for each(var item in childItems) {
ids.push(item.id);
}
return ids;
return childItems.map(item => item.id);
}
// Return Zotero.Item objects
var objs = [];
for each(var item in childItems) {
objs.push(item);
}
return objs;
return childItems.slice();
}
Zotero.Collection.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
@ -388,7 +364,6 @@ Zotero.Collection.prototype.addItems = Zotero.Promise.coroutine(function* (itemI
return;
}
yield this.loadChildItems();
var current = this.getChildItems(true);
Zotero.DB.requireTransaction();
@ -400,15 +375,14 @@ Zotero.Collection.prototype.addItems = Zotero.Promise.coroutine(function* (itemI
continue;
}
let item = yield this.ChildObjects.getAsync(itemID);
yield item.loadCollections();
let item = this.ChildObjects.get(itemID);
item.addToCollection(this.id);
yield item.save({
skipDateModifiedUpdate: true
});
}
yield this.loadChildItems(true);
yield this._loadDataType('childItems');
});
/**
@ -434,7 +408,6 @@ Zotero.Collection.prototype.removeItems = Zotero.Promise.coroutine(function* (it
return;
}
yield this.loadChildItems();
var current = this.getChildItems(true);
return Zotero.DB.executeTransaction(function* () {
@ -447,7 +420,6 @@ Zotero.Collection.prototype.removeItems = Zotero.Promise.coroutine(function* (it
}
let item = yield this.ChildObjects.getAsync(itemID);
yield item.loadCollections();
item.removeFromCollection(this.id);
yield item.save({
skipDateModifiedUpdate: true
@ -455,7 +427,7 @@ Zotero.Collection.prototype.removeItems = Zotero.Promise.coroutine(function* (it
}
}.bind(this));
yield this.loadChildItems(true);
yield this._loadDataType('childItems');
});
@ -464,13 +436,7 @@ Zotero.Collection.prototype.removeItems = Zotero.Promise.coroutine(function* (it
**/
Zotero.Collection.prototype.hasItem = function(itemID) {
this._requireData('childItems');
for (let i=0; i<this._childItems.length; i++) {
if (this._childItems[i].id == itemID) {
return true;
}
}
return false;
return this._childItems.has(itemID);
}
@ -692,8 +658,8 @@ Zotero.Collection.prototype.fromJSON = function (json) {
}
Zotero.Collection.prototype.toResponseJSON = Zotero.Promise.coroutine(function* (options = {}) {
var json = yield this.constructor._super.prototype.toResponseJSON.apply(this, options);
Zotero.Collection.prototype.toResponseJSON = function (options = {}) {
var json = this.constructor._super.prototype.toResponseJSON.apply(this, options);
// TODO: library block?
@ -713,10 +679,10 @@ Zotero.Collection.prototype.toResponseJSON = Zotero.Promise.coroutine(function*
json.meta.numChildren = this.numChildren();
}
return json;
})
};
Zotero.Collection.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {}) {
Zotero.Collection.prototype.toJSON = function (options = {}) {
var env = this._preToJSON(options);
var mode = env.mode;
@ -729,7 +695,7 @@ Zotero.Collection.prototype.toJSON = Zotero.Promise.coroutine(function* (options
obj.relations = {}; // TEMP
return this._postToJSON(env);
});
}
/**
@ -865,75 +831,6 @@ Zotero.Collection.prototype.addLinkedCollection = Zotero.Promise.coroutine(funct
//
// Private methods
//
Zotero.Collection.prototype.reloadHasChildCollections = Zotero.Promise.coroutine(function* () {
var sql = "SELECT COUNT(*) FROM collections WHERE parentCollectionID=?";
this._hasChildCollections = !!(yield Zotero.DB.valueQueryAsync(sql, this.id));
});
Zotero.Collection.prototype.loadChildCollections = Zotero.Promise.coroutine(function* (reload) {
if (this._loaded.childCollections && !reload) {
return;
}
var sql = "SELECT collectionID FROM collections WHERE parentCollectionID=?";
var ids = yield Zotero.DB.columnQueryAsync(sql, this.id);
this._childCollections = [];
if (ids.length) {
for each(var id in ids) {
var col = yield this.ObjectsClass.getAsync(id);
if (!col) {
throw new Error('Collection ' + id + ' not found');
}
this._childCollections.push(col);
}
this._hasChildCollections = true;
}
else {
this._hasChildCollections = false;
}
this._loaded.childCollections = true;
this._clearChanged('childCollections');
});
Zotero.Collection.prototype.reloadHasChildItems = Zotero.Promise.coroutine(function* () {
var sql = "SELECT COUNT(*) FROM collectionItems WHERE collectionID=?";
this._hasChildItems = !!(yield Zotero.DB.valueQueryAsync(sql, this.id));
});
Zotero.Collection.prototype.loadChildItems = Zotero.Promise.coroutine(function* (reload) {
if (this._loaded.childItems && !reload) {
return;
}
var sql = "SELECT itemID FROM collectionItems WHERE collectionID=? "
// DEBUG: Fix for child items created via context menu on parent within
// a collection being added to the current collection
+ "AND itemID NOT IN "
+ "(SELECT itemID FROM itemNotes WHERE parentItemID IS NOT NULL) "
+ "AND itemID NOT IN "
+ "(SELECT itemID FROM itemAttachments WHERE parentItemID IS NOT NULL)";
var ids = yield Zotero.DB.columnQueryAsync(sql, this.id);
this._childItems = [];
if (ids.length) {
var items = yield this.ChildObjects.getAsync(ids);
if (items) {
this._childItems = items;
}
}
this._loaded.childItems = true;
this._clearChanged('childItems');
});
/**
* Add a collection to the cached child collections list if loaded
*/
@ -941,8 +838,7 @@ Zotero.Collection.prototype._registerChildCollection = function (collectionID) {
if (this._loaded.childCollections) {
let collection = this.ObjectsClass.get(collectionID);
if (collection) {
this._hasChildCollections = true;
this._childCollections.push(collection);
this._childCollections.add(collectionID);
}
}
}
@ -953,13 +849,7 @@ Zotero.Collection.prototype._registerChildCollection = function (collectionID) {
*/
Zotero.Collection.prototype._unregisterChildCollection = function (collectionID) {
if (this._loaded.childCollections) {
for (let i = 0; i < this._childCollections.length; i++) {
if (this._childCollections[i].id == collectionID) {
this._childCollections.splice(i, 1);
break;
}
}
this._hasChildCollections = this._childCollections.length > 0;
this._childCollections.delete(collectionID);
}
}
@ -971,8 +861,7 @@ Zotero.Collection.prototype._registerChildItem = function (itemID) {
if (this._loaded.childItems) {
let item = this.ChildObjects.get(itemID);
if (item) {
this._hasChildItems = true;
this._childItems.push(item);
this._childItems.add(itemID);
}
}
}
@ -983,12 +872,6 @@ Zotero.Collection.prototype._registerChildItem = function (itemID) {
*/
Zotero.Collection.prototype._unregisterChildItem = function (itemID) {
if (this._loaded.childItems) {
for (let i = 0; i < this._childItems.length; i++) {
if (this._childItems[i].id == itemID) {
this._childItems.splice(i, 1);
break;
}
}
this._hasChildItems = this._childItems.length > 0;
this._childItems.delete(itemID);
}
}

View File

@ -85,8 +85,7 @@ Zotero.Collections = function() {
let children;
if (parentID) {
let parent = yield Zotero.Collections.getAsync(parentID);
yield parent.loadChildCollections();
let parent = Zotero.Collections.get(parentID);
children = parent.getChildCollections();
} else if (libraryID) {
let sql = "SELECT collectionID AS id FROM collections "
@ -156,6 +155,103 @@ Zotero.Collections = function() {
}
this._loadChildCollections = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var sql = "SELECT C1.collectionID, C2.collectionID AS childCollectionID "
+ "FROM collections C1 LEFT JOIN collections C2 ON (C1.collectionID=C2.parentCollectionID) "
+ "WHERE C1.libraryID=?"
+ (ids.length ? " AND C1.collectionID IN (" + ids.map(id => parseInt(id)).join(", ") + ")" : "");
var params = [libraryID];
var lastID;
var rows = [];
var setRows = function (collectionID, rows) {
var collection = this._objectCache[collectionID];
if (!collection) {
throw new Error("Collection " + collectionID + " not found");
}
collection._childCollections = new Set(rows);
collection._loaded.childCollections = true;
collection._clearChanged('childCollections');
}.bind(this);
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
let collectionID = row.getResultByIndex(0);
if (lastID && collectionID !== lastID) {
setRows(lastID, rows);
rows = [];
}
lastID = collectionID;
let childCollectionID = row.getResultByIndex(1);
// No child collections
if (childCollectionID === null) {
return;
}
rows.push(childCollectionID);
}
}
);
if (lastID) {
setRows(lastID, rows);
}
});
this._loadChildItems = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var sql = "SELECT collectionID, itemID FROM collections "
+ "LEFT JOIN collectionItems USING (collectionID) "
+ "WHERE libraryID=?" + idSQL;
var params = [libraryID];
var lastID;
var rows = [];
var setRows = function (collectionID, rows) {
var collection = this._objectCache[collectionID];
if (!collection) {
throw new Error("Collection " + collectionID + " not found");
}
collection._childItems = new Set(rows);
collection._loaded.childItems = true;
collection._clearChanged('childItems');
}.bind(this);
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
let collectionID = row.getResultByIndex(0);
if (lastID && collectionID !== lastID) {
setRows(lastID, rows);
rows = [];
}
lastID = collectionID;
let itemID = row.getResultByIndex(1);
// No child items
if (itemID === null) {
return;
}
rows.push(itemID);
}
}
);
if (lastID) {
setRows(lastID, rows);
}
});
this.registerChildCollection = function (collectionID, childCollectionID) {
if (this._objectCache[collectionID]) {
this._objectCache[collectionID]._registerChildCollection(childCollectionID);

View File

@ -30,29 +30,35 @@ Zotero.Creators = new function() {
var _cache = {};
this.init = Zotero.Promise.coroutine(function* () {
var sql = "SELECT * FROM creators";
var rows = yield Zotero.DB.queryAsync(sql);
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
_cache[row.creatorID] = this.cleanData({
// Avoid "DB column 'name' not found" warnings from the DB row Proxy
firstName: row.firstName,
lastName: row.lastName,
fieldMode: row.fieldMode
});
}
});
/*
* Returns creator data in internal format for a given creatorID
*/
this.getAsync = Zotero.Promise.coroutine(function* (creatorID) {
this.get = function (creatorID) {
if (!creatorID) {
throw new Error("creatorID not provided");
}
if (_cache[creatorID]) {
return this.cleanData(_cache[creatorID]);
}
var sql = "SELECT * FROM creators WHERE creatorID=?";
var row = yield Zotero.DB.rowQueryAsync(sql, creatorID);
if (!row) {
if (!_cache[creatorID]) {
throw new Error("Creator " + creatorID + " not found");
}
return _cache[creatorID] = this.cleanData({
firstName: row.firstName, // avoid "DB column 'name' not found" warnings from the DB row Proxy
lastName: row.lastName,
fieldMode: row.fieldMode
});
});
// Return copy of data
return this.cleanData(_cache[creatorID]);
};
this.getItemsWithCreator = function (creatorID) {
@ -87,12 +93,10 @@ Zotero.Creators = new function() {
id = yield Zotero.ID.get('creators');
let sql = "INSERT INTO creators (creatorID, firstName, lastName, fieldMode) "
+ "VALUES (?, ?, ?, ?)";
let insertID = yield Zotero.DB.queryAsync(
yield Zotero.DB.queryAsync(
sql, [id, data.firstName, data.lastName, data.fieldMode]
);
if (!id) {
id = insertID;
}
_cache[id] = data;
}
return id;
});

View File

@ -401,7 +401,7 @@ Zotero.DataObject.prototype.setRelations = function (newRelations) {
// Relations are stored internally as a flat array with individual predicate-object pairs,
// so convert the incoming relations to that
var newRelationsFlat = this._flattenRelations(newRelations);
var newRelationsFlat = this.ObjectsClass.flattenRelations(newRelations);
var changed = false;
if (oldRelations.length != newRelationsFlat.length) {
@ -457,8 +457,6 @@ Zotero.DataObject.prototype._getLinkedObject = Zotero.Promise.coroutine(function
throw new Error(this._ObjectType + " is already in library " + libraryID);
}
yield this.loadRelations();
var predicate = Zotero.Relations.linkedObjectPredicate;
var libraryObjectPrefix = Zotero.URI.getLibraryURI(libraryID)
+ "/" + this._objectTypePlural + "/";
@ -514,8 +512,6 @@ Zotero.DataObject.prototype._addLinkedObject = Zotero.Promise.coroutine(function
throw new Error("Can't add linked " + this._objectType + " in same library");
}
yield this.loadRelations();
var predicate = Zotero.Relations.linkedObjectPredicate;
var thisURI = Zotero.URI['get' + this._ObjectType + 'URI'](this);
var objectURI = Zotero.URI['get' + this._ObjectType + 'URI'](object);
@ -539,7 +535,6 @@ Zotero.DataObject.prototype._addLinkedObject = Zotero.Promise.coroutine(function
});
}
else {
yield object.loadRelations();
object.addRelation(predicate, thisURI);
yield object.save({
skipDateModifiedUpdate: true,
@ -551,9 +546,11 @@ Zotero.DataObject.prototype._addLinkedObject = Zotero.Promise.coroutine(function
});
/*
* Build object from database
*/
//
// Bulk data loading functions
//
// These are called by Zotero.DataObjects.prototype._loadDataType().
//
Zotero.DataObject.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (reload, failOnMissing) {
if (this._loaded.primaryData && !reload) return;
@ -610,65 +607,6 @@ Zotero.DataObject.prototype.loadPrimaryData = Zotero.Promise.coroutine(function*
});
Zotero.DataObject.prototype.loadRelations = Zotero.Promise.coroutine(function* (reload) {
if (!this.ObjectsClass._relationsTable) {
throw new Error("Relations not supported for " + this._objectTypePlural);
}
if (this._loaded.relations && !reload) {
return;
}
Zotero.debug("Loading relations for " + this._objectType + " " + this.libraryKey);
this._requireData('primaryData');
var sql = "SELECT predicate, object FROM " + this.ObjectsClass._relationsTable + " "
+ "JOIN relationPredicates USING (predicateID) "
+ "WHERE " + this.ObjectsClass.idColumn + "=?";
var rows = yield Zotero.DB.queryAsync(sql, this.id);
var relations = {};
function addRel(predicate, object) {
if (!relations[predicate]) {
relations[predicate] = [];
}
relations[predicate].push(object);
}
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
addRel(row.predicate, row.object);
}
/*if (this._objectType == 'item') {
let getURI = Zotero.URI["get" + this._ObjectType + "URI"].bind(Zotero.URI);
let objectURI = getURI(this);
// Related items are bidirectional, so include any pointing to this object
let objects = yield Zotero.Relations.getByPredicateAndObject(
Zotero.Relations.relatedItemPredicate, objectURI
);
for (let i = 0; i < objects.length; i++) {
addRel(Zotero.Relations.relatedItemPredicate, getURI(objects[i]));
}
// Also include any owl:sameAs relations pointing to this object
objects = yield Zotero.Relations.getByPredicateAndObject(
Zotero.Relations.linkedObjectPredicate, objectURI
);
for (let i = 0; i < objects.length; i++) {
addRel(Zotero.Relations.linkedObjectPredicate, getURI(objects[i]));
}
}*/
// Relations are stored as predicate-object pairs
this._relations = this._flattenRelations(relations);
this._loaded.relations = true;
this._clearChanged('relations');
});
/**
* Reloads loaded, changed data
*
@ -735,7 +673,7 @@ Zotero.DataObject.prototype._requireData = function (dataType) {
* @param {Boolean} reload
*/
Zotero.DataObject.prototype._loadDataType = function (dataType, reload) {
return this["load" + dataType[0].toUpperCase() + dataType.substr(1)](reload);
return this._ObjectsClass._loadDataType(dataType, this.libraryID, [this.id]);
}
Zotero.DataObject.prototype.loadAllData = function (reload) {
@ -868,6 +806,16 @@ Zotero.DataObject.prototype.save = Zotero.Promise.coroutine(function* (options)
Zotero.debug('Updating database with new ' + this._objectType + ' data', 4);
}
if (env.options.skipAll) {
[
'skipDateModifiedUpdate',
'skipClientDateModifiedUpdate',
'skipSyncedUpdate',
'skipEditCheck',
'skipSelect'
].forEach(x => env.options[x] = true);
}
try {
if (Zotero.DataObject.prototype._finalizeSave == this._finalizeSave) {
throw new Error("_finalizeSave not implemented for Zotero." + this._ObjectType);
@ -1214,23 +1162,19 @@ Zotero.DataObject.prototype._finalizeErase = Zotero.Promise.coroutine(function*
});
Zotero.DataObject.prototype.toResponseJSON = Zotero.Promise.coroutine(function* (options) {
Zotero.DataObject.prototype.toResponseJSON = function (options) {
// TODO: library block?
return {
key: this.key,
version: this.version,
meta: {},
data: yield this.toJSON(options)
data: this.toJSON(options)
};
});
}
Zotero.DataObject.prototype._preToJSON = function (options) {
if (!this._id) {
throw new Error(`${this._ObjectType} must be saved before running toJSON()`);
}
var env = { options };
env.mode = options.mode || 'new';
if (env.mode == 'patch') {
@ -1272,32 +1216,3 @@ Zotero.DataObject.prototype._disabledCheck = function () {
+ "use Zotero." + this._ObjectTypePlural + ".getAsync()");
}
}
/**
* Flatten API JSON relations object into an array of unique predicate-object pairs
*
* @param {Object} relations - Relations object in API JSON format, with predicates as keys
* and arrays of URIs as objects
* @return {Array[]} - Predicate-object pairs
*/
Zotero.DataObject.prototype._flattenRelations = function (relations) {
var relationsFlat = [];
for (let predicate in relations) {
let object = relations[predicate];
if (Array.isArray(object)) {
object = Zotero.Utilities.arrayUnique(object);
for (let i = 0; i < object.length; i++) {
relationsFlat.push([predicate, object[i]]);
}
}
else if (typeof object == 'string') {
relationsFlat.push([predicate, object]);
}
else {
Zotero.debug(object, 1);
throw new Error("Invalid relation value");
}
}
return relationsFlat;
}

View File

@ -336,6 +336,254 @@ Zotero.DataObjects.prototype.getNewer = Zotero.Promise.method(function (libraryI
});
/**
* Loads data for a given data type
* @param {String} dataType
* @param {Integer} libraryID
* @param {Integer[]} [ids]
*/
Zotero.DataObjects.prototype._loadDataType = Zotero.Promise.coroutine(function* (dataType, libraryID, ids) {
var funcName = "_load" + dataType[0].toUpperCase() + dataType.substr(1)
// Single data types need an 's' (e.g., 'note' -> 'loadNotes()')
+ ((dataType.endsWith('s') || dataType.endsWith('Data') ? '' : 's'));
if (!this[funcName]) {
throw new Error(`Zotero.${this._ZDO_Objects}.${funcName} is not a function`);
}
if (ids && ids.length == 0) {
return;
}
var t = new Date;
var libraryName = Zotero.Libraries.get(libraryID).name;
var idSQL = "";
if (ids) {
idSQL = " AND " + this.idColumn + " IN (" + ids.map(id => parseInt(id)).join(", ") + ")";
}
Zotero.debug("Loading " + dataType
+ (ids
? " for " + ids.length + " " + (ids.length == 1 ? this._ZDO_object : this._ZDO_objects)
: '')
+ " in " + libraryName);
yield this[funcName](libraryID, ids ? ids : [], idSQL);
Zotero.debug(`Loaded ${dataType} in ${libraryName} in ${new Date() - t} ms`);
});
Zotero.DataObjects.prototype.loadAll = Zotero.Promise.coroutine(function* (libraryID, ids) {
var t = new Date();
var libraryName = Zotero.Libraries.get(libraryID).name;
Zotero.debug("Loading "
+ (ids ? ids.length : "all") + " "
+ (ids && ids.length == 1 ? this._ZDO_object : this._ZDO_objects)
+ " in " + libraryName);
let dataTypes = this.ObjectClass.prototype._dataTypes;
for (let i = 0; i < dataTypes.length; i++) {
yield this._loadDataType(dataTypes[i], libraryID, ids);
}
Zotero.debug(`Loaded all data in ${libraryName} in ${new Date() - t} ms`);
});
Zotero.DataObjects.prototype._loadPrimaryData = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL, options) {
var loaded = {};
// If library isn't an integer (presumably false or null), skip it
if (parseInt(libraryID) != libraryID) {
libraryID = false;
}
var sql = this.primaryDataSQL;
var params = [];
if (libraryID !== false) {
sql += ' AND O.libraryID=?';
params.push(libraryID);
}
if (ids.length) {
sql += ' AND O.' + this._ZDO_id + ' IN (' + ids.join(',') + ')';
}
yield Zotero.DB.queryAsync(
sql,
params,
{
onRow: function (row) {
var id = row.getResultByName(this._ZDO_id);
var columns = Object.keys(this._primaryDataSQLParts);
var rowObj = {};
for (let i=0; i<columns.length; i++) {
rowObj[columns[i]] = row.getResultByIndex(i);
}
var obj;
// Existing object -- reload in place
if (this._objectCache[id]) {
this._objectCache[id].loadFromRow(rowObj, true);
obj = this._objectCache[id];
}
// Object doesn't exist -- create new object and stuff in cache
else {
obj = this._getObjectForRow(rowObj);
obj.loadFromRow(rowObj, true);
if (!options || !options.noCache) {
this.registerObject(obj);
}
}
loaded[id] = obj;
}.bind(this)
}
);
if (!ids) {
this._loadedLibraries[libraryID] = true;
// If loading all objects, remove cached objects that no longer exist
for (let i in this._objectCache) {
let obj = this._objectCache[i];
if (libraryID !== false && obj.libraryID !== libraryID) {
continue;
}
if (!loaded[obj.id]) {
this.unload(obj.id);
}
}
if (this._postLoad) {
this._postLoad(libraryID, ids);
}
}
return loaded;
});
Zotero.DataObjects.prototype._loadRelations = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
if (!this._relationsTable) {
throw new Error("Relations not supported for " + this._ZDO_objects);
}
var sql = "SELECT " + this.idColumn + ", predicate, object "
+ `FROM ${this.table} LEFT JOIN ${this._relationsTable} USING (${this.idColumn}) `
+ "LEFT JOIN relationPredicates USING (predicateID) "
+ "WHERE libraryID=?" + idSQL;
var params = [libraryID];
var lastID;
var rows = [];
var setRows = function (id, rows) {
var obj = this._objectCache[id];
if (!obj) {
throw new Error(this._ZDO_Object + " " + id + " not found");
}
var relations = {};
function addRel(predicate, object) {
if (!relations[predicate]) {
relations[predicate] = [];
}
relations[predicate].push(object);
}
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
addRel(row.predicate, row.object);
}
/*if (this._objectType == 'item') {
let getURI = Zotero.URI["get" + this._ObjectType + "URI"].bind(Zotero.URI);
let objectURI = getURI(this);
// Related items are bidirectional, so include any pointing to this object
let objects = yield Zotero.Relations.getByPredicateAndObject(
Zotero.Relations.relatedItemPredicate, objectURI
);
for (let i = 0; i < objects.length; i++) {
addRel(Zotero.Relations.relatedItemPredicate, getURI(objects[i]));
}
// Also include any owl:sameAs relations pointing to this object
objects = yield Zotero.Relations.getByPredicateAndObject(
Zotero.Relations.linkedObjectPredicate, objectURI
);
for (let i = 0; i < objects.length; i++) {
addRel(Zotero.Relations.linkedObjectPredicate, getURI(objects[i]));
}
}*/
// Relations are stored as predicate-object pairs
obj._relations = this.flattenRelations(relations);
obj._loaded.relations = true;
obj._clearChanged('relations');
}.bind(this);
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
let id = row.getResultByIndex(0);
if (lastID && id !== lastID) {
setRows(lastID, rows);
rows = [];
}
lastID = id;
let predicate = row.getResultByIndex(1);
// No relations
if (predicate === null) {
return;
}
rows.push({
predicate,
object: row.getResultByIndex(2)
});
}.bind(this)
}
);
if (lastID) {
setRows(lastID, rows);
}
});
/**
* Flatten API JSON relations object into an array of unique predicate-object pairs
*
* @param {Object} relations - Relations object in API JSON format, with predicates as keys
* and arrays of URIs as objects
* @return {Array[]} - Predicate-object pairs
*/
Zotero.DataObjects.prototype.flattenRelations = function (relations) {
var relationsFlat = [];
for (let predicate in relations) {
let object = relations[predicate];
if (Array.isArray(object)) {
object = Zotero.Utilities.arrayUnique(object);
for (let i = 0; i < object.length; i++) {
relationsFlat.push([predicate, object[i]]);
}
}
else if (typeof object == 'string') {
relationsFlat.push([predicate, object]);
}
else {
Zotero.debug(object, 1);
throw new Error("Invalid relation value");
}
}
return relationsFlat;
}
/**
* Reload loaded data of loaded objects
*
@ -557,25 +805,23 @@ Zotero.DataObjects.prototype.erase = Zotero.Promise.coroutine(function* (ids, op
});
// TEMP: remove
Zotero.DataObjects.prototype._load = Zotero.Promise.coroutine(function* (libraryID, ids, options) {
var loaded = {};
// If library isn't an integer (presumably false or null), skip it
if (parseInt(libraryID) != libraryID) {
libraryID = false;
}
if (libraryID === false && !ids) {
throw new Error("Either libraryID or ids must be provided");
}
if (libraryID !== false && this._loadedLibraries[libraryID]) {
return loaded;
}
var sql = this.primaryDataSQL;
var params = [];
if (libraryID !== false) {
@ -585,7 +831,7 @@ Zotero.DataObjects.prototype._load = Zotero.Promise.coroutine(function* (library
if (ids) {
sql += ' AND O.' + this._ZDO_id + ' IN (' + ids.join(',') + ')';
}
var t = new Date();
yield Zotero.DB.queryAsync(
sql,
@ -599,7 +845,7 @@ Zotero.DataObjects.prototype._load = Zotero.Promise.coroutine(function* (library
rowObj[columns[i]] = row.getResultByIndex(i);
}
var obj;
// Existing object -- reload in place
if (this._objectCache[id]) {
this._objectCache[id].loadFromRow(rowObj, true);
@ -618,10 +864,10 @@ Zotero.DataObjects.prototype._load = Zotero.Promise.coroutine(function* (library
}
);
Zotero.debug("Loaded " + this._ZDO_objects + " in " + ((new Date) - t) + "ms");
if (!ids) {
this._loadedLibraries[libraryID] = true;
// If loading all objects, remove cached objects that no longer exist
for (let i in this._objectCache) {
let obj = this._objectCache[i];
@ -632,15 +878,17 @@ Zotero.DataObjects.prototype._load = Zotero.Promise.coroutine(function* (library
this.unload(obj.id);
}
}
if (this._postLoad) {
this._postLoad(libraryID, ids);
}
}
return loaded;
});
Zotero.DataObjects.prototype._getObjectForRow = function(row) {
return new Zotero[this._ZDO_Object];
};

View File

@ -50,6 +50,9 @@ Zotero.Item = function(itemTypeOrID) {
this._attachmentLinkMode = null;
this._attachmentContentType = null;
this._attachmentPath = null;
this._attachmentSyncState = 0;
this._attachmentSyncedModificationTime = null;
this._attachmentSyncedHash = null;
// loadCreators
this._creators = [];
@ -90,9 +93,9 @@ Zotero.defineProperty(Zotero.Item.prototype, 'ContainerObjectsClass', {
});
Zotero.Item.prototype._dataTypes = Zotero.Item._super.prototype._dataTypes.concat([
'creators',
'itemData',
'note',
'creators',
'childItems',
// 'relatedItems', // TODO: remove
'tags',
@ -327,12 +330,14 @@ Zotero.Item.prototype._parseRowData = function(row) {
//Zotero.debug("Setting field '" + col + "' to '" + val + "' for item " + this.id);
switch (col) {
// Skip
// Unchanged
case 'libraryID':
case 'itemTypeID':
case 'attachmentSyncState':
case 'attachmentSyncedHash':
case 'attachmentSyncedModificationTime':
break;
// Unchanged
case 'itemID':
col = 'id';
break;
@ -658,10 +663,16 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
switch (field) {
case 'itemTypeID':
case 'dateAdded':
break;
case 'dateAdded':
case 'dateModified':
// Accept ISO dates
if (Zotero.Date.isISODate(value)) {
let d = Zotero.Date.isoToDate(value);
value = Zotero.Date.dateToSQL(d, true);
}
// Make sure it's valid
let date = Zotero.Date.sqlToDate(value, true);
if (!date) throw new Error("Invalid SQL date: " + value);
@ -785,11 +796,18 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
}
// Validate access date
else if (fieldID == Zotero.ItemFields.getID('accessDate')) {
if (value && (!Zotero.Date.isSQLDate(value) &&
!Zotero.Date.isSQLDateTime(value) &&
value != 'CURRENT_TIMESTAMP')) {
Zotero.debug("Discarding invalid accessDate '" + value + "' in Item.setField()");
return false;
if (value && value != 'CURRENT_TIMESTAMP') {
// Accept ISO dates
if (Zotero.Date.isISODate(value)) {
let d = Zotero.Date.isoToDate(value);
value = Zotero.Date.dateToSQL(d, true);
}
if (!Zotero.Date.isSQLDate(value) && !Zotero.Date.isSQLDateTime(value)) {
Zotero.logError(`Discarding invalid ${field} '${value}' for `
+ `item ${this.libraryKey} in setField()`);
return false;
}
}
}
@ -831,6 +849,105 @@ Zotero.Item.prototype.getDisplayTitle = function (includeAuthorAndDate) {
}
/**
* Update the generated display title from the loaded data
*/
Zotero.Item.prototype.updateDisplayTitle = function () {
var title = this.getField('title', false, true);
var itemTypeID = this.itemTypeID;
var itemTypeName = Zotero.ItemTypes.getName(itemTypeID);
if (title === "" && (itemTypeID == 8 || itemTypeID == 10)) { // 'letter' and 'interview' itemTypeIDs
var creatorsData = this.getCreators();
var authors = [];
var participants = [];
for (let i=0; i<creatorsData.length; i++) {
let creatorData = creatorsData[i];
let creatorTypeID = creatorsData[i].creatorTypeID;
if ((itemTypeID == 8 && creatorTypeID == 16) || // 'letter'
(itemTypeID == 10 && creatorTypeID == 7)) { // 'interview'
participants.push(creatorData);
}
else if ((itemTypeID == 8 && creatorTypeID == 1) || // 'letter'/'author'
(itemTypeID == 10 && creatorTypeID == 6)) { // 'interview'/'interviewee'
authors.push(creatorData);
}
}
var strParts = [];
if (participants.length > 0) {
let names = [];
let max = Math.min(4, participants.length);
for (let i=0; i<max; i++) {
names.push(
participants[i].name !== undefined
? participants[i].name
: participants[i].lastName
);
}
switch (names.length) {
case 1:
var str = 'oneParticipant';
break;
case 2:
var str = 'twoParticipants';
break;
case 3:
var str = 'threeParticipants';
break;
default:
var str = 'manyParticipants';
}
strParts.push(Zotero.getString('pane.items.' + itemTypeName + '.' + str, names));
}
else {
strParts.push(Zotero.ItemTypes.getLocalizedString(itemTypeID));
}
title = '[' + strParts.join('; ') + ']';
}
else if (itemTypeID == 17) { // 'case' itemTypeID
if (title) { // common law cases always have case names
var reporter = this.getField('reporter');
if (reporter) {
title = title + ' (' + reporter + ')';
} else {
var court = this.getField('court');
if (court) {
title = title + ' (' + court + ')';
}
}
}
else { // civil law cases have only shortTitle as case name
var strParts = [];
var caseinfo = "";
var part = this.getField('court');
if (part) {
strParts.push(part);
}
part = Zotero.Date.multipartToSQL(this.getField('date', true, true));
if (part) {
strParts.push(part);
}
var creatorData = this.getCreator(0);
if (creatorData && creatorData.creatorTypeID === 1) { // author
strParts.push(creatorData.lastName);
}
title = '[' + strParts.join(', ') + ']';
}
}
this._displayTitle = title;
};
/*
* Returns the number of creators for this item
*/
@ -1318,9 +1435,7 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
this.libraryID, parentItemKey
);
for (let i=0; i<changedCollections.length; i++) {
yield parentItem.loadCollections();
parentItem.addToCollection(changedCollections[i]);
yield this.loadCollections();
this.removeFromCollection(changedCollections[i]);
Zotero.Notifier.queue(
@ -1452,14 +1567,19 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
}
if (this._changed.attachmentData) {
let sql = "REPLACE INTO itemAttachments (itemID, parentItemID, linkMode, "
+ "contentType, charsetID, path) VALUES (?,?,?,?,?,?)";
let sql = "REPLACE INTO itemAttachments "
+ "(itemID, parentItemID, linkMode, contentType, charsetID, path, "
+ "syncState, storageModTime, storageHash) "
+ "VALUES (?,?,?,?,?,?,?,?,?)";
let linkMode = this.attachmentLinkMode;
let contentType = this.attachmentContentType;
let charsetID = this.attachmentCharset
? Zotero.CharacterSets.getID(this.attachmentCharset)
: null;
let path = this.attachmentPath;
let syncState = this.attachmentSyncState;
let storageModTime = this.attachmentSyncedModificationTime;
let storageHash = this.attachmentSyncedHash;
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE && libraryType != 'user') {
throw new Error("Linked files can only be added to user library");
@ -1471,7 +1591,10 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
{ int: linkMode },
contentType ? { string: contentType } : null,
charsetID ? { int: charsetID } : null,
path ? { string: path } : null
path ? { string: path } : null,
syncState !== undefined ? syncState : 0,
storageModTime !== undefined ? storageModTime : null,
storageHash || null
];
yield Zotero.DB.queryAsync(sql, params);
@ -1812,7 +1935,9 @@ Zotero.Item.prototype.setNote = function(text) {
this._hasNote = text !== '';
this._noteText = text;
this._noteTitle = Zotero.Notes.noteToTitle(text);
this._displayTitle = this._noteTitle;
if (this.isNote()) {
this._displayTitle = this._noteTitle;
}
this._markFieldChange('note', oldText);
this._changed.note = true;
@ -2296,10 +2421,9 @@ Zotero.Item.prototype.renameAttachmentFile = Zotero.Promise.coroutine(function*
yield this.relinkAttachmentFile(destPath);
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedHash(this.id, null, false);
yield Zotero.Sync.Storage.Local.setSyncState(this.id, "to_upload");
}.bind(this));
this.attachmentSyncedHash = null;
this.attachmentSyncState = "to_upload";
yield this.saveTx({ skipAll: true });
return true;
}
@ -2680,7 +2804,11 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentSyncState', {
},
set: function(val) {
if (!this.isAttachment()) {
throw ("attachmentSyncState can only be set for attachment items");
throw new Error("attachmentSyncState can only be set for attachment items");
}
if (typeof val == 'string') {
val = Zotero.Sync.Storage.Local["SYNC_STATE_" + val.toUpperCase()];
}
switch (this.attachmentLinkMode) {
@ -2689,8 +2817,7 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentSyncState', {
break;
default:
throw ("attachmentSyncState can only be set for snapshots and "
+ "imported files");
throw new Error("attachmentSyncState can only be set for stored files");
}
switch (val) {
@ -2703,8 +2830,7 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentSyncState', {
break;
default:
throw ("Invalid sync state '" + val
+ "' in Zotero.Item.attachmentSyncState setter");
throw new Error("Invalid sync state '" + val + "'");
}
if (val == this.attachmentSyncState) {
@ -2720,6 +2846,85 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentSyncState', {
});
Zotero.defineProperty(Zotero.Item.prototype, 'attachmentSyncedModificationTime', {
get: function () {
if (!this.isFileAttachment()) {
return undefined;
}
return this._attachmentSyncedModificationTime;
},
set: function (val) {
if (!this.isAttachment()) {
throw ("attachmentSyncedModificationTime can only be set for attachment items");
}
switch (this.attachmentLinkMode) {
case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
break;
default:
throw new Error("attachmentSyncedModificationTime can only be set for stored files");
}
if (typeof val != 'number') {
throw new Error("attachmentSyncedModificationTime must be a number");
}
if (parseInt(val) != val || val < 0) {
throw new Error("attachmentSyncedModificationTime must be a timestamp in milliseconds");
}
if (val == this._attachmentSyncedModificationTime) {
return;
}
if (!this._changed.attachmentData) {
this._changed.attachmentData = {};
}
this._changed.attachmentData.syncedModificationTime = true;
this._attachmentSyncedModificationTime = val;
}
});
Zotero.defineProperty(Zotero.Item.prototype, 'attachmentSyncedHash', {
get: function () {
if (!this.isFileAttachment()) {
return undefined;
}
return this._attachmentSyncedHash;
},
set: function (val) {
if (!this.isAttachment()) {
throw ("attachmentSyncedHash can only be set for attachment items");
}
switch (this.attachmentLinkMode) {
case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
break;
default:
throw new Error("attachmentSyncedHash can only be set for stored files");
}
if (val !== null && val.length != 32) {
throw new Error("Invalid attachment hash '" + val + "'");
}
if (val == this._attachmentSyncedHash) {
return;
}
if (!this._changed.attachmentData) {
this._changed.attachmentData = {};
}
this._changed.attachmentData.syncedHash = true;
this._attachmentSyncedHash = val;
}
});
/**
* Modification time of an attachment file
*
@ -2784,6 +2989,7 @@ Zotero.defineProperty(Zotero.Item.prototype, 'attachmentHash', {
});
/**
* Return plain text of attachment content
*
@ -3337,13 +3543,12 @@ Zotero.Item.prototype.getImageSrcWithTags = Zotero.Promise.coroutine(function* (
var uri = this.getImageSrc();
// TODO: Optimize this. Maybe load color/item associations in batch in cacheFields?
yield this.loadTags();
var tags = this.getTags();
if (!tags.length) {
return uri;
}
var tagColors = yield Zotero.Tags.getColors(this.libraryID);
var tagColors = Zotero.Tags.getColors(this.libraryID);
var colorData = [];
for (let i=0; i<tags.length; i++) {
let tag = tags[i];
@ -3512,34 +3717,30 @@ Zotero.Item.prototype.diff = function (item, includeMatches, ignoreFields) {
*
* Currently compares only item data, not primary fields
*/
Zotero.Item.prototype.multiDiff = Zotero.Promise.coroutine(function* (otherItems, ignoreFields) {
var thisData = yield this.toJSON();
Zotero.Item.prototype.multiDiff = function (otherItems, ignoreFields) {
var thisData = this.toJSON();
var alternatives = {};
var hasDiffs = false;
for (let i = 0; i < otherItems.length; i++) {
let otherItem = otherItems[i];
let diff = [];
let otherData = yield otherItem.toJSON();
let numDiffs = this.ObjectsClass.diff(thisData, otherData, diff);
let otherData = otherItems[i].toJSON();
let changeset = Zotero.DataObjectUtilities.diff(thisData, otherData, ignoreFields);
if (numDiffs) {
for (let field in diff[1]) {
if (ignoreFields && ignoreFields.indexOf(field) != -1) {
continue;
}
var value = diff[1][field];
if (!alternatives[field]) {
hasDiffs = true;
alternatives[field] = [value];
}
else if (alternatives[field].indexOf(value) == -1) {
hasDiffs = true;
alternatives[field].push(value);
}
for (let i = 0; i < changeset.length; i++) {
let change = changeset[i];
if (change.op == 'delete') {
continue;
}
if (!alternatives[change.field]) {
hasDiffs = true;
alternatives[change.field] = [change.value];
}
else if (alternatives[change.field].indexOf(change.value) == -1) {
hasDiffs = true;
alternatives[change.field].push(change.value);
}
}
}
@ -3549,7 +3750,7 @@ Zotero.Item.prototype.multiDiff = Zotero.Promise.coroutine(function* (otherItems
}
return alternatives;
});
};
/**
@ -3561,15 +3762,13 @@ Zotero.Item.prototype.multiDiff = Zotero.Promise.coroutine(function* (otherItems
* @param {Boolean} [skipTags=false] - Skip tags
* @return {Promise<Zotero.Item>}
*/
Zotero.Item.prototype.clone = Zotero.Promise.coroutine(function* (libraryID, skipTags) {
Zotero.Item.prototype.clone = function (libraryID, skipTags) {
Zotero.debug('Cloning item ' + this.id);
if (libraryID !== undefined && libraryID !== null && typeof libraryID !== 'number') {
throw new Error("libraryID must be null or an integer");
}
yield this.loadPrimaryData();
if (libraryID === undefined || libraryID === null) {
libraryID = this.libraryID;
}
@ -3579,7 +3778,6 @@ Zotero.Item.prototype.clone = Zotero.Promise.coroutine(function* (libraryID, ski
newItem.libraryID = libraryID;
newItem.setType(this.itemTypeID);
yield this.loadItemData();
var fieldIDs = this.getUsedFields();
for (let i = 0; i < fieldIDs.length; i++) {
let fieldID = fieldIDs[i];
@ -3588,11 +3786,9 @@ Zotero.Item.prototype.clone = Zotero.Promise.coroutine(function* (libraryID, ski
// Regular item
if (this.isRegularItem()) {
yield this.loadCreators();
newItem.setCreators(this.getCreators());
}
else {
yield this.loadNote();
newItem.setNote(this.getNote());
if (sameLibrary) {
var parent = this.parentKey;
@ -3614,29 +3810,16 @@ Zotero.Item.prototype.clone = Zotero.Promise.coroutine(function* (libraryID, ski
}
if (!skipTags) {
yield this.loadTags();
newItem.setTags(this.getTags());
}
if (sameLibrary) {
// DEBUG: this will add reverse-only relateds too
yield this.loadRelations();
newItem.setRelations(this.getRelations());
}
return newItem;
});
/**
* @return {Promise<Zotero.Item>} - A copy of the item with primary data loaded
*/
Zotero.Item.prototype.copy = Zotero.Promise.coroutine(function* () {
var newItem = new Zotero.Item;
newItem.id = this.id;
yield newItem.loadPrimaryData();
return newItem;
});;
}
Zotero.Item.prototype._eraseData = Zotero.Promise.coroutine(function* (env) {
@ -3721,8 +3904,6 @@ Zotero.Item.prototype.isCollection = function() {
/**
* Populate the object's data from an API JSON data object
*
* If this object is identified (has an id or library/key), loadAllData() must have been called.
*/
Zotero.Item.prototype.fromJSON = function (json) {
if (!json.itemType && !this._itemTypeID) {
@ -3867,7 +4048,7 @@ Zotero.Item.prototype.fromJSON = function (json) {
/**
* @param {Object} options
*/
Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {}) {
Zotero.Item.prototype.toJSON = function (options = {}) {
var env = this._preToJSON(options);
var mode = env.mode;
@ -3877,7 +4058,6 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {})
obj.itemType = Zotero.ItemTypes.getName(this.itemTypeID);
// Fields
yield this.loadItemData();
for (let i in this._itemData) {
let val = this.getField(i) + '';
if (val !== '' || mode == 'full') {
@ -3887,7 +4067,6 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {})
// Creators
if (this.isRegularItem()) {
yield this.loadCreators()
obj.creators = this.getCreatorsJSON();
}
else {
@ -3912,18 +4091,18 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {})
if (this.isFileAttachment()) {
if (options.syncedStorageProperties) {
obj.mtime = yield Zotero.Sync.Storage.Local.getSyncedModificationTime(this.id);
obj.md5 = yield Zotero.Sync.Storage.Local.getSyncedHash(this.id);
obj.mtime = this.attachmentSyncedModificationTime;
obj.md5 = this.attachmentSyncedHash;
}
else {
obj.mtime = (yield this.attachmentModificationTime) || null;
obj.md5 = (yield this.attachmentHash) || null;
// TEMP
//obj.mtime = (yield this.attachmentModificationTime) || null;
//obj.md5 = (yield this.attachmentHash) || null;
}
}
}
// Notes and embedded attachment notes
yield this.loadNote();
let note = this.getNote();
if (note !== "" || mode == 'full' || (mode == 'new' && this.isNote())) {
obj.note = note;
@ -3932,7 +4111,6 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {})
// Tags
obj.tags = [];
yield this.loadTags()
var tags = this.getTags();
for (let i=0; i<tags.length; i++) {
obj.tags.push(tags[i]);
@ -3940,14 +4118,12 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {})
// Collections
if (this.isTopLevelItem()) {
yield this.loadCollections();
obj.collections = this.getCollections().map(function (id) {
return this.ContainerObjectsClass.getLibraryAndKeyFromID(id).key;
}.bind(this));
}
// Relations
yield this.loadRelations();
obj.relations = this.getRelations()
// Deleted
@ -3956,16 +4132,21 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {})
obj.deleted = deleted ? 1 : 0;
}
obj.dateAdded = Zotero.Date.sqlToISO8601(this.dateAdded);
obj.dateModified = Zotero.Date.sqlToISO8601(this.dateModified);
if (obj.accessDate) obj.accessDate = Zotero.Date.sqlToISO8601(obj.accessDate);
if (this.dateAdded) {
obj.dateAdded = Zotero.Date.sqlToISO8601(this.dateAdded);
}
if (this.dateModified) {
obj.dateModified = Zotero.Date.sqlToISO8601(this.dateModified);
}
return this._postToJSON(env);
});
}
Zotero.Item.prototype.toResponseJSON = Zotero.Promise.coroutine(function* (options = {}) {
var json = yield this.constructor._super.prototype.toResponseJSON.apply(this, options);
Zotero.Item.prototype.toResponseJSON = function (options = {}) {
var json = this.constructor._super.prototype.toResponseJSON.apply(this, options);
// creatorSummary
var firstCreator = this.getField('firstCreator');
@ -3983,7 +4164,7 @@ Zotero.Item.prototype.toResponseJSON = Zotero.Promise.coroutine(function* (optio
json.meta.numChildren = this.numChildren();
}
return json;
})
};
//////////////////////////////////////////////////////////////////////////////
@ -3992,352 +4173,6 @@ Zotero.Item.prototype.toResponseJSON = Zotero.Promise.coroutine(function* (optio
//
//////////////////////////////////////////////////////////////////////////////
/*
* Load in the field data from the database
*/
Zotero.Item.prototype.loadItemData = Zotero.Promise.coroutine(function* (reload) {
if (this._loaded.itemData && !reload) {
return;
}
Zotero.debug("Loading item data for item " + this.libraryKey);
if (!this.id) {
throw new Error('ItemID not set for object before attempting to load data');
}
if (!this.isNote()) {
var sql = "SELECT fieldID, value FROM itemData NATURAL JOIN itemDataValues WHERE itemID=?";
yield Zotero.DB.queryAsync(
sql,
this.id,
{
onRow: function (row) {
this.setField(row.getResultByIndex(0), row.getResultByIndex(1), true);
}.bind(this)
}
);
// Mark nonexistent fields as loaded
let itemTypeFields = Zotero.ItemFields.getItemTypeFields(this.itemTypeID);
for (let i=0; i<itemTypeFields.length; i++) {
let fieldID = itemTypeFields[i];
if (this._itemData[fieldID] === null) {
this._itemData[fieldID] = false;
}
}
}
if (this.isNote() || this.isAttachment()) {
var sql = "SELECT title FROM itemNotes WHERE itemID=?";
var row = yield Zotero.DB.rowQueryAsync(sql, this.id);
if (row) {
let title = row.title;
this._noteTitle = title !== false ? title : '';
}
}
this._loaded.itemData = true;
this._clearChanged('itemData');
yield this.loadDisplayTitle(reload);
});
Zotero.Item.prototype.loadNote = Zotero.Promise.coroutine(function* (reload) {
if (this._loaded.note && !reload) {
return;
}
if (!this.isNote() && !this.isAttachment()) {
throw new Error("Can only load note for note or attachment item");
}
Zotero.debug("Loading note data for item " + this.libraryKey);
var sql = "SELECT note FROM itemNotes WHERE itemID=?";
var row = yield Zotero.DB.rowQueryAsync(sql, this.id);
if (row) {
let note = row.note;
// Convert non-HTML notes on-the-fly
if (note !== "") {
if (!note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)) {
note = Zotero.Utilities.htmlSpecialChars(note);
note = Zotero.Notes.notePrefix + '<p>'
+ note.replace(/\n/g, '</p><p>')
.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
.replace(/ /g, '&nbsp;&nbsp;')
+ '</p>' + Zotero.Notes.noteSuffix;
note = note.replace(/<p>\s*<\/p>/g, '<p>&nbsp;</p>');
let sql = "UPDATE itemNotes SET note=? WHERE itemID=?";
yield Zotero.DB.queryAsync(sql, [note, this.id]);
}
// Don't include <div> wrapper when returning value
let startLen = note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)[0].length;
let endLen = 6; // "</div>".length
note = note.substr(startLen, note.length - startLen - endLen);
}
this._noteText = note ? note : '';
}
this._loaded.note = true;
this._clearChanged('note');
});
Zotero.Item.prototype.loadDisplayTitle = Zotero.Promise.coroutine(function* (reload) {
if (this._displayTitle !== null && !reload) {
return;
}
var title = this.getField('title', false, true);
var itemTypeID = this.itemTypeID;
var itemTypeName = Zotero.ItemTypes.getName(itemTypeID);
if (title === "" && (itemTypeID == 8 || itemTypeID == 10)) { // 'letter' and 'interview' itemTypeIDs
yield this.loadCreators();
var creatorsData = this.getCreators();
var authors = [];
var participants = [];
for (let i=0; i<creatorsData.length; i++) {
let creatorData = creatorsData[i];
let creatorTypeID = creatorsData[i].creatorTypeID;
if ((itemTypeID == 8 && creatorTypeID == 16) || // 'letter'
(itemTypeID == 10 && creatorTypeID == 7)) { // 'interview'
participants.push(creatorData);
}
else if ((itemTypeID == 8 && creatorTypeID == 1) || // 'letter'/'author'
(itemTypeID == 10 && creatorTypeID == 6)) { // 'interview'/'interviewee'
authors.push(creatorData);
}
}
var strParts = [];
if (participants.length > 0) {
let names = [];
let max = Math.min(4, participants.length);
for (let i=0; i<max; i++) {
names.push(
participants[i].name !== undefined
? participants[i].name
: participants[i].lastName
);
}
switch (names.length) {
case 1:
var str = 'oneParticipant';
break;
case 2:
var str = 'twoParticipants';
break;
case 3:
var str = 'threeParticipants';
break;
default:
var str = 'manyParticipants';
}
strParts.push(Zotero.getString('pane.items.' + itemTypeName + '.' + str, names));
}
else {
strParts.push(Zotero.ItemTypes.getLocalizedString(itemTypeID));
}
title = '[' + strParts.join('; ') + ']';
}
else if (itemTypeID == 17) { // 'case' itemTypeID
if (title) { // common law cases always have case names
var reporter = this.getField('reporter');
if (reporter) {
title = title + ' (' + reporter + ')';
} else {
var court = this.getField('court');
if (court) {
title = title + ' (' + court + ')';
}
}
}
else { // civil law cases have only shortTitle as case name
var strParts = [];
var caseinfo = "";
var part = this.getField('court');
if (part) {
strParts.push(part);
}
part = Zotero.Date.multipartToSQL(this.getField('date', true, true));
if (part) {
strParts.push(part);
}
yield this.loadCreators()
var creatorData = this.getCreator(0);
if (creatorData && creatorData.creatorTypeID === 1) { // author
strParts.push(creatorData.lastName);
}
title = '[' + strParts.join(', ') + ']';
}
}
return this._displayTitle = title;
});
/*
* Load in the creators from the database
*/
Zotero.Item.prototype.loadCreators = Zotero.Promise.coroutine(function* (reload) {
if (this._loaded.creators && !reload) {
return;
}
Zotero.debug("Loading creators for item " + this.libraryKey);
if (!this.id) {
throw new Error('ItemID not set for item before attempting to load creators');
}
var sql = 'SELECT creatorID, creatorTypeID, orderIndex FROM itemCreators '
+ 'WHERE itemID=? ORDER BY orderIndex';
var rows = yield Zotero.DB.queryAsync(sql, this.id);
this._creators = [];
this._creatorIDs = [];
this._loaded.creators = true;
this._clearChanged('creators');
if (!rows) {
return true;
}
var maxOrderIndex = -1;
for (var i=0; i<rows.length; i++) {
let row = rows[i];
if (row.orderIndex > maxOrderIndex) {
maxOrderIndex = row.orderIndex;
}
let creatorData = yield Zotero.Creators.getAsync(row.creatorID);
creatorData.creatorTypeID = row.creatorTypeID;
this._creators[i] = creatorData;
this._creatorIDs[i] = row.creatorID;
}
if (i <= maxOrderIndex) {
Zotero.debug("Fixing incorrect creator indexes for item " + this.libraryKey
+ " (" + i + ", " + maxOrderIndex + ")", 2);
while (i <= maxOrderIndex) {
this._changed.creators[i] = true;
i++;
}
}
return true;
});
Zotero.Item.prototype.loadChildItems = Zotero.Promise.coroutine(function* (reload) {
if (this._loaded.childItems && !reload) {
return;
}
if (this.isNote() || this.isAttachment()) {
return;
}
// Attachments
this._attachments = {
rows: null,
chronologicalWithTrashed: null,
chronologicalWithoutTrashed: null,
alphabeticalWithTrashed: null,
alphabeticalWithoutTrashed: null
};
var sql = "SELECT A.itemID, value AS title, CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed "
+ "FROM itemAttachments A "
+ "NATURAL JOIN items I "
+ "LEFT JOIN itemData ID ON (fieldID=110 AND A.itemID=ID.itemID) "
+ "LEFT JOIN itemDataValues IDV USING (valueID) "
+ "LEFT JOIN deletedItems DI USING (itemID) "
+ "WHERE parentItemID=?";
// Since we do the sort here and cache these results, a restart will be required
// if this pref (off by default) is turned on, but that's OK
if (Zotero.Prefs.get('sortAttachmentsChronologically')) {
sql += " ORDER BY dateAdded";
}
this._attachments.rows = yield Zotero.DB.queryAsync(sql, this.id);
//
// Notes
//
this._notes = {
rows: null,
rowsEmbedded: null,
chronologicalWithTrashed: null,
chronologicalWithoutTrashed: null,
alphabeticalWithTrashed: null,
alphabeticalWithoutTrashed: null,
numWithTrashed: null,
numWithoutTrashed: null,
numWithTrashedWithEmbedded: null,
numWithoutTrashedWithoutEmbedded: null
};
var sql = "SELECT N.itemID, title, CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed "
+ "FROM itemNotes N "
+ "NATURAL JOIN items I "
+ "LEFT JOIN deletedItems DI USING (itemID) "
+ "WHERE parentItemID=?";
if (Zotero.Prefs.get('sortAttachmentsChronologically')) {
sql += " ORDER BY dateAdded";
}
this._notes.rows = yield Zotero.DB.queryAsync(sql, this.id);
this._loaded.childItems = true;
this._clearChanged('childItems');
});
Zotero.Item.prototype.loadTags = Zotero.Promise.coroutine(function* (reload) {
if (this._loaded.tags && !reload) {
return;
}
if (!this._id) {
return;
}
var sql = "SELECT tagID AS id, name AS tag, type FROM itemTags "
+ "JOIN tags USING (tagID) WHERE itemID=?";
var rows = yield Zotero.DB.queryAsync(sql, this.id);
this._tags = [];
for (let i=0; i<rows.length; i++) {
let row = rows[i];
this._tags.push(Zotero.Tags.cleanData(row));
}
this._loaded.tags = true;
this._clearChanged('tags');
});
Zotero.Item.prototype.loadCollections = Zotero.Promise.coroutine(function* (reload) {
if (this._loaded.collections && !reload) {
return;
}
if (!this._id) {
return;
}
var sql = "SELECT collectionID FROM collectionItems WHERE itemID=?";
this._collections = yield Zotero.DB.columnQueryAsync(sql, this.id);
this._loaded.collections = true;
this._clearChanged('collections');
});
/**
* Return an item in the specified library equivalent to this item

View File

@ -84,7 +84,10 @@ Zotero.Items = function() {
attachmentCharset: "CS.charset AS attachmentCharset",
attachmentLinkMode: "IA.linkMode AS attachmentLinkMode",
attachmentContentType: "IA.contentType AS attachmentContentType",
attachmentPath: "IA.path AS attachmentPath"
attachmentPath: "IA.path AS attachmentPath",
attachmentSyncState: "IA.syncState AS attachmentSyncState",
attachmentSyncedModificationTime: "IA.storageModTime AS attachmentSyncedModificationTime",
attachmentSyncedHash: "IA.storageHash AS attachmentSyncedHash"
};
}
}, {lazy: true});
@ -204,7 +207,7 @@ Zotero.Items = function() {
for (let i=0; i<ids.length; i++) {
let prefix = i > 0 ? ',\n' : '';
let item = yield this.getAsync(ids[i], { noCache: true });
var json = yield item.toResponseJSON();
var json = item.toResponseJSON();
yield prefix + JSON.stringify(json, null, 4);
}
@ -212,105 +215,24 @@ Zotero.Items = function() {
};
this._cachedFields = {};
this.cacheFields = Zotero.Promise.coroutine(function* (libraryID, fields, items) {
if (items && items.length == 0) {
return;
}
var t = new Date;
fields = fields.concat();
// Needed for display titles for some item types
if (fields.indexOf('title') != -1) {
fields.push('reporter', 'court');
}
Zotero.debug("Caching fields [" + fields.join() + "]"
+ (items ? " for " + items.length + " items" : '')
+ " in library " + libraryID);
if (items && items.length > 0) {
yield this._load(libraryID, items);
}
else {
yield this._load(libraryID);
}
var primaryFields = [];
var fieldIDs = [];
for each(var field in fields) {
// Check if field already cached
if (this._cachedFields[libraryID] && this._cachedFields[libraryID].indexOf(field) != -1) {
continue;
}
if (!this._cachedFields[libraryID]) {
this._cachedFields[libraryID] = [];
}
this._cachedFields[libraryID].push(field);
if (this.isPrimaryField(field)) {
primaryFields.push(field);
}
else {
fieldIDs.push(Zotero.ItemFields.getID(field));
if (Zotero.ItemFields.isBaseField(field)) {
fieldIDs = fieldIDs.concat(Zotero.ItemFields.getTypeFieldsFromBase(field));
}
}
}
if (primaryFields.length) {
var sql = "SELECT O.itemID, "
+ primaryFields.map((val) => this.getPrimaryDataSQLPart(val)).join(', ')
+ this.primaryDataSQLFrom + " AND O.libraryID=?";
var params = [libraryID];
if (items) {
sql += " AND O.itemID IN (" + items.join() + ")";
}
yield Zotero.DB.queryAsync(
sql,
params,
{
onRow: function (row) {
let obj = {
itemID: row.getResultByIndex(0)
};
for (let i=0; i<primaryFields.length; i++) {
obj[primaryFields[i]] = row.getResultByIndex(i);
}
Zotero.debug(obj.itemID);
Zotero.debug(Object.keys(this._objectCache));
this._objectCache[obj.itemID].loadFromRow(obj);
}.bind(this)
}
);
}
// All fields already cached
if (!fieldIDs.length) {
Zotero.debug('All fields already cached');
return;
}
var sql = "SELECT itemID FROM items WHERE libraryID=?";
var params = [libraryID];
var allItemIDs = yield Zotero.DB.columnQueryAsync(sql, params);
//
// Bulk data loading functions
//
// These are called by Zotero.DataObjects.prototype._loadDataType().
//
this._loadItemData = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var missingItems = {};
var itemFieldsCached = {};
var sql = "SELECT itemID, fieldID, value FROM items JOIN itemData USING (itemID) "
+ "JOIN itemDataValues USING (valueID) WHERE libraryID=?";
var params = [libraryID];
if (items) {
sql += " AND itemID IN (" + items.join() + ")";
}
sql += " AND fieldID IN (" + fieldIDs.join() + ")";
var sql = "SELECT itemID, fieldID, value FROM items "
+ "JOIN itemData USING (itemID) "
+ "JOIN itemDataValues USING (valueID) WHERE libraryID=? AND itemTypeID!=?" + idSQL;
var params = [libraryID, Zotero.ItemTypes.getID('note')];
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
let itemID = row.getResultByIndex(0);
let fieldID = row.getResultByIndex(1);
@ -318,19 +240,17 @@ Zotero.Items = function() {
//Zotero.debug('Setting field ' + fieldID + ' for item ' + itemID);
if (this._objectCache[itemID]) {
if (value === null) {
value = false;
}
this._objectCache[itemID].setField(fieldID, value, true);
}
else {
if (!missingItems) {
var missingItems = {};
}
if (!missingItems[itemID]) {
missingItems[itemID] = true;
Zotero.debug("itemData row references nonexistent item " + itemID);
Components.utils.reportError("itemData row references nonexistent item " + itemID);
Zotero.logError("itemData row references nonexistent item " + itemID);
}
}
if (!itemFieldsCached[itemID]) {
itemFieldsCached[itemID] = {};
}
@ -339,68 +259,463 @@ Zotero.Items = function() {
}
);
// Set nonexistent fields in the cache list to false (instead of null)
var sql = "SELECT itemID FROM items WHERE libraryID=?" + idSQL;
var params = [libraryID];
var allItemIDs = [];
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
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);
for (let j=0; j<fieldIDs.length; j++) {
let fieldID = fieldIDs[j];
if (!itemFieldsCached[itemID] || !itemFieldsCached[itemID][fieldID]) {
//Zotero.debug('Setting field ' + fieldID + ' to false for item ' + itemID);
item.setField(fieldID, false, true);
}
}
allItemIDs.push(itemID);
}.bind(this)
}
);
var titleFieldID = Zotero.ItemFields.getID('title');
// Note titles
var sql = "SELECT itemID, title FROM items JOIN itemNotes USING (itemID) "
+ "WHERE libraryID=? AND itemID NOT IN (SELECT itemID FROM itemAttachments)" + idSQL;
var params = [libraryID];
yield Zotero.DB.queryAsync(
sql,
params,
{
onRow: function (row) {
let itemID = row.getResultByIndex(0);
let title = row.getResultByIndex(1);
//Zotero.debug('Setting title for note ' + row.itemID);
if (this._objectCache[itemID]) {
this._objectCache[itemID].setField(titleFieldID, title, true);
}
else {
if (!missingItems[itemID]) {
missingItems[itemID] = true;
Zotero.logError("itemData row references nonexistent item " + itemID);
}
}
}.bind(this)
}
);
for (let i=0; i<allItemIDs.length; i++) {
let itemID = allItemIDs[i];
for (let j=0; j<fieldIDs.length; j++) {
let fieldID = fieldIDs[j];
if (Zotero.ItemFields.isValidForType(fieldID, this._objectCache[itemID].itemTypeID)) {
if (!itemFieldsCached[itemID] || !itemFieldsCached[itemID][fieldID]) {
//Zotero.debug('Setting field ' + fieldID + ' to false for item ' + itemID);
this._objectCache[itemID].setField(fieldID, false, true);
}
}
}
}
// If 'title' is one of the fields, load in display titles (note titles, letter titles...)
if (fields.indexOf('title') != -1) {
var titleFieldID = Zotero.ItemFields.getID('title');
let item = this._objectCache[itemID];
// Note titles
var sql = "SELECT itemID, title FROM items JOIN itemNotes USING (itemID) "
+ "WHERE libraryID=? AND itemID NOT IN (SELECT itemID FROM itemAttachments)";
var params = [libraryID];
if (items) {
sql += " AND itemID IN (" + items.join() + ")";
}
yield Zotero.DB.queryAsync(
sql,
params,
{
onRow: function (row) {
let itemID = row.getResultByIndex(0);
let title = row.getResultByIndex(1);
//Zotero.debug('Setting title for note ' + row.itemID);
if (this._objectCache[itemID]) {
this._objectCache[itemID].setField(titleFieldID, title, true);
}
else {
if (!missingItems) {
var missingItems = {};
}
if (!missingItems[itemID]) {
missingItems[itemID] = true;
Components.utils.reportError(
"itemData row references nonexistent item " + itemID
);
}
}
}.bind(this)
}
);
// Mark as loaded
item._loaded.itemData = true;
item._clearChanged('itemData');
// Display titles
for (let i=0; i<allItemIDs.length; i++) {
let itemID = allItemIDs[i];
let item = this._objectCache[itemID];
yield item.loadDisplayTitle()
item.updateDisplayTitle()
}
});
this._loadCreators = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var sql = 'SELECT itemID, creatorID, creatorTypeID, orderIndex '
+ 'FROM items LEFT JOIN itemCreators USING (itemID) '
+ 'WHERE libraryID=?' + idSQL + " ORDER BY itemID, orderIndex";
var params = [libraryID];
var rows = yield Zotero.DB.queryAsync(sql, params);
// Mark creator indexes above the number of creators as changed,
// so that they're cleared if the item is saved
var fixIncorrectIndexes = function (item, numCreators, maxOrderIndex) {
Zotero.debug("Fixing incorrect creator indexes for item " + item.libraryKey
+ " (" + numCreators + ", " + maxOrderIndex + ")", 2);
var i = numCreators;
while (i <= maxOrderIndex) {
item._changed.creators[i] = true;
i++;
}
};
var lastItemID;
var item;
var index = 0;
var maxOrderIndex = -1;
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
let itemID = row.itemID;
if (itemID != lastItemID) {
if (!this._objectCache[itemID]) {
throw new Error("Item " + itemID + " not loaded");
}
item = this._objectCache[itemID];
item._creators = [];
item._creatorIDs = [];
item._loaded.creators = true;
item._clearChanged('creators');
if (!row.creatorID) {
lastItemID = row.itemID;
continue;
}
if (index <= maxOrderIndex) {
fixIncorrectIndexes(item, index, maxOrderIndex);
}
index = 0;
maxOrderIndex = -1;
}
lastItemID = row.itemID;
if (row.orderIndex > maxOrderIndex) {
maxOrderIndex = row.orderIndex;
}
let creatorData = Zotero.Creators.get(row.creatorID);
creatorData.creatorTypeID = row.creatorTypeID;
item._creators[index] = creatorData;
item._creatorIDs[index] = row.creatorID;
index++;
}
Zotero.debug("Cached fields in " + ((new Date) - t) + "ms");
if (index <= maxOrderIndex) {
fixIncorrectIndexes(item, index, maxOrderIndex);
}
});
this._loadNotes = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var notesToUpdate = [];
var sql = "SELECT itemID, note FROM items "
+ "JOIN itemNotes USING (itemID) "
+ "WHERE libraryID=?" + idSQL;
var params = [libraryID];
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
let itemID = row.getResultByIndex(0);
let item = this._objectCache[itemID];
if (!item) {
throw new Error("Item " + itemID + " not found");
}
let note = row.getResultByIndex(1);
// Convert non-HTML notes on-the-fly
if (note !== "") {
if (!note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)) {
note = Zotero.Utilities.htmlSpecialChars(note);
note = Zotero.Notes.notePrefix + '<p>'
+ note.replace(/\n/g, '</p><p>')
.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
.replace(/ /g, '&nbsp;&nbsp;')
+ '</p>' + Zotero.Notes.noteSuffix;
note = note.replace(/<p>\s*<\/p>/g, '<p>&nbsp;</p>');
notesToUpdate.push([item.id, note]);
}
// Don't include <div> wrapper when returning value
let startLen = note.substr(0, 36).match(/^<div class="zotero-note znv[0-9]+">/)[0].length;
let endLen = 6; // "</div>".length
note = note.substr(startLen, note.length - startLen - endLen);
}
item._noteText = note ? note : '';
item._loaded.note = true;
item._clearChanged('note');
}.bind(this)
}
);
if (notesToUpdate.length) {
yield Zotero.DB.executeTransaction(function* () {
for (let i = 0; i < notesToUpdate.length; i++) {
let row = notesToUpdate[i];
let sql = "UPDATE itemNotes SET note=? WHERE itemID=?";
yield Zotero.DB.queryAsync(sql, [row[1], row[0]]);
}
}.bind(this));
}
// Mark notes and attachments without notes as loaded
sql = "SELECT itemID FROM items WHERE libraryID=?" + idSQL
+ " AND itemTypeID IN (?, ?) AND itemID NOT IN (SELECT itemID FROM itemNotes)";
params = [libraryID, Zotero.ItemTypes.getID('note'), Zotero.ItemTypes.getID('attachment')];
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
let itemID = row.getResultByIndex(0);
let item = this._objectCache[itemID];
if (!item) {
throw new Error("Item " + itemID + " not loaded");
}
item._noteText = '';
item._loaded.note = true;
item._clearChanged('note');
}.bind(this)
}
);
});
this._loadChildItems = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var params = [libraryID];
var rows = [];
var onRow = function (row, setFunc) {
var itemID = row.getResultByIndex(0);
if (lastItemID && itemID !== lastItemID) {
setFunc(lastItemID, rows);
rows = [];
}
lastItemID = itemID;
rows.push({
itemID: row.getResultByIndex(1),
title: row.getResultByIndex(2),
trashed: row.getResultByIndex(3)
});
};
var sql = "SELECT parentItemID, A.itemID, value AS title, "
+ "CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed "
+ "FROM itemAttachments A "
+ "JOIN items I ON (A.parentItemID=I.itemID) "
+ "LEFT JOIN itemData ID ON (fieldID=110 AND A.itemID=ID.itemID) "
+ "LEFT JOIN itemDataValues IDV USING (valueID) "
+ "LEFT JOIN deletedItems DI USING (itemID) "
+ "WHERE libraryID=?"
+ (ids.length ? " AND parentItemID IN (" + ids.map(id => parseInt(id)).join(", ") + ")" : "");
// Since we do the sort here and cache these results, a restart will be required
// if this pref (off by default) is turned on, but that's OK
if (Zotero.Prefs.get('sortAttachmentsChronologically')) {
sql += " ORDER BY parentItemID, dateAdded";
}
var setAttachmentItem = function (itemID, rows) {
var item = this._objectCache[itemID];
if (!item) {
throw new Error("Item " + itemID + " not loaded");
}
item._attachments = {
rows,
chronologicalWithTrashed: null,
chronologicalWithoutTrashed: null,
alphabeticalWithTrashed: null,
alphabeticalWithoutTrashed: null
};
}.bind(this);
var lastItemID = null;
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
onRow(row, setAttachmentItem);
}
}
);
if (lastItemID) {
setAttachmentItem(lastItemID, rows);
}
//
// Notes
//
sql = "SELECT parentItemID, N.itemID, title, "
+ "CASE WHEN DI.itemID IS NULL THEN 0 ELSE 1 END AS trashed "
+ "FROM itemNotes N "
+ "JOIN items I ON (N.parentItemID=I.itemID) "
+ "LEFT JOIN deletedItems DI USING (itemID) "
+ "WHERE libraryID=?"
+ (ids.length ? " AND parentItemID IN (" + ids.map(id => parseInt(id)).join(", ") + ")" : "");
if (Zotero.Prefs.get('sortNotesChronologically')) {
sql += " ORDER BY parentItemID, dateAdded";
}
var setNoteItem = function (itemID, rows) {
var item = this._objectCache[itemID];
if (!item) {
throw new Error("Item " + itemID + " not loaded");
}
item._notes = {
rows,
rowsEmbedded: null,
chronologicalWithTrashed: null,
chronologicalWithoutTrashed: null,
alphabeticalWithTrashed: null,
alphabeticalWithoutTrashed: null,
numWithTrashed: null,
numWithoutTrashed: null,
numWithTrashedWithEmbedded: null,
numWithoutTrashedWithoutEmbedded: null
};
}.bind(this);
lastItemID = null;
rows = [];
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
onRow(row, setNoteItem);
}
}
);
if (lastItemID) {
setNoteItem(lastItemID, rows);
}
// Mark all top-level items as having child items loaded
sql = "SELECT itemID FROM items I WHERE libraryID=?" + idSQL + " AND itemID NOT IN "
+ "(SELECT itemID FROM itemAttachments UNION SELECT itemID FROM itemNotes)";
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
var itemID = row.getResultByIndex(0);
var item = this._objectCache[itemID];
if (!item) {
throw new Error("Item " + itemID + " not loaded");
}
item._loaded.childItems = true;
item._clearChanged('childItems');
}.bind(this)
}
);
});
this._loadTags = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var sql = "SELECT itemID, name, type FROM items "
+ "LEFT JOIN itemTags USING (itemID) "
+ "LEFT JOIN tags USING (tagID) WHERE libraryID=?" + idSQL;
var params = [libraryID];
var lastItemID;
var rows = [];
var setRows = function (itemID, rows) {
var item = this._objectCache[itemID];
if (!item) {
throw new Error("Item " + itemID + " not found");
}
item._tags = [];
for (let i = 0; i < rows.length; i++) {
let row = rows[i];
item._tags.push(Zotero.Tags.cleanData(row));
}
item._loaded.tags = true;
item._clearChanged('tags');
}.bind(this);
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
let itemID = row.getResultByIndex(0);
if (lastItemID && itemID !== lastItemID) {
setRows(lastItemID, rows);
rows = [];
}
lastItemID = itemID;
// Item has no tags
let tag = row.getResultByIndex(1);
if (tag === null) {
return;
}
rows.push({
tag: tag,
type: row.getResultByIndex(2)
});
}.bind(this)
}
);
if (lastItemID) {
setRows(lastItemID, rows);
}
});
this._loadCollections = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var sql = "SELECT itemID, collectionID FROM items "
+ "LEFT JOIN collectionItems USING (itemID) "
+ "WHERE libraryID=?" + idSQL;
var params = [libraryID];
var lastItemID;
var rows = [];
var setRows = function (itemID, rows) {
var item = this._objectCache[itemID];
if (!item) {
throw new Error("Item " + itemID + " not found");
}
item._collections = rows;
item._loaded.collections = true;
item._clearChanged('collections');
}.bind(this);
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
let itemID = row.getResultByIndex(0);
if (lastItemID && itemID !== lastItemID) {
setRows(lastItemID, rows);
rows = [];
}
lastItemID = itemID;
let collectionID = row.getResultByIndex(1);
// No collections
if (collectionID === null) {
return;
}
rows.push(collectionID);
}.bind(this)
}
);
if (lastItemID) {
setRows(lastItemID, rows);
}
});
@ -409,17 +724,11 @@ Zotero.Items = function() {
var otherItemIDs = [];
var itemURI = Zotero.URI.getItemURI(item);
yield item.loadTags();
yield item.loadRelations();
var replPred = Zotero.Relations.replacedItemPredicate;
var toSave = {};
toSave[this.id];
for each(var otherItem in otherItems) {
yield otherItem.loadChildItems();
yield otherItem.loadCollections();
yield otherItem.loadTags();
yield otherItem.loadRelations();
let otherItemURI = Zotero.URI.getItemURI(otherItem);
// Move child items to master
@ -632,16 +941,6 @@ Zotero.Items = function() {
});
this._postLoad = function (libraryID, ids) {
if (!ids) {
if (!this._cachedFields[libraryID]) {
this._cachedFields[libraryID] = [];
}
this._cachedFields[libraryID] = this.primaryFields.concat();
}
}
/*
* Generate SQL to retrieve firstCreator field
*

View File

@ -315,6 +315,16 @@ Zotero.Library.prototype._reloadFromDB = Zotero.Promise.coroutine(function* () {
this._loadDataFromRow(row);
});
/**
* Load object data in this library
*/
Zotero.Library.prototype.loadAllDataTypes = Zotero.Promise.coroutine(function* () {
yield Zotero.SyncedSettings.loadAll(this.libraryID);
yield Zotero.Collections.loadAll(this.libraryID);
yield Zotero.Searches.loadAll(this.libraryID);
yield Zotero.Items.loadAll(this.libraryID);
});
Zotero.Library.prototype.isChildObjectAllowed = function(type) {
return this._childObjectTypes.indexOf(type) != -1;
};
@ -461,6 +471,8 @@ Zotero.Library.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env
yield this._reloadFromDB();
Zotero.Libraries.register(this);
yield this.loadAllDataTypes();
}
});

View File

@ -77,7 +77,7 @@ Zotero.Tags = new function() {
/**
* Get all tags indexed by tagID
* Get all tags in library
*
* @param {Number} libraryID
* @param {Array} [types] Tag types to fetch
@ -181,7 +181,7 @@ Zotero.Tags = new function() {
// We need to know if the old tag has a color assigned so that
// we can assign it to the new name
var oldColorData = yield this.getColor(libraryID, oldName);
var oldColorData = this.getColor(libraryID, oldName);
yield Zotero.DB.executeTransaction(function* () {
var oldItemIDs = yield this.getTagItems(libraryID, oldTagID);
@ -393,13 +393,13 @@ Zotero.Tags = new function() {
*
* @param {Integer} libraryID
* @param {String} name Tag name
* @return {Promise} A Q promise for the tag color as a hex string (e.g., '#990000')
* @return {Object|false} An object containing 'color' as a hex string (e.g., '#990000') and
* 'position', or false if no colored tag with that name
*/
this.getColor = function (libraryID, name) {
return this.getColors(libraryID)
.then(function () {
return _libraryColorsByName[libraryID].get(name) || false;
});
// Cache colors
this.getColors(libraryID);
return _libraryColorsByName[libraryID].get(name) || false;
}
@ -408,14 +408,12 @@ Zotero.Tags = new function() {
*
* @param {Integer} libraryID
* @param {Integer} position The position of the tag, starting at 0
* @return {Promise} A promise for an object containing 'name' and 'color'
* @return {Object|false} An object containing 'name' and 'color', or false if no color at
* the given position
*/
this.getColorByPosition = function (libraryID, position) {
return this.getColors(libraryID)
.then(function () {
return _libraryColors[libraryID][position]
? _libraryColors[libraryID][position] : false;
});
this.getColors(libraryID);
return _libraryColors[libraryID][position] ? _libraryColors[libraryID][position] : false;
}
@ -423,20 +421,19 @@ Zotero.Tags = new function() {
* Get colored tags within a given library
*
* @param {Integer} libraryID
* @return {Promise<Map>} - A promise for a Map with tag names as keys and
* objects containing 'color' and 'position' as values
* @return {Map} - A Map with tag names as keys and objects containing 'color' and 'position'
* as values
*/
this.getColors = Zotero.Promise.coroutine(function* (libraryID) {
this.getColors = function (libraryID) {
if (!libraryID) {
throw new Error("libraryID not provided");
}
if (_libraryColorsByName[libraryID]) {
return _libraryColorsByName[libraryID];
}
var tagColors = yield Zotero.SyncedSettings.get(libraryID, 'tagColors');
// If the colors became available from another run
if (_libraryColorsByName[libraryID]) {
return _libraryColorsByName[libraryID];
}
var tagColors = Zotero.SyncedSettings.get(libraryID, 'tagColors');
tagColors = tagColors || [];
@ -452,7 +449,7 @@ Zotero.Tags = new function() {
}
return _libraryColorsByName[libraryID];
});
};
/**
@ -465,7 +462,7 @@ Zotero.Tags = new function() {
throw new Error("libraryID must be an integer");
}
yield this.getColors(libraryID);
this.getColors(libraryID);
var tagColors = _libraryColors[libraryID];
@ -541,7 +538,7 @@ Zotero.Tags = new function() {
delete _libraryColorsByName[libraryID];
// Get the tag colors for each library in which they were modified
let tagColors = yield Zotero.SyncedSettings.get(libraryID, 'tagColors');
let tagColors = Zotero.SyncedSettings.get(libraryID, 'tagColors');
if (!tagColors) {
tagColors = [];
}

View File

@ -87,6 +87,10 @@ Zotero.Date = new function(){
**/
function sqlToDate(sqldate, isUTC){
try {
if (!this.isSQLDate(sqldate) && !this.isSQLDateTime(sqldate)) {
throw new Error("Invalid date");
}
var datetime = sqldate.split(' ');
var dateparts = datetime[0].split('-');
if (datetime[1]){
@ -98,7 +102,7 @@ Zotero.Date = new function(){
// Invalid date part
if (dateparts.length==1){
return false;
throw new Error("Invalid date part");
}
if (isUTC){
@ -699,7 +703,7 @@ Zotero.Date = new function(){
function toUnixTimestamp(date) {
if (date === null || typeof date != 'object' ||
date.constructor.name != 'Date') {
throw ('Not a valid date in Zotero.Date.toUnixTimestamp()');
throw new Error(`'${date}' is not a valid date`);
}
return Math.round(date.getTime() / 1000);
}

View File

@ -630,7 +630,13 @@ Zotero.DBConnection.prototype.queryAsync = Zotero.Promise.coroutine(function* (s
}
}
}
let rows = yield conn.executeCached(sql, params, onRow);
let rows;
if (options && options.noCache) {
rows = yield conn.execute(sql, params, onRow);
}
else {
rows = yield conn.executeCached(sql, params, onRow);
}
// Parse out the SQL command being used
let op = sql.match(/^[^a-z]*[^ ]+/i);
if (op) {

View File

@ -303,13 +303,13 @@ Zotero.Duplicates.prototype._findDuplicates = Zotero.Promise.coroutine(function*
itemCreators = [];
}
}
else {
itemCreators.push({
lastName: normalizeString(row.lastName),
firstInitial: row.fieldMode == 0 ? normalizeString(row.firstName).charAt(0) : false
});
}
lastItemID = row.itemID;
itemCreators.push({
lastName: normalizeString(row.lastName),
firstInitial: row.fieldMode == 0 ? normalizeString(row.firstName).charAt(0) : false
});
}
// Add final item creators
if (itemCreators.length) {

View File

@ -82,7 +82,7 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
if (this._treebox) {
if (this._needsSort) {
yield this.sort();
this.sort();
}
return;
}
@ -133,11 +133,11 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
if (self._treebox.view.selection.count > 1) {
switch (event.keyCode) {
case 39:
self.expandSelectedRows().done();
self.expandSelectedRows();
break;
case 37:
self.collapseSelectedRows().done();
self.collapseSelectedRows();
break;
}
@ -148,7 +148,7 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
var key = String.fromCharCode(event.which);
if (key == '+' && !(event.ctrlKey || event.altKey || event.metaKey)) {
self.expandAllRows().done();
self.expandAllRows();
event.preventDefault();
return;
}
@ -170,12 +170,11 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
if (coloredTagsRE.test(key)) {
let libraryID = self.collectionTreeRow.ref.libraryID;
let position = parseInt(key) - 1;
let colorData = yield Zotero.Tags.getColorByPosition(libraryID, position);
let colorData = Zotero.Tags.getColorByPosition(libraryID, position);
// If a color isn't assigned to this number or any
// other numbers, allow key navigation
if (!colorData) {
let colors = yield Zotero.Tags.getColors(libraryID);
return !colors.size;
return !Zotero.Tags.getColors(libraryID).size;
}
var items = self.getSelectedItems();
@ -230,8 +229,8 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
// handleKeyPress() in zoteroPane.js.
tree._handleEnter = function () {};
yield this.sort();
yield this.expandMatchParents();
this.sort();
this.expandMatchParents();
if (this._ownerDocument.defaultView.ZoteroPane_Local) {
this._ownerDocument.defaultView.ZoteroPane_Local.clearItemsPaneMessage();
@ -266,13 +265,10 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
*/
Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(function* () {
Zotero.debug('Refreshing items list for ' + this.id);
//if(!Zotero.ItemTreeView._haveCachedFields) yield Zotero.Promise.resolve();
var cacheFields = ['title', 'date'];
// Cache the visible fields so they don't load individually
// DEBUG: necessary?
try {
var visibleFields = this.getVisibleFields();
this._treebox.columns.count
}
// If treebox isn't ready, skip refresh
catch (e) {
@ -286,33 +282,6 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f
});
try {
for (let i=0; i<visibleFields.length; i++) {
let field = visibleFields[i];
switch (field) {
case 'hasAttachment':
// Needed by item.getBestAttachments(), called by getBestAttachmentStateAsync()
field = 'url';
break;
case 'numNotes':
continue;
case 'year':
field = 'date';
break;
case 'itemType':
field = 'itemTypeID';
break;
}
if (cacheFields.indexOf(field) == -1) {
cacheFields = cacheFields.concat(field);
}
}
yield Zotero.Items.cacheFields(this.collectionTreeRow.ref.libraryID, cacheFields);
Zotero.ItemTreeView._haveCachedFields = true;
Zotero.CollectionTreeCache.clear();
if (!this.selection.selectEventsSuppressed) {
@ -382,9 +351,9 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f
this._searchParentIDs = newSearchParentIDs;
this._cellTextCache = {};
yield this.rememberOpenState(savedOpenState);
yield this.rememberSelection(savedSelection);
yield this.expandMatchParents();
this.rememberOpenState(savedOpenState);
this.rememberSelection(savedSelection);
this.expandMatchParents();
if (unsuppress) {
// This causes a problem with the row count being wrong between views
//this._treebox.endUpdateBatch();
@ -404,12 +373,6 @@ Zotero.ItemTreeView.prototype.refresh = Zotero.serial(Zotero.Promise.coroutine(f
}));
/**
* Generator used internally for refresh
*/
Zotero.ItemTreeView._haveCachedFields = false;
/*
* Called by Zotero.Notifier on any changes to items in the data layer
*/
@ -502,7 +465,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
delete this._cellTextCache[row];
this.selection.clearSelection();
yield this.rememberSelection(savedSelection);
this.rememberSelection(savedSelection);
}
else {
this._cellTextCache = {};
@ -569,7 +532,6 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
push = true;
}
else {
yield collectionTreeRow.ref.loadChildItems();
push = !collectionTreeRow.ref.hasItem(ids[i]);
}
// Row might already be gone (e.g. if this is a child and
@ -627,7 +589,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
// If no quicksearch, process modifications manually
else if (!quicksearch || quicksearch.value == '')
{
var items = yield Zotero.Items.getAsync(ids);
var items = Zotero.Items.get(ids);
for (let i = 0; i < items.length; i++) {
let item = items[i];
@ -646,9 +608,16 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
let parentItemID = this.getRow(row).ref.parentItemID;
let parentIndex = this.getParentIndex(row);
// Top-level items just have to be resorted
// Top-level item
if (this.isContainer(row)) {
sort = id;
// If Unfiled Items and itm was added to a collection, remove from view
if (collectionTreeRow.isUnfiled() && item.getCollections().length) {
this._removeRow(row);
}
// Otherwise just resort
else {
sort = id;
}
}
// If item moved from top-level to under another item, remove the old row.
else if (parentIndex == -1 && parentItemID) {
@ -679,7 +648,6 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
&& collectionTreeRow.ref.libraryID == item.libraryID;
// Collection containing item
if (!add && collectionTreeRow.isCollection()) {
yield item.loadCollections();
add = item.inCollection(collectionTreeRow.ref.id);
}
if (add) {
@ -719,14 +687,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
}
else if(action == 'add')
{
// New items need their item data and collections loaded
// before they're inserted into the tree
let items = yield Zotero.Items.getAsync(ids);
for (let i=0; i<items.length; i++) {
let item = items[i];
yield item.loadItemData();
yield item.loadCollections();
}
// In some modes, just re-run search
if (collectionTreeRow.isSearch() || collectionTreeRow.isTrash() || collectionTreeRow.isUnfiled()) {
@ -824,7 +785,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
}
if (sort) {
yield this.sort(typeof sort == 'number' ? sort : false);
this.sort(typeof sort == 'number' ? sort : false);
}
else {
this._refreshItemRowMap();
@ -853,7 +814,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
yield this.selectItem(ids[0]);
}
else {
yield this.rememberSelection(savedSelection);
this.rememberSelection(savedSelection);
}
}
// On removal of a row, select item at previous position
@ -891,7 +852,7 @@ Zotero.ItemTreeView.prototype.notify = Zotero.Promise.coroutine(function* (actio
}
}
else {
yield this.rememberSelection(savedSelection);
this.rememberSelection(savedSelection);
}
}
@ -1159,8 +1120,7 @@ Zotero.ItemTreeView.prototype.hasNextSibling = function(row,afterIndex)
}
}
Zotero.ItemTreeView.prototype.toggleOpenState = Zotero.Promise.coroutine(function* (row, skipItemMapRefresh)
{
Zotero.ItemTreeView.prototype.toggleOpenState = function (row, skipItemMapRefresh) {
// Shouldn't happen but does if an item is dragged over a closed
// container until it opens and then released, since the container
// is no longer in the same place when the spring-load closes
@ -1179,7 +1139,6 @@ Zotero.ItemTreeView.prototype.toggleOpenState = Zotero.Promise.coroutine(functio
// Open
//
var item = this.getRow(row).ref;
yield item.loadChildItems();
//Get children
var includeTrashed = this.collectionTreeRow.isTrash();
@ -1198,7 +1157,7 @@ Zotero.ItemTreeView.prototype.toggleOpenState = Zotero.Promise.coroutine(functio
}
if (newRows) {
newRows = yield Zotero.Items.getAsync(newRows);
newRows = Zotero.Items.get(newRows);
for (let i = 0; i < newRows.length; i++) {
count++;
@ -1221,7 +1180,7 @@ Zotero.ItemTreeView.prototype.toggleOpenState = Zotero.Promise.coroutine(functio
Zotero.debug('Refreshing hash map');
this._refreshItemRowMap();
}
});
}
Zotero.ItemTreeView.prototype._closeContainer = function (row, skipItemMapRefresh) {
@ -1263,8 +1222,7 @@ Zotero.ItemTreeView.prototype.isSorted = function()
return true;
}
Zotero.ItemTreeView.prototype.cycleHeader = Zotero.Promise.coroutine(function* (column)
{
Zotero.ItemTreeView.prototype.cycleHeader = function (column) {
for(var i=0, len=this._treebox.columns.count; i<len; i++)
{
col = this._treebox.columns.getColumnAt(i);
@ -1291,8 +1249,8 @@ Zotero.ItemTreeView.prototype.cycleHeader = Zotero.Promise.coroutine(function* (
if (savedSelection.length == 1) {
var pos = this._rowMap[savedSelection[0]] - this._treebox.getFirstVisibleRow();
}
yield this.sort();
yield this.rememberSelection(savedSelection);
this.sort();
this.rememberSelection(savedSelection);
// If single row was selected, try to keep it in the same place
if (savedSelection.length == 1) {
var newRow = this._rowMap[savedSelection[0]];
@ -1304,12 +1262,12 @@ Zotero.ItemTreeView.prototype.cycleHeader = Zotero.Promise.coroutine(function* (
}
this._treebox.invalidate();
this.selection.selectEventsSuppressed = false;
});
}
/*
* Sort the items by the currently sorted column.
*/
Zotero.ItemTreeView.prototype.sort = Zotero.Promise.coroutine(function* (itemID) {
Zotero.ItemTreeView.prototype.sort = function (itemID) {
var t = new Date;
// If Zotero pane is hidden, mark tree for sorting later in setTree()
@ -1324,7 +1282,7 @@ Zotero.ItemTreeView.prototype.sort = Zotero.Promise.coroutine(function* (itemID)
this.getRow(this._rowMap[itemID]).ref.parentKey) {
let parentIndex = this.getParentIndex(this._rowMap[itemID]);
this._closeContainer(parentIndex);
yield this.toggleOpenState(parentIndex);
this.toggleOpenState(parentIndex);
return;
}
@ -1566,8 +1524,8 @@ Zotero.ItemTreeView.prototype.sort = Zotero.Promise.coroutine(function* (itemID)
this._refreshItemRowMap();
yield this.rememberOpenState(openItemIDs);
yield this.rememberSelection(savedSelection);
this.rememberOpenState(openItemIDs);
this.rememberSelection(savedSelection);
if (unsuppress) {
this.selection.selectEventsSuppressed = false;
@ -1575,7 +1533,7 @@ Zotero.ItemTreeView.prototype.sort = Zotero.Promise.coroutine(function* (itemID)
}
Zotero.debug("Sorted items list in " + (new Date - t) + " ms");
});
};
////////////////////////////////////////////////////////////////////////////////
@ -1633,7 +1591,7 @@ Zotero.ItemTreeView.prototype.selectItem = Zotero.Promise.coroutine(function* (i
// Clear the quicksearch and tag selection and try again (once)
if (!noRecurse && this._ownerDocument.defaultView.ZoteroPane_Local) {
let cleared1 = yield this._ownerDocument.defaultView.ZoteroPane_Local.clearQuicksearch();
let cleared2 = yield this._ownerDocument.defaultView.ZoteroPane_Local.clearTagSelection();
let cleared2 = this._ownerDocument.defaultView.ZoteroPane_Local.clearTagSelection();
if (cleared1 || cleared2) {
return this.selectItem(id, expand, true);
}
@ -1648,7 +1606,7 @@ Zotero.ItemTreeView.prototype.selectItem = Zotero.Promise.coroutine(function* (i
this._closeContainer(parentRow);
// Open the parent
yield this.toggleOpenState(parentRow);
this.toggleOpenState(parentRow);
row = this._rowMap[id];
}
@ -1669,7 +1627,7 @@ Zotero.ItemTreeView.prototype.selectItem = Zotero.Promise.coroutine(function* (i
// If |expand|, open row if container
if (expand && this.isContainer(row) && !this.isContainerOpen(row)) {
yield this.toggleOpenState(row);
this.toggleOpenState(row);
}
this.selection.select(row);
@ -1836,7 +1794,7 @@ Zotero.ItemTreeView.prototype.setFilter = Zotero.Promise.coroutine(function* (ty
var oldCount = this.rowCount;
yield this.refresh();
yield this.sort();
this.sort();
//this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
@ -1869,8 +1827,7 @@ Zotero.ItemTreeView.prototype.saveSelection = function () {
/*
* Sets the selection based on saved selection ids
*/
Zotero.ItemTreeView.prototype.rememberSelection = Zotero.Promise.coroutine(function* (selection)
{
Zotero.ItemTreeView.prototype.rememberSelection = function (selection) {
if (!selection.length) {
return;
}
@ -1888,7 +1845,7 @@ Zotero.ItemTreeView.prototype.rememberSelection = Zotero.Promise.coroutine(funct
}
// Try the parent
else {
var item = yield Zotero.Items.getAsync(selection[i]);
var item = Zotero.Items.get(selection[i]);
if (!item) {
continue;
}
@ -1900,7 +1857,7 @@ Zotero.ItemTreeView.prototype.rememberSelection = Zotero.Promise.coroutine(funct
if (this._rowMap[parent] != null) {
this._closeContainer(this._rowMap[parent]);
yield this.toggleOpenState(this._rowMap[parent]);
this.toggleOpenState(this._rowMap[parent]);
this.selection.toggleSelect(this._rowMap[selection[i]]);
}
}
@ -1909,21 +1866,21 @@ Zotero.ItemTreeView.prototype.rememberSelection = Zotero.Promise.coroutine(funct
//this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
}
});
}
Zotero.ItemTreeView.prototype.selectSearchMatches = Zotero.Promise.coroutine(function* () {
Zotero.ItemTreeView.prototype.selectSearchMatches = function () {
if (this._searchMode) {
var ids = [];
for (var id in this._searchItemIDs) {
ids.push(id);
}
yield this.rememberSelection(ids);
this.rememberSelection(ids);
}
else {
this.selection.clearSelection();
}
});
}
Zotero.ItemTreeView.prototype._saveOpenState = function (close) {
@ -1953,7 +1910,7 @@ Zotero.ItemTreeView.prototype._saveOpenState = function (close) {
}
Zotero.ItemTreeView.prototype.rememberOpenState = Zotero.Promise.coroutine(function* (itemIDs) {
Zotero.ItemTreeView.prototype.rememberOpenState = function (itemIDs) {
var rowsToOpen = [];
for each(var id in itemIDs) {
var row = this._rowMap[id];
@ -1973,17 +1930,17 @@ Zotero.ItemTreeView.prototype.rememberOpenState = Zotero.Promise.coroutine(funct
}
// Reopen from bottom up
for (var i=rowsToOpen.length-1; i>=0; i--) {
yield this.toggleOpenState(rowsToOpen[i], true);
this.toggleOpenState(rowsToOpen[i], true);
}
this._refreshItemRowMap();
if (unsuppress) {
//this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
}
});
}
Zotero.ItemTreeView.prototype.expandMatchParents = Zotero.Promise.coroutine(function* () {
Zotero.ItemTreeView.prototype.expandMatchParents = function () {
// Expand parents of child matches
if (!this._searchMode) {
return;
@ -2001,7 +1958,7 @@ Zotero.ItemTreeView.prototype.expandMatchParents = Zotero.Promise.coroutine(func
for (var i=0; i<this.rowCount; i++) {
var id = this.getRow(i).ref.id;
if (hash[id] && this.isContainer(i) && !this.isContainerOpen(i)) {
yield this.toggleOpenState(i, true);
this.toggleOpenState(i, true);
}
}
this._refreshItemRowMap();
@ -2009,7 +1966,7 @@ Zotero.ItemTreeView.prototype.expandMatchParents = Zotero.Promise.coroutine(func
//this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
}
});
}
Zotero.ItemTreeView.prototype.saveFirstRow = function() {
@ -2028,18 +1985,18 @@ Zotero.ItemTreeView.prototype.rememberFirstRow = function(firstRow) {
}
Zotero.ItemTreeView.prototype.expandAllRows = Zotero.Promise.coroutine(function* () {
Zotero.ItemTreeView.prototype.expandAllRows = function () {
var unsuppress = this.selection.selectEventsSuppressed = true;
//this._treebox.beginUpdateBatch();
for (var i=0; i<this.rowCount; i++) {
if (this.isContainer(i) && !this.isContainerOpen(i)) {
yield this.toggleOpenState(i, true);
this.toggleOpenState(i, true);
}
}
this._refreshItemRowMap();
//this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
});
}
Zotero.ItemTreeView.prototype.collapseAllRows = Zotero.Promise.coroutine(function* () {
@ -2056,7 +2013,7 @@ Zotero.ItemTreeView.prototype.collapseAllRows = Zotero.Promise.coroutine(functio
});
Zotero.ItemTreeView.prototype.expandSelectedRows = Zotero.Promise.coroutine(function* () {
Zotero.ItemTreeView.prototype.expandSelectedRows = function () {
var start = {}, end = {};
this.selection.selectEventsSuppressed = true;
//this._treebox.beginUpdateBatch();
@ -2064,17 +2021,17 @@ Zotero.ItemTreeView.prototype.expandSelectedRows = Zotero.Promise.coroutine(func
this.selection.getRangeAt(i, start, end);
for (var j = start.value; j <= end.value; j++) {
if (this.isContainer(j) && !this.isContainerOpen(j)) {
yield this.toggleOpenState(j, true);
this.toggleOpenState(j, true);
}
}
}
this._refreshItemRowMap();
//this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
});
}
Zotero.ItemTreeView.prototype.collapseSelectedRows = Zotero.Promise.coroutine(function* () {
Zotero.ItemTreeView.prototype.collapseSelectedRows = function () {
var start = {}, end = {};
this.selection.selectEventsSuppressed = true;
//this._treebox.beginUpdateBatch();
@ -2089,7 +2046,7 @@ Zotero.ItemTreeView.prototype.collapseSelectedRows = Zotero.Promise.coroutine(fu
this._refreshItemRowMap();
//this._treebox.endUpdateBatch();
this.selection.selectEventsSuppressed = false;
});
}
Zotero.ItemTreeView.prototype.getVisibleFields = function() {
@ -2392,17 +2349,16 @@ Zotero.ItemTreeCommandController.prototype.isCommandEnabled = function(cmd)
return (cmd == 'cmd_selectAll');
}
Zotero.ItemTreeCommandController.prototype.doCommand = Zotero.Promise.coroutine(function* (cmd)
{
Zotero.ItemTreeCommandController.prototype.doCommand = function (cmd) {
if (cmd == 'cmd_selectAll') {
if (this.tree.view.wrappedJSObject.collectionTreeRow.isSearchMode()) {
yield this.tree.view.wrappedJSObject.selectSearchMatches();
this.tree.view.wrappedJSObject.selectSearchMatches();
}
else {
this.tree.view.selection.selectAll();
}
}
});
}
Zotero.ItemTreeCommandController.prototype.onEvent = function(evt)
{
@ -2474,10 +2430,6 @@ Zotero.ItemTreeView.prototype.onDragStart = function (event) {
}
}
// TEMP
Zotero.debug("TEMP: Skipping Quick Copy");
return;
// Get Quick Copy format for current URL
var url = this._ownerDocument.defaultView.content && this._ownerDocument.defaultView.content.location ?
this._ownerDocument.defaultView.content.location.href : null;
@ -2937,7 +2889,6 @@ Zotero.ItemTreeView.prototype.drop = Zotero.Promise.coroutine(function* (row, or
for (let i=0; i<items.length; i++) {
let item = items[i];
var source = item.isRegularItem() ? false : item.parentItemID;
yield item.loadCollections();
// Top-level item
if (source) {
item.parentID = false;

View File

@ -79,7 +79,7 @@ Zotero.LibraryTreeView.prototype = {
* Return the index of the row with a given ID (e.g., "C123" for collection 123)
*
* @param {String} - Row id
* @return {Integer}
* @return {Integer|false}
*/
getRowIndexByID: function (id) {
var type = "";
@ -87,7 +87,7 @@ Zotero.LibraryTreeView.prototype = {
var type = id[0];
id = ('' + id).substr(1);
}
return this._rowMap[type + id];
return this._rowMap[type + id] !== undefined ? this._rowMap[type + id] : false;
},

View File

@ -542,7 +542,7 @@ Zotero.Schema = new function(){
var Mode = Zotero.Utilities.capitalize(mode);
var repotime = yield Zotero.File.getContentsFromURLAsync("resource://zotero/schema/repotime.txt");
var date = Zotero.Date.sqlToDate(repotime, true);
var date = Zotero.Date.sqlToDate(repotime.trim(), true);
repotime = Zotero.Date.toUnixTimestamp(date);
var fileNameRE = new RegExp("^[^\.].+\\" + fileExt + "$");

View File

@ -382,6 +382,7 @@ Zotero.Search.prototype.addCondition = function (condition, operator, value, req
this._sqlParams = false;
this._markFieldChange('conditions', this._conditions);
this._changed.conditions = true;
return searchConditionID;
}
@ -499,14 +500,11 @@ Zotero.Search.prototype.hasPostSearchFilter = function() {
Zotero.Search.prototype.search = Zotero.Promise.coroutine(function* (asTempTable) {
var tmpTable;
if (this._identified) {
yield this.loadConditions();
}
// Mark conditions as loaded
else {
// TODO: Necessary?
if (!this._identified) {
this._requireData('conditions');
}
try {
if (!this._sql){
yield this._buildQuery();
@ -554,11 +552,6 @@ Zotero.Search.prototype.search = Zotero.Promise.coroutine(function* (asTempTable
// Run a subsearch to define the superset of possible results
if (this._scope) {
if (this._scope._identified) {
yield this._scope.loadPrimaryData();
yield this._scope.loadConditions();
}
// If subsearch has post-search filter, run and insert ids into temp table
if (this._scope.hasPostSearchFilter()) {
var ids = yield this._scope.search();
@ -822,7 +815,7 @@ Zotero.Search.prototype.search = Zotero.Promise.coroutine(function* (asTempTable
/**
* Populate the object's data from an API JSON data object
*
* If this object is identified (has an id or library/key), loadAllData() must have been called.
* If this object is identified (has an id or library/key), loadAll() must have been called.
*/
Zotero.Search.prototype.fromJSON = function (json) {
if (!json.name) {
@ -840,13 +833,13 @@ Zotero.Search.prototype.fromJSON = function (json) {
}
}
Zotero.Collection.prototype.toResponseJSON = Zotero.Promise.coroutine(function* (options = {}) {
var json = yield this.constructor._super.prototype.toResponseJSON.apply(this, options);
Zotero.Collection.prototype.toResponseJSON = function (options = {}) {
var json = this.constructor._super.prototype.toResponseJSON.apply(this, options);
return json;
});
};
Zotero.Search.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {}) {
Zotero.Search.prototype.toJSON = function (options = {}) {
var env = this._preToJSON(options);
var mode = env.mode;
@ -854,11 +847,10 @@ Zotero.Search.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {
obj.key = this.key;
obj.version = this.version;
obj.name = this.name;
yield this.loadConditions();
obj.conditions = this.getConditions();
return this._postToJSON(env);
});
}
/*
@ -866,7 +858,6 @@ Zotero.Search.prototype.toJSON = Zotero.Promise.coroutine(function* (options = {
*/
Zotero.Search.prototype.getSQL = Zotero.Promise.coroutine(function* () {
if (!this._sql) {
yield this.loadConditions();
yield this._buildQuery();
}
return this._sql;
@ -875,68 +866,12 @@ Zotero.Search.prototype.getSQL = Zotero.Promise.coroutine(function* () {
Zotero.Search.prototype.getSQLParams = Zotero.Promise.coroutine(function* () {
if (!this._sql) {
yield this.loadConditions();
yield this._buildQuery();
}
return this._sqlParams;
});
Zotero.Search.prototype.loadConditions = Zotero.Promise.coroutine(function* (reload) {
if (this._loaded.conditions && !reload) return;
Zotero.debug("Loading conditions for search " + this.libraryKey);
if (!this.id) {
throw new Error('ID not set for object before attempting to load conditions');
}
var sql = "SELECT * FROM savedSearchConditions "
+ "WHERE savedSearchID=? ORDER BY searchConditionID";
var conditions = yield Zotero.DB.queryAsync(sql, this.id);
if (conditions.length) {
this._maxSearchConditionID = conditions[conditions.length - 1].searchConditionID;
}
this._conditions = {};
// Reindex conditions, in case they're not contiguous in the DB
for (let i=0; i<conditions.length; i++) {
let condition = conditions[i];
// Parse "condition[/mode]"
let [conditionName, mode] = Zotero.SearchConditions.parseCondition(condition.condition);
let cond = Zotero.SearchConditions.get(conditionName);
if (!cond || cond.noLoad) {
Zotero.debug("Invalid saved search condition '" + conditionName + "' -- skipping", 2);
continue;
}
// Convert itemTypeID to itemType
//
// TEMP: This can be removed at some point
if (conditionName == 'itemTypeID') {
conditionName = 'itemType';
condition.value = Zotero.ItemTypes.getName(condition.value);
}
this._conditions[i] = {
id: i,
condition: conditionName,
mode: mode,
operator: condition.operator,
value: condition.value,
required: !!condition.required
};
}
this._loaded.conditions = true;
this._clearChanged('conditions');
});
/*
* Batch insert
*/
@ -1687,26 +1622,18 @@ Zotero.Searches = function() {
* @param {Integer} [libraryID]
*/
this.getAll = Zotero.Promise.coroutine(function* (libraryID) {
var sql = "SELECT savedSearchID AS id, savedSearchName AS name FROM savedSearches ";
if (libraryID) {
sql += "WHERE libraryID=? ";
var params = libraryID;
var sql = "SELECT savedSearchID FROM savedSearches WHERE libraryID=?";
var ids = yield Zotero.DB.columnQueryAsync(sql, libraryID);
if (!ids.length) {
return []
}
var rows = yield Zotero.DB.queryAsync(sql, params);
var searches = this.get(ids);
// Do proper collation sort
var collation = Zotero.getLocaleCollation();
rows.sort(function (a, b) {
searches.sort(function (a, b) {
return collation.compareString(1, a.name, b.name);
});
var searches = [];
for (var i=0; i<rows.length; i++) {
let search = new Zotero.Search;
search.id = rows[i].id;
yield search.loadPrimaryData();
searches.push(search);
}
return searches;
});
@ -1719,6 +1646,95 @@ Zotero.Searches = function() {
+ "FROM savedSearches O WHERE 1";
}
this._loadConditions = Zotero.Promise.coroutine(function* (libraryID, ids, idSQL) {
var sql = "SELECT savedSearchID, searchConditionID, condition, operator, value, required "
+ "FROM savedSearches LEFT JOIN savedSearchConditions USING (savedSearchID) "
+ "WHERE libraryID=?" + idSQL
+ "ORDER BY savedSearchID, searchConditionID";
var params = [libraryID];
var lastID = null;
var rows = [];
var setRows = function (searchID, rows) {
var search = this._objectCache[searchID];
if (!search) {
throw new Error("Search " + searchID + " not found");
}
search._conditions = {};
if (rows.length) {
search._maxSearchConditionID = rows[rows.length - 1].searchConditionID;
}
// Reindex conditions, in case they're not contiguous in the DB
for (let i = 0; i < rows.length; i++) {
let condition = rows[i];
// Parse "condition[/mode]"
let [conditionName, mode] = Zotero.SearchConditions.parseCondition(condition.condition);
let cond = Zotero.SearchConditions.get(conditionName);
if (!cond || cond.noLoad) {
Zotero.debug("Invalid saved search condition '" + conditionName + "' -- skipping", 2);
continue;
}
// Convert itemTypeID to itemType
//
// TEMP: This can be removed at some point
if (conditionName == 'itemTypeID') {
conditionName = 'itemType';
condition.value = Zotero.ItemTypes.getName(condition.value);
}
search._conditions[i] = {
id: i,
condition: conditionName,
mode: mode,
operator: condition.operator,
value: condition.value,
required: !!condition.required
};
}
search._loaded.conditions = true;
search._clearChanged('conditions');
}.bind(this);
yield Zotero.DB.queryAsync(
sql,
params,
{
noCache: ids.length != 1,
onRow: function (row) {
let searchID = row.getResultByIndex(0);
if (lastID && searchID != lastID) {
setRows(lastID, rows);
rows = [];
}
lastID = searchID;
let searchConditionID = row.getResultByIndex(1);
// No conditions
if (searchConditionID === null) {
return;
}
rows.push({
searchConditionID,
condition: row.getResultByIndex(2),
operator: row.getResultByIndex(3),
value: row.getResultByIndex(4),
required: row.getResultByIndex(5)
});
}.bind(this)
}
);
if (lastID) {
setRows(lastID, rows);
}
});
Zotero.DataObjects.call(this);
return this;

View File

@ -65,9 +65,9 @@ Zotero.Sync.Storage = new function () {
return false;
}
var syncModTime = Zotero.Sync.Storage.getSyncedModificationTime(itemID);
var syncModTime = item.attachmentSyncedModificationTime;
if (fileModTime != syncModTime) {
var syncHash = Zotero.Sync.Storage.getSyncedHash(itemID);
var syncHash = item.attachmentSyncedHash;
if (syncHash) {
var fileHash = item.attachmentHash;
if (fileHash && fileHash == syncHash) {

View File

@ -273,7 +273,7 @@ Zotero.Sync.Storage.Engine.prototype.stop = function () {
}
Zotero.Sync.Storage.Engine.prototype.queueItem = Zotero.Promise.coroutine(function* (item) {
switch (yield this.local.getSyncState(item.id)) {
switch (item.attachmentSyncState) {
case Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD:
case Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_DOWNLOAD:
var type = 'download';
@ -295,7 +295,7 @@ Zotero.Sync.Storage.Engine.prototype.queueItem = Zotero.Promise.coroutine(functi
return;
default:
throw new Error("Invalid sync state " + (yield this.local.getSyncState(item.id)));
throw new Error("Invalid sync state " + item.attachmentSyncState);
}
var request = new Zotero.Sync.Storage.Request({

View File

@ -239,7 +239,8 @@ Zotero.Sync.Storage.Local = {
// TODO: Catch error?
let state = yield this._checkForUpdatedFile(item, attachmentData[item.id]);
if (state !== false) {
yield Zotero.Sync.Storage.Local.setSyncState(item.id, state);
item.attachmentSyncState = state;
yield item.saveTx({ skipAll: true });
changed = true;
}
}
@ -282,7 +283,7 @@ Zotero.Sync.Storage.Local = {
// If file is already marked for upload, skip check. Even if the file was changed
// both locally and remotely, conflicts are checked at upload time, so we don't need
// to worry about it here.
if ((yield this.getSyncState(item.id)) == this.SYNC_STATE_TO_UPLOAD) {
if (item.attachmentSyncState == this.SYNC_STATE_TO_UPLOAD) {
Zotero.debug("File is already marked for upload");
return false;
}
@ -298,7 +299,7 @@ Zotero.Sync.Storage.Local = {
Zotero.debug(`Remote mod time for item ${lk} is ${remoteModTime}`);
// Ignore attachments whose stored mod times haven't changed
mtime = mtime !== false ? mtime : (yield this.getSyncedModificationTime(item.id));
mtime = mtime !== false ? mtime : item.attachmentSyncedModificationTime;
if (mtime == remoteModTime) {
Zotero.debug(`Synced mod time (${mtime}) hasn't changed for item ${lk}`);
return false;
@ -474,125 +475,23 @@ Zotero.Sync.Storage.Local = {
},
/**
* @param {Integer} itemID
*/
getSyncState: function (itemID) {
var sql = "SELECT syncState FROM itemAttachments WHERE itemID=?";
return Zotero.DB.valueQueryAsync(sql, itemID);
},
/**
* @param {Integer} itemID
* @param {Integer|String} syncState - Zotero.Sync.Storage.Local.SYNC_STATE_* or last part
* as string (e.g., "TO_UPLOAD")
*/
setSyncState: Zotero.Promise.method(function (itemID, syncState) {
if (typeof syncState == 'string') {
syncState = this["SYNC_STATE_" + syncState.toUpperCase()];
resetModeSyncStates: Zotero.Promise.coroutine(function* () {
var sql = "SELECT itemID FROM items JOIN itemAttachments USING (itemID) "
+ "WHERE libraryID=? AND itemTypeID=? AND linkMode IN (?, ?)";
var params = [
Zotero.Libraries.userLibraryID,
Zotero.ItemTypes.getID('attachment'),
Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
Zotero.Attachments.LINK_MODE_IMPORTED_URL,
];
var itemIDs = yield Zotero.DB.columnQueryAsync(sql, params);
for (let itemID of items) {
let item = Zotero.Items.get(itemID);
item._attachmentSyncState = this.SYNC_STATE_TO_UPLOAD;
}
switch (syncState) {
case this.SYNC_STATE_TO_UPLOAD:
case this.SYNC_STATE_TO_DOWNLOAD:
case this.SYNC_STATE_IN_SYNC:
case this.SYNC_STATE_FORCE_UPLOAD:
case this.SYNC_STATE_FORCE_DOWNLOAD:
case this.SYNC_STATE_IN_CONFLICT:
break;
default:
throw new Error("Invalid sync state " + syncState);
}
var sql = "UPDATE itemAttachments SET syncState=? WHERE itemID=?";
return Zotero.DB.valueQueryAsync(sql, [syncState, itemID]);
}),
resetModeSyncStates: Zotero.Promise.coroutine(function* (mode) {
var sql = "UPDATE itemAttachments SET syncState=? "
+ "WHERE itemID IN (SELECT itemID FROM items WHERE libraryID=?)";
var params = [this.SYNC_STATE_TO_UPLOAD, Zotero.Libraries.userLibraryID];
yield Zotero.DB.queryAsync(sql, params);
}),
/**
* @param {Integer} itemID
* @return {Integer|NULL} Mod time as timestamp in ms,
* or NULL if never synced
*/
getSyncedModificationTime: Zotero.Promise.coroutine(function* (itemID) {
var sql = "SELECT storageModTime FROM itemAttachments WHERE itemID=?";
var mtime = yield Zotero.DB.valueQueryAsync(sql, itemID);
if (mtime === false) {
throw new Error("Item " + itemID + " not found")
}
return mtime;
}),
/**
* @param {Integer} itemID
* @param {Integer} mtime - File modification time as timestamp in ms
* @param {Boolean} [updateItem=FALSE] - Mark attachment item as unsynced
*/
setSyncedModificationTime: Zotero.Promise.coroutine(function* (itemID, mtime, updateItem) {
mtime = parseInt(mtime)
if (isNaN(mtime) || mtime < 0) {
Components.utils.reportError("Invalid file mod time " + mtime
+ " in Zotero.Storage.setSyncedModificationTime()");
mtime = 0;
}
Zotero.DB.requireTransaction();
var sql = "UPDATE itemAttachments SET storageModTime=? WHERE itemID=?";
yield Zotero.DB.queryAsync(sql, [mtime, itemID]);
if (updateItem) {
let item = yield Zotero.Items.getAsync(itemID);
yield item.updateSynced(false);
}
}),
/**
* @param {Integer} itemID
* @return {Promise<String|null|false>} - File hash, null if never synced, if false if
* file doesn't exist
*/
getSyncedHash: Zotero.Promise.coroutine(function* (itemID) {
var sql = "SELECT storageHash FROM itemAttachments WHERE itemID=?";
var hash = yield Zotero.DB.valueQueryAsync(sql, itemID);
if (hash === false) {
throw new Error("Item " + itemID + " not found");
}
return hash;
}),
/**
* @param {Integer} itemID
* @param {String} hash File hash
* @param {Boolean} [updateItem=FALSE] - Mark attachment item as unsynced
*/
setSyncedHash: Zotero.Promise.coroutine(function* (itemID, hash, updateItem) {
if (hash !== null && hash.length != 32) {
throw new Error("Invalid file hash '" + hash + "'");
}
Zotero.DB.requireTransaction();
var sql = "UPDATE itemAttachments SET storageHash=? WHERE itemID=?";
yield Zotero.DB.queryAsync(sql, [hash, itemID]);
if (updateItem) {
let item = yield Zotero.Items.getAsync(itemID);
yield item.updateSynced(false);
}
sql = "UPDATE itemAttachments SET syncState=? WHERE itemID IN (" + sql + ")";
yield Zotero.DB.queryAsync(sql, [this.SYNC_STATE_TO_UPLOAD].concat(params));
}),
@ -678,11 +577,10 @@ Zotero.Sync.Storage.Local = {
// Set the file mtime to the time from the server
yield OS.File.setDates(path, null, new Date(parseInt(mtime)));
yield Zotero.DB.executeTransaction(function* () {
yield this.setSyncedHash(item.id, md5);
yield this.setSyncState(item.id, this.SYNC_STATE_IN_SYNC);
yield this.setSyncedModificationTime(item.id, mtime);
}.bind(this));
item.attachmentSyncedModificationTime = mtime;
item.attachmentSyncedHash = md5;
item.attachmentSyncState = "in_sync";
yield item.saveTx({ skipAll: true });
return new Zotero.Sync.Storage.Result({
localChanges: true
@ -1040,7 +938,7 @@ Zotero.Sync.Storage.Local = {
for (let localItem of localItems) {
// Use the mtime for the dateModified field, since that's all that's shown in the
// CR window at the moment
let localItemJSON = yield localItem.toJSON();
let localItemJSON = localItem.toJSON();
localItemJSON.dateModified = Zotero.Date.dateToISO(
new Date(yield localItem.attachmentModificationTime)
);
@ -1101,8 +999,9 @@ Zotero.Sync.Storage.Local = {
else {
syncState = this.SYNC_STATE_FORCE_DOWNLOAD;
}
let itemID = Zotero.Items.getIDFromLibraryAndKey(libraryID, conflict.left.key);
yield Zotero.Sync.Storage.Local.setSyncState(itemID, syncState);
let item = Zotero.Items.getByLibraryAndKey(libraryID, conflict.left.key);
item.attachmentSyncState = syncState;
yield item.save({ skipAll: true });
}
}.bind(this));
return true;

View File

@ -156,6 +156,8 @@ Zotero.Sync.Storage.StreamListener.prototype = {
if (!result) {
oldChannel.cancel(Components.results.NS_BINDING_ABORTED);
newChannel.cancel(Components.results.NS_BINDING_ABORTED);
Zotero.debug("Cancelling redirect");
// TODO: Prevent onStateChange error
return false;
}
}

View File

@ -288,15 +288,14 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
Zotero.debug("File mod time matches remote file -- skipping download of "
+ item.libraryKey);
yield Zotero.DB.executeTransaction(function* () {
var syncState = Zotero.Sync.Storage.Local.getSyncState(item.id);
// DEBUG: Necessary to update item?
var updateItem = syncState != 1;
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(
item.id, metadata.mtime, updateItem
);
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync");
});
var updateItem = item.attachmentSyncState != 1
item.attachmentSyncedModificationTime = metadata.mtime;
item.attachmentSyncState = "in_sync";
yield item.saveTx({ skipAll: true });
// DEBUG: Necessary?
if (updateItem) {
yield item.updateSynced(false);
}
return new Zotero.Sync.Storage.Result({
localChanges: true, // ?
@ -416,7 +415,7 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
}
// Check if file already exists on WebDAV server
if ((yield Zotero.Sync.Storage.Local.getSyncState(item.id))
if (item.attachmentSyncState
!= Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_UPLOAD) {
if (metadata.mtime) {
// Local file time
@ -438,15 +437,14 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
// If WebDAV server already has file, update synced properties
if (!changed) {
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(
item.id, fmtime, true
);
if (hash) {
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, hash);
}
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync");
});
item.attachmentSyncedModificationTime = fmtime;
if (hash) {
item.attachmentSyncedHash = hash;
}
item.attachmentSyncState = "in_sync";
yield item.saveTx({ skipAll: true });
// skipAll doesn't mark as unsynced, so do that separately
yield item.updateSynced(false);
return new Zotero.Sync.Storage.Result;
}
}
@ -460,9 +458,9 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
// API would ever be updated with the correct values, so we can't just wait for
// the API to change.) If a conflict is found, we flag the item as in conflict
// and require another file sync, which will trigger conflict resolution.
let smtime = yield Zotero.Sync.Storage.Local.getSyncedModificationTime(item.id);
let smtime = item.attachmentSyncedModificationTime;
if (smtime != mtime) {
let shash = yield Zotero.Sync.Storage.Local.getSyncedHash(item.id);
let shash = item.attachmentSyncedHash;
if (shash && metadata.md5 && shash == metadata.md5) {
Zotero.debug("Last synced mod time for item " + item.libraryKey
+ " doesn't match time on storage server but hash does -- ignoring");
@ -472,12 +470,13 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
Zotero.logError("Conflict -- last synced file mod time for item "
+ item.libraryKey + " does not match time on storage server"
+ " (" + smtime + " != " + mtime + ")");
yield Zotero.DB.executeTransaction(function* () {
// Conflict resolution uses the synced mtime as the remote value, so set
// that to the WebDAV value, since that's the one in conflict.
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, mtime);
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_conflict");
});
// Conflict resolution uses the synced mtime as the remote value, so set
// that to the WebDAV value, since that's the one in conflict.
item.attachmentSyncedModificationTime = mtime;
item.attachmentSyncState = "in_conflict";
yield item.saveTx({ skipAll: true });
return new Zotero.Sync.Storage.Result({
fileSyncRequired: true
});
@ -1073,7 +1072,7 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
response, ".//D:getlastmodified", { D: 'DAV:' }
);
lastModified = Zotero.Date.strToISO(lastModified);
lastModified = Zotero.Date.sqlToDate(lastModified);
lastModified = Zotero.Date.sqlToDate(lastModified, true);
// Delete files older than a day before last sync time
var days = (lastSyncDate - lastModified) / 1000 / 60 / 60 / 24;
@ -1191,7 +1190,10 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
throw new Error(Zotero.Sync.Storage.Mode.WebDAV.defaultError);
}
return { mtime, md5 };
return {
mtime: parseInt(mtime),
md5
};
}),
@ -1243,11 +1245,12 @@ Zotero.Sync.Storage.Mode.WebDAV.prototype = {
// Update .prop file on WebDAV server
yield this._setStorageFileMetadata(item);
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync");
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, params.mtime, true);
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, params.md5);
});
item.attachmentSyncedModificationTime = params.mtime;
item.attachmentSyncedHash = params.md5;
item.attachmentSyncState = "in_sync";
yield item.saveTx({ skipAll: true });
// skipAll doesn't mark as unsynced, so do that separately
yield item.updateSynced(false);
try {
yield OS.File.remove(

View File

@ -99,7 +99,7 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = {
var header;
try {
header = "Zotero-File-Modification-Time";
requestData.mtime = oldChannel.getResponseHeader(header);
requestData.mtime = parseInt(oldChannel.getResponseHeader(header));
header = "Zotero-File-MD5";
requestData.md5 = oldChannel.getResponseHeader(header);
header = "Zotero-File-Compressed";
@ -131,15 +131,18 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = {
}
// Update local metadata and stop request, skipping file download
yield Zotero.DB.executeTransaction(function* () {
if (updateHash) {
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, requestData.md5);
}
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(
item.id, requestData.mtime
);
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync");
});
yield OS.File.setDates(path, null, new Date(requestData.mtime));
item.attachmentSyncedModificationTime = requestData.mtime;
if (updateHash) {
item.attachmentSyncedHash = requestData.md5;
}
item.attachmentSyncState = "in_sync";
yield item.saveTx({ skipAll: true });
deferred.resolve(new Zotero.Sync.Storage.Result({
localChanges: true
}));
return false;
}),
onProgress: function (a, b, c) {
@ -261,7 +264,7 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = {
var sql = "SELECT value FROM settings WHERE setting=? AND key=?";
var values = yield Zotero.DB.columnQueryAsync(sql, ['storage', 'zfsPurge']);
if (!values) {
if (!values.length) {
return false;
}
@ -353,7 +356,7 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = {
var headers = {
"Content-Type": "application/x-www-form-urlencoded"
};
var storedHash = yield Zotero.Sync.Storage.Local.getSyncedHash(item.id);
var storedHash = item.attachmentSyncedHash;
//var storedModTime = yield Zotero.Sync.Storage.getSyncedModificationTime(item.id);
if (storedHash) {
headers["If-Match"] = storedHash;
@ -538,17 +541,17 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = {
Zotero.debug(fileHash);
if (json.data.md5 == fileHash) {
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(
item.id, fileModTime
);
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, fileHash);
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync");
});
item.attachmentSyncedModificationTime = fileModTime;
item.attachmentSyncedHash = fileHash;
item.attachmentSyncState = "in_sync";
yield item.saveTx({ skipAll: true });
return new Zotero.Sync.Storage.Result;
}
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_conflict");
item.attachmentSyncState = "in_conflict";
yield item.saveTx({ skipAll: true });
return new Zotero.Sync.Storage.Result({
fileSyncRequired: true
});
@ -767,11 +770,12 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = {
_updateItemFileInfo: Zotero.Promise.coroutine(function* (item, params) {
// Mark as in-sync
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync");
// Store file mod time and hash
item.attachmentSyncedModificationTime = params.mtime;
item.attachmentSyncedHash = params.md5;
item.attachmentSyncState = "in_sync";
yield item.save({ skipAll: true });
// Store file mod time and hash
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, params.mtime);
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, params.md5);
// Update sync cache with new file metadata and version from server
var json = yield Zotero.Sync.Data.Local.getCacheObject(
'item', item.libraryID, item.key, item.version
@ -933,7 +937,7 @@ Zotero.Sync.Storage.Mode.ZFS.prototype = {
}
// Check for conflict
if ((yield Zotero.Sync.Storage.Local.getSyncState(item.id))
if (item.attachmentSyncState
!= Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_UPLOAD) {
if (info) {
// Local file time

View File

@ -437,7 +437,7 @@ Zotero.Styles = new function() {
yield Zotero.File.putContentsAsync(destFile, style);
yield Zotero.Styles.reinit();
// Refresh preferences windows
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
getService(Components.interfaces.nsIWindowMediator);
@ -691,7 +691,7 @@ Zotero.Style.prototype.getCiteProc = function(locale, automaticJournalAbbreviati
}
try {
var citeproc = new Zotero.Cite.AsyncCiteProc(
var citeproc = new Zotero.CiteProc.CSL.Engine(
new Zotero.Cite.System(automaticJournalAbbreviations),
xml,
locale,
@ -832,4 +832,4 @@ Zotero.Style.prototype.remove = Zotero.Promise.coroutine(function* () {
}
return Zotero.Styles.reinit();
});
});

View File

@ -290,7 +290,7 @@ Zotero.Sync.Data.Engine.prototype._startDownload = Zotero.Promise.coroutine(func
}
if (objectType == 'setting') {
let meta = yield Zotero.SyncedSettings.getMetadata(this.libraryID, key);
let meta = Zotero.SyncedSettings.getMetadata(this.libraryID, key);
if (!meta) {
continue;
}
@ -316,7 +316,7 @@ Zotero.Sync.Data.Engine.prototype._startDownload = Zotero.Promise.coroutine(func
// Conflict resolution
else if (objectType == 'item') {
conflicts.push({
left: yield obj.toJSON(),
left: obj.toJSON(),
right: {
deleted: true
}

View File

@ -512,7 +512,7 @@ Zotero.Sync.Data.Local = {
objectType, obj.libraryID, obj.key, obj.version
);
let jsonDataLocal = yield obj.toJSON();
let jsonDataLocal = obj.toJSON();
// For items, check if mtime or file hash changed in metadata,
// which would indicate that a remote storage sync took place and
@ -780,7 +780,8 @@ Zotero.Sync.Data.Local = {
markToDownload = true;
}
if (markToDownload) {
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "to_download");
item.attachmentSyncState = "to_download";
yield item.save({ skipAll: true });
}
}),
@ -870,7 +871,6 @@ Zotero.Sync.Data.Local = {
_saveObjectFromJSON: Zotero.Promise.coroutine(function* (obj, json, options) {
try {
yield obj.loadAllData();
obj.fromJSON(json);
if (!options.saveAsChanged) {
obj.version = json.version;

View File

@ -27,6 +27,8 @@
* @namespace
*/
Zotero.SyncedSettings = (function () {
var _cache = {};
//
// Public methods
//
@ -34,47 +36,95 @@ Zotero.SyncedSettings = (function () {
idColumn: "setting",
table: "syncedSettings",
get: Zotero.Promise.coroutine(function* (libraryID, setting) {
var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
var json = yield Zotero.DB.valueQueryAsync(sql, [setting, libraryID]);
if (!json) {
return false;
loadAll: Zotero.Promise.coroutine(function* (libraryID) {
Zotero.debug("Loading synced settings for library " + libraryID);
if (!_cache[libraryID]) {
_cache[libraryID] = {};
}
return JSON.parse(json);
var invalid = [];
var sql = "SELECT setting, value, synced, version FROM syncedSettings "
+ "WHERE libraryID=?";
yield Zotero.DB.queryAsync(
sql,
libraryID,
{
onRow: function (row) {
var setting = row.getResultByIndex(0);
var value = row.getResultByIndex(1);
try {
value = JSON.parse(value);
}
catch (e) {
invalid.push([libraryID, setting]);
return;
}
_cache[libraryID][setting] = {
value,
synced: !!row.getResultByIndex(2),
version: row.getResultByIndex(3)
};
}
}
);
// TODO: Delete invalid settings
}),
/**
* Return settings object
*
* @return {Object|null}
*/
get: function (libraryID, setting) {
if (!_cache[libraryID]) {
throw new Zotero.Exception.UnloadedDataException(
"Synced settings not loaded for library " + libraryID,
"syncedSettings"
);
}
if (!_cache[libraryID][setting]) {
return null;
}
return JSON.parse(JSON.stringify(_cache[libraryID][setting].value));
},
/**
* Used by sync and tests
*
* @return {Object} - Object with 'synced' and 'version' properties
*/
getMetadata: Zotero.Promise.coroutine(function* (libraryID, setting) {
var sql = "SELECT * FROM syncedSettings WHERE setting=? AND libraryID=?";
var row = yield Zotero.DB.rowQueryAsync(sql, [setting, libraryID]);
if (!row) {
return false;
getMetadata: function (libraryID, setting) {
if (!_cache[libraryID]) {
throw new Zotero.Exception.UnloadedDataException(
"Synced settings not loaded for library " + libraryID,
"syncedSettings"
);
}
var o = _cache[libraryID][setting];
if (!o) {
return null;
}
return {
synced: !!row.synced,
version: row.version
synced: o.synced,
version: o.version
};
}),
},
set: Zotero.Promise.coroutine(function* (libraryID, setting, value, version = 0, synced) {
if (typeof value == undefined) {
throw new Error("Value not provided");
}
// TODO: get rid of this once we have proper affected rows handling
var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
var currentValue = yield Zotero.DB.valueQueryAsync(sql, [setting, libraryID]);
// Make sure we can tell the difference between a
// missing setting (FALSE as returned by valueQuery())
// and a FALSE setting (FALSE as returned by JSON.parse())
var hasCurrentValue = currentValue !== false;
currentValue = JSON.parse(currentValue);
var currentValue = this.get(libraryID, setting);
var hasCurrentValue = currentValue !== null;
// Value hasn't changed
if (value === currentValue) {
@ -93,7 +143,7 @@ Zotero.SyncedSettings = (function () {
};
}
if (currentValue === false) {
if (!hasCurrentValue) {
var event = 'add';
var extraData = {};
}
@ -102,6 +152,7 @@ Zotero.SyncedSettings = (function () {
}
synced = synced ? 1 : 0;
version = parseInt(version);
if (hasCurrentValue) {
var sql = "UPDATE syncedSettings SET value=?, version=?, synced=? "
@ -117,6 +168,13 @@ Zotero.SyncedSettings = (function () {
sql, [setting, libraryID, JSON.stringify(value), version, synced]
);
}
_cache[libraryID][setting] = {
value,
synced: !!synced,
version
}
yield Zotero.Notifier.trigger(event, 'setting', [id], extraData);
return true;
}),
@ -124,22 +182,16 @@ Zotero.SyncedSettings = (function () {
clear: Zotero.Promise.coroutine(function* (libraryID, setting, options) {
options = options || {};
// TODO: get rid of this once we have proper affected rows handling
var sql = "SELECT value FROM syncedSettings WHERE setting=? AND libraryID=?";
var currentValue = yield Zotero.DB.valueQueryAsync(sql, [setting, libraryID]);
if (currentValue === false) {
return false;
}
currentValue = JSON.parse(currentValue);
var currentValue = this.get(libraryID, setting);
var hasCurrentValue = currentValue !== null;
var id = libraryID + '/' + setting;
var extraData = {};
extraData[id] = {
changed: {}
};
extraData[id].changed = {
value: currentValue
changed: {
value: currentValue
}
};
if (options.skipDeleteLog) {
extraData[id].skipDeleteLog = true;
@ -148,6 +200,8 @@ Zotero.SyncedSettings = (function () {
var sql = "DELETE FROM syncedSettings WHERE setting=? AND libraryID=?";
yield Zotero.DB.queryAsync(sql, [setting, libraryID]);
delete _cache[libraryID][setting];
yield Zotero.Notifier.trigger('delete', 'setting', [id], extraData);
return true;
})

View File

@ -31,7 +31,6 @@ Zotero.Timeline = {
yield '<data>\n';
for (let i=0; i<items.length; i++) {
let item = items[i];
yield item.loadItemData();
var date = item.getField(dateType, true, true);
if (date) {
let sqlDate = (dateType == 'date') ? Zotero.Date.multipartToSQL(date) : date;

View File

@ -659,7 +659,6 @@ Zotero.Translate.ItemGetter.prototype = {
"setCollection": Zotero.Promise.coroutine(function* (collection, getChildCollections) {
// get items in this collection
yield collection.loadChildItems();
var items = new Set(collection.getChildItems());
if(getChildCollections) {
@ -668,7 +667,6 @@ Zotero.Translate.ItemGetter.prototype = {
// get items in child collections
for (let collection of this._collectionsLeft) {
yield collection.loadChildItems();
var childItems = collection.getChildItems();
childItems.forEach(item => items.add(item));
}
@ -720,7 +718,7 @@ Zotero.Translate.ItemGetter.prototype = {
* Converts an attachment to array format and copies it to the export folder if desired
*/
"_attachmentToArray":Zotero.Promise.coroutine(function* (attachment) {
var attachmentArray = yield Zotero.Utilities.Internal.itemToExportFormat(attachment, this.legacy);
var attachmentArray = Zotero.Utilities.Internal.itemToExportFormat(attachment, this.legacy);
var linkMode = attachment.attachmentLinkMode;
if(linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
var attachFile = attachment.getFile();
@ -864,13 +862,13 @@ Zotero.Translate.ItemGetter.prototype = {
var returnItemArray = yield this._attachmentToArray(returnItem);
if(returnItemArray) return returnItemArray;
} else {
var returnItemArray = yield Zotero.Utilities.Internal.itemToExportFormat(returnItem, this.legacy);
var returnItemArray = Zotero.Utilities.Internal.itemToExportFormat(returnItem, this.legacy);
// get attachments, although only urls will be passed if exportFileData is off
returnItemArray.attachments = [];
var attachments = returnItem.getAttachments();
for each(var attachmentID in attachments) {
var attachment = yield Zotero.Items.getAsync(attachmentID);
var attachment = Zotero.Items.get(attachmentID);
var attachmentInfo = yield this._attachmentToArray(attachment);
if(attachmentInfo) {

View File

@ -1591,8 +1591,9 @@ Zotero.Utilities = {
*/
"itemToCSLJSON":function(zoteroItem) {
if (zoteroItem instanceof Zotero.Item) {
return Zotero.Utilities.Internal.itemToExportFormat(zoteroItem).
then(Zotero.Utilities.itemToCSLJSON);
return this.itemToCSLJSON(
Zotero.Utilities.Internal.itemToExportFormat(zoteroItem)
);
}
var cslType = CSL_TYPE_MAPPINGS[zoteroItem.itemType];

View File

@ -610,44 +610,7 @@ Zotero.Utilities.Internal = {
* @param {Boolean} legacy Add mappings for legacy (pre-4.0.27) translators
* @return {Promise<Object>}
*/
"itemToExportFormat": new function() {
return Zotero.Promise.coroutine(function* (zoteroItem, legacy) {
var item = yield zoteroItem.toJSON();
item.uri = Zotero.URI.getItemURI(zoteroItem);
delete item.key;
if (!zoteroItem.isAttachment() && !zoteroItem.isNote()) {
yield zoteroItem.loadChildItems();
// Include attachments
item.attachments = [];
let attachments = zoteroItem.getAttachments();
for (let i=0; i<attachments.length; i++) {
let zoteroAttachment = yield Zotero.Items.getAsync(attachments[i]),
attachment = yield zoteroAttachment.toJSON();
if (legacy) addCompatibilityMappings(attachment, zoteroAttachment);
item.attachments.push(attachment);
}
// Include notes
item.notes = [];
let notes = zoteroItem.getNotes();
for (let i=0; i<notes.length; i++) {
let zoteroNote = yield Zotero.Items.getAsync(notes[i]),
note = yield zoteroNote.toJSON();
if (legacy) addCompatibilityMappings(note, zoteroNote);
item.notes.push(note);
}
}
if (legacy) addCompatibilityMappings(item, zoteroItem);
return item;
});
itemToExportFormat: function (zoteroItem, legacy) {
function addCompatibilityMappings(item, zoteroItem) {
item.uniqueFields = {};
@ -735,6 +698,39 @@ Zotero.Utilities.Internal = {
return item;
}
var item = zoteroItem.toJSON();
item.uri = Zotero.URI.getItemURI(zoteroItem);
delete item.key;
if (!zoteroItem.isAttachment() && !zoteroItem.isNote()) {
// Include attachments
item.attachments = [];
let attachments = zoteroItem.getAttachments();
for (let i=0; i<attachments.length; i++) {
let zoteroAttachment = Zotero.Items.get(attachments[i]),
attachment = zoteroAttachment.toJSON();
if (legacy) addCompatibilityMappings(attachment, zoteroAttachment);
item.attachments.push(attachment);
}
// Include notes
item.notes = [];
let notes = zoteroItem.getNotes();
for (let i=0; i<notes.length; i++) {
let zoteroNote = Zotero.Items.get(notes[i]),
note = zoteroNote.toJSON();
if (legacy) addCompatibilityMappings(note, zoteroNote);
item.notes.push(note);
}
}
if (legacy) addCompatibilityMappings(item, zoteroItem);
return item;
},
/**

View File

@ -621,11 +621,18 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
// Initialize Locate Manager
Zotero.LocateManager.init();
Zotero.Collections.init();
Zotero.Items.init();
yield Zotero.Collections.init();
yield Zotero.Items.init();
yield Zotero.Searches.init();
yield Zotero.Creators.init();
yield Zotero.Groups.init();
let libraryIDs = Zotero.Libraries.getAll().map(x => x.libraryID);
for (let libraryID of libraryIDs) {
let library = Zotero.Libraries.get(libraryID);
yield library.loadAllDataTypes();
}
yield Zotero.QuickCopy.init();
Zotero.Items.startEmptyTrashTimer();

View File

@ -860,7 +860,7 @@ var ZoteroPane = new function()
});
this.setVirtual = function (libraryID, mode, show) {
this.setVirtual = Zotero.Promise.coroutine(function* (libraryID, mode, show) {
switch (mode) {
case 'duplicates':
var prefKey = 'duplicateLibraries';
@ -873,7 +873,7 @@ var ZoteroPane = new function()
break;
default:
throw ("Invalid virtual mode '" + mode + "' in ZoteroPane.setVirtual()");
throw new Error("Invalid virtual mode '" + mode + "'");
}
try {
@ -883,10 +883,6 @@ var ZoteroPane = new function()
var ids = [];
}
if (!libraryID) {
libraryID = Zotero.Libraries.userLibraryID;
}
var newids = [];
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
@ -898,8 +894,8 @@ var ZoteroPane = new function()
if (id == libraryID && !show) {
continue;
}
// Remove libraryIDs that no longer exist
if (id != 0 && !Zotero.Libraries.exists(id)) {
// Remove libraries that no longer exist
if (!Zotero.Libraries.exists(id)) {
continue;
}
newids.push(id);
@ -914,22 +910,19 @@ var ZoteroPane = new function()
Zotero.Prefs.set(prefKey, newids.join());
this.collectionsView.refresh();
// If group is closed, open it
this.collectionsView.selectLibrary(libraryID);
row = this.collectionsView.selection.currentIndex;
if (!this.collectionsView.isContainerOpen(row)) {
this.collectionsView.toggleOpenState(row);
}
yield this.collectionsView.refresh();
// Select new row
if (show) {
Zotero.Prefs.set('lastViewedFolder', lastViewedFolderID);
var row = this.collectionsView.getLastViewedRow();
this.collectionsView.selection.select(row);
yield this.collectionsView.selectByID(lastViewedFolderID);
}
}
// Select library root when hiding
else {
yield this.collectionsView.selectLibrary(libraryID);
}
this.collectionsView.selection.selectEventsSuppressed = false;
});
this.openLookupWindow = Zotero.Promise.coroutine(function* () {
@ -1294,7 +1287,6 @@ var ZoteroPane = new function()
var clearUndo = noteEditor.item ? noteEditor.item.id != item.id : false;
noteEditor.parent = null;
yield item.loadNote();
noteEditor.item = item;
// If loading new or different note, disable undo while we repopulate the text field
@ -1325,8 +1317,6 @@ var ZoteroPane = new function()
else if (item.isAttachment()) {
var attachmentBox = document.getElementById('zotero-attachment-box');
attachmentBox.mode = this.collectionsView.editable ? 'edit' : 'view';
yield item.loadItemData();
yield item.loadNote();
attachmentBox.item = item;
document.getElementById('zotero-item-pane-content').selectedIndex = 3;
@ -1588,7 +1578,7 @@ var ZoteroPane = new function()
var newItem;
yield Zotero.DB.executeTransaction(function* () {
newItem = yield item.clone(null, !Zotero.Prefs.get('groups.copyTags'));
newItem = item.clone(null, !Zotero.Prefs.get('groups.copyTags'));
yield newItem.save();
if (self.collectionsView.selectedTreeRow.isCollection() && newItem.isTopLevelItem()) {
@ -3641,7 +3631,6 @@ var ZoteroPane = new function()
// Fall back to first attachment link
if (!uri) {
yield item.loadChildItems();
let attachmentID = item.getAttachments()[0];
if (attachmentID) {
let attachment = yield Zotero.Items.getAsync(attachmentID);
@ -3851,7 +3840,7 @@ var ZoteroPane = new function()
});
this.showPublicationsWizard = Zotero.Promise.coroutine(function* (items) {
this.showPublicationsWizard = function (items) {
var io = {
hasFiles: false,
hasNotes: false,
@ -3863,14 +3852,12 @@ var ZoteroPane = new function()
for (let i = 0; i < items.length; i++) {
let item = items[i];
yield item.loadItemData();
yield item.loadChildItems();
// Files
if (!io.hasFiles && item.numAttachments()) {
let attachments = item.getAttachments();
attachments = yield Zotero.Items.getAsync(attachments);
io.hasFiles = attachments.some(attachment => attachment.isFileAttachment());
let attachmentIDs = item.getAttachments();
io.hasFiles = Zotero.Items.get(attachmentIDs).some(
attachment => attachment.isFileAttachment()
);
}
// Notes
if (!io.hasNotes && item.numNotes()) {
@ -3887,7 +3874,7 @@ var ZoteroPane = new function()
io.hasRights = allItemsHaveRights ? 'all' : (noItemsHaveRights ? 'none' : 'some');
window.openDialog('chrome://zotero/content/publicationsDialog.xul','','chrome,modal', io);
return io.license ? io : false;
});
};
/**

View File

@ -214,7 +214,7 @@ function ZoteroProtocolHandler() {
else if (combineChildItems || !results[i].isRegularItem()
|| results[i].numChildren() == 0) {
itemsHash[results[i].id] = [items.length];
items.push(yield results[i].toJSON({ mode: 'full' }));
items.push(results[i].toJSON({ mode: 'full' }));
// Flag item as a search match
items[items.length - 1].reportSearchMatch = true;
}
@ -241,7 +241,6 @@ function ZoteroProtocolHandler() {
}
}
};
yield item.loadChildItems();
func(item.getNotes());
func(item.getAttachments());
}
@ -252,7 +251,7 @@ function ZoteroProtocolHandler() {
else {
for (var i in unhandledParents) {
itemsHash[results[i].id] = [items.length];
items.push(yield results[i].toJSON({ mode: 'full' }));
items.push(results[i].toJSON({ mode: 'full' }));
// Flag item as a search match
items[items.length - 1].reportSearchMatch = true;
}
@ -264,7 +263,7 @@ function ZoteroProtocolHandler() {
if (!searchItemIDs[id] && !itemsHash[id]) {
var item = yield Zotero.Items.getAsync(id);
itemsHash[id] = items.length;
items.push(yield item.toJSON({ mode: 'full' }));
items.push(item.toJSON({ mode: 'full' }));
}
}
@ -279,10 +278,10 @@ function ZoteroProtocolHandler() {
};
}
if (item.isNote()) {
items[itemsHash[parentID]].reportChildren.notes.push(yield item.toJSON({ mode: 'full' }));
items[itemsHash[parentID]].reportChildren.notes.push(item.toJSON({ mode: 'full' }));
}
if (item.isAttachment()) {
items[itemsHash[parentID]].reportChildren.attachments.push(yield item.toJSON({ mode: 'full' }));
items[itemsHash[parentID]].reportChildren.attachments.push(item.toJSON({ mode: 'full' }));
}
}
}
@ -299,7 +298,7 @@ function ZoteroProtocolHandler() {
// add on its own
if (searchItemIDs[parentID]) {
itemsHash[parentID] = [items.length];
items.push(yield parentItem.toJSON({ mode: 'full' }));
items.push(parentItem.toJSON({ mode: 'full' }));
items[items.length - 1].reportSearchMatch = true;
}
else {
@ -312,14 +311,14 @@ function ZoteroProtocolHandler() {
items.push(parentItem.toJSON({ mode: 'full' }));
if (item.isNote()) {
items[items.length - 1].reportChildren = {
notes: [yield item.toJSON({ mode: 'full' })],
notes: [item.toJSON({ mode: 'full' })],
attachments: []
};
}
else if (item.isAttachment()) {
items[items.length - 1].reportChildren = {
notes: [],
attachments: [yield item.toJSON({ mode: 'full' })]
attachments: [item.toJSON({ mode: 'full' })]
};
}
}
@ -609,7 +608,6 @@ function ZoteroProtocolHandler() {
if (params.controller == 'data') {
switch (params.scopeObject) {
case 'collections':
yield collection.loadChildItems();
var results = collection.getChildItems();
break;

View File

@ -137,6 +137,8 @@ function Reporter(runner) {
dump("\r" + indentStr
// Dark red X for errors
+ "\033[31;40m" + Mocha.reporters.Base.symbols.err + " [FAIL]\033[0m"
// Trigger bell if interactive
+ (Zotero.noUserInput ? "" : "\007")
+ " " + test.title + "\n"
+ indentStr + " " + err.toString() + " at\n"
+ err.stack.replace(/^/gm, indentStr + " "));

View File

@ -251,6 +251,33 @@ function waitForCallback(cb, interval, timeout) {
}
function clickOnItemsRow(itemsView, row, button = 0) {
var x = {};
var y = {};
var width = {};
var height = {};
itemsView._treebox.getCoordsForCellItem(
row,
itemsView._treebox.columns.getNamedColumn('zotero-items-column-title'),
'text',
x, y, width, height
);
// Select row to trigger multi-select
var tree = itemsView._treebox.treeBody;
var rect = tree.getBoundingClientRect();
var x = rect.left + x.value;
var y = rect.top + y.value;
tree.dispatchEvent(new MouseEvent("mousedown", {
clientX: x,
clientY: y,
button,
detail: 1
}));
}
/**
* Get a default group used by all tests that want one, creating one if necessary
*/
@ -352,10 +379,9 @@ function getNameProperty(objectType) {
return objectType == 'item' ? 'title' : 'name';
}
var modifyDataObject = Zotero.Promise.coroutine(function* (obj, params = {}, saveOptions) {
var modifyDataObject = function (obj, params = {}, saveOptions) {
switch (obj.objectType) {
case 'item':
yield obj.loadItemData();
obj.setField(
'title',
params.title !== undefined ? params.title : Zotero.Utilities.randomString()
@ -366,7 +392,7 @@ var modifyDataObject = Zotero.Promise.coroutine(function* (obj, params = {}, sav
obj.name = params.name !== undefined ? params.name : Zotero.Utilities.randomString();
}
return obj.saveTx(saveOptions);
});
};
/**
* Return a promise for the error thrown by a promise, or false if none
@ -584,7 +610,7 @@ var generateItemJSONData = Zotero.Promise.coroutine(function* generateItemJSONDa
for (let itemName in items) {
let zItem = yield Zotero.Items.getAsync(items[itemName].id);
jsonData[itemName] = yield zItem.toJSON(options);
jsonData[itemName] = zItem.toJSON(options);
// Don't replace some fields that _always_ change (e.g. item keys)
// as long as it follows expected format

@ -1 +1 @@
Subproject commit b369f252432c3486a66a0e93f441e4abb133d229
Subproject commit 775281e138df26101fba1e554c516f47438851b5

@ -1 +1 @@
Subproject commit 2a8594424c73ffeca41ef1668446372160528b4a
Subproject commit 44b0045463907b1d7963a2e9560c24d9552aac5d

View File

@ -152,7 +152,6 @@ describe("Zotero.Collection", function() {
var collection2 = yield createDataObject('collection', { parentID: collection1.id });
yield collection1.saveTx();
yield collection1.loadChildCollections();
var childCollections = collection1.getChildCollections();
assert.lengthOf(childCollections, 1);
assert.equal(childCollections[0].id, collection2.id);
@ -163,8 +162,6 @@ describe("Zotero.Collection", function() {
var collection2 = yield createDataObject('collection', { parentID: collection1.id });
yield collection1.saveTx();
yield collection1.loadChildCollections();
collection2.parentID = false;
yield collection2.save()
@ -180,7 +177,6 @@ describe("Zotero.Collection", function() {
item.addToCollection(collection.key);
yield item.saveTx();
yield collection.loadChildItems();
assert.lengthOf(collection.getChildItems(), 1);
})
@ -191,7 +187,6 @@ describe("Zotero.Collection", function() {
item.addToCollection(collection.key);
yield item.saveTx();
yield collection.loadChildItems();
assert.lengthOf(collection.getChildItems(), 0);
})
@ -202,7 +197,6 @@ describe("Zotero.Collection", function() {
item.addToCollection(collection.key);
yield item.saveTx();
yield collection.loadChildItems();
assert.lengthOf(collection.getChildItems(false, true), 1);
})
})

View File

@ -1,12 +1,13 @@
"use strict";
describe("Zotero.CollectionTreeView", function() {
var win, zp, cv;
var win, zp, cv, userLibraryID;
before(function* () {
win = yield loadZoteroPane();
zp = win.ZoteroPane;
cv = zp.collectionsView;
userLibraryID = Zotero.Libraries.userLibraryID;
});
beforeEach(function () {
// TODO: Add a selectCollection() function and select a collection instead?
@ -16,31 +17,52 @@ describe("Zotero.CollectionTreeView", function() {
win.close();
});
describe("#refresh()", function () {
it("should show Duplicate Items and Unfiled Items in My Library by default", function* () {
Zotero.Prefs.clear('duplicateLibraries');
Zotero.Prefs.clear('unfiledLibraries');
yield cv.refresh();
assert.ok(cv.getRowIndexByID("D" + userLibraryID));
assert.ok(cv.getRowIndexByID("U" + userLibraryID));
assert.equal(Zotero.Prefs.get('duplicateLibraries'), "" + userLibraryID);
assert.equal(Zotero.Prefs.get('unfiledLibraries'), "" + userLibraryID);
});
it("shouldn't show Duplicate Items and Unfiled Items if hidden", function* () {
Zotero.Prefs.set('duplicateLibraries', "");
Zotero.Prefs.set('unfiledLibraries', "");
yield cv.refresh();
assert.isFalse(cv.getRowIndexByID("D" + userLibraryID));
assert.isFalse(cv.getRowIndexByID("U" + userLibraryID));
assert.strictEqual(Zotero.Prefs.get('duplicateLibraries'), "");
assert.strictEqual(Zotero.Prefs.get('unfiledLibraries'), "");
});
});
describe("collapse/expand", function () {
it("should close and open My Library repeatedly", function* () {
var libraryID = Zotero.Libraries.userLibraryID;
yield cv.selectLibrary(libraryID);
yield cv.selectLibrary(userLibraryID);
var row = cv.selection.currentIndex;
cv.collapseLibrary(libraryID);
cv.collapseLibrary(userLibraryID);
var nextRow = cv.getRow(row + 1);
assert.equal(cv.selection.currentIndex, row);
assert.ok(nextRow.isSeparator());
assert.isFalse(cv.isContainerOpen(row));
yield cv.expandLibrary(libraryID);
yield cv.expandLibrary(userLibraryID);
nextRow = cv.getRow(row + 1);
assert.equal(cv.selection.currentIndex, row);
assert.ok(!nextRow.isSeparator());
assert.ok(cv.isContainerOpen(row));
cv.collapseLibrary(libraryID);
cv.collapseLibrary(userLibraryID);
nextRow = cv.getRow(row + 1);
assert.equal(cv.selection.currentIndex, row);
assert.ok(nextRow.isSeparator());
assert.isFalse(cv.isContainerOpen(row));
yield cv.expandLibrary(libraryID);
yield cv.expandLibrary(userLibraryID);
nextRow = cv.getRow(row + 1);
assert.equal(cv.selection.currentIndex, row);
assert.ok(!nextRow.isSeparator());
@ -74,13 +96,13 @@ describe("Zotero.CollectionTreeView", function() {
var row = cv.selection.currentIndex;
var treeRow = cv.getRow(row);
assert.ok(treeRow.isTrash());
assert.equal(treeRow.ref.libraryID, Zotero.Libraries.userLibraryID);
assert.equal(treeRow.ref.libraryID, userLibraryID);
})
})
describe("#selectWait()", function () {
it("shouldn't hang if row is already selected", function* () {
var row = cv.getRowIndexByID("T" + Zotero.Libraries.userLibraryID);
var row = cv.getRowIndexByID("T" + userLibraryID);
cv.selection.select(row);
yield Zotero.Promise.delay(50);
yield cv.selectWait(row);
@ -108,7 +130,7 @@ describe("Zotero.CollectionTreeView", function() {
});
// Library should still be selected
assert.equal(cv.getSelectedLibraryID(), Zotero.Libraries.userLibraryID);
assert.equal(cv.getSelectedLibraryID(), userLibraryID);
});
it("shouldn't select a new collection if skipSelect is passed", function* () {
@ -120,7 +142,7 @@ describe("Zotero.CollectionTreeView", function() {
});
// Library should still be selected
assert.equal(cv.getSelectedLibraryID(), Zotero.Libraries.userLibraryID);
assert.equal(cv.getSelectedLibraryID(), userLibraryID);
});
it("shouldn't select a modified collection", function* () {
@ -135,7 +157,7 @@ describe("Zotero.CollectionTreeView", function() {
yield collection.saveTx();
// Modified collection should not be selected
assert.equal(cv.getSelectedLibraryID(), Zotero.Libraries.userLibraryID);
assert.equal(cv.getSelectedLibraryID(), userLibraryID);
});
it("should maintain selection on a selected modified collection", function* () {
@ -217,8 +239,8 @@ describe("Zotero.CollectionTreeView", function() {
var collectionRow = cv._rowMap["C" + collectionID];
var searchRow = cv._rowMap["S" + searchID];
var duplicatesRow = cv._rowMap["D" + Zotero.Libraries.userLibraryID];
var unfiledRow = cv._rowMap["U" + Zotero.Libraries.userLibraryID];
var duplicatesRow = cv._rowMap["D" + userLibraryID];
var unfiledRow = cv._rowMap["U" + userLibraryID];
assert.isAbove(searchRow, collectionRow);
// If there's a duplicates row or an unfiled row, add before those.
@ -230,7 +252,7 @@ describe("Zotero.CollectionTreeView", function() {
assert.isBelow(searchRow, unfiledRow);
}
else {
var trashRow = cv._rowMap["T" + Zotero.Libraries.userLibraryID];
var trashRow = cv._rowMap["T" + userLibraryID];
assert.isBelow(searchRow, trashRow);
}
})
@ -238,7 +260,7 @@ describe("Zotero.CollectionTreeView", function() {
it("shouldn't select a new group", function* () {
var group = yield createGroup();
// Library should still be selected
assert.equal(cv.getSelectedLibraryID(), Zotero.Libraries.userLibraryID);
assert.equal(cv.getSelectedLibraryID(), userLibraryID);
})
it("should remove a group and all children", function* () {
@ -390,12 +412,6 @@ describe("Zotero.CollectionTreeView", function() {
parentItemID: item.id
});
// Hack to unload relations to test proper loading
//
// Probably need a better method for this
item._loaded.relations = false;
attachment._loaded.relations = false;
var ids = (yield drop('item', 'L' + group.libraryID, [item.id])).ids;
yield cv.selectLibrary(group.libraryID);
@ -413,7 +429,7 @@ describe("Zotero.CollectionTreeView", function() {
// Check attachment
assert.isTrue(itemsView.isContainer(0));
yield itemsView.toggleOpenState(0);
itemsView.toggleOpenState(0);
assert.equal(itemsView.rowCount, 2);
treeRow = itemsView.getRow(1);
assert.equal(treeRow.ref.id, ids[1]);

View File

@ -0,0 +1,21 @@
"use strict";
describe("Zotero.Creators", function() {
describe("#getIDFromData()", function () {
it("should create creator and cache data", function* () {
var data1 = {
firstName: "First",
lastName: "Last"
};
var creatorID;
yield Zotero.DB.executeTransaction(function* () {
creatorID = yield Zotero.Creators.getIDFromData(data1, true);
});
assert.typeOf(creatorID, 'number');
var data2 = Zotero.Creators.get(creatorID);
assert.isObject(data2);
assert.propertyVal(data2, "firstName", data1.firstName);
assert.propertyVal(data2, "lastName", data1.lastName);
});
});
});

View File

@ -56,7 +56,6 @@ describe("Zotero.DataObject", function() {
yield obj.saveTx();
if (type == 'item') {
yield obj.loadItemData();
obj.setField('title', Zotero.Utilities.randomString());
}
else {
@ -131,7 +130,6 @@ describe("Zotero.DataObject", function() {
yield obj.saveTx();
if (type == 'item') {
yield obj.loadItemData();
obj.setField('title', Zotero.Utilities.randomString());
}
else {
@ -294,7 +292,7 @@ describe("Zotero.DataObject", function() {
let obj = yield createDataObject(type);
let libraryID = obj.libraryID;
let key = obj.key;
let json = yield obj.toJSON();
let json = obj.toJSON();
yield Zotero.Sync.Data.Local.saveCacheObjects(type, libraryID, [json]);
yield obj.eraseTx();
let versions = yield Zotero.Sync.Data.Local.getCacheObjectVersions(

View File

@ -25,11 +25,11 @@ describe("Zotero.DataObjectUtilities", function() {
yield Zotero.DB.executeTransaction(function* () {
var item = new Zotero.Item('book');
id1 = yield item.save();
json1 = yield item.toJSON();
json1 = item.toJSON();
var item = new Zotero.Item('book');
id2 = yield item.save();
json2 = yield item.toJSON();
json2 = item.toJSON();
});
var changes = Zotero.DataObjectUtilities.diff(json1, json2);

View File

@ -1,4 +1,33 @@
describe("Zotero.Date", function() {
describe("#sqlToDate()", function () {
it("should convert an SQL local date into a JS Date object", function* () {
var d1 = new Date();
var sqlDate = d1.getFullYear()
+ '-'
+ Zotero.Utilities.lpad(d1.getMonth() + 1, '0', 2)
+ '-'
+ Zotero.Utilities.lpad(d1.getDate(), '0', 2)
+ ' '
+ Zotero.Utilities.lpad(d1.getHours(), '0', 2)
+ ':'
+ Zotero.Utilities.lpad(d1.getMinutes(), '0', 2)
+ ':'
+ Zotero.Utilities.lpad(d1.getSeconds(), '0', 2);
var offset = d1.getTimezoneOffset() * 60 * 1000;
var d2 = Zotero.Date.sqlToDate(sqlDate);
assert.equal(
Zotero.Date.sqlToDate(sqlDate).getTime(),
Math.floor(new Date().getTime() / 1000) * 1000
);
})
it("should convert an SQL UTC date into a JS Date object", function* () {
var date = "2016-02-27 22:00:00";
date = Zotero.Date.sqlToDate(date, true);
assert.equal(date.getTime(), 1456610400000);
})
})
describe("#isISODate()", function () {
it("should determine whether a date is an ISO 8601 date", function () {
assert.ok(Zotero.Date.isISODate("2015"));

View File

@ -0,0 +1,56 @@
"use strict";
describe("Duplicate Items", function () {
var win, zp, cv;
beforeEach(function* () {
Zotero.Prefs.clear('duplicateLibraries');
win = yield loadZoteroPane();
zp = win.ZoteroPane;
cv = zp.collectionsView;
return selectLibrary(win);
})
after(function () {
if (win) {
win.close();
}
});
describe("Merging", function () {
it("should merge two items in duplicates view", function* () {
var item1 = yield createDataObject('item', { setTitle: true });
var item2 = item1.clone();
yield item2.saveTx();
var uri2 = Zotero.URI.getItemURI(item2);
var userLibraryID = Zotero.Libraries.userLibraryID;
var selected = yield cv.selectByID('D' + userLibraryID);
assert.ok(selected);
yield waitForItemsLoad(win);
// Select the first item, which should select both
var iv = zp.itemsView;
var row = iv.getRowIndexByID(item1.id);
assert.isNumber(row);
clickOnItemsRow(iv, row);
assert.equal(iv.selection.count, 2);
// Click merge button
var button = win.document.getElementById('zotero-duplicates-merge-button');
button.click();
yield waitForNotifierEvent('refresh', 'trash');
// Items should be gone
assert.isFalse(iv.getRowIndexByID(item1.id));
assert.isFalse(iv.getRowIndexByID(item2.id));
assert.isTrue(item2.deleted);
var rels = item1.getRelations();
var pred = Zotero.Relations.replacedItemPredicate;
assert.property(rels, pred);
assert.equal(rels[pred], uri2);
});
});
});

View File

@ -21,7 +21,7 @@ describe("Zotero_File_Interface", function() {
let childItems = importedCollection[0].getChildItems();
let savedItems = {};
for (let i=0; i<childItems.length; i++) {
let savedItem = yield childItems[i].toJSON();
let savedItem = childItems[i].toJSON();
delete savedItem.dateAdded;
delete savedItem.dateModified;
delete savedItem.key;

View File

@ -135,13 +135,52 @@ describe("Zotero.Item", function () {
item = yield Zotero.Items.getAsync(id);
assert.equal(item.getField("versionNumber"), "1.0");
});
it("should accept ISO 8601 dates", function* () {
var fields = {
accessDate: "2015-06-07T20:56:00Z",
dateAdded: "2015-06-07T20:57:00Z",
dateModified: "2015-06-07T20:58:00Z",
};
var item = createUnsavedDataObject('item');
for (let i in fields) {
item.setField(i, fields[i]);
}
assert.equal(item.getField('accessDate'), '2015-06-07 20:56:00');
assert.equal(item.dateAdded, '2015-06-07 20:57:00');
assert.equal(item.dateModified, '2015-06-07 20:58:00');
})
it("should accept SQL dates", function* () {
var fields = {
accessDate: "2015-06-07 20:56:00",
dateAdded: "2015-06-07 20:57:00",
dateModified: "2015-06-07 20:58:00",
};
var item = createUnsavedDataObject('item');
for (let i in fields) {
item.setField(i, fields[i]);
item.getField(i, fields[i]);
}
})
it("should ignore unknown accessDate values", function* () {
var fields = {
accessDate: "foo"
};
var item = createUnsavedDataObject('item');
for (let i in fields) {
item.setField(i, fields[i]);
}
assert.strictEqual(item.getField('accessDate'), '');
})
})
describe("#dateAdded", function () {
it("should use current time if value was not given for a new item", function* () {
var item = new Zotero.Item('book');
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
item = Zotero.Items.get(id);
assert.closeTo(Zotero.Date.sqlToDate(item.dateAdded, true).getTime(), Date.now(), 2000);
})
@ -184,10 +223,9 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item('book');
item.dateModified = dateModified;
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
item = Zotero.Items.get(id);
// Save again without changing Date Modified
yield item.loadItemData();
item.setField('title', 'Test');
yield item.saveTx()
@ -199,10 +237,9 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item('book');
item.dateModified = dateModified;
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
item = Zotero.Items.get(id);
// Set Date Modified to existing value
yield item.loadItemData();
item.setField('title', 'Test');
item.dateModified = dateModified;
yield item.saveTx()
@ -223,10 +260,9 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item('book');
item.dateModified = dateModified;
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
item = Zotero.Items.get(id);
// Resave with skipDateModifiedUpdate
yield item.loadItemData();
item.setField('title', 'Test');
yield item.saveTx({
skipDateModifiedUpdate: true
@ -353,8 +389,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item("journalArticle");
item.setCreators(creators);
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadCreators();
item = Zotero.Items.get(id);
assert.sameDeepMembers(item.getCreatorsJSON(), creators);
})
@ -377,8 +412,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item("journalArticle");
item.setCreators(creators);
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadCreators();
item = Zotero.Items.get(id);
assert.sameDeepMembers(item.getCreators(), creators);
})
})
@ -614,11 +648,8 @@ describe("Zotero.Item", function () {
// File should be flagged for upload
// DEBUG: Is this necessary?
assert.equal(
(yield Zotero.Sync.Storage.Local.getSyncState(item.id)),
Zotero.Sync.Storage.Local.SYNC_STATE_TO_UPLOAD
);
assert.isNull(yield Zotero.Sync.Storage.Local.getSyncedHash(item.id));
assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_UPLOAD);
assert.isNull(item.attachmentSyncedHash);
})
})
@ -686,8 +717,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item('journalArticle');
item.setTags(tags);
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadTags();
item = Zotero.Items.get(id);
assert.sameDeepMembers(item.getTags(tags), tags);
})
@ -703,8 +733,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item('journalArticle');
item.setTags(tags);
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadTags();
item = Zotero.Items.get(id);
item.setTags(tags);
assert.isFalse(item.hasChanged());
})
@ -721,8 +750,7 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item('journalArticle');
item.setTags(tags);
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
yield item.loadTags();
item = Zotero.Items.get(id);
item.setTags(tags.slice(0));
yield item.saveTx();
assert.sameDeepMembers(item.getTags(tags), tags.slice(0));
@ -825,6 +853,38 @@ describe("Zotero.Item", function () {
})
})
describe("#multiDiff", function () {
it("should return set of alternatives for differing fields in other items", function* () {
var type = 'item';
var dates = ['2016-03-08 17:44:45'];
var accessDates = ['2016-03-08T18:44:45Z'];
var urls = ['http://www.example.com', 'http://example.net'];
var obj1 = createUnsavedDataObject(type);
obj1.setField('date', '2016-03-07 12:34:56'); // different in 1 and 3, not in 2
obj1.setField('url', 'http://example.com'); // different in all three
obj1.setField('title', 'Test'); // only in 1
var obj2 = createUnsavedDataObject(type);
obj2.setField('url', urls[0]);
obj2.setField('accessDate', accessDates[0]); // only in 2
var obj3 = createUnsavedDataObject(type);
obj3.setField('date', dates[0]);
obj3.setField('url', urls[1]);
var alternatives = obj1.multiDiff([obj2, obj3]);
assert.sameMembers(Object.keys(alternatives), ['url', 'date', 'accessDate']);
assert.sameMembers(alternatives.url, urls);
assert.sameMembers(alternatives.date, dates);
assert.sameMembers(alternatives.accessDate, accessDates);
});
});
describe("#clone()", function () {
// TODO: Expand to other data
it("should copy creators", function* () {
@ -837,7 +897,7 @@ describe("Zotero.Item", function () {
}
]);
yield item.saveTx();
var newItem = yield item.clone();
var newItem = item.clone();
assert.sameDeepMembers(item.getCreators(), newItem.getCreators());
})
})
@ -851,8 +911,8 @@ describe("Zotero.Item", function () {
var item = new Zotero.Item(itemType);
item.setField("title", title);
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
var json = yield item.toJSON();
item = Zotero.Items.get(id);
var json = item.toJSON();
assert.equal(json.itemType, itemType);
assert.equal(json.title, title);
@ -868,13 +928,13 @@ describe("Zotero.Item", function () {
item.setField("title", title);
item.deleted = true;
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
var json = yield item.toJSON();
item = Zotero.Items.get(id);
var json = item.toJSON();
assert.strictEqual(json.deleted, 1);
})
it("should output attachment fields from file", function* () {
it.skip("should output attachment fields from file", function* () {
var file = getTestDataDirectory();
file.append('test.png');
var item = yield Zotero.Attachments.importFromFile({ file });
@ -888,7 +948,7 @@ describe("Zotero.Item", function () {
);
});
var json = yield item.toJSON();
var json = item.toJSON();
assert.equal(json.linkMode, 'imported_file');
assert.equal(json.filename, 'test.png');
assert.isUndefined(json.path);
@ -905,24 +965,23 @@ describe("Zotero.Item", function () {
var mtime = new Date().getTime();
var md5 = 'b32e33f529942d73bea4ed112310f804';
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, mtime);
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, md5);
});
item.attachmentSyncedModificationTime = mtime;
item.attachmentSyncedHash = md5;
yield item.saveTx({ skipAll: true });
var json = yield item.toJSON({
var json = item.toJSON({
syncedStorageProperties: true
});
assert.equal(json.mtime, mtime);
assert.equal(json.md5, md5);
})
it("should output unset storage properties as null", function* () {
it.skip("should output unset storage properties as null", function* () {
var item = new Zotero.Item('attachment');
item.attachmentLinkMode = 'imported_file';
item.fileName = 'test.txt';
var id = yield item.saveTx();
var json = yield item.toJSON();
var json = item.toJSON();
assert.isNull(json.mtime);
assert.isNull(json.md5);
@ -938,7 +997,7 @@ describe("Zotero.Item", function () {
item.setField("title", title);
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
var json = yield item.toJSON({ mode: 'full' });
var json = item.toJSON({ mode: 'full' });
assert.equal(json.title, title);
assert.equal(json.date, "");
assert.equal(json.numPages, "");
@ -955,11 +1014,11 @@ describe("Zotero.Item", function () {
item.setField("title", title);
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
var patchBase = yield item.toJSON();
var patchBase = item.toJSON();
item.setField("date", date);
yield item.saveTx();
var json = yield item.toJSON({
var json = item.toJSON({
patchBase: patchBase
})
assert.isUndefined(json.itemType);
@ -978,10 +1037,10 @@ describe("Zotero.Item", function () {
item.deleted = true;
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
var patchBase = yield item.toJSON();
var patchBase = item.toJSON();
item.deleted = false;
var json = yield item.toJSON({
var json = item.toJSON({
patchBase: patchBase
})
assert.isUndefined(json.title);
@ -992,10 +1051,10 @@ describe("Zotero.Item", function () {
item.deleted = false;
var id = yield item.saveTx();
item = yield Zotero.Items.getAsync(id);
var patchBase = yield item.toJSON();
var patchBase = item.toJSON();
item.deleted = true;
var json = yield item.toJSON({
var json = item.toJSON({
patchBase: patchBase
})
assert.isUndefined(json.title);

View File

@ -1,31 +1,31 @@
"use strict";
describe("Zotero.ItemTreeView", function() {
var win, zp, itemsView, existingItemID;
var win, zp, cv, itemsView, existingItemID;
// Load Zotero pane and select library
before(function* () {
win = yield loadZoteroPane();
zp = win.ZoteroPane;
cv = zp.collectionsView;
var item = new Zotero.Item('book');
existingItemID = yield item.saveTx();
});
beforeEach(function* () {
yield zp.collectionsView.selectLibrary();
yield waitForItemsLoad(win)
yield selectLibrary(win);
itemsView = zp.itemsView;
})
after(function () {
win.close();
});
it("shouldn't show items in trash", function* () {
it("shouldn't show items in trash in library root", function* () {
var item = yield createDataObject('item', { title: "foo" });
var itemID = item.id;
item.deleted = true;
yield item.saveTx();
assert.notOk(itemsView.getRowIndexByID(itemID));
assert.isFalse(itemsView.getRowIndexByID(itemID));
})
describe("#selectItem()", function () {
@ -45,6 +45,17 @@ describe("Zotero.ItemTreeView", function() {
});
})
describe("#getCellText()", function () {
it("should return new value after edit", function* () {
var str = Zotero.Utilities.randomString();
var item = yield createDataObject('item', { title: str });
var row = itemsView.getRowIndexByID(item.id);
assert.equal(itemsView.getCellText(row, { id: 'zotero-items-column-title' }), str);
yield modifyDataObject(item);
assert.notEqual(itemsView.getCellText(row, { id: 'zotero-items-column-title' }), str);
})
})
describe("#notify()", function () {
beforeEach(function () {
sinon.spy(win.ZoteroPane, "itemSelected");
@ -220,6 +231,22 @@ describe("Zotero.ItemTreeView", function() {
yield Zotero.Items.erase(items.map(item => item.id));
})
it("should remove items from Unfiled Items when added to a collection", function* () {
var userLibraryID = Zotero.Libraries.userLibraryID;
var collection = yield createDataObject('collection');
var item = yield createDataObject('item', { title: "Unfiled Item" });
yield zp.setVirtual(userLibraryID, 'unfiled', true);
var selected = yield cv.selectByID("U" + userLibraryID);
assert.ok(selected);
yield waitForItemsLoad(win);
assert.isNumber(zp.itemsView.getRowIndexByID(item.id));
yield Zotero.DB.executeTransaction(function* () {
yield collection.addItem(item.id);
});
assert.isFalse(zp.itemsView.getRowIndexByID(item.id));
});
})
describe("#drop()", function () {

View File

@ -150,6 +150,11 @@ describe("Zotero.Library", function() {
yield library.saveTx();
assert.isFalse(Zotero.Libraries.isEditable(library.libraryID));
});
it("should initialize library after creation", function* () {
let library = yield createGroup({});
Zotero.SyncedSettings.get(library.libraryID, "tagColors");
});
});
describe("#erase()", function() {
it("should erase a group library", function* () {

View File

@ -148,7 +148,7 @@ describe("Sync Preferences", function () {
var cont = yield win.Zotero_Preferences.Sync.checkUser(1, "A");
assert.isTrue(cont);
var json = yield item1.toJSON();
var json = item1.toJSON();
var uri = json.relations[Zotero.Relations.linkedObjectPredicate][0];
assert.notInclude(uri, 'users/local');
assert.include(uri, 'users/1/publications');

View File

@ -71,12 +71,10 @@ describe("Related Box", function () {
var item1 = yield createDataObject('item');
var item2 = yield createDataObject('item');
yield item1.loadRelations();
item1.addRelatedItem(item2);
yield item1.save();
yield item2.loadRelations();
yield item1.saveTx();
item2.addRelatedItem(item1);
yield item2.save();
yield item2.saveTx();
// Select the Related pane
var tabbox = doc.getElementById('zotero-view-tabbox');

View File

@ -14,16 +14,20 @@ describe("Zotero.Search", function() {
var s = new Zotero.Search;
s.name = "Test";
s.addCondition('title', 'is', 'test');
Zotero.debug("BEFORE SAVING");
Zotero.debug(s._conditions);
var id = yield s.saveTx();
Zotero.debug("DONE SAVING");
Zotero.debug(s._conditions);
assert.typeOf(id, 'number');
// Check saved search
s = yield Zotero.Searches.getAsync(id);
s = Zotero.Searches.get(id);
assert.ok(s);
assert.instanceOf(s, Zotero.Search);
assert.equal(s.libraryID, Zotero.Libraries.userLibraryID);
assert.equal(s.name, "Test");
yield s.loadConditions();
Zotero.debug("GETTING CONDITIONS");
var conditions = s.getConditions();
assert.lengthOf(Object.keys(conditions), 1);
assert.property(conditions, "0");
@ -45,14 +49,12 @@ describe("Zotero.Search", function() {
// Add condition
s = yield Zotero.Searches.getAsync(id);
yield s.loadConditions();
s.addCondition('title', 'contains', 'foo');
var saved = yield s.saveTx();
assert.isTrue(saved);
// Check saved search
s = yield Zotero.Searches.getAsync(id);
yield s.loadConditions();
var conditions = s.getConditions();
assert.lengthOf(Object.keys(conditions), 2);
});
@ -69,14 +71,12 @@ describe("Zotero.Search", function() {
// Remove condition
s = yield Zotero.Searches.getAsync(id);
yield s.loadConditions();
s.removeCondition(0);
var saved = yield s.saveTx();
assert.isTrue(saved);
// Check saved search
s = yield Zotero.Searches.getAsync(id);
yield s.loadConditions();
var conditions = s.getConditions();
assert.lengthOf(Object.keys(conditions), 1);
assert.property(conditions, "0");

View File

@ -28,11 +28,10 @@ describe("Zotero.Sync.Storage.Local", function () {
yield OS.File.setDates((yield item.getFilePathAsync()), null, mtime);
// Mark as synced, so it will be checked
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, hash);
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, mtime);
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync");
});
item.attachmentSyncedModificationTime = mtime;
item.attachmentSyncedHash = hash;
item.attachmentSyncState = "in_sync";
yield item.saveTx({ skipAll: true });
// Update mtime and contents
var path = yield item.getFilePathAsync();
@ -46,10 +45,7 @@ describe("Zotero.Sync.Storage.Local", function () {
yield item.eraseTx();
assert.equal(changed, true);
assert.equal(
(yield Zotero.Sync.Storage.Local.getSyncState(item.id)),
Zotero.Sync.Storage.Local.SYNC_STATE_TO_UPLOAD
);
assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_UPLOAD);
})
it("should skip a file if mod time hasn't changed", function* () {
@ -59,15 +55,14 @@ describe("Zotero.Sync.Storage.Local", function () {
var mtime = yield item.attachmentModificationTime;
// Mark as synced, so it will be checked
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, hash);
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, mtime);
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync");
});
item.attachmentSyncedModificationTime = mtime;
item.attachmentSyncedHash = hash;
item.attachmentSyncState = "in_sync";
yield item.saveTx({ skipAll: true });
var libraryID = Zotero.Libraries.userLibraryID;
var changed = yield Zotero.Sync.Storage.Local.checkForUpdatedFiles(libraryID);
var syncState = yield Zotero.Sync.Storage.Local.getSyncState(item.id);
var syncState = item.attachmentSyncState;
yield item.eraseTx();
@ -84,11 +79,10 @@ describe("Zotero.Sync.Storage.Local", function () {
yield OS.File.setDates((yield item.getFilePathAsync()), null, mtime);
// Mark as synced, so it will be checked
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, hash);
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, mtime);
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync");
});
item.attachmentSyncedModificationTime = mtime;
item.attachmentSyncedHash = hash;
item.attachmentSyncState = "in_sync";
yield item.saveTx({ skipAll: true });
// Update mtime, but not contents
var path = yield item.getFilePathAsync();
@ -96,8 +90,8 @@ describe("Zotero.Sync.Storage.Local", function () {
var libraryID = Zotero.Libraries.userLibraryID;
var changed = yield Zotero.Sync.Storage.Local.checkForUpdatedFiles(libraryID);
var syncState = yield Zotero.Sync.Storage.Local.getSyncState(item.id);
var syncedModTime = yield Zotero.Sync.Storage.Local.getSyncedModificationTime(item.id);
var syncState = item.attachmentSyncState;
var syncedModTime = item.attachmentSyncedModificationTime;
var newModTime = yield item.attachmentModificationTime;
yield item.eraseTx();
@ -202,8 +196,8 @@ describe("Zotero.Sync.Storage.Local", function () {
item3.version = 11;
yield item3.saveTx();
var json1 = yield item1.toJSON();
var json3 = yield item3.toJSON();
var json1 = item1.toJSON();
var json3 = item3.toJSON();
// Change remote mtimes
// Round to nearest second because OS X doesn't support ms resolution
var now = Math.round(new Date().getTime() / 1000) * 1000;
@ -211,8 +205,10 @@ describe("Zotero.Sync.Storage.Local", function () {
json3.mtime = now - 20000;
yield Zotero.Sync.Data.Local.saveCacheObjects('item', libraryID, [json1, json3]);
yield Zotero.Sync.Storage.Local.setSyncState(item1.id, "in_conflict");
yield Zotero.Sync.Storage.Local.setSyncState(item3.id, "in_conflict");
item1.attachmentSyncState = "in_conflict";
yield item1.saveTx({ skipAll: true });
item3.attachmentSyncState = "in_conflict";
yield item3.saveTx({ skipAll: true });
var conflicts = yield Zotero.Sync.Storage.Local.getConflicts(libraryID);
assert.lengthOf(conflicts, 2);
@ -251,19 +247,17 @@ describe("Zotero.Sync.Storage.Local", function () {
item3.version = 11;
yield item3.saveTx();
var json1 = yield item1.toJSON();
var json3 = yield item3.toJSON();
var json1 = item1.toJSON();
var json3 = item3.toJSON();
// Change remote mtimes
json1.mtime = new Date().getTime() + 10000;
json3.mtime = new Date().getTime() - 10000;
yield Zotero.Sync.Data.Local.saveCacheObjects('item', libraryID, [json1, json3]);
yield Zotero.Sync.Storage.Local.setSyncState(
item1.id, "in_conflict"
);
yield Zotero.Sync.Storage.Local.setSyncState(
item3.id, "in_conflict"
);
item1.attachmentSyncState = "in_conflict";
yield item1.saveTx({ skipAll: true });
item3.attachmentSyncState = "in_conflict";
yield item3.saveTx({ skipAll: true });
var promise = waitForWindow('chrome://zotero/content/merge.xul', function (dialog) {
var doc = dialog.document;
@ -305,14 +299,8 @@ describe("Zotero.Sync.Storage.Local", function () {
yield Zotero.Sync.Storage.Local.resolveConflicts(libraryID);
yield promise;
yield assert.eventually.equal(
Zotero.Sync.Storage.Local.getSyncState(item1.id),
Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_UPLOAD
);
yield assert.eventually.equal(
Zotero.Sync.Storage.Local.getSyncState(item3.id),
Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_DOWNLOAD
);
assert.equal(item1.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_UPLOAD);
assert.equal(item3.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_FORCE_DOWNLOAD);
})
})

View File

@ -239,10 +239,10 @@ describe("Zotero.Sync.Data.Engine", function () {
assert.equal(Zotero.Libraries.getVersion(userLibraryID), 3);
// Make sure local objects exist
var setting = yield Zotero.SyncedSettings.get(userLibraryID, "tagColors");
var setting = Zotero.SyncedSettings.get(userLibraryID, "tagColors");
assert.lengthOf(setting, 1);
assert.equal(setting[0].name, 'A');
var settingMetadata = yield Zotero.SyncedSettings.getMetadata(userLibraryID, "tagColors");
var settingMetadata = Zotero.SyncedSettings.getMetadata(userLibraryID, "tagColors");
assert.equal(settingMetadata.version, 2);
assert.isTrue(settingMetadata.synced);
@ -283,7 +283,7 @@ describe("Zotero.Sync.Data.Engine", function () {
for (let type of types) {
objects[type] = [yield createDataObject(type, { setTitle: true })];
objectVersions[type] = {};
objectResponseJSON[type] = yield Zotero.Promise.all(objects[type].map(o => o.toResponseJSON()));
objectResponseJSON[type] = objects[type].map(o => o.toResponseJSON());
}
server.respond(function (req) {
@ -457,12 +457,11 @@ describe("Zotero.Sync.Data.Engine", function () {
var mtime = new Date().getTime();
var md5 = '57f8a4fda823187b91e1191487b87fe6';
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, mtime);
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, md5);
});
item.attachmentSyncedModificationTime = mtime;
item.attachmentSyncedHash = md5;
yield item.saveTx({ skipAll: true });
var itemResponseJSON = yield item.toResponseJSON();
var itemResponseJSON = item.toResponseJSON();
itemResponseJSON.version = itemResponseJSON.data.version = lastLibraryVersion;
itemResponseJSON.data.mtime = mtime;
itemResponseJSON.data.md5 = md5;
@ -520,7 +519,7 @@ describe("Zotero.Sync.Data.Engine", function () {
for (let type of types) {
objects[type] = [yield createDataObject(type, { setTitle: true })];
objectNames[type] = {};
objectResponseJSON[type] = yield Zotero.Promise.all(objects[type].map(o => o.toResponseJSON()));
objectResponseJSON[type] = objects[type].map(o => o.toResponseJSON());
}
server.respond(function (req) {
@ -569,7 +568,6 @@ describe("Zotero.Sync.Data.Engine", function () {
let version = o.version;
let name = objectNames[type][key];
if (type == 'item') {
yield o.loadItemData();
assert.equal(name, o.getField('title'));
}
else {
@ -675,7 +673,7 @@ describe("Zotero.Sync.Data.Engine", function () {
{
key: obj.key,
version: obj.version,
data: (yield obj.toJSON())
data: obj.toJSON()
}
]
);
@ -814,7 +812,7 @@ describe("Zotero.Sync.Data.Engine", function () {
yield engine._startDownload();
// Make sure objects were deleted
assert.isFalse(yield Zotero.SyncedSettings.get(userLibraryID, 'tagColors'));
assert.isNull(Zotero.SyncedSettings.get(userLibraryID, 'tagColors'));
assert.isFalse(Zotero.Collections.exists(collectionID));
assert.isFalse(Zotero.Searches.exists(searchID));
assert.isFalse(Zotero.Items.exists(itemID));
@ -903,7 +901,7 @@ describe("Zotero.Sync.Data.Engine", function () {
yield engine._startDownload();
// Make sure objects weren't deleted
assert.ok(yield Zotero.SyncedSettings.get(userLibraryID, 'tagColors'));
assert.ok(Zotero.SyncedSettings.get(userLibraryID, 'tagColors'));
assert.ok(Zotero.Collections.exists(collectionID));
assert.ok(Zotero.Searches.exists(searchID));
})
@ -1214,10 +1212,10 @@ describe("Zotero.Sync.Data.Engine", function () {
yield engine._fullSync();
// Check settings
var setting = yield Zotero.SyncedSettings.get(userLibraryID, "tagColors");
var setting = Zotero.SyncedSettings.get(userLibraryID, "tagColors");
assert.lengthOf(setting, 1);
assert.equal(setting[0].name, 'A');
var settingMetadata = yield Zotero.SyncedSettings.getMetadata(userLibraryID, "tagColors");
var settingMetadata = Zotero.SyncedSettings.getMetadata(userLibraryID, "tagColors");
assert.equal(settingMetadata.version, 2);
assert.isTrue(settingMetadata.synced);

View File

@ -105,7 +105,7 @@ describe("Zotero.Sync.Data.Local", function() {
for (let type of types) {
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
let obj = yield createDataObject(type);
let data = yield obj.toJSON();
let data = obj.toJSON();
data.key = obj.key;
data.version = 10;
let json = {
@ -130,7 +130,7 @@ describe("Zotero.Sync.Data.Local", function() {
var type = 'item';
let obj = yield createDataObject(type, { version: 5 });
let data = yield obj.toJSON();
let data = obj.toJSON();
yield Zotero.Sync.Data.Local.saveCacheObjects(
type, libraryID, [data]
);
@ -165,7 +165,7 @@ describe("Zotero.Sync.Data.Local", function() {
for (let type of types) {
let obj = yield createDataObject(type, { version: 5 });
let data = yield obj.toJSON();
let data = obj.toJSON();
yield Zotero.Sync.Data.Local.saveCacheObjects(
type, libraryID, [data]
);
@ -175,7 +175,7 @@ describe("Zotero.Sync.Data.Local", function() {
let objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(type);
let obj = yield createDataObject(type, { version: 10 });
let data = yield obj.toJSON();
let data = obj.toJSON();
yield Zotero.Sync.Data.Local.saveCacheObjects(
type, libraryID, [data]
);
@ -222,11 +222,8 @@ describe("Zotero.Sync.Data.Local", function() {
yield Zotero.Sync.Data.Local.processSyncCacheForObjectType(
libraryID, 'item', { stopOnError: true }
);
var id = Zotero.Items.getIDFromLibraryAndKey(libraryID, key);
assert.equal(
(yield Zotero.Sync.Storage.Local.getSyncState(id)),
Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD
);
var item = Zotero.Items.getByLibraryAndKey(libraryID, key);
assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD);
})
it("should mark updated attachment items for download", function* () {
@ -239,18 +236,13 @@ describe("Zotero.Sync.Data.Local", function() {
yield item.saveTx();
// Set file as synced
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(
item.id, (yield item.attachmentModificationTime)
);
yield Zotero.Sync.Storage.Local.setSyncedHash(
item.id, (yield item.attachmentHash)
);
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync");
});
item.attachmentSyncedModificationTime = yield item.attachmentModificationTime;
item.attachmentSyncedHash = yield item.attachmentHash;
item.attachmentSyncState = "in_sync";
yield item.saveTx({ skipAll: true });
// Simulate download of version with updated attachment
var json = yield item.toResponseJSON();
var json = item.toResponseJSON();
json.version = 10;
json.data.version = 10;
json.data.md5 = '57f8a4fda823187b91e1191487b87fe6';
@ -263,10 +255,7 @@ describe("Zotero.Sync.Data.Local", function() {
libraryID, 'item', { stopOnError: true }
);
assert.equal(
(yield Zotero.Sync.Storage.Local.getSyncState(item.id)),
Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD
);
assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD);
})
it("should ignore attachment metadata when resolving metadata conflict", function* () {
@ -276,19 +265,14 @@ describe("Zotero.Sync.Data.Local", function() {
var item = yield importFileAttachment('test.png');
item.version = 5;
yield item.saveTx();
var json = yield item.toResponseJSON();
var json = item.toResponseJSON();
yield Zotero.Sync.Data.Local.saveCacheObjects('item', libraryID, [json]);
// Set file as synced
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(
item.id, (yield item.attachmentModificationTime)
);
yield Zotero.Sync.Storage.Local.setSyncedHash(
item.id, (yield item.attachmentHash)
);
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "in_sync");
});
item.attachmentSyncedModificationTime = yield item.attachmentModificationTime;
item.attachmentSyncedHash = yield item.attachmentHash;
item.attachmentSyncState = "in_sync";
yield item.saveTx({ skipAll: true });
// Modify title locally, leaving item unsynced
var newTitle = Zotero.Utilities.randomString();
@ -307,10 +291,7 @@ describe("Zotero.Sync.Data.Local", function() {
);
assert.equal(item.getField('title'), newTitle);
assert.equal(
(yield Zotero.Sync.Storage.Local.getSyncState(item.id)),
Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD
);
assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_TO_DOWNLOAD);
})
})
@ -348,7 +329,7 @@ describe("Zotero.Sync.Data.Local", function() {
)
}
);
let jsonData = yield obj.toJSON();
let jsonData = obj.toJSON();
jsonData.key = obj.key;
jsonData.version = 10;
let json = {
@ -426,7 +407,7 @@ describe("Zotero.Sync.Data.Local", function() {
)
}
);
let jsonData = yield obj.toJSON();
let jsonData = obj.toJSON();
jsonData.key = obj.key;
jsonData.version = 10;
let json = {
@ -496,7 +477,7 @@ describe("Zotero.Sync.Data.Local", function() {
// Create object, generate JSON, and delete
var obj = yield createDataObject(type, { version: 10 });
var jsonData = yield obj.toJSON();
var jsonData = obj.toJSON();
var key = jsonData.key = obj.key;
jsonData.version = 10;
let json = {
@ -544,7 +525,7 @@ describe("Zotero.Sync.Data.Local", function() {
// Create object, generate JSON, and delete
var obj = yield createDataObject(type, { version: 10 });
var jsonData = yield obj.toJSON();
var jsonData = obj.toJSON();
var key = jsonData.key = obj.key;
jsonData.version = 10;
let json = {
@ -576,7 +557,6 @@ describe("Zotero.Sync.Data.Local", function() {
obj = objectsClass.getByLibraryAndKey(libraryID, key);
assert.ok(obj);
yield obj.loadItemData();
assert.equal(obj.getField('title'), jsonData.title);
})
@ -594,7 +574,7 @@ describe("Zotero.Sync.Data.Local", function() {
obj.setNote("");
obj.version = 10;
yield obj.saveTx();
var jsonData = yield obj.toJSON();
var jsonData = obj.toJSON();
var key = jsonData.key = obj.key;
let json = {
key: obj.key,
@ -626,7 +606,6 @@ describe("Zotero.Sync.Data.Local", function() {
obj = objectsClass.getByLibraryAndKey(libraryID, key);
assert.ok(obj);
yield obj.loadNote();
assert.equal(obj.getNote(), noteText2);
})
})

View File

@ -4,7 +4,7 @@ describe("Tag Selector", function () {
var win, doc, collectionsView;
var clearTagColors = Zotero.Promise.coroutine(function* (libraryID) {
var tagColors = yield Zotero.Tags.getColors(libraryID);
var tagColors = Zotero.Tags.getColors(libraryID);
for (let name of tagColors.keys()) {
yield Zotero.Tags.setColor(libraryID, name, false);
}
@ -155,6 +155,18 @@ describe("Tag Selector", function () {
assert.equal(getRegularTags().length, 1);
})
it("should show a colored tag at the top of the list even when linked to no items", function* () {
var libraryID = Zotero.Libraries.userLibraryID;
var tagSelector = doc.getElementById('zotero-tag-selector');
var tagElems = tagSelector.id('tags-box').childNodes;
var count = tagElems.length;
yield Zotero.Tags.setColor(libraryID, "Top", '#AAAAAA');
assert.equal(tagElems.length, count + 1);
});
it("shouldn't re-insert a new tag that matches an existing color", function* () {
var libraryID = Zotero.Libraries.userLibraryID;

View File

@ -64,4 +64,38 @@ describe("Zotero.Tags", function () {
assert.isFalse(yield Zotero.Tags.getName(tagID));
})
})
describe("#setColor()", function () {
var libraryID;
before(function* () {
libraryID = Zotero.Libraries.userLibraryID;
// Clear library tag colors
var colors = Zotero.Tags.getColors(libraryID);
for (let color of colors.keys()) {
yield Zotero.Tags.setColor(libraryID, color);
}
});
it("should set color for a tag", function* () {
var aColor = '#ABCDEF';
var bColor = '#BCDEF0';
yield Zotero.Tags.setColor(libraryID, "A", aColor);
yield Zotero.Tags.setColor(libraryID, "B", bColor);
var o = Zotero.Tags.getColor(libraryID, "A")
assert.equal(o.color, aColor);
assert.equal(o.position, 0);
var o = Zotero.Tags.getColor(libraryID, "B")
assert.equal(o.color, bColor);
assert.equal(o.position, 1);
var o = Zotero.SyncedSettings.get(libraryID, 'tagColors');
assert.isArray(o);
assert.lengthOf(o, 2);
assert.sameMembers(o.map(c => c.color), [aColor, bColor]);
});
});
})

View File

@ -14,17 +14,6 @@ describe("Item Tags Box", function () {
win.close();
});
function waitForTagsBox() {
var deferred = Zotero.Promise.defer();
var tagsbox = doc.getElementById('zotero-editpane-tags');
var onRefresh = function (event) {
tagsbox.removeEventListener('refresh', onRefresh);
deferred.resolve();
}
tagsbox.addEventListener('refresh', onRefresh);
return deferred.promise;
}
describe("#notify()", function () {
it("should update an existing tag on rename", function* () {
var tag = Zotero.Utilities.randomString();
@ -43,7 +32,6 @@ describe("Item Tags Box", function () {
var tabbox = doc.getElementById('zotero-view-tabbox');
tabbox.selectedIndex = 2;
yield waitForTagsBox();
var tagsbox = doc.getElementById('zotero-editpane-tags');
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
assert.equal(rows.length, 1);
@ -77,7 +65,6 @@ describe("Item Tags Box", function () {
var tabbox = doc.getElementById('zotero-view-tabbox');
tabbox.selectedIndex = 2;
yield waitForTagsBox();
var tagsbox = doc.getElementById('zotero-editpane-tags');
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
@ -108,7 +95,6 @@ describe("Item Tags Box", function () {
var tabbox = doc.getElementById('zotero-view-tabbox');
tabbox.selectedIndex = 2;
yield waitForTagsBox();
var tagsbox = doc.getElementById('zotero-editpane-tags');
var rows = tagsbox.id('tagRows').getElementsByTagName('row');
assert.equal(rows.length, 1);

View File

@ -64,13 +64,13 @@ function saveItemsThroughTranslator(translatorType, items) {
* Convert an array of items to an object in which they are indexed by
* their display titles
*/
var itemsArrayToObject = Zotero.Promise.coroutine(function* itemsArrayToObject(items) {
function itemsArrayToObject(items) {
var obj = {};
for (let item of items) {
obj[yield item.loadDisplayTitle(true)] = item;
obj[item.getDisplayTitle()] = item;
}
return obj;
});
}
const TEST_TAGS = [
"manual tag as string",
@ -175,7 +175,7 @@ describe("Zotero.Translate", function() {
let newItems = yield saveItemsThroughTranslator("import", saveItems);
let savedItems = {};
for (let i=0; i<newItems.length; i++) {
let savedItem = yield newItems[i].toJSON();
let savedItem = newItems[i].toJSON();
savedItems[Zotero.ItemTypes.getName(newItems[i].itemTypeID)] = savedItem;
delete savedItem.dateAdded;
delete savedItem.dateModified;
@ -223,7 +223,7 @@ describe("Zotero.Translate", function() {
}
];
let newItems = yield itemsArrayToObject(yield saveItemsThroughTranslator("import", myItems));
let newItems = itemsArrayToObject(yield saveItemsThroughTranslator("import", myItems));
let noteIDs = newItems["Test Item"].getNotes();
let note1 = yield Zotero.Items.getAsync(noteIDs[0]);
assert.equal(Zotero.ItemTypes.getName(note1.itemTypeID), "note");
@ -261,7 +261,7 @@ describe("Zotero.Translate", function() {
'}'));
let newItems = yield translate.translate();
assert.equal(newItems.length, 3);
newItems = yield itemsArrayToObject(newItems);
newItems = itemsArrayToObject(newItems);
assert.equal(newItems["Not in Collection"].getCollections().length, 0);
let parentCollection = newItems["In Parent Collection"].getCollections();
@ -313,7 +313,7 @@ describe("Zotero.Translate", function() {
"attachments":childAttachments
});
let newItems = yield itemsArrayToObject(yield saveItemsThroughTranslator("import", myItems));
let newItems = itemsArrayToObject(yield saveItemsThroughTranslator("import", myItems));
let containedAttachments = yield Zotero.Items.getAsync(newItems["Container Item"].getAttachments());
assert.equal(containedAttachments.length, 3);
@ -447,7 +447,7 @@ describe("Zotero.Translate", function() {
let newItems = yield saveItemsThroughTranslator("web", myItems);
assert.equal(newItems.length, 1);
let containedAttachments = yield itemsArrayToObject(yield Zotero.Items.getAsync(newItems[0].getAttachments()));
let containedAttachments = itemsArrayToObject(yield Zotero.Items.getAsync(newItems[0].getAttachments()));
let link = containedAttachments["Link to zotero.org"];
assert.equal(link.getField("url"), "http://www.zotero.org/");

View File

@ -179,7 +179,7 @@ describe("Zotero.Utilities", function() {
let fromZoteroItem;
try {
fromZoteroItem = yield Zotero.Utilities.itemToCSLJSON(item);
fromZoteroItem = Zotero.Utilities.itemToCSLJSON(item);
} catch(e) {
assert.fail(e, null, 'accepts Zotero Item');
}
@ -190,7 +190,7 @@ describe("Zotero.Utilities", function() {
let fromExportItem;
try {
fromExportItem = Zotero.Utilities.itemToCSLJSON(
yield Zotero.Utilities.Internal.itemToExportFormat(item)
Zotero.Utilities.Internal.itemToExportFormat(item)
);
} catch(e) {
assert.fail(e, null, 'accepts Zotero export item');
@ -205,7 +205,7 @@ describe("Zotero.Utilities", function() {
note.setNote('Some note longer than 50 characters, which will become the title.');
yield note.saveTx();
let cslJSONNote = yield Zotero.Utilities.itemToCSLJSON(note);
let cslJSONNote = Zotero.Utilities.itemToCSLJSON(note);
assert.equal(cslJSONNote.type, 'article', 'note is exported as "article"');
assert.equal(cslJSONNote.title, note.getNoteTitle(), 'note title is set to Zotero pseudo-title');
}));
@ -221,7 +221,7 @@ describe("Zotero.Utilities", function() {
yield attachment.saveTx();
let cslJSONAttachment = yield Zotero.Utilities.itemToCSLJSON(attachment);
let cslJSONAttachment = Zotero.Utilities.itemToCSLJSON(attachment);
assert.equal(cslJSONAttachment.type, 'article', 'attachment is exported as "article"');
assert.equal(cslJSONAttachment.title, 'Empty', 'attachment title is correct');
assert.deepEqual(cslJSONAttachment.accessed, {"date-parts":[["2001",2,3]]}, 'attachment access date is mapped correctly');
@ -240,27 +240,27 @@ describe("Zotero.Utilities", function() {
item.setField('extra', 'PMID: 12345\nPMCID:123456');
yield item.saveTx();
let cslJSON = yield Zotero.Utilities.itemToCSLJSON(item);
let cslJSON = Zotero.Utilities.itemToCSLJSON(item);
assert.equal(cslJSON.PMID, '12345', 'PMID from Extra is mapped to PMID');
assert.equal(cslJSON.PMCID, '123456', 'PMCID from Extra is mapped to PMCID');
item.setField('extra', 'PMID: 12345');
yield item.saveTx();
cslJSON = yield Zotero.Utilities.itemToCSLJSON(item);
cslJSON = Zotero.Utilities.itemToCSLJSON(item);
assert.equal(cslJSON.PMID, '12345', 'single-line entry is extracted correctly');
item.setField('extra', 'some junk: note\nPMID: 12345\nstuff in-between\nPMCID: 123456\nlast bit of junk!');
yield item.saveTx();
cslJSON = yield Zotero.Utilities.itemToCSLJSON(item);
cslJSON = Zotero.Utilities.itemToCSLJSON(item);
assert.equal(cslJSON.PMID, '12345', 'PMID from mixed Extra field is mapped to PMID');
assert.equal(cslJSON.PMCID, '123456', 'PMCID from mixed Extra field is mapped to PMCID');
item.setField('extra', 'a\n PMID: 12345\nfoo PMCID: 123456');
yield item.saveTx();
cslJSON = yield Zotero.Utilities.itemToCSLJSON(item);
cslJSON = Zotero.Utilities.itemToCSLJSON(item);
assert.isUndefined(cslJSON.PMCID, 'field label must not be preceded by other text');
assert.isUndefined(cslJSON.PMID, 'field label must not be preceded by a space');
@ -268,7 +268,7 @@ describe("Zotero.Utilities", function() {
item.setField('extra', 'something\npmid: 12345\n');
yield item.saveTx();
cslJSON = yield Zotero.Utilities.itemToCSLJSON(item);
cslJSON = Zotero.Utilities.itemToCSLJSON(item);
assert.isUndefined(cslJSON.PMID, 'field labels are case-sensitive');
}));
@ -347,7 +347,7 @@ describe("Zotero.Utilities", function() {
});
let item = Zotero.Items.get(data.item.id);
let cslCreators = (yield Zotero.Utilities.itemToCSLJSON(item)).author;
let cslCreators = Zotero.Utilities.itemToCSLJSON(item).author;
assert.deepEqual(cslCreators[0], creators[0].expect, 'simple name is not parsed');
assert.deepEqual(cslCreators[1], creators[1].expect, 'name with dropping and non-dropping particles is parsed');
@ -359,6 +359,7 @@ describe("Zotero.Utilities", function() {
});
describe("itemFromCSLJSON", function () {
it("should stably perform itemToCSLJSON -> itemFromCSLJSON -> itemToCSLJSON", function* () {
this.timeout(10000);
let data = loadSampleData('citeProcJSExport');
for (let i in data) {
@ -368,7 +369,7 @@ describe("Zotero.Utilities", function() {
Zotero.Utilities.itemFromCSLJSON(item, json);
yield item.saveTx();
let newJSON = yield Zotero.Utilities.itemToCSLJSON(item);
let newJSON = Zotero.Utilities.itemToCSLJSON(item);
delete newJSON.id;
delete json.id;
@ -382,7 +383,7 @@ describe("Zotero.Utilities", function() {
note.setNote('Some note longer than 50 characters, which will become the title.');
yield note.saveTx();
let jsonNote = yield Zotero.Utilities.itemToCSLJSON(note);
let jsonNote = Zotero.Utilities.itemToCSLJSON(note);
let item = new Zotero.Item();
Zotero.Utilities.itemFromCSLJSON(item, jsonNote);
@ -397,7 +398,7 @@ describe("Zotero.Utilities", function() {
attachment.setNote('Note');
yield attachment.saveTx();
let jsonAttachment = yield Zotero.Utilities.itemToCSLJSON(attachment);
let jsonAttachment = Zotero.Utilities.itemToCSLJSON(attachment);
let item = new Zotero.Item();
Zotero.Utilities.itemFromCSLJSON(item, jsonAttachment);

View File

@ -186,8 +186,8 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
var item = new Zotero.Item("attachment");
item.attachmentLinkMode = 'imported_file';
item.attachmentPath = 'storage:test.txt';
item.attachmentSyncState = "to_download";
yield item.saveTx();
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "to_download");
setResponse({
method: "GET",
@ -217,8 +217,8 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
var item = new Zotero.Item("attachment");
item.attachmentLinkMode = 'imported_file';
item.attachmentPath = 'storage:test.txt';
item.attachmentSyncState = "to_download";
yield item.saveTx();
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "to_download");
setResponse({
method: "GET",
@ -251,8 +251,8 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
var item = new Zotero.Item("attachment");
item.attachmentLinkMode = 'imported_file';
item.attachmentPath = 'storage:test.txt';
item.attachmentSyncState = "to_download";
yield item.saveTx();
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "to_download");
setResponse({
method: "GET",
@ -298,8 +298,8 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
item.attachmentPath = 'storage:' + fileName;
// TODO: Test binary data
var text = Zotero.Utilities.randomString();
item.attachmentSyncState = "to_download";
yield item.saveTx();
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "to_download");
// Create ZIP file containing above text file
var tmpPath = Zotero.getTempDirectory().path;
@ -447,8 +447,8 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
assert.isTrue(result.syncRequired);
// Check local objects
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedModificationTime(item.id)), mtime);
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedHash(item.id)), hash);
assert.equal(item.attachmentSyncedModificationTime, mtime);
assert.equal(item.attachmentSyncedHash, hash);
assert.isFalse(item.synced);
})
@ -464,12 +464,9 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
var syncedModTime = Date.now() - 10000;
var syncedHash = "3a2f092dd62178eb8bbfda42e07e64da";
yield Zotero.DB.executeTransaction(function* () {
// Set an mtime in the past
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(item.id, syncedModTime);
// And a different hash
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, syncedHash);
});
item.attachmentSyncedModificationTime = syncedModTime;
item.attachmentSyncedHash = syncedHash;
yield item.saveTx({ skipAll: true });
var mtime = yield item.attachmentModificationTime;
var hash = yield item.attachmentHash;
@ -507,8 +504,8 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
assert.isFalse(result.fileSyncRequired);
// Check local objects
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedModificationTime(item.id)), mtime);
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedHash(item.id)), hash);
assert.equal(item.attachmentSyncedModificationTime, mtime);
assert.equal(item.attachmentSyncedHash, hash);
assert.isFalse(item.synced);
})
@ -547,8 +544,8 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
assert.isFalse(result.syncRequired);
// Check local object
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedModificationTime(item.id)), mtime);
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedHash(item.id)), hash);
assert.equal(item.attachmentSyncedModificationTime, mtime);
assert.equal(item.attachmentSyncedHash, hash);
assert.isFalse(item.synced);
})
@ -593,15 +590,10 @@ describe("Zotero.Sync.Storage.Mode.WebDAV", function () {
// Check local object
//
// Item should be marked as in conflict
assert.equal(
(yield Zotero.Sync.Storage.Local.getSyncState(item.id)),
Zotero.Sync.Storage.Local.SYNC_STATE_IN_CONFLICT
);
assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_IN_CONFLICT);
// Synced mod time should have been changed, because that's what's shown in the
// conflict dialog
assert.equal(
(yield Zotero.Sync.Storage.Local.getSyncedModificationTime(item.id)), newModTime
);
assert.equal(item.attachmentSyncedModificationTime, newModTime);
assert.isTrue(item.synced);
})
})

View File

@ -143,8 +143,8 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
var item = new Zotero.Item("attachment");
item.attachmentLinkMode = 'imported_file';
item.attachmentPath = 'storage:test.txt';
item.attachmentSyncState = "to_download";
yield item.saveTx();
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "to_download");
this.httpd.registerPathHandler(
`/users/1/items/${item.key}/file`,
@ -175,8 +175,8 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
var item = new Zotero.Item("attachment");
item.attachmentLinkMode = 'imported_file';
item.attachmentPath = 'storage:test.txt';
item.attachmentSyncState = "to_download";
yield item.saveTx();
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "to_download");
this.httpd.registerPathHandler(
`/users/1/items/${item.key}/file`,
@ -208,8 +208,8 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
item.attachmentPath = 'storage:test.txt';
// TODO: Test binary data
var text = Zotero.Utilities.randomString();
item.attachmentSyncState = "to_download";
yield item.saveTx();
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "to_download");
var mtime = "1441252524905";
var md5 = Zotero.Utilities.Internal.md5(text)
@ -553,14 +553,68 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
assert.isFalse(result.syncRequired);
// Check local objects
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedModificationTime(item1.id)), mtime1);
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedHash(item1.id)), hash1);
assert.equal(item1.attachmentSyncedModificationTime, mtime1);
assert.equal(item1.attachmentSyncedHash, hash1);
assert.equal(item1.version, 10);
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedModificationTime(item2.id)), mtime2);
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedHash(item2.id)), hash2);
assert.equal(item2.attachmentSyncedModificationTime, mtime2);
assert.equal(item2.attachmentSyncedHash, hash2);
assert.equal(item2.version, 15);
})
it("should update local info for remotely updated file that matches local file", function* () {
var { engine, client, caller } = yield setup();
var library = Zotero.Libraries.userLibrary;
library.libraryVersion = 5;
yield library.saveTx();
library.storageDownloadNeeded = true;
var file = getTestDataDirectory();
file.append('test.txt');
var item = yield Zotero.Attachments.importFromFile({ file });
item.version = 5;
item.attachmentSyncState = "to_download";
yield item.saveTx();
var path = yield item.getFilePathAsync();
yield OS.File.setDates(path, null, new Date() - 100000);
var json = item.toJSON();
yield Zotero.Sync.Data.Local.saveCacheObject('item', item.libraryID, json);
var mtime = (Math.floor(new Date().getTime() / 1000) * 1000) + "";
var md5 = Zotero.Utilities.Internal.md5(file)
var s3Path = `pretend-s3/${item.key}`;
this.httpd.registerPathHandler(
`/users/1/items/${item.key}/file`,
{
handle: function (request, response) {
if (!request.hasHeader('Zotero-API-Key')) {
response.setStatusLine(null, 403, "Forbidden");
return;
}
var key = request.getHeader('Zotero-API-Key');
if (key != apiKey) {
response.setStatusLine(null, 403, "Invalid key");
return;
}
response.setStatusLine(null, 302, "Found");
response.setHeader("Zotero-File-Modification-Time", mtime, false);
response.setHeader("Zotero-File-MD5", md5, false);
response.setHeader("Zotero-File-Compressed", "No", false);
response.setHeader("Location", baseURL + s3Path, false);
}
}
);
var result = yield engine.start();
assert.equal(item.attachmentSyncedModificationTime, mtime);
yield assert.eventually.equal(item.attachmentModificationTime, mtime);
assert.isTrue(result.localChanges);
assert.isFalse(result.remoteChanges);
assert.isFalse(result.syncRequired);
})
it("should update local info for file that already exists on the server", function* () {
var { engine, client, caller } = yield setup();
@ -569,7 +623,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
var item = yield Zotero.Attachments.importFromFile({ file: file });
item.version = 5;
yield item.saveTx();
var json = yield item.toJSON();
var json = item.toJSON();
yield Zotero.Sync.Data.Local.saveCacheObject('item', item.libraryID, json);
var mtime = yield item.attachmentModificationTime;
@ -615,8 +669,8 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
assert.isFalse(result.syncRequired);
// Check local objects
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedModificationTime(item.id)), mtime);
assert.equal((yield Zotero.Sync.Storage.Local.getSyncedHash(item.id)), hash);
assert.equal(item.attachmentSyncedModificationTime, mtime);
assert.equal(item.attachmentSyncedHash, hash);
assert.equal(item.version, newVersion);
})
})
@ -635,7 +689,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
item.synced = true;
yield item.saveTx();
var itemJSON = yield item.toResponseJSON();
var itemJSON = item.toResponseJSON();
itemJSON.data.mtime = yield item.attachmentModificationTime;
itemJSON.data.md5 = yield item.attachmentHash;
@ -645,9 +699,8 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
// storage directory was transferred, the mtime doesn't match, but the file was
// never downloaded), but there's no difference in behavior
var dbHash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedHash(item.id, dbHash)
});
item.attachmentSyncedHash = dbHash;
yield item.saveTx({ skipAll: true });
server.respond(function (req) {
if (req.method == "POST"
@ -674,10 +727,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
var result = yield zfs._processUploadFile({
name: item.libraryKey
});
yield assert.eventually.equal(
Zotero.Sync.Storage.Local.getSyncedHash(item.id),
(yield item.attachmentHash)
);
assert.equal(item.attachmentSyncedHash, (yield item.attachmentHash));
assert.isFalse(result.localChanges);
assert.isFalse(result.remoteChanges);
assert.isFalse(result.syncRequired);
@ -697,7 +747,7 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
yield item.saveTx();
var fileHash = yield item.attachmentHash;
var itemJSON = yield item.toResponseJSON();
var itemJSON = item.toResponseJSON();
itemJSON.data.md5 = 'aaaaaaaaaaaaaaaaaaaaaaaa'
server.respond(function (req) {
@ -725,11 +775,8 @@ describe("Zotero.Sync.Storage.Mode.ZFS", function () {
var result = yield zfs._processUploadFile({
name: item.libraryKey
});
yield assert.eventually.isNull(Zotero.Sync.Storage.Local.getSyncedHash(item.id));
yield assert.eventually.equal(
Zotero.Sync.Storage.Local.getSyncState(item.id),
Zotero.Sync.Storage.Local.SYNC_STATE_IN_CONFLICT
);
assert.isNull(item.attachmentSyncedHash);
assert.equal(item.attachmentSyncState, Zotero.Sync.Storage.Local.SYNC_STATE_IN_CONFLICT);
assert.isFalse(result.localChanges);
assert.isFalse(result.remoteChanges);
assert.isFalse(result.syncRequired);

View File

@ -1,13 +1,14 @@
"use strict";
describe("ZoteroPane", function() {
var win, doc, zp;
var win, doc, zp, userLibraryID;
// Load Zotero pane and select library
before(function* () {
win = yield loadZoteroPane();
doc = win.document;
zp = win.ZoteroPane;
userLibraryID = Zotero.Libraries.userLibraryID;
});
after(function () {
@ -157,8 +158,8 @@ describe("ZoteroPane", function() {
item.attachmentPath = 'storage:test.txt';
// TODO: Test binary data
var text = Zotero.Utilities.randomString();
item.attachmentSyncState = "to_download";
yield item.saveTx();
yield Zotero.Sync.Storage.Local.setSyncState(item.id, "to_download");
var mtime = "1441252524000";
var md5 = Zotero.Utilities.Internal.md5(text)
@ -201,4 +202,92 @@ describe("ZoteroPane", function() {
assert.equal((yield Zotero.File.getContentsAsync(path)), text);
})
})
describe("#setVirtual()", function () {
var cv;
before(function* () {
cv = zp.collectionsView;
});
beforeEach(function () {
Zotero.Prefs.clear('duplicateLibraries');
Zotero.Prefs.clear('unfiledLibraries');
return selectLibrary(win);
})
it("should show a hidden virtual folder", function* () {
// Create unfiled, duplicate items
var title = Zotero.Utilities.randomString();
var item1 = yield createDataObject('item', { title });
var item2 = yield createDataObject('item', { title });
// Start hidden
Zotero.Prefs.set('duplicateLibraries', "");
Zotero.Prefs.set('unfiledLibraries', "");
yield cv.refresh();
// Show Duplicate Items
var id = "D" + userLibraryID;
assert.isFalse(cv.getRowIndexByID(id));
yield zp.setVirtual(userLibraryID, 'duplicates', true);
// Clicking should select both items
var row = cv.getRowIndexByID(id);
assert.ok(row);
assert.equal(cv.selection.currentIndex, row);
yield waitForItemsLoad(win);
var iv = zp.itemsView;
row = iv.getRowIndexByID(item1.id);
assert.isNumber(row);
clickOnItemsRow(iv, row);
assert.equal(iv.selection.count, 2);
// Show Unfiled Items
id = "U" + userLibraryID;
assert.isFalse(cv.getRowIndexByID(id));
yield zp.setVirtual(userLibraryID, 'unfiled', true);
assert.ok(cv.getRowIndexByID(id));
});
it("should hide a virtual folder shown by default", function* () {
yield cv.refresh();
// Hide Duplicate Items
var id = "D" + userLibraryID;
assert.ok(yield cv.selectByID(id));
yield zp.setVirtual(userLibraryID, 'duplicates', false);
assert.isFalse(cv.getRowIndexByID(id));
// Hide Unfiled Items
id = "U" + userLibraryID;
assert.ok(yield cv.selectByID(id));
yield zp.setVirtual(userLibraryID, 'unfiled', false);
assert.isFalse(cv.getRowIndexByID(id));
});
it("should hide an explicitly shown virtual folder", function* () {
// Start shown
Zotero.Prefs.set('duplicateLibraries', "" + userLibraryID);
Zotero.Prefs.set('unfiledLibraries', "" + userLibraryID);
yield cv.refresh();
// Hide Duplicate Items
var id = "D" + userLibraryID;
assert.ok(yield cv.selectByID(id));
yield waitForItemsLoad(win);
yield zp.setVirtual(userLibraryID, 'duplicates', false);
assert.isFalse(cv.getRowIndexByID(id));
assert.equal(cv.getSelectedLibraryID(), userLibraryID);
// Hide Unfiled Items
id = "U" + userLibraryID;
assert.ok(yield cv.selectByID(id));
yield waitForItemsLoad(win);
yield zp.setVirtual(userLibraryID, 'unfiled', false);
assert.isFalse(cv.getRowIndexByID(id));
assert.equal(cv.getSelectedLibraryID(), userLibraryID);
});
});
})