├── .gitignore ├── build_extension.sh ├── chrome ├── auth.js ├── background.html ├── badge.png ├── chrome.js ├── icon128.png ├── icon16.png ├── icon48.png ├── manifest.json ├── popup.html └── popup.js ├── common ├── stackalert.js └── style.css ├── firefox ├── chrome.manifest ├── chrome │ ├── content │ │ ├── auth.xul │ │ ├── popup.html │ │ ├── stackalert.xml │ │ └── stackalert.xul │ ├── locale │ │ └── en-US │ │ │ └── stackalert.dtd │ └── skin │ │ ├── button.png │ │ └── stackalert.css ├── icon.png └── install.rdf ├── opera ├── auth_complete.html ├── badge.png ├── config.xml ├── icon.png ├── includes │ └── auth.user.js ├── index.html ├── opera.js └── popup.html └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Swap files for emacs, gedit etc ## 2 | 3 | *~ 4 | # OS generated files # 5 | ###################### 6 | .DS_Store* 7 | ehthumbs.db 8 | Icon? 9 | Thumbs.db 10 | Desktop.ini 11 | -------------------------------------------------------------------------------- /build_extension.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #==================== 4 | # Chrome 5 | #==================== 6 | 7 | echo -e "\033[34mPackaging Chrome extension...\033[m" 8 | 9 | # Switch to the directory and copy the JS / CSS files 10 | cp common/stackalert.js chrome 11 | cp common/style.css chrome 12 | 13 | # Check for an existing PEM key 14 | KEYFILE="stackalert.pem" 15 | 16 | if [ -f $KEYFILE ] ; then 17 | KEYARG="--pack-extension-key=$KEYFILE" 18 | echo -e "\033[32m - found existing key: $KEYFILE\033[m" 19 | fi 20 | 21 | ARGS="--pack-extension=chrome $KEYARG" 22 | 23 | # Check to see if Chrome or Chromium is installed 24 | if ! chromium-browser $ARGS 2>/dev/null ; then 25 | if ! google-chrome $ARGS 2>/dev/null ; then 26 | echo -e "\033[1m\033[31mError: neither Google Chrome nor Chromium was found on your system. At least one of them is required to run this script.\033[m" 27 | exit 1 28 | fi 29 | fi 30 | 31 | # Clean up 32 | mv chrome.crx stackalert.crx 33 | 34 | if [ -f chrome.pem ] ; then 35 | mv chrome.pem stackalert.pem 36 | fi 37 | 38 | #==================== 39 | # Firefox 40 | #==================== 41 | 42 | echo -e "\033[34mPackaging Firefox add-on...\033[m" 43 | 44 | # Switch to the directory and copy the JS / CSS files 45 | cd firefox 46 | cp ../common/stackalert.js chrome/content/stackalert.jsm 47 | cp ../common/style.css chrome/skin/popup.css 48 | 49 | # Now create the add-on 50 | zip -r ../stackalert.xpi . 51 | 52 | #==================== 53 | # Opera 54 | #==================== 55 | 56 | echo -e "\033[34mPackaging Opera extension...\033[m" 57 | 58 | # Switch to the directory and copy the JS / CSS files 59 | cd ../opera 60 | cp ../common/stackalert.js . 61 | cp ../common/style.css . 62 | 63 | # Now create the add-on 64 | zip -r ../stackalert.oex . 65 | 66 | echo -e "\033[32mPackaging complete!\033[m" -------------------------------------------------------------------------------- /chrome/auth.js: -------------------------------------------------------------------------------- 1 | // Send the hash to complete the authorization 2 | chrome.extension.sendRequest({ command: 'authorize', 3 | hash: window.location.hash }); -------------------------------------------------------------------------------- /chrome/background.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /chrome/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathan-osman/Stack-Alert/b5339b41186e763d5d49b6646966d7b1718a79d2/chrome/badge.png -------------------------------------------------------------------------------- /chrome/chrome.js: -------------------------------------------------------------------------------- 1 | // This file sets the browser and API key for Chrome 2 | 3 | StackAlert.Browser = 'chrome'; 4 | StackAlert.IconSize = '19'; 5 | StackAlert.APIKey = 'jo7ss)kjUytEaOELX5xdcg(('; 6 | StackAlert.ClientID = '24'; 7 | 8 | // Perform the initial update 9 | StackAlert.PerformUpdate(); 10 | 11 | // Listen for requests to update the data 12 | chrome.extension.onRequest.addListener(function(request, sender) { 13 | 14 | // The command to perform is listed in request 15 | if(request.command == 'update') 16 | StackAlert.PerformUpdate(); 17 | else if(request.command == 'authorize') { 18 | StackAlert.CompleteAuthorization(request.hash); 19 | chrome.tabs.remove(sender.tab.id); 20 | StackAlert.PerformUpdate(); 21 | } 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /chrome/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathan-osman/Stack-Alert/b5339b41186e763d5d49b6646966d7b1718a79d2/chrome/icon128.png -------------------------------------------------------------------------------- /chrome/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathan-osman/Stack-Alert/b5339b41186e763d5d49b6646966d7b1718a79d2/chrome/icon16.png -------------------------------------------------------------------------------- /chrome/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathan-osman/Stack-Alert/b5339b41186e763d5d49b6646966d7b1718a79d2/chrome/icon48.png -------------------------------------------------------------------------------- /chrome/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | 4 | "name": "Stack Alert", 5 | "version": "0.21", 6 | "description": "Monitors the inbox of a Stack Exchange account for new items.", 7 | "icons": { "16": "icon16.png", 8 | "48": "icon48.png", 9 | "128": "icon128.png" }, 10 | 11 | "update_url": "http://quickmediasolutions.com/stackalert/updates.xml", 12 | 13 | "background": { 14 | "page": "background.html" 15 | }, 16 | "browser_action": { 17 | "default_icon": "badge.png", 18 | "default_popup": "popup.html" 19 | }, 20 | "content_scripts": [ 21 | { 22 | "matches": ["https://stackexchange.com/oauth/login_success*"], 23 | "js": ["auth.js"] 24 | } 25 | ], 26 | 27 | "permissions": [ 28 | "https://api.stackexchange.com/", 29 | "notifications", 30 | "tabs" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /chrome/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /chrome/popup.js: -------------------------------------------------------------------------------- 1 | var div = document.createElement('div'); 2 | StackAlert.GeneratePopupHTML(window, div); 3 | 4 | document.getElementsByTagName('body')[0].appendChild(div); 5 | -------------------------------------------------------------------------------- /common/stackalert.js: -------------------------------------------------------------------------------- 1 | // Stack Alert - Monitor your Stack Exchange inbox. 2 | // Copyright (C) 2013 Nathan Osman 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | var EXPORTED_SYMBOLS = ['StackAlert']; 18 | 19 | var StackAlert = { 20 | 21 | //========================================================== 22 | // Constants 23 | //========================================================== 24 | 25 | // Each add-on needs to set these values its 26 | 27 | Browser: 'firefox', // the current browser 28 | IconSize: 24, // the size of the icon in the toolbar 29 | 30 | APIKey: ')VDFrEIR*wIQ32QVY19EGQ((', // the API key for this browser 31 | ClientID: '49', // the client ID for this browser 32 | 33 | //========================================================== 34 | // Methods for getting / setting preferences 35 | //========================================================== 36 | 37 | // Sets the preference with the given name to the given value 38 | SetPreference: function(name, value) { 39 | 40 | if(StackAlert.Browser == 'firefox') 41 | Components.classes['@mozilla.org/preferences-service;1'] 42 | .getService(Components.interfaces.nsIPrefService) 43 | .getBranch('extensions.stackalert.') 44 | .setCharPref(name, value); 45 | else 46 | localStorage.setItem(name, value); 47 | 48 | }, 49 | 50 | // Retrieves the preference with the given name, setting and 51 | // returning the provided default value if the preference does not exist 52 | GetPreference: function(name, default_value) { 53 | 54 | if(StackAlert.Browser == 'firefox') { 55 | 56 | var prefs = Components.classes['@mozilla.org/preferences-service;1'] 57 | .getService(Components.interfaces.nsIPrefService) 58 | .getBranch('extensions.stackalert.'); 59 | 60 | if(prefs.prefHasUserValue(name)) 61 | return prefs.getCharPref(name); 62 | else { 63 | 64 | StackAlert.SetPreference(name, default_value); 65 | return default_value; 66 | 67 | } 68 | 69 | } else { 70 | 71 | if(localStorage.getItem(name) !== null) 72 | return localStorage.getItem(name); 73 | else { 74 | 75 | StackAlert.SetPreference(name, default_value); 76 | return default_value; 77 | 78 | } 79 | } 80 | }, 81 | 82 | //========================================================== 83 | // Utility methods for working with colors / icons 84 | //========================================================== 85 | 86 | GenerateRGB: function(r, g, b) { 87 | 88 | // If either of the other two parameters were not specified, 89 | // set their values to 'r'. 90 | if(typeof g == 'undefined') 91 | g = r; 92 | if(typeof b == 'undefined') 93 | b = r; 94 | 95 | return 'rgb(' + parseInt(r) + ',' + parseInt(g) + ',' + parseInt(b) + ')'; 96 | 97 | }, 98 | 99 | // Generates a data:// URL for the button 100 | GenerateImageData: function(document, text, complete_callback) { 101 | 102 | // Create a canvas element that will be used to overlay the icon 103 | // and the colored text. 104 | var canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas'); 105 | 106 | canvas.setAttribute('width', StackAlert.IconSize); 107 | canvas.setAttribute('height', StackAlert.IconSize); 108 | 109 | // Get the context for the canvas 110 | var context = canvas.getContext('2d'); 111 | 112 | // Load the base image and draw it onto the canvas 113 | var base_image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img'); 114 | 115 | // Once the image has loaded, draw it 116 | base_image.addEventListener('load', function() { 117 | 118 | context.drawImage(base_image, 0, 0); 119 | 120 | // Calculate the dimensions of the text 121 | var height = parseInt(StackAlert.IconSize / 24 * 18); 122 | context.font = 'bold ' + height + 'px sans-serif'; 123 | var text_width = context.measureText(text).width; 124 | var x = StackAlert.IconSize / 2 - text_width / 2; 125 | var y = StackAlert.IconSize / 2 + height / 2 - 2; 126 | 127 | // Draw the text 'shadow' and then the text 128 | context.fillStyle = StackAlert.GenerateRGB(0); 129 | context.fillText(text, x - 1, y - 1); 130 | context.fillStyle = StackAlert.GenerateRGB(255); 131 | context.fillText(text, x, y); 132 | 133 | // Return the canvas object 134 | complete_callback(canvas, context); 135 | 136 | }, true); 137 | 138 | base_image.src = (StackAlert.Browser == 'firefox')? 139 | 'chrome://stackalert/skin/button.png': 140 | 'badge.png'; 141 | 142 | }, 143 | 144 | // The single button instance 145 | ButtonInstance: null, 146 | 147 | // List of buttons that receive notifications when the icon changes 148 | ButtonList: [], 149 | 150 | // The current details for all of the buttons 151 | ButtonText: null, 152 | ButtonTooltip: null, 153 | 154 | // Updates the information on a particular icon 155 | UpdateButton: function(button) { 156 | 157 | StackAlert.GenerateImageData(button.getUserData('document'), 158 | StackAlert.ButtonText, 159 | function(canvas, context) { 160 | 161 | button.style.listStyleImage = 'url(' + canvas.toDataURL("image/png") + ')'; 162 | button.setAttribute('tooltiptext', StackAlert.ButtonTooltip); 163 | 164 | }); 165 | }, 166 | 167 | // Updates the information on all currently registered buttons 168 | // and stores the information for updating future buttons. 169 | UpdateAllButtons: function(text, tooltip, color) { 170 | 171 | if(StackAlert.Browser == 'firefox') { 172 | 173 | StackAlert.ButtonText = text; 174 | StackAlert.ButtonTooltip = tooltip; 175 | 176 | for(var i=0;i]/g, function (m) { "&" + ({ "&": "amp", '"': "quot", "<": "lt", ">": "gt" })[m] + ";" }); 241 | 242 | }, 243 | 244 | // And this function comes from further down the same page as the 245 | // function above. 246 | SafelyAppendHTML: function(html, element) { 247 | 248 | if(StackAlert.Browser == 'firefox') { 249 | 250 | var fragment = Components.classes["@mozilla.org/feed-unescapehtml;1"] 251 | .getService(Components.interfaces.nsIScriptableUnescapeHTML) 252 | .parseFragment(html, false, null, element); 253 | 254 | element.appendChild(fragment); 255 | 256 | } else 257 | element.innerHTML += html; 258 | 259 | }, 260 | 261 | MakeAPIRequest: function(method, parameters, success_callback, failure_callback) { 262 | 263 | // Create the request 264 | var request = (StackAlert.Browser == 'firefox')?Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance():new XMLHttpRequest(); 265 | 266 | if(StackAlert.Browser == 'firefox') 267 | request.mozBackgroundRequest=true; 268 | 269 | // URL-encode the parameters 270 | var param_str = ''; 271 | for(var key in parameters) 272 | param_str += '&' + key + '=' + encodeURIComponent(parameters[key]); 273 | 274 | // Open the request 275 | request.open('GET', 'https://api.stackexchange.com/2.0' + method + '?key=' + StackAlert.APIKey + param_str); 276 | 277 | request.onload = function() { 278 | 279 | var json_response = JSON.parse(request.responseText); 280 | 281 | if(typeof json_response['error_message'] != 'undefined') 282 | failure_callback(json_response['error_message']); 283 | else 284 | success_callback(json_response); 285 | 286 | }; 287 | 288 | request.onerror = function() { 289 | 290 | failure_callback(request.statusText); 291 | 292 | }; 293 | 294 | // Send the request 295 | request.send(); 296 | 297 | }, 298 | 299 | // Displays the authorization window on the screen 300 | ShowAuthWindow: function(window) { 301 | 302 | // For firefox, we have an XUL window we display 303 | // but for everything else, we just open a window 304 | if(StackAlert.Browser == 'firefox') 305 | window.open('auth.xul', 'stackalert_auth', 'chrome'); 306 | else { 307 | 308 | var window_url = 'https://stackexchange.com/oauth/dialog?client_id=' + StackAlert.ClientID + 309 | '&scope=no_expiry,read_inbox&redirect_uri=' + encodeURIComponent('https://stackexchange.com/oauth/login_success'); 310 | 311 | var new_window = window.open(window_url, 'auth_window', 'width=640,height=400,menubar=no,toolbar=no,location=no,status=no'); 312 | 313 | } 314 | }, 315 | 316 | // Begins the authorization process by opening a window for approving the extension 317 | // using the specified parameters 318 | BeginAuthorization: function(browser, return_uri) { 319 | 320 | // Listen for the load event in the browser control so that we know when the 321 | // browser control navigates to another page. 322 | browser.addEventListener('load', function() { 323 | 324 | var browser_location = browser.contentWindow.location; 325 | 326 | if(browser_location.href.match(/^https:\/\/stackexchange\.com\/oauth\/login_success/) !== null) { 327 | 328 | var error_message = StackAlert.CompleteAuthorization(browser_location.hash); 329 | if(error_message != '') 330 | browser.contentWindow.alert("An error has occurred:\n\n" + error_message + "\n\nPlease click OK and try again."); 331 | 332 | // Force a refresh 333 | //... 334 | 335 | // Close the window (by a very confusing means) 336 | browser.contentWindow.parent.close(); 337 | 338 | } 339 | 340 | }, true); 341 | 342 | browser.setAttribute('src', 343 | 'https://stackexchange.com/oauth/dialog?client_id=' + StackAlert.ClientID + 344 | '&scope=no_expiry,read_inbox&redirect_uri=' + encodeURIComponent(return_uri)); 345 | 346 | }, 347 | 348 | // Completes the authorization process by storing the access token and fetching the data 349 | CompleteAuthorization: function(hash) { 350 | 351 | if(hash.indexOf('#') === 0) 352 | hash = hash.substr(1); 353 | 354 | hash = hash.split('&'); 355 | 356 | // Convert to an array 357 | var hash_map = {}; 358 | for(var i=0;iThere was a problem retrieving error details.

