From 8efa7db10567c438780e490d2771c56c440d5a29 Mon Sep 17 00:00:00 2001 From: Chromakode Date: Tue, 22 Sep 2009 13:34:32 -0700 Subject: [PATCH] Add info tooltips to url bar icons, refactoring reddit API code and DOM sorting. --- content/reddit/mail.png | Bin 0 -> 212 bytes content/reddit/mailgray.png | Bin 0 -> 223 bytes content/reddit/redditBar.xml | 16 +-- content/reddit/redditSiteInfo.xml | 84 ++++++++++++ content/reddit/scoreTooltip.xml | 2 - content/siteInfoBox.xml | 144 ++++++++++++++++++++ content/siteMenuItem.js | 6 +- content/siteUrlBarIcon.js | 14 +- content/socialiteBar.xml | 11 +- content/socialitePreferences.js | 6 +- content/urlBarIcon.xml | 25 ++-- install.rdf | 2 +- license.txt | 1 + locale/en-US/reddit.dtd | 6 + modules/reddit/reddit.jsm | 26 +++- modules/reddit/redditAPI.jsm | 188 ++++++++++++-------------- modules/utils/action/action.jsm | 135 +++++++++--------- modules/utils/action/cachedAction.jsm | 16 ++- modules/utils/domUtils.jsm | 20 +++ modules/utils/faviconWatch.jsm | 20 ++- modules/utils/hitch.jsm | 2 - skin/reddit.css | 10 +- skin/socialite.css | 67 ++++++++- 23 files changed, 562 insertions(+), 239 deletions(-) create mode 100644 content/reddit/mail.png create mode 100644 content/reddit/mailgray.png create mode 100644 content/reddit/redditSiteInfo.xml create mode 100644 content/siteInfoBox.xml diff --git a/content/reddit/mail.png b/content/reddit/mail.png new file mode 100644 index 0000000000000000000000000000000000000000..48f88e3bc40c84161b8a87585880d4a769f6fe31 GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngb!2~3qR8C$8q*&4&eH|GXuHCreA7KRKOP07s zlmzFem6RtIr7}3ChCLV@!e`0ORRW0g(HZpj+`njxg HN@xNAY}7-B literal 0 HcmV?d00001 diff --git a/content/reddit/mailgray.png b/content/reddit/mailgray.png new file mode 100644 index 0000000000000000000000000000000000000000..bc3d8005a32dea2f64ff410b70430f048957cb63 GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^{6Ngb!2~3qR8C$8q*&4&eH|GXuHCreA7KRKOP07s zlmzFem6RtIr7}3CMEy+RK<7!nVC7 - + @@ -46,8 +46,6 @@ onget="return this.getAttribute('isloggedin') == 'true';"/> - this.className += " reddit-content-ui"; - // Public members for easy access this.labelScore = document.getAnonymousElementByAttribute(this, "anonid", "labelScore"); this.buttonLike = document.getAnonymousElementByAttribute(this, "anonid", "buttonLike"); diff --git a/content/reddit/redditSiteInfo.xml b/content/reddit/redditSiteInfo.xml new file mode 100644 index 0000000..a510137 --- /dev/null +++ b/content/reddit/redditSiteInfo.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + var self = this; + function setLabel(anonid, value) { + var label = document.getAnonymousElementByAttribute(self, "anonid", anonid); + label.value = value; + } + + this.site.API.auth.getAuthInfo(function(authInfo) { + self.setAttribute("isloggedin", authInfo.isLoggedIn); + setLabel("labelUsername", authInfo.username); + }).perform(); + + var messageCount = this.site.newMessages.length; + setLabel("labelMailCount", messageCount); + var imageMailIcon = document.getAnonymousElementByAttribute(this, "anonid", "imageMailIcon"); + if (messageCount > 0) { + imageMailIcon.src = "chrome://socialite/content/reddit/mail.png"; + } else { + imageMailIcon.src = "chrome://socialite/content/reddit/mailgray.png"; + } + + this.site.cached.myuserinfo( + function success(r, json) { + setLabel("labelLinkKarma", json.data.link_karma); + setLabel("labelCommentKarma", json.data.comment_karma); + } + ).perform(); + + + + + \ No newline at end of file diff --git a/content/reddit/scoreTooltip.xml b/content/reddit/scoreTooltip.xml index 2c812ac..9bdfb0c 100644 --- a/content/reddit/scoreTooltip.xml +++ b/content/reddit/scoreTooltip.xml @@ -6,7 +6,6 @@ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - @@ -66,6 +65,5 @@ this.dislikeCount = linkInfo.localState.dislikeCount; - diff --git a/content/siteInfoBox.xml b/content/siteInfoBox.xml new file mode 100644 index 0000000..e2bdde5 --- /dev/null +++ b/content/siteInfoBox.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + if (!this._constructed) { + this.updateSiteName(); + + var faviconWatch = Components.utils.import("resource://socialite/utils/faviconWatch.jsm", null); + this._removeFaviconWatch = faviconWatch.useFaviconAsAttribute(this, "image", this.site.siteURL); + this._constructed = true; + } + + + + this.destroySiteInfoBox(); + + + + + if (this._removeFaviconWatch) { this._removeFaviconWatch(); } + + + + + + this.name = this.site.siteName; + + + + + + this.siteInfo.refresh(); + + + + + + + + + + + + + + + if (!this._constructed) { + this.afterBound(); + this._constructed = true; + } + + + + this.destroySiteInfoTooltip(); + + + + + Array.forEach(this.childNodes, function(siteInfoBox) { + siteInfoBox.destroySiteInfoBox(); + }); + + + + + + + var siteInfoBox = document.createElement("box"); + siteInfoBox.className = "socialite-site-info-box"; + siteInfoBox.site = site; + siteInfoBox.setAttribute("name", site.siteName); + siteInfoBox.appendChild(site.createInfoUI(document)); + this.appendChild(siteInfoBox); + + + + + + + + + + + + + Array.forEach(this.childNodes, function(siteInfoBox) { + siteInfoBox.updateSiteName(); + }); + + + + + + Array.forEach(this.childNodes, function(siteInfoBox) { + siteInfoBox.refresh(); + }); + + + + + + + // We must refresh site names on this event, because the XBL doesn't seem to bind until the popup is showing. + this.updateSiteNames(); + this.refresh(); + + + + \ No newline at end of file diff --git a/content/siteMenuItem.js b/content/siteMenuItem.js index 85da323..e3c195f 100644 --- a/content/siteMenuItem.js +++ b/content/siteMenuItem.js @@ -56,11 +56,7 @@ SocialiteWindow.SiteMenuItem = (function() { if (menuItems.length == 0) { fileMenuPopup.insertBefore(menuItem, sendMenuItem.nextSibling); } else { - domUtils.insertSorted(menuItem, menuItems, function(item1, item2) { - let label1 = item1.getAttribute("label"); - let label2 = item2.getAttribute("label"); - return label1.localeCompare(label2); - }); + domUtils.insertSorted(menuItem, menuItems, domUtils.compareBy(function(e) e.getAttribute("label"))); } } menuItem.updateSiteName(site.siteName); diff --git a/content/siteUrlBarIcon.js b/content/siteUrlBarIcon.js index 99f53a9..a3c33a7 100644 --- a/content/siteUrlBarIcon.js +++ b/content/siteUrlBarIcon.js @@ -41,6 +41,12 @@ SocialiteWindow.SiteUrlBarIcon = (function() { Socialite.utils.openUILink(site.inboxURL, e); }, false); + let infoTooltip = document.createElement("tooltip"); + infoTooltip.className = "socialite-site-info-tooltip"; + infoTooltip.afterBound = function() { + infoTooltip.addSite(site); + } + urlBarIcon.appendChild(infoTooltip); // Hide the icon before we add and position it. urlBarIcon.setAttribute("hidden", true); @@ -104,18 +110,14 @@ SocialiteWindow.SiteUrlBarIcon = (function() { let urlBarIcon = this.get(site); let feedButton = document.getElementById("feed-button"); let urlBarIconParent = document.getElementById("urlbar-icons"); - + urlBarIcon.name = newSiteName; let urlBarIcons = SiteUrlBarIcon.getAll(); if (urlBarIcons.length == 0) { urlBarIconParent.insertBefore(urlBarIcon, feedButton); } else { - domUtils.insertSorted(urlBarIcon, urlBarIcons, function compare(urlBarIcon1, urlBarIcon2) { - let name1 = urlBarIcon1.name; - let name2 = urlBarIcon2.name; - return name1.localeCompare(name2); - }); + domUtils.insertSorted(urlBarIcon, urlBarIcons, domUtils.compareBy(function(e) e.name)); } }, diff --git a/content/socialiteBar.xml b/content/socialiteBar.xml index 4c4210f..57bfd23 100644 --- a/content/socialiteBar.xml +++ b/content/socialiteBar.xml @@ -424,8 +424,8 @@ } if (self.selectedSite.siteID == siteID) { - let siteName = document.getAnonymousElementByAttribute(self, "anonid", "labelSite"); - siteName.value = newSiteName; + let labelSiteName = document.getAnonymousElementByAttribute(self, "anonid", "labelSite"); + labelSiteName.value = newSiteName; } } } @@ -481,11 +481,7 @@ menuItem.updateSiteName = function(newName) { var domUtils = Components.utils.import("resource://socialite/utils/domUtils.jsm", null); menuItem.setAttribute("label", newName); - domUtils.addSorted(menuItem, self.menuSites, function(item1, item2) { - let label1 = item1.getAttribute("label"); - let label2 = item2.getAttribute("label"); - return label1.localeCompare(label2); - }); + domUtils.addSorted(menuItem, self.menuSites, domUtils.compareBy(function(e) e.getAttribute("label"))); } menuItem.updateSiteIcon = function() { var faviconWatch = Components.utils.import("resource://socialite/utils/faviconWatch.jsm", null); @@ -582,7 +578,6 @@ let observerService = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); - // Site name change observer let self = this; this.loadedSiteObserver = { observe: function(subject, topic, data) { diff --git a/content/socialitePreferences.js b/content/socialitePreferences.js index 2ffdb29..fd23594 100644 --- a/content/socialitePreferences.js +++ b/content/socialitePreferences.js @@ -31,11 +31,7 @@ var SocialiteSitePreferences = (function() { if (siteCell.getAttribute("label") != site.siteName) { siteCell.setAttribute("label", site.siteName); - domUtils.insertListboxSorted(newItem, SSPrefs.siteListbox, function(item1, item2) { - let label1 = item1.firstChild.getAttribute("label"); - let label2 = item2.firstChild.getAttribute("label"); - return label1.localeCompare(label2); - }); + domUtils.insertListboxSorted(newItem, SSPrefs.siteListbox, domUtils.compareBy(function(e) e.firstChild.getAttribute("label"))); } if (newItem.removeFaviconWatch) { newItem.removeFaviconWatch(); } diff --git a/content/urlBarIcon.xml b/content/urlBarIcon.xml index fff54a7..3a21b18 100644 --- a/content/urlBarIcon.xml +++ b/content/urlBarIcon.xml @@ -5,11 +5,12 @@ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - + - + + @@ -82,16 +83,18 @@ --> - var faviconWatch = Components.utils.import("resource://socialite/utils/faviconWatch.jsm", null); - this._removeFaviconWatch = faviconWatch.useFaviconAsAttribute(this, "icon", this.site.siteURL); - this.icon = faviconWatch.getFaviconURL(this.site.siteURL); - - var self = this; - let updateAlertState = function() { - self.isAlert = self.site.alertState; + if (!this._constructed) { + var faviconWatch = Components.utils.import("resource://socialite/utils/faviconWatch.jsm", null); + this._removeFaviconWatch = faviconWatch.useFaviconAsProperty(this, "icon", this.site.siteURL); + + var self = this; + let updateAlertState = function() { + self.isAlert = self.site.alertState; + } + this._removeAlertStateWatch = this.site.onAlertStateChange.watch(updateAlertState); + updateAlertState(); + this._constructed = true; } - this._removeAlertStateWatch = this.site.onAlertStateChange.watch(updateAlertState); - updateAlertState(); diff --git a/install.rdf b/install.rdf index 6f73673..90a9555 100644 --- a/install.rdf +++ b/install.rdf @@ -8,7 +8,7 @@ em:maxVersion="3.5.*" /> diff --git a/license.txt b/license.txt index ef46a23..de244f5 100644 --- a/license.txt +++ b/license.txt @@ -5,6 +5,7 @@ The following images in the directory "content/reddit/" are (C) 2008 CondeNet, I downgray.png downmod.png mail.png + mailgray.png The following images in the directory "content/reddit/" are (C) 2008 Mark James, licensed under the Creative Commons Attribution 2.5 License: comments.png diff --git a/locale/en-US/reddit.dtd b/locale/en-US/reddit.dtd index 0cc0de0..4a3e28b 100644 --- a/locale/en-US/reddit.dtd +++ b/locale/en-US/reddit.dtd @@ -27,3 +27,9 @@ + + + + + + diff --git a/modules/reddit/reddit.jsm b/modules/reddit/reddit.jsm index dc001d5..0b8211f 100644 --- a/modules/reddit/reddit.jsm +++ b/modules/reddit/reddit.jsm @@ -2,6 +2,7 @@ Components.utils.import("resource://socialite/socialite.jsm"); logger = Components.utils.import("resource://socialite/utils/log.jsm"); Components.utils.import("resource://socialite/site.jsm"); Components.utils.import("resource://socialite/utils/action/action.jsm"); +Components.utils.import("resource://socialite/utils/action/cachedAction.jsm"); Components.utils.import("resource://socialite/utils/hitch.jsm"); Components.utils.import("resource://socialite/utils/domUtils.jsm"); Components.utils.import("resource://socialite/reddit/authentication.jsm"); @@ -16,7 +17,7 @@ let XPathResult = Components.interfaces.nsIDOMXPathResult; let stringBundle = Components.classes["@mozilla.org/intl/stringbundle;1"] .getService(Components.interfaces.nsIStringBundleService) .createBundle("chrome://socialite/locale/reddit.properties") - + function RedditSite(siteID, siteName, siteURL) { SocialiteSite.apply(this, arguments); } @@ -37,10 +38,23 @@ RedditSite.prototype.onLoad = function() { this.lastNewMessageCount = null; this.API.init(version); + + this.cached = { + mysubreddits: CachedAction(this.API.mysubreddits.bind(this.API), 30*60), + myuserinfo: CachedAction(this.API.myuserinfo.bind(this.API), 1*60) + }; + + this.cached._removeUsernameWatch = this.API.auth.onUsernameChange.watch( + hitchThis(this, function(username) { + // Reset subreddit cache when the username changes. + this.cached.mysubreddits.cachedValue.reset(); + }) + ); }; RedditSite.prototype.onUnload = function() { SocialiteSite.prototype.onUnload.apply(this, arguments); + this.cached._removeUsernameWatch(); this.API.destroy(); }; @@ -446,7 +460,7 @@ RedditSite.prototype.createBarSubmitUI = function(document) { var site = this; barSubmit.afterBound = function() { // Get subreddit listing and initialize menu - site.API.mysubreddits_cached( + site.cached.mysubreddits( function success(r, json) { // Check that the bar hasn't been removed if (barSubmit.parentNode != null) { @@ -506,6 +520,14 @@ RedditSite.prototype.createBarSubmitUI = function(document) { return barSubmit; }; +RedditSite.prototype.createInfoUI = function(document) { + var redditSiteInfo = document.createElement("hbox"); + redditSiteInfo.site = this; + redditSiteInfo.className = "reddit-site-info"; + redditSiteInfo.style.MozBinding = "url(chrome://socialite/content/reddit/redditSiteInfo.xml#reddit-site-info)"; + return redditSiteInfo; +}; + RedditSite.prototype.createPreferencesUI = function(document, propertiesWindow) { var propertiesBox = document.createElement("vbox"); diff --git a/modules/reddit/redditAPI.jsm b/modules/reddit/redditAPI.jsm index 53cb838..33814fa 100644 --- a/modules/reddit/redditAPI.jsm +++ b/modules/reddit/redditAPI.jsm @@ -2,7 +2,6 @@ logger = Components.utils.import("resource://socialite/utils/log.jsm"); Components.utils.import("resource://socialite/utils/action/action.jsm"); -Components.utils.import("resource://socialite/utils/action/cachedAction.jsm"); http = Components.utils.import("resource://socialite/utils/action/httpRequest.jsm"); Components.utils.import("resource://socialite/utils/hitch.jsm"); Components.utils.import("resource://socialite/utils/quantizer.jsm"); @@ -15,7 +14,6 @@ var EXPORTED_SYMBOLS = ["RedditAPI", "RedditVersion"]; QUANTIZE_TIME = 1000; AUTH_EXPIRE_AGE = 4*60*60; -SUBREDDITS_EXPIRE_AGE = 30*60; // Socialite recognizes the following unofficial versions for compatibility purposes: // @@ -59,11 +57,8 @@ function sameURL(func1, arg1, func2, arg2) { return (url1 == url2) && (subreddit1 == subreddit2); } -function sameLinkID(func1, arg1, func2, arg2) { - var linkID1 = arg1[0]; - var linkID2 = arg2[0]; - - return (linkID1 == linkID2); +function sameFirstArg(func1, arg1, func2, arg2) { + return (arg1[0] == arg2[0]); } function tryJSON(action, r) { @@ -146,23 +141,19 @@ function RedditAPI(siteURL) { this.urlinfoQuantizer = new Quantizer("reddit.urlInfo.quantizer", QUANTIZE_TIME, sameURL); this.urlinfo = Action("reddit.urlinfo", this.urlinfoQuantizer.quantize(this._urlinfo)); - this.thinginfoQuantizer = new Quantizer("reddit.thinginfo.quantizer", QUANTIZE_TIME, sameLinkID); + this.thinginfoQuantizer = new Quantizer("reddit.thinginfo.quantizer", QUANTIZE_TIME, sameFirstArg); this.thinginfo = Action("reddit.thinginfo", this.thinginfoQuantizer.quantize(this._thinginfo)); - - this.voteQuantizer = new Quantizer("reddit.vote.quantizer", QUANTIZE_TIME, sameLinkID); + + this.voteQuantizer = new Quantizer("reddit.vote.quantizer", QUANTIZE_TIME, sameFirstArg); this.vote = Action("reddit.vote", this.voteQuantizer.quantize(this._vote)); - this.saveQuantizer = new Quantizer("reddit.save.quantizer", QUANTIZE_TIME, sameLinkID); + this.saveQuantizer = new Quantizer("reddit.save.quantizer", QUANTIZE_TIME, sameFirstArg); this.save = Action("reddit.save", this.saveQuantizer.quantize(this._save)); this.unsave = Action("reddit.unsave", this.saveQuantizer.quantize(this._unsave)); - this.hideQuantizer = new Quantizer("reddit.hide.quantizer", QUANTIZE_TIME, sameLinkID); + this.hideQuantizer = new Quantizer("reddit.hide.quantizer", QUANTIZE_TIME, sameFirstArg); this.hide = Action("reddit.hide", this.hideQuantizer.quantize(this._hide)); this.unhide = Action("reddit.unhide", this.hideQuantizer.quantize(this._unhide)); - - this.mysubreddits_cached = CachedAction(this.mysubreddits, SUBREDDITS_EXPIRE_AGE); - - this.messages = Action("reddit.messages", this._messages); } RedditAPI.prototype.init = function(version, auth) { @@ -181,25 +172,16 @@ RedditAPI.prototype.init = function(version, auth) { } else { this.auth = new RedditAuth(this.siteURL, this.version, AUTH_EXPIRE_AGE); } - - // Reset subreddit cache when the username changes. - let self = this; - this._removeUsernameWatch = this.auth.onUsernameChange.watch(function(username) { - self.mysubreddits_cached.cachedValue.reset(); - }); } RedditAPI.prototype.destroy = function() { - this._removeUsernameWatch(); + // Nothing to clean up, yet. } -RedditAPI.prototype._urlinfo = function(url, subreddit, action) { - logger.log("reddit", "Making info API request"); - - http.GetAction( - APIURL(this.auth.siteURL, "info.json", subreddit), - {url:url, limit:1}, - +// Generalized API actions + +RedditAPI.prototype._getJSON = function(url, params, action) { + http.GetAction(url, params, function success(r) { tryJSON(action, r); }, @@ -207,29 +189,81 @@ RedditAPI.prototype._urlinfo = function(url, subreddit, action) { ).perform(); }; +RedditAPI.prototype._postLinkCommand = function(command, linkID, params, action) { + logger.log("reddit", "Making " + command + " API call"); + + if (params == null) { + params = {}; + } + params["id"] = linkID; + + let self = this; + this.auth.actionParams(action, params, + function success(authParams) { + var act = http.PostAction(APIURL(self.auth.siteURL, command), authParams); + act.chainTo(action); + act.perform(); + } + ); +}; + +function postLinkCommand(command) { + return function(linkID, action) { + this._postLinkCommand(command, linkID, {}, action); + }; +}; + +// Reddit API actions + +RedditAPI.prototype._urlinfo = function(url, subreddit, action) { + logger.log("reddit", "Making info API request"); + this._getJSON( + APIURL(this.auth.siteURL, "info.json", subreddit), + {url:url, limit:1}, action + ); +}; + RedditAPI.prototype._thinginfo = function(thingID, action) { logger.log("reddit", "Making by_id API request"); - - http.GetAction( + this._getJSON( this.auth.siteURL + "by_id/" + thingID + ".json", - null, - - function success(r) { - tryJSON(action, r); - }, - action.chainFailure() - ).perform(); + null, action + ); }; RedditAPI.prototype.randomrising = Action("reddit.randomrising", function(action) { logger.log("reddit", "Making randomrising API request"); - - var act = http.GetAction( + this._getJSON( this.auth.siteURL + "randomrising.json", - {limit:1}, - - function success(r) { - tryJSON(action, r); + {limit:1}, action + ); +}); + +RedditAPI.prototype.messages = Action("reddit.messages", function(mark, action) { + logger.log("reddit", "Making messages API request"); + this._getJSON( + this.auth.siteURL + "message/inbox.json", + {mark:mark}, action + ); +}); + +RedditAPI.prototype.userinfo = Action("reddit.userinfo", function(username, action) { + logger.log("reddit", "Making userinfo API request"); + this._getJSON( + this.auth.siteURL + "user/" + username + "/about.json", + null, action + ); +}); + +RedditAPI.prototype.myuserinfo = Action("reddit.myuserinfo", function(action) { + logger.log("reddit", "Making myuserinfo API request"); + + let self = this; + this.auth.getAuthInfo( + function(authInfo) { + var act = self.userinfo(); + act.chainTo(action); + act.perform(authInfo.username); }, action.chainFailure() ).perform(); @@ -243,65 +277,17 @@ RedditAPI.prototype.mysubreddits = Action("reddit.mysubreddits", function(action act.perform(); }); -RedditAPI.prototype._messages = function(mark, action) { - logger.log("reddit", "Making messages API request"); - - http.GetAction( - this.auth.siteURL + "message/inbox.json", - {mark:mark}, - - function success(r) { - tryJSON(action, r); - }, - action.chainFailure() - ).perform(); -}; - RedditAPI.prototype._vote = function(linkID, isLiked, action) { - logger.log("reddit", "Making vote API call"); - var dir; - if (isLiked == true) { - dir = 1; - } else if (isLiked == false) { - dir = -1; - } else { + if (isLiked == null) { dir = 0; + } else { + dir = isLiked ? 1 : -1; } - - let self = this; - this.auth.actionParams(action, - {id:linkID, dir:dir}, - function success(authParams) { - var act = http.PostAction(APIURL(self.auth.siteURL, "vote"), authParams); - act.chainTo(action); - act.perform(); - } - ); -}; - - -RedditAPI.prototype._simpleLinkPost = function(command, linkID, action) { - logger.log("reddit", "Making " + command + " API call"); - - let self = this; - this.auth.actionParams(action, - {id:linkID}, - function success(authParams) { - var act = http.PostAction(APIURL(self.auth.siteURL, command), authParams); - act.chainTo(action); - act.perform(); - } - ); -}; - -function simpleLinkPost(command) { - return function(linkID, action) { - this._simpleLinkPost(command, linkID, action); - }; + this._postLinkCommand("vote", linkID, {dir:dir}, action); }; -RedditAPI.prototype._save = simpleLinkPost("save"); -RedditAPI.prototype._unsave = simpleLinkPost("unsave"); -RedditAPI.prototype._hide = simpleLinkPost("hide"); -RedditAPI.prototype._unhide = simpleLinkPost("unhide"); \ No newline at end of file +RedditAPI.prototype._save = postLinkCommand("save"); +RedditAPI.prototype._unsave = postLinkCommand("unsave"); +RedditAPI.prototype._hide = postLinkCommand("hide"); +RedditAPI.prototype._unhide = postLinkCommand("unhide"); \ No newline at end of file diff --git a/modules/utils/action/action.jsm b/modules/utils/action/action.jsm index fe8fdc1..6971156 100644 --- a/modules/utils/action/action.jsm +++ b/modules/utils/action/action.jsm @@ -5,6 +5,17 @@ Components.utils.import("resource://socialite/utils/hitch.jsm"); var EXPORTED_SYMBOLS = ["Action", "ActionType"]; +let ActionConstructorMethodProto = { + // Actions will use whatever "this" is set to when they are called. Use this method to create a reference to an action with a static value of "this". + bind: function(thisObj) { + bound = hitchThis(thisObj, this); + bound.actionClass = this.actionClass; + bound.actionPrototype = this.actionPrototype; + return bound; + } +} +ActionConstructorMethodProto.__proto__ = Function.prototype; + function Action(name, func) { // Create a new object "class" for this action var ActionClass = function(){}; @@ -25,6 +36,7 @@ function Action(name, func) { return action; } + ActionConstructorMethod.__proto__ = ActionConstructorMethodProto; // To modify the action class after the fact, we'll create a property on the constructor ActionConstructorMethod.actionClass = ActionClass; ActionConstructorMethod.actionPrototype = ActionClass.prototype; @@ -33,70 +45,71 @@ function Action(name, func) { } function ActionType() {} - -ActionType.prototype.perform = function() { - logger.log("action", "Performing " + this.name + " action"); - - this.startTime = Date.now(); +ActionType.prototype = { + perform: function() { + logger.log("action", "Performing " + this.name + " action"); - var argArray = Array.prototype.splice.call(arguments, 0) || []; - - // Copy and store the array - this.lastArgs = argArray.concat(); + this.startTime = Date.now(); + + var argArray = Array.prototype.splice.call(arguments, 0) || []; - // Add this action object to the end of the arguments list and call. - var newargs = this.addToArgs(argArray); - return this.func.apply(this.thisObj, newargs); -} - -ActionType.prototype.addToArgs = function(args) { - // Arguments contain the arguments passed to this function, with this action object at the end. - var newargs = Array.prototype.splice.call(args, 0) || []; - if ((newargs.length == 0) || (newargs[newargs.length-1] != this)) { - newargs.push(this); - } - return newargs; -} - -ActionType.prototype.doCallback = function(callback, args) { - // Arguments contain the arguments passed to this function, with this action object at the end. - var newargs = this.addToArgs(args); + // Copy and store the array + this.lastArgs = argArray.concat(); + + // Add this action object to the end of the arguments list and call. + var newargs = this.addToArgs(argArray); + return this.func.apply(this.thisObj, newargs); + }, - if (callback) { - // A little sugar to allow actions to be passed in without calling toFunction() - if (callback instanceof ActionType) { - return callback.perform.apply(callback, newargs); - } else { - return callback.apply(null, newargs); + addToArgs: function(args) { + // Arguments contain the arguments passed to this function, with this action object at the end. + var newargs = Array.prototype.splice.call(args, 0) || []; + if ((newargs.length == 0) || (newargs[newargs.length-1] != this)) { + newargs.push(this); } - } -} - -ActionType.prototype.success = function() { - logger.log("action", this.name + " succeeded"); - return this.doCallback(this.successCallback, arguments); -} - -ActionType.prototype.failure = function() { - logger.log("action", this.name + " failed"); - return this.doCallback(this.failureCallback, arguments); -} - -ActionType.prototype.toFunction = function() { - return hitchThis(this, this.perform); -} - -ActionType.prototype.chainSuccess = function() { - return hitchThis(this, this.success); -} - -ActionType.prototype.chainFailure = function() { - return hitchThis(this, this.failure); -} - -ActionType.prototype.chainTo = function(action) { - this.successCallback = function() {action.success.apply(action, arguments)}; - this.failureCallback = function() {action.failure.apply(action, arguments)}; + return newargs; + }, + + doCallback: function(callback, args) { + // Arguments contain the arguments passed to this function, with this action object at the end. + var newargs = this.addToArgs(args); + + if (callback) { + // A little sugar to allow actions to be passed in without calling toFunction() + if (callback instanceof ActionType) { + return callback.perform.apply(callback, newargs); + } else { + return callback.apply(null, newargs); + } + } + }, + + success: function() { + logger.log("action", this.name + " succeeded"); + return this.doCallback(this.successCallback, arguments); + }, + + failure: function() { + logger.log("action", this.name + " failed"); + return this.doCallback(this.failureCallback, arguments); + }, + + toFunction: function() { + return hitchThis(this, this.perform); + }, - return; + chainSuccess: function() { + return hitchThis(this, this.success); + }, + + chainFailure: function() { + return hitchThis(this, this.failure); + }, + + chainTo: function(action) { + this.successCallback = function() {action.success.apply(action, arguments)}; + this.failureCallback = function() {action.failure.apply(action, arguments)}; + + return; + } } diff --git a/modules/utils/action/cachedAction.jsm b/modules/utils/action/cachedAction.jsm index 083ea03..29137dc 100644 --- a/modules/utils/action/cachedAction.jsm +++ b/modules/utils/action/cachedAction.jsm @@ -7,7 +7,7 @@ var EXPORTED_SYMBOLS = ["CachedAction"]; function CachedAction(updateAction, expireSeconds) { let updateActionName = updateAction.actionClass.prototype.name; - let cachedAction = Action(updateActionName+"[cached]", _cachedAction); + let cachedAction = Action(updateActionName+".cache", _cachedAction); let actionPrototype = cachedAction.actionPrototype; actionPrototype.updateAction = updateAction; @@ -18,7 +18,7 @@ function CachedAction(updateAction, expireSeconds) { } function _cachedAction(action) { - if (action.cachedValue.valid) { + if (action.cachedValue.isValid) { action.success.apply(action, action.cachedValue.value); } else { action.updateAction.call(this, @@ -45,13 +45,17 @@ CachedValue.prototype = { return this._value; }, - get expired() { + get hasValue() { + return this.value != false; + }, + + get isExpired() { let elapsed = Date.now() - this.lastUpdated; - return elapsed >= this.expireSeconds*1000; + return this.expireSeconds != false && elapsed >= this.expireSeconds*1000; }, - get valid() { - return (this.value != false) && (!this.expired); + get isValid() { + return this.hasValue && !this.isExpired; }, updated: function() { diff --git a/modules/utils/domUtils.jsm b/modules/utils/domUtils.jsm index 2628044..2bac5d8 100644 --- a/modules/utils/domUtils.jsm +++ b/modules/utils/domUtils.jsm @@ -39,6 +39,26 @@ function insertListboxSorted(insertElement, listbox, compareFunc) { } } +function compareBy(keyFunction) { + return function(item1, item2) { + let key1 = keyFunction(item1); + let key2 = keyFunction(item2); + return key1.localeCompare(key2); + }; +} + +function sortChildren(parentElement, compareFunc) { + sorted = Array.sort(Array.slice(parentElement.childNodes), compareFunc) + + let parentElement = curElement.parentNode; + if (i == nodeList.length) { + // Insert after the last node + parentElement.insertBefore(insertElement, curElement.nextSibling); + } else { + parentElement.insertBefore(insertElement, curElement); + } +} + /** * Return the first child node of an element with the specified class. * diff --git a/modules/utils/faviconWatch.jsm b/modules/utils/faviconWatch.jsm index f5f234c..d254338 100644 --- a/modules/utils/faviconWatch.jsm +++ b/modules/utils/faviconWatch.jsm @@ -40,14 +40,22 @@ function addFaviconWatch(siteURL, changedCallback) { return watchables[siteURISpec].watch(changedCallback); } +function useFaviconWatch(siteURL, changedCallback) { + let removeFunction = addFaviconWatch(siteURL, changedCallback); + changedCallback(getFaviconURL(siteURL)); + return removeFunction; +} + function useFaviconAsAttribute(element, attributeName, siteURL) { - function update(faviconURL) { + return useFaviconWatch(siteURL, function update(faviconURL) { element.setAttribute(attributeName, faviconURL); - } - - let removeFunction = addFaviconWatch(siteURL, update); - update(getFaviconURL(siteURL)); - return removeFunction; + }); +} + +function useFaviconAsProperty(element, propertyName, siteURL) { + return useFaviconWatch(siteURL, function update(faviconURL) { + element[propertyName] = faviconURL; + }); } historyObserver = { diff --git a/modules/utils/hitch.jsm b/modules/utils/hitch.jsm index 26289e9..10fa556 100644 --- a/modules/utils/hitch.jsm +++ b/modules/utils/hitch.jsm @@ -2,11 +2,9 @@ var EXPORTED_SYMBOLS = ["hitchThis", "hitchHandler", "hitchHandlerFlip"] // Little helper to ease simple hitching operations function hitchThis(thisArg, func) { - var self = thisArg; var hitched = function() { return func.apply(thisArg, arguments); } - return hitched; } diff --git a/skin/reddit.css b/skin/reddit.css index 217725c..c081856 100644 --- a/skin/reddit.css +++ b/skin/reddit.css @@ -1,11 +1,3 @@ -.reddit-content-ui[isloggedin="false"] .buttonbox > .reddit-logged-in { - display: none; -} - -.reddit-content-ui[isloggedin="true"] .buttonbox > .reddit-not-logged-in { - display: none; -} - .reddit-score { font-weight: bold; color: #969696; @@ -31,4 +23,4 @@ .reddit-subreddit:hover { text-decoration: underline; -} +} \ No newline at end of file diff --git a/skin/socialite.css b/skin/socialite.css index 82ce7ad..a1e848a 100644 --- a/skin/socialite.css +++ b/skin/socialite.css @@ -1,3 +1,5 @@ +/* Socialite bar styles */ + .socialite-title { margin-bottom: 2px !important; } @@ -55,7 +57,13 @@ notification[value="socialite-submitbar-notification"] { margin-bottom: 0; } -.socialite-notification-button[image] > .button-box > .button-icon, +.socialite-form-error { + color: red; +} + +/* Socialite bar buttons */ + +.socialite-notification-button[image] > .button-box > .button-icon, e .socialite-notification-button > .button-box > .button-icon[src] { margin-right: 3px; } @@ -75,6 +83,20 @@ notification[value="socialite-submitbar-notification"] { display: none; } +.socialite-not-logged-in { + display: none; +} + +.socialite-login-state[isloggedin="false"] .socialite-logged-in { + display: none; +} + +.socialite-login-state[isloggedin="false"] .socialite-not-logged-in { + display: -moz-box; +} + +/* URL Bar Icons */ + .socialite-urlbar-icon { -moz-binding: url(chrome://socialite/content/urlBarIcon.xml#urlbar-icon); } @@ -115,11 +137,46 @@ notification[value="socialite-submitbar-notification"] { display: block; } +/* Info bar tooltips */ -hbox[anonid="loadedSiteSelector"] { - -moz-binding: url(chrome://socialite/content/socialiteBar.xml#socialite-loaded-site-selector); +.socialite-site-info-box { + -moz-binding: url(chrome://socialite/content/siteInfoBox.xml#site-info-box); } -.socialite-form-error { - color: red; +.socialite-site-info-tooltip { + -moz-binding: url(chrome://socialite/content/siteInfoBox.xml#site-info-tooltip); +} + +.socialite-site-info-box groupbox { + margin: 0; + border: none; + -moz-appearance: none; +} + +.socialite-site-info-box .groupbox-body { + padding: 0; + -moz-appearance: none; + font-size: 85%; +} + +.socialite-site-info-box .caption-text { + -moz-margin-start: 4px !important; +} + +.socialite-site-info-box caption { + padding: 0px 5px; + border: 1px solid rgba(0,0,0,0.5); + background: rgba(0,0,0,0.1); + -moz-border-radius: 3px; + font-weight: bold; +} + +.socialite-site-info-username { + font-size:110%; } + +.socialite-site-info-data { + color: #555; + font-weight: bold; + text-align: right; +} \ No newline at end of file