/* Digg Sidebar - Shows Digg stories in the Firefox sidebar in real time. Copyright (C) 2008 Abhinav Sarkar This file is a part of Digg Sidebar. Digg Sidebar is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Digg Sidebar is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Digg Sidebar. If not, see . */ var DiggSidebar = { homepageURL: "http://code.google.com/p/digg-sidebar/", prefs: Application.extensions.get("diggsidebar@abhinavsarkar.net").prefs, observerService: Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService), storyListRefreshEventTopic: "extension-diggsidebar-abhinavsarkar-net-storylist-refresh", updateInterval: 1000, updateIntervalDecay: 0, shownStoriesCount: 30,//make configurable cachedStoriesCount: 1000,//make configurable categories: ["All", "Popular", "Upcoming", "Hot", "Top"], endpoint: { container: null, topic: null, category: "all" }, lastUpdateAt: new Date().getTime(), timerId: null, storyIds: [], refreshing: false, stories: [], expandedStory: null, $ce: function (tagName) { return document.createElement(tagName); }, fetchData: function (url, loadHandler, errorHandler) { var request = new XMLHttpRequest(); request.onerror = DiggSidebar.errorHandler; request.onload = loadHandler; request.onerror = errorHandler; request.open("GET", url, true); request.send(null); }, errorHandler: function (e) { /*if (DiggSidebar.UI.notificationBox.allNotifications.length > 0) DiggSidebar.UI.notificationBox.removeAllNotifications(true);*/ DiggSidebar.UI.notificationBox.appendNotification( "Unable to access Digg", "dsAccessFailNotification", null, "PRIORITY_CRITICAL_HIGH", [{callback: DiggSidebar.getStories, label: "Retry", accessKey: "R"}] ); DiggSidebar.refreshing = false; DiggSidebar.indicateInactivity(); }, populateMenu: function (e) { var XHR = e.target; var data = DiggSidebar.Utils.decodeJson(XHR.responseText); var containers = data.containers; containers.forEach(function (container) { var cmenu = DiggSidebar.$ce("menu"); with (cmenu) { setAttribute("id", container.short_name + "Menu"); setAttribute("label", container.name); setAttribute("accesskey", container.name.charAt(0)); } var cmenupopup = DiggSidebar.$ce("menupopup"); cmenupopup.setAttribute("id", container.short_name + "Popup"); DiggSidebar.categories.forEach(function (category) { var cmenuitem = DiggSidebar.$ce("menuitem"); with (cmenuitem) { setAttribute("label", category); setAttribute("value", "/container/" + container.short_name + "/" + category.toLowerCase()); setAttribute("accesskey", category.charAt(0)); setAttribute("oncommand", "DiggSidebar.setEndPoint(this.value)"); } cmenupopup.appendChild(cmenuitem); }); cmenupopup.appendChild(DiggSidebar.$ce("menuseparator")); var topics = container.topics; topics.forEach(function (topic) { var tmenu = DiggSidebar.$ce("menu"); with (tmenu) { setAttribute("id", topic.short_name + "Menu"); setAttribute("label", topic.name); setAttribute("accesskey", topic.name.charAt(0)); } var tmenupopup = DiggSidebar.$ce("menupopup"); tmenupopup.setAttribute("id", topic.short_name + "Popup"); DiggSidebar.categories.forEach(function (category) { var tmenuitem = DiggSidebar.$ce("menuitem"); with (tmenuitem) { setAttribute("label", category); setAttribute("value", "/topic/" + topic.short_name + "/" + category.toLowerCase()); setAttribute("accesskey", category.charAt(0)); setAttribute("oncommand", "DiggSidebar.setEndPoint(this.value)"); } tmenupopup.appendChild(tmenuitem); }); tmenu.appendChild(tmenupopup); cmenupopup.appendChild(tmenu); }); cmenu.appendChild(cmenupopup); DiggSidebar.UI.storiesPopup.appendChild(cmenu); }); }, populateStoryList: function () { var ep = DiggSidebar.prefs.get("endpoint").value || ""; DiggSidebar.UI.endPointDesc.value = "digg" + ep; var newStoryIds = []; var jp = new JPath(DiggSidebar.stories); var filteredStories = jp.$(function (story) { return (story.$("category").json == DiggSidebar.endpoint.category); }).json; jp = new JPath(filteredStories); if (DiggSidebar.endpoint.topic) { filteredStories = jp.$(function (story) { return story.$("topic/short_name").json == DiggSidebar.endpoint.topic; }).json; } if (DiggSidebar.endpoint.container) { filteredStories = jp.$(function (story) { return story.$("container/short_name").json == DiggSidebar.endpoint.container; }).json; } if (filteredStories.length > 0) { DiggSidebar.Utils.removeAllChildren(DiggSidebar.UI.storyListBox); } var now = new Date(); filteredStories.forEach(function (story, index) { if (index < DiggSidebar.shownStoriesCount) { var then = (story.promote_date != null) ? new Date(story.promote_date * 1000) : new Date(story.submit_date * 1000); var diff = Math.max(now - then, 0); var hr = Math.floor(diff / (1000 * 3600)); var min = Math.floor(diff / (1000 * 60)) - 60 * hr; var relativeTime = ((hr > 0) && (min > 0)) ? (hr + " hr " + min + " mins ago") : ((hr == 0) && (min > 0)) ? (min + " mins ago") : ((hr == 0) && (min == 0)) ? "just now" : ""; var li = DiggSidebar.UI.storyListBox.appendChild( DiggSidebar.$ce("richlistitem")); li.id = "story_" + story.id; var attributes = { title: story.title, date: relativeTime, status: story.status, container: story.container.name, topic: story.topic.name, username: story.user.name, userlink: "http://digg.com/users/" + story.user.name, diggs: story.diggs, comments: story.comments, desc: story.description, link: story.link, href: story.href }; for (var attr in attributes) { li.setAttribute(attr, attributes[attr]); } li.read = story.read; if (story.id == DiggSidebar.expandedStory) { li.showDescription(); } } if (DiggSidebar.storyIds.indexOf(story.id) == -1) { newStoryIds.push(story.id); } }); DiggSidebar.storyIds = newStoryIds.concat(DiggSidebar.storyIds); if (DiggSidebar.storyIds.length > DiggSidebar.cachedStoriesCount) { DiggSidebar.storyIds.length = DiggSidebar.cachedStoriesCount; } }, showDescription: function (storyId) { var jp = new JPath(DiggSidebar.stories); var story = jp.query("//[id == " + storyId + "]")[0]; story.read = true; DiggSidebar.expandedStory = parseInt(storyId); var listitems = DiggSidebar.UI.storyListBox.children; for (var i = 0; i < listitems.length; i++) { listitems[i].hideDescription(); } document.getElementById("story_" + story.id).showDescription(); }, getStories: function () { if (!DiggSidebar.refreshing) { DiggSidebar.refreshing = true; var ep = DiggSidebar.prefs.get("endpoint").value || ""; DiggSidebar.indicateActivity(); DiggSidebar.fetchData( "http://services.digg.com/stories" + ep.replace(/\/all/g, "") + "?count=30" + "&type=json" + "&appkey=" + encodeURIComponent(DiggSidebar.homepageURL), function (e) { var stories = DiggSidebar.Utils.decodeJson(e.target.responseText).stories; stories.forEach(function (story) { story.id = parseInt(story.id); }); var newStories = [ story for each (story in stories) if (DiggSidebar.storyIds.indexOf(story.id) == -1)]; newStories.forEach(function (story) { story.category = DiggSidebar.endpoint.category; story.read = false; }); DiggSidebar.stories = newStories.concat(DiggSidebar.stories); if (DiggSidebar.stories.length > DiggSidebar.cachedStoriesCount) { DiggSidebar.stories.length = DiggSidebar.cachedStoriesCount; } DiggSidebar.observerService.notifyObservers( null, DiggSidebar.storyListRefreshEventTopic, null); DiggSidebar.refreshing = false; DiggSidebar.setUpdateInterval(stories); window.clearTimeout(DiggSidebar.timerId); var timeout = DiggSidebar.calculateTimeout(); DiggSidebar.timerId = window.setTimeout(DiggSidebar.getStories, timeout); DiggSidebar.indicateInactivity(); }, DiggSidebar.errorHandler); } else { window.setTimeout(DiggSidebar.getStories, 1000); } }, createMenu: function () { DiggSidebar.indicateActivity(); DiggSidebar.fetchData( "http://services.digg.com/containers" + "?type=json" + "&appkey=" + encodeURIComponent(DiggSidebar.homepageURL), DiggSidebar.populateMenu, DiggSidebar.errorHandler); }, setUpdateInterval: function (stories) { //Adaptive update interval code START if (stories.length == 0) { return; } var sum = 0; var weights = [4, 3, 2, 1]; var date = (stories[0].promote_date != null) ? "promote_date" : "submit_date"; stories.sort(function (a, b) { if (a[date] > b[date]) { return -1; } else if (a[date] < b[date]) { return 1; } else { return 0; } }); for (var i = 0; i < Math.min(4, stories.length - 1); i++) { var diff = stories[i][date] - stories[i + 1][date]; sum += weights[i] * diff; } var newUpdateInterval = Math.round( sum * 1000 / weights.slice(0, Math.min(4, stories.length - 1)) .reduce(function (a, b) a + b)); if (newUpdateInterval > 0) { var previousUpdateInterval = DiggSidebar.updateInterval; DiggSidebar.updateInterval = newUpdateInterval; if (previousUpdateInterval == newUpdateInterval) { DiggSidebar.updateIntervalDecay += 1; } else { DiggSidebar.updateIntervalDecay = 0; } } //Adaptive update interval code END }, calculateTimeout: function () { return Math.round(DiggSidebar.updateInterval * Math.pow(1.5, DiggSidebar.updateIntervalDecay)); }, setEndPoint: function (ep) { DiggSidebar.prefs.get("endpoint").value = ep; DiggSidebar.endpoint = DiggSidebar.getEndpointParts(ep); window.clearTimeout(DiggSidebar.timerId); DiggSidebar.getStories(); }, getEndpointParts: function (ep) { var tmp = { topic: null, container: null, category: "all" }; var parts = ep.split("/"); if (parts.length == 2) { tmp.category = parts[1]; } else if (parts.length == 4) { tmp[parts[1]] = parts[2]; tmp.category = parts[3]; } return tmp; }, indicateActivity: function () { DiggSidebar.UI.indicator.src = "chrome://global/skin/icons/loading_16.png"; with (DiggSidebar.UI.indicator.parentNode) { disabled = true; setAttribute("tooltiptext", "Refreshing"); onmouseover = null; } }, indicateInactivity: function () { DiggSidebar.UI.indicator.src = "chrome://global/skin/icons/notloading_16.png"; with (DiggSidebar.UI.indicator.parentNode) { disabled = false; onmouseover = function () { var autoRefreshTime = Math.round( (DiggSidebar.lastUpdateAt + DiggSidebar.calculateTimeout() - new Date().getTime()) / 1000); autoRefreshTime = (autoRefreshTime > 3600) ? Math.round(autoRefreshTime / 3600) + " hours": (autoRefreshTime > 60) ? Math.round(autoRefreshTime / 60) + " minutes": autoRefreshTime + " seconds"; setAttribute("tooltiptext", "Click to refresh now.\n" + "Autorefreshing in " + autoRefreshTime); }; } DiggSidebar.lastUpdateAt = new Date().getTime(); }, initialize: function () { DiggSidebar.endpoint = DiggSidebar.getEndpointParts( DiggSidebar.prefs.get("endpoint").value); var $ = function (id) { return document.getElementById(id); }; DiggSidebar.UI = {}; DiggSidebar.UI.notificationBox = $("dsNotificationBox"); DiggSidebar.UI.indicator = $("dsBusyIndicator"); DiggSidebar.UI.storiesPopup = $("dsStoriesPopup"); DiggSidebar.UI.storyListBox = $("dsStoryListBox"); DiggSidebar.UI.endPointDesc = $("dsEndPointDesc"); DiggSidebar.storyListObserver = new DiggSidebar.StoryListObserver(); DiggSidebar.storyListObserver.register(); DiggSidebar.createMenu(); DiggSidebar.getStories(); }, destroy: function () { DiggSidebar.storyListObserver.unregister(); DiggSidebar.stories = []; } }; DiggSidebar.Utils = { url: function (spec) { var ios = Components.classes["@mozilla.org/network/io-service;1"] .getService(Components.interfaces.nsIIOService); return ios.newURI(spec, null, null); }, openUrlInTab: function (url) { Application.activeWindow.open(DiggSidebar.Utils.url(url)); }, decodeJson: function (string) { var json = Components.classes["@mozilla.org/dom/json;1"] .createInstance(Components.interfaces.nsIJSON); return json.decode(string); }, removeAllChildren: function (node) { while (node.firstChild) { node.removeChild(node.firstChild); } } }; DiggSidebar.StoryListObserver = function () { this.registered = false; }; DiggSidebar.StoryListObserver.prototype = { observe: function (subject, topic, data) { if (topic == DiggSidebar.storyListRefreshEventTopic) { DiggSidebar.populateStoryList(); } }, register: function () { if (!this.registered) { DiggSidebar.observerService.addObserver( this, DiggSidebar.storyListRefreshEventTopic, false); this.registered = true; } }, unregister: function () { if (this.registered) { DiggSidebar.observerService.removeObserver( this, DiggSidebar.storyListRefreshEventTopic); this.registered = false; } } } /*DONE v0.2 added kdb navigation Added progressmeter added error notification added accesskeys added new story notification v0.2.1 adaptive polling interval promote_date pause/play v0.5 new ui v0.7 moved to JSON data format moved HTML template to XBL refactored JavaScript/Using FUEL implemented caching and notifier-observer new UI added toolbar button put license */