'), 427 | element); 428 | 429 | // Add the dismissal button 430 | var button = window.document.createElement('button'); 431 | button.innerHTML = 'OK'; 432 | button.addEventListener('click', function() { 433 | 434 | StackAlert.ResetError(); 435 | location.href = location.href; 436 | 437 | }, true); 438 | 439 | element.appendChild(button); 440 | 441 | } else if(StackAlert.GetPreference('access_token', '') == '') { 442 | 443 | // No access token has been specified yet, so generate the HTML 444 | // that asks the user to authorize the application. 445 | element.setAttribute('class', 'auth'); 446 | element.innerHTML = '

You need to authorize this extension to access the contents of your Stack Exchange account.

'; 447 | 448 | var button = window.document.createElement('button'); 449 | button.innerHTML = 'Authorize Extension'; 450 | button.addEventListener('click', function() { StackAlert.ShowAuthWindow(window); }, true); 451 | 452 | element.appendChild(button); 453 | 454 | } else { 455 | 456 | // Retrieve the inbox items 457 | var inbox_items = JSON.parse(StackAlert.GetPreference('inbox_contents', '[]')); 458 | 459 | element.setAttribute('class', 'inbox'); 460 | element.innerHTML = '

Inbox Contents

'; 461 | 462 | // Begin generating the HTML contents for the inbox 463 | var ul = window.document.createElement('ul'); 464 | ul.setAttribute('class', 'contents'); 465 | 466 | // The color of the item is determined by this factor 467 | var color_factor = 0.4; 468 | 469 | for(var i=0;i.

