diff --git a/chrome/content/zotero/bindings/itembox.xml b/chrome/content/zotero/bindings/itembox.xml
index 02e4e796fd..1e4184f068 100644
--- a/chrome/content/zotero/bindings/itembox.xml
+++ b/chrome/content/zotero/bindings/itembox.xml
@@ -341,7 +341,7 @@
fieldNames.push(Zotero.ItemFields.getName(fields[i]));
}
- if (! (this.item instanceof Zotero.FeedItem)) {
+ if (!(this.item instanceof Zotero.FeedItem)) {
fieldNames.push("dateAdded", "dateModified");
}
}
diff --git a/chrome/content/zotero/feedSettings.js b/chrome/content/zotero/feedSettings.js
index 56555a1d61..fd0db9d85a 100644
--- a/chrome/content/zotero/feedSettings.js
+++ b/chrome/content/zotero/feedSettings.js
@@ -36,31 +36,28 @@ var Zotero_Feed_Settings = new function() {
urlTainted = false;
let cleanURL = function(url) {
- let cleanUrl = Zotero.Utilities.cleanURL(url, true);
+ let cleanURL = Zotero.Utilities.cleanURL(url, true);
- if (cleanUrl) {
- if (/^https?:\/\/[^\/\s]+\/\S/.test(cleanUrl)) {
- return cleanUrl;
+ if (cleanURL) {
+ if (/^https?:\/\/[^\/\s]+\/\S/.test(cleanURL)) {
+ return cleanURL;
} else {
- Zotero.debug(uri.scheme + " is not a supported protocol for feeds.");
+ Zotero.debug(uri.scheme + " is not a supported protocol for feeds");
}
}
};
- this.init = function() {
+ this.init = Zotero.Promise.coroutine(function* () {
this.toggleAdvancedOptions(false);
data = window.arguments[0];
if (data.url) {
document.getElementById('feed-url').value = data.url;
- }
-
- if (!data.url) {
- this.invalidateUrl();
- } else {
// Do not allow to change URL for existing feed
document.getElementById('feed-url').readOnly = true;
+ } else {
+ this.invalidateURL();
}
if (data.title) {
@@ -71,20 +68,20 @@ var Zotero_Feed_Settings = new function() {
if (data.ttl !== undefined) {
ttl = Math.floor(data.ttl / 60);
} else {
- ttl = 1;
+ ttl = Zotero.Prefs.get('feeds.defaultTTL');
}
document.getElementById('feed-ttl').value = ttl;
let cleanupAfter = data.cleanupAfter;
- if (cleanupAfter === undefined) cleanupAfter = 2;
+ if (cleanupAfter === undefined) cleanupAfter = Zotero.Prefs.get('feeds.defaultCleanupAfter');
document.getElementById('feed-cleanupAfter').value = cleanupAfter;
if (data.url && !data.urlIsValid) {
- this.validateUrl();
+ yield this.validateURL();
}
- };
+ });
- this.invalidateUrl = function() {
+ this.invalidateURL = function() {
urlTainted = true;
if (feedReader) {
feedReader.terminate();
@@ -100,7 +97,7 @@ var Zotero_Feed_Settings = new function() {
document.documentElement.getButton('accept').disabled = true;
};
- this.validateUrl = Zotero.Promise.coroutine(function* () {
+ this.validateURL = Zotero.Promise.coroutine(function* () {
if (feedReader) {
feedReader.terminate();
feedReader = null;
@@ -111,11 +108,11 @@ var Zotero_Feed_Settings = new function() {
if (!url) return;
try {
- let fr = feedReader = new Zotero.FeedReader(url);
+ var fr = feedReader = new Zotero.FeedReader(url);
yield fr.process();
let feed = fr.feedProperties;
// Prevent progress if textbox changes triggered another call to
- // validateUrl / invalidateUrl (old session)
+ // validateURL / invalidateURL (old session)
if (feedReader !== fr || urlTainted) return;
let title = document.getElementById('feed-title');
diff --git a/chrome/content/zotero/feedSettings.xul b/chrome/content/zotero/feedSettings.xul
index 2550c9ca7a..2cc6e22140 100644
--- a/chrome/content/zotero/feedSettings.xul
+++ b/chrome/content/zotero/feedSettings.xul
@@ -15,36 +15,48 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/chrome/content/zotero/itemPane.js b/chrome/content/zotero/itemPane.js
index 45bfad1324..e42a3aa572 100644
--- a/chrome/content/zotero/itemPane.js
+++ b/chrome/content/zotero/itemPane.js
@@ -86,6 +86,10 @@ var ZoteroItemPane = new function() {
_lastItem = item;
+ // Hide for feed items
+ document.getElementById('zotero-editpane-tabs').setAttribute('hidden', item.isFeedItem);
+ document.getElementById('zotero-view-item').classList.add('no-tabs');
+
if (index == 1) {
var editable = ZoteroPane_Local.canEdit();
_notesButton.hidden = !editable;
diff --git a/chrome/content/zotero/itemPane.xul b/chrome/content/zotero/itemPane.xul
index 932a2493ae..2176e7ebc1 100644
--- a/chrome/content/zotero/itemPane.xul
+++ b/chrome/content/zotero/itemPane.xul
@@ -52,41 +52,41 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/chrome/content/zotero/preferences/preferences_advanced.js b/chrome/content/zotero/preferences/preferences_advanced.js
index 88c84a8853..b37a0d269d 100644
--- a/chrome/content/zotero/preferences/preferences_advanced.js
+++ b/chrome/content/zotero/preferences/preferences_advanced.js
@@ -816,7 +816,7 @@ Zotero_Preferences.Keys = {
for (var i=0; i
-
+
+ %zoteroDTD;
+
+ %preferencesDTD;
+]>
@@ -50,13 +55,18 @@
+
+
+
+
-
+
+
@@ -218,37 +228,37 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -256,7 +266,7 @@
-
+
@@ -264,7 +274,7 @@
-
+
@@ -280,6 +290,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/chrome/content/zotero/preferences/preferences_advanced_firefox.xul b/chrome/content/zotero/preferences/preferences_advanced_firefox.xul
index da52cda305..a408a50e05 100644
--- a/chrome/content/zotero/preferences/preferences_advanced_firefox.xul
+++ b/chrome/content/zotero/preferences/preferences_advanced_firefox.xul
@@ -51,19 +51,19 @@
-
+
-
+
-
+
diff --git a/chrome/content/zotero/preferences/preferences_general.xul b/chrome/content/zotero/preferences/preferences_general.xul
index 1626ae4901..ec5347cd92 100644
--- a/chrome/content/zotero/preferences/preferences_general.xul
+++ b/chrome/content/zotero/preferences/preferences_general.xul
@@ -44,7 +44,6 @@
-
@@ -132,18 +131,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/chrome/content/zotero/xpcom/attachments.js b/chrome/content/zotero/xpcom/attachments.js
index 2f2ec45daf..2a787b7363 100644
--- a/chrome/content/zotero/xpcom/attachments.js
+++ b/chrome/content/zotero/xpcom/attachments.js
@@ -689,6 +689,7 @@ Zotero.Attachments = new function(){
* @deprecated Use Zotero.Utilities.cleanURL instead
*/
this.cleanAttachmentURI = function (uri, tryHttp) {
+ Zotero.debug("Zotero.Attachments.cleanAttachmentURI() is deprecated -- use Zotero.Utilities.cleanURL");
return Zotero.Utilities.cleanURL(uri, tryHttp);
}
diff --git a/chrome/content/zotero/xpcom/collectionTreeRow.js b/chrome/content/zotero/xpcom/collectionTreeRow.js
index 9dbed7bd68..02a0e7b9d1 100644
--- a/chrome/content/zotero/xpcom/collectionTreeRow.js
+++ b/chrome/content/zotero/xpcom/collectionTreeRow.js
@@ -152,13 +152,13 @@ Zotero.CollectionTreeRow.prototype.__defineGetter__('editable', function () {
if (this.isTrash() || this.isShare() || this.isBucket()) {
return false;
}
+ if (this.isGroup() || this.isFeed()) {
+ return this.ref.editable;
+ }
if (!this.isWithinGroup() || this.isPublications()) {
return true;
}
var libraryID = this.ref.libraryID;
- if (this.isGroup() || this.isFeed()) {
- return this.ref.editable;
- }
if (this.isCollection() || this.isSearch() || this.isDuplicates() || this.isUnfiled()) {
var type = Zotero.Libraries.get(libraryID).libraryType;
if (type == 'group') {
diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js
index 22b7703e23..9ae67cc430 100644
--- a/chrome/content/zotero/xpcom/collectionTreeView.js
+++ b/chrome/content/zotero/xpcom/collectionTreeView.js
@@ -724,11 +724,14 @@ Zotero.CollectionTreeView.prototype.getImageSrc = function(row, col)
switch (collectionType) {
case 'library':
case 'feed':
+ // Better alternative needed: https://github.com/zotero/zotero/pull/902#issuecomment-183185973
+ /*
if (treeRow.ref.updating) {
collectionType += '-updating';
} else if (treeRow.ref.lastCheckError) {
collectionType += '-error';
}
+ */
break;
case 'trash':
@@ -1337,7 +1340,7 @@ Zotero.CollectionTreeView.prototype._rememberOpenStates = Zotero.Promise.corouti
var open = this.isContainerOpen(i);
// Collections and feeds default to closed
- if (!open && treeRow.isCollection() || treeRow.isFeed()) {
+ if ((!open && treeRow.isCollection()) || treeRow.isFeed()) {
delete state[treeRow.id];
continue;
}
@@ -1938,7 +1941,7 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r
}
var items = yield Zotero.Items.getAsync(ids);
- if (!items) {
+ if (items.length == 0) {
return;
}
diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js
index c1a8a55efe..bb667122a2 100644
--- a/chrome/content/zotero/xpcom/data/dataObject.js
+++ b/chrome/content/zotero/xpcom/data/dataObject.js
@@ -757,11 +757,16 @@ Zotero.DataObject.prototype.isEditable = function () {
Zotero.DataObject.prototype.editCheck = function () {
+ let library = Zotero.Libraries.get(this.libraryID);
if ((this._objectType == 'collection' || this._objectType == 'search')
- && Zotero.Libraries.get(this.libraryID).libraryType == 'publications') {
+ && library.libraryType == 'publications') {
throw new Error(this._ObjectTypePlural + " cannot be added to My Publications");
}
+ if (library.libraryType == 'feed') {
+ return;
+ }
+
if (!this.isEditable()) {
throw new Error("Cannot edit " + this._objectType + " in read-only library "
+ Zotero.Libraries.getName(this.libraryID));
diff --git a/chrome/content/zotero/xpcom/data/feed.js b/chrome/content/zotero/xpcom/data/feed.js
index 2318483131..7c3208235b 100644
--- a/chrome/content/zotero/xpcom/data/feed.js
+++ b/chrome/content/zotero/xpcom/data/feed.js
@@ -70,6 +70,8 @@ Zotero.Feed = function(params = {}) {
this._feedUnreadCount = null;
this._updating = false;
+ this._syncedSettings = null;
+ this._previousURL = null;
}
Zotero.Feed._colToProp = function(c) {
@@ -79,13 +81,13 @@ Zotero.Feed._colToProp = function(c) {
Zotero.extendClass(Zotero.Library, Zotero.Feed);
Zotero.defineProperty(Zotero.Feed, '_unreadCountSQL', {
- value: "(SELECT COUNT(*) FROM items I JOIN feedItems FeI USING (itemID)"
- + " WHERE I.libraryID=F.libraryID AND FeI.readTime IS NULL) AS _feedUnreadCount"
+ value: "(SELECT COUNT(*) FROM items I JOIN feedItems FI USING (itemID)"
+ + " WHERE I.libraryID=F.libraryID AND FI.readTime IS NULL) AS _feedUnreadCount"
});
Zotero.defineProperty(Zotero.Feed, '_dbColumns', {
value: Object.freeze(['name', 'url', 'lastUpdate', 'lastCheck',
- 'lastCheckError', 'lastGUID', 'cleanupAfter', 'refreshInterval'])
+ 'lastCheckError', 'cleanupAfter', 'refreshInterval'])
});
Zotero.defineProperty(Zotero.Feed, '_primaryDataSQLParts');
@@ -130,7 +132,7 @@ for (let i=0; i 0";
- let expiredIDs = yield Zotero.DB.queryAsync(sql, [this.id, {int: this.cleanupAfter}]);
- return expiredIDs.map(row => row.id);
+ return Zotero.DB.columnQueryAsync(sql, [this.id, {int: this.cleanupAfter}]);
});
-Zotero.Feed.prototype.clearExpiredItems = Zotero.Promise.coroutine(function* () {
+/**
+ * Clearing conditions for an item:
+ * - Has been read at least feed.cleanupAfter earlier AND
+ * - Does not exist in the RSS feed anymore
+ *
+ * If we clear items once they've been read, we may potentially end up
+ * with empty feeds for those that do not update very frequently.
+ */
+Zotero.Feed.prototype.clearExpiredItems = Zotero.Promise.coroutine(function* (itemsInFeedIDs) {
+ itemsInFeedIDs = itemsInFeedIDs || new Set();
try {
// Clear expired items
if (this.cleanupAfter) {
let expiredItems = yield this.getExpiredFeedItemIDs();
- Zotero.debug("Cleaning up read feed items...");
- if (expiredItems.length) {
- Zotero.debug(expiredItems.join(', '));
- yield Zotero.FeedItems.forceErase(expiredItems);
+ let toClear = expiredItems;
+ if (itemsInFeedIDs.size) {
+ toClear = [];
+ for (let id of expiredItems) {
+ if (!itemsInFeedIDs.has(id)) {
+ toClear.push(id);
+ }
+ }
+ }
+ Zotero.debug("Clearing up read feed items...");
+ if (toClear.length) {
+ Zotero.debug(toClear.join(', '));
+ yield Zotero.FeedItems.erase(toClear);
} else {
Zotero.debug("No expired feed items");
}
}
} catch(e) {
- Zotero.debug("Error clearing expired feed items.");
+ Zotero.debug("Error clearing expired feed items");
Zotero.debug(e);
}
+ return this.storeSyncedSettings();
});
Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () {
- var toAdd = [];
- var createNew = true;
+ var toSave = [], attachmentsToAdd = [], feedItemIDs = new Set();
if (this._updating) {
return this._updating;
}
@@ -338,49 +418,59 @@ Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () {
Zotero.Notifier.trigger('statusChanged', 'feed', this.id);
this._set('_feedLastCheckError', null);
- yield this.clearExpiredItems();
try {
let fr = new Zotero.FeedReader(this.url);
yield fr.process();
let itemIterator = new fr.ItemIterator();
- let item, processedGUIDs = [];
+ let item, processedGUIDs = new Set();
while (item = yield itemIterator.next().value) {
- // Append id at the end to prevent same item collisions from different feeds
- // when to terminate item retrieval.
- item.guid += ":" + this.id;
- if (item.guid == this.lastGUID) {
- Zotero.debug("Feed#update: last seen item reached (" + this.lastGUID + ")");
- Zotero.debug(item);
- // Don't create new items (expired and deleted), but update existing ones
- createNew = false;
- }
-
- if (processedGUIDs.indexOf(item.guid) != -1) {
- Zotero.debug("Feed item " + item.guid + " already processed from feed.");
+ if (processedGUIDs.has(item.guid)) {
+ Zotero.debug("Feed item " + item.guid + " already processed from feed");
continue;
}
- processedGUIDs.push(item.guid);
+ processedGUIDs.add(item.guid);
- Zotero.debug("New feed item retrieved:", 5);
+ Zotero.debug("Feed item retrieved:", 5);
Zotero.debug(item, 5);
let feedItem = yield Zotero.FeedItems.getAsyncByGUID(item.guid);
- if (!feedItem && createNew) {
+ if (feedItem) {
+ feedItemIDs.add(feedItem.id);
+ }
+ if (!feedItem) {
+ Zotero.debug("Creating new feed item " + item.guid);
feedItem = new Zotero.FeedItem();
feedItem.guid = item.guid;
feedItem.libraryID = this.id;
- } else if(! feedItem.isTranslated) {
- Zotero.debug("Feed item " + item.guid + " already in library.");
+ } else if (!feedItem.isTranslated) {
+ // TODO: maybe handle enclosed items on update better
+ item.enclosedItems = [];
+
+ // TODO figure out a better GUID collision resolution system
+ // that works with sync.
+ if (feedItem.libraryID != this.libraryID) {
+ let otherFeed = Zotero.Feeds.get(feedItem.libraryID);
+ Zotero.debug("Feed item " + feedItem.url + " from " + this.url +
+ " exists in a different feed " + otherFeed.url + ". Skipping");
+ continue;
+ }
+
+ Zotero.debug("Feed item " + item.guid + " already in library");
Zotero.debug("Updating metadata");
- yield feedItem.loadItemData();
- yield feedItem.loadCreators();
} else {
- // Either has been translated or beyond lastGUID
+ // Not new and has been translated
+ Zotero.debug("Feed item " + item.guid + " is not new and has already been translated. Skipping");
continue;
}
+ for (let enclosedItem of item.enclosedItems) {
+ enclosedItem.parentItem = feedItem;
+ attachmentsToAdd.push(enclosedItem);
+ }
+
// Delete invalid data
delete item.guid;
+ delete item.enclosedItems;
feedItem.fromJSON(item);
if (!feedItem.hasChanged()) {
@@ -388,7 +478,7 @@ Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () {
continue
}
feedItem.isRead = false;
- toAdd.push(feedItem);
+ toSave.push(feedItem);
}
}
catch (e) {
@@ -398,18 +488,24 @@ Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () {
}
this._set('_feedLastCheckError', e.message || 'Error processing feed');
}
- if (toAdd.length) {
+ if (toSave.length) {
yield Zotero.DB.executeTransaction(function* () {
// Save in reverse order
- for (let i=toAdd.length-1; i>=0; i--) {
- // Saving currently has to happen sequentially so as not to violate the
- // unique constraints in itemDataValues (FIXME)
- yield toAdd[i].save({skipEditCheck: true});
+ for (let i=toSave.length-1; i>=0; i--) {
+ yield toSave[i].save();
}
+
});
this._set('_feedLastUpdate', Zotero.Date.dateToSQL(new Date(), true));
- this._set('_feedLastGUID', toAdd[0].guid);
}
+ for (let attachment of attachmentsToAdd) {
+ if (attachment.url.indexOf('pdf') != -1 || attachment.contentType.indexOf('pdf') != -1) {
+ attachment.parentItemID = attachment.parentItem.id;
+ attachment.title = Zotero.getString('fileTypes.pdf');
+ yield Zotero.Attachments.linkFromURL(attachment);
+ }
+ }
+ yield this.clearExpiredItems(feedItemIDs);
this._set('_feedLastCheck', Zotero.Date.dateToSQL(new Date(), true));
yield this.saveTx();
yield this.updateUnreadCount();
@@ -427,23 +523,6 @@ Zotero.Feed.prototype.updateFeed = Zotero.Promise.coroutine(function* () {
}
});
-Zotero.Feed.prototype._finalizeErase = Zotero.Promise.coroutine(function* (){
- let notifierData = {};
- notifierData[this.libraryID] = {
- libraryID: this.libraryID
- };
- Zotero.Notifier.trigger('delete', 'feed', this.id, notifierData);
- Zotero.Feeds.unregister(this.libraryID);
- return Zotero.Feed._super.prototype._finalizeErase.apply(this, arguments);
-});
-
-Zotero.Feed.prototype.erase = Zotero.Promise.coroutine(function* (options = {}) {
- let childItemIDs = yield Zotero.FeedItems.getAll(this.id, false, false, true);
- yield Zotero.FeedItems.forceErase(childItemIDs);
-
- yield Zotero.Feed._super.prototype.erase.call(this, options);
-});
-
Zotero.Feed.prototype.updateUnreadCount = Zotero.Promise.coroutine(function* () {
let sql = "SELECT " + Zotero.Feed._unreadCountSQL
+ " FROM feeds F JOIN libraries L USING (libraryID)"
@@ -455,3 +534,9 @@ Zotero.Feed.prototype.updateUnreadCount = Zotero.Promise.coroutine(function* ()
Zotero.Notifier.trigger('unreadCountUpdated', 'feed', this.id);
}
});
+
+Zotero.Feed.prototype.updateFromJSON = Zotero.Promise.coroutine(function* (json) {
+ yield this.updateFeed();
+ yield Zotero.FeedItems.markAsReadByGUID(Object.keys(json.markedAsRead));
+ yield this.updateUnreadCount();
+});
diff --git a/chrome/content/zotero/xpcom/data/feedItem.js b/chrome/content/zotero/xpcom/data/feedItem.js
index 1e8f2cfbb9..068f87fb71 100644
--- a/chrome/content/zotero/xpcom/data/feedItem.js
+++ b/chrome/content/zotero/xpcom/data/feedItem.js
@@ -121,8 +121,8 @@ Zotero.FeedItem.prototype.fromJSON = function(json) {
d = new Date(d.year, d.month, d.day);
Zotero.debug(dateField + " " + JSON.stringify(d), 1);
}
- if (!d) {
- Zotero.logError("Discarding invalid " + field + " '" + val
+ if (isNaN(d.getTime())) {
+ Zotero.logError("Discarding invalid " + dateField + " '" + json[dateField]
+ "' for item " + this.libraryKey);
delete json[dateField];
continue;
@@ -160,13 +160,6 @@ Zotero.FeedItem.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
return proceed;
});
-Zotero.FeedItem.prototype.forceSaveTx = function(options) {
- let newOptions = {};
- Object.assign(newOptions, options || {});
- newOptions.skipEditCheck = true;
- return this.saveTx(newOptions);
-}
-
Zotero.FeedItem.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
yield Zotero.FeedItem._super.prototype._saveData.apply(this, arguments);
@@ -178,25 +171,38 @@ Zotero.FeedItem.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
}
});
+Zotero.FeedItem.prototype._finalizeErase = Zotero.Promise.coroutine(function* () {
+ // Set for syncing
+ let feed = Zotero.Feeds.get(this.libraryID);
+ let syncedSettings = feed.getSyncedSettings();
+ delete syncedSettings.markedAsRead[this.guid];
+ yield feed.setSyncedSettings(syncedSettings);
+
+ return Zotero.FeedItem._super.prototype._finalizeErase.apply(this, arguments);
+});
+
Zotero.FeedItem.prototype.toggleRead = Zotero.Promise.coroutine(function* (state) {
state = state !== undefined ? !!state : !this.isRead;
let changed = this.isRead != state;
if (changed) {
this.isRead = state;
- yield this.saveTx({skipEditCheck: true, skipDateModifiedUpdate: true});
+ // Set for syncing
let feed = Zotero.Feeds.get(this.libraryID);
+ let syncedSettings = feed.getSyncedSettings();
+ if (state) {
+ syncedSettings.markedAsRead[this.guid] = true;
+ } else {
+ delete syncedSettings.markedAsRead[this.guid];
+ }
+ yield feed.setSyncedSettings(syncedSettings, true);
+
+ yield this.saveTx();
+
yield feed.updateUnreadCount();
}
});
-Zotero.FeedItem.prototype.forceEraseTx = function(options) {
- let newOptions = {};
- Object.assign(newOptions, options || {});
- newOptions.skipEditCheck = true;
- return this.eraseTx(newOptions);
-};
-
/**
* Uses the item url to translate an existing feed item.
* If libraryID empty, overwrites feed item, otherwise saves
@@ -233,7 +239,7 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (librar
// Load document
let hiddenBrowser = Zotero.HTTP.processDocuments(
this.getField('url'),
- (item) => deferred.resolve(item),
+ item => deferred.resolve(item),
()=>{}, error, true
);
let doc = yield deferred.promise;
@@ -246,9 +252,9 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (librar
translate.setHandler('translators', (me, translators) => deferred.resolve(translators));
translate.getTranslators();
let translators = yield deferred.promise;
- if (! translators || !translators.length) {
- Zotero.debug("No translators detected for FeedItem " + this.id + " with url " + this.getField('url'), 2);
- throw new Zotero.Error("No translators detected for FeedItem " + this.id + " with url " + this.getField('url'))
+ if (!translators || !translators.length) {
+ Zotero.debug("No translators detected for feed item " + this.id + " with URL " + this.getField('url'), 2);
+ throw new Zotero.Error("No translators detected for feed item " + this.id + " with URL " + this.getField('url'))
}
translate.setTranslator(translators[0]);
@@ -280,7 +286,7 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (librar
this.fromJSON(itemData);
this.isTranslated = true;
- this.forceSaveTx();
+ yield this.saveTx();
return this;
});
diff --git a/chrome/content/zotero/xpcom/data/feedItems.js b/chrome/content/zotero/xpcom/data/feedItems.js
index c989f3bea6..824d5e7f5e 100644
--- a/chrome/content/zotero/xpcom/data/feedItems.js
+++ b/chrome/content/zotero/xpcom/data/feedItems.js
@@ -38,13 +38,13 @@ Zotero.FeedItems = new Proxy(function() {
Zotero.defineProperty(Zotero.Items, '_primaryDataSQLParts', {
get: function() {
let obj = zi_primaryDataSQLParts.call(this);
- obj.feedItemGUID = "FeI.guid AS feedItemGUID";
- obj.feedItemReadTime = "FeI.readTime AS feedItemReadTime";
- obj.feedItemTranslatedTime = "FeI.translatedTime AS feedItemTranslatedTime";
+ obj.feedItemGUID = "FI.guid AS feedItemGUID";
+ obj.feedItemReadTime = "FI.readTime AS feedItemReadTime";
+ obj.feedItemTranslatedTime = "FI.translatedTime AS feedItemTranslatedTime";
return obj;
}
}, {lazy: true});
- Zotero.Items._primaryDataSQLFrom += " LEFT JOIN feedItems FeI ON (FeI.itemID=O.itemID)";
+ Zotero.Items._primaryDataSQLFrom += " LEFT JOIN feedItems FI ON (FI.itemID=O.itemID)";
let zi_getObjectForRow = Zotero.Items._getObjectForRow;
Zotero.Items._getObjectForRow = function(row) {
@@ -58,7 +58,7 @@ Zotero.FeedItems = new Proxy(function() {
this.getIDFromGUID = Zotero.Promise.coroutine(function* (guid) {
if (_idCache[guid] !== undefined) return _idCache[guid];
- id = yield Zotero.DB.valueQueryAsync('SELECT itemID FROM feedItems WHERE guid=?', [guid]);
+ let id = yield Zotero.DB.valueQueryAsync('SELECT itemID FROM feedItems WHERE guid=?', [guid]);
if (!id) return false;
this._setGUIDMapping(guid, id);
@@ -95,6 +95,38 @@ Zotero.FeedItems = new Proxy(function() {
return this.getAsync(id);
});
+ this.getMarkedAsRead = Zotero.Promise.coroutine(function* (libraryID, onlyGUIDs=false) {
+ let sql = "SELECT " + (onlyGUIDs ? "guid " : "itemID ") +
+ "FROM feedItems FI " +
+ "JOIN items I USING (itemID) " +
+ "WHERE libraryID=? AND readTime IS NOT NULL";
+ let ids = yield Zotero.DB.columnQueryAsync(sql, [libraryID]);
+ if (onlyGUIDs) {
+ return ids;
+ }
+ return Zotero.FeedItems.getAsync(ids);
+
+ });
+
+ /**
+ * Used on restore from sync
+ */
+ this.markAsReadByGUID = Zotero.Promise.coroutine(function* (guids) {
+ if (! Array.isArray(guids)) {
+ throw new Error('guids must be an array in Zotero.FeedItems.toggleReadByID');
+ }
+ let ids = [];
+ Zotero.debug("Marking items as read");
+ Zotero.debug(guids);
+ for (let guid of guids) {
+ let id = yield this.getIDFromGUID(guid);
+ if (id) {
+ ids.push(id);
+ }
+ }
+ return this.toggleReadByID(ids, true);
+ });
+
this.toggleReadByID = Zotero.Promise.coroutine(function* (ids, state) {
var feedsToUpdate = new Set();
if (!Array.isArray(ids)) {
@@ -115,24 +147,31 @@ Zotero.FeedItems = new Proxy(function() {
}
}
+
yield Zotero.DB.executeTransaction(function() {
for (let i=0; i>}
*/
- this.getAll = Zotero.Promise.coroutine(function* (libraryID, onlyTopLevel, includeDeleted, onlyIDs=false) {
+ this.getAll = Zotero.Promise.coroutine(function* (libraryID, onlyTopLevel, includeDeleted, asIDs=false) {
var sql = 'SELECT A.itemID FROM items A';
if (onlyTopLevel) {
sql += ' LEFT JOIN itemNotes B USING (itemID) '
@@ -151,7 +151,7 @@ Zotero.Items = function() {
}
sql += " AND libraryID=?";
var ids = yield Zotero.DB.columnQueryAsync(sql, libraryID);
- if (onlyIDs) {
+ if (asIDs) {
return ids;
}
return this.getAsync(ids);
diff --git a/chrome/content/zotero/xpcom/feedReader.js b/chrome/content/zotero/xpcom/feedReader.js
index 259962b02d..eb44bc03ef 100644
--- a/chrome/content/zotero/xpcom/feedReader.js
+++ b/chrome/content/zotero/xpcom/feedReader.js
@@ -462,6 +462,8 @@ Zotero.FeedReader._getFeedItem = function(feedEntry, feedInfo) {
Zotero.FeedReader._guessItemType(item);
+ item.enclosedItems = Zotero.FeedReader._getEnclosedItems(feedEntry);
+
return item;
}
@@ -501,3 +503,19 @@ Zotero.FeedReader._getFeedField = function(feedEntry, field, namespace) {
return;
}
+
+Zotero.FeedReader._getEnclosedItems = function(feedEntry) {
+ var enclosedItems = [];
+
+ if (feedEntry.enclosures) {
+ for (let i = 0; i < feedEntry.enclosures.length; i++) {
+ let elem = feedEntry.enclosures.queryElementAt(0, Components.interfaces.nsIPropertyBag2);
+ if (elem.get('url')) {
+ let enclosedItem = {url: elem.get('url'), contentType: elem.get('type') || ''};
+ enclosedItems.push(enclosedItem);
+ }
+ }
+ }
+
+ return enclosedItems;
+}
diff --git a/chrome/content/zotero/xpcom/itemTreeView.js b/chrome/content/zotero/xpcom/itemTreeView.js
index bd808a66e4..012f5c2f25 100644
--- a/chrome/content/zotero/xpcom/itemTreeView.js
+++ b/chrome/content/zotero/xpcom/itemTreeView.js
@@ -98,6 +98,7 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
}
this._treebox = treebox;
+ this.setSortColumn();
if (this._ownerDocument.defaultView.ZoteroPane_Local) {
this._ownerDocument.defaultView.ZoteroPane_Local.setItemsPaneMessage(Zotero.getString('pane.items.loading'));
@@ -259,6 +260,43 @@ Zotero.ItemTreeView.prototype.setTree = Zotero.Promise.coroutine(function* (tree
});
+Zotero.ItemTreeView.prototype.setSortColumn = function() {
+ var dir, col, currentCol, currentDir;
+
+ for (let i=0, len=this._treebox.columns.count; i non-feed)
+ if (! this.collectionTreeRow.isFeed() && colID) {
+ col = this._treebox.columns.getNamedColumn(colID);
+ dir = Zotero.Prefs.get('itemTree.sortDirection');
+ Zotero.Prefs.clear('itemTree.sortColumnID');
+ Zotero.Prefs.clear('itemTree.sortDirection');
+ // No previous sort setting stored, so store it (non-feed -> feed)
+ } else if (this.collectionTreeRow.isFeed() && !colID && currentCol) {
+ Zotero.Prefs.set('itemTree.sortColumnID', currentCol.id);
+ Zotero.Prefs.set('itemTree.sortDirection', currentDir);
+ // Retain current sort setting (non-feed -> non-feed)
+ } else {
+ col = currentCol;
+ dir = currentDir;
+ }
+ if (col) {
+ col.element.setAttribute('sortActive', true);
+ col.element.setAttribute('sortDirection', dir);
+ }
+}
+
+
/**
* Reload the rows from the data access methods
* (doesn't call the tree.invalidate methods, etc.)
@@ -1228,6 +1266,9 @@ Zotero.ItemTreeView.prototype.isSorted = function()
}
Zotero.ItemTreeView.prototype.cycleHeader = function (column) {
+ if (this.collectionTreeRow.isFeed()) {
+ return;
+ }
for(var i=0, len=this._treebox.columns.count; i x.libraryID);
for (let libraryID of libraryIDs) {
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
index f48cc3df4c..a2b4d25636 100644
--- a/chrome/content/zotero/zoteroPane.js
+++ b/chrome/content/zotero/zoteroPane.js
@@ -1381,9 +1381,10 @@ var ZoteroPane = new function()
}
if (item.isFeedItem) {
- if (! item.isTranslated) {
- item.translate();
- }
+ // Too slow for now
+ // if (!item.isTranslated) {
+ // item.translate();
+ // }
this.startItemReadTimeout(item.id);
}
}
@@ -1756,7 +1757,7 @@ var ZoteroPane = new function()
return;
}
- if (!this.canEdit()) {
+ if (!this.canEdit() && !collectionTreeRow.isFeed()) {
this.displayCannotEditLibraryMessage();
return;
}
@@ -1874,7 +1875,7 @@ var ZoteroPane = new function()
}
if (this.collectionsView.selection.count > 0) {
- var row = this.collectionsView.getRow(this.collectionsView.selection.currentIndex);
+ var row = this.collectionsView.selectedTreeRow;
if (row.isCollection()) {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
@@ -1924,7 +1925,7 @@ var ZoteroPane = new function()
this.markFeedRead = Zotero.Promise.coroutine(function* () {
if (!this.collectionsView.selection.count) return;
- let feed = this.collectionsView.getRow(this.collectionsView.selection.currentIndex).ref;
+ let feed = this.collectionsView.selectedTreeRow.ref;
let feedItemIDs = yield Zotero.FeedItems.getAll(feed.libraryID, true, false, true);
yield Zotero.FeedItems.toggleReadByID(feedItemIDs, true);
});
@@ -1933,7 +1934,7 @@ var ZoteroPane = new function()
this.editSelectedFeed = Zotero.Promise.coroutine(function* () {
if (!this.collectionsView.selection.count) return;
- let feed = this.collectionsView.getRow(this.collectionsView.selection.currentIndex).ref;
+ let feed = this.collectionsView.selectedTreeRow.ref;
let data = {
url: feed.url,
title: feed.name,
@@ -1954,7 +1955,7 @@ var ZoteroPane = new function()
this.refreshFeed = function() {
if (!this.collectionsView.selection.count) return;
- let feed = this.collectionsView.getRow(this.collectionsView.selection.currentIndex).ref;
+ let feed = this.collectionsView.selectedTreeRow.ref;
return feed.updateFeed();
}
@@ -2297,29 +2298,15 @@ var ZoteroPane = new function()
m.sep1,
m.markReadFeed,
m.editSelectedFeed,
- m.deleteCollectionAndItems,
- m.sep2,
- m.exportCollection,
- m.createBibCollection,
- m.loadReport
+ m.deleteCollectionAndItems
];
- if (!this.itemsView.rowCount) {
- disable = [m.createBibCollection, m.loadReport]
- if (this.collectionsView.isContainerEmpty(this.collectionsView.selection.currentIndex)) {
- disable.push(m.exportCollection);
- }
- }
-
if (collectionTreeRow.ref.unreadCount == 0) {
disable.push(m.markReadFeed);
}
// Adjust labels
menu.childNodes[m.deleteCollectionAndItems].setAttribute('label', Zotero.getString('pane.collections.menu.delete.feedAndItems'));
- menu.childNodes[m.exportCollection].setAttribute('label', Zotero.getString('pane.collections.menu.export.feed'));
- menu.childNodes[m.createBibCollection].setAttribute('label', Zotero.getString('pane.collections.menu.createBib.feed'));
- menu.childNodes[m.loadReport].setAttribute('label', Zotero.getString('pane.collections.menu.generateReport.feed'));
}
else if (collectionTreeRow.isSearch()) {
show = [
@@ -2464,11 +2451,13 @@ var ZoteroPane = new function()
else if (collectionTreeRow.isPublications()) {
show.push(m.deleteFromLibrary);
}
- else if (! collectionTreeRow.isFeed()) {
+ else if (!collectionTreeRow.isFeed()) {
show.push(m.moveToTrash);
}
-
- show.push(m.sep3, m.exportItems, m.createBib, m.loadReport);
+
+ if(!collectionTreeRow.isFeed()) {
+ show.push(m.sep3, m.exportItems, m.createBib, m.loadReport);
+ }
if (this.itemsView.selection.count > 0) {
// Multiple items selected
@@ -2523,9 +2512,9 @@ var ZoteroPane = new function()
if (canMarkRead) {
show.push(m.toggleRead);
if (markUnread) {
- menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.collections.menu.toggleRead.markUnread'));
+ menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.item.markAsUnread'));
} else {
- menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.collections.menu.toggleRead.markRead'));
+ menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.item.markAsRead'));
}
}
@@ -2618,9 +2607,9 @@ var ZoteroPane = new function()
else if (item.isFeedItem) {
show.push(m.toggleRead);
if (item.isRead) {
- menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.collections.menu.toggleRead.markUnread'));
+ menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.item.markAsUnread'));
} else {
- menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.collections.menu.toggleRead.markRead'));
+ menu.childNodes[m.toggleRead].setAttribute('label', Zotero.getString('pane.item.markAsRead'));
}
}
else {
@@ -2652,13 +2641,14 @@ var ZoteroPane = new function()
m.moveToTrash, m.deleteFromLibrary, m.exportItems, m.createBib, m.loadReport);
}
- if (!collectionTreeRow.editable || collectionTreeRow.isPublications()) {
+ if ((!collectionTreeRow.editable || collectionTreeRow.isPublications()) && !collectionTreeRow.isFeed()) {
for (let i in m) {
- // Still show export/bib/report for non-editable views
+ // Still allow export/bib/report/read for non-editable views
switch (i) {
case 'exportItems':
case 'createBib':
case 'loadReport':
+ case 'toggleRead':
continue;
}
if (isTrash) {
diff --git a/chrome/locale/en-US/zotero/preferences.dtd b/chrome/locale/en-US/zotero/preferences.dtd
index b6f345456c..5df5092219 100644
--- a/chrome/locale/en-US/zotero/preferences.dtd
+++ b/chrome/locale/en-US/zotero/preferences.dtd
@@ -40,9 +40,11 @@
-
-
-
+
+
+
+
+
diff --git a/chrome/locale/en-US/zotero/zotero.dtd b/chrome/locale/en-US/zotero/zotero.dtd
index d68c91a7ce..7a634ca0f0 100644
--- a/chrome/locale/en-US/zotero/zotero.dtd
+++ b/chrome/locale/en-US/zotero/zotero.dtd
@@ -108,7 +108,7 @@
-
+
@@ -262,11 +262,10 @@
-
-
+
+
-
diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties
index 53819021f7..e251df1c2d 100644
--- a/chrome/locale/en-US/zotero/zotero.properties
+++ b/chrome/locale/en-US/zotero/zotero.properties
@@ -161,8 +161,8 @@ pane.collections.delete = Are you sure you want to delete the selected collect
pane.collections.delete.keepItems = Items within this collection will not be deleted.
pane.collections.deleteWithItems.title = Delete Collection and Items
pane.collections.deleteWithItems = Are you sure you want to delete the selected collection and move all items within it to the Trash?
-pane.feed.deleteWithItems.title = Delete Feed and Items
-pane.feed.deleteWithItems = Are you sure you want to delete the selected feed and all items within it?
+pane.feed.deleteWithItems.title = Unsubscribe
+pane.feed.deleteWithItems = Are you sure you want to unsubscribe from this feed?
pane.collections.deleteSearch.title = Delete Search
pane.collections.deleteSearch = Are you sure you want to delete the selected search?
@@ -188,7 +188,7 @@ pane.collections.menu.edit.feed = Edit Feed…
pane.collections.menu.delete.collection = Delete Collection…
pane.collections.menu.delete.collectionAndItems = Delete Collection and Items…
pane.collections.menu.delete.savedSearch = Delete Saved Search…
-pane.collections.menu.delete.feedAndItems = Delete Feed and Items…
+pane.collections.menu.delete.feedAndItems = Unsubscribe From Feed…
pane.collections.menu.export.collection = Export Collection…
pane.collections.menu.export.savedSearch = Export Saved Search…
pane.collections.menu.export.feed = Export Feed…
@@ -201,8 +201,6 @@ pane.collections.menu.generateReport.savedSearch = Generate Report from Saved Se
pane.collections.menu.generateReport.feed = Generate Report from Feed…
pane.collections.menu.refresh.feed = Refresh Feed
-pane.collections.menu.toggleRead.markRead = Mark Read
-pane.collections.menu.toggleRead.markUnread = Mark Unread
pane.tagSelector.rename.title = Rename Tag
pane.tagSelector.rename.message = Please enter a new name for this tag.\n\nThe tag will be changed in all associated items.
@@ -273,6 +271,8 @@ pane.item.duplicates.writeAccessRequired = Library write access is required to m
pane.item.duplicates.onlyTopLevel = Only top-level full items can be merged.
pane.item.duplicates.onlySameItemType = Merged items must all be of the same item type.
+pane.item.markAsRead = Mark As Read
+pane.item.markAsUnread = Mark As Unread
pane.item.changeType.title = Change Item Type
pane.item.changeType.text = Are you sure you want to change the item type?\n\nThe following fields will be lost:
pane.item.defaultFirstName = first
@@ -506,7 +506,7 @@ save.link.error = An error occurred while saving this link.
save.error.cannotMakeChangesToCollection = You cannot make changes to the currently selected collection.
save.error.cannotAddFilesToCollection = You cannot add files to the currently selected collection.
save.error.cannotAddToMyPublications = You cannot save items directly to My Publications. To add items to My Publications, drag them from another library.
-save.error.cannotAddToFeed = You cannot save items directly to feeds.
+save.error.cannotAddToFeed = You cannot save items to feeds.
ingester.saveToZotero = Save to Zotero
ingester.saveToZoteroUsing = Save to Zotero using "%S"
diff --git a/chrome/skin/default/zotero/itemPane.css b/chrome/skin/default/zotero/itemPane.css
index ef7798afd3..99a56d8fc6 100644
--- a/chrome/skin/default/zotero/itemPane.css
+++ b/chrome/skin/default/zotero/itemPane.css
@@ -25,6 +25,11 @@
padding: 1.5em .25em .25em;
}
+#zotero-view-item.no-tabs
+{
+ padding: .25em .25em .25em;
+}
+
#zotero-view-item > tabpanel > *
{
overflow: auto;
diff --git a/chrome/skin/default/zotero/zotero.css b/chrome/skin/default/zotero/zotero.css
index 8960d45854..d17494e966 100644
--- a/chrome/skin/default/zotero/zotero.css
+++ b/chrome/skin/default/zotero/zotero.css
@@ -450,6 +450,10 @@ label.zotero-text-link {
transform: none;
}
+#zotero-feed-settings grid hbox:first-child {
+ -moz-box-pack: end;
+}
+
/* BEGIN 2X BLOCK -- DO NOT EDIT MANUALLY -- USE 2XIZE */
@media (min-resolution: 1.5dppx) {
#zotero-toolbar-save-button,#zotero-toolbar-save-button-single { list-style-image: url("chrome://zotero/skin/treeitem-webpage@2x.png"); }
diff --git a/defaults/preferences/zotero.js b/defaults/preferences/zotero.js
index 669fcaefdb..0e2cbd00ee 100644
--- a/defaults/preferences/zotero.js
+++ b/defaults/preferences/zotero.js
@@ -52,7 +52,9 @@ pref("extensions.zotero.groups.copyChildFileAttachments", true);
pref("extensions.zotero.groups.copyChildNotes", true);
pref("extensions.zotero.groups.copyTags", true);
-pref("extensions.zotero.feeds.sortAsc", false);
+pref("extensions.zotero.feeds.sortAscending", false);
+pref("extensions.zotero.feeds.defaultTTL", 1);
+pref("extensions.zotero.feeds.defaultCleanupAfter", 2);
pref("extensions.zotero.backup.numBackups", 2);
pref("extensions.zotero.backup.interval", 1440);
diff --git a/resource/schema/userdata.sql b/resource/schema/userdata.sql
index 12a5e33848..c388b6b6aa 100644
--- a/resource/schema/userdata.sql
+++ b/resource/schema/userdata.sql
@@ -203,7 +203,6 @@ CREATE TABLE feeds (
lastUpdate TIMESTAMP,
lastCheck TIMESTAMP,
lastCheckError TEXT,
- lastGUID TEXT,
cleanupAfter INT,
refreshInterval INT,
FOREIGN KEY (libraryID) REFERENCES libraries(libraryID) ON DELETE CASCADE
diff --git a/test/content/support.js b/test/content/support.js
index 760bb705d6..f1c8e5a7d0 100644
--- a/test/content/support.js
+++ b/test/content/support.js
@@ -385,11 +385,7 @@ function createUnsavedDataObject(objectType, params = {}) {
var createDataObject = Zotero.Promise.coroutine(function* (objectType, params = {}, saveOptions) {
var obj = createUnsavedDataObject(objectType, params);
- if (objectType == 'feedItem') {
- yield obj.forceSaveTx(saveOptions);
- } else {
- yield obj.saveTx(saveOptions);
- }
+ yield obj.saveTx(saveOptions);
return obj;
});
@@ -453,8 +449,12 @@ function getTestDataDirectory() {
QueryInterface(Components.interfaces.nsIFileURL).file;
}
-function getTestDataItemUrl(path) {
- return OS.Path.join("resource://zotero-unit-tests/data", path);
+function getTestDataUrl(path) {
+ path = path.split('/');
+ if (path[0].length == 0) {
+ path.splice(0, 1);
+ }
+ return "resource://zotero-unit-tests/data/" + path.join('/');
}
/**
diff --git a/test/tests/collectionTreeViewTest.js b/test/tests/collectionTreeViewTest.js
index 947249dc11..bc51fbe3ac 100644
--- a/test/tests/collectionTreeViewTest.js
+++ b/test/tests/collectionTreeViewTest.js
@@ -682,10 +682,10 @@ describe("Zotero.CollectionTreeView", function() {
it('should add a translated feed item recovered from an URL', function* (){
var feed = yield createFeed();
var collection = yield createDataObject('collection', false, { skipSelect: true });
- var url = getTestDataItemUrl('metadata/journalArticle-single.html');
+ var url = getTestDataUrl('metadata/journalArticle-single.html');
var feedItem = yield createDataObject('feedItem', {libraryID: feed.libraryID}, { skipSelect: true });
feedItem.setField('url', url);
- yield feedItem.forceSaveTx();
+ yield feedItem.saveTx();
var translateFn = sinon.spy(feedItem, 'translate');
// Add observer to wait for collection add
diff --git a/test/tests/data/feed.rss b/test/tests/data/feed.rss
index 8b732094ac..99319619d3 100644
--- a/test/tests/data/feed.rss
+++ b/test/tests/data/feed.rss
@@ -18,6 +18,7 @@
How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's <a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm">Star City</a>.
Tue, 03 Jun 2003 09:39:21 GMT
http://liftoff.msfc.nasa.gov/2003/06/03.html#item573
+
-
The Engine That Does More
diff --git a/test/tests/feedItemTest.js b/test/tests/feedItemTest.js
index ca0b4f5982..1d4573ba6b 100644
--- a/test/tests/feedItemTest.js
+++ b/test/tests/feedItemTest.js
@@ -23,11 +23,11 @@ describe("Zotero.FeedItem", function () {
it("should accept required fields as arguments", function* () {
let guid = Zotero.randomString();
let feedItem = new Zotero.FeedItem();
- yield assert.isRejected(feedItem.forceSaveTx());
+ yield assert.isRejected(feedItem.saveTx());
feedItem = new Zotero.FeedItem('book', { guid });
feedItem.libraryID = libraryID;
- yield assert.isFulfilled(feedItem.forceSaveTx());
+ yield assert.isFulfilled(feedItem.saveTx());
assert.equal(feedItem.itemTypeID, Zotero.ItemTypes.getID('book'));
assert.equal(feedItem.guid, guid);
@@ -83,7 +83,7 @@ describe("Zotero.FeedItem", function () {
expectedTimestamp = Date.now();
feedItem.isRead = true;
yield Zotero.Promise.delay(2001);
- yield feedItem.forceSaveTx();
+ yield feedItem.saveTx();
readTime = yield Zotero.DB.valueQueryAsync('SELECT readTime FROM feedItems WHERE itemID=?', feedItem.id);
readTime = Zotero.Date.sqlToDate(readTime, true).getTime();
@@ -107,41 +107,36 @@ describe("Zotero.FeedItem", function () {
})
});
describe("#save()", function() {
- it("should require edit check override", function* () {
- let feedItem = new Zotero.FeedItem('book', { guid: Zotero.randomString() });
- feedItem.libraryID = feed.libraryID;
- yield assert.isRejected(feedItem.saveTx(), /^Error: Cannot edit feedItem in read-only library/);
- });
it("should require feed being set", function* () {
let feedItem = new Zotero.FeedItem('book', { guid: Zotero.randomString() });
// Defaults to user library ID
- yield assert.isRejected(feedItem.forceSaveTx(), /^Error: Cannot add /);
+ yield assert.isRejected(feedItem.saveTx(), /^Error: Cannot add /);
});
it("should require GUID being set", function* () {
let feedItem = new Zotero.FeedItem('book');
feedItem.libraryID = feed.libraryID;
- yield assert.isRejected(feedItem.forceSaveTx(), /^Error: GUID must be set before saving FeedItem$/);
+ yield assert.isRejected(feedItem.saveTx(), /^Error: GUID must be set before saving FeedItem$/);
});
it("should require a unique GUID", function* () {
let guid = Zotero.randomString();
let feedItem1 = yield createDataObject('feedItem', { libraryID, guid });
let feedItem2 = createUnsavedDataObject('feedItem', { libraryID, guid });
- yield assert.isRejected(feedItem2.forceSaveTx());
+ yield assert.isRejected(feedItem2.saveTx());
// But we should be able to save it after deleting the original feed
- yield feedItem1.forceEraseTx();
- yield assert.isFulfilled(feedItem2.forceSaveTx());
+ yield feedItem1.eraseTx();
+ yield assert.isFulfilled(feedItem2.saveTx());
});
it("should require item type being set", function* () {
let feedItem = new Zotero.FeedItem(null, { guid: Zotero.randomString() });
feedItem.libraryID = feed.libraryID;
- yield assert.isRejected(feedItem.forceSaveTx(), /^Error: Item type must be set before saving$/);
+ yield assert.isRejected(feedItem.saveTx(), /^Error: Item type must be set before saving$/);
});
it("should save feed item", function* () {
let guid = Zotero.randomString();
let feedItem = createUnsavedDataObject('feedItem', { libraryID, guid });
- yield assert.isFulfilled(feedItem.forceSaveTx());
+ yield assert.isFulfilled(feedItem.saveTx());
feedItem = yield Zotero.FeedItems.getAsync(feedItem.id);
assert.ok(feedItem);
@@ -155,7 +150,7 @@ describe("Zotero.FeedItem", function () {
let feedItem = new Zotero.FeedItem(null, type, feed.libraryID);
feedItem.fromJSON(allTypesAndFields[type]);
- yield feedItem.forceSaveTx();
+ yield feedItem.saveTx();
feedItems.push(feedItem);
}
@@ -172,7 +167,7 @@ describe("Zotero.FeedItem", function () {
let feedItem = yield createDataObject('feedItem', { libraryID });
feedItem.setField('title', 'bar');
- yield assert.isFulfilled(feedItem.forceSaveTx());
+ yield assert.isFulfilled(feedItem.saveTx());
assert.equal(feedItem.getField('title'), 'bar');
});
});
@@ -180,15 +175,22 @@ describe("Zotero.FeedItem", function () {
it("should erase an existing feed item", function* () {
let feedItem = yield createDataObject('feedItem', { libraryID });
- yield feedItem.forceEraseTx();
+ yield feedItem.eraseTx();
assert.isFalse(yield Zotero.FeedItems.getAsync(feedItem.id));
- //yield assert.isRejected(feedItem.forceEraseTx(), "does not allow erasing twice");
+ //yield assert.isRejected(feedItem.EraseTx(), "does not allow erasing twice");
});
- it("should require edit check override to erase", function* () {
- let feedItem = yield createDataObject('feedItem', { libraryID });
+ it("should remove synced setting if exists", function* () {
+ let item = yield createDataObject('feedItem', { libraryID });
- yield assert.isRejected(feedItem.eraseTx(), /^Error: Cannot edit feedItem in read-only library/);
+ yield item.toggleRead();
+ let syncedSettings = feed.getSyncedSettings();
+ assert.ok(syncedSettings.markedAsRead[item.guid]);
+
+ yield item.eraseTx();
+
+ syncedSettings = feed.getSyncedSettings();
+ assert.notOk(syncedSettings.markedAsRead[item.guid]);
});
});
@@ -196,7 +198,7 @@ describe("Zotero.FeedItem", function () {
it('should toggle state', function* () {
let item = yield createDataObject('feedItem', { libraryID });
item.isRead = false;
- yield item.forceSaveTx();
+ yield item.saveTx();
yield item.toggleRead();
assert.isTrue(item.isRead, "item is toggled to read state");
@@ -204,7 +206,7 @@ describe("Zotero.FeedItem", function () {
it('should save if specified state is different from current', function* (){
let item = yield createDataObject('feedItem', { libraryID });
item.isRead = false;
- yield item.forceSaveTx();
+ yield item.saveTx();
sinon.spy(item, 'save');
yield item.toggleRead(true);
@@ -215,6 +217,17 @@ describe("Zotero.FeedItem", function () {
yield item.toggleRead(true);
assert.isFalse(item.save.called, "item was not saved on toggle read to same state");
});
+ it('should set relevant synced settings', function* () {
+ let item = yield createDataObject('feedItem', { libraryID });
+ item.isRead = false;
+ yield item.saveTx();
+
+ yield item.toggleRead();
+
+ let feed = Zotero.Feeds.get(item.libraryID);
+ let syncedSettings = feed.getSyncedSettings();
+ assert.ok(syncedSettings.markedAsRead[item.guid], "item marked as read stored in synced settings");
+ });
});
describe('#translate()', function() {
@@ -224,9 +237,9 @@ describe("Zotero.FeedItem", function () {
});
it('translates and saves items', function* () {
var feedItem = yield createDataObject('feedItem', {libraryID});
- var url = getTestDataItemUrl('metadata/journalArticle-single.html');
+ var url = getTestDataUrl('metadata/journalArticle-single.html');
feedItem.setField('url', url);
- yield feedItem.forceSaveTx();
+ yield feedItem.saveTx();
yield feedItem.translate();
@@ -237,9 +250,9 @@ describe("Zotero.FeedItem", function () {
let collection = yield createDataObject('collection', {libraryID: group.libraryID});
var feedItem = yield createDataObject('feedItem', {libraryID});
- var url = getTestDataItemUrl('metadata/journalArticle-single.html');
+ var url = getTestDataUrl('metadata/journalArticle-single.html');
feedItem.setField('url', url);
- yield feedItem.forceSaveTx();
+ yield feedItem.saveTx();
yield feedItem.translate(group.libraryID, collection.id);
diff --git a/test/tests/feedItemsTest.js b/test/tests/feedItemsTest.js
index 4626adef93..ab82e74003 100644
--- a/test/tests/feedItemsTest.js
+++ b/test/tests/feedItemsTest.js
@@ -7,6 +7,51 @@ describe("Zotero.FeedItems", function () {
return clearFeeds();
});
+ describe("#getMarkedAsRead", function() {
+ var items = [];
+ var result;
+ before(function* () {
+ for (let i = 0; i < 4; i++) {
+ let f = yield createDataObject('feedItem', {libraryID: feed.libraryID, guid: 'http://www.example.com/' + i});
+ items.push(f);
+ }
+ yield items[0].toggleRead();
+ yield items[2].toggleRead();
+ result = yield Zotero.FeedItems.getMarkedAsRead(feed.libraryID);
+ });
+ it('should get all marked as read items', function() {
+ assert.include(result, items[0]);
+ assert.include(result, items[2]);
+ });
+ it('should not include items that were not marked', function() {
+ assert.notInclude(result, items[1]);
+ assert.notInclude(result, items[3]);
+ });
+ });
+
+ describe("#markAsReadByGUID", function() {
+ var items = [];
+ var result;
+ before(function* () {
+ for (let i = 0; i < 4; i++) {
+ let f = yield createDataObject('feedItem', {
+ libraryID: feed.libraryID,
+ guid: 'http://' + Zotero.Utilities.randomString() + '.com/feed.rss'
+ });
+ items.push(f);
+ }
+ yield Zotero.FeedItems.markAsReadByGUID([items[0].guid, items[2].guid]);
+ });
+ it('should mark as read only specified guids', function() {
+ assert.isTrue(items[0].isRead);
+ assert.isTrue(items[2].isRead);
+ });
+ it('should leave other items marked unread', function() {
+ assert.isFalse(items[1].isRead);
+ assert.isFalse(items[3].isRead);
+ });
+ });
+
describe("#getIDFromGUID()", function() {
it("should return false for non-existent GUID", function* () {
let id = yield Zotero.FeedItems.getIDFromGUID(Zotero.randomString());
@@ -14,7 +59,7 @@ describe("Zotero.FeedItems", function () {
});
it("should return feed item id from GUID", function* () {
let feedItem = yield createDataObject('feedItem', { libraryID: feed.libraryID });
- yield feedItem.forceSaveTx();
+ yield feedItem.saveTx();
let id2 = yield Zotero.FeedItems.getIDFromGUID(feedItem.guid);
assert.equal(id2, feedItem.id);
@@ -24,7 +69,7 @@ describe("Zotero.FeedItems", function () {
it("should return feed item from GUID", function* () {
let guid = Zotero.randomString();
let feedItem = yield createDataObject('feedItem', { guid, libraryID: feed.libraryID });
- yield feedItem.forceSaveTx();
+ yield feedItem.saveTx();
let feedItem2 = yield Zotero.FeedItems.getAsyncByGUID(guid);
assert.equal(feedItem2.id, feedItem.id);
@@ -48,7 +93,7 @@ describe("Zotero.FeedItems", function () {
for (let i = 0; i < 10; i++) {
let item = yield createDataObject('feedItem', { guid: Zotero.randomString(), libraryID: feed.id });
item.isRead = true;
- yield item.forceSaveTx();
+ yield item.saveTx();
items.push(item);
}
ids = Array.map(items, (i) => i.id);
@@ -66,7 +111,7 @@ describe("Zotero.FeedItems", function () {
it('should toggle all items read if at least one unread', function* () {
items[0].isRead = false;
- yield items[0].forceSaveTx();
+ yield items[0].saveTx();
yield Zotero.FeedItems.toggleReadByID(ids);
@@ -85,7 +130,7 @@ describe("Zotero.FeedItems", function () {
it('should toggle all items unread if unread state specified', function* () {
items[0].isRead = false;
- yield items[0].forceSaveTx();
+ yield items[0].saveTx();
yield Zotero.FeedItems.toggleReadByID(ids, false);
@@ -93,5 +138,15 @@ describe("Zotero.FeedItems", function () {
assert.isFalse(save.thisValues[i].isRead, "#toggleRead called with true");
}
});
+
+ it('should set relevant sync settings', function* () {
+ items[0].isRead = false;
+ yield items[0].saveTx();
+ yield Zotero.FeedItems.toggleReadByID(ids);
+
+ let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
+ let markedAsRead = Object.keys(syncedFeeds[feed.url].markedAsRead);
+ assert.deepEqual(markedAsRead, Object.keys(items).map((k) => items[k].guid));
+ });
});
});
diff --git a/test/tests/feedReaderTest.js b/test/tests/feedReaderTest.js
index 9329303155..3e40e5f076 100644
--- a/test/tests/feedReaderTest.js
+++ b/test/tests/feedReaderTest.js
@@ -2,9 +2,9 @@
describe("Zotero.FeedReader", function () {
- var htmlUrl = getTestDataItemUrl("test.html");
+ var htmlUrl = getTestDataUrl("test.html");
- var feedUrl = getTestDataItemUrl("feed.rss");
+ var feedUrl = getTestDataUrl("feed.rss");
var feedInfo = {
title: 'Liftoff News',
subtitle: 'Liftoff to Space Exploration.',
@@ -18,7 +18,7 @@ describe("Zotero.FeedReader", function () {
language: 'en-us'
};
- var detailedFeedUrl = getTestDataItemUrl("feedDetailed.rss");
+ var detailedFeedUrl = getTestDataUrl("feedDetailed.rss");
var detailedFeedInfo = {
title: 'Feed',
subtitle: 'Feed Description',
@@ -106,20 +106,15 @@ describe("Zotero.FeedReader", function () {
});
it('should parse items correctly for a sparse feed', function* () {
- let expected = {
- guid: 'http://liftoff.msfc.nasa.gov/2003/06/03.html#item573',
+ let expected = { guid: 'http://liftoff.msfc.nasa.gov/2003/06/03.html#item573',
title: 'Star City',
abstractNote: 'How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia\'s Star City.',
url: 'http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp',
- creators: [{
- firstName: '',
- lastName: 'editor@example.com',
- creatorType: 'author',
- fieldMode: 1
- }],
+ creators: [{ firstName: '', lastName: 'editor@example.com', creatorType: 'author', fieldMode: 1 }],
date: 'Tue, 03 Jun 2003 09:39:21 GMT',
language: 'en-us',
- itemType: 'journalArticle'
+ itemType: 'journalArticle',
+ enclosedItems: [{ url: 'http://www.example.com/example.pdf', contentType: 'application/pdf' }]
};
let fr = new Zotero.FeedReader(feedUrl);
@@ -149,7 +144,8 @@ describe("Zotero.FeedReader", function () {
publisher: 'Publisher',
rights: '©2016 Published by Publisher',
language: 'en',
- itemType: 'journalArticle'
+ itemType: 'journalArticle',
+ enclosedItems: []
};
let fr = new Zotero.FeedReader(detailedFeedUrl);
diff --git a/test/tests/feedTest.js b/test/tests/feedTest.js
index ac37fd7d94..b3d39fb6b4 100644
--- a/test/tests/feedTest.js
+++ b/test/tests/feedTest.js
@@ -42,12 +42,11 @@ describe("Zotero.Feed", function() {
yield feed.saveTx();
assert.isFalse(feed.editable);
});
- it("should not allow adding items without editCheck override", function* () {
+ it("should allow adding items without editCheck override", function* () {
let feed = yield createFeed();
let feedItem = new Zotero.FeedItem('book', { guid: Zotero.randomString() });
feedItem.libraryID = feed.libraryID;
- yield assert.isRejected(feedItem.saveTx(), /^Error: Cannot edit feedItem in read-only library/);
- yield assert.isFulfilled(feedItem.saveTx({ skipEditCheck: true }));
+ yield assert.isFulfilled(feedItem.saveTx());
});
});
@@ -135,6 +134,31 @@ describe("Zotero.Feed", function() {
assert.equal(feed.name, 'bar');
assert.equal(dbVal, feed.name);
});
+ it("should add a new synced setting after creation", function* () {
+ let url = 'http://' + Zotero.Utilities.randomString(10, 'abcde') + '.com/feed.rss';
+
+ let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
+ assert.notOk(syncedFeeds[url]);
+
+ yield createFeed({url});
+
+ syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
+ assert.ok(syncedFeeds[url]);
+ });
+ it("should remove previous feed and add a new one if url changed", function* () {
+ let feed = yield createFeed();
+
+ let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
+ assert.ok(syncedFeeds[feed.url]);
+
+ let oldUrl = feed.url;
+ feed.url = 'http://' + Zotero.Utilities.randomString(10, 'abcde') + '.com/feed.rss';
+ yield feed.saveTx();
+
+ syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
+ assert.notOk(syncedFeeds[oldUrl]);
+ assert.ok(syncedFeeds[feed.url]);
+ });
});
describe("#erase()", function() {
it("should erase a saved feed", function* () {
@@ -160,10 +184,59 @@ describe("Zotero.Feed", function() {
assert.notOk(yield Zotero.FeedItems.getAsync(feedItem.id));
});
+ it("should remove synced settings", function* () {
+ let url = 'http://' + Zotero.Utilities.randomString(10, 'abcde') + '.com/feed.rss';
+ let feed = yield createFeed({url});
+
+ let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
+ assert.ok(syncedFeeds[feed.url]);
+
+ yield feed.eraseTx();
+
+ syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
+ assert.notOk(syncedFeeds[url]);
+
+ });
+ });
+
+ describe("#getSyncedSettings", function() {
+ it("should return correct synced settings for the feed", function* () {
+ let url = 'http://' + Zotero.Utilities.randomString(10, 'abcde') + '.com/feed.rss';
+ let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
+ assert.notOk(syncedFeeds[url]);
+
+ let feed = yield createFeed({url});
+
+ syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
+ assert.ok(syncedFeeds[url]);
+
+ let syncedData = feed.getSyncedSettings();
+
+ assert.deepEqual(syncedData, syncedFeeds[url]);
+ });
+ });
+
+ describe("#storeSyncedSettings", function() {
+ it("should store updated settings for the feed", function* () {
+ let guid = Zotero.Utilities.randomString();
+ let feed = yield createFeed();
+
+ let syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
+ assert.notOk(syncedFeeds[feed.url].markedAsRead[guid]);
+
+ let syncedData = feed.getSyncedSettings();
+ syncedData.markedAsRead[guid] = true;
+ yield feed.setSyncedSettings(syncedData);
+ yield feed.storeSyncedSettings();
+
+ syncedFeeds = Zotero.SyncedSettings.get(Zotero.Libraries.userLibraryID, 'feeds');
+
+ assert.isTrue(syncedFeeds[feed.url].markedAsRead[guid]);
+ });
});
describe("#clearExpiredItems()", function() {
- var feed, expiredFeedItem, readFeedItem, feedItem, feedItemIDs;
+ var feed, expiredFeedItem, readFeedItem, feedItem, readStillInFeed, feedItemIDs;
before(function* (){
feed = yield createFeed({cleanupAfter: 1});
@@ -173,11 +246,18 @@ describe("Zotero.Feed", function() {
expiredFeedItem.isRead = true;
expiredFeedItem._feedItemReadTime = Zotero.Date.dateToSQL(
new Date(Date.now() - 2 * 24*60*60*1000), true);
- yield expiredFeedItem.forceSaveTx();
+ yield expiredFeedItem.saveTx();
+
+ readStillInFeed = yield createDataObject('feedItem', { libraryID: feed.libraryID });
+ // Read 2 days ago
+ readStillInFeed.isRead = true;
+ readStillInFeed._feedItemReadTime = Zotero.Date.dateToSQL(
+ new Date(Date.now() - 2 * 24*60*60*1000), true);
+ yield readStillInFeed.saveTx();
readFeedItem = yield createDataObject('feedItem', { libraryID: feed.libraryID });
readFeedItem.isRead = true;
- yield readFeedItem.forceSaveTx();
+ yield readFeedItem.saveTx();
feedItem = yield createDataObject('feedItem', { libraryID: feed.libraryID });
@@ -186,8 +266,9 @@ describe("Zotero.Feed", function() {
assert.include(feedItemIDs, feedItem.id, "feed contains unread feed item");
assert.include(feedItemIDs, readFeedItem.id, "feed contains read feed item");
assert.include(feedItemIDs, expiredFeedItem.id, "feed contains expired feed item");
+ assert.include(feedItemIDs, readStillInFeed.id, "feed contains expired but still in rss feed item");
- yield feed.clearExpiredItems();
+ yield feed.clearExpiredItems(new Set([readStillInFeed.id]));
feedItemIDs = yield Zotero.FeedItems.getAll(feed.libraryID).map((row) => row.id);
});
@@ -198,7 +279,11 @@ describe("Zotero.Feed", function() {
it('should not clear read items that have not expired yet', function() {
assert.include(feedItemIDs, readFeedItem.id, "feed still contains new feed item");
- })
+ });
+
+ it('should not clear read items that are still in rss', function() {
+ assert.include(feedItemIDs, readStillInFeed.id, "feed still contains read still in rss feed item");
+ });
it('should not clear unread items', function() {
assert.include(feedItemIDs, feedItem.id, "feed still contains new feed item");
@@ -207,8 +292,8 @@ describe("Zotero.Feed", function() {
describe('#updateFeed()', function() {
var feed;
- var feedUrl = getTestDataItemUrl("feed.rss");
- var modifiedFeedUrl = getTestDataItemUrl("feedModified.rss");
+ var feedUrl = getTestDataUrl("feed.rss");
+ var modifiedFeedUrl = getTestDataUrl("feedModified.rss");
beforeEach(function* (){
feed = yield createFeed();
@@ -232,11 +317,12 @@ describe("Zotero.Feed", function() {
});
it('should add new feed items', function* () {
- let feedItems = yield Zotero.FeedItems.getAll(feed.id);
+ let feedItems = yield Zotero.FeedItems.getAll(feed.id, true);
assert.equal(feedItems.length, 3);
});
- it('should set lastCheck, lastUpdated and lastGUID values', function* () {
+ it('should set lastCheck and lastUpdated values', function* () {
+ yield clearFeeds();
let feed = yield createFeed();
feed._feedUrl = feedUrl;
@@ -245,15 +331,14 @@ describe("Zotero.Feed", function() {
yield feed.updateFeed();
- assert.ok(feed.lastCheck >= Zotero.Date.dateToSQL(new Date(Date.now() - 1000*60), true));
- assert.ok(feed.lastUpdate >= Zotero.Date.dateToSQL(new Date(Date.now() - 1000*60), true));
- assert.equal(feed.lastGUID, 'http://liftoff.msfc.nasa.gov/2003/06/03.html#item573:'+feed.id);
+ assert.isTrue(feed.lastCheck > Zotero.Date.dateToSQL(new Date(Date.now() - 1000*60), true), 'feed.lastCheck updated');
+ assert.isTrue(feed.lastUpdate > Zotero.Date.dateToSQL(new Date(Date.now() - 1000*60), true), 'feed.lastUpdate updated');
});
it('should update modified items and set unread', function* () {
- let feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573:"+feed.id);
+ let feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573");
feedItem.isRead = true;
- yield feedItem.forceSaveTx();
- feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573:"+feed.id);
+ yield feedItem.saveTx();
+ feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573");
assert.isTrue(feedItem.isRead);
let oldDateModified = feedItem.getField('date');
@@ -261,7 +346,7 @@ describe("Zotero.Feed", function() {
feed._feedUrl = modifiedFeedUrl;
yield feed.updateFeed();
- feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573:"+feed.id);
+ feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573");
assert.notEqual(oldDateModified, feedItem.getField('date'));
assert.isFalse(feedItem.isRead)
@@ -272,7 +357,7 @@ describe("Zotero.Feed", function() {
feed._feedUrl = modifiedFeedUrl;
yield feed.updateFeed();
- assert.equal(save.thisValues[0].guid, "http://liftoff.msfc.nasa.gov/2003/06/03.html#item573:"+feed.id);
+ assert.equal(save.thisValues[0].guid, "http://liftoff.msfc.nasa.gov/2003/06/03.html#item573");
save.restore();
});
it('should update unread count', function* () {
@@ -281,7 +366,7 @@ describe("Zotero.Feed", function() {
let feedItems = yield Zotero.FeedItems.getAll(feed.id);
for (let feedItem of feedItems) {
feedItem.isRead = true;
- yield feedItem.forceSaveTx();
+ yield feedItem.saveTx();
}
feed._feedUrl = modifiedFeedUrl;
@@ -289,20 +374,11 @@ describe("Zotero.Feed", function() {
assert.equal(feed.unreadCount, 2);
});
- it('should not re-add deleted items, but add new ones', function* () {
- let feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/05/20.html#item570:"+feed.id);
- yield feedItem.forceEraseTx();
+ it('should add a link to enclosed pdfs from elements', function* () {
+ let feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573");
+ let pdf = yield Zotero.Items.getAsync(feedItem.getAttachments()[0]);
- let feedItems = yield Zotero.FeedItems.getAll(feed.id);
- for (let feedItem of feedItems) {
- feedItem.isRead = true;
- yield feedItem.forceSaveTx();
- }
-
- feed._feedUrl = modifiedFeedUrl;
- yield feed.updateFeed();
-
- assert.equal(feed.unreadCount, 2);
+ assert.equal(pdf.getField('url'), "http://www.example.com/example.pdf");
});
});
@@ -311,11 +387,6 @@ describe("Zotero.Feed", function() {
before(function* () {
feed = yield createFeed();
})
- it("should not allow adding regular items", function* () {
- let item = new Zotero.Item('book');
- item.libraryID = feed.libraryID;
- yield assert.isRejected(item.saveTx({ skipEditCheck: true }), /^Error: Cannot add /);
- });
it("should not allow adding collections", function* () {
let collection = new Zotero.Collection({ name: 'test', libraryID: feed.libraryID });
yield assert.isRejected(collection.saveTx({ skipEditCheck: true }), /^Error: Cannot add /);
@@ -327,7 +398,7 @@ describe("Zotero.Feed", function() {
it("should allow adding feed item", function* () {
let feedItem = new Zotero.FeedItem('book', { guid: Zotero.randomString() });
feedItem.libraryID = feed.libraryID;
- yield assert.isFulfilled(feedItem.forceSaveTx());
+ yield assert.isFulfilled(feedItem.saveTx());
});
});
})
diff --git a/test/tests/feedsTest.js b/test/tests/feedsTest.js
index 7afcfc7668..a0243b571b 100644
--- a/test/tests/feedsTest.js
+++ b/test/tests/feedsTest.js
@@ -3,6 +3,77 @@ describe("Zotero.Feeds", function () {
after(function* () {
yield clearFeeds();
});
+
+ describe("#restoreFromJSON", function() {
+ var json = {};
+ var expiredFeedURL, existingFeedURL;
+
+ before(function() {
+ sinon.stub(Zotero.Feed.prototype, 'updateFeed').resolves();
+ });
+
+ after(function() {
+ Zotero.Feed.prototype.updateFeed.restore();
+ });
+
+ beforeEach(function* () {
+ yield clearFeeds();
+
+ for (let i = 0; i < 2; i++) {
+ let url = "http://" + Zotero.Utilities.randomString(10, 'abcdefgh') + ".com/feed.rss";
+ json[url] = {
+ url,
+ name: Zotero.Utilities.randomString(),
+ refreshInterval: 5,
+ cleanupAfter: 3,
+ markedAsRead: []
+ };
+ if (i == 0) {
+ existingFeedURL = url;
+ yield createFeed({url});
+ }
+ }
+ expiredFeedURL = (yield createFeed()).url;
+ });
+
+ it("restores correctly when merge is true", function* () {
+ let feeds = Zotero.Feeds.getAll();
+ assert.equal(feeds.length, 2);
+
+ yield Zotero.Feeds.restoreFromJSON(json, true);
+ feeds = Zotero.Feeds.getAll();
+
+ for (let url in json) {
+ let feed = Zotero.Feeds.getByURL(url);
+ assert.ok(feed, "new feed created");
+ }
+
+ let expiredFeed = Zotero.Feeds.getByURL(expiredFeedURL);
+ assert.ok(expiredFeed, "does not remove feeds not in JSON");
+
+ let existingFeed = Zotero.Feeds.getByURL(existingFeedURL);
+ assert.ok(existingFeed, "does not remove feeds in database and JSON");
+ });
+
+ it("restores correctly when merge is false", function* () {
+ let feeds = Zotero.Feeds.getAll();
+ assert.equal(feeds.length, 2);
+
+ yield Zotero.Feeds.restoreFromJSON(json);
+ feeds = Zotero.Feeds.getAll();
+
+ for (let url in json) {
+ let feed = Zotero.Feeds.getByURL(url);
+ assert.ok(feed, "new feed created");
+ }
+
+ let expiredFeed = Zotero.Feeds.getByURL(expiredFeedURL);
+ assert.notOk(expiredFeed, "removes feeds not in JSON");
+
+ let existingFeed = Zotero.Feeds.getByURL(existingFeedURL);
+ assert.ok(existingFeed, "does not remove feeds in database and JSON");
+ });
+ });
describe("#haveFeeds()", function() {
it("should return false for a DB without feeds", function* () {
@@ -40,6 +111,23 @@ describe("Zotero.Feeds", function () {
assert.sameMembers(feeds, [feed1, feed2]);
});
});
+
+ describe('#getByURL', function() {
+ it("should return a feed by url", function* () {
+ let url = 'http://' + Zotero.Utilities.randomString(10, 'abcdefg') + '.com/feed.rss';
+ yield createFeed({url});
+ let feed = Zotero.Feeds.getByURL(url);
+ assert.ok(feed);
+ assert.equal(feed.url, url);
+ });
+ it("should return undefined if feed does not exist", function* () {
+ var feed;
+ assert.doesNotThrow(function() {
+ feed = Zotero.Feeds.getByURL('doesnotexist');
+ });
+ assert.isUndefined(feed);
+ });
+ });
describe('#updateFeeds', function() {
var freshFeed, recentFeed, oldFeed;
var _updateFeed;
@@ -49,7 +137,7 @@ describe("Zotero.Feeds", function () {
sinon.stub(Zotero.Feeds, 'scheduleNextFeedCheck');
_updateFeed = sinon.stub(Zotero.Feed.prototype, '_updateFeed').resolves();
- let url = getTestDataItemUrl("feed.rss");
+ let url = getTestDataUrl("feed.rss");
freshFeed = yield createFeed({refreshInterval: 2});
freshFeed._feedUrl = url;
@@ -97,7 +185,7 @@ describe("Zotero.Feeds", function () {
break;
}
// should never reach
- assert.isOk(null, "does not update feed that did not need updating")
+ assert.ok(null, "does not update feed that did not need updating")
}
});
});
diff --git a/test/tests/itemsTest.js b/test/tests/itemsTest.js
index 486c12a9d7..c97512bfb7 100644
--- a/test/tests/itemsTest.js
+++ b/test/tests/itemsTest.js
@@ -131,7 +131,7 @@ describe("Zotero.Items", function () {
let feedItem = new Zotero.FeedItem('journalArticle', { guid: Zotero.randomString() });
feedItem.libraryID = feed.libraryID;
- let id = yield feedItem.forceSaveTx();
+ let id = yield feedItem.saveTx();
feedItem = yield Zotero.Items.getAsync(id);