Skip to content
Browse files

make it pretty. design by @chrissiebrodigan

  • Loading branch information...
1 parent 73ab504 commit 0d8cb6ac8e50f880fb250e67a42edb6c81eceaea @jbalogh committed Mar 29, 2012
Showing with 312 additions and 51 deletions.
  1. +117 −33 index.html
  2. +163 −6 static/app.css
  3. +32 −12 static/app.js
  4. BIN static/hero.png
  5. BIN static/spinner.gif
View
150 index.html
@@ -9,49 +9,133 @@
<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="http://z.jbalogh.me/push.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">
+ <script type="text/template" id="step-3-template">
+ <h2>Get Notifications for your Repos</h2>
+ <p>
+ Press <code>Add</code> to add a webhook pointing to
+ <code>{{ site }}/hook</code>.
+ </p>
+ <p>
+ Press <code>Test</code> to send a test webhook and get a
+ notification.
+ </p>
+ <div id="repos"></div>
+ </script>
+ </li>
+ </ol>
+ <script>
+ var view = {site: document.location.host};
+ $('#step-1').html(Mustache.render($('#step-1-template').text(), view));
+ $('#step-3').html(Mustache.render($('#step-3-template').text(), view));
+ </script>
+
+ <footer>
+ <ul>
+ <li><a class="secret" href="http://jbalogh.me">Jeff Balogh</a></li>
+ <li>jeff@jbalogh.me</li>
+ </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="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}}
+ <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 class="add">Add</button>
+ {{/hasHook}}
+ {{#hasHook}}
+ <button class="disconnect">Disconnect</button>
+ <button 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>
</body>
</html>
View
169 static/app.css
@@ -1,8 +1,165 @@
-div {
- margin: 1em;
- padding: 1em;
- border: 1px solid;
+/** General styles. **/
+* {
+ margin: 0 auto;
+ padding: 0;
}
-.showing {
- background: #ddd;
+body {
+ font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
+ text-align: center;
+ font-size: 16px;
+ color: #333;
+ width: 800px;
+}
+h1 {
+ font-size: 40px;
+ margin: 12px 0 20px;
+}
+h2 {
+ margin-bottom: .75em;
+}
+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: 450px;
+ font-size: 13px;
+ 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/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.

0 comments on commit 0d8cb6a

Please sign in to comment.
Something went wrong with that request. Please try again.