├── .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 |
20 |
21 | Screenshots
22 | Here are a couple of screenshots showing Stack Alert in action:
23 |
24 |
25 |
26 | Google Chrome
27 | Mozilla Firefox
28 |
29 |
30 |
31 |
32 |
34 |
35 |
36 |
37 |
38 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------