'; 594 | 595 | // Store the error HTML 596 | StackAlert.SetPreference('error_details', error_html); 597 | 598 | StackAlert.UpdateAllButtons('!', error_details); 599 | 600 | }); 601 | 602 | // Schedule the next update 603 | if(StackAlert.Browser == 'firefox') { 604 | 605 | StackAlert.Timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); 606 | StackAlert.Timer.initWithCallback(StackAlert, 120000, Components.interfaces.nsITimer.TYPE_ONE_SHOT); 607 | 608 | } else 609 | StackAlert.Timer = window.setTimeout(function() { StackAlert.PerformUpdate(); }, 120000); 610 | 611 | } else 612 | // Set a timeout to work around a Chrome bug 613 | if(StackAlert.Browser == 'firefox') 614 | StackAlert.UpdateAllButtons('X', 'Stack Alert has not been authorized to access your account.'); 615 | else 616 | window.setTimeout(function() { 617 | 618 | StackAlert.UpdateAllButtons('X', 'Stack Alert has not been authorized to access your account.'); 619 | 620 | }, 100); 621 | }, 622 | 623 | // Whether or not we have already begun the background timer 624 | BackgroundTimer: false, 625 | 626 | // Begins the background timer that checks periodically for new items 627 | StartBackgroundTimer: function() { 628 | 629 | if(!StackAlert.BackgroundTimer) { 630 | 631 | StackAlert.BackgroundTimer = true; 632 | StackAlert.PerformUpdate(); 633 | 634 | } 635 | } 636 | }; 637 | -------------------------------------------------------------------------------- /common/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: arial, helvetica, sans; 3 | width: 300px; 4 | } 5 | 6 | .auth button { 7 | width: 100%; 8 | } 9 | 10 | .inbox .contents { 11 | padding-left: 0px; 12 | } 13 | 14 | .inbox .contents li { 15 | list-style-type: none; 16 | padding: 4px 8px; 17 | margin: 6px 2px; 18 | border-radius: 8px; 19 | -moz-border-radius: 8px; 20 | } 21 | 22 | .inbox .contents a { 23 | text-decoration: none; 24 | } 25 | 26 | .inbox .contents a:hover { 27 | color: #777 !important; 28 | } 29 | 30 | .inbox .contents .title { 31 | font-size: 10pt; 32 | display: block; 33 | font-weight: bold; 34 | } 35 | 36 | .inbox .contents .body { 37 | font-size: 9pt; 38 | } 39 | 40 | .error h3 { 41 | color: #900; 42 | } 43 | 44 | .error button { 45 | width: 100%; 46 | } -------------------------------------------------------------------------------- /firefox/chrome.manifest: -------------------------------------------------------------------------------- 1 | content stackalert chrome/content/ 2 | skin stackalert classic/1.0 chrome/skin/ 3 | 4 | style chrome://global/content/customizeToolbar.xul chrome://stackalert/skin/stackalert.css 5 | overlay chrome://browser/content/browser.xul chrome://stackalert/content/stackalert.xul 6 | 7 | locale stackalert en-US chrome/locale/en-US/ -------------------------------------------------------------------------------- /firefox/chrome/content/auth.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 28 | 29 | -------------------------------------------------------------------------------- /firefox/chrome/content/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /firefox/chrome/content/stackalert.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Components.utils.import('chrome://stackalert/content/stackalert.jsm'); 17 | 18 | // Register the button 19 | var button = document.getAnonymousNodes(this)[0]; 20 | StackAlert.RegisterButton(document, button); 21 | 22 | // We want to open the popup when the button is clicked 23 | this.addEventListener('click', function() { 24 | 25 | document.getAnonymousNodes(this)[1].openPopup(button, 'after_end', 0, 0, false, false); 26 | document.getAnonymousNodes(this)[1].childNodes[0].contentWindow.location.reload(); 27 | 28 | }, true); 29 | 30 | // Start the background timer 31 | StackAlert.StartBackgroundTimer(); 32 | 33 | 34 | 35 | 36 | // Unregister the button 37 | Components.utils.import('chrome://stackalert/content/stackalert.jsm'); 38 | StackAlert.UnregisterButton(document.getAnonymousNodes(this)[0]); 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /firefox/chrome/content/stackalert.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /firefox/chrome/locale/en-US/stackalert.dtd: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /firefox/chrome/skin/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathan-osman/Stack-Alert/b5339b41186e763d5d49b6646966d7b1718a79d2/firefox/chrome/skin/button.png -------------------------------------------------------------------------------- /firefox/chrome/skin/stackalert.css: -------------------------------------------------------------------------------- 1 | /* ... */ 2 | 3 | .stackalert-button { 4 | -moz-binding: url("chrome://stackalert/content/stackalert.xml#stackalert-binding"); 5 | } 6 | 7 | .stackalert-button toolbarbutton { 8 | list-style-image: url("chrome://stackalert/skin/button.png"); 9 | } 10 | 11 | .stackalert-button panel { 12 | padding: 5px; 13 | border: 4px solid #999; 14 | -moz-border-radius: 16px; 15 | background-color: #fff; 16 | } -------------------------------------------------------------------------------- /firefox/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathan-osman/Stack-Alert/b5339b41186e763d5d49b6646966d7b1718a79d2/firefox/icon.png -------------------------------------------------------------------------------- /firefox/install.rdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | stackalert@quickmediasolutions.com 8 | 0.23 9 | 2 10 | 11 | 13 | 14 | 15 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 16 | 8.0 17 | 10.* 18 | 19 | 20 | 21 | 22 | Stack Alert 23 | Displays the number of unread inbox items from your Stack Exchange accounts. 24 | Nathan Osman 25 | https://github.com/nathan-osman/Stack-Alert 26 | 27 | -------------------------------------------------------------------------------- /opera/auth_complete.html: -------------------------------------------------------------------------------- 1 | Hello! -------------------------------------------------------------------------------- /opera/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathan-osman/Stack-Alert/b5339b41186e763d5d49b6646966d7b1718a79d2/opera/badge.png -------------------------------------------------------------------------------- /opera/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Stack Alert 4 | Displays the number of unread inbox items from your Stack Exchange accounts. 5 | Nathan Osman 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /opera/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nathan-osman/Stack-Alert/b5339b41186e763d5d49b6646966d7b1718a79d2/opera/icon.png -------------------------------------------------------------------------------- /opera/includes/auth.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @include https://stackexchange.com/oauth/login_success* 3 | // ==/UserScript== 4 | 5 | // Close this window. 6 | window.addEventListener('load', function() { 7 | 8 | // Send the access token to the background script 9 | // which will store it for later usage. 10 | opera.extension.postMessage(window.location.hash); 11 | 12 | // Close the window 13 | window.close(); 14 | 15 | }, true); -------------------------------------------------------------------------------- /opera/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /opera/opera.js: -------------------------------------------------------------------------------- 1 | // This file sets the browser and API key for Opera 2 | 3 | StackAlert.Browser = 'opera'; 4 | StackAlert.IconSize = '18'; 5 | StackAlert.APIKey = '0WFcdwLcxOFOTzTXArczrg(('; 6 | StackAlert.ClientID = '56'; -------------------------------------------------------------------------------- /opera/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 |

About

2 |

3 | Stack Alert is a set of browser extensions used to display unread items in your Stack Exchange inbox. 4 | Currently Stack Alert is available only for Google Chrome and Mozilla Firefox. 5 | Other browsers are being considered where reasonable. The extensions use v2.0 of 6 | the Stack Exchange API. 7 |

8 | 9 |

Download

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
Google Chrome[download crx].
Mozilla Firefox[download xpi].
20 | 21 |

Screenshots

22 |

Here are a couple of screenshots showing Stack Alert in action:

23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 42 | 43 |
Google ChromeMozilla Firefox
31 | 32 | Chrome extension in action 34 | 35 | 37 | 38 | Firefox add-on in action 40 | 41 |
44 | --------------------------------------------------------------------------------