Close adomasven/zotero#11. Add support for feed imports from OPML files
This commit is contained in:
parent
3dabd63a0a
commit
3b758e562b
9 changed files with 119 additions and 2 deletions
|
@ -236,6 +236,8 @@ Zotero.Feed.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
|
|||
|
||||
if (!this._feedName) throw new Error("Feed name not set");
|
||||
if (!this._feedUrl) throw new Error("Feed URL not set");
|
||||
if (!this.refreshInterval) this.refreshInterval = Zotero.Prefs.get('feeds.defaultTTL') * 60;
|
||||
if (!this.cleanupAfter) this.cleanupAfter = Zotero.Prefs.get('feeds.defaultCleanupAfter');
|
||||
|
||||
if (env.isNew) {
|
||||
// Make sure URL is unique
|
||||
|
|
|
@ -73,6 +73,44 @@ Zotero.Feeds = new function() {
|
|||
return this.scheduleNextFeedCheck();
|
||||
}
|
||||
|
||||
this.importFromOPML = Zotero.Promise.coroutine(function* (opmlString) {
|
||||
var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
|
||||
.createInstance(Components.interfaces.nsIDOMParser);
|
||||
var doc = parser.parseFromString(opmlString, "application/xml");
|
||||
// Per some random spec (https://developer.mozilla.org/en-US/docs/Web/API/DOMParser),
|
||||
// DOMParser returns a special type of xml document on error, so we do some magic checking here.
|
||||
if (doc.documentElement.tagName == 'parseerror') {
|
||||
return false;
|
||||
}
|
||||
var body = doc.getElementsByTagName('body')[0];
|
||||
var feedElems = doc.querySelectorAll('[type=rss][url], [xmlUrl]');
|
||||
var newFeeds = [];
|
||||
var registeredUrls = new Set();
|
||||
for (let feedElem of feedElems) {
|
||||
let url = feedElem.getAttribute('xmlUrl');
|
||||
if (!url) url = feedElem.getAttribute('url');
|
||||
let name = feedElem.getAttribute('title');
|
||||
if (!name) name = feedElem.getAttribute('text');
|
||||
if (Zotero.Feeds.existsByURL(url) || registeredUrls.has(url)) {
|
||||
Zotero.debug("Feed Import from OPML: Feed " + name + " : " + url + " already exists. Skipping");
|
||||
continue;
|
||||
}
|
||||
// Prevent duplicates from the same OPML file
|
||||
registeredUrls.add(url);
|
||||
let feed = new Zotero.Feed({url, name});
|
||||
newFeeds.push(feed);
|
||||
}
|
||||
// This could potentially be a massive list, so we save in a transaction.
|
||||
yield Zotero.DB.executeTransaction(function* () {
|
||||
for (let feed of newFeeds) {
|
||||
yield feed.save();
|
||||
}
|
||||
});
|
||||
// Finally, update
|
||||
yield Zotero.Feeds.updateFeeds();
|
||||
return true;
|
||||
});
|
||||
|
||||
this.restoreFromJSON = Zotero.Promise.coroutine(function* (json, merge=false) {
|
||||
Zotero.debug("Restoring feeds from remote JSON");
|
||||
Zotero.debug(json);
|
||||
|
|
|
@ -870,6 +870,27 @@ var ZoteroPane = new function()
|
|||
return collection.saveTx();
|
||||
});
|
||||
|
||||
this.importFeedsFromOPML = Zotero.Promise.coroutine(function* (event) {
|
||||
var nsIFilePicker = Components.interfaces.nsIFilePicker;
|
||||
while (true) {
|
||||
var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
|
||||
fp.init(window, Zotero.getString('fileInterface.importOPML'), nsIFilePicker.modeOpen);
|
||||
fp.appendFilter(Zotero.getString('fileInterface.OPMLFeedFilter'), '*.opml; *.xml');
|
||||
fp.appendFilters(nsIFilePicker.filterAll);
|
||||
if (fp.show() == nsIFilePicker.returnOK) {
|
||||
var contents = yield Zotero.File.getContentsAsync(fp.file.path);
|
||||
var success = yield Zotero.Feeds.importFromOPML(contents);
|
||||
if (success) {
|
||||
return true;
|
||||
}
|
||||
// Try again
|
||||
Zotero.alert(window, Zotero.getString('general.error'), Zotero.getString('fileInterface.unsupportedFormat'));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.newFeedFromPage = Zotero.Promise.coroutine(function* (event) {
|
||||
let data = {unsaved: true};
|
||||
if (event) {
|
||||
|
|
|
@ -117,6 +117,8 @@
|
|||
<menupopup oncommand="ZoteroPane_Local.newFeedFromPage(event)"
|
||||
onpopupshowing="FeedHandler.buildFeedList(event.target)"/>
|
||||
</menu>
|
||||
<menuitem id="zotero-tb-feed-add-fromOPML" label="&zotero.toolbar.feeds.new.fromOPML;"
|
||||
oncommand="ZoteroPane_Local.importFeedsFromOPML()"/>
|
||||
</menupopup>
|
||||
</menu>
|
||||
</menupopup>
|
||||
|
|
|
@ -127,7 +127,8 @@
|
|||
|
||||
<!ENTITY zotero.toolbar.feeds.new "New Feed…">
|
||||
<!ENTITY zotero.toolbar.feeds.new.fromURL "From URL…">
|
||||
<!ENTITY zotero.toolbar.feeds.new.fromPage "From Page…">
|
||||
<!ENTITY zotero.toolbar.feeds.new.fromPage "From Page…">
|
||||
<!ENTITY zotero.toolbar.feeds.new.fromOPML "From OPML…">
|
||||
<!ENTITY zotero.toolbar.feeds.refresh "Refresh Feed">
|
||||
<!ENTITY zotero.toolbar.feeds.edit "Edit Feed…">
|
||||
|
||||
|
|
|
@ -637,6 +637,8 @@ fileInterface.importClipboardNoDataError = No importable data could be read from
|
|||
fileInterface.noReferencesError = The items you have selected contain no references. Please select one or more references and try again.
|
||||
fileInterface.bibliographyGenerationError = An error occurred generating your bibliography. Please try again.
|
||||
fileInterface.exportError = An error occurred while trying to export the selected file.
|
||||
fileInterface.importOPML = Import Feeds from OPML
|
||||
fileInterface.OPMLFeedFilter = OPML Feed List
|
||||
|
||||
quickSearch.mode.titleCreatorYear = Title, Creator, Year
|
||||
quickSearch.mode.fieldsAndTags = All Fields & Tags
|
||||
|
|
18
test/tests/data/feeds.opml
Normal file
18
test/tests/data/feeds.opml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<opml version="1.0">
|
||||
<head>
|
||||
<title>An OPML file with a list of rss/atom feeds</title>
|
||||
<docs>The OPML format is fairly poorly spec'ed out here http://dev.opml.org/spec2.html</docs>
|
||||
</head>
|
||||
<body>
|
||||
<outline text="Standard format">
|
||||
<outline type="rss" text="A title 1" title="A title 1" xmlUrl="http://example.com/feed1.rss"/>
|
||||
<outline type="rss" text="A title 2" title="A title 2" xmlUrl="http://example.com/feed2.rss"/>
|
||||
</outline>
|
||||
<outline text="Non-standard format">
|
||||
<outline type="rss" text="A title 3" title="A title 3" url="http://example.com/feed3.rss"/>
|
||||
<outline type="rss" text="A title 4" title="A title 4" url="http://example.com/feed4.rss"/>
|
||||
</outline>
|
||||
</body>
|
||||
</opml>
|
|
@ -4,6 +4,39 @@ describe("Zotero.Feeds", function () {
|
|||
yield clearFeeds();
|
||||
});
|
||||
|
||||
describe('#importFromOPML', function() {
|
||||
var opmlUrl = getTestDataUrl("feeds.opml");
|
||||
var opmlString;
|
||||
|
||||
before(function* (){
|
||||
opmlString = yield Zotero.File.getContentsFromURLAsync(opmlUrl)
|
||||
});
|
||||
|
||||
beforeEach(function* () {
|
||||
yield clearFeeds();
|
||||
});
|
||||
|
||||
it('imports feeds correctly', function* (){
|
||||
let shouldExist = {
|
||||
"http://example.com/feed1.rss": "A title 1",
|
||||
"http://example.com/feed2.rss": "A title 2",
|
||||
"http://example.com/feed3.rss": "A title 3",
|
||||
"http://example.com/feed4.rss": "A title 4"
|
||||
}; yield Zotero.Feeds.importFromOPML(opmlString);
|
||||
let feeds = Zotero.Feeds.getAll();
|
||||
for (let feed of feeds) {
|
||||
assert.equal(shouldExist[feed.url], feed.name, "Feed exists and title matches");
|
||||
delete shouldExist[feed.url];
|
||||
}
|
||||
assert.equal(Object.keys(shouldExist).length, 0, "All feeds from opml have been created");
|
||||
});
|
||||
|
||||
it("doesn't fail if some feeds already exist", function* (){
|
||||
yield createFeed({url: "http://example.com/feed1.rss"});
|
||||
yield Zotero.Feeds.importFromOPML(opmlString)
|
||||
});
|
||||
});
|
||||
|
||||
describe("#restoreFromJSON", function() {
|
||||
var json = {};
|
||||
var expiredFeedURL, existingFeedURL;
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit c834d847805f030afe3c38c4ad92f135faf01a4d
|
||||
Subproject commit f0aa00219aeb75f04654a59339aa94e1accbc956
|
Loading…
Reference in a new issue