From 2c3eb205ab35239dcd23e041dafd21bb5800cc9b Mon Sep 17 00:00:00 2001 From: Aurimas Vinckevicius Date: Sun, 14 Dec 2014 23:07:37 -0600 Subject: [PATCH] Implement read/unread functionality in feeds --- .../zotero/xpcom/collectionTreeView.js | 18 +++- chrome/content/zotero/xpcom/data/feed.js | 29 +++++-- chrome/content/zotero/xpcom/data/feedItem.js | 45 ++++++++++ chrome/content/zotero/xpcom/data/feedItems.js | 13 +++ chrome/content/zotero/xpcom/data/feeds.js | 25 +++++- chrome/content/zotero/xpcom/data/item.js | 3 + chrome/content/zotero/xpcom/itemTreeView.js | 72 +++++----------- chrome/content/zotero/xpcom/notifier.js | 5 +- chrome/content/zotero/zoteroPane.js | 86 +++++++++++-------- chrome/skin/default/zotero/overlay.css | 6 ++ 10 files changed, 204 insertions(+), 98 deletions(-) diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js index 0a3df01cf..8ab70b8aa 100644 --- a/chrome/content/zotero/xpcom/collectionTreeView.js +++ b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -50,6 +50,7 @@ Zotero.CollectionTreeView = function() 'publications', 'share', 'group', + 'feedItem', 'trash', 'bucket' ], @@ -225,10 +226,10 @@ Zotero.CollectionTreeView.prototype.refresh = Zotero.Promise.coroutine(function* }, 0), added++ ); - for (let i = 0, len = groups.length; i < len; i++) { + for (let feed of feeds) { this._addRowToArray( newRows, - new Zotero.CollectionTreeRow('feed', feeds[i]), + new Zotero.CollectionTreeRow('feed', feed), added++ ); } @@ -251,10 +252,10 @@ Zotero.CollectionTreeView.prototype.refresh = Zotero.Promise.coroutine(function* }, 0), added++ ); - for (let i = 0, len = groups.length; i < len; i++) { + for (let group of groups) { this._addRowToArray( newRows, - new Zotero.CollectionTreeRow('group', groups[i]), + new Zotero.CollectionTreeRow('group', group), added++ ); } @@ -314,6 +315,13 @@ Zotero.CollectionTreeView.prototype.selectWait = Zotero.Promise.method(function * Called by Zotero.Notifier on any changes to collections in the data layer */ Zotero.CollectionTreeView.prototype.notify = Zotero.Promise.coroutine(function* (action, type, ids, extraData) { + if (type == 'feed' && action == 'unreadCountUpdated') { + for (let i=0; i 0"; + + "WHERE readTime IS NOT NULL " + + "AND (julianday(readTime, 'utc') + (?) - julianday('now', 'utc')) > 0"; let expiredIDs = yield Zotero.DB.queryAsync(sql, [{int: this.cleanupAfter}]); return expiredIDs.map(row => row.id); }); @@ -289,7 +290,7 @@ Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () { Zotero.debug("Cleaning up read feed items..."); if (expiredItems.length) { Zotero.debug(expiredItems.join(', ')); - yield Zotero.FeedItems.erase(expiredItems); + yield Zotero.FeedItems.eraseTx(expiredItems); } else { Zotero.debug("No expired feed items"); } @@ -301,7 +302,7 @@ Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () { try { let fr = new Zotero.FeedReader(this.url); - let itemIterator = fr.createItemIterator(); + let itemIterator = fr.itemIterator; let item, toAdd = [], processedGUIDs = []; while (item = yield itemIterator.next().value) { if (item.dateModified && this.lastUpdate @@ -320,14 +321,14 @@ Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () { } processedGUIDs.push(item.guid); - Zotero.debug("New feed item retrieved:"); - Zotero.debug(item); + Zotero.debug("New feed item retrieved:", 5); + Zotero.debug(item, 5); let feedItem = yield Zotero.FeedItems.getAsyncByGUID(item.guid); if (!feedItem) { feedItem = new Zotero.FeedItem(); feedItem.guid = item.guid; - feedItem.setCollections([this.id]); + feedItem.libraryID = this.id; } else { Zotero.debug("Feed item " + item.guid + " already in library."); if (item.dateModified && feedItem.dateModified @@ -364,7 +365,7 @@ Zotero.Feed.prototype._updateFeed = Zotero.Promise.coroutine(function* () { this.lastCheck = Zotero.Date.dateToSQL(new Date(), true); this.lastCheckError = errorMessage || null; - yield this.save({skipEditCheck: true}); + yield this.saveTx({skipEditCheck: true}); }); Zotero.Feed.prototype.updateFeed = function() { @@ -380,3 +381,15 @@ Zotero.Feed.prototype.erase = Zotero.Promise.coroutine(function* () { yield Zotero.FeedItems.erase(childItemIDs); return Zotero.Feed._super.prototype.erase.call(this); // Don't tell it to delete child items. They're already gone }) + +Zotero.Feed.prototype.updateUnreadCount = Zotero.Promise.coroutine(function* () { + let sql = "SELECT " + this._ObjectsClass._primaryDataSQLParts.feedUnreadCount + + this._ObjectsClass.primaryDataSQLFrom + + " AND O.libraryID=?"; + let newCount = yield Zotero.DB.valueQueryAsync(sql, [this.id]); + + if (newCount != this._feedUnreadCount) { + this._feedUnreadCount = newCount; + Zotero.Notifier.trigger('unreadCountUpdated', 'feed', this.id); + } +}); diff --git a/chrome/content/zotero/xpcom/data/feedItem.js b/chrome/content/zotero/xpcom/data/feedItem.js index 08d66bd72..f50de3260 100644 --- a/chrome/content/zotero/xpcom/data/feedItem.js +++ b/chrome/content/zotero/xpcom/data/feedItem.js @@ -132,6 +132,51 @@ Zotero.FeedItem.prototype._saveData = Zotero.Promise.coroutine(function* (env) { yield Zotero.DB.queryAsync(sql, [env.id, this.guid, this._feedItemReadTime]); this._clearChanged('feedItemData'); + +/* let itemID; + if (env.isNew) { + // For new items, run this first so we get an item ID + yield Zotero.FeedItem._super.prototype._saveData.apply(this, arguments); + itemID = env.id; + } else { + itemID = this.id; + } + + } + + if (!env.isNew) { + if (this.hasChanged()) { + yield Zotero.FeedItem._super.prototype._saveData.apply(this, arguments); + } else { + env.skipPrimaryDataReload = true; + } + Zotero.Notifier.trigger('modify', 'feedItem', itemID); + } else { + Zotero.Notifier.trigger('add', 'feedItem', itemID); + } + + if (env.collectionsAdded || env.collectionsRemoved) { + let affectedCollections = (env.collectionsAdded || []) + .concat(env.collectionsRemoved || []); + if (affectedCollections.length) { + let feeds = yield Zotero.Feeds.getAsync(affectedCollections); + for (let i=0; i Zotero.Libraries.get(id)); } + this.get = Zotero.Libraries.get; + this.haveFeeds = function() { if (!this._cache) throw new Error("Zotero.Feeds cache is not initialized"); return !!Object.keys(this._cache.urlByLibraryID).length } + let globalFeedCheckDelay = Zotero.Promise.resolve(); this.scheduleNextFeedCheck = Zotero.Promise.coroutine(function* () { Zotero.debug("Scheduling next feed update."); let sql = "SELECT ( CASE " @@ -146,4 +149,24 @@ Zotero.Feeds = new function() { Zotero.debug("No feeds with auto-update."); } }); + + this.updateFeeds = Zotero.Promise.coroutine(function* () { + let sql = "SELECT libraryID AS id FROM feeds " + + "WHERE refreshInterval IS NOT NULL " + + "AND ( lastCheck IS NULL " + + "OR (julianday(lastCheck, 'utc') + (refreshInterval/1440) - julianday('now', 'utc')) <= 0 )"; + let needUpdate = yield Zotero.DB.queryAsync(sql).map(row => row.id); + Zotero.debug("Running update for feeds: " + needUpdate.join(', ')); + let feeds = Zotero.Libraries.get(needUpdate); + let updatePromises = []; + for (let i=0; i { + Zotero.debug("All feed updates done."); + this.scheduleNextFeedCheck() + }); + }); } diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 81d8c3e1c..341f290b0 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -1660,6 +1660,9 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) { let toAdd = Zotero.Utilities.arrayDiff(newCollections, oldCollections); let toRemove = Zotero.Utilities.arrayDiff(oldCollections, newCollections); + + env.collectionsAdded = toAdd; + env.collectionsRemoved = toRemove; if (toAdd.length) { for (let i=0; i { itemReadTimeout = null; // Check to make sure we're still on the same item @@ -4315,7 +4325,15 @@ var ZoteroPane = new function() let row = this.itemsView.getRow(this.itemsView.selection.currentIndex); if (!row || !row.ref || !row.ref.id == feedItemID) return; - return this.markItemRead(feedItemID, true); + return feedItem.toggleRead(true); + }) + .catch(function(e) { + if (e instanceof Zotero.Promise.CancellationError) { + Zotero.debug(e.message); + return; + } + + Zotero.debug(e, 1); }); } diff --git a/chrome/skin/default/zotero/overlay.css b/chrome/skin/default/zotero/overlay.css index fd6dd68f6..8d335dab2 100644 --- a/chrome/skin/default/zotero/overlay.css +++ b/chrome/skin/default/zotero/overlay.css @@ -215,6 +215,12 @@ color: inherit; } +/* Style unread items/collections in bold */ +#zotero-items-tree treechildren::-moz-tree-cell-text(unread), +#zotero-collections-tree treechildren::-moz-tree-cell-text(unread) { + font-weight: bold; +} + #zotero-items-pane { min-width: 290px;