Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

make it pretty. design by @chrissiebrodigan

  • Loading branch information...
commit efd184a71f6b58a26083821e37cf1c15419b5f49 1 parent 7a5cc9d
@jbalogh authored Jeff Balogh committed
View
1  app.py
@@ -100,7 +100,6 @@ def oauth():
@app.route('/hook', methods=['POST'])
def hook():
payload = json.loads(request.form['payload'])
- import pprint; pprint.pprint(payload)
repo = payload['repository']
if not payload['commits']:
return ''
View
152 index.html
@@ -9,49 +9,135 @@
<script src="static/app.js"></script>
</head>
<body>
- <h1>Push Notifications for Github</h1>
+ <h1>Push Notifications for Github <small>in Firefox</small></h1>
<div id="intro">
+ <div id="hero"></div>
<p>
- This app lets you get push notfications in your browser when someone
- pushes to your github repo. It uses web hooks.
+ This app uses Github's web hook system to send push notifications when
+ someone commits to your repository. It's a demo of
+ <a href="http://jbalogh.me/2012/01/30/push-notifications/">push notifications in Firefox</a>.
</p>
</div>
- <div id="step-1">
- First you need to get the add-on to enable push notifications in
- Firefox. Soon this will be built into the browser.
- <a href="http://z.jbalogh.me/push.xpi">Get it here.</a>
- </div>
- <div id="step-2">
- Now you need to authorize push notifications for this site.
+ <div id="omg">
+ <svg id="omgsvg">
+ <polygon class="selected" points="0,0 110,0 125,15 110,30 0,30" />
+ <polygon points="115,0 235,0 250,15 235,30 115,30, 130,15" />
+ <polygon points="240,0 360,0 375,15 360,30 240,30 255,15" />
+ </svg>
+ <ol id="nav">
+ <li class="selected">Install</li>
+ <li>Authorize</li>
+ <li>Connect</li>
+ </ol>
</div>
- <div id="step-3">
- Now we'll connect to github with oauth.
- <a href="https://github.com/login/oauth/authorize?client_id=8ff773cbf1a78dad7d7c&scope=repo">OAuth me!</a>
- </div>
+ <ol id="steps">
+ <li id="step-1" class="selected">
+ <script type="text/template" id="step-1-template">
+ <h2>Install the add-on</h2>
+ <a class="button" href="https://people.mozilla.com/~jbalogh/push-notifications.xpi">Install</a>
+ <p>
+ Firefox will ask if you'll allow <code>{{ site }}</code> to
+ install software on your computer. Press <b>Allow</b>.
+ </p>
+ <p>
+ Then press <b>Install Now</b> in the add-on install dialog box.
+ </p>
+ </script>
+ </li>
+ <li id="step-2">
+ <ol>
+ <li class="selected">
+ <h2>Turn on Push Notifications</h2>
+ <img height="30" width="30" src="static/spinner.gif">
+ <p>The install dialog is hanging off the URL bar.</p>
+ </li>
+ <li>
+ <h2>Authorize this app with Github</h2>
+ <a class="button" href="https://github.com/login/oauth/authorize?client_id=8ff773cbf1a78dad7d7c&scope=repo">Authorize</a>
+ <p>
+ No personal data is stored on the server (except your username).
+ Everything else is kept in the browser's <code>localStorage</code>.
+ </p>
+ <p>
+ The app asks for write access so it can change your web hooks.
+ </p>
+ </li>
+ </ol>
- <div id="step-4">
- Now let's hook up your repos to get notifications.
- <ul id="repos"></ul>
- </div>
+ </li>
+ <li id="step-3">
+ <h2>Get Notifications for your Repos</h2>
+ <div id="repos"></div>
+ <p>
+ Thanks for testing notifications.<br> If you have 30 seconds, please
+ fill out <a href="http://bit.ly/HkSH9i">this survey</a>.
+ </p>
+ </li>
+ </ol>
+ <script>
+ var view = {site: document.location.host};
+ $('#step-1').html(Mustache.render($('#step-1-template').text(), view));
+ </script>
- <script type="template" id="repos-template">
- {{#repos}}
- <li data-id="{{ id }}">
- <b><a href="{{ html_url }}">{{ name }}</a></b>
- <time>{{ pushed_at }}</time>
- <span>{{ watchers }}</span>
- {{^hasHook}}
- <button class="add">Add Hook</button>
- {{/hasHook}}
- {{#hasHook}}
- <button disabled class="add">Add Hook</button>
- <button class="test">Test Hook</button>
- {{/hasHook}}
- </li>
- {{/repos}}
+ <footer>
+ <ul>
+ <li><a class="secret" href="http://jbalogh.me">Jeff Balogh</a></li>
+ <li>jeff@jbalogh.me</li>
@cvan
cvan added a note

you don't want RTL your email address? it's so clever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ </ul>
+ <ul>
+ <li><a href="https://github.com/jbalogh">github.com/jbalogh</a></li>
+ <li><a href="https://twitter.com/jeffbalogh">@jeffbalogh</a></li>
+ </ul>
+ </footer>
+
+ <a href="http://github.com/jbalogh/github-notifications"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://a248.e.akamai.net/assets.github.com/img/7afbc8b248c68eb468279e8c17986ad46549fb71/687474703a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6461726b626c75655f3132313632312e706e67" alt="Fork me on GitHub"></a>
+
+ <script type="text/template" id="repos-template">
+ {{#hasRepos}}
+ <table>
+ <thead>
+ <tr>
+ <td>Name</td>
+ <td colspan="2">Hooks</td>
+ </tr>
+ </thead>
+ <tbody>
+ {{#repos}}
+ <tr>
+ <td><a class="secret" href="{{ html_url }}">{{ name }}</a></td>
+ <td data-id="{{ id }}">
+ {{^hasHook}}
+ <button title="Add a web hook to get notifications" class="add">Connect</button>
+ {{/hasHook}}
+ {{#hasHook}}
+ <button title="Remove the web hook and stop getting notifications" class="disconnect">Disconnect</button>
+ <button title="Test the web hook" class="test">Test</button>
+ {{/hasHook}}
+ </td>
+ </tr>
+ {{/repos}}
+ </tbody>
+ </table>
+ {{/hasRepos}}
+ {{^hasRepos}}
+ <b>Sorry, you don't have any repositories on Github to try the demo.</b>
+ {{/hasRepos}}
+ </tbody>
+ </script>
+ <script type="text/javascript">
+ var _gauges = _gauges || [];
+ (function() {
+ var t = document.createElement('script');
+ t.async = true;
+ t.id = 'gauges-tracker';
+ t.setAttribute('data-site-id', '4f754bba613f5d1452000082');
+ t.src = '//secure.gaug.es/track.js';
+ var s = document.getElementsByTagName('script')[0];
+ s.parentNode.insertBefore(t, s);
+ })();
</script>
</body>
</html>
View
BIN  static/OpenSans-Light-webfont.woff
Binary file not shown
View
188 static/app.css
@@ -1,8 +1,184 @@
-div {
- margin: 1em;
- padding: 1em;
- border: 1px solid;
+/** General styles. **/
+* {
+ margin: 0 auto;
+ padding: 0;
}
-.showing {
- background: #ddd;
+@font-face {
+ font-family: "Open Sans Light";
+ src: url(OpenSans-Light-webfont.woff);
+ font-weight: normal;
+ font-style: normal;
+}
+body {
+ font-family: "Open Sans Light", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 16px;
+ text-align: center;
+ color: rgb(72,72,72);
+ width: 800px;
+ background: url(bg-sky.png) 0 0 repeat-x,
+ #eee -moz-linear-gradient(to bottom, #eee, #fff 100px) 0 436px;
+ background: url(bg-sky.png) 0 0 repeat-x,
+ #eee -webkit-linear-gradient(top, #eee, #fff 100px) 0 436px;
+ background: url(bg-sky.png) 0 0 repeat-x,
+ #eee -ms-linear-gradient(top, #eee, #fff 100px) 0 436px;
+ background: url(bg-sky.png) 0 0 repeat-x,
+ #eee -o-linear-gradient(top, #eee, #fff 100px) 0 436px;
+ background: url(bg-sky.png) 0 0 repeat-x,
+ #eee linear-gradient(to bottom, #eee, #fff 100px) 0 436px;
+}
+h1 {
+ font-size: 40px;
+ margin: 12px 0 20px;
+ letter-spacing: -1px;
+ text-shadow: 0 1px 0 rgba(255,255,255,0.75);
+}
+h2 {
+ margin-bottom: .75em;
+ letter-spacing: -1px;
+}
+ol, ul {
+ list-style-type: none;
+}
+h1 small {
+ display: block;
+}
+p {
+ margin: 1.5em 0 2em;
+}
+a {
+ color: inherit;
+}
+a.secret {
+ text-decoration: none;
+}
+
+/** Intro block. **/
+#hero {
+ width: 600px;
+ height: 300px;
+ background: url(hero.png) no-repeat 99% top;
+ border-radius: 4px;
+ box-shadow: 0 0 4px 4px #aaa;
+}
+#intro {
+ width: 600px;
+}
+
+/** Arrow navigation. **/
+#nav {
+ margin-bottom: 1em;
+}
+#nav li {
+ position: relative;
+ display: inline-block;
+ width: 100px;
+ margin: 0;
+ padding: 14px 8px 8px;
+ color: #424242;
+ font-weight: bold;
+}
+#nav li.selected {
+ color: #fff;
+}
+
+#omg {
+ position: relative;
+}
+#omgsvg {
+ width: 400px;
+ height: 40px;
+ background: transparent;
+ position: absolute;
+ top: 8px;
+ left: 220px;
+}
+#omgsvg polygon {
+ fill: #e4e4e4;
+}
+#omgsvg polygon.selected {
+ fill: #424242;
+}
+
+/** Instructions. **/
+#steps p {
+ width: 500px;
+ font-size: 14px;
+ margin: 1em auto;
+}
+
+#steps p:first-of-type {
+ margin-top: 2em;
+}
+
+#steps li:not(.selected) {
+ display: none;
+}
+
+/** Buttons (via bootstrap.css). **/
+button,
+.button {
+ display: inline-block;
+ padding: 4px 10px 4px;
+ color: #333;
+ text-decoration: none;
+ line-height: 18px;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+ background-color: #e6e6e6;
+ background-image: -webkit-linear-gradient(top, #fff, #e6e6e6);
+ background-image: -moz-linear-gradient(to bottom, #fff, #e6e6e6);
+ background-image: -ms-linear-gradient(top, #fff, #e6e6e6);
+ background-image: -o-linear-gradient(top, #fff, #e6e6e6);
+ background-image: -webkit-linear-gradient(to bottom, #fff, #e6e6e6);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ border: 1px solid #cccccc;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ border-radius: 4px;
+}
+button:hover,
+.button:hover {
+ background: #e6e6e6;
+}
+button:focus, button:active,
+.button:focus, .button:active {
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+/** Tables (via bootstrap.css). **/
+table {
+ text-align: left;
+ border-collapse: collapse;
+ font-size: 14px;
+}
+th, td {
+ padding: 8px;
+ line-height: 16px;
+ border: 1px solid #ddd;
+ min-width: 150px;
+}
+tbody tr:nth-child(odd) {
+ background-color: #f9f9f9;
+}
+thead {
+ background-color: transparent;
+ font-weight: bold;
+}
+
+/** Vanity. **/
+footer {
+ border-top: 4px solid #e3e3e3;
+ margin-top: 3em;
+ font-size: 13px;
+}
+footer ul {
+ float: left;
+ margin: 1em 0 0 100px;
+ text-align: left;
+}
+footer ul:last-child {
+ float: right;
+ text-align: right;
+ margin: 1em 100px 0 0;
+}
+footer li {
+ margin-bottom: .5em;
}
View
44 static/app.js
@@ -53,7 +53,6 @@ function saveHook(repo, hook) {
var hooks = getHooks();
hooks[repo.id] = hook.url;
localStorage.setItem('hooks', JSON.stringify(hooks));
- console.log(JSON.parse(localStorage.hooks));
}
function getHooks() {
@@ -69,7 +68,7 @@ function addHook(repo) {
var promise = $.Deferred().done(function(hook) {
saveHook(repo, hook);
render();
- $.post('/subscribe', {repo: repo.url, access_token: token});
+ $.post('/subscribe', {repo: repo.url});
});
$.getJSON(url, function(hooks) {
@@ -90,6 +89,18 @@ function addHook(repo) {
});
}
+function removeHook(repo) {
+ var hooks = getHooks(),
+ hook = hooks[repo.id];
+ console.log('deleting', repo.id, repo.name, hook);
+ delete hooks[repo.id];
+ localStorage.setItem('hooks', JSON.stringify(hooks));
+ $.ajax({url: hook + '?access_token=' + token, type: 'DELETE'});
+ $.post('/unsubscribe', {repo: repo.url});
+ render();
+ console.log('deleted');
+}
+
function main() {
step1().pipe(step2).pipe(step3).pipe(step4);
@@ -102,6 +113,9 @@ function main() {
if (hook) {
$.post(hook + '/test?access_token=' + token);
}
+ }).on('click', 'button.disconnect', function() {
+ var id = $(this).parent().attr('data-id');
+ removeHook(repoMap[id]);
});
}
@@ -129,6 +143,7 @@ function step1() {
function step2() {
$(document).trigger('step', [2]);
+ $('#step-2 li:first-child').addClass('selected');
var promise = $.Deferred();
var notification = navigator.mozNotification,
@@ -152,14 +167,14 @@ function step2() {
alert('error checking remote permission');
}
promise.done(function() {
- document.getElementById('step-2').innerHTML += '<p>Your push URL: <tt>' + pushUrl + '</tt>.</p>';
+ console.log('Your push URL:', pushUrl);
+ $('#step-2 li').toggleClass('selected');
});
return promise;
}
function step3() {
- $(document).trigger('step', [3]);
var promise = $.Deferred(),
cookies = bakeCookies();
if (cookies.username && cookies.access_token) {
@@ -184,7 +199,7 @@ function step3() {
function step4() {
- $(document).trigger('step', [4]);
+ $(document).trigger('step', [3]);
var promise = getUserData();
promise.pipe(fetchRepos).then(render);
}
@@ -193,19 +208,24 @@ function step4() {
function render() {
var hooks = getHooks();
for (idx in repos) {
- if (repos[idx].id in hooks) {
- repos[idx].hasHook = true;
- }
+ repos[idx].hasHook = (repos[idx].id in hooks);
}
$('#repos').html(Mustache.render($('#repos-template').text(),
- {repos: repos}));
+ {hasRepos: !!repos, repos: repos}));
}
$(document).bind('step', function(e, step) {
- console.log('step', step);
- $('.showing').removeClass('showing');
- $('#step-' + step).addClass('showing');
+ var child = ':nth-child(' + step + ')';
+ // jQuery's addClass/removeClass isn't playing nice with svg.
+ $('.selected').each(function(idx, el) {
+ if (el.classList)
+ el.classList.remove('selected');
+ })
+ $('#omgsvg,#nav,#steps').children(child).each(function(idx, el) {
+ if (el.classList)
+ el.classList.add('selected');
+ });
});
$(document).ready(main);
View
BIN  static/bg-sky.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  static/hero.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  static/spinner.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Please sign in to comment.
Something went wrong with that request. Please try again.