display feeds from other open tabs in the river view

--HG--
extra : rebase_source : 41d8556403e74850c093d9e3dceecf613a4a0ece
This commit is contained in:
Myk Melez 2009-06-05 17:42:59 -07:00
Родитель 4b6081e8db
Коммит 7ada648717
3 изменённых файлов: 177 добавлений и 40 удалений

Просмотреть файл

@ -348,36 +348,17 @@ let Snowl = {
// Feed Button
_onClickFeedButton: function(event) {
let feeds = gBrowser.selectedBrowser.feeds;
// How could this happen? Users shouldn't be able to click the button
// if there are no feeds.
// FIXME: figure out if we need this and what to do if we encounter it.
if (feeds == null)
if (gBrowser.selectedBrowser.feeds == null)
return;
let feedsToPreview = feeds;
// If there are two feeds, one of which seems to be an Atom feed, the other
// of which seems to be RSS, assume they're duplicates and only preview one
// (in this case we choose the Atom feed).
let areDupes = function(a, b) (/atom/i.test(a) && /rss/i.test(b)) ||
(/atom/i.test(b) && /rss/i.test(a));
if (feeds.length == 2 && areDupes(feeds[0].title, feeds[1].title)) {
// FIXME: log this so developers/testers know it happened.
// Filter the array for the item whose title contains "atom", but only
// grab the first item from the filtered array on the off chance that both
// items contain "atom".
feedsToPreview = [feeds.filter(function(v) /atom/i.test(v.title))[0]];
// Use the title of the page instead of the Atom-specific feed title.
if (gBrowser.selectedBrowser.contentTitle)
feedsToPreview[0].title = gBrowser.selectedBrowser.contentTitle;
}
let feeds = SnowlUtils.canonicalizeFeeds(gBrowser.selectedBrowser.feeds,
gBrowser.selectedBrowser.contentTitle);
// Open the river view, passing it the feeds to preview.
let param = "feedsToPreview=" + encodeURIComponent(JSON.stringify(feedsToPreview));
let param = "feedsToPreview=" + encodeURIComponent(JSON.stringify(feeds));
let href = "chrome://snowl/content/river.xul?" + param;
openUILink(href, event, false, true, false, null);
},

Просмотреть файл

@ -878,25 +878,32 @@ let Sources = {
this._log.info("selected " + source.name + " with ID " + source.id);
if (!source.messages) {
let constraints = [];
constraints.push({ expression: "sources.id = " + source.id });
// FIXME: use a left join here once the SQLite bug breaking left joins to
// virtual tables has been fixed (i.e. after we upgrade to SQLite 3.5.7+).
if (SnowlMessageView._filter.value) {
constraints.push({ expression: "messages.id IN (SELECT messageID FROM parts JOIN partsText ON parts.id = partsText.docid WHERE partsText.content MATCH :filter)",
parameters: { filter: SnowlUtils.appendAsterisks(SnowlMessageView._filter.value) } });
if (source.id) {
let constraints = [];
constraints.push({ expression: "sources.id = " + source.id });
// FIXME: use a left join here once the SQLite bug breaking left joins to
// virtual tables has been fixed (i.e. after we upgrade to SQLite 3.5.7+).
if (SnowlMessageView._filter.value) {
constraints.push({ expression: "messages.id IN (SELECT messageID FROM parts JOIN partsText ON parts.id = partsText.docid WHERE partsText.content MATCH :filter)",
parameters: { filter: SnowlUtils.appendAsterisks(SnowlMessageView._filter.value) } });
}
if (SnowlMessageView._periodMenu.selectedItem) {
constraints.push({ expression: "received >= :startTime AND received < :endTime",
parameters: { startTime: SnowlMessageView._periodStartTime,
endTime: SnowlMessageView._periodEndTime } });
}
// XXX replace this with a SnowlSource::retrieve method that handles
// constraints (and ultimately multiple source IDs)?
source.messages = new Collection2({ constraints: constraints,
order: "messages.id DESC" });
}
if (SnowlMessageView._periodMenu.selectedItem) {
constraints.push({ expression: "received >= :startTime AND received < :endTime",
parameters: { startTime: SnowlMessageView._periodStartTime,
endTime: SnowlMessageView._periodEndTime } });
else {
source.refresh();
}
source.messages = new Collection2({ constraints: constraints,
order: "messages.id DESC" });
}
SnowlMessageView._collection = source.messages;
@ -924,6 +931,22 @@ let Sources = {
}
}
let otherTabFeeds = this._getFeedsInOtherTabs();
if (otherTabFeeds.length > 0) {
let item = document.createElementNS(XUL_NS, "richlistitem");
// FIXME: make this localizable.
item.setAttribute("label", "Other Tabs");
item.className = "header";
this._list.appendChild(item);
for each (let otherTabFeed in otherTabFeeds) {
let feed = new SnowlFeed(null, otherTabFeed.title, new URI(otherTabFeed.href), undefined, null);
let item = this._list.appendItem(otherTabFeed.title);
item.source = feed;
item.className = "source";
}
}
let item = document.createElementNS(XUL_NS, "richlistitem");
// FIXME: make this localizable.
item.setAttribute("label", "Subscriptions");
@ -937,6 +960,22 @@ let Sources = {
item.setAttribute("subscription", "true");
item.className = "source";
}
},
_getFeedsInOtherTabs: function() {
// I would use FUEL here, but its tab API doesn't provide access to feeds.
let tabBrowser = gBrowserWindow.gBrowser;
let tabs = tabBrowser.mTabs;
let pages = [];
for (let i = 0; i < tabs.length; i++) {
let tab = tabs[i];
let browser = tabBrowser.getBrowserForTab(tab);
if (browser.feeds)
pages.push({ feeds: browser.feeds, title: browser.contentTitle });
}
return SnowlUtils.canonicalizeFeedsFromMultiplePages(pages);
}
};

Просмотреть файл

@ -590,6 +590,123 @@ let SnowlUtils = {
container.appendChild(desc);
}
}
},
/**
* Canonicalize the feeds provided by a web page by removing probable
* duplicates that use different protocols (Atom, RSS) and titling them
* after the page itself, since we treat feeds as the transport protocol
* for updates from web pages, so the web page itself is the first-class
* object to which we expose users, and its title is the more memorable
* in that regard.
*
* @param feeds {Array}
* the feeds to canonicalize; each element is a feed {Object}
* with two properties:
* href {String} URL of the feed
* title {String} title of the feed
*
* @param pageTitle {String}
* title of page providing the feeds
*
* @returns {Array} canonicalized array of feeds
*/
canonicalizeFeeds: function(feeds, pageTitle) {
// Operate on a copy of the feeds array so we don't hork other extensions
// or core code that expect that array to remain intact.
let canonicalFeeds = feeds.concat();
if (canonicalFeeds.length == 1) {
// If the page title is available, name the feed after the page,
// as the page's title is likely to be better than the feed title.
if (pageTitle)
canonicalFeeds[0].title = pageTitle;
}
else if (canonicalFeeds.length == 2) {
// If the two feeds appear to be duplicates (i.e. one RSS, the other
// Atom), then remove one. We remove the RSS feed by default, assuming
// that the Atom feed is better because Atom is better specified, but we
// could just as well remove the Atom feed if the RSS feed seems better
// under certain circumstances.
let areDupes = function(a, b) (/atom/i.test(a) && /rss/i.test(b)) ||
(/atom/i.test(b) && /rss/i.test(a));
if (areDupes(canonicalFeeds[0].title, canonicalFeeds[1].title)) {
// This code is overly complicated (filtering to an array, extracting
// its first element, and then putting that into another array)
// to ensure we always reduce the array to a single element even if
// both of their names happen to contain the string "atom"
// (f.e. if one was called "Atom Feed" and the other was called
// "RSS Feed - Not Atom").
canonicalFeeds =
[canonicalFeeds.filter(function(v) /atom/i.test(v.title))[0]];
}
// If the page title is available, name the feed after the page,
// as the page's title is likely to be better than the feed title.
if (pageTitle)
canonicalFeeds[0].title = pageTitle;
}
// If there are more than two feeds, we don't currently do anything.
// Perhaps there are things we could do? Use cases would be handy.
return canonicalFeeds;
},
/**
* Canonicalize feeds provided by multiple web pages. This calls
* canonicalizeFeeds on the feeds for each individual page, then it removes
* any exact duplicates from the list, so if you generate a list of feeds
* from a set of pages (f.e. pages open in tabs), and you have the same page
* in the set twice (or two pages from the same site that both provide
* the same feeds), you don't get duplicate feeds.
*
* @param pages {Array}
* the pages to canonicalize; each element is a page {Object}
* with two properties:
* feeds {Array} the feeds to canonicalize (@see canonicalizeFeeds
* for a description of feed objects)
* title {String} the title of the page
*
* @returns {Array} canonicalized array of feeds
*/
canonicalizeFeedsFromMultiplePages: function(pages) {
let feeds = [];
// Convert the array of pages into an array of feeds from those pages
// which have been canonicalized with respect to each individual page.
for each (let page in pages)
feeds = feeds.concat(this.canonicalizeFeeds(page.feeds, page.title));
// We can do the above with a single statement, but I don't think we gain
// anything, since I can't find a way to make the statement more compact
// while retaining readability.
//feeds =
// pages.map(function(page) this.canonicalizeFeeds(page.feeds, page.title),
// this).reduce(function(feeds, feed) feeds.concat(feed), []);
// Convert the array of feeds into an array of feeds that have been
// canonicalized with respect to the set of pages as a whole (i.e. remove
// exact duplicates from the list of feeds from all pages). We consider
// two feeds to be duplicates if their URLs match, even if their titles
// are different, since users only benefit from subscribing to any given
// feed once, even if the feed is offered by two different pages
// with different titles.
let uniqueFeeds = {};
for each (let feed in feeds)
uniqueFeeds[feed.href] = feed;
feeds = [feed for ([, feed] in Iterator(uniqueFeeds))];
// We can do the above with a single statement, but I don't think we gain
// anything, since I can't find a way to make the statement more compact
// while retaining readibility. I suppose there's benefit to not having
// to declare the temporary uniqueFeeds object.
//feeds = [feed for ([, feed] in
// Iterator(feeds.reduce(function(uniqueFeeds, feed) {
// uniqueFeeds[feed.href] = feed; return uniqueFeeds
// }, {})))];
return feeds;
}
};