diff --git a/chrome/content/zotero/browser.js b/chrome/content/zotero/browser.js index 204a68802..8dcd1b1a6 100644 --- a/chrome/content/zotero/browser.js +++ b/chrome/content/zotero/browser.js @@ -640,6 +640,14 @@ var Zotero_Browser = new function() { return; } + if (Zotero.Feeds.get(libraryID)) { + Zotero_Browser.progress.changeHeadline(Zotero.getString("ingester.scrapeError")); + Zotero_Browser.progress.addDescription(Zotero.getString('save.error.cannotAddToFeed')); + Zotero_Browser.progress.show(); + Zotero_Browser.progress.startCloseTimer(8000); + return; + } + if(Zotero.isConnector) { Zotero.Connector.callMethod("getSelectedCollection", {}, function(response, status) { if(status !== 200) { diff --git a/chrome/content/zotero/xpcom/collectionTreeView.js b/chrome/content/zotero/xpcom/collectionTreeView.js index 1136b191f..22b7703e2 100644 --- a/chrome/content/zotero/xpcom/collectionTreeView.js +++ b/chrome/content/zotero/xpcom/collectionTreeView.js @@ -1471,6 +1471,10 @@ Zotero.CollectionTreeView.prototype.canDropCheck = function (row, orient, dataTr Zotero.debug("Top-level attachments and notes cannot be added to My Publications"); return false; } + if(item instanceof Zotero.FeedItem) { + Zotero.debug("FeedItems cannot be added to My Publications"); + return false; + } skip = false; continue; } @@ -1933,8 +1937,25 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r return; } + var items = yield Zotero.Items.getAsync(ids); + if (!items) { + return; + } + + if (items[0] instanceof Zotero.FeedItem) { + if (!(targetTreeRow.isCollection() || targetTreeRow.isLibrary() || targetTreeRow.isGroup())) { + return; + } + + let promises = []; + for (let item of items) { + // No transaction, because most time is spent traversing urls + promises.push(item.translate(targetLibraryID, targetCollectionID)) + } + return Zotero.Promise.all(promises); + } + if (targetTreeRow.isPublications()) { - let items = yield Zotero.Items.getAsync(ids); let io = this._treebox.treeBody.ownerDocument.defaultView .ZoteroPane.showPublicationsWizard(items); if (!io) { @@ -1950,11 +1971,6 @@ Zotero.CollectionTreeView.prototype.drop = Zotero.Promise.coroutine(function* (r } yield Zotero.DB.executeTransaction(function* () { - var items = yield Zotero.Items.getAsync(ids); - if (!items) { - return; - } - var newItems = []; var newIDs = []; var toMove = []; diff --git a/chrome/content/zotero/xpcom/data/dataObjects.js b/chrome/content/zotero/xpcom/data/dataObjects.js index 958392d4b..5b8ac8331 100644 --- a/chrome/content/zotero/xpcom/data/dataObjects.js +++ b/chrome/content/zotero/xpcom/data/dataObjects.js @@ -68,6 +68,10 @@ Zotero.defineProperty(Zotero.DataObjects.prototype, 'table', { get: function() this._ZDO_table }); +Zotero.defineProperty(Zotero.DataObjects.prototype, 'relationsTable', { + get: function() this._ZDO_object + 'Relations' +}); + Zotero.defineProperty(Zotero.DataObjects.prototype, 'primaryFields', { get: function () Object.keys(this._primaryDataSQLParts) }, {lazy: true}); diff --git a/chrome/content/zotero/xpcom/data/feedItem.js b/chrome/content/zotero/xpcom/data/feedItem.js index 4ba805131..c1143f176 100644 --- a/chrome/content/zotero/xpcom/data/feedItem.js +++ b/chrome/content/zotero/xpcom/data/feedItem.js @@ -207,9 +207,9 @@ Zotero.FeedItem.prototype.forceEraseTx = function(options) { * in the library * @param libraryID {int} save item in library * @param collectionID {int} add item to collection - * @return {Promise} translated feed item + * @return {Promise} translated feed item */ -Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (libaryID, collectionID) { +Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (libraryID, collectionID) { let deferred = Zotero.Promise.defer(); let error = function(e) { Zotero.debug(e, 1); deferred.reject(e); }; @@ -237,6 +237,12 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (libary translate.setTranslator(translators[0]); deferred = Zotero.Promise.defer(); + + if (libraryID) { + return translate.translate({libraryID, collections: collectionID ? [collectionID] : false}) + .then(items => items ? items[0] : false); + } + // Clear these to prevent saving translate.clearHandlers('itemDone'); translate.clearHandlers('itemsDone'); @@ -253,7 +259,10 @@ Zotero.FeedItem.prototype.translate = Zotero.Promise.coroutine(function* (libary for (let field of deleteFields) { delete itemData[field]; } + // TODO: handle no items like the ones in french history studies feed // set new translated data for item this.fromJSON(itemData); this.forceSaveTx(); + + return this; }); diff --git a/chrome/content/zotero/xpcom/data/feeds.js b/chrome/content/zotero/xpcom/data/feeds.js index 909636e04..7e788df4b 100644 --- a/chrome/content/zotero/xpcom/data/feeds.js +++ b/chrome/content/zotero/xpcom/data/feeds.js @@ -105,7 +105,10 @@ Zotero.Feeds = new function() { .map(id => Zotero.Libraries.get(id)); } - this.get = Zotero.Libraries.get.bind(Zotero.Libraries); + this.get = function(libraryID) { + let library = Zotero.Libraries.get(libraryID); + return library.isFeed ? library : undefined; + } this.haveFeeds = function() { if (!this._cache) throw new Error("Zotero.Feeds cache is not initialized"); diff --git a/chrome/content/zotero/xpcom/data/relations.js b/chrome/content/zotero/xpcom/data/relations.js index ff51bf079..66068c770 100644 --- a/chrome/content/zotero/xpcom/data/relations.js +++ b/chrome/content/zotero/xpcom/data/relations.js @@ -219,8 +219,8 @@ Zotero.Relations = new function () { // Get all object URIs except merge-tracking ones let sql = "SELECT " + objectsClass.idColumn + " AS id, predicate, object " - + "FROM " + type + "Relations " - + "JOIN relationPredicates USING (predicateID) WHERE predicate != ?"; + + "FROM " + objectsClass.relationsTable + + " JOIN relationPredicates USING (predicateID) WHERE predicate != ?"; let rows = yield Zotero.DB.queryAsync(sql, [this.replacedItemPredicate]); for (let i = 0; i < rows.length; i++) { let row = rows[i]; diff --git a/chrome/content/zotero/xpcom/feedReader.js b/chrome/content/zotero/xpcom/feedReader.js index f74fc1a4d..a66a42f6b 100644 --- a/chrome/content/zotero/xpcom/feedReader.js +++ b/chrome/content/zotero/xpcom/feedReader.js @@ -35,7 +35,7 @@ * http://rss.sciencedirect.com/publication/science/20925212 * http://www.ncbi.nlm.nih.gov/entrez/eutils/erss.cgi?rss_guid=1fmfIeN4X5Q8HemTZD5Rj6iu6-FQVCn7xc7_IPIIQtS1XiD9bf * http://export.arxiv.org/rss/astro-ph - * http://fhs.dukejournals.org/rss_feeds/recent.xml TODO: refreshing unreads all items + * http://fhs.dukejournals.org/rss_feeds/recent.xml */ /** diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index d7cf32ae5..b36d084a3 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -1377,7 +1377,7 @@ var ZoteroPane = new function() if (item.isFeedItem) { item.translate(); - this.startItemReadTimeout.bind(this, item.id); + this.startItemReadTimeout(item.id); } } } diff --git a/chrome/locale/en-US/zotero/zotero.properties b/chrome/locale/en-US/zotero/zotero.properties index fc537182e..68cd8351b 100644 --- a/chrome/locale/en-US/zotero/zotero.properties +++ b/chrome/locale/en-US/zotero/zotero.properties @@ -504,6 +504,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. ingester.saveToZotero = Save to Zotero ingester.saveToZoteroUsing = Save to Zotero using "%S" diff --git a/test/tests/collectionTreeViewTest.js b/test/tests/collectionTreeViewTest.js index f94a964b3..947249dc1 100644 --- a/test/tests/collectionTreeViewTest.js +++ b/test/tests/collectionTreeViewTest.js @@ -609,72 +609,106 @@ describe("Zotero.CollectionTreeView", function() { // TODO: Check deeper subcollection open states }) - }) - - it("should move a subcollection and its subcollection up under another collection", function* () { - var collectionA = yield createDataObject('collection', { name: "A" }, { skipSelect: true }); - var collectionB = yield createDataObject('collection', { name: "B", parentKey: collectionA.key }); - var collectionC = yield createDataObject('collection', { name: "C", parentKey: collectionB.key }); - var collectionD = yield createDataObject('collection', { name: "D" }, { skipSelect: true }); - var collectionE = yield createDataObject('collection', { name: "E" }, { skipSelect: true }); - var collectionF = yield createDataObject('collection', { name: "F" }, { skipSelect: true }); - var collectionG = yield createDataObject('collection', { name: "G", parentKey: collectionE.key }); - var collectionH = yield createDataObject('collection', { name: "H", parentKey: collectionG.key }); - - var colIndexA = cv.getRowIndexByID('C' + collectionA.id); - var colIndexB = cv.getRowIndexByID('C' + collectionB.id); - var colIndexC = cv.getRowIndexByID('C' + collectionC.id); - var colIndexD = cv.getRowIndexByID('C' + collectionD.id); - var colIndexE = cv.getRowIndexByID('C' + collectionE.id); - var colIndexF = cv.getRowIndexByID('C' + collectionF.id); - var colIndexG = cv.getRowIndexByID('C' + collectionG.id); - var colIndexH = cv.getRowIndexByID('C' + collectionH.id); - - yield cv.selectCollection(collectionG.id); - - // Add observer to wait for collection add - var deferred = Zotero.Promise.defer(); - var observerID = Zotero.Notifier.registerObserver({ - notify: function (event, type, ids, extraData) { - if (type == 'collection' && event == 'modify' && ids[0] == collectionG.id) { - setTimeout(function () { - deferred.resolve(); - }, 50); + + it("should move a subcollection and its subcollection up under another collection", function* () { + var collectionA = yield createDataObject('collection', { name: "A" }, { skipSelect: true }); + var collectionB = yield createDataObject('collection', { name: "B", parentKey: collectionA.key }); + var collectionC = yield createDataObject('collection', { name: "C", parentKey: collectionB.key }); + var collectionD = yield createDataObject('collection', { name: "D" }, { skipSelect: true }); + var collectionE = yield createDataObject('collection', { name: "E" }, { skipSelect: true }); + var collectionF = yield createDataObject('collection', { name: "F" }, { skipSelect: true }); + var collectionG = yield createDataObject('collection', { name: "G", parentKey: collectionE.key }); + var collectionH = yield createDataObject('collection', { name: "H", parentKey: collectionG.key }); + + var colIndexA = cv.getRowIndexByID('C' + collectionA.id); + var colIndexB = cv.getRowIndexByID('C' + collectionB.id); + var colIndexC = cv.getRowIndexByID('C' + collectionC.id); + var colIndexD = cv.getRowIndexByID('C' + collectionD.id); + var colIndexE = cv.getRowIndexByID('C' + collectionE.id); + var colIndexF = cv.getRowIndexByID('C' + collectionF.id); + var colIndexG = cv.getRowIndexByID('C' + collectionG.id); + var colIndexH = cv.getRowIndexByID('C' + collectionH.id); + + yield cv.selectCollection(collectionG.id); + + // Add observer to wait for collection add + var deferred = Zotero.Promise.defer(); + var observerID = Zotero.Notifier.registerObserver({ + notify: function (event, type, ids, extraData) { + if (type == 'collection' && event == 'modify' && ids[0] == collectionG.id) { + setTimeout(function () { + deferred.resolve(); + }, 50); + } } - } - }, 'collection', 'test'); - - yield Zotero.Promise.delay(2000); - - yield drop( - 'collection', - { - row: colIndexD, - orient: 0 - }, - [collectionG.id], - deferred.promise - ); - - Zotero.Notifier.unregisterObserver(observerID); - - var newColIndexA = cv.getRowIndexByID('C' + collectionA.id); - var newColIndexB = cv.getRowIndexByID('C' + collectionB.id); - var newColIndexC = cv.getRowIndexByID('C' + collectionC.id); - var newColIndexD = cv.getRowIndexByID('C' + collectionD.id); - var newColIndexE = cv.getRowIndexByID('C' + collectionE.id); - var newColIndexF = cv.getRowIndexByID('C' + collectionF.id); - var newColIndexG = cv.getRowIndexByID('C' + collectionG.id); - var newColIndexH = cv.getRowIndexByID('C' + collectionH.id); - - assert.isFalse(cv.isContainerOpen(newColIndexE)); - assert.isTrue(cv.isContainerEmpty(newColIndexE)); - assert.isTrue(cv.isContainerOpen(newColIndexD)); - assert.isFalse(cv.isContainerEmpty(newColIndexD)); - assert.equal(newColIndexD, newColIndexG - 1); - assert.equal(newColIndexG, newColIndexH - 1); - - // TODO: Check deeper subcollection open states + }, 'collection', 'test'); + + yield Zotero.Promise.delay(2000); + + yield drop( + 'collection', + { + row: colIndexD, + orient: 0 + }, + [collectionG.id], + deferred.promise + ); + + Zotero.Notifier.unregisterObserver(observerID); + + var newColIndexA = cv.getRowIndexByID('C' + collectionA.id); + var newColIndexB = cv.getRowIndexByID('C' + collectionB.id); + var newColIndexC = cv.getRowIndexByID('C' + collectionC.id); + var newColIndexD = cv.getRowIndexByID('C' + collectionD.id); + var newColIndexE = cv.getRowIndexByID('C' + collectionE.id); + var newColIndexF = cv.getRowIndexByID('C' + collectionF.id); + var newColIndexG = cv.getRowIndexByID('C' + collectionG.id); + var newColIndexH = cv.getRowIndexByID('C' + collectionH.id); + + assert.isFalse(cv.isContainerOpen(newColIndexE)); + assert.isTrue(cv.isContainerEmpty(newColIndexE)); + assert.isTrue(cv.isContainerOpen(newColIndexD)); + assert.isFalse(cv.isContainerEmpty(newColIndexD)); + assert.equal(newColIndexD, newColIndexG - 1); + assert.equal(newColIndexG, newColIndexH - 1); + + // TODO: Check deeper subcollection open states + }) + }) + + + describe("with feed items", 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 feedItem = yield createDataObject('feedItem', {libraryID: feed.libraryID}, { skipSelect: true }); + feedItem.setField('url', url); + yield feedItem.forceSaveTx(); + var translateFn = sinon.spy(feedItem, 'translate'); + + // Add observer to wait for collection add + var deferred = Zotero.Promise.defer(); + var itemIds; + + var ids = (yield drop('item', 'C' + collection.id, [feedItem.id])).ids; + + // Check that the translated item was the one that was created after drag + var item; + yield translateFn.returnValues[0].then(function(i) { + item = i; + assert.equal(item.id, ids[0]); + }); + + yield cv.selectCollection(collection.id); + yield waitForItemsLoad(win); + + var itemsView = win.ZoteroPane.itemsView; + assert.equal(itemsView.rowCount, 1); + var treeRow = itemsView.getRow(0); + assert.equal(treeRow.ref.id, item.id); + }) }) }) }) diff --git a/test/tests/feedItemTest.js b/test/tests/feedItemTest.js index c8717a901..ca0b4f598 100644 --- a/test/tests/feedItemTest.js +++ b/test/tests/feedItemTest.js @@ -194,9 +194,7 @@ describe("Zotero.FeedItem", function () { describe("#toggleRead()", function() { it('should toggle state', function* () { - feed = yield createFeed(); - - let item = yield createDataObject('feedItem', { guid: Zotero.randomString(), libraryID: feed.id }); + let item = yield createDataObject('feedItem', { libraryID }); item.isRead = false; yield item.forceSaveTx(); @@ -204,9 +202,7 @@ describe("Zotero.FeedItem", function () { assert.isTrue(item.isRead, "item is toggled to read state"); }); it('should save if specified state is different from current', function* (){ - feed = yield createFeed(); - - let item = yield createDataObject('feedItem', { guid: Zotero.randomString(), libraryID: feed.id }); + let item = yield createDataObject('feedItem', { libraryID }); item.isRead = false; yield item.forceSaveTx(); sinon.spy(item, 'save'); @@ -220,4 +216,36 @@ describe("Zotero.FeedItem", function () { assert.isFalse(item.save.called, "item was not saved on toggle read to same state"); }); }); + + describe('#translate()', function() { + before(function* () { + // Needs an open window to be able to create a hidden window for translation + yield loadBrowserWindow(); + }); + it('translates and saves items', function* () { + var feedItem = yield createDataObject('feedItem', {libraryID}); + var url = getTestDataItemUrl('metadata/journalArticle-single.html'); + feedItem.setField('url', url); + yield feedItem.forceSaveTx(); + + yield feedItem.translate(); + + assert.equal(feedItem.getField('title'), 'Scarcity or Abundance? Preserving the Past in a Digital Era'); + }); + it('translates and saves items to corresponding library and collection', function* () { + let group = yield createGroup(); + let collection = yield createDataObject('collection', {libraryID: group.libraryID}); + + var feedItem = yield createDataObject('feedItem', {libraryID}); + var url = getTestDataItemUrl('metadata/journalArticle-single.html'); + feedItem.setField('url', url); + yield feedItem.forceSaveTx(); + + yield feedItem.translate(group.libraryID, collection.id); + + let item = collection.getChildItems(false, false)[0]; + + assert.equal(item.getField('title'), 'Scarcity or Abundance? Preserving the Past in a Digital Era'); + }); + }); }); diff --git a/test/tests/feedTest.js b/test/tests/feedTest.js index bf7ebad91..96cf9bc44 100644 --- a/test/tests/feedTest.js +++ b/test/tests/feedTest.js @@ -250,10 +250,10 @@ describe("Zotero.Feed", function() { feed._feedUrl = feedUrl; yield feed.updateFeed(); - let feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573"); + let feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573:"+feed.id); feedItem.isRead = true; yield feedItem.forceSaveTx(); - feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573"); + feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573:"+feed.id); assert.isTrue(feedItem.isRead); let oldDateModified = feedItem.dateModified; @@ -261,7 +261,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"); + feedItem = yield Zotero.FeedItems.getAsyncByGUID("http://liftoff.msfc.nasa.gov/2003/06/03.html#item573:"+feed.id); assert.notEqual(oldDateModified, feedItem.dateModified); assert.isFalse(feedItem.isRead)