├── VERSION ├── .gitignore ├── Guardfile ├── app ├── tests │ ├── mock_response_data │ │ ├── repos │ │ │ ├── nokinen │ │ │ │ ├── fdc │ │ │ │ │ ├── events?page=10.json │ │ │ │ │ ├── events?page=9.json │ │ │ │ │ └── events?page=8.json │ │ │ │ └── fdc.json │ │ │ └── pangratz │ │ │ │ ├── dashboard │ │ │ │ ├── events?page=10.json │ │ │ │ ├── events?page=8.json │ │ │ │ ├── events?page=9.json │ │ │ │ └── events?page=7.json │ │ │ │ ├── dashboard.json │ │ │ │ ├── ember.js.json │ │ │ │ └── ember.js │ │ │ │ └── events?page=9.json │ │ └── users │ │ │ ├── nokinen.json │ │ │ ├── pangratz.json │ │ │ └── nokinen │ │ │ └── repos.json │ ├── unit │ │ ├── core_test.js │ │ ├── router_test.js │ │ ├── store_test.js │ │ ├── view_test.js │ │ ├── model_test.js │ │ ├── controller_test.js │ │ ├── handlebars_helpers_test.js │ │ └── github_adapter_test.js │ ├── functional │ │ ├── title_test.js │ │ ├── show_user_test.js │ │ ├── show_user_from_repository_test.js │ │ └── show_repository_test.js │ └── integration │ │ └── store_adapter_test.js ├── templates │ ├── events │ │ ├── PublicEvent-template.handlebars │ │ ├── WatchEvent-template.handlebars │ │ ├── FollowEvent-template.handlebars │ │ ├── DeleteEvent-template.handlebars │ │ ├── ForkEvent-template.handlebars │ │ ├── GistEvent-template.handlebars │ │ ├── DownloadEvent-template.handlebars │ │ ├── CommitCommentEvent-template.handlebars │ │ ├── MemberEvent-template.handlebars │ │ ├── actor.handlebars │ │ ├── IssuesEvent-template.handlebars │ │ ├── PushEvent-template.handlebars │ │ ├── PullRequestEvent-template.handlebars │ │ ├── CreateEvent-template.handlebars │ │ ├── githubEvent.handlebars │ │ ├── PullRequestReviewCommentEvent-template.handlebars │ │ ├── IssueCommentEvent-template.handlebars │ │ └── GollumEvent-template.handlebars │ ├── events.handlebars │ ├── loadMore.handlebars │ ├── repositories.handlebars │ ├── repository.handlebars │ ├── application.handlebars │ └── user.handlebars ├── static │ └── img │ │ ├── ajax-loader.gif │ │ ├── glyphicons-halflings.png │ │ └── glyphicons-halflings-white.png ├── css │ ├── main.css │ ├── links.css │ └── github-events.css ├── lib │ ├── main.js │ ├── store.js │ ├── core.js │ ├── ext.js │ ├── model.js │ ├── handlebars_helpers.js │ ├── controller.js │ ├── view.js │ ├── github_adapter.js │ └── router.js ├── index.html ├── plugins │ └── loader.js └── vendor │ ├── jquery.inview.js │ └── moment.js ├── config.ru ├── Gemfile ├── LICENSE ├── tests ├── index.html ├── run-tests.js └── qunit │ └── qunit.css ├── Gemfile.lock ├── README.md ├── Assetfile └── Rakefile /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.15 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | tmp/ 3 | 4 | assets/ 5 | test_assets/ 6 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | guard :rake, :task => :test do 2 | watch(%r{^app/.+\.js$}) 3 | end 4 | -------------------------------------------------------------------------------- /app/tests/mock_response_data/repos/nokinen/fdc/events?page=10.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] 4 | -------------------------------------------------------------------------------- /app/tests/mock_response_data/repos/nokinen/fdc/events?page=9.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] 4 | -------------------------------------------------------------------------------- /app/tests/mock_response_data/repos/pangratz/dashboard/events?page=10.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] 4 | -------------------------------------------------------------------------------- /app/tests/mock_response_data/repos/pangratz/dashboard/events?page=8.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] 4 | -------------------------------------------------------------------------------- /app/tests/mock_response_data/repos/pangratz/dashboard/events?page=9.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] 4 | -------------------------------------------------------------------------------- /app/templates/events/PublicEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} open sourced some stuff!!{{/view}} -------------------------------------------------------------------------------- /app/static/img/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangratz/dashboard/develop/app/static/img/ajax-loader.gif -------------------------------------------------------------------------------- /app/templates/events/WatchEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} {{event.payload.action}} watching{{/view}} -------------------------------------------------------------------------------- /app/static/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangratz/dashboard/develop/app/static/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /app/templates/events/FollowEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} started following {{event.payload.target.login}}{{/view}} -------------------------------------------------------------------------------- /app/static/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangratz/dashboard/develop/app/static/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /app/templates/events/DeleteEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} 2 | deleted {{event.payload.ref_type}} {{event.payload.ref}} 3 | {{/view}} -------------------------------------------------------------------------------- /app/css/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | overflow-y: scroll; 3 | } 4 | 5 | body { 6 | padding-top: 60px; 7 | } 8 | 9 | .navbar .brand { 10 | color: #fff; 11 | } 12 | -------------------------------------------------------------------------------- /app/tests/unit/core_test.js: -------------------------------------------------------------------------------- 1 | require('dashboard/core'); 2 | 3 | module("Dashboard"); 4 | 5 | test("is defined", function () { 6 | ok(Dashboard, "is defined"); 7 | }); 8 | -------------------------------------------------------------------------------- /app/lib/main.js: -------------------------------------------------------------------------------- 1 | require('dashboard/controller'); 2 | require('dashboard/view'); 3 | require('dashboard/store'); 4 | require('dashboard/router'); 5 | 6 | Dashboard.initialize(); -------------------------------------------------------------------------------- /app/tests/unit/router_test.js: -------------------------------------------------------------------------------- 1 | require('dashboard/router'); 2 | 3 | module("Dashboard.Router"); 4 | 5 | test("it exists", function() { 6 | ok(Dashboard.Router, "it exists"); 7 | }); -------------------------------------------------------------------------------- /app/templates/events/ForkEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} 2 | forked 3 | repository 4 | {{/view}} -------------------------------------------------------------------------------- /app/lib/store.js: -------------------------------------------------------------------------------- 1 | require('dashboard/core'); 2 | require('dashboard/github_adapter'); 3 | 4 | Dashboard.Store = DS.Store.extend({ 5 | adapter: Dashboard.GitHubAdpater.create(), 6 | revision: 4 7 | }); -------------------------------------------------------------------------------- /app/templates/events/GistEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} {{event.payload.action}}d gist #{{event.payload.gist.id}}{{/view}} -------------------------------------------------------------------------------- /app/templates/events/DownloadEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} 2 | created download {{event.payload.download.name}} 3 | {{/view}} -------------------------------------------------------------------------------- /app/templates/events/CommitCommentEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView actorBinding="event.actor"}} 2 | commented on commit 3 | {{/view}} -------------------------------------------------------------------------------- /app/templates/events/MemberEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} 2 | added {{event.payload.member.login}} as a collaborator 3 | {{/view}} -------------------------------------------------------------------------------- /app/templates/events/actor.handlebars: -------------------------------------------------------------------------------- 1 | 2 | {{event.actor.login}} 3 | {{yield}} 4 | - ( {{event.repo.name}} ) 5 | -------------------------------------------------------------------------------- /app/templates/events/IssuesEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} 2 | {{event.payload.action}} issue 3 | #{{event.payload.issue.number}} 4 | {{/view}} -------------------------------------------------------------------------------- /app/tests/unit/store_test.js: -------------------------------------------------------------------------------- 1 | require('dashboard/store'); 2 | 3 | module("Dashboard.Store"); 4 | 5 | test("it exists", function() { 6 | ok(Dashboard.Store, "it exists"); 7 | ok(DS.Store.detect(Dashboard.Store), "it is a DS.Store"); 8 | }); -------------------------------------------------------------------------------- /app/templates/events/PushEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} 2 | pushed to 3 | {{event.payload.ref}} 4 | {{/view}} -------------------------------------------------------------------------------- /app/templates/events/PullRequestEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} 2 | {{event.payload.action}} Pull Request 3 | #{{event.payload.number}} 4 | {{/view}} -------------------------------------------------------------------------------- /app/lib/core.js: -------------------------------------------------------------------------------- 1 | require('jquery'); 2 | require('handlebars'); 3 | require('ember'); 4 | require('ember-data'); 5 | require('moment'); 6 | require('dashboard/ext'); 7 | 8 | Dashboard = Ember.Application.create({ 9 | VERSION: '0.0.0' 10 | }); 11 | -------------------------------------------------------------------------------- /app/templates/events.handlebars: -------------------------------------------------------------------------------- 1 |

Events

2 | 8 | -------------------------------------------------------------------------------- /app/templates/events/CreateEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} 2 | created {{event.payload.ref_type}} 3 | {{#if event.payload.ref}} 4 | {{event.payload.ref}} 5 | {{/if}} 6 | {{/view}} -------------------------------------------------------------------------------- /app/css/links.css: -------------------------------------------------------------------------------- 1 | a { 2 | cursor: pointer; 3 | } 4 | 5 | a[href^="http"] { 6 | color: green; 7 | } 8 | 9 | a[href^="https://github.com"] { 10 | display: inline-block; 11 | line-height: 18px; 12 | background:url('https://github.com/favicon.ico') center right no-repeat; 13 | padding-right: 18px; 14 | } 15 | -------------------------------------------------------------------------------- /app/templates/events/githubEvent.handlebars: -------------------------------------------------------------------------------- 1 | {{view.timeAgoString}} 2 | 3 |
4 |
5 | 6 |
7 | 8 | {{view view.DetailView eventBinding="event" }} 9 | 10 |
11 |
-------------------------------------------------------------------------------- /app/templates/loadMore.handlebars: -------------------------------------------------------------------------------- 1 | {{#if isLoading}} 2 | fetching some more stuff 3 | {{else}} 4 | {{#if canLoadMore}} 5 | click to load more 6 | {{else}} 7 | no more items 8 | {{/if}} 9 | {{/if}} 10 | -------------------------------------------------------------------------------- /app/templates/events/PullRequestReviewCommentEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} 2 | reviewed on commit 3 | 4 | {{trim event.payload.comment.commit_id length=7}} 5 | 6 | {{/view}} 7 |
8 | {{event.payload.comment.body}} 9 |
-------------------------------------------------------------------------------- /app/templates/events/IssueCommentEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} 2 | commented on issue 3 | 4 | #{{event.payload.issue.number}} 5 | 6 | {{/view}} 7 |
8 | {{event.payload.issue.title}} 9 |
-------------------------------------------------------------------------------- /app/templates/events/GollumEvent-template.handlebars: -------------------------------------------------------------------------------- 1 | {{#view view.ActorView}} 2 | modified the wiki 3 | {{/view}} 4 |
5 | {{#each event.payload.pages }} 6 | {{echo "action"}} {{page_name}} 7 | {{/each}} 8 |
9 | -------------------------------------------------------------------------------- /app/tests/functional/title_test.js: -------------------------------------------------------------------------------- 1 | casper.test.comment('Intial routing'); 2 | 3 | casper.start('http://localhost:9292', function() { 4 | this.test.assertTitle('GitHub Dashboard', 'title is the one expected'); 5 | this.test.assertEquals(this.getCurrentUrl(), 'http://localhost:9292/#/pangratz', 'routes to default'); 6 | }); 7 | 8 | casper.run(function() { 9 | this.test.done(); 10 | }); -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'rake-pipeline' 2 | require 'rake-pipeline/middleware' 3 | use Rake::Pipeline::Middleware, 'Assetfile' 4 | 5 | # require 'rack/streaming_proxy' 6 | # use Rack::StreamingProxy do |request| 7 | # if request.path.start_with?('/proxy') 8 | # "http://127.0.0.1:8080#{request.path}" 9 | # end 10 | # end 11 | 12 | require 'rack-rewrite' 13 | use Rack::Rewrite do 14 | rewrite %r{^(.*)\/$}, '$1/index.html' 15 | end 16 | 17 | run Rack::Directory.new('.') 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'colored' 4 | gem 'versionomy' 5 | 6 | gem 'casperjs' 7 | 8 | gem 'guard' 9 | gem 'guard-rake' 10 | 11 | gem 'rack' 12 | gem 'rack-rewrite' 13 | # gem 'rack-streaming-proxy' 14 | 15 | gem 'sass' 16 | gem 'compass' 17 | 18 | gem 'uglifier' 19 | gem 'yui-compressor' 20 | 21 | gem 'rake-pipeline', :git => 'https://github.com/livingsocial/rake-pipeline.git' 22 | gem 'rake-pipeline-web-filters', :git => 'https://github.com/wycats/rake-pipeline-web-filters.git' 23 | -------------------------------------------------------------------------------- /app/templates/repositories.handlebars: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/css/github-events.css: -------------------------------------------------------------------------------- 1 | .profilePic { 2 | float: left; 3 | margin: 3px; 4 | margin-right: 10px; 5 | } 6 | 7 | .profilePic > img { 8 | border-radius: 3px; 9 | } 10 | 11 | .info { 12 | float: none; 13 | } 14 | 15 | div.additional-info { 16 | margin-left: 5px; 17 | margin-top: 5px; 18 | } 19 | 20 | div.event { 21 | background-color: #F7F7F9; 22 | -webkit-border-radius: 5px; 23 | -moz-border-radius: 5px; 24 | border-radius: 5px; 25 | margin-bottom: 1em; 26 | padding: 0.5em; 27 | border: 1px solid #E1E1E8; 28 | } -------------------------------------------------------------------------------- /app/lib/ext.js: -------------------------------------------------------------------------------- 1 | var get = Ember.get, fmt = Ember.String.fmt; 2 | 3 | Ember.View.reopen({ 4 | templateForName: function(name, type) { 5 | if (!name) { return; } 6 | 7 | var templates = get(this, 'templates'), 8 | template = get(templates, name); 9 | 10 | if (!template) { 11 | try { 12 | template = require('dashboard/~templates/' + name); 13 | } catch (e) { 14 | throw new Ember.Error(fmt('%@ - Unable to find %@ "%@".', [this, type, name])); 15 | } 16 | } 17 | 18 | return template; 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /app/tests/functional/show_user_test.js: -------------------------------------------------------------------------------- 1 | var x = require('casper').selectXPath; 2 | 3 | casper.test.comment('Clicking on a user routes to this user'); 4 | 5 | casper.start('http://localhost:9292/#/nokinen'); 6 | 7 | var nokinen = x('//a[text()="nokinen"]'); 8 | casper.waitUntilVisible(nokinen, function() { 9 | casper.click(nokinen); 10 | }); 11 | 12 | casper.then(function() { 13 | this.test.assertEquals(this.getCurrentUrl(), 'http://localhost:9292/#/nokinen', 'routes to clicked repository'); 14 | }); 15 | 16 | casper.run(function() { 17 | this.test.done(); 18 | }); -------------------------------------------------------------------------------- /app/tests/unit/view_test.js: -------------------------------------------------------------------------------- 1 | require('dashboard/view'); 2 | 3 | module("Dashboard.ApplicationView"); 4 | 5 | test("it exists", function() { 6 | ok(Dashboard.ApplicationView, "it exists"); 7 | }); 8 | 9 | module("Dashboard.RepositoryView"); 10 | 11 | test("it exists", function() { 12 | ok(Dashboard.RepositoryView, "it exists"); 13 | }); 14 | 15 | module("Dashboard.RepositoriesView"); 16 | 17 | test("it exists", function() { 18 | ok(Dashboard.RepositoriesView, "it exists"); 19 | }); 20 | 21 | module("Dashboard.EventsView"); 22 | 23 | test("it exists", function() { 24 | ok(Dashboard.EventsView, "it exists"); 25 | }); -------------------------------------------------------------------------------- /app/templates/repository.handlebars: -------------------------------------------------------------------------------- 1 |
2 | {{owner.login}} / {{name}} – show on GitHub 3 |
4 | {{description}} 5 |
6 | {{watchers}} 7 | {{forks}} 8 | {{language}} 9 |
10 |
11 | {{outlet "events"}} 12 |
-------------------------------------------------------------------------------- /app/tests/functional/show_user_from_repository_test.js: -------------------------------------------------------------------------------- 1 | var x = require('casper').selectXPath; 2 | 3 | casper.test.comment('Clicking on a user when watching a specific repository changes route to user'); 4 | 5 | casper.start('http://localhost:9292/#/nokinen/fdc'); 6 | 7 | var nokinen = x('//a[text()="nokinen"]'); 8 | casper.waitUntilVisible(nokinen, function() { 9 | casper.click(nokinen); 10 | }); 11 | 12 | casper.then(function() { 13 | this.test.assertEquals(this.getCurrentUrl(), 'http://localhost:9292/#/nokinen', 'routes to clicked repository'); 14 | }); 15 | 16 | casper.run(function() { 17 | this.test.done(); 18 | }); -------------------------------------------------------------------------------- /app/tests/unit/model_test.js: -------------------------------------------------------------------------------- 1 | require('dashboard/model'); 2 | 3 | module("Dashboard.Repository"); 4 | 5 | test("it exists", function() { 6 | ok(Dashboard.Repository, "it exists"); 7 | ok(DS.Model.detect(Dashboard.Repository), "it is a DS.Model"); 8 | }); 9 | 10 | module("Dashboard.Event"); 11 | 12 | test("it exists", function() { 13 | ok(Dashboard.Event, "it exists"); 14 | ok(DS.Model.detect(Dashboard.Event), "it is a DS.Model"); 15 | }); 16 | 17 | module("Dashboard.User"); 18 | 19 | test("it exists", function() { 20 | ok(Dashboard.User, "it exists"); 21 | ok(DS.Model.detect(Dashboard.User), "it is a DS.Model"); 22 | }); -------------------------------------------------------------------------------- /app/templates/application.handlebars: -------------------------------------------------------------------------------- 1 |
2 | 16 |
17 | {{outlet}} 18 |
19 |
-------------------------------------------------------------------------------- /app/tests/functional/show_repository_test.js: -------------------------------------------------------------------------------- 1 | var x = require('casper').selectXPath; 2 | 3 | casper.test.comment('Clicking on a repository shows repository page'); 4 | 5 | casper.start('http://localhost:9292/#/pangratz'); 6 | 7 | // change this to check for availability of repository which shall be shown 8 | var dashboard = x('//a[text()="dashboard"]'); 9 | casper.waitUntilVisible(dashboard, function() { 10 | casper.click(dashboard); 11 | }); 12 | 13 | casper.then(function() { 14 | this.test.assertEquals(this.getCurrentUrl(), 'http://localhost:9292/#/pangratz/dashboard', 'routes to clicked repository'); 15 | }); 16 | 17 | casper.run(function() { 18 | this.test.done(); 19 | }); -------------------------------------------------------------------------------- /app/tests/mock_response_data/users/nokinen.json: -------------------------------------------------------------------------------- 1 | { 2 | "created_at": "2010-10-11T19:55:19Z", 3 | "type": "User", 4 | "login": "nokinen", 5 | "avatar_url": "https://secure.gravatar.com/avatar/3f60ae2a5febcca386fa08d6b4af43f0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 6 | "email": "tobias@noig.es", 7 | "public_repos": 9, 8 | "followers": 5, 9 | "html_url": "https://github.com/nokinen", 10 | "blog": "twitter.com/tnugg", 11 | "hireable": false, 12 | "public_gists": 0, 13 | "name": "Tobias Noiges", 14 | "company": null, 15 | "url": "https://api.github.com/users/nokinen", 16 | "gravatar_id": "3f60ae2a5febcca386fa08d6b4af43f0", 17 | "bio": null, 18 | "id": 435811, 19 | "following": 11, 20 | "location": "Central Alps" 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Clemens Müller <@pangratz> 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /app/tests/mock_response_data/users/pangratz.json: -------------------------------------------------------------------------------- 1 | { 2 | "created_at": "2010-07-23T12:46:08Z", 3 | "type": "User", 4 | "login": "pangratz", 5 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 6 | "email": "cmueller.418@gmail.com", 7 | "public_repos": 78, 8 | "followers": 17, 9 | "html_url": "https://github.com/pangratz", 10 | "blog": "http://code418.com", 11 | "hireable": true, 12 | "public_gists": 7, 13 | "name": "Clemens Müller", 14 | "company": null, 15 | "url": "https://api.github.com/users/pangratz", 16 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 17 | "bio": "", 18 | "id": 341877, 19 | "following": 28, 20 | "location": "localhost, linz, austria" 21 | } 22 | -------------------------------------------------------------------------------- /app/templates/user.handlebars: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 | {{login}} - {{name}} 8 | {{#if blog}} 9 | - {{blog}} 10 | {{/if}} 11 |
12 |
13 | {{public_repos}} public repos 14 |
15 |
16 |
17 |
18 |
19 |
20 |

Repositories

21 | {{outlet "repositories"}} 22 |
23 |
24 | {{outlet "events"}} 25 |
26 |
-------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GitHub Dashboard 6 | 7 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dashboard Test Suite 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/lib/model.js: -------------------------------------------------------------------------------- 1 | require('dashboard/core'); 2 | 3 | Dashboard.Repository = DS.Model.extend({ 4 | primaryKey: 'full_name', 5 | name: DS.attr('string'), 6 | full_name: DS.attr('string'), 7 | description: DS.attr('string'), 8 | html_url: DS.attr('string'), 9 | homepage: DS.attr('string'), 10 | watchers: DS.attr('number'), 11 | forks: DS.attr('number'), 12 | language: DS.attr('string'), 13 | updated_at: DS.attr('date'), 14 | owner: function() { 15 | return this.get('data.owner') 16 | }.property('data') 17 | }); 18 | 19 | Dashboard.Event = DS.Model.extend({ 20 | type: DS.attr('string'), 21 | created_at: DS.attr('string'), 22 | actor: function() { return this.get('data.actor'); }.property('data.actor'), 23 | repo: function() { return this.get('data.repo'); }.property('data.repo'), 24 | org: function() { return this.get('data.org'); }.property('data.org'), 25 | payload: function() { return this.get('data.payload'); }.property('data.payload') 26 | }); 27 | 28 | Dashboard.User = DS.Model.extend({ 29 | primaryKey: 'login', 30 | login: DS.attr('string'), 31 | avatar_url: DS.attr('string'), 32 | name: DS.attr('string'), 33 | blog: DS.attr('string'), 34 | email: DS.attr('string'), 35 | bio: DS.attr('string'), 36 | public_repos: DS.attr('number'), 37 | followers: DS.attr('number'), 38 | following: DS.attr('number'), 39 | html_url: DS.attr('string'), 40 | created_at: DS.attr('string') 41 | }); -------------------------------------------------------------------------------- /app/tests/unit/controller_test.js: -------------------------------------------------------------------------------- 1 | require('dashboard/controller'); 2 | 3 | var controller; 4 | 5 | module("Dashboard.ApplicationController"); 6 | 7 | test("it exists", function() { 8 | ok(Dashboard.ApplicationController, "it exists"); 9 | }); 10 | 11 | module("Dashboard.UserController"); 12 | 13 | test("it exists", function() { 14 | ok(Dashboard.UserController, "it exists"); 15 | }); 16 | 17 | module("Dashboard.EventsController"); 18 | 19 | test("it exists", function() { 20 | ok(Dashboard.EventsController, "it exists"); 21 | }); 22 | 23 | module("Dashboard.RepositoriesController", { 24 | setup: function() { 25 | controller = Dashboard.RepositoriesController.create(); 26 | }, 27 | teardowm: function() { 28 | controller.destroy(); 29 | } 30 | }); 31 | 32 | test("it exists", function() { 33 | ok(Dashboard.RepositoriesController, "it exists"); 34 | }); 35 | 36 | test("loadWatchedRepositories invokes watchedRepositories on dataSource", function() { 37 | var watchedRepositoriesCalled, usernameParameter; 38 | var dataSource = { 39 | watchedRepositories: function(username) { 40 | watchedRepositoriesCalled = true; 41 | usernameParameter = username; 42 | } 43 | }; 44 | 45 | Ember.run(function() { controller.set('dataSource', dataSource); }); 46 | controller.loadWatchedRepositories('buster'); 47 | 48 | ok(watchedRepositoriesCalled, "loadWatchedRepositories on dataSource has been called"); 49 | equal(usernameParameter, 'buster', "supplied username has been passed to dataSource"); 50 | }); -------------------------------------------------------------------------------- /app/tests/mock_response_data/repos/pangratz/dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "forks": 3, 3 | "language": "JavaScript", 4 | "git_url": "git://github.com/pangratz/dashboard.git", 5 | "created_at": "2012-03-05T09:21:46Z", 6 | "master_branch": "develop", 7 | "description": "GitHub Dashboard using Ember.js and Twitter Bootstrap", 8 | "ssh_url": "git@github.com:pangratz/dashboard.git", 9 | "owner": { 10 | "login": "pangratz", 11 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 12 | "url": "https://api.github.com/users/pangratz", 13 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 14 | "id": 341877 15 | }, 16 | "mirror_url": null, 17 | "has_downloads": true, 18 | "watchers_count": 11, 19 | "updated_at": "2012-08-09T12:57:34Z", 20 | "open_issues_count": 13, 21 | "forks_count": 3, 22 | "svn_url": "https://github.com/pangratz/dashboard", 23 | "network_count": 3, 24 | "has_wiki": false, 25 | "html_url": "https://github.com/pangratz/dashboard", 26 | "watchers": 11, 27 | "size": 660, 28 | "fork": false, 29 | "full_name": "pangratz/dashboard", 30 | "clone_url": "https://github.com/pangratz/dashboard.git", 31 | "name": "dashboard", 32 | "url": "https://api.github.com/repos/pangratz/dashboard", 33 | "open_issues": 13, 34 | "has_issues": true, 35 | "homepage": "http://pangratz.github.com/dashboard", 36 | "private": false, 37 | "id": 3625527, 38 | "pushed_at": "2012-07-27T10:49:23Z" 39 | } 40 | -------------------------------------------------------------------------------- /app/lib/handlebars_helpers.js: -------------------------------------------------------------------------------- 1 | require('dashboard/core'); 2 | 3 | var getPath = Ember.Handlebars.getPath, normalizePath = Ember.Handlebars.normalizePath; 4 | 5 | var getValue = function(property, options, context) { 6 | var context = (options.contexts && options.contexts[0]) || context, 7 | normalized = normalizePath(context, property, options.data), 8 | pathRoot = normalized.root, 9 | path = normalized.path, 10 | value = (path === 'this') ? pathRoot : getPath(pathRoot, path, options); 11 | 12 | return value; 13 | }; 14 | 15 | // this helper is needed to print out properties which are called "action" 16 | Ember.Handlebars.registerHelper('echo', function(property, options) { 17 | return getValue(property, options, this); 18 | }); 19 | 20 | // trim a given property to specific length 21 | Ember.Handlebars.registerHelper('trim', function(property, options) { 22 | var length = options.hash.length; 23 | var string = getValue(property, options, this); 24 | if (string && length) { 25 | string = string.substring(0, length); 26 | } 27 | return new Handlebars.SafeString(string || ''); 28 | }); 29 | 30 | // output timestamp property as something like `1h ago` 31 | Ember.Handlebars.registerHelper('ago', function(property, options) { 32 | var timestamp = getValue(property, options, this); 33 | if (options.hash.isSeconds) { 34 | // the given property represents seconds since UNIX epoch, so we multiply 35 | // by 1000 to get the date in milliseconds since UNIX epoch 36 | timestamp *= 1000; 37 | } 38 | 39 | return moment(new Date(timestamp)).fromNow(); 40 | }); -------------------------------------------------------------------------------- /app/tests/mock_response_data/repos/nokinen/fdc.json: -------------------------------------------------------------------------------- 1 | { 2 | "forks": 2, 3 | "language": "Ruby", 4 | "git_url": "git://github.com/nokinen/fdc.git", 5 | "created_at": "2012-05-14T22:32:28Z", 6 | "master_branch": "master", 7 | "description": "A platform independent command-line utility written in Ruby for converting files in the avionics flight recorder data format (IGC) to the keyhole markup language (KML) for their display in applications such as Google Earth.", 8 | "ssh_url": "git@github.com:nokinen/fdc.git", 9 | "owner": { 10 | "login": "nokinen", 11 | "avatar_url": "https://secure.gravatar.com/avatar/3f60ae2a5febcca386fa08d6b4af43f0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 12 | "url": "https://api.github.com/users/nokinen", 13 | "gravatar_id": "3f60ae2a5febcca386fa08d6b4af43f0", 14 | "id": 435811 15 | }, 16 | "mirror_url": null, 17 | "has_downloads": true, 18 | "watchers_count": 2, 19 | "updated_at": "2012-06-21T18:23:39Z", 20 | "open_issues_count": 1, 21 | "forks_count": 2, 22 | "svn_url": "https://github.com/nokinen/fdc", 23 | "network_count": 2, 24 | "has_wiki": false, 25 | "html_url": "https://github.com/nokinen/fdc", 26 | "watchers": 2, 27 | "size": 296, 28 | "fork": false, 29 | "full_name": "nokinen/fdc", 30 | "clone_url": "https://github.com/nokinen/fdc.git", 31 | "name": "fdc", 32 | "url": "https://api.github.com/repos/nokinen/fdc", 33 | "open_issues": 1, 34 | "has_issues": true, 35 | "homepage": "https://rubygems.org/gems/fdc", 36 | "private": false, 37 | "id": 4329788, 38 | "pushed_at": "2012-06-21T18:23:39Z" 39 | } 40 | -------------------------------------------------------------------------------- /app/plugins/loader.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | function requireWrapper(self) { 3 | var require = function() { 4 | return self.require.apply(self, arguments); 5 | }; 6 | require.exists = function() { 7 | return self.exists.apply(self, arguments); 8 | }; 9 | return require; 10 | } 11 | 12 | var Loader = function() { 13 | this.modules = {}; 14 | this.loaded = {}; 15 | this.exports = {}; 16 | return this; 17 | }; 18 | 19 | Loader.prototype.require = function(name) { 20 | if (!this.loaded[name]) { 21 | var module = this.modules[name]; 22 | if (module) { 23 | var require = requireWrapper(this); 24 | try { 25 | this.exports[name] = module.call(window, require); 26 | return this.exports[name]; 27 | } finally { 28 | this.loaded[name] = true; 29 | } 30 | } else { 31 | throw "The module '" + name + "' has not been registered"; 32 | } 33 | } 34 | return this.exports[name]; 35 | }; 36 | 37 | Loader.prototype.register = function(name, module) { 38 | if (this.exists(name)) { 39 | throw "The module '" + name + "' has already been registered"; 40 | } 41 | this.modules[name] = module; 42 | return true; 43 | }; 44 | 45 | Loader.prototype.unregister = function(name) { 46 | var loaded = !!this.loaded[name]; 47 | if (loaded) { 48 | delete this.exports[name]; 49 | delete this.modules[name]; 50 | delete this.loaded[name]; 51 | } 52 | return loaded; 53 | }; 54 | 55 | Loader.prototype.exists = function(name) { 56 | return name in this.modules; 57 | }; 58 | 59 | window.loader = new Loader(); 60 | })(this); 61 | -------------------------------------------------------------------------------- /app/lib/controller.js: -------------------------------------------------------------------------------- 1 | require('dashboard/core'); 2 | 3 | Dashboard.LoadMoreMixin = Ember.Mixin.create(Ember.Evented, { 4 | canLoadMore: true, 5 | autoFetch: true, 6 | currentPage: 1, 7 | resetLoadMore: function() { 8 | this.set('currentPage', 1); 9 | }, 10 | loadMore: Ember.K 11 | }); 12 | 13 | Dashboard.ApplicationController = Ember.Controller.extend(); 14 | 15 | Dashboard.UserController = Ember.ObjectController.extend(); 16 | 17 | Dashboard.RepositoryController = Ember.ObjectController.extend(); 18 | 19 | Dashboard.EventsController = Ember.ArrayController.extend(Dashboard.LoadMoreMixin, { 20 | canLoadMore: function() { 21 | // github only allows 10 pages for events, see http://developer.github.com/v3/events/ 22 | return this.get('currentPage') < 10; 23 | }.property('currentPage'), 24 | 25 | loadMore: function() { 26 | if (this.get('canLoadMore')) { 27 | var page = this.incrementProperty('currentPage'); 28 | this.get('target').send('loadMoreEvents', page); 29 | } 30 | } 31 | }); 32 | 33 | Dashboard.RepositoriesController = Ember.ArrayController.extend(Dashboard.LoadMoreMixin, { 34 | sortProperties: 'updated_at'.w(), 35 | sortAscending: false, 36 | loadWatchedRepositories: function(username) { 37 | this.get('dataSource').watchedRepositories(username, this, 'addObjects'); 38 | }, 39 | 40 | canLoadMore: function() { 41 | var reposCount = this.get('owner.publicRepos'); 42 | var length = this.get('length'); 43 | return true || (length < reposCount); 44 | }.property('owner.publicRepos', 'length'), 45 | 46 | loadMore: function() { 47 | if (this.get('canLoadMore')) { 48 | var page = this.incrementProperty('currentPage'); 49 | this.get('target').send('loadMoreRepos', page); 50 | } 51 | } 52 | }); -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: https://github.com/livingsocial/rake-pipeline.git 3 | revision: 543f4322fe70facee9572d29ddabf7f090dad68a 4 | specs: 5 | rake-pipeline (0.6.0) 6 | rake (~> 0.9.0) 7 | thor 8 | 9 | GIT 10 | remote: https://github.com/wycats/rake-pipeline-web-filters.git 11 | revision: 81a22fb0808dfdeab8ed92d5d8c898ad198b9938 12 | specs: 13 | rake-pipeline-web-filters (0.6.0) 14 | rack 15 | rake-pipeline (~> 0.6) 16 | 17 | GEM 18 | remote: http://rubygems.org/ 19 | specs: 20 | POpen4 (0.1.4) 21 | Platform (>= 0.4.0) 22 | open4 23 | Platform (0.4.0) 24 | blockenspiel (0.4.5) 25 | casperjs (1.0.0.RC1) 26 | chunky_png (1.2.5) 27 | colored (1.2) 28 | compass (0.12.1) 29 | chunky_png (~> 1.2) 30 | fssm (>= 0.2.7) 31 | sass (~> 3.1) 32 | execjs (1.4.0) 33 | multi_json (~> 1.0) 34 | ffi (1.0.11) 35 | fssm (0.2.9) 36 | guard (1.1.1) 37 | listen (>= 0.4.2) 38 | thor (>= 0.14.6) 39 | guard-rake (0.0.5) 40 | guard 41 | rake 42 | listen (0.4.3) 43 | rb-fchange (~> 0.0.5) 44 | rb-fsevent (~> 0.9.1) 45 | rb-inotify (~> 0.8.8) 46 | multi_json (1.3.6) 47 | open4 (1.3.0) 48 | rack (1.4.1) 49 | rack-rewrite (1.2.1) 50 | rake (0.9.2.2) 51 | rb-fchange (0.0.5) 52 | ffi 53 | rb-fsevent (0.9.1) 54 | rb-inotify (0.8.8) 55 | ffi (>= 0.5.0) 56 | sass (3.1.19) 57 | thor (0.15.2) 58 | uglifier (1.2.4) 59 | execjs (>= 0.3.0) 60 | multi_json (>= 1.0.2) 61 | versionomy (0.4.4) 62 | blockenspiel (>= 0.4.5) 63 | yui-compressor (0.9.6) 64 | POpen4 (>= 0.1.4) 65 | 66 | PLATFORMS 67 | ruby 68 | 69 | DEPENDENCIES 70 | casperjs 71 | colored 72 | compass 73 | guard 74 | guard-rake 75 | rack 76 | rack-rewrite 77 | rake-pipeline! 78 | rake-pipeline-web-filters! 79 | sass 80 | uglifier 81 | versionomy 82 | yui-compressor 83 | -------------------------------------------------------------------------------- /app/lib/view.js: -------------------------------------------------------------------------------- 1 | require('dashboard/core'); 2 | require('dashboard/handlebars_helpers'); 3 | require('jquery.inview'); 4 | 5 | Dashboard.ApplicationView = Ember.View.extend({ 6 | templateName: 'application' 7 | }); 8 | 9 | Dashboard.LoadMoreView = Ember.View.extend({ 10 | templateName: 'loadMore', 11 | didInsertElement: function() { 12 | if (this.get('controller.autoFetch')) { 13 | var view = this; 14 | this.$().bind('inview', function(event, isInView, visiblePartX, visiblePartY) { 15 | if (isInView) Ember.tryInvoke(view.get('controller'), 'loadMore'); 16 | }); 17 | } 18 | } 19 | }); 20 | 21 | Dashboard.EventsView = Ember.View.extend({ 22 | templateName: 'events', 23 | EventView: Ember.View.extend({ 24 | templateName: 'events/githubEvent', 25 | 26 | timeAgoString: function() { 27 | var timeAgo = this.get('event.created_at'); 28 | return moment(timeAgo).fromNow(); 29 | }.property('timeAgo'), 30 | 31 | avatarUrl: function() { 32 | var gravatarId = this.get('event.actor.gravatar_id'); 33 | return 'http://www.gravatar.com/avatar/%@'.fmt(gravatarId); 34 | }.property('event.actor.gravatar_id'), 35 | 36 | DetailView: Ember.View.extend({ 37 | className: 'info'.w(), 38 | 39 | templateName: function() { 40 | var type = this.get('event.type'); 41 | return 'events/%@-template'.fmt(type); 42 | }.property('event.type'), 43 | 44 | ActorView: Ember.View.extend({ 45 | layoutName: 'events/actor', 46 | defaultTemplate: Ember.Handlebars.compile(''), 47 | tagName: '', 48 | eventBinding: 'parentView.event', 49 | 50 | href: function() { 51 | var login = this.get('event.actor.login'); 52 | return 'https://github.com/%@'.fmt(login); 53 | }.property('event.actor.login') 54 | }) 55 | }) 56 | }) 57 | }); 58 | 59 | Dashboard.UserView = Ember.View.extend({ 60 | templateName: 'user' 61 | }); 62 | 63 | Dashboard.RepositoryView = Ember.View.extend({ 64 | templateName: 'repository' 65 | }); 66 | 67 | Dashboard.RepositoriesView = Ember.View.extend({ 68 | templateName: 'repositories' 69 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [GitHub Dashboard](http://pangratz.github.com/dashboard) 2 | ======================================================== 3 | 4 | GitHub Dashboard using Ember.js and Twitter Bootstrap. This repo is developed in the course of the accompanying blog posts at [code418.com](http://code418.com/blog/categories/github-dashboard/). 5 | 6 | Running 7 | ------- 8 | 9 | $ bundle install 10 | $ bundle exec rackup 11 | 12 | App Structure 13 | ------------- 14 | 15 | dashboard 16 | ├── Assetfile - App build file 17 | ├── Gemfile - Package dependencies for rakep/rack 18 | ├── Gemfile.lock - Here be dragons: don't touch, always include 19 | ├── app - App specific code 20 | │ ├── index.html - The app entry point 21 | │ ├── css - App CSS or SCSS (.scss) 22 | │ ├── lib - App code, *modularized during build* 23 | │ ├── modules - Module code, *already modularized* 24 | │ ├── plugins - Plugins (e.g. jquery.jsonrpc.js) 25 | │ │ └── loader.js - JS module loader 26 | │ ├── static - Static files, never touched, copied over during build 27 | │ ├── templates - Handlebars templates, *modularized during build* 28 | │ ├── tests - QUnit application tests 29 | │ └── vendor - Vendor code, *modularized during build* 30 | ├── assets - Built out asset files, minified in production 31 | │ ├── app.css - Built out app CSS/SCSS 32 | │ ├── app.js - Built out app JS 33 | │ └── loader.js - Built out JS module loader 34 | ├── config.ru - Rack development web server configuration 35 | ├── tests - QUnit testing files 36 | │ ├── index.html - The testing entry point 37 | │ ├── qunit - Testing support files 38 | │ └── run-tests.js - The PhantomJS QUnit test runner 39 | ├── test_assets - Built out test asset files 40 | │ ├── app-tests.js - App test files 41 | └── tmp - Temporary build files used by rakep 42 | 43 | Testing 44 | ------- 45 | 46 | You can test the app by invoking: 47 | 48 | $ bundle exec rake test 49 | 50 | This executes the tests by using [PhantomJS](http://www.phantomjs.org/), 51 | which you need to have installed. 52 | 53 | Or you can run the tests via: 54 | 55 | $ bundle exec rackup 56 | $ open http://localhost:9292/tests/index.html 57 | 58 | If you want to continuously run the tests every time a file changes, invoke: 59 | 60 | $ bundle exec guard 61 | -------------------------------------------------------------------------------- /tests/run-tests.js: -------------------------------------------------------------------------------- 1 | // PhantomJS QUnit Test Runner 2 | 3 | var args = phantom.args; 4 | if (args.length < 1 || args.length > 2) { 5 | console.log("Usage: " + phantom.scriptName + " "); 6 | phantom.exit(1); 7 | } 8 | 9 | var page = require('webpage').create(); 10 | 11 | var depRe = /^DEPRECATION:/; 12 | page.onConsoleMessage = function(msg) { 13 | if (!depRe.test(msg)) console.log(msg); 14 | }; 15 | 16 | var uri = args[0]; 17 | page.open(uri, function(status) { 18 | if (status !== 'success') { 19 | console.error("Unable to access: " + uri + " [" + status + "]"); 20 | phantom.exit(1); 21 | } else { 22 | page.evaluate(addLogging); 23 | 24 | var timeout = parseInt(args[1] || 30000, 10); 25 | var start = Date.now(); 26 | var interval = setInterval(function() { 27 | if (Date.now() > start + timeout) { 28 | console.error("Tests timed out"); 29 | phantom.exit(1); 30 | } else { 31 | var qunitDone = page.evaluate(function() { 32 | return window.qunitDone; 33 | }); 34 | 35 | if (qunitDone) { 36 | clearInterval(interval); 37 | if (qunitDone.failed > 0) { 38 | phantom.exit(1); 39 | } else { 40 | phantom.exit(); 41 | } 42 | } 43 | } 44 | }, 500); 45 | } 46 | }); 47 | 48 | function addLogging() { 49 | var testErrors = []; 50 | var assertionErrors = []; 51 | 52 | QUnit.moduleDone(function(context) { 53 | if (context.failed) { 54 | var msg = "Module Failed: " + context.name + "\n" + testErrors.join("\n"); 55 | console.error(msg); 56 | testErrors = []; 57 | } 58 | }); 59 | 60 | QUnit.testDone(function(context) { 61 | if (context.failed) { 62 | var msg = " Test Failed: " + context.name + assertionErrors.join(" "); 63 | testErrors.push(msg); 64 | assertionErrors = []; 65 | } 66 | }); 67 | 68 | QUnit.log(function(context) { 69 | if (context.result) return; 70 | 71 | var msg = "\n Assertion Failed:"; 72 | if (context.message) { 73 | msg += " " + context.message; 74 | } 75 | 76 | if (context.expected) { 77 | msg += "\n Expected: " + context.expected + ", Actual: " + context.actual; 78 | } 79 | 80 | assertionErrors.push(msg); 81 | }); 82 | 83 | QUnit.done(function(context) { 84 | var stats = [ 85 | "Time: " + context.runtime + "ms", 86 | "Total: " + context.total, 87 | "Passed: " + context.passed, 88 | "Failed: " + context.failed 89 | ]; 90 | console.log(stats.join(", ")); 91 | window.qunitDone = context; 92 | }); 93 | } 94 | -------------------------------------------------------------------------------- /app/lib/github_adapter.js: -------------------------------------------------------------------------------- 1 | require('dashboard/core'); 2 | require('dashboard/model'); 3 | 4 | Dashboard.GitHubAdpater = DS.Adapter.extend({ 5 | PREFIX: 'https://api.github.com', 6 | 7 | _ajax: function(url, callback) { 8 | Ember.$.ajax({ 9 | url: this.PREFIX + url, 10 | dataType: 'jsonp', 11 | context: this, 12 | success: callback 13 | }); 14 | }, 15 | 16 | ajax: function(url, callback) { 17 | this._ajax(url, 18 | function(response) { 19 | this._updateLimits(response); 20 | callback.call(this, response.data); 21 | }); 22 | }, 23 | 24 | _updateLimits: function(response) { 25 | if (response.meta) { 26 | this.set('remaining', response.meta['X-RateLimit-Remaining']); 27 | this.set('limit', response.meta['X-RateLimit-Limit']); 28 | } 29 | }, 30 | 31 | _invoke: function(target, callback, query) { 32 | return function(data) { 33 | Ember.tryInvoke(target, callback, [data]); 34 | Ember.tryInvoke(query, 'isLoadedCallback'); 35 | }; 36 | }, 37 | 38 | _storeLoad: function(store, type, id) { 39 | return function(data) { 40 | store.load(type, id, data); 41 | }; 42 | }, 43 | 44 | find: function(store, type, id) { 45 | if (Dashboard.Repository.detect(type)) { 46 | this.ajax('/repos/%@'.fmt(id), this._storeLoad(store, type, id)); 47 | } else if (Dashboard.User.detect(type)) { 48 | this.ajax('/users/%@'.fmt(id), this._storeLoad(store, type, id)); 49 | } 50 | }, 51 | 52 | findQuery: function(store, type, query, modelArray) { 53 | // watched repositories for a user 54 | if (Dashboard.Repository.detect(type) && query.username && 'watched' === query.type) { 55 | this.ajax('/users/%@/watched'.fmt(query.username), this._invoke(modelArray, 'load')); 56 | 57 | // repositories for a user 58 | } else if (Dashboard.Repository.detect(type) && query.username) { 59 | this.ajax('/users/%@/repos'.fmt(query.username), this._invoke(modelArray, 'load')); 60 | 61 | // events for a repository 62 | } else if (Dashboard.Event.detect(type) && query.repoName) { 63 | this.ajax('/repos/%@/events?page=%@'.fmt(query.repoName, query.page || 1), this._invoke(modelArray, 'load', query)); 64 | 65 | // events for a user 66 | } else if (Dashboard.Event.detect(type) && query.username) { 67 | this.ajax('/users/%@/events?page=%@'.fmt(query.username, query.page || 1), this._invoke(modelArray, 'load', query)); 68 | } 69 | } 70 | }); 71 | 72 | Dashboard.GitHubFixtureAdpater = Dashboard.GitHubAdpater.extend({ 73 | PREFIX: 'http://localhost:9292/app/tests/mock_response_data', 74 | _ajax: function(url, callback) { 75 | var encodedUrl = url.replace(/\?/, '%3F').replace(/\=/, '%3D'); 76 | Ember.run.later(this, function() { 77 | Ember.$.ajax({ 78 | url: this.PREFIX + encodedUrl + '.json', 79 | context: this, 80 | success: function(data) { 81 | callback.call(this, {meta: {}, data: data}); 82 | } 83 | }); 84 | }, 500); 85 | } 86 | }); -------------------------------------------------------------------------------- /app/tests/unit/handlebars_helpers_test.js: -------------------------------------------------------------------------------- 1 | require('dashboard/handlebars_helpers'); 2 | 3 | module("{{echo}}"); 4 | 5 | test("helper is available", function() { 6 | ok(Ember.Handlebars.helpers.echo, "echo helper is available"); 7 | }); 8 | 9 | test("returns the specified property", function() { 10 | var view = Ember.View.create({ 11 | obj: Ember.Object.create({ 12 | action: 'dingdong' 13 | }), 14 | template: Ember.Handlebars.compile('{{#with obj}}{{echo "action"}}{{/with}}') 15 | }); 16 | 17 | Ember.run(function() { 18 | view.appendTo('#qunit-fixture'); 19 | }); 20 | 21 | equal(Ember.$('#qunit-fixture').text(), "dingdong", "echo helper outputs correct value"); 22 | }); 23 | 24 | module("{{trim}}"); 25 | 26 | test("trim is available", function() { 27 | ok(Ember.Handlebars.helpers.trim, "trim helper is available"); 28 | }); 29 | 30 | test("outputs by default given string", function() { 31 | var view = Ember.View.create({ 32 | property: 'mystring', 33 | template: Ember.Handlebars.compile('{{trim property}}') 34 | }); 35 | 36 | Ember.run(function() { 37 | view.appendTo('#qunit-fixture'); 38 | }); 39 | 40 | equal(Ember.$('#qunit-fixture').text(), 'mystring', "by default doesn't trim anything"); 41 | }); 42 | 43 | test("trims string to specific length", function() { 44 | var view = Ember.View.create({ 45 | property: 'mystring', 46 | template: Ember.Handlebars.compile('{{trim property length=2}}') 47 | }); 48 | 49 | Ember.run(function() { 50 | view.appendTo('#qunit-fixture'); 51 | }); 52 | 53 | equal(Ember.$('#qunit-fixture').text(), 'my', "it trims the string to given length"); 54 | }); 55 | 56 | test("exceeding length does not blow up the handler", 57 | function() { 58 | var view = Ember.View.create({ 59 | property: 'mystring', 60 | template: Ember.Handlebars.compile('{{trim property length=100}}') 61 | }); 62 | 63 | Ember.run(function() { 64 | view.appendTo('#qunit-fixture'); 65 | }); 66 | 67 | equal(Ember.$('#qunit-fixture').text(), 'mystring', "it trims the string to given length"); 68 | }); 69 | 70 | module("{{ago}}"); 71 | 72 | test("helper is available", function() { 73 | ok(Ember.Handlebars.helpers.ago, "ago helper is availbale"); 74 | }); 75 | 76 | test("ago can handle Date objects", function() { 77 | var view = Ember.View.create({ 78 | time: new Date(), 79 | template: Ember.Handlebars.compile('{{ago time}}') 80 | }); 81 | 82 | Ember.run(function() { 83 | view.appendTo('#qunit-fixture'); 84 | }); 85 | 86 | equal(Ember.$('#qunit-fixture').text(), "a few seconds ago", "ago helper outputs correct value"); 87 | }); 88 | 89 | test("ago can handle numbers", function() { 90 | var view = Ember.View.create({ 91 | time: new Date().getTime(), 92 | template: Ember.Handlebars.compile('{{ago time}}') 93 | }); 94 | 95 | Ember.run(function() { 96 | view.appendTo('#qunit-fixture'); 97 | }); 98 | 99 | equal(Ember.$('#qunit-fixture').text(), "a few seconds ago", "ago helper outputs correct value"); 100 | }); 101 | 102 | test("ago respects 'isSeconds' parameter", function() { 103 | var nowInSeconds = new Date().getTime() / 1000; 104 | var view = Ember.View.create({ 105 | time: nowInSeconds, 106 | template: Ember.Handlebars.compile('{{ago time isSeconds=true}}') 107 | }); 108 | 109 | Ember.run(function() { 110 | view.appendTo('#qunit-fixture'); 111 | }); 112 | 113 | equal(Ember.$('#qunit-fixture').text(), "a few seconds ago", "ago helper outputs correct value"); 114 | }); -------------------------------------------------------------------------------- /app/tests/integration/store_adapter_test.js: -------------------------------------------------------------------------------- 1 | require('dashboard/store'); 2 | require('dashboard/model'); 3 | require('dashboard/github_adapter'); 4 | 5 | var store, adapter; 6 | 7 | var callLater = function(func) { 8 | stop(); 9 | var args = arguments; 10 | setTimeout(function() { 11 | args = Array.prototype.slice.call(args, 1); 12 | func.apply(this, args); 13 | start(); 14 | }, 50); 15 | }; 16 | 17 | module("Dashboard.Store and Dashboard.GitHubAdpater integration tests", { 18 | setup: function() { 19 | adapter = Dashboard.GitHubAdpater.create(); 20 | store = Dashboard.Store.create({ adapter: adapter }); 21 | }, 22 | teardown: function() { 23 | adapter.destroy(); 24 | store.destroy(); 25 | } 26 | }); 27 | 28 | test("find works for getting a repository", 4, function() { 29 | // it is expected that a find for Dashboard.Repository 30 | // invokes ajax 31 | adapter.reopen({ 32 | ajax: function(url, callback) { 33 | ok(true, "ajax has been called"); 34 | equal(url, '/repos/pangratz/ember.js', 'correct url has been passed'); 35 | 36 | callback({full_name: 'pangratz/ember.js'}); 37 | } 38 | }); 39 | 40 | var repo = store.find(Dashboard.Repository, 'pangratz/ember.js'); 41 | ok(repo); 42 | equal(repo.get('id'), 'pangratz/ember.js', 'id is set immediately'); 43 | }); 44 | 45 | test("find works for getting a user", 4, function() { 46 | // it is expected that a find for Dashboard.User 47 | // invokes ajax 48 | adapter.reopen({ 49 | ajax: function(url, callback) { 50 | ok(true, "ajax has been called"); 51 | equal(url, '/users/pangratz', 'correct url has been passed'); 52 | 53 | callback({login: 'pangratz'}); 54 | } 55 | }); 56 | 57 | var user = store.find(Dashboard.User, 'pangratz'); 58 | ok(user, 'user is found'); 59 | ok(user.get('id'), 'pangratz', 'id is set immediately'); 60 | }); 61 | 62 | test("findQuery works for watched repositories", 4, function() { 63 | // it is expected that a findQuery for Dashboard.Repository 64 | // invokes `watchedRepositories` on adapter when type in the query is 'watched' 65 | adapter.reopen({ 66 | ajax: function(url, callback) { 67 | ok(true, "watched repositories has been called"); 68 | equal(url, '/users/pangratz/watched', 'correct url has been passed'); 69 | 70 | callLater(callback, [{full_name: 'first/repo'}, {full_name: 'second/repo'}]); 71 | } 72 | }); 73 | 74 | var watchedRepos = store.findQuery(Dashboard.Repository, { 75 | username: 'pangratz', 76 | type: 'watched' 77 | }); 78 | equal(watchedRepos.get('length'), 0, 'length is initially 0'); 79 | 80 | watchedRepos.addArrayObserver(this, { 81 | didChange: function() { 82 | equal(watchedRepos.get('length'), 2); 83 | }, 84 | willChange: Ember.K 85 | }); 86 | }); 87 | 88 | test("findQuery works for repositories", 4, function() { 89 | // it is expected that a findQuery for Dashboard.Repository 90 | // invokes `repositories` on adapter when there is just a username 91 | adapter.reopen({ 92 | ajax: function(url, callback) { 93 | ok(true, "repositories has been called"); 94 | equal(url, '/users/pangratz/repos', 'correct username has been passed'); 95 | 96 | callLater(callback, [{full_name: 'pangratz/first'}, {full_name: 'pangratz/second'}]); 97 | } 98 | }); 99 | 100 | var repos = store.findQuery(Dashboard.Repository, { username: 'pangratz' }); 101 | equal(repos.get('length'), 0, 'length is initially 0'); 102 | 103 | repos.addArrayObserver(this, { 104 | didChange: function(target, start, removed, added) { 105 | equal(repos.get('length'), 2, 'the array is populated'); 106 | }, 107 | willChange: Ember.K 108 | }); 109 | }); -------------------------------------------------------------------------------- /app/tests/unit/github_adapter_test.js: -------------------------------------------------------------------------------- 1 | require('dashboard/github_adapter'); 2 | 3 | var get = Ember.get; 4 | 5 | var ajaxCalled, ajaxParams; 6 | var ajaxUrl, ajaxCallback; 7 | var dataSource, store; 8 | 9 | var setupDataSource = { 10 | setup: function() { 11 | dataSource = Dashboard.GitHubAdpater.create({ 12 | _ajax: function(url, callback) { 13 | ajaxCalled = true; 14 | ajaxUrl = url; 15 | ajaxCallback = callback; 16 | } 17 | }); 18 | store = DS.Store.create({ 19 | adapter: dataSource, 20 | revision: 4 21 | }); 22 | }, 23 | teardowm: function() { 24 | ajaxCallback = ajaxParams = null; 25 | ajaxUrl = ajaxCallback = null; 26 | dataSource.destroy(); 27 | store.destroy(); 28 | } 29 | }; 30 | var callAjaxCallback = function(response) { 31 | ajaxCallback.call(dataSource, response); 32 | }; 33 | 34 | module("Dashboard.GitHubAdpater", setupDataSource); 35 | 36 | test("is defined", function() { 37 | ok(Dashboard.GitHubAdpater, "is defined"); 38 | ok(DS.Adapter.detect(Dashboard.GitHubAdpater), "it is a DS.Adapter"); 39 | }); 40 | 41 | test("ajax invokes _updateLimits", function() { 42 | var callbackCalled, callbackThis, callbackResponse; 43 | 44 | dataSource.ajax('/url', function(data) { 45 | callbackCalled = true; 46 | callbackThis = this; 47 | callbackResponse = data; 48 | }); 49 | 50 | callAjaxCallback({ 51 | meta: { 52 | 'X-RateLimit-Remaining': 42, 53 | 'X-RateLimit-Limit': 100 54 | }, 55 | data: { 56 | a: 1, 57 | b: 'hello' 58 | } 59 | }); 60 | 61 | equal(get(dataSource, 'remaining'), 42, "remaining has been read from repsonse.meta and set on the dataSource"); 62 | equal(get(dataSource, 'limit'), 100, "limit has been read from repsonse.meta and set on the dataSource"); 63 | 64 | ok(callbackCalled, 'callback has been called'); 65 | deepEqual(callbackThis, dataSource, "this inside called callback is set to the adapter"); 66 | deepEqual(callbackResponse, {a: 1, b: 'hello'}, "data of response is passed to callback"); 67 | }); 68 | 69 | module("Dashboard.GitHubAdpater#find for Dashboard.User calls ajax", setupDataSource); 70 | 71 | test("invokes ajax", function() { 72 | store.find(Dashboard.User, 'buster'); 73 | 74 | ok(ajaxCalled, 'ajax has been called'); 75 | deepEqual(ajaxUrl, '/users/buster', "the passed url is correct"); 76 | }); 77 | 78 | module("Dashboard.GitHubAdpater#findQuery for a user's watched repositories", setupDataSource); 79 | 80 | test("invokes ajax", function() { 81 | store.findQuery(Dashboard.Repository, { type: 'watched', username: 'buster' }); 82 | 83 | ok(ajaxCalled, 'ajax has been called'); 84 | deepEqual(ajaxUrl, '/users/buster/watched', "the passed url is correct"); 85 | }); 86 | 87 | 88 | module("Dashboard.GitHubAdpater#findQuery for a user's repositories calls ajax", setupDataSource); 89 | 90 | test("invokes ajax", function() { 91 | store.findQuery(Dashboard.Repository, {username: 'buster'}); 92 | 93 | ok(ajaxCalled, 'ajax has been called'); 94 | deepEqual(ajaxUrl, '/users/buster/repos', "the passed url is correct"); 95 | }); 96 | 97 | module("Dashboard.GitHubAdpater#findQuery for events for a repository calls ajax", setupDataSource); 98 | 99 | test("invokes ajax", function() { 100 | store.findQuery(Dashboard.Event, {username: 'buster', repository: 'bluth'}); 101 | 102 | ok(ajaxCalled, 'ajax has been called'); 103 | deepEqual(ajaxUrl, '/repos/buster/bluth/events', "the passed url is correct"); 104 | }); 105 | 106 | module("Dashboard.GitHubAdpater#findQuery for events for a user calls ajax", setupDataSource); 107 | 108 | test("invokes ajax", function() { 109 | store.findQuery(Dashboard.Event, {username: 'buster'}); 110 | 111 | ok(ajaxCalled, 'ajax has been called'); 112 | deepEqual(ajaxUrl, '/users/buster/events', "the passed url is correct"); 113 | }); -------------------------------------------------------------------------------- /Assetfile: -------------------------------------------------------------------------------- 1 | APPNAME = 'dashboard' 2 | 3 | require 'json' 4 | require 'rake-pipeline-web-filters' 5 | 6 | WebFilters = Rake::Pipeline::Web::Filters 7 | 8 | class LoaderFilter < WebFilters::MinispadeFilter 9 | def generate_output(inputs, output) 10 | inputs.each do |input| 11 | code = input.read 12 | module_id = @module_id_generator.call(input) 13 | contents = "function(require) {\n#{code}\n}" 14 | ret = "\nloader.register('#{module_id}', #{contents});\n" 15 | output.write ret 16 | end 17 | end 18 | end 19 | 20 | class EmberAssertFilter < Filter 21 | def generate_output(inputs, output) 22 | inputs.each do |input| 23 | result = input.read 24 | result.gsub!(/ember_assert\((.*)\);/, '') 25 | output.write(result) 26 | end 27 | end 28 | end 29 | 30 | class HandlebarsFilter < Filter 31 | def generate_output(inputs, output) 32 | inputs.each do |input| 33 | code = input.read.to_json 34 | name = File.basename(input.path, '.handlebars') 35 | output.write "\nreturn Ember.Handlebars.compile(#{code});\n" 36 | end 37 | end 38 | end 39 | 40 | class VersionFilter < Filter 41 | def generate_output(inputs, output) 42 | version = File.read("VERSION").strip 43 | inputs.each do |input| 44 | result = input.read 45 | result.gsub!(/VERSION: '.*'/, "VERSION: '#{version}'") 46 | output.write(result) 47 | end 48 | end 49 | end 50 | 51 | class GitHubFixtureAdpater < Filter 52 | def generate_output(inputs, output) 53 | inputs.each do |input| 54 | result = input.read 55 | result.gsub!("adapter: Dashboard.GitHubAdpater.create()", "adapter: Dashboard.GitHubFixtureAdpater.create()") 56 | output.write(result) 57 | end 58 | end 59 | end 60 | 61 | output 'assets' 62 | input 'app' do 63 | match 'index.html' do 64 | copy 'index.html' 65 | end 66 | 67 | match 'lib/**/*.js' do 68 | if ENV["TEST_MODE"] == "functional" 69 | filter GitHubFixtureAdpater 70 | end 71 | filter VersionFilter 72 | filter LoaderFilter, 73 | :module_id_generator => proc { |input| 74 | input.path.sub(/^lib\//, "#{APPNAME}/").sub(/\.js$/, '') 75 | } 76 | 77 | if ENV['RAKEP_MODE'] == 'production' 78 | filter EmberAssertFilter 79 | uglify {|input| input} 80 | end 81 | concat 'js/app.js' 82 | end 83 | 84 | match 'vendor/**/*.js' do 85 | filter LoaderFilter, 86 | :module_id_generator => proc { |input| 87 | input.path.sub(/^vendor\//, '').sub(/\.js$/, '') 88 | } 89 | 90 | if ENV['RAKEP_MODE'] == 'production' 91 | filter EmberAssertFilter 92 | uglify {|input| input} 93 | end 94 | concat %w[ 95 | vendor/jquery.js 96 | vendor/ember.js 97 | vendor/ember-data.js 98 | ], 'js/app.js' 99 | end 100 | 101 | match 'modules/**/*.js' do 102 | if ENV['RAKEP_MODE'] == 'production' 103 | filter EmberAssertFilter 104 | uglify {|input| input} 105 | end 106 | concat 'js/app.js' 107 | end 108 | 109 | match 'plugins/**/*.js' do 110 | if ENV['RAKEP_MODE'] == 'production' 111 | uglify {|input| input} 112 | end 113 | concat do |input| 114 | input.sub(/plugins\//, 'js/') 115 | end 116 | end 117 | 118 | match 'templates/**/*.handlebars' do 119 | filter HandlebarsFilter 120 | filter LoaderFilter, 121 | :module_id_generator => proc { |input| 122 | input.path.sub(/^templates\//, "#{APPNAME}/~templates/").sub(/\.handlebars$/, '') 123 | } 124 | if ENV['RAKEP_MODE'] == 'production' 125 | uglify {|input| input} 126 | end 127 | concat 'js/app.js' 128 | end 129 | 130 | match 'css/**/*.css' do 131 | if ENV['RAKEP_MODE'] == 'production' 132 | yui_css 133 | end 134 | concat ['bootstrap.css', 'main.css'], 'css/app.css' 135 | end 136 | 137 | match 'css/**/*.scss' do 138 | sass 139 | if ENV['RAKEP_MODE'] == 'production' 140 | yui_css 141 | end 142 | concat 'css/app.css' 143 | end 144 | 145 | match "static/**/*" do 146 | concat do |input| 147 | input.sub(/static\//, '') 148 | end 149 | end 150 | end 151 | 152 | output 'test_assets' 153 | input 'app' do 154 | match 'tests/{unit,integration}/**/*.js' do 155 | filter LoaderFilter, 156 | :module_id_generator => proc { |input| 157 | input.path.sub(/^lib\//, "#{APPNAME}/").sub(/\.js$/, '') 158 | } 159 | concat 'app-tests.js' 160 | end 161 | end 162 | 163 | # vim: filetype=ruby 164 | -------------------------------------------------------------------------------- /app/tests/mock_response_data/repos/pangratz/ember.js.json: -------------------------------------------------------------------------------- 1 | { 2 | "clone_url": "https://github.com/pangratz/ember.js.git", 3 | "open_issues": 0, 4 | "has_issues": false, 5 | "owner": { 6 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 7 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 8 | "login": "pangratz", 9 | "url": "https://api.github.com/users/pangratz", 10 | "id": 341877 11 | }, 12 | "pushed_at": "2012-08-09T06:08:27Z", 13 | "forks": 0, 14 | "language": "JavaScript", 15 | "full_name": "pangratz/ember.js", 16 | "parent": { 17 | "clone_url": "https://github.com/emberjs/ember.js.git", 18 | "open_issues": 112, 19 | "has_issues": true, 20 | "owner": { 21 | "gravatar_id": "792333d2bad390e8a2e23f5e2f41f214", 22 | "avatar_url": "https://secure.gravatar.com/avatar/792333d2bad390e8a2e23f5e2f41f214?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-orgs.png", 23 | "login": "emberjs", 24 | "url": "https://api.github.com/users/emberjs", 25 | "id": 1253363 26 | }, 27 | "pushed_at": "2012-08-10T22:03:48Z", 28 | "forks": 530, 29 | "language": "JavaScript", 30 | "full_name": "emberjs/ember.js", 31 | "ssh_url": "git@github.com:emberjs/ember.js.git", 32 | "git_url": "git://github.com/emberjs/ember.js.git", 33 | "has_downloads": true, 34 | "homepage": "http://www.emberjs.com", 35 | "watchers_count": 4235, 36 | "forks_count": 530, 37 | "size": 11549, 38 | "fork": false, 39 | "svn_url": "https://github.com/emberjs/ember.js", 40 | "created_at": "2011-05-25T23:39:40Z", 41 | "open_issues_count": 112, 42 | "description": "Ember.js - A JavaScript framework for creating ambitious web applications", 43 | "name": "ember.js", 44 | "url": "https://api.github.com/repos/emberjs/ember.js", 45 | "mirror_url": null, 46 | "has_wiki": true, 47 | "html_url": "https://github.com/emberjs/ember.js", 48 | "private": false, 49 | "id": 1801829, 50 | "watchers": 4235, 51 | "updated_at": "2012-08-12T11:32:29Z" 52 | }, 53 | "master_branch": "master", 54 | "ssh_url": "git@github.com:pangratz/ember.js.git", 55 | "git_url": "git://github.com/pangratz/ember.js.git", 56 | "has_downloads": true, 57 | "homepage": "http://www.emberjs.com", 58 | "source": { 59 | "clone_url": "https://github.com/emberjs/ember.js.git", 60 | "open_issues": 112, 61 | "has_issues": true, 62 | "owner": { 63 | "gravatar_id": "792333d2bad390e8a2e23f5e2f41f214", 64 | "avatar_url": "https://secure.gravatar.com/avatar/792333d2bad390e8a2e23f5e2f41f214?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-orgs.png", 65 | "login": "emberjs", 66 | "url": "https://api.github.com/users/emberjs", 67 | "id": 1253363 68 | }, 69 | "pushed_at": "2012-08-10T22:03:48Z", 70 | "forks": 530, 71 | "language": "JavaScript", 72 | "full_name": "emberjs/ember.js", 73 | "ssh_url": "git@github.com:emberjs/ember.js.git", 74 | "git_url": "git://github.com/emberjs/ember.js.git", 75 | "has_downloads": true, 76 | "homepage": "http://www.emberjs.com", 77 | "watchers_count": 4235, 78 | "forks_count": 530, 79 | "size": 11549, 80 | "fork": false, 81 | "svn_url": "https://github.com/emberjs/ember.js", 82 | "created_at": "2011-05-25T23:39:40Z", 83 | "open_issues_count": 112, 84 | "description": "Ember.js - A JavaScript framework for creating ambitious web applications", 85 | "name": "ember.js", 86 | "url": "https://api.github.com/repos/emberjs/ember.js", 87 | "mirror_url": null, 88 | "has_wiki": true, 89 | "html_url": "https://github.com/emberjs/ember.js", 90 | "private": false, 91 | "id": 1801829, 92 | "watchers": 4235, 93 | "updated_at": "2012-08-12T11:32:29Z" 94 | }, 95 | "watchers_count": 1, 96 | "forks_count": 0, 97 | "size": 2211, 98 | "fork": true, 99 | "svn_url": "https://github.com/pangratz/ember.js", 100 | "created_at": "2011-12-16T20:12:37Z", 101 | "open_issues_count": 0, 102 | "description": "Ember.js - A JavaScript framework for creating ambitious web applications", 103 | "name": "ember.js", 104 | "url": "https://api.github.com/repos/pangratz/ember.js", 105 | "network_count": 530, 106 | "mirror_url": null, 107 | "has_wiki": true, 108 | "html_url": "https://github.com/pangratz/ember.js", 109 | "private": false, 110 | "id": 2997378, 111 | "watchers": 1, 112 | "updated_at": "2012-08-09T06:08:29Z" 113 | } 114 | -------------------------------------------------------------------------------- /app/vendor/jquery.inview.js: -------------------------------------------------------------------------------- 1 | /** 2 | * author Christopher Blum 3 | * - based on the idea of Remy Sharp, http://remysharp.com/2009/01/26/element-in-view-event-plugin/ 4 | * - forked from http://github.com/zuk/jquery.inview/ 5 | */ 6 | (function ($) { 7 | var inviewObjects = {}, viewportSize, viewportOffset, 8 | d = document, w = window, documentElement = d.documentElement, expando = $.expando; 9 | 10 | $.event.special.inview = { 11 | add: function(data) { 12 | inviewObjects[data.guid + "-" + this[expando]] = { data: data, $element: $(this) }; 13 | }, 14 | 15 | remove: function(data) { 16 | try { delete inviewObjects[data.guid + "-" + this[expando]]; } catch(e) {} 17 | } 18 | }; 19 | 20 | function getViewportSize() { 21 | var mode, domObject, size = { height: w.innerHeight, width: w.innerWidth }; 22 | 23 | // if this is correct then return it. iPad has compat Mode, so will 24 | // go into check clientHeight/clientWidth (which has the wrong value). 25 | if (!size.height) { 26 | mode = d.compatMode; 27 | if (mode || !$.support.boxModel) { // IE, Gecko 28 | domObject = mode === 'CSS1Compat' ? 29 | documentElement : // Standards 30 | d.body; // Quirks 31 | size = { 32 | height: domObject.clientHeight, 33 | width: domObject.clientWidth 34 | }; 35 | } 36 | } 37 | 38 | return size; 39 | } 40 | 41 | function getViewportOffset() { 42 | return { 43 | top: w.pageYOffset || documentElement.scrollTop || d.body.scrollTop, 44 | left: w.pageXOffset || documentElement.scrollLeft || d.body.scrollLeft 45 | }; 46 | } 47 | 48 | function checkInView() { 49 | var $elements = $(), elementsLength, i = 0; 50 | 51 | $.each(inviewObjects, function(i, inviewObject) { 52 | var selector = inviewObject.data.selector, 53 | $element = inviewObject.$element; 54 | $elements = $elements.add(selector ? $element.find(selector) : $element); 55 | }); 56 | 57 | elementsLength = $elements.length; 58 | if (elementsLength) { 59 | viewportSize = viewportSize || getViewportSize(); 60 | viewportOffset = viewportOffset || getViewportOffset(); 61 | 62 | for (; i viewportOffset.top && 86 | elementOffset.top < viewportOffset.top + viewportSize.height && 87 | elementOffset.left + elementSize.width > viewportOffset.left && 88 | elementOffset.left < viewportOffset.left + viewportSize.width) { 89 | visiblePartX = (viewportOffset.left > elementOffset.left ? 90 | 'right' : (viewportOffset.left + viewportSize.width) < (elementOffset.left + elementSize.width) ? 91 | 'left' : 'both'); 92 | visiblePartY = (viewportOffset.top > elementOffset.top ? 93 | 'bottom' : (viewportOffset.top + viewportSize.height) < (elementOffset.top + elementSize.height) ? 94 | 'top' : 'both'); 95 | visiblePartsMerged = visiblePartX + "-" + visiblePartY; 96 | if (!inView || inView !== visiblePartsMerged) { 97 | $element.data('inview', visiblePartsMerged).trigger('inview', [true, visiblePartX, visiblePartY]); 98 | } 99 | } else if (inView) { 100 | $element.data('inview', false).trigger('inview', [false]); 101 | } 102 | } 103 | } 104 | } 105 | 106 | $(w).bind("scroll resize", function() { 107 | viewportSize = viewportOffset = null; 108 | }); 109 | 110 | // Use setInterval in order to also make sure this captures elements within 111 | // "overflow:scroll" elements or elements that appeared in the dom tree due to 112 | // dom manipulation and reflow 113 | // old: $(window).scroll(checkInView); 114 | // 115 | // By the way, iOS (iPad, iPhone, ...) seems to not execute, or at least delays 116 | // intervals while the user scrolls. Therefore the inview event might fire a bit late there 117 | setInterval(checkInView, 250); 118 | })(jQuery); -------------------------------------------------------------------------------- /app/lib/router.js: -------------------------------------------------------------------------------- 1 | require('dashboard/core'); 2 | require('dashboard/model'); 3 | 4 | Dashboard.Router = Ember.Router.extend({ 5 | root: Ember.Route.extend({ 6 | 7 | gotoIndex: function(router) { 8 | // FIXME: somehow router.transitionTo('root.index') doesn't work 9 | router.route('/'); 10 | }, 11 | 12 | index: Ember.Route.extend({ 13 | route: '/', 14 | connectOutlets: function(router) { 15 | router.route('/pangratz'); 16 | } 17 | }), 18 | 19 | user: Ember.Route.extend({ 20 | route: '/:user_id', 21 | connectOutlets: function(router, user) { 22 | router.set('userController.content', user); 23 | }, 24 | 25 | index: Ember.Route.extend({ 26 | route: '/', 27 | connectOutlets: function(router) { 28 | var username = router.get('userController.id'); 29 | var store = router.get('store'); 30 | 31 | router.get('eventsController').resetLoadMore(); 32 | router.get('repositoriesController').resetLoadMore(); 33 | 34 | // set current query 35 | var query = { username: username, isLoadedCallback: function() { 36 | router.set('eventsController.isLoading', false); 37 | }}; 38 | router.set('eventsController.query', query); 39 | router.set('eventsController.isLoading', true); 40 | 41 | // get repositories for user 42 | var userRepositories = store.findQuery(Dashboard.Repository, { username: username }); 43 | 44 | // get events performed by user 45 | var filter = function(data) { 46 | if (Ember.get(data, 'savedData.org.login') === username) { return true; } 47 | return Ember.get(data, 'savedData.actor.login') === username; 48 | }; 49 | var userEvents = store.filter(Dashboard.Event, query, filter); 50 | 51 | // connect user with events and watched repositories 52 | router.get('applicationController').connectOutlet('user'); 53 | router.get('userController').connectOutlet('repositories', 'repositories', userRepositories); 54 | router.get('userController').connectOutlet('events', 'events', userEvents); 55 | } 56 | }), 57 | 58 | repository: Ember.Route.extend({ 59 | route: '/:repository', 60 | connectOutlets: function(router, context) { 61 | var username = router.get('userController.id'); 62 | var repoName = context.repository; 63 | var name = '%@/%@'.fmt(username, repoName); 64 | 65 | router.get('eventsController').resetLoadMore(); 66 | 67 | // set current query 68 | var query = { 69 | repoName: name, 70 | isLoadedCallback: function() { 71 | router.set('eventsController.isLoading', false); 72 | } 73 | }; 74 | router.set('eventsController.query', query); 75 | router.set('eventsController.isLoading', true); 76 | 77 | // fetch repo for current user 78 | var repository = router.get('store').find(Dashboard.Repository, name); 79 | 80 | // get all events for this repository 81 | var filter = function(data) { 82 | return Ember.get(data, 'savedData.repo.name') === name; 83 | }; 84 | var events = router.get('store').filter(Dashboard.Event, query, filter); 85 | 86 | // connect repository and events 87 | router.set('repositoryController.content', repository); 88 | router.get('applicationController').connectOutlet('repository'); 89 | router.get('repositoryController').connectOutlet('events', 'events', events); 90 | } 91 | }), 92 | 93 | loadMoreEvents: function(router, page) { 94 | var query = router.get('eventsController.query'); 95 | query.page = page; 96 | router.get('store').findQuery(Dashboard.Event, query); 97 | router.set('eventsController.isLoading', true); 98 | }, 99 | loadMoreRepos: function(router, page) { 100 | var username = router.get('userController.id'); 101 | var query = { username: username }; 102 | var store = router.get('store').findQuery(Dashboard.Repository, query); 103 | }, 104 | 105 | showUserOfEvent: function(router, evt) { 106 | var e = evt.context; 107 | var username = e.get('actor.login'); 108 | router.route('/%@'.fmt(username)); 109 | }, 110 | showUserOfRepository: function(router, evt) { 111 | var repository = evt.context; 112 | var username = repository.get('owner.login'); 113 | router.route('/%@'.fmt(username)); 114 | }, 115 | 116 | showRepository: function(router, evt) { 117 | var repository = evt.context; 118 | var full_name = repository.get('full_name'); 119 | router.route('/%@'.fmt(full_name)); 120 | }, 121 | showRepositoryOfEvent: function(router, evt) { 122 | var e = evt.context; 123 | var full_name = e.get('repo.name'); 124 | router.route('/%@'.fmt(full_name)); 125 | } 126 | }) 127 | }) 128 | }); 129 | -------------------------------------------------------------------------------- /tests/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.9.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | } 71 | 72 | #qunit-userAgent { 73 | padding: 0.5em 0 0.5em 2.5em; 74 | background-color: #2b81af; 75 | color: #fff; 76 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 77 | } 78 | 79 | 80 | /** Tests: Pass/Fail */ 81 | 82 | #qunit-tests { 83 | list-style-position: inside; 84 | } 85 | 86 | #qunit-tests li { 87 | padding: 0.4em 0.5em 0.4em 2.5em; 88 | border-bottom: 1px solid #fff; 89 | list-style-position: inside; 90 | } 91 | 92 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 93 | display: none; 94 | } 95 | 96 | #qunit-tests li strong { 97 | cursor: pointer; 98 | } 99 | 100 | #qunit-tests li a { 101 | padding: 0.5em; 102 | color: #c2ccd1; 103 | text-decoration: none; 104 | } 105 | #qunit-tests li a:hover, 106 | #qunit-tests li a:focus { 107 | color: #000; 108 | } 109 | 110 | #qunit-tests ol { 111 | margin-top: 0.5em; 112 | padding: 0.5em; 113 | 114 | background-color: #fff; 115 | 116 | border-radius: 5px; 117 | -moz-border-radius: 5px; 118 | -webkit-border-radius: 5px; 119 | } 120 | 121 | #qunit-tests table { 122 | border-collapse: collapse; 123 | margin-top: .2em; 124 | } 125 | 126 | #qunit-tests th { 127 | text-align: right; 128 | vertical-align: top; 129 | padding: 0 .5em 0 0; 130 | } 131 | 132 | #qunit-tests td { 133 | vertical-align: top; 134 | } 135 | 136 | #qunit-tests pre { 137 | margin: 0; 138 | white-space: pre-wrap; 139 | word-wrap: break-word; 140 | } 141 | 142 | #qunit-tests del { 143 | background-color: #e0f2be; 144 | color: #374e0c; 145 | text-decoration: none; 146 | } 147 | 148 | #qunit-tests ins { 149 | background-color: #ffcaca; 150 | color: #500; 151 | text-decoration: none; 152 | } 153 | 154 | /*** Test Counts */ 155 | 156 | #qunit-tests b.counts { color: black; } 157 | #qunit-tests b.passed { color: #5E740B; } 158 | #qunit-tests b.failed { color: #710909; } 159 | 160 | #qunit-tests li li { 161 | padding: 5px; 162 | background-color: #fff; 163 | border-bottom: none; 164 | list-style-position: inside; 165 | } 166 | 167 | /*** Passing Styles */ 168 | 169 | #qunit-tests li li.pass { 170 | color: #3c510c; 171 | background-color: #fff; 172 | border-left: 10px solid #C6E746; 173 | } 174 | 175 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 176 | #qunit-tests .pass .test-name { color: #366097; } 177 | 178 | #qunit-tests .pass .test-actual, 179 | #qunit-tests .pass .test-expected { color: #999999; } 180 | 181 | #qunit-banner.qunit-pass { background-color: #C6E746; } 182 | 183 | /*** Failing Styles */ 184 | 185 | #qunit-tests li li.fail { 186 | color: #710909; 187 | background-color: #fff; 188 | border-left: 10px solid #EE5757; 189 | white-space: pre; 190 | } 191 | 192 | #qunit-tests > li:last-child { 193 | border-radius: 0 0 5px 5px; 194 | -moz-border-radius: 0 0 5px 5px; 195 | -webkit-border-bottom-right-radius: 5px; 196 | -webkit-border-bottom-left-radius: 5px; 197 | } 198 | 199 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 200 | #qunit-tests .fail .test-name, 201 | #qunit-tests .fail .module-name { color: #000000; } 202 | 203 | #qunit-tests .fail .test-actual { color: #EE5757; } 204 | #qunit-tests .fail .test-expected { color: green; } 205 | 206 | #qunit-banner.qunit-fail { background-color: #EE5757; } 207 | 208 | 209 | /** Result */ 210 | 211 | #qunit-testresult { 212 | padding: 0.5em 0.5em 0.5em 2.5em; 213 | 214 | color: #2b81af; 215 | background-color: #D2E0E6; 216 | 217 | border-bottom: 1px solid white; 218 | } 219 | #qunit-testresult .module-name { 220 | font-weight: bold; 221 | } 222 | 223 | /** Fixture */ 224 | 225 | #qunit-fixture { 226 | position: absolute; 227 | top: -10000px; 228 | left: -10000px; 229 | width: 1000px; 230 | height: 1000px; 231 | } 232 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | APPNAME = 'dashboard' 2 | 3 | require 'colored' 4 | require 'rake-pipeline' 5 | require 'versionomy' 6 | require 'rack' 7 | 8 | def pipeline 9 | Rake::Pipeline::Project.new('Assetfile') 10 | end 11 | 12 | def version 13 | File.read("VERSION").strip 14 | end 15 | 16 | desc "Clean #{APPNAME}" 17 | task :clean do 18 | pipeline.clean 19 | end 20 | 21 | desc "Build #{APPNAME}" 22 | task :build => :clean do 23 | pipeline.invoke 24 | end 25 | 26 | def download(path) 27 | last_slash = path.rindex("/") 28 | system "mkdir -p app/tests/mock_response_data/#{path[0..last_slash]}" 29 | system "wget https://api.github.com/#{path} -O app/tests/mock_response_data/#{path}.json" 30 | end 31 | 32 | def download_event(url, page = 1) 33 | folder = "app/tests/mock_response_data/#{url}" 34 | system "mkdir -p #{folder}" 35 | Dir.chdir folder do 36 | system "wget https://api.github.com/#{url}?page=#{page}.json" 37 | end 38 | end 39 | 40 | def download_user_event(user, page = 1) 41 | download_event("users/#{user}/events", page) 42 | end 43 | 44 | def download_repo_event(user, repo, page = 1) 45 | download_event("repos/#{user}/#{repo}/events", page) 46 | end 47 | 48 | task :download_mock_responses do 49 | download "users/pangratz" 50 | download "users/nokinen" 51 | 52 | download "users/pangratz/repos" 53 | download "users/nokinen/repos" 54 | 55 | (1..10).each {|page| download_user_event("pangratz", page)} 56 | (1..10).each {|page| download_user_event("nokinen", page)} 57 | 58 | download "repos/pangratz/ember.js" 59 | download "repos/pangratz/dashboard" 60 | download "repos/nokinen/fdc" 61 | 62 | (1..10).each {|page| download_repo_event("pangratz", "ember.js", page)} 63 | (1..10).each {|page| download_repo_event("pangratz", "dashboard", page)} 64 | (1..10).each {|page| download_repo_event("nokinen", "fdc", page)} 65 | end 66 | 67 | task :test => ["test:all"] 68 | namespace :test do 69 | task :all => [:unit, :functional] 70 | 71 | desc "Run casper.js tests" 72 | task :functional do 73 | ENV["TEST_MODE"] = "functional" 74 | Rake::Task["build"].invoke 75 | 76 | unless system("which casperjs > /dev/null 2>&1") 77 | abort "Casper.js is not installed. Download from http://casperjs.org/" 78 | end 79 | 80 | puts "start Rack server" 81 | pid = Thread.new do 82 | server = Rack::Server.new({ 83 | :config => "config.ru", 84 | :Port => 9292, 85 | :Logger => nil 86 | }) 87 | server.start 88 | end 89 | 90 | puts "Running #{APPNAME} Casper.js tests" 91 | success = system("casperjs test app/tests/functional/") 92 | 93 | pid.kill 94 | 95 | if success 96 | puts "Tests Passed".green 97 | else 98 | puts "Tests Failed".red 99 | exit(1) 100 | end 101 | end 102 | 103 | desc "Run Unit and Integration test" 104 | task :unit => :build do 105 | unless system("which phantomjs > /dev/null 2>&1") 106 | abort "PhantomJS is not installed. Download from http://phantomjs.org/" 107 | end 108 | 109 | cmd = "phantomjs tests/run-tests.js \"file://#{File.dirname(__FILE__)}/tests/index.html\"" 110 | 111 | # Run the tests 112 | puts "Running #{APPNAME} tests" 113 | success = system(cmd) 114 | 115 | if success 116 | puts "Tests Passed".green 117 | else 118 | puts "Tests Failed".red 119 | exit(1) 120 | end 121 | end 122 | end 123 | 124 | desc "Bumps current version" 125 | task :bump_version do 126 | next_version = Versionomy.parse(version).bump(:tiny) 127 | 128 | print "Enter next version (#{next_version}): " 129 | user_input = STDIN.gets.chomp 130 | next_version = user_input unless user_input.empty? 131 | 132 | File.open('VERSION', 'w') { |f| f.write( next_version ) } 133 | system "git commit VERSION -m 'Bump to new version #{next_version}'" 134 | end 135 | 136 | desc "Tag current code with current version" 137 | task :release => :deploy do 138 | system "git tag -a v#{version}" 139 | system "git push --tag" 140 | 141 | Rake::Task["bump_version"].invoke 142 | end 143 | 144 | desc "deploy app" 145 | task :deploy => :clean do 146 | FileUtils.rm_rf "assets" 147 | 148 | ENV['RAKEP_MODE'] = "production" 149 | Rake::Task["build"].invoke 150 | 151 | origin = `git config remote.origin.url`.chomp 152 | username = `git config user.name`.chomp 153 | cd "assets" do 154 | system "git init" 155 | system "git remote add origin #{origin}" 156 | system "git checkout -b gh-pages" 157 | system "git add ." 158 | puts "\n## Commiting: Site updated at #{Time.now.utc}" 159 | message = "Site updated at #{Time.now.utc}" 160 | system "git commit -m \"#{message}\"" 161 | puts "\n## Pushing generated website" 162 | system "git push origin gh-pages --force" 163 | puts "\n## Github Pages deploy complete -- http://#{username}.github.com/dashboard" 164 | end 165 | end 166 | 167 | namespace :upgrade do 168 | def download_ember(repo_name, source = repo_name, target = source) 169 | FileUtils.rm_rf "tmp/#{repo_name}" 170 | `git clone https://github.com/emberjs/#{repo_name} tmp/#{repo_name}` 171 | Dir.chdir("tmp/#{repo_name}") do 172 | `bundle install` 173 | `rake dist` 174 | end 175 | FileUtils.copy "tmp/#{repo_name}/dist/#{source}", "app/vendor/#{target}" 176 | FileUtils.rm_rf "tmp/#{repo_name}" 177 | end 178 | 179 | task :ember do 180 | download_ember("ember.js") 181 | end 182 | 183 | task :data do 184 | download_ember("data", "ember-data.js") 185 | end 186 | 187 | task :qunit do 188 | FileUtils.rm_rf "tmp/qunit" 189 | `git clone https://github.com/jquery/qunit tmp/qunit` 190 | Dir.chdir("tmp/qunit") do 191 | latest_tag = `git describe --abbrev=0 --tags` 192 | system "git checkout #{latest_tag}" 193 | end 194 | FileUtils.cp_r "tmp/qunit/qunit/.", "tests/qunit" 195 | FileUtils.rm_rf "tmp/qunit" 196 | end 197 | 198 | task :all => [:ember, :data, :qunit] 199 | end 200 | 201 | task :upgrade => ["upgrade:all"] 202 | -------------------------------------------------------------------------------- /app/tests/mock_response_data/users/nokinen/repos.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "forks": 2, 4 | "language": "Ruby", 5 | "git_url": "git://github.com/nokinen/fdc.git", 6 | "created_at": "2012-05-14T22:32:28Z", 7 | "description": "A platform independent command-line utility written in Ruby for converting files in the avionics flight recorder data format (IGC) to the keyhole markup language (KML) for their display in applications such as Google Earth.", 8 | "ssh_url": "git@github.com:nokinen/fdc.git", 9 | "owner": { 10 | "login": "nokinen", 11 | "avatar_url": "https://secure.gravatar.com/avatar/3f60ae2a5febcca386fa08d6b4af43f0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 12 | "url": "https://api.github.com/users/nokinen", 13 | "gravatar_id": "3f60ae2a5febcca386fa08d6b4af43f0", 14 | "id": 435811 15 | }, 16 | "mirror_url": null, 17 | "has_downloads": true, 18 | "watchers_count": 2, 19 | "updated_at": "2012-06-21T18:23:39Z", 20 | "open_issues_count": 1, 21 | "forks_count": 2, 22 | "svn_url": "https://github.com/nokinen/fdc", 23 | "has_wiki": false, 24 | "html_url": "https://github.com/nokinen/fdc", 25 | "watchers": 2, 26 | "size": 296, 27 | "fork": false, 28 | "full_name": "nokinen/fdc", 29 | "clone_url": "https://github.com/nokinen/fdc.git", 30 | "name": "fdc", 31 | "url": "https://api.github.com/repos/nokinen/fdc", 32 | "open_issues": 1, 33 | "has_issues": true, 34 | "homepage": "https://rubygems.org/gems/fdc", 35 | "private": false, 36 | "id": 4329788, 37 | "pushed_at": "2012-06-21T18:23:39Z" 38 | }, 39 | { 40 | "forks": 1, 41 | "language": "Ruby", 42 | "git_url": "git://github.com/nokinen/gamsjaga.git", 43 | "created_at": "2012-06-22T11:51:02Z", 44 | "master_branch": "master", 45 | "description": "A modern CMS for associations, clubs or school classes that is implemented with Ruby on Rails and Twitter Bootstrap.", 46 | "ssh_url": "git@github.com:nokinen/gamsjaga.git", 47 | "owner": { 48 | "login": "nokinen", 49 | "avatar_url": "https://secure.gravatar.com/avatar/3f60ae2a5febcca386fa08d6b4af43f0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 50 | "url": "https://api.github.com/users/nokinen", 51 | "gravatar_id": "3f60ae2a5febcca386fa08d6b4af43f0", 52 | "id": 435811 53 | }, 54 | "mirror_url": null, 55 | "has_downloads": true, 56 | "watchers_count": 2, 57 | "updated_at": "2012-08-06T21:22:44Z", 58 | "open_issues_count": 11, 59 | "forks_count": 1, 60 | "svn_url": "https://github.com/nokinen/gamsjaga", 61 | "has_wiki": false, 62 | "html_url": "https://github.com/nokinen/gamsjaga", 63 | "watchers": 2, 64 | "size": 944, 65 | "fork": false, 66 | "full_name": "nokinen/gamsjaga", 67 | "clone_url": "https://github.com/nokinen/gamsjaga.git", 68 | "name": "gamsjaga", 69 | "url": "https://api.github.com/repos/nokinen/gamsjaga", 70 | "open_issues": 11, 71 | "has_issues": true, 72 | "homepage": "", 73 | "private": false, 74 | "id": 4750558, 75 | "pushed_at": "2012-08-06T21:22:43Z" 76 | }, 77 | { 78 | "forks": 2, 79 | "language": "Java", 80 | "git_url": "git://github.com/nokinen/lunch-oracle.git", 81 | "created_at": "2011-06-02T11:05:22Z", 82 | "description": "Oracle that decides where to have lunch on and near campus of JKU Linz", 83 | "ssh_url": "git@github.com:nokinen/lunch-oracle.git", 84 | "owner": { 85 | "login": "nokinen", 86 | "avatar_url": "https://secure.gravatar.com/avatar/3f60ae2a5febcca386fa08d6b4af43f0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 87 | "url": "https://api.github.com/users/nokinen", 88 | "gravatar_id": "3f60ae2a5febcca386fa08d6b4af43f0", 89 | "id": 435811 90 | }, 91 | "mirror_url": null, 92 | "has_downloads": true, 93 | "watchers_count": 2, 94 | "updated_at": "2011-10-04T15:35:44Z", 95 | "open_issues_count": 0, 96 | "forks_count": 2, 97 | "svn_url": "https://github.com/nokinen/lunch-oracle", 98 | "has_wiki": true, 99 | "html_url": "https://github.com/nokinen/lunch-oracle", 100 | "watchers": 2, 101 | "size": 120, 102 | "fork": false, 103 | "full_name": "nokinen/lunch-oracle", 104 | "clone_url": "https://github.com/nokinen/lunch-oracle.git", 105 | "name": "lunch-oracle", 106 | "url": "https://api.github.com/repos/nokinen/lunch-oracle", 107 | "open_issues": 0, 108 | "has_issues": true, 109 | "homepage": "", 110 | "private": false, 111 | "id": 1836682, 112 | "pushed_at": "2011-06-07T13:37:31Z" 113 | }, 114 | { 115 | "forks": 0, 116 | "language": "Shell", 117 | "git_url": "git://github.com/nokinen/macosx-backup.git", 118 | "created_at": "2011-05-04T10:22:04Z", 119 | "description": "script for mac os x backup (Mail, Safari, Address Book, iCal, Dashboard, ...)", 120 | "ssh_url": "git@github.com:nokinen/macosx-backup.git", 121 | "owner": { 122 | "login": "nokinen", 123 | "avatar_url": "https://secure.gravatar.com/avatar/3f60ae2a5febcca386fa08d6b4af43f0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 124 | "url": "https://api.github.com/users/nokinen", 125 | "gravatar_id": "3f60ae2a5febcca386fa08d6b4af43f0", 126 | "id": 435811 127 | }, 128 | "mirror_url": null, 129 | "has_downloads": true, 130 | "watchers_count": 2, 131 | "updated_at": "2011-10-04T14:45:45Z", 132 | "open_issues_count": 0, 133 | "forks_count": 0, 134 | "svn_url": "https://github.com/nokinen/macosx-backup", 135 | "has_wiki": true, 136 | "html_url": "https://github.com/nokinen/macosx-backup", 137 | "watchers": 2, 138 | "size": 356, 139 | "fork": true, 140 | "full_name": "nokinen/macosx-backup", 141 | "clone_url": "https://github.com/nokinen/macosx-backup.git", 142 | "name": "macosx-backup", 143 | "url": "https://api.github.com/repos/nokinen/macosx-backup", 144 | "open_issues": 0, 145 | "has_issues": false, 146 | "homepage": "", 147 | "private": false, 148 | "id": 1700625, 149 | "pushed_at": "2011-05-10T08:43:55Z" 150 | }, 151 | { 152 | "forks": 2, 153 | "language": "Objective-C", 154 | "git_url": "git://github.com/nokinen/minilogo-cocoa.git", 155 | "created_at": "2010-12-13T10:59:07Z", 156 | "description": "Minimal LOGO interpreter written in Objective-C with COCOA", 157 | "ssh_url": "git@github.com:nokinen/minilogo-cocoa.git", 158 | "owner": { 159 | "login": "nokinen", 160 | "avatar_url": "https://secure.gravatar.com/avatar/3f60ae2a5febcca386fa08d6b4af43f0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 161 | "url": "https://api.github.com/users/nokinen", 162 | "gravatar_id": "3f60ae2a5febcca386fa08d6b4af43f0", 163 | "id": 435811 164 | }, 165 | "mirror_url": null, 166 | "has_downloads": true, 167 | "watchers_count": 2, 168 | "updated_at": "2012-03-07T07:38:12Z", 169 | "open_issues_count": 4, 170 | "forks_count": 2, 171 | "svn_url": "https://github.com/nokinen/minilogo-cocoa", 172 | "has_wiki": true, 173 | "html_url": "https://github.com/nokinen/minilogo-cocoa", 174 | "watchers": 2, 175 | "size": 488, 176 | "fork": false, 177 | "full_name": "nokinen/minilogo-cocoa", 178 | "clone_url": "https://github.com/nokinen/minilogo-cocoa.git", 179 | "name": "minilogo-cocoa", 180 | "url": "https://api.github.com/repos/nokinen/minilogo-cocoa", 181 | "open_issues": 4, 182 | "has_issues": true, 183 | "homepage": "", 184 | "private": false, 185 | "id": 1164392, 186 | "pushed_at": "2011-04-05T20:11:09Z" 187 | }, 188 | { 189 | "forks": 1, 190 | "language": "R", 191 | "git_url": "git://github.com/nokinen/msc-analysis.git", 192 | "created_at": "2012-01-19T09:07:55Z", 193 | "description": "R project to analyze data gathered from my msc evaluation", 194 | "ssh_url": "git@github.com:nokinen/msc-analysis.git", 195 | "owner": { 196 | "login": "nokinen", 197 | "avatar_url": "https://secure.gravatar.com/avatar/3f60ae2a5febcca386fa08d6b4af43f0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 198 | "url": "https://api.github.com/users/nokinen", 199 | "gravatar_id": "3f60ae2a5febcca386fa08d6b4af43f0", 200 | "id": 435811 201 | }, 202 | "mirror_url": null, 203 | "has_downloads": true, 204 | "watchers_count": 1, 205 | "updated_at": "2012-04-02T19:31:18Z", 206 | "open_issues_count": 0, 207 | "forks_count": 1, 208 | "svn_url": "https://github.com/nokinen/msc-analysis", 209 | "has_wiki": true, 210 | "html_url": "https://github.com/nokinen/msc-analysis", 211 | "watchers": 1, 212 | "size": 216, 213 | "fork": false, 214 | "full_name": "nokinen/msc-analysis", 215 | "clone_url": "https://github.com/nokinen/msc-analysis.git", 216 | "name": "msc-analysis", 217 | "url": "https://api.github.com/repos/nokinen/msc-analysis", 218 | "open_issues": 0, 219 | "has_issues": true, 220 | "homepage": "", 221 | "private": false, 222 | "id": 3216304, 223 | "pushed_at": "2012-04-02T19:26:55Z" 224 | }, 225 | { 226 | "forks": 1, 227 | "language": "R", 228 | "git_url": "git://github.com/nokinen/msc-datagen.git", 229 | "created_at": "2011-10-21T14:34:47Z", 230 | "description": "R evaluation tool for evaluation of my master's thesis", 231 | "ssh_url": "git@github.com:nokinen/msc-datagen.git", 232 | "owner": { 233 | "login": "nokinen", 234 | "avatar_url": "https://secure.gravatar.com/avatar/3f60ae2a5febcca386fa08d6b4af43f0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 235 | "url": "https://api.github.com/users/nokinen", 236 | "gravatar_id": "3f60ae2a5febcca386fa08d6b4af43f0", 237 | "id": 435811 238 | }, 239 | "mirror_url": null, 240 | "has_downloads": true, 241 | "watchers_count": 1, 242 | "updated_at": "2012-06-10T19:36:49Z", 243 | "open_issues_count": 0, 244 | "forks_count": 1, 245 | "svn_url": "https://github.com/nokinen/msc-datagen", 246 | "has_wiki": true, 247 | "html_url": "https://github.com/nokinen/msc-datagen", 248 | "watchers": 1, 249 | "size": 228, 250 | "fork": false, 251 | "full_name": "nokinen/msc-datagen", 252 | "clone_url": "https://github.com/nokinen/msc-datagen.git", 253 | "name": "msc-datagen", 254 | "url": "https://api.github.com/repos/nokinen/msc-datagen", 255 | "open_issues": 0, 256 | "has_issues": true, 257 | "homepage": "", 258 | "private": false, 259 | "id": 2620805, 260 | "pushed_at": "2012-01-07T20:19:43Z" 261 | }, 262 | { 263 | "forks": 0, 264 | "language": "Shell", 265 | "git_url": "git://github.com/nokinen/oh-my-zsh.git", 266 | "created_at": "2012-05-14T09:13:41Z", 267 | "description": "A community-driven framework for managing your zsh configuration. Includes 40+ optional plugins (rails, git, OSX, hub, capistrano, brew, ant, macports, etc), over 80 terminal themes to spice up your morning, and an auto-update tool so that makes it easy to keep up with the latest updates from the community.", 268 | "ssh_url": "git@github.com:nokinen/oh-my-zsh.git", 269 | "owner": { 270 | "login": "nokinen", 271 | "avatar_url": "https://secure.gravatar.com/avatar/3f60ae2a5febcca386fa08d6b4af43f0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 272 | "url": "https://api.github.com/users/nokinen", 273 | "gravatar_id": "3f60ae2a5febcca386fa08d6b4af43f0", 274 | "id": 435811 275 | }, 276 | "mirror_url": null, 277 | "has_downloads": true, 278 | "watchers_count": 1, 279 | "updated_at": "2012-05-14T09:21:25Z", 280 | "open_issues_count": 0, 281 | "forks_count": 0, 282 | "svn_url": "https://github.com/nokinen/oh-my-zsh", 283 | "has_wiki": true, 284 | "html_url": "https://github.com/nokinen/oh-my-zsh", 285 | "watchers": 1, 286 | "size": 112, 287 | "fork": true, 288 | "full_name": "nokinen/oh-my-zsh", 289 | "clone_url": "https://github.com/nokinen/oh-my-zsh.git", 290 | "name": "oh-my-zsh", 291 | "url": "https://api.github.com/repos/nokinen/oh-my-zsh", 292 | "open_issues": 0, 293 | "has_issues": false, 294 | "homepage": "http://twitter.com/ohmyzsh", 295 | "private": false, 296 | "id": 4321968, 297 | "pushed_at": "2012-05-14T09:21:25Z" 298 | }, 299 | { 300 | "forks": 2, 301 | "language": null, 302 | "git_url": "git://github.com/nokinen/randi.git", 303 | "created_at": "2011-06-17T23:03:15Z", 304 | "description": "Random file selection in Mac OS X Finder", 305 | "ssh_url": "git@github.com:nokinen/randi.git", 306 | "owner": { 307 | "login": "nokinen", 308 | "avatar_url": "https://secure.gravatar.com/avatar/3f60ae2a5febcca386fa08d6b4af43f0?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 309 | "url": "https://api.github.com/users/nokinen", 310 | "gravatar_id": "3f60ae2a5febcca386fa08d6b4af43f0", 311 | "id": 435811 312 | }, 313 | "mirror_url": null, 314 | "has_downloads": true, 315 | "watchers_count": 1, 316 | "updated_at": "2011-10-04T16:05:36Z", 317 | "open_issues_count": 1, 318 | "forks_count": 2, 319 | "svn_url": "https://github.com/nokinen/randi", 320 | "has_wiki": true, 321 | "html_url": "https://github.com/nokinen/randi", 322 | "watchers": 1, 323 | "size": 175, 324 | "fork": false, 325 | "full_name": "nokinen/randi", 326 | "clone_url": "https://github.com/nokinen/randi.git", 327 | "name": "randi", 328 | "url": "https://api.github.com/repos/nokinen/randi", 329 | "open_issues": 1, 330 | "has_issues": true, 331 | "homepage": "", 332 | "private": false, 333 | "id": 1913745, 334 | "pushed_at": "2011-09-27T07:22:35Z" 335 | } 336 | ] 337 | -------------------------------------------------------------------------------- /app/tests/mock_response_data/repos/nokinen/fdc/events?page=8.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "created_at": "2012-05-16T11:02:45Z", 4 | "type": "IssuesEvent", 5 | "actor": { 6 | "login": "nokinen", 7 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 8 | "id": 435811, 9 | "url": "https://api.github.com/users/nokinen", 10 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" 11 | }, 12 | "payload": { 13 | "action": "opened", 14 | "issue": { 15 | "body": "", 16 | "number": 4, 17 | "updated_at": "2012-05-16T11:02:45Z", 18 | "created_at": "2012-05-16T11:02:45Z", 19 | "labels": [ 20 | 21 | ], 22 | "assignee": null, 23 | "title": "Command line capabilities", 24 | "state": "open", 25 | "milestone": null, 26 | "user": { 27 | "login": "nokinen", 28 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 29 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 30 | "id": 435811, 31 | "url": "https://api.github.com/users/nokinen" 32 | }, 33 | "id": 4602158, 34 | "closed_at": null, 35 | "comments": 0, 36 | "url": "https://api.github.com/repos/nokinen/igc-kml/issues/4", 37 | "html_url": "https://github.com/nokinen/igc-kml/issues/4", 38 | "pull_request": { 39 | "patch_url": null, 40 | "diff_url": null, 41 | "html_url": null 42 | } 43 | } 44 | }, 45 | "public": true, 46 | "repo": { 47 | "id": 4329788, 48 | "url": "https://api.github.com/repos/nokinen/igc-kml", 49 | "name": "nokinen/igc-kml" 50 | }, 51 | "id": "1552653720" 52 | }, 53 | { 54 | "created_at": "2012-05-16T11:02:16Z", 55 | "type": "IssuesEvent", 56 | "actor": { 57 | "login": "nokinen", 58 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 59 | "id": 435811, 60 | "url": "https://api.github.com/users/nokinen", 61 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" 62 | }, 63 | "payload": { 64 | "action": "closed", 65 | "issue": { 66 | "body": "", 67 | "number": 3, 68 | "updated_at": "2012-05-16T11:02:15Z", 69 | "created_at": "2012-05-14T23:26:30Z", 70 | "labels": [ 71 | 72 | ], 73 | "assignee": null, 74 | "title": "Make use of regular expressions for parsing .icg", 75 | "state": "closed", 76 | "milestone": null, 77 | "user": { 78 | "login": "nokinen", 79 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 80 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 81 | "id": 435811, 82 | "url": "https://api.github.com/users/nokinen" 83 | }, 84 | "id": 4574881, 85 | "closed_at": "2012-05-16T11:02:15Z", 86 | "comments": 0, 87 | "url": "https://api.github.com/repos/nokinen/igc-kml/issues/3", 88 | "html_url": "https://github.com/nokinen/igc-kml/issues/3", 89 | "pull_request": { 90 | "patch_url": null, 91 | "diff_url": null, 92 | "html_url": null 93 | } 94 | } 95 | }, 96 | "public": true, 97 | "repo": { 98 | "id": 4329788, 99 | "url": "https://api.github.com/repos/nokinen/igc-kml", 100 | "name": "nokinen/igc-kml" 101 | }, 102 | "id": "1552653603" 103 | }, 104 | { 105 | "created_at": "2012-05-16T11:02:15Z", 106 | "type": "IssuesEvent", 107 | "actor": { 108 | "login": "nokinen", 109 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 110 | "id": 435811, 111 | "url": "https://api.github.com/users/nokinen", 112 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" 113 | }, 114 | "payload": { 115 | "action": "closed", 116 | "issue": { 117 | "body": "Supports timestamps through ", 118 | "number": 1, 119 | "updated_at": "2012-05-16T11:02:15Z", 120 | "created_at": "2012-05-14T23:22:37Z", 121 | "labels": [ 122 | 123 | ], 124 | "assignee": null, 125 | "title": "Use for KLM", 126 | "state": "closed", 127 | "milestone": null, 128 | "user": { 129 | "login": "nokinen", 130 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 131 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 132 | "id": 435811, 133 | "url": "https://api.github.com/users/nokinen" 134 | }, 135 | "id": 4574838, 136 | "closed_at": "2012-05-16T11:02:15Z", 137 | "comments": 0, 138 | "url": "https://api.github.com/repos/nokinen/igc-kml/issues/1", 139 | "html_url": "https://github.com/nokinen/igc-kml/issues/1", 140 | "pull_request": { 141 | "patch_url": null, 142 | "diff_url": null, 143 | "html_url": null 144 | } 145 | } 146 | }, 147 | "public": true, 148 | "repo": { 149 | "id": 4329788, 150 | "url": "https://api.github.com/repos/nokinen/igc-kml", 151 | "name": "nokinen/igc-kml" 152 | }, 153 | "id": "1552653602" 154 | }, 155 | { 156 | "created_at": "2012-05-16T11:00:51Z", 157 | "type": "PushEvent", 158 | "actor": { 159 | "login": "nokinen", 160 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 161 | "id": 435811, 162 | "url": "https://api.github.com/users/nokinen", 163 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" 164 | }, 165 | "payload": { 166 | "ref": "refs/heads/master", 167 | "size": 1, 168 | "commits": [ 169 | { 170 | "author": { 171 | "email": "tobias.noiges@gmail.com", 172 | "name": "Tobias Noiges" 173 | }, 174 | "sha": "a1e373a0c66706dd27bf1c732e92114f5e4e95ed", 175 | "message": "Refactor Converter\nImprove API to load igc files\nImprove API to write kml files\nModify constructor", 176 | "url": "https://api.github.com/repos/nokinen/igc-kml/commits/a1e373a0c66706dd27bf1c732e92114f5e4e95ed", 177 | "distinct": true 178 | } 179 | ], 180 | "push_id": 78703515, 181 | "head": "a1e373a0c66706dd27bf1c732e92114f5e4e95ed" 182 | }, 183 | "public": true, 184 | "repo": { 185 | "id": 4329788, 186 | "url": "https://api.github.com/repos/nokinen/igc-kml", 187 | "name": "nokinen/igc-kml" 188 | }, 189 | "id": "1552653224" 190 | }, 191 | { 192 | "created_at": "2012-05-16T09:14:26Z", 193 | "type": "PushEvent", 194 | "actor": { 195 | "login": "nokinen", 196 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 197 | "id": 435811, 198 | "url": "https://api.github.com/users/nokinen", 199 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" 200 | }, 201 | "payload": { 202 | "ref": "refs/heads/master", 203 | "size": 1, 204 | "commits": [ 205 | { 206 | "author": { 207 | "email": "tobias.noiges@gmail.com", 208 | "name": "Tobias Noiges" 209 | }, 210 | "sha": "3584409aa3c6c482532c2f1d649d70b0e2e234cc", 211 | "message": "Fix wrong track tag case", 212 | "url": "https://api.github.com/repos/nokinen/igc-kml/commits/3584409aa3c6c482532c2f1d649d70b0e2e234cc", 213 | "distinct": true 214 | } 215 | ], 216 | "push_id": 78686043, 217 | "head": "3584409aa3c6c482532c2f1d649d70b0e2e234cc" 218 | }, 219 | "public": true, 220 | "repo": { 221 | "id": 4329788, 222 | "url": "https://api.github.com/repos/nokinen/igc-kml", 223 | "name": "nokinen/igc-kml" 224 | }, 225 | "id": "1552619622" 226 | }, 227 | { 228 | "created_at": "2012-05-15T22:32:32Z", 229 | "type": "PushEvent", 230 | "actor": { 231 | "login": "nokinen", 232 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 233 | "id": 435811, 234 | "url": "https://api.github.com/users/nokinen", 235 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" 236 | }, 237 | "payload": { 238 | "ref": "refs/heads/master", 239 | "size": 1, 240 | "commits": [ 241 | { 242 | "author": { 243 | "email": "tobias.noiges@gmail.com", 244 | "name": "Tobias Noiges" 245 | }, 246 | "sha": "d6da6c0985e020597886aa2017546f68cfaf570b", 247 | "message": "Implement Converter and Location", 248 | "url": "https://api.github.com/repos/nokinen/igc-kml/commits/d6da6c0985e020597886aa2017546f68cfaf570b", 249 | "distinct": true 250 | } 251 | ], 252 | "push_id": 78609756, 253 | "head": "d6da6c0985e020597886aa2017546f68cfaf570b" 254 | }, 255 | "public": true, 256 | "repo": { 257 | "id": 4329788, 258 | "url": "https://api.github.com/repos/nokinen/igc-kml", 259 | "name": "nokinen/igc-kml" 260 | }, 261 | "id": "1552491937" 262 | }, 263 | { 264 | "created_at": "2012-05-14T23:26:30Z", 265 | "type": "IssuesEvent", 266 | "actor": { 267 | "login": "nokinen", 268 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 269 | "id": 435811, 270 | "url": "https://api.github.com/users/nokinen", 271 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" 272 | }, 273 | "payload": { 274 | "action": "opened", 275 | "issue": { 276 | "body": "", 277 | "number": 3, 278 | "updated_at": "2012-05-14T23:26:30Z", 279 | "created_at": "2012-05-14T23:26:30Z", 280 | "labels": [ 281 | 282 | ], 283 | "assignee": null, 284 | "title": "Make use of regular expressions for parsing .icg", 285 | "state": "open", 286 | "milestone": null, 287 | "user": { 288 | "login": "nokinen", 289 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 290 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 291 | "id": 435811, 292 | "url": "https://api.github.com/users/nokinen" 293 | }, 294 | "id": 4574881, 295 | "closed_at": null, 296 | "comments": 0, 297 | "url": "https://api.github.com/repos/nokinen/igc-kml/issues/3", 298 | "html_url": "https://github.com/nokinen/igc-kml/issues/3", 299 | "pull_request": { 300 | "patch_url": null, 301 | "diff_url": null, 302 | "html_url": null 303 | } 304 | } 305 | }, 306 | "public": true, 307 | "repo": { 308 | "id": 4329788, 309 | "url": "https://api.github.com/repos/nokinen/igc-kml", 310 | "name": "nokinen/igc-kml" 311 | }, 312 | "id": "1552081698" 313 | }, 314 | { 315 | "created_at": "2012-05-14T23:23:25Z", 316 | "type": "IssuesEvent", 317 | "actor": { 318 | "login": "nokinen", 319 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 320 | "id": 435811, 321 | "url": "https://api.github.com/users/nokinen", 322 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" 323 | }, 324 | "payload": { 325 | "action": "opened", 326 | "issue": { 327 | "body": "", 328 | "number": 2, 329 | "updated_at": "2012-05-14T23:23:23Z", 330 | "created_at": "2012-05-14T23:23:23Z", 331 | "labels": [ 332 | 333 | ], 334 | "assignee": null, 335 | "title": "Sync .igc files on device with rsync", 336 | "state": "open", 337 | "milestone": null, 338 | "user": { 339 | "login": "nokinen", 340 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 341 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 342 | "id": 435811, 343 | "url": "https://api.github.com/users/nokinen" 344 | }, 345 | "id": 4574846, 346 | "closed_at": null, 347 | "comments": 0, 348 | "url": "https://api.github.com/repos/nokinen/igc-kml/issues/2", 349 | "html_url": "https://github.com/nokinen/igc-kml/issues/2", 350 | "pull_request": { 351 | "patch_url": null, 352 | "diff_url": null, 353 | "html_url": null 354 | } 355 | } 356 | }, 357 | "public": true, 358 | "repo": { 359 | "id": 4329788, 360 | "url": "https://api.github.com/repos/nokinen/igc-kml", 361 | "name": "nokinen/igc-kml" 362 | }, 363 | "id": "1552080912" 364 | }, 365 | { 366 | "created_at": "2012-05-14T23:22:38Z", 367 | "type": "IssuesEvent", 368 | "actor": { 369 | "login": "nokinen", 370 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 371 | "id": 435811, 372 | "url": "https://api.github.com/users/nokinen", 373 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" 374 | }, 375 | "payload": { 376 | "action": "opened", 377 | "issue": { 378 | "body": "Supports timestamps through ", 379 | "number": 1, 380 | "updated_at": "2012-05-14T23:22:37Z", 381 | "created_at": "2012-05-14T23:22:37Z", 382 | "labels": [ 383 | 384 | ], 385 | "assignee": null, 386 | "title": "Use for KLM", 387 | "state": "open", 388 | "milestone": null, 389 | "user": { 390 | "login": "nokinen", 391 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 392 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 393 | "id": 435811, 394 | "url": "https://api.github.com/users/nokinen" 395 | }, 396 | "id": 4574838, 397 | "closed_at": null, 398 | "comments": 0, 399 | "url": "https://api.github.com/repos/nokinen/igc-kml/issues/1", 400 | "html_url": "https://github.com/nokinen/igc-kml/issues/1", 401 | "pull_request": { 402 | "patch_url": null, 403 | "diff_url": null, 404 | "html_url": null 405 | } 406 | } 407 | }, 408 | "public": true, 409 | "repo": { 410 | "id": 4329788, 411 | "url": "https://api.github.com/repos/nokinen/igc-kml", 412 | "name": "nokinen/igc-kml" 413 | }, 414 | "id": "1552080732" 415 | }, 416 | { 417 | "created_at": "2012-05-14T22:42:48Z", 418 | "type": "PushEvent", 419 | "actor": { 420 | "login": "nokinen", 421 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 422 | "id": 435811, 423 | "url": "https://api.github.com/users/nokinen", 424 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" 425 | }, 426 | "payload": { 427 | "ref": "refs/heads/master", 428 | "size": 1, 429 | "commits": [ 430 | { 431 | "author": { 432 | "email": "tobias.noiges@gmail.com", 433 | "name": "Tobias Noiges" 434 | }, 435 | "sha": "8c90c8509937891a8db11c970b3ce17d911f3b84", 436 | "message": "Add .rb and sample .igc files", 437 | "url": "https://api.github.com/repos/nokinen/igc-kml/commits/8c90c8509937891a8db11c970b3ce17d911f3b84", 438 | "distinct": true 439 | } 440 | ], 441 | "push_id": 78363549, 442 | "head": "8c90c8509937891a8db11c970b3ce17d911f3b84" 443 | }, 444 | "public": true, 445 | "repo": { 446 | "id": 4329788, 447 | "url": "https://api.github.com/repos/nokinen/igc-kml", 448 | "name": "nokinen/igc-kml" 449 | }, 450 | "id": "1552069606" 451 | }, 452 | { 453 | "created_at": "2012-05-14T22:32:29Z", 454 | "type": "CreateEvent", 455 | "actor": { 456 | "login": "nokinen", 457 | "gravatar_id": "b72c639457d872c7c65edb4bb94d8003", 458 | "id": 435811, 459 | "url": "https://api.github.com/users/nokinen", 460 | "avatar_url": "https://secure.gravatar.com/avatar/b72c639457d872c7c65edb4bb94d8003?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png" 461 | }, 462 | "payload": { 463 | "ref": null, 464 | "master_branch": "master", 465 | "ref_type": "repository", 466 | "description": "Ruby tool to convert IGC files to KML" 467 | }, 468 | "public": true, 469 | "repo": { 470 | "id": 4329788, 471 | "url": "https://api.github.com/repos/nokinen/igc-kml", 472 | "name": "nokinen/igc-kml" 473 | }, 474 | "id": "1552066543" 475 | } 476 | ] 477 | -------------------------------------------------------------------------------- /app/tests/mock_response_data/repos/pangratz/dashboard/events?page=7.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "PushEvent", 4 | "actor": { 5 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 6 | "login": "pangratz", 7 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 8 | "id": 341877, 9 | "url": "https://api.github.com/users/pangratz" 10 | }, 11 | "public": true, 12 | "payload": { 13 | "before": "905f82d4f56901a91ffad5b7eaac9310857cde63", 14 | "push_id": 91895857, 15 | "size": 1, 16 | "ref": "refs/heads/dev", 17 | "commits": [ 18 | { 19 | "message": "Update README", 20 | "author": { 21 | "email": "cmueller.418@gmail.com", 22 | "name": "pangratz" 23 | }, 24 | "distinct": true, 25 | "sha": "9e6536acc7c5d15abdc597783d2bc872d3c2a9a6", 26 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/9e6536acc7c5d15abdc597783d2bc872d3c2a9a6" 27 | } 28 | ], 29 | "head": "9e6536acc7c5d15abdc597783d2bc872d3c2a9a6" 30 | }, 31 | "created_at": "2012-07-21T21:16:07Z", 32 | "repo": { 33 | "id": 3625527, 34 | "url": "https://api.github.com/repos/pangratz/dashboard", 35 | "name": "pangratz/dashboard" 36 | }, 37 | "id": "1576373209" 38 | }, 39 | { 40 | "type": "PushEvent", 41 | "actor": { 42 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 43 | "login": "pangratz", 44 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 45 | "id": 341877, 46 | "url": "https://api.github.com/users/pangratz" 47 | }, 48 | "public": true, 49 | "payload": { 50 | "before": "0cb88f4a69a42bf45cd66b134a2f74808e1fec06", 51 | "size": 1, 52 | "push_id": 91895578, 53 | "commits": [ 54 | { 55 | "message": "Add link to dashboard", 56 | "author": { 57 | "email": "cmueller.418@gmail.com", 58 | "name": "pangratz" 59 | }, 60 | "distinct": true, 61 | "sha": "905f82d4f56901a91ffad5b7eaac9310857cde63", 62 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/905f82d4f56901a91ffad5b7eaac9310857cde63" 63 | } 64 | ], 65 | "ref": "refs/heads/dev", 66 | "head": "905f82d4f56901a91ffad5b7eaac9310857cde63" 67 | }, 68 | "created_at": "2012-07-21T21:12:19Z", 69 | "repo": { 70 | "id": 3625527, 71 | "url": "https://api.github.com/repos/pangratz/dashboard", 72 | "name": "pangratz/dashboard" 73 | }, 74 | "id": "1576372694" 75 | }, 76 | { 77 | "type": "PushEvent", 78 | "actor": { 79 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 80 | "login": "pangratz", 81 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 82 | "id": 341877, 83 | "url": "https://api.github.com/users/pangratz" 84 | }, 85 | "public": true, 86 | "payload": { 87 | "before": "8c34b210f3dc0e677d7e86b8eddc937500eb4c04", 88 | "push_id": 91895426, 89 | "size": 1, 90 | "commits": [ 91 | { 92 | "message": "Update link to blog post", 93 | "author": { 94 | "email": "cmueller.418@gmail.com", 95 | "name": "pangratz" 96 | }, 97 | "distinct": true, 98 | "sha": "0cb88f4a69a42bf45cd66b134a2f74808e1fec06", 99 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/0cb88f4a69a42bf45cd66b134a2f74808e1fec06" 100 | } 101 | ], 102 | "ref": "refs/heads/dev", 103 | "head": "0cb88f4a69a42bf45cd66b134a2f74808e1fec06" 104 | }, 105 | "created_at": "2012-07-21T21:10:25Z", 106 | "repo": { 107 | "id": 3625527, 108 | "url": "https://api.github.com/repos/pangratz/dashboard", 109 | "name": "pangratz/dashboard" 110 | }, 111 | "id": "1576372439" 112 | }, 113 | { 114 | "type": "PushEvent", 115 | "actor": { 116 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 117 | "login": "pangratz", 118 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 119 | "id": 341877, 120 | "url": "https://api.github.com/users/pangratz" 121 | }, 122 | "public": true, 123 | "payload": { 124 | "before": "50056241432c4c593b982b371f3af69bf6b4fcb5", 125 | "push_id": 91895201, 126 | "size": 1, 127 | "commits": [ 128 | { 129 | "message": "Site updated at 2012-07-21 21:07:11 UTC", 130 | "author": { 131 | "email": "cmueller.418@gmail.com", 132 | "name": "pangratz" 133 | }, 134 | "distinct": true, 135 | "sha": "0f153aadfd65dc6a64419585783a373992acbe51", 136 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/0f153aadfd65dc6a64419585783a373992acbe51" 137 | } 138 | ], 139 | "ref": "refs/heads/gh-pages", 140 | "head": "0f153aadfd65dc6a64419585783a373992acbe51" 141 | }, 142 | "created_at": "2012-07-21T21:07:18Z", 143 | "repo": { 144 | "id": 3625527, 145 | "url": "https://api.github.com/repos/pangratz/dashboard", 146 | "name": "pangratz/dashboard" 147 | }, 148 | "id": "1576372086" 149 | }, 150 | { 151 | "type": "CreateEvent", 152 | "actor": { 153 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 154 | "login": "pangratz", 155 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 156 | "id": 341877, 157 | "url": "https://api.github.com/users/pangratz" 158 | }, 159 | "public": true, 160 | "payload": { 161 | "master_branch": "master", 162 | "ref_type": "branch", 163 | "ref": "master", 164 | "description": "" 165 | }, 166 | "created_at": "2012-07-21T21:06:54Z", 167 | "repo": { 168 | "id": 3625527, 169 | "url": "https://api.github.com/repos/pangratz/dashboard", 170 | "name": "pangratz/dashboard" 171 | }, 172 | "id": "1576372034" 173 | }, 174 | { 175 | "type": "PushEvent", 176 | "actor": { 177 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 178 | "login": "pangratz", 179 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 180 | "id": 341877, 181 | "url": "https://api.github.com/users/pangratz" 182 | }, 183 | "public": true, 184 | "payload": { 185 | "before": "1e7f5ddf8265e8706b2009b22da9aa439fc851be", 186 | "size": 8, 187 | "push_id": 91895058, 188 | "commits": [ 189 | { 190 | "message": "Rename app to dashboard", 191 | "author": { 192 | "email": "cmueller.418@gmail.com", 193 | "name": "pangratz" 194 | }, 195 | "distinct": true, 196 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/df7b88820e5a61e8bc355b791be4d9c4d64c1893", 197 | "sha": "df7b88820e5a61e8bc355b791be4d9c4d64c1893" 198 | }, 199 | { 200 | "message": "Add upgrade tasks", 201 | "author": { 202 | "email": "cmueller.418@gmail.com", 203 | "name": "pangratz" 204 | }, 205 | "distinct": true, 206 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/c5b0d769c1251b869bff4efe1abcfe185caec1ec", 207 | "sha": "c5b0d769c1251b869bff4efe1abcfe185caec1ec" 208 | }, 209 | { 210 | "message": "Update Ember.js to latest version", 211 | "author": { 212 | "email": "cmueller.418@gmail.com", 213 | "name": "pangratz" 214 | }, 215 | "distinct": true, 216 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/3180a0517dfac9fb0516ab2826657dc4099381b6", 217 | "sha": "3180a0517dfac9fb0516ab2826657dc4099381b6" 218 | }, 219 | { 220 | "message": "Update Ember-Data to latest version", 221 | "author": { 222 | "email": "cmueller.418@gmail.com", 223 | "name": "pangratz" 224 | }, 225 | "distinct": true, 226 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/0cf72317b77f3e0be9751ef41920eab1594320b3", 227 | "sha": "0cf72317b77f3e0be9751ef41920eab1594320b3" 228 | }, 229 | { 230 | "message": "Update QUnit to version 1.9.0", 231 | "author": { 232 | "email": "cmueller.418@gmail.com", 233 | "name": "pangratz" 234 | }, 235 | "distinct": true, 236 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/39cb423ecc321fb4186aa06ff68fd042bf2b45e0", 237 | "sha": "39cb423ecc321fb4186aa06ff68fd042bf2b45e0" 238 | }, 239 | { 240 | "message": "Add handlebars to app/vendor since it's needed by latest Ember.js", 241 | "author": { 242 | "email": "cmueller.418@gmail.com", 243 | "name": "pangratz" 244 | }, 245 | "distinct": true, 246 | "sha": "751dae14dabac2572d0f6770e73cdd6345e4a2ab", 247 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/751dae14dabac2572d0f6770e73cdd6345e4a2ab" 248 | }, 249 | { 250 | "message": "Update LICENSE", 251 | "author": { 252 | "email": "cmueller.418@gmail.com", 253 | "name": "pangratz" 254 | }, 255 | "distinct": true, 256 | "sha": "51507a6878e9194fe12f57a4d1f4ce5fd8d736cf", 257 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/51507a6878e9194fe12f57a4d1f4ce5fd8d736cf" 258 | }, 259 | { 260 | "message": "Update README", 261 | "author": { 262 | "email": "cmueller.418@gmail.com", 263 | "name": "pangratz" 264 | }, 265 | "distinct": true, 266 | "sha": "8c34b210f3dc0e677d7e86b8eddc937500eb4c04", 267 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/8c34b210f3dc0e677d7e86b8eddc937500eb4c04" 268 | } 269 | ], 270 | "ref": "refs/heads/dev", 271 | "head": "8c34b210f3dc0e677d7e86b8eddc937500eb4c04" 272 | }, 273 | "created_at": "2012-07-21T21:05:22Z", 274 | "repo": { 275 | "id": 3625527, 276 | "url": "https://api.github.com/repos/pangratz/dashboard", 277 | "name": "pangratz/dashboard" 278 | }, 279 | "id": "1576371828" 280 | }, 281 | { 282 | "type": "PushEvent", 283 | "actor": { 284 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 285 | "login": "pangratz", 286 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 287 | "id": 341877, 288 | "url": "https://api.github.com/users/pangratz" 289 | }, 290 | "public": true, 291 | "payload": { 292 | "before": "5f4a902232496ebb30503c9a952aca8a2545eb25", 293 | "push_id": 91892463, 294 | "size": 1, 295 | "commits": [ 296 | { 297 | "message": "Site updated at 2012-07-21 20:29:40 UTC", 298 | "author": { 299 | "email": "cmueller.418@gmail.com", 300 | "name": "pangratz" 301 | }, 302 | "distinct": true, 303 | "sha": "50056241432c4c593b982b371f3af69bf6b4fcb5", 304 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/50056241432c4c593b982b371f3af69bf6b4fcb5" 305 | } 306 | ], 307 | "ref": "refs/heads/gh-pages", 308 | "head": "50056241432c4c593b982b371f3af69bf6b4fcb5" 309 | }, 310 | "created_at": "2012-07-21T20:29:45Z", 311 | "repo": { 312 | "id": 3625527, 313 | "url": "https://api.github.com/repos/pangratz/dashboard", 314 | "name": "pangratz/dashboard" 315 | }, 316 | "id": "1576366959" 317 | }, 318 | { 319 | "type": "PushEvent", 320 | "actor": { 321 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 322 | "login": "pangratz", 323 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 324 | "id": 341877, 325 | "url": "https://api.github.com/users/pangratz" 326 | }, 327 | "public": true, 328 | "payload": { 329 | "before": "a1db8456696dcd97cc343144c7d192f10c050d7e", 330 | "size": 5, 331 | "push_id": 91892429, 332 | "commits": [ 333 | { 334 | "message": "Add upgrade tasks", 335 | "author": { 336 | "email": "cmueller.418@gmail.com", 337 | "name": "pangratz" 338 | }, 339 | "distinct": true, 340 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/d31d7140550e3d67fbf6b9175f35133b351f3da2", 341 | "sha": "d31d7140550e3d67fbf6b9175f35133b351f3da2" 342 | }, 343 | { 344 | "message": "Update Ember.js to latest version", 345 | "author": { 346 | "email": "cmueller.418@gmail.com", 347 | "name": "pangratz" 348 | }, 349 | "distinct": true, 350 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/859c18ac1a8067e30db499f0074eca2c607975dd", 351 | "sha": "859c18ac1a8067e30db499f0074eca2c607975dd" 352 | }, 353 | { 354 | "message": "Update Ember-Data to latest version", 355 | "author": { 356 | "email": "cmueller.418@gmail.com", 357 | "name": "pangratz" 358 | }, 359 | "distinct": true, 360 | "sha": "c140ad15965424cbd9ecadc32174a3d5b0860046", 361 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/c140ad15965424cbd9ecadc32174a3d5b0860046" 362 | }, 363 | { 364 | "message": "Update QUnit to version 1.9.0", 365 | "author": { 366 | "email": "cmueller.418@gmail.com", 367 | "name": "pangratz" 368 | }, 369 | "distinct": true, 370 | "sha": "bbff2cb9279c7a47c212467f786e9ef04b5d6518", 371 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/bbff2cb9279c7a47c212467f786e9ef04b5d6518" 372 | }, 373 | { 374 | "message": "Add handlebars to app/vendor since it's needed by latest Ember.js", 375 | "author": { 376 | "email": "cmueller.418@gmail.com", 377 | "name": "pangratz" 378 | }, 379 | "distinct": true, 380 | "sha": "1e7f5ddf8265e8706b2009b22da9aa439fc851be", 381 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/1e7f5ddf8265e8706b2009b22da9aa439fc851be" 382 | } 383 | ], 384 | "ref": "refs/heads/dev", 385 | "head": "1e7f5ddf8265e8706b2009b22da9aa439fc851be" 386 | }, 387 | "created_at": "2012-07-21T20:29:22Z", 388 | "repo": { 389 | "id": 3625527, 390 | "url": "https://api.github.com/repos/pangratz/dashboard", 391 | "name": "pangratz/dashboard" 392 | }, 393 | "id": "1576366909" 394 | }, 395 | { 396 | "type": "CreateEvent", 397 | "actor": { 398 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 399 | "login": "pangratz", 400 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 401 | "id": 341877, 402 | "url": "https://api.github.com/users/pangratz" 403 | }, 404 | "public": true, 405 | "payload": { 406 | "master_branch": "dev", 407 | "ref_type": "branch", 408 | "ref": "dev", 409 | "description": "" 410 | }, 411 | "created_at": "2012-07-21T20:10:31Z", 412 | "repo": { 413 | "id": 3625527, 414 | "url": "https://api.github.com/repos/pangratz/dashboard", 415 | "name": "pangratz/dashboard" 416 | }, 417 | "id": "1576364551" 418 | }, 419 | { 420 | "type": "PushEvent", 421 | "actor": { 422 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 423 | "login": "pangratz", 424 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 425 | "id": 341877, 426 | "url": "https://api.github.com/users/pangratz" 427 | }, 428 | "public": true, 429 | "payload": { 430 | "before": "ef958c028fa9528856afd72075eaad3c2e9132c9", 431 | "size": 1, 432 | "push_id": 91889329, 433 | "commits": [ 434 | { 435 | "message": "Site updated at 2012-07-21 19:45:37 UTC", 436 | "author": { 437 | "email": "cmueller.418@gmail.com", 438 | "name": "pangratz" 439 | }, 440 | "distinct": true, 441 | "sha": "5f4a902232496ebb30503c9a952aca8a2545eb25", 442 | "url": "https://api.github.com/repos/pangratz/dashboard/commits/5f4a902232496ebb30503c9a952aca8a2545eb25" 443 | } 444 | ], 445 | "ref": "refs/heads/gh-pages", 446 | "head": "5f4a902232496ebb30503c9a952aca8a2545eb25" 447 | }, 448 | "created_at": "2012-07-21T19:45:42Z", 449 | "repo": { 450 | "id": 3625527, 451 | "url": "https://api.github.com/repos/pangratz/dashboard", 452 | "name": "pangratz/dashboard" 453 | }, 454 | "id": "1576361369" 455 | }, 456 | { 457 | "type": "CreateEvent", 458 | "actor": { 459 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 460 | "login": "pangratz", 461 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 462 | "id": 341877, 463 | "url": "https://api.github.com/users/pangratz" 464 | }, 465 | "public": true, 466 | "payload": { 467 | "master_branch": "gh-pages", 468 | "ref_type": "branch", 469 | "ref": "gh-pages", 470 | "description": "" 471 | }, 472 | "created_at": "2012-07-21T19:43:51Z", 473 | "repo": { 474 | "id": 3625527, 475 | "url": "https://api.github.com/repos/pangratz/dashboard", 476 | "name": "pangratz/dashboard" 477 | }, 478 | "id": "1576361149" 479 | }, 480 | { 481 | "type": "IssuesEvent", 482 | "actor": { 483 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 484 | "login": "pangratz", 485 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 486 | "id": 341877, 487 | "url": "https://api.github.com/users/pangratz" 488 | }, 489 | "public": true, 490 | "payload": { 491 | "action": "opened", 492 | "issue": { 493 | "closed_at": null, 494 | "title": "Order repositories by last updated", 495 | "milestone": null, 496 | "user": { 497 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab", 498 | "login": "pangratz", 499 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 500 | "id": 341877, 501 | "url": "https://api.github.com/users/pangratz" 502 | }, 503 | "pull_request": { 504 | "patch_url": null, 505 | "html_url": null, 506 | "diff_url": null 507 | }, 508 | "id": 5297274, 509 | "updated_at": "2012-06-27T13:47:41Z", 510 | "number": 5, 511 | "assignee": null, 512 | "labels": [ 513 | { 514 | "color": "444444", 515 | "url": "https://api.github.com/repos/pangratz/dashboard/labels/view", 516 | "name": "view" 517 | } 518 | ], 519 | "body": "", 520 | "comments": 0, 521 | "url": "https://api.github.com/repos/pangratz/dashboard/issues/5", 522 | "html_url": "https://github.com/pangratz/dashboard/issues/5", 523 | "state": "open", 524 | "created_at": "2012-06-27T13:47:41Z" 525 | } 526 | }, 527 | "created_at": "2012-06-27T13:47:42Z", 528 | "repo": { 529 | "id": 3625527, 530 | "url": "https://api.github.com/repos/pangratz/dashboard", 531 | "name": "pangratz/dashboard" 532 | }, 533 | "id": "1567169330" 534 | }, 535 | { 536 | "type": "IssuesEvent", 537 | "actor": { 538 | "gravatar_id": "3cf2c8a8b86101d861efb7b14938513c", 539 | "login": "pangratz", 540 | "avatar_url": "https://secure.gravatar.com/avatar/3cf2c8a8b86101d861efb7b14938513c?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 541 | "id": 341877, 542 | "url": "https://api.github.com/users/pangratz" 543 | }, 544 | "public": true, 545 | "payload": { 546 | "action": "opened", 547 | "issue": { 548 | "closed_at": null, 549 | "title": "Breadcrumb navigation", 550 | "milestone": null, 551 | "user": { 552 | "gravatar_id": "3cf2c8a8b86101d861efb7b14938513c", 553 | "login": "pangratz", 554 | "avatar_url": "https://secure.gravatar.com/avatar/3cf2c8a8b86101d861efb7b14938513c?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 555 | "id": 341877, 556 | "url": "https://api.github.com/users/pangratz" 557 | }, 558 | "pull_request": { 559 | "patch_url": null, 560 | "html_url": null, 561 | "diff_url": null 562 | }, 563 | "id": 3718679, 564 | "updated_at": "2012-03-19T21:41:11Z", 565 | "number": 4, 566 | "assignee": null, 567 | "labels": [ 568 | 569 | ], 570 | "body": "which can be bookmarked", 571 | "comments": 0, 572 | "url": "https://api.github.com/repos/pangratz/dashboard/issues/4", 573 | "html_url": "https://github.com/pangratz/dashboard/issues/4", 574 | "state": "open", 575 | "created_at": "2012-03-19T21:41:11Z" 576 | } 577 | }, 578 | "created_at": "2012-03-19T21:41:11Z", 579 | "repo": { 580 | "id": 3625527, 581 | "url": "https://api.github.com/repos/pangratz/dashboard", 582 | "name": "pangratz/dashboard" 583 | }, 584 | "id": "1531831372" 585 | }, 586 | { 587 | "type": "IssuesEvent", 588 | "actor": { 589 | "gravatar_id": "3cf2c8a8b86101d861efb7b14938513c", 590 | "login": "pangratz", 591 | "avatar_url": "https://secure.gravatar.com/avatar/3cf2c8a8b86101d861efb7b14938513c?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 592 | "id": 341877, 593 | "url": "https://api.github.com/users/pangratz" 594 | }, 595 | "public": true, 596 | "payload": { 597 | "action": "opened", 598 | "issue": { 599 | "closed_at": null, 600 | "title": "Events can be filtered by type", 601 | "milestone": null, 602 | "user": { 603 | "gravatar_id": "3cf2c8a8b86101d861efb7b14938513c", 604 | "login": "pangratz", 605 | "avatar_url": "https://secure.gravatar.com/avatar/3cf2c8a8b86101d861efb7b14938513c?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 606 | "id": 341877, 607 | "url": "https://api.github.com/users/pangratz" 608 | }, 609 | "pull_request": { 610 | "patch_url": null, 611 | "html_url": null, 612 | "diff_url": null 613 | }, 614 | "id": 3718670, 615 | "updated_at": "2012-03-19T21:40:39Z", 616 | "number": 3, 617 | "assignee": null, 618 | "labels": [ 619 | 620 | ], 621 | "body": "* all commits\r\n* all wiki entries\r\n* ...", 622 | "comments": 0, 623 | "url": "https://api.github.com/repos/pangratz/dashboard/issues/3", 624 | "html_url": "https://github.com/pangratz/dashboard/issues/3", 625 | "state": "open", 626 | "created_at": "2012-03-19T21:40:39Z" 627 | } 628 | }, 629 | "created_at": "2012-03-19T21:40:41Z", 630 | "repo": { 631 | "id": 3625527, 632 | "url": "https://api.github.com/repos/pangratz/dashboard", 633 | "name": "pangratz/dashboard" 634 | }, 635 | "id": "1531831141" 636 | }, 637 | { 638 | "type": "IssuesEvent", 639 | "actor": { 640 | "gravatar_id": "3cf2c8a8b86101d861efb7b14938513c", 641 | "login": "pangratz", 642 | "avatar_url": "https://secure.gravatar.com/avatar/3cf2c8a8b86101d861efb7b14938513c?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 643 | "id": 341877, 644 | "url": "https://api.github.com/users/pangratz" 645 | }, 646 | "public": true, 647 | "payload": { 648 | "action": "opened", 649 | "issue": { 650 | "closed_at": null, 651 | "title": "Show all events for a repository", 652 | "milestone": null, 653 | "user": { 654 | "gravatar_id": "3cf2c8a8b86101d861efb7b14938513c", 655 | "login": "pangratz", 656 | "avatar_url": "https://secure.gravatar.com/avatar/3cf2c8a8b86101d861efb7b14938513c?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 657 | "id": 341877, 658 | "url": "https://api.github.com/users/pangratz" 659 | }, 660 | "pull_request": { 661 | "patch_url": null, 662 | "html_url": null, 663 | "diff_url": null 664 | }, 665 | "id": 3718652, 666 | "updated_at": "2012-03-19T21:39:33Z", 667 | "number": 2, 668 | "assignee": null, 669 | "labels": [ 670 | 671 | ], 672 | "body": "", 673 | "comments": 0, 674 | "url": "https://api.github.com/repos/pangratz/dashboard/issues/2", 675 | "html_url": "https://github.com/pangratz/dashboard/issues/2", 676 | "state": "open", 677 | "created_at": "2012-03-19T21:39:33Z" 678 | } 679 | }, 680 | "created_at": "2012-03-19T21:39:34Z", 681 | "repo": { 682 | "id": 3625527, 683 | "url": "https://api.github.com/repos/pangratz/dashboard", 684 | "name": "pangratz/dashboard" 685 | }, 686 | "id": "1531830742" 687 | }, 688 | { 689 | "type": "IssuesEvent", 690 | "actor": { 691 | "gravatar_id": "3cf2c8a8b86101d861efb7b14938513c", 692 | "login": "pangratz", 693 | "avatar_url": "https://secure.gravatar.com/avatar/3cf2c8a8b86101d861efb7b14938513c?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 694 | "id": 341877, 695 | "url": "https://api.github.com/users/pangratz" 696 | }, 697 | "public": true, 698 | "payload": { 699 | "action": "opened", 700 | "issue": { 701 | "closed_at": null, 702 | "title": "Show all events for an user", 703 | "milestone": null, 704 | "user": { 705 | "gravatar_id": "3cf2c8a8b86101d861efb7b14938513c", 706 | "login": "pangratz", 707 | "avatar_url": "https://secure.gravatar.com/avatar/3cf2c8a8b86101d861efb7b14938513c?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 708 | "id": 341877, 709 | "url": "https://api.github.com/users/pangratz" 710 | }, 711 | "pull_request": { 712 | "patch_url": null, 713 | "html_url": null, 714 | "diff_url": null 715 | }, 716 | "id": 3718649, 717 | "updated_at": "2012-03-19T21:39:19Z", 718 | "number": 1, 719 | "assignee": null, 720 | "labels": [ 721 | 722 | ], 723 | "body": "", 724 | "comments": 0, 725 | "url": "https://api.github.com/repos/pangratz/dashboard/issues/1", 726 | "html_url": "https://github.com/pangratz/dashboard/issues/1", 727 | "state": "open", 728 | "created_at": "2012-03-19T21:39:19Z" 729 | } 730 | }, 731 | "created_at": "2012-03-19T21:39:19Z", 732 | "repo": { 733 | "id": 3625527, 734 | "url": "https://api.github.com/repos/pangratz/dashboard", 735 | "name": "pangratz/dashboard" 736 | }, 737 | "id": "1531830655" 738 | }, 739 | { 740 | "type": "CreateEvent", 741 | "actor": { 742 | "gravatar_id": "3cf2c8a8b86101d861efb7b14938513c", 743 | "login": "pangratz", 744 | "avatar_url": "https://secure.gravatar.com/avatar/3cf2c8a8b86101d861efb7b14938513c?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 745 | "id": 341877, 746 | "url": "https://api.github.com/users/pangratz" 747 | }, 748 | "public": true, 749 | "payload": { 750 | "master_branch": "master", 751 | "ref_type": "repository", 752 | "ref": null, 753 | "description": "" 754 | }, 755 | "created_at": "2012-03-05T09:21:47Z", 756 | "repo": { 757 | "id": 3625527, 758 | "url": "https://api.github.com/repos/pangratz/dashboard", 759 | "name": "pangratz/dashboard" 760 | }, 761 | "id": "1526403816" 762 | } 763 | ] 764 | -------------------------------------------------------------------------------- /app/vendor/moment.js: -------------------------------------------------------------------------------- 1 | // moment.js 2 | // version : 1.6.2 3 | // author : Tim Wood 4 | // license : MIT 5 | // momentjs.com 6 | 7 | (function (Date, undefined) { 8 | 9 | var moment, 10 | VERSION = "1.6.2", 11 | round = Math.round, i, 12 | // internal storage for language config files 13 | languages = {}, 14 | currentLanguage = 'en', 15 | 16 | // check for nodeJS 17 | hasModule = (typeof module !== 'undefined'), 18 | 19 | // parameters to check for on the lang config 20 | langConfigProperties = 'months|monthsShort|monthsParse|weekdays|weekdaysShort|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'), 21 | 22 | // ASP.NET json date format regex 23 | aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, 24 | 25 | // format tokens 26 | formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|dddd?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?|LT|LL?L?L?)/g, 27 | 28 | // parsing tokens 29 | parseMultipleFormatChunker = /([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi, 30 | 31 | // parsing token regexes 32 | parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 33 | parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 34 | parseTokenThreeDigits = /\d{3}/, // 000 - 999 35 | parseTokenFourDigits = /\d{4}/, // 0000 - 9999 36 | parseTokenWord = /[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i, // any word characters or numbers 37 | parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z 38 | parseTokenT = /T/i, // T (ISO seperator) 39 | 40 | // preliminary iso regex 41 | // 0000-00-00 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 42 | isoRegex = /^\s*\d{4}-\d\d-\d\d(T(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/, 43 | isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', 44 | 45 | // iso time formats and regexes 46 | isoTimes = [ 47 | ['HH:mm:ss.S', /T\d\d:\d\d:\d\d\.\d{1,3}/], 48 | ['HH:mm:ss', /T\d\d:\d\d:\d\d/], 49 | ['HH:mm', /T\d\d:\d\d/], 50 | ['HH', /T\d\d/] 51 | ], 52 | 53 | // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] 54 | parseTimezoneChunker = /([\+\-]|\d\d)/gi, 55 | 56 | // getter and setter names 57 | proxyGettersAndSetters = 'Month|Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), 58 | unitMillisecondFactors = { 59 | 'Milliseconds' : 1, 60 | 'Seconds' : 1e3, 61 | 'Minutes' : 6e4, 62 | 'Hours' : 36e5, 63 | 'Days' : 864e5, 64 | 'Months' : 2592e6, 65 | 'Years' : 31536e6 66 | }; 67 | 68 | // Moment prototype object 69 | function Moment(date, isUTC) { 70 | this._d = date; 71 | this._isUTC = !!isUTC; 72 | } 73 | 74 | function absRound(number) { 75 | if (number < 0) { 76 | return Math.ceil(number); 77 | } else { 78 | return Math.floor(number); 79 | } 80 | } 81 | 82 | // Duration Constructor 83 | function Duration(duration) { 84 | var data = this._data = {}, 85 | years = duration.years || duration.y || 0, 86 | months = duration.months || duration.M || 0, 87 | weeks = duration.weeks || duration.w || 0, 88 | days = duration.days || duration.d || 0, 89 | hours = duration.hours || duration.h || 0, 90 | minutes = duration.minutes || duration.m || 0, 91 | seconds = duration.seconds || duration.s || 0, 92 | milliseconds = duration.milliseconds || duration.ms || 0; 93 | 94 | // representation for dateAddRemove 95 | this._milliseconds = milliseconds + 96 | seconds * 1e3 + // 1000 97 | minutes * 6e4 + // 1000 * 60 98 | hours * 36e5; // 1000 * 60 * 60 99 | // Because of dateAddRemove treats 24 hours as different from a 100 | // day when working around DST, we need to store them separately 101 | this._days = days + 102 | weeks * 7; 103 | // It is impossible translate months into days without knowing 104 | // which months you are are talking about, so we have to store 105 | // it separately. 106 | this._months = months + 107 | years * 12; 108 | 109 | // The following code bubbles up values, see the tests for 110 | // examples of what that means. 111 | data.milliseconds = milliseconds % 1000; 112 | seconds += absRound(milliseconds / 1000); 113 | 114 | data.seconds = seconds % 60; 115 | minutes += absRound(seconds / 60); 116 | 117 | data.minutes = minutes % 60; 118 | hours += absRound(minutes / 60); 119 | 120 | data.hours = hours % 24; 121 | days += absRound(hours / 24); 122 | 123 | days += weeks * 7; 124 | data.days = days % 30; 125 | 126 | months += absRound(days / 30); 127 | 128 | data.months = months % 12; 129 | years += absRound(months / 12); 130 | 131 | data.years = years; 132 | } 133 | 134 | // left zero fill a number 135 | // see http://jsperf.com/left-zero-filling for performance comparison 136 | function leftZeroFill(number, targetLength) { 137 | var output = number + ''; 138 | while (output.length < targetLength) { 139 | output = '0' + output; 140 | } 141 | return output; 142 | } 143 | 144 | // helper function for _.addTime and _.subtractTime 145 | function addOrSubtractDurationFromMoment(mom, duration, isAdding) { 146 | var ms = duration._milliseconds, 147 | d = duration._days, 148 | M = duration._months, 149 | currentDate; 150 | 151 | if (ms) { 152 | mom._d.setTime(+mom + ms * isAdding); 153 | } 154 | if (d) { 155 | mom.date(mom.date() + d * isAdding); 156 | } 157 | if (M) { 158 | currentDate = mom.date(); 159 | mom.date(1) 160 | .month(mom.month() + M * isAdding) 161 | .date(Math.min(currentDate, mom.daysInMonth())); 162 | } 163 | } 164 | 165 | // check if is an array 166 | function isArray(input) { 167 | return Object.prototype.toString.call(input) === '[object Array]'; 168 | } 169 | 170 | // convert an array to a date. 171 | // the array should mirror the parameters below 172 | // note: all values past the year are optional and will default to the lowest possible value. 173 | // [year, month, day , hour, minute, second, millisecond] 174 | function dateFromArray(input) { 175 | return new Date(input[0], input[1] || 0, input[2] || 1, input[3] || 0, input[4] || 0, input[5] || 0, input[6] || 0); 176 | } 177 | 178 | // format date using native date object 179 | function formatMoment(m, inputString) { 180 | var currentMonth = m.month(), 181 | currentDate = m.date(), 182 | currentYear = m.year(), 183 | currentDay = m.day(), 184 | currentHours = m.hours(), 185 | currentMinutes = m.minutes(), 186 | currentSeconds = m.seconds(), 187 | currentMilliseconds = m.milliseconds(), 188 | currentZone = -m.zone(), 189 | ordinal = moment.ordinal, 190 | meridiem = moment.meridiem; 191 | // check if the character is a format 192 | // return formatted string or non string. 193 | // 194 | // uses switch/case instead of an object of named functions (like http://phpjs.org/functions/date:380) 195 | // for minification and performance 196 | // see http://jsperf.com/object-of-functions-vs-switch for performance comparison 197 | function replaceFunction(input) { 198 | // create a couple variables to be used later inside one of the cases. 199 | var a, b; 200 | switch (input) { 201 | // MONTH 202 | case 'M' : 203 | return currentMonth + 1; 204 | case 'Mo' : 205 | return (currentMonth + 1) + ordinal(currentMonth + 1); 206 | case 'MM' : 207 | return leftZeroFill(currentMonth + 1, 2); 208 | case 'MMM' : 209 | return moment.monthsShort[currentMonth]; 210 | case 'MMMM' : 211 | return moment.months[currentMonth]; 212 | // DAY OF MONTH 213 | case 'D' : 214 | return currentDate; 215 | case 'Do' : 216 | return currentDate + ordinal(currentDate); 217 | case 'DD' : 218 | return leftZeroFill(currentDate, 2); 219 | // DAY OF YEAR 220 | case 'DDD' : 221 | a = new Date(currentYear, currentMonth, currentDate); 222 | b = new Date(currentYear, 0, 1); 223 | return ~~ (((a - b) / 864e5) + 1.5); 224 | case 'DDDo' : 225 | a = replaceFunction('DDD'); 226 | return a + ordinal(a); 227 | case 'DDDD' : 228 | return leftZeroFill(replaceFunction('DDD'), 3); 229 | // WEEKDAY 230 | case 'd' : 231 | return currentDay; 232 | case 'do' : 233 | return currentDay + ordinal(currentDay); 234 | case 'ddd' : 235 | return moment.weekdaysShort[currentDay]; 236 | case 'dddd' : 237 | return moment.weekdays[currentDay]; 238 | // WEEK OF YEAR 239 | case 'w' : 240 | a = new Date(currentYear, currentMonth, currentDate - currentDay + 5); 241 | b = new Date(a.getFullYear(), 0, 4); 242 | return ~~ ((a - b) / 864e5 / 7 + 1.5); 243 | case 'wo' : 244 | a = replaceFunction('w'); 245 | return a + ordinal(a); 246 | case 'ww' : 247 | return leftZeroFill(replaceFunction('w'), 2); 248 | // YEAR 249 | case 'YY' : 250 | return leftZeroFill(currentYear % 100, 2); 251 | case 'YYYY' : 252 | return currentYear; 253 | // AM / PM 254 | case 'a' : 255 | return meridiem ? meridiem(currentHours, currentMinutes, false) : (currentHours > 11 ? 'pm' : 'am'); 256 | case 'A' : 257 | return meridiem ? meridiem(currentHours, currentMinutes, true) : (currentHours > 11 ? 'PM' : 'AM'); 258 | // 24 HOUR 259 | case 'H' : 260 | return currentHours; 261 | case 'HH' : 262 | return leftZeroFill(currentHours, 2); 263 | // 12 HOUR 264 | case 'h' : 265 | return currentHours % 12 || 12; 266 | case 'hh' : 267 | return leftZeroFill(currentHours % 12 || 12, 2); 268 | // MINUTE 269 | case 'm' : 270 | return currentMinutes; 271 | case 'mm' : 272 | return leftZeroFill(currentMinutes, 2); 273 | // SECOND 274 | case 's' : 275 | return currentSeconds; 276 | case 'ss' : 277 | return leftZeroFill(currentSeconds, 2); 278 | // MILLISECONDS 279 | case 'S' : 280 | return ~~ (currentMilliseconds / 100); 281 | case 'SS' : 282 | return leftZeroFill(~~(currentMilliseconds / 10), 2); 283 | case 'SSS' : 284 | return leftZeroFill(currentMilliseconds, 3); 285 | // TIMEZONE 286 | case 'Z' : 287 | return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(Math.abs(currentZone) / 60), 2) + ':' + leftZeroFill(~~(Math.abs(currentZone) % 60), 2); 288 | case 'ZZ' : 289 | return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(10 * Math.abs(currentZone) / 6), 4); 290 | // LONG DATES 291 | case 'L' : 292 | case 'LL' : 293 | case 'LLL' : 294 | case 'LLLL' : 295 | case 'LT' : 296 | return formatMoment(m, moment.longDateFormat[input]); 297 | // DEFAULT 298 | default : 299 | return input.replace(/(^\[)|(\\)|\]$/g, ""); 300 | } 301 | } 302 | return inputString.replace(formattingTokens, replaceFunction); 303 | } 304 | 305 | // get the regex to find the next token 306 | function getParseRegexForToken(token) { 307 | switch (token) { 308 | case 'DDDD': 309 | return parseTokenThreeDigits; 310 | case 'YYYY': 311 | return parseTokenFourDigits; 312 | case 'S': 313 | case 'SS': 314 | case 'SSS': 315 | case 'DDD': 316 | return parseTokenOneToThreeDigits; 317 | case 'MMM': 318 | case 'MMMM': 319 | case 'ddd': 320 | case 'dddd': 321 | case 'a': 322 | case 'A': 323 | return parseTokenWord; 324 | case 'Z': 325 | case 'ZZ': 326 | return parseTokenTimezone; 327 | case 'T': 328 | return parseTokenT; 329 | case 'MM': 330 | case 'DD': 331 | case 'dd': 332 | case 'YY': 333 | case 'HH': 334 | case 'hh': 335 | case 'mm': 336 | case 'ss': 337 | case 'M': 338 | case 'D': 339 | case 'd': 340 | case 'H': 341 | case 'h': 342 | case 'm': 343 | case 's': 344 | return parseTokenOneOrTwoDigits; 345 | default : 346 | return new RegExp(token.replace('\\', '')); 347 | } 348 | } 349 | 350 | // function to convert string input to date 351 | function addTimeToArrayFromToken(token, input, datePartArray, config) { 352 | var a; 353 | //console.log('addTime', format, input); 354 | switch (token) { 355 | // MONTH 356 | case 'M' : // fall through to MM 357 | case 'MM' : 358 | datePartArray[1] = (input == null) ? 0 : ~~input - 1; 359 | break; 360 | case 'MMM' : // fall through to MMMM 361 | case 'MMMM' : 362 | for (a = 0; a < 12; a++) { 363 | if (moment.monthsParse[a].test(input)) { 364 | datePartArray[1] = a; 365 | break; 366 | } 367 | } 368 | break; 369 | // DAY OF MONTH 370 | case 'D' : // fall through to DDDD 371 | case 'DD' : // fall through to DDDD 372 | case 'DDD' : // fall through to DDDD 373 | case 'DDDD' : 374 | datePartArray[2] = ~~input; 375 | break; 376 | // YEAR 377 | case 'YY' : 378 | input = ~~input; 379 | datePartArray[0] = input + (input > 70 ? 1900 : 2000); 380 | break; 381 | case 'YYYY' : 382 | datePartArray[0] = ~~Math.abs(input); 383 | break; 384 | // AM / PM 385 | case 'a' : // fall through to A 386 | case 'A' : 387 | config.isPm = ((input + '').toLowerCase() === 'pm'); 388 | break; 389 | // 24 HOUR 390 | case 'H' : // fall through to hh 391 | case 'HH' : // fall through to hh 392 | case 'h' : // fall through to hh 393 | case 'hh' : 394 | datePartArray[3] = ~~input; 395 | break; 396 | // MINUTE 397 | case 'm' : // fall through to mm 398 | case 'mm' : 399 | datePartArray[4] = ~~input; 400 | break; 401 | // SECOND 402 | case 's' : // fall through to ss 403 | case 'ss' : 404 | datePartArray[5] = ~~input; 405 | break; 406 | // MILLISECOND 407 | case 'S' : 408 | case 'SS' : 409 | case 'SSS' : 410 | datePartArray[6] = ~~ (('0.' + input) * 1000); 411 | break; 412 | // TIMEZONE 413 | case 'Z' : // fall through to ZZ 414 | case 'ZZ' : 415 | config.isUTC = true; 416 | a = (input + '').match(parseTimezoneChunker); 417 | if (a && a[1]) { 418 | config.tzh = ~~a[1]; 419 | } 420 | if (a && a[2]) { 421 | config.tzm = ~~a[2]; 422 | } 423 | // reverse offsets 424 | if (a && a[0] === '+') { 425 | config.tzh = -config.tzh; 426 | config.tzm = -config.tzm; 427 | } 428 | break; 429 | } 430 | } 431 | 432 | // date from string and format string 433 | function makeDateFromStringAndFormat(string, format) { 434 | var datePartArray = [0, 0, 1, 0, 0, 0, 0], 435 | config = { 436 | tzh : 0, // timezone hour offset 437 | tzm : 0 // timezone minute offset 438 | }, 439 | tokens = format.match(formattingTokens), 440 | i, parsedInput; 441 | 442 | for (i = 0; i < tokens.length; i++) { 443 | parsedInput = (getParseRegexForToken(tokens[i]).exec(string) || [])[0]; 444 | string = string.replace(getParseRegexForToken(tokens[i]), ''); 445 | addTimeToArrayFromToken(tokens[i], parsedInput, datePartArray, config); 446 | } 447 | // handle am pm 448 | if (config.isPm && datePartArray[3] < 12) { 449 | datePartArray[3] += 12; 450 | } 451 | // if is 12 am, change hours to 0 452 | if (config.isPm === false && datePartArray[3] === 12) { 453 | datePartArray[3] = 0; 454 | } 455 | // handle timezone 456 | datePartArray[3] += config.tzh; 457 | datePartArray[4] += config.tzm; 458 | // return 459 | return config.isUTC ? new Date(Date.UTC.apply({}, datePartArray)) : dateFromArray(datePartArray); 460 | } 461 | 462 | // compare two arrays, return the number of differences 463 | function compareArrays(array1, array2) { 464 | var len = Math.min(array1.length, array2.length), 465 | lengthDiff = Math.abs(array1.length - array2.length), 466 | diffs = 0, 467 | i; 468 | for (i = 0; i < len; i++) { 469 | if (~~array1[i] !== ~~array2[i]) { 470 | diffs++; 471 | } 472 | } 473 | return diffs + lengthDiff; 474 | } 475 | 476 | // date from string and array of format strings 477 | function makeDateFromStringAndArray(string, formats) { 478 | var output, 479 | inputParts = string.match(parseMultipleFormatChunker) || [], 480 | formattedInputParts, 481 | scoreToBeat = 99, 482 | i, 483 | currentDate, 484 | currentScore; 485 | for (i = 0; i < formats.length; i++) { 486 | currentDate = makeDateFromStringAndFormat(string, formats[i]); 487 | formattedInputParts = formatMoment(new Moment(currentDate), formats[i]).match(parseMultipleFormatChunker) || []; 488 | currentScore = compareArrays(inputParts, formattedInputParts); 489 | if (currentScore < scoreToBeat) { 490 | scoreToBeat = currentScore; 491 | output = currentDate; 492 | } 493 | } 494 | return output; 495 | } 496 | 497 | // date from iso format 498 | function makeDateFromString(string) { 499 | var format = 'YYYY-MM-DDT', 500 | i; 501 | if (isoRegex.exec(string)) { 502 | for (i = 0; i < 4; i++) { 503 | if (isoTimes[i][1].exec(string)) { 504 | format += isoTimes[i][0]; 505 | break; 506 | } 507 | } 508 | return parseTokenTimezone.exec(string) ? 509 | makeDateFromStringAndFormat(string, format + ' Z') : 510 | makeDateFromStringAndFormat(string, format); 511 | } 512 | return new Date(string); 513 | } 514 | 515 | // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize 516 | function substituteTimeAgo(string, number, withoutSuffix, isFuture) { 517 | var rt = moment.relativeTime[string]; 518 | return (typeof rt === 'function') ? 519 | rt(number || 1, !!withoutSuffix, string, isFuture) : 520 | rt.replace(/%d/i, number || 1); 521 | } 522 | 523 | function relativeTime(milliseconds, withoutSuffix) { 524 | var seconds = round(Math.abs(milliseconds) / 1000), 525 | minutes = round(seconds / 60), 526 | hours = round(minutes / 60), 527 | days = round(hours / 24), 528 | years = round(days / 365), 529 | args = seconds < 45 && ['s', seconds] || 530 | minutes === 1 && ['m'] || 531 | minutes < 45 && ['mm', minutes] || 532 | hours === 1 && ['h'] || 533 | hours < 22 && ['hh', hours] || 534 | days === 1 && ['d'] || 535 | days <= 25 && ['dd', days] || 536 | days <= 45 && ['M'] || 537 | days < 345 && ['MM', round(days / 30)] || 538 | years === 1 && ['y'] || ['yy', years]; 539 | args[2] = withoutSuffix; 540 | args[3] = milliseconds > 0; 541 | return substituteTimeAgo.apply({}, args); 542 | } 543 | 544 | moment = function (input, format) { 545 | if (input === null || input === '') { 546 | return null; 547 | } 548 | var date, 549 | matched, 550 | isUTC; 551 | // parse Moment object 552 | if (moment.isMoment(input)) { 553 | date = new Date(+input._d); 554 | isUTC = input._isUTC; 555 | // parse string and format 556 | } else if (format) { 557 | if (isArray(format)) { 558 | date = makeDateFromStringAndArray(input, format); 559 | } else { 560 | date = makeDateFromStringAndFormat(input, format); 561 | } 562 | // evaluate it as a JSON-encoded date 563 | } else { 564 | matched = aspNetJsonRegex.exec(input); 565 | date = input === undefined ? new Date() : 566 | matched ? new Date(+matched[1]) : 567 | input instanceof Date ? input : 568 | isArray(input) ? dateFromArray(input) : 569 | typeof input === 'string' ? makeDateFromString(input) : 570 | new Date(input); 571 | } 572 | return new Moment(date, isUTC); 573 | }; 574 | 575 | // creating with utc 576 | moment.utc = function (input, format) { 577 | if (isArray(input)) { 578 | return new Moment(new Date(Date.UTC.apply({}, input)), true); 579 | } 580 | return (format && input) ? 581 | moment(input + ' +0000', format + ' Z').utc() : 582 | moment(input && !parseTokenTimezone.exec(input) ? input + '+0000' : input).utc(); 583 | }; 584 | 585 | // creating with unix timestamp (in seconds) 586 | moment.unix = function (input) { 587 | return moment(input * 1000); 588 | }; 589 | 590 | // duration 591 | moment.duration = function (input, key) { 592 | var isDuration = moment.isDuration(input), 593 | isNumber = (typeof input === 'number'), 594 | duration = (isDuration ? input._data : (isNumber ? {} : input)); 595 | 596 | if (isNumber) { 597 | if (key) { 598 | duration[key] = input; 599 | } else { 600 | duration.milliseconds = input; 601 | } 602 | } 603 | 604 | return new Duration(duration); 605 | }; 606 | 607 | // humanizeDuration 608 | // This method is deprecated in favor of the new Duration object. Please 609 | // see the moment.duration method. 610 | moment.humanizeDuration = function (num, type, withSuffix) { 611 | return moment.duration(num, type === true ? null : type).humanize(type === true ? true : withSuffix); 612 | }; 613 | 614 | // version number 615 | moment.version = VERSION; 616 | 617 | // default format 618 | moment.defaultFormat = isoFormat; 619 | 620 | // language switching and caching 621 | moment.lang = function (key, values) { 622 | var i, req, 623 | parse = []; 624 | if (!key) { 625 | return currentLanguage; 626 | } 627 | if (values) { 628 | for (i = 0; i < 12; i++) { 629 | parse[i] = new RegExp('^' + values.months[i] + '|^' + values.monthsShort[i].replace('.', ''), 'i'); 630 | } 631 | values.monthsParse = values.monthsParse || parse; 632 | languages[key] = values; 633 | } 634 | if (languages[key]) { 635 | for (i = 0; i < langConfigProperties.length; i++) { 636 | moment[langConfigProperties[i]] = languages[key][langConfigProperties[i]] || 637 | languages.en[langConfigProperties[i]]; 638 | } 639 | currentLanguage = key; 640 | } else { 641 | if (hasModule) { 642 | req = require('./lang/' + key); 643 | moment.lang(key, req); 644 | } 645 | } 646 | }; 647 | 648 | // set default language 649 | moment.lang('en', { 650 | months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), 651 | monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), 652 | weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), 653 | weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), 654 | longDateFormat : { 655 | LT : "h:mm A", 656 | L : "MM/DD/YYYY", 657 | LL : "MMMM D YYYY", 658 | LLL : "MMMM D YYYY LT", 659 | LLLL : "dddd, MMMM D YYYY LT" 660 | }, 661 | meridiem : false, 662 | calendar : { 663 | sameDay : '[Today at] LT', 664 | nextDay : '[Tomorrow at] LT', 665 | nextWeek : 'dddd [at] LT', 666 | lastDay : '[Yesterday at] LT', 667 | lastWeek : '[last] dddd [at] LT', 668 | sameElse : 'L' 669 | }, 670 | relativeTime : { 671 | future : "in %s", 672 | past : "%s ago", 673 | s : "a few seconds", 674 | m : "a minute", 675 | mm : "%d minutes", 676 | h : "an hour", 677 | hh : "%d hours", 678 | d : "a day", 679 | dd : "%d days", 680 | M : "a month", 681 | MM : "%d months", 682 | y : "a year", 683 | yy : "%d years" 684 | }, 685 | ordinal : function (number) { 686 | var b = number % 10; 687 | return (~~ (number % 100 / 10) === 1) ? 'th' : 688 | (b === 1) ? 'st' : 689 | (b === 2) ? 'nd' : 690 | (b === 3) ? 'rd' : 'th'; 691 | } 692 | }); 693 | 694 | // compare moment object 695 | moment.isMoment = function (obj) { 696 | return obj instanceof Moment; 697 | }; 698 | 699 | // for typechecking Duration objects 700 | moment.isDuration = function (obj) { 701 | return obj instanceof Duration; 702 | }; 703 | 704 | // shortcut for prototype 705 | moment.fn = Moment.prototype = { 706 | 707 | clone : function () { 708 | return moment(this); 709 | }, 710 | 711 | valueOf : function () { 712 | return +this._d; 713 | }, 714 | 715 | unix : function () { 716 | return Math.floor(+this._d / 1000); 717 | }, 718 | 719 | toString : function () { 720 | return this._d.toString(); 721 | }, 722 | 723 | toDate : function () { 724 | return this._d; 725 | }, 726 | 727 | utc : function () { 728 | this._isUTC = true; 729 | return this; 730 | }, 731 | 732 | local : function () { 733 | this._isUTC = false; 734 | return this; 735 | }, 736 | 737 | format : function (inputString) { 738 | return formatMoment(this, inputString ? inputString : moment.defaultFormat); 739 | }, 740 | 741 | add : function (input, val) { 742 | var dur = val ? moment.duration(+val, input) : moment.duration(input); 743 | addOrSubtractDurationFromMoment(this, dur, 1); 744 | return this; 745 | }, 746 | 747 | subtract : function (input, val) { 748 | var dur = val ? moment.duration(+val, input) : moment.duration(input); 749 | addOrSubtractDurationFromMoment(this, dur, -1); 750 | return this; 751 | }, 752 | 753 | diff : function (input, val, asFloat) { 754 | var inputMoment = this._isUTC ? moment(input).utc() : moment(input).local(), 755 | zoneDiff = (this.zone() - inputMoment.zone()) * 6e4, 756 | diff = this._d - inputMoment._d - zoneDiff, 757 | year = this.year() - inputMoment.year(), 758 | month = this.month() - inputMoment.month(), 759 | date = this.date() - inputMoment.date(), 760 | output; 761 | if (val === 'months') { 762 | output = year * 12 + month + date / 30; 763 | } else if (val === 'years') { 764 | output = year + (month + date / 30) / 12; 765 | } else { 766 | output = val === 'seconds' ? diff / 1e3 : // 1000 767 | val === 'minutes' ? diff / 6e4 : // 1000 * 60 768 | val === 'hours' ? diff / 36e5 : // 1000 * 60 * 60 769 | val === 'days' ? diff / 864e5 : // 1000 * 60 * 60 * 24 770 | val === 'weeks' ? diff / 6048e5 : // 1000 * 60 * 60 * 24 * 7 771 | diff; 772 | } 773 | return asFloat ? output : round(output); 774 | }, 775 | 776 | from : function (time, withoutSuffix) { 777 | return moment.duration(this.diff(time)).humanize(!withoutSuffix); 778 | }, 779 | 780 | fromNow : function (withoutSuffix) { 781 | return this.from(moment(), withoutSuffix); 782 | }, 783 | 784 | calendar : function () { 785 | var diff = this.diff(moment().sod(), 'days', true), 786 | calendar = moment.calendar, 787 | allElse = calendar.sameElse, 788 | format = diff < -6 ? allElse : 789 | diff < -1 ? calendar.lastWeek : 790 | diff < 0 ? calendar.lastDay : 791 | diff < 1 ? calendar.sameDay : 792 | diff < 2 ? calendar.nextDay : 793 | diff < 7 ? calendar.nextWeek : allElse; 794 | return this.format(typeof format === 'function' ? format.apply(this) : format); 795 | }, 796 | 797 | isLeapYear : function () { 798 | var year = this.year(); 799 | return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; 800 | }, 801 | 802 | isDST : function () { 803 | return (this.zone() < moment([this.year()]).zone() || 804 | this.zone() < moment([this.year(), 5]).zone()); 805 | }, 806 | 807 | day : function (input) { 808 | var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); 809 | return input == null ? day : 810 | this.add({ d : input - day }); 811 | }, 812 | 813 | sod: function () { 814 | return moment(this) 815 | .hours(0) 816 | .minutes(0) 817 | .seconds(0) 818 | .milliseconds(0); 819 | }, 820 | 821 | eod: function () { 822 | // end of day = start of day plus 1 day, minus 1 millisecond 823 | return this.sod().add({ 824 | d : 1, 825 | ms : -1 826 | }); 827 | }, 828 | 829 | zone : function () { 830 | return this._isUTC ? 0 : this._d.getTimezoneOffset(); 831 | }, 832 | 833 | daysInMonth : function () { 834 | return moment(this).month(this.month() + 1).date(0).date(); 835 | } 836 | }; 837 | 838 | // helper for adding shortcuts 839 | function makeGetterAndSetter(name, key) { 840 | moment.fn[name] = function (input) { 841 | var utc = this._isUTC ? 'UTC' : ''; 842 | if (input != null) { 843 | this._d['set' + utc + key](input); 844 | return this; 845 | } else { 846 | return this._d['get' + utc + key](); 847 | } 848 | }; 849 | } 850 | 851 | // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds) 852 | for (i = 0; i < proxyGettersAndSetters.length; i ++) { 853 | makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase(), proxyGettersAndSetters[i]); 854 | } 855 | 856 | // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') 857 | makeGetterAndSetter('year', 'FullYear'); 858 | 859 | moment.duration.fn = Duration.prototype = { 860 | weeks : function () { 861 | return absRound(this.days() / 7); 862 | }, 863 | 864 | valueOf : function () { 865 | return this._milliseconds + 866 | this._days * 864e5 + 867 | this._months * 2592e6; 868 | }, 869 | 870 | humanize : function (withSuffix) { 871 | var difference = +this, 872 | rel = moment.relativeTime, 873 | output = relativeTime(difference, !withSuffix); 874 | 875 | if (withSuffix) { 876 | output = (difference <= 0 ? rel.past : rel.future).replace(/%s/i, output); 877 | } 878 | 879 | return output; 880 | } 881 | }; 882 | 883 | function makeDurationGetter(name) { 884 | moment.duration.fn[name] = function () { 885 | return this._data[name]; 886 | }; 887 | } 888 | 889 | function makeDurationAsGetter(name, factor) { 890 | moment.duration.fn['as' + name] = function () { 891 | return +this / factor; 892 | }; 893 | } 894 | 895 | for (i in unitMillisecondFactors) { 896 | if (unitMillisecondFactors.hasOwnProperty(i)) { 897 | makeDurationAsGetter(i, unitMillisecondFactors[i]); 898 | makeDurationGetter(i.toLowerCase()); 899 | } 900 | } 901 | 902 | makeDurationAsGetter('Weeks', 6048e5); 903 | 904 | // CommonJS module is defined 905 | if (hasModule) { 906 | module.exports = moment; 907 | } 908 | /*global ender:false */ 909 | if (typeof window !== 'undefined' && typeof ender === 'undefined') { 910 | window.moment = moment; 911 | } 912 | /*global define:false */ 913 | if (typeof define === "function" && define.amd) { 914 | define("moment", [], function () { 915 | return moment; 916 | }); 917 | } 918 | })(Date); 919 | -------------------------------------------------------------------------------- /app/tests/mock_response_data/repos/pangratz/ember.js/events?page=9.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "created_at": "2012-05-02T08:02:38Z", 4 | "type": "DownloadEvent", 5 | "actor": { 6 | "id": 341877, 7 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 8 | "url": "https://api.github.com/users/pangratz", 9 | "login": "pangratz", 10 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 11 | }, 12 | "payload": { 13 | "download": { 14 | "content_type": "application/json", 15 | "created_at": "2012-05-02T08:02:38Z", 16 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 17 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230861", 18 | "id": 230861, 19 | "download_count": 0, 20 | "name": "ember-latest.js", 21 | "description": "Ember.js Master", 22 | "size": 581632 23 | } 24 | }, 25 | "public": true, 26 | "repo": { 27 | "id": 2997378, 28 | "url": "https://api.github.com/repos/pangratz/ember.js", 29 | "name": "pangratz/ember.js" 30 | }, 31 | "id": "1547862317" 32 | }, 33 | { 34 | "created_at": "2012-05-02T07:58:42Z", 35 | "type": "PushEvent", 36 | "actor": { 37 | "id": 341877, 38 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 39 | "url": "https://api.github.com/users/pangratz", 40 | "login": "pangratz", 41 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 42 | }, 43 | "payload": { 44 | "commits": [ 45 | { 46 | "distinct": true, 47 | "author": { 48 | "email": "cmueller.418@gmail.com", 49 | "name": "pangratz" 50 | }, 51 | "url": "https://api.github.com/repos/pangratz/ember.js/commits/b8d3cc5d931283eabcca1c580ebbe85e9b59bedf", 52 | "message": "Another test", 53 | "sha": "b8d3cc5d931283eabcca1c580ebbe85e9b59bedf" 54 | } 55 | ], 56 | "push_id": 76024626, 57 | "ref": "refs/heads/add_task_to_upload_latest_build", 58 | "head": "b8d3cc5d931283eabcca1c580ebbe85e9b59bedf", 59 | "size": 1 60 | }, 61 | "public": true, 62 | "repo": { 63 | "id": 2997378, 64 | "url": "https://api.github.com/repos/pangratz/ember.js", 65 | "name": "pangratz/ember.js" 66 | }, 67 | "id": "1547861382" 68 | }, 69 | { 70 | "created_at": "2012-05-02T07:56:46Z", 71 | "type": "DownloadEvent", 72 | "actor": { 73 | "id": 341877, 74 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 75 | "url": "https://api.github.com/users/pangratz", 76 | "login": "pangratz", 77 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 78 | }, 79 | "payload": { 80 | "download": { 81 | "content_type": "application/json", 82 | "created_at": "2012-05-02T07:56:45Z", 83 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.min.js", 84 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230859", 85 | "id": 230859, 86 | "download_count": 0, 87 | "name": "ember-latest.min.js", 88 | "description": "Ember.js Master (minified)", 89 | "size": 151552 90 | } 91 | }, 92 | "public": true, 93 | "repo": { 94 | "id": 2997378, 95 | "url": "https://api.github.com/repos/pangratz/ember.js", 96 | "name": "pangratz/ember.js" 97 | }, 98 | "id": "1547860860" 99 | }, 100 | { 101 | "created_at": "2012-05-02T07:56:43Z", 102 | "type": "DownloadEvent", 103 | "actor": { 104 | "id": 341877, 105 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 106 | "url": "https://api.github.com/users/pangratz", 107 | "login": "pangratz", 108 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 109 | }, 110 | "payload": { 111 | "download": { 112 | "content_type": "application/json", 113 | "created_at": "2012-05-02T07:56:43Z", 114 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 115 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230858", 116 | "id": 230858, 117 | "download_count": 0, 118 | "name": "ember-latest.js", 119 | "description": "Ember.js Master", 120 | "size": 581632 121 | } 122 | }, 123 | "public": true, 124 | "repo": { 125 | "id": 2997378, 126 | "url": "https://api.github.com/repos/pangratz/ember.js", 127 | "name": "pangratz/ember.js" 128 | }, 129 | "id": "1547860854" 130 | }, 131 | { 132 | "created_at": "2012-05-02T07:55:14Z", 133 | "type": "DownloadEvent", 134 | "actor": { 135 | "id": 341877, 136 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 137 | "url": "https://api.github.com/users/pangratz", 138 | "login": "pangratz", 139 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 140 | }, 141 | "payload": { 142 | "download": { 143 | "content_type": "application/json", 144 | "created_at": "2012-05-02T07:55:11Z", 145 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.min.js", 146 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230855", 147 | "id": 230855, 148 | "download_count": 0, 149 | "name": "ember-latest.min.js", 150 | "description": "Ember.js Master (minified)", 151 | "size": 151552 152 | } 153 | }, 154 | "public": true, 155 | "repo": { 156 | "id": 2997378, 157 | "url": "https://api.github.com/repos/pangratz/ember.js", 158 | "name": "pangratz/ember.js" 159 | }, 160 | "id": "1547860556" 161 | }, 162 | { 163 | "created_at": "2012-05-02T07:55:10Z", 164 | "type": "DownloadEvent", 165 | "actor": { 166 | "id": 341877, 167 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 168 | "url": "https://api.github.com/users/pangratz", 169 | "login": "pangratz", 170 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 171 | }, 172 | "payload": { 173 | "download": { 174 | "content_type": "application/json", 175 | "created_at": "2012-05-02T07:55:10Z", 176 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 177 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230854", 178 | "id": 230854, 179 | "download_count": 0, 180 | "name": "ember-latest.js", 181 | "description": "Ember.js Master", 182 | "size": 581632 183 | } 184 | }, 185 | "public": true, 186 | "repo": { 187 | "id": 2997378, 188 | "url": "https://api.github.com/repos/pangratz/ember.js", 189 | "name": "pangratz/ember.js" 190 | }, 191 | "id": "1547860537" 192 | }, 193 | { 194 | "created_at": "2012-05-02T07:52:26Z", 195 | "type": "PushEvent", 196 | "actor": { 197 | "id": 341877, 198 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 199 | "url": "https://api.github.com/users/pangratz", 200 | "login": "pangratz", 201 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 202 | }, 203 | "payload": { 204 | "commits": [ 205 | { 206 | "distinct": true, 207 | "author": { 208 | "email": "cmueller.418@gmail.com", 209 | "name": "pangratz" 210 | }, 211 | "url": "https://api.github.com/repos/pangratz/ember.js/commits/645a7bcff6a580fa477f2d221a6fccf3591267bb", 212 | "message": "Fix", 213 | "sha": "645a7bcff6a580fa477f2d221a6fccf3591267bb" 214 | } 215 | ], 216 | "push_id": 76023896, 217 | "ref": "refs/heads/add_task_to_upload_latest_build", 218 | "head": "645a7bcff6a580fa477f2d221a6fccf3591267bb", 219 | "size": 1 220 | }, 221 | "public": true, 222 | "repo": { 223 | "id": 2997378, 224 | "url": "https://api.github.com/repos/pangratz/ember.js", 225 | "name": "pangratz/ember.js" 226 | }, 227 | "id": "1547859907" 228 | }, 229 | { 230 | "created_at": "2012-05-02T07:47:50Z", 231 | "type": "PushEvent", 232 | "actor": { 233 | "id": 341877, 234 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 235 | "url": "https://api.github.com/users/pangratz", 236 | "login": "pangratz", 237 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 238 | }, 239 | "payload": { 240 | "commits": [ 241 | { 242 | "distinct": true, 243 | "author": { 244 | "email": "cmueller.418@gmail.com", 245 | "name": "pangratz" 246 | }, 247 | "url": "https://api.github.com/repos/pangratz/ember.js/commits/c1441189d985533f6c405e902f51058c43537291", 248 | "message": "Another test", 249 | "sha": "c1441189d985533f6c405e902f51058c43537291" 250 | } 251 | ], 252 | "push_id": 76023363, 253 | "ref": "refs/heads/add_task_to_upload_latest_build", 254 | "head": "c1441189d985533f6c405e902f51058c43537291", 255 | "size": 1 256 | }, 257 | "public": true, 258 | "repo": { 259 | "id": 2997378, 260 | "url": "https://api.github.com/repos/pangratz/ember.js", 261 | "name": "pangratz/ember.js" 262 | }, 263 | "id": "1547859000" 264 | }, 265 | { 266 | "created_at": "2012-05-02T07:44:27Z", 267 | "type": "CreateEvent", 268 | "actor": { 269 | "id": 341877, 270 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 271 | "url": "https://api.github.com/users/pangratz", 272 | "login": "pangratz", 273 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 274 | }, 275 | "payload": { 276 | "master_branch": "master", 277 | "ref": "upload_task_test", 278 | "ref_type": "branch", 279 | "description": "Ember.js - formerly known as SproutCore 2.0" 280 | }, 281 | "public": true, 282 | "repo": { 283 | "id": 2997378, 284 | "url": "https://api.github.com/repos/pangratz/ember.js", 285 | "name": "pangratz/ember.js" 286 | }, 287 | "id": "1547858272" 288 | }, 289 | { 290 | "created_at": "2012-05-02T07:42:58Z", 291 | "type": "DownloadEvent", 292 | "actor": { 293 | "id": 341877, 294 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 295 | "url": "https://api.github.com/users/pangratz", 296 | "login": "pangratz", 297 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 298 | }, 299 | "payload": { 300 | "download": { 301 | "content_type": "application/json", 302 | "created_at": "2012-05-02T07:42:57Z", 303 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.min.js", 304 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230844", 305 | "id": 230844, 306 | "download_count": 0, 307 | "name": "ember-latest.min.js", 308 | "description": "Ember.js Master (minified)", 309 | "size": 151552 310 | } 311 | }, 312 | "public": true, 313 | "repo": { 314 | "id": 2997378, 315 | "url": "https://api.github.com/repos/pangratz/ember.js", 316 | "name": "pangratz/ember.js" 317 | }, 318 | "id": "1547857922" 319 | }, 320 | { 321 | "created_at": "2012-05-02T07:42:55Z", 322 | "type": "DownloadEvent", 323 | "actor": { 324 | "id": 341877, 325 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 326 | "url": "https://api.github.com/users/pangratz", 327 | "login": "pangratz", 328 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 329 | }, 330 | "payload": { 331 | "download": { 332 | "content_type": "application/json", 333 | "created_at": "2012-05-02T07:42:54Z", 334 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 335 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230843", 336 | "id": 230843, 337 | "download_count": 0, 338 | "name": "ember-latest.js", 339 | "description": "Ember.js Master", 340 | "size": 581632 341 | } 342 | }, 343 | "public": true, 344 | "repo": { 345 | "id": 2997378, 346 | "url": "https://api.github.com/repos/pangratz/ember.js", 347 | "name": "pangratz/ember.js" 348 | }, 349 | "id": "1547857912" 350 | }, 351 | { 352 | "created_at": "2012-05-02T07:40:52Z", 353 | "type": "DownloadEvent", 354 | "actor": { 355 | "id": 341877, 356 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 357 | "url": "https://api.github.com/users/pangratz", 358 | "login": "pangratz", 359 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 360 | }, 361 | "payload": { 362 | "download": { 363 | "content_type": "application/json", 364 | "created_at": "2012-05-02T07:40:50Z", 365 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.min.js", 366 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230842", 367 | "id": 230842, 368 | "download_count": 0, 369 | "name": "ember-latest.min.js", 370 | "description": "Ember.js Master (minified)", 371 | "size": 151552 372 | } 373 | }, 374 | "public": true, 375 | "repo": { 376 | "id": 2997378, 377 | "url": "https://api.github.com/repos/pangratz/ember.js", 378 | "name": "pangratz/ember.js" 379 | }, 380 | "id": "1547857433" 381 | }, 382 | { 383 | "created_at": "2012-05-02T07:40:48Z", 384 | "type": "DownloadEvent", 385 | "actor": { 386 | "id": 341877, 387 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 388 | "url": "https://api.github.com/users/pangratz", 389 | "login": "pangratz", 390 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 391 | }, 392 | "payload": { 393 | "download": { 394 | "content_type": "application/json", 395 | "created_at": "2012-05-02T07:40:48Z", 396 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 397 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230841", 398 | "id": 230841, 399 | "download_count": 0, 400 | "name": "ember-latest.js", 401 | "description": "Ember.js Master", 402 | "size": 581632 403 | } 404 | }, 405 | "public": true, 406 | "repo": { 407 | "id": 2997378, 408 | "url": "https://api.github.com/repos/pangratz/ember.js", 409 | "name": "pangratz/ember.js" 410 | }, 411 | "id": "1547857429" 412 | }, 413 | { 414 | "created_at": "2012-05-02T07:38:41Z", 415 | "type": "DownloadEvent", 416 | "actor": { 417 | "id": 341877, 418 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 419 | "url": "https://api.github.com/users/pangratz", 420 | "login": "pangratz", 421 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 422 | }, 423 | "payload": { 424 | "download": { 425 | "content_type": "application/json", 426 | "created_at": "2012-05-02T07:38:41Z", 427 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.min.js", 428 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230840", 429 | "id": 230840, 430 | "download_count": 0, 431 | "name": "ember-latest.min.js", 432 | "description": "Ember.js Master (minified)", 433 | "size": 151552 434 | } 435 | }, 436 | "public": true, 437 | "repo": { 438 | "id": 2997378, 439 | "url": "https://api.github.com/repos/pangratz/ember.js", 440 | "name": "pangratz/ember.js" 441 | }, 442 | "id": "1547857000" 443 | }, 444 | { 445 | "created_at": "2012-05-02T07:38:40Z", 446 | "type": "DownloadEvent", 447 | "actor": { 448 | "id": 341877, 449 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 450 | "url": "https://api.github.com/users/pangratz", 451 | "login": "pangratz", 452 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 453 | }, 454 | "payload": { 455 | "download": { 456 | "content_type": "application/json", 457 | "created_at": "2012-05-02T07:38:39Z", 458 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 459 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230839", 460 | "id": 230839, 461 | "download_count": 0, 462 | "name": "ember-latest.js", 463 | "description": "Ember.js Master", 464 | "size": 581632 465 | } 466 | }, 467 | "public": true, 468 | "repo": { 469 | "id": 2997378, 470 | "url": "https://api.github.com/repos/pangratz/ember.js", 471 | "name": "pangratz/ember.js" 472 | }, 473 | "id": "1547856996" 474 | }, 475 | { 476 | "created_at": "2012-05-02T07:38:14Z", 477 | "type": "DownloadEvent", 478 | "actor": { 479 | "id": 341877, 480 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 481 | "url": "https://api.github.com/users/pangratz", 482 | "login": "pangratz", 483 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 484 | }, 485 | "payload": { 486 | "download": { 487 | "content_type": "application/json", 488 | "created_at": "2012-05-02T07:38:13Z", 489 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.min.js", 490 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230838", 491 | "id": 230838, 492 | "name": "ember-latest.min.js", 493 | "download_count": 0, 494 | "description": "Ember.js Master (minified)", 495 | "size": 151552 496 | } 497 | }, 498 | "public": true, 499 | "repo": { 500 | "id": 2997378, 501 | "url": "https://api.github.com/repos/pangratz/ember.js", 502 | "name": "pangratz/ember.js" 503 | }, 504 | "id": "1547856899" 505 | }, 506 | { 507 | "created_at": "2012-05-02T07:38:11Z", 508 | "type": "DownloadEvent", 509 | "actor": { 510 | "id": 341877, 511 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 512 | "url": "https://api.github.com/users/pangratz", 513 | "login": "pangratz", 514 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 515 | }, 516 | "payload": { 517 | "download": { 518 | "content_type": "application/json", 519 | "created_at": "2012-05-02T07:38:11Z", 520 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 521 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230837", 522 | "id": 230837, 523 | "download_count": 0, 524 | "name": "ember-latest.js", 525 | "description": "Ember.js Master", 526 | "size": 581632 527 | } 528 | }, 529 | "public": true, 530 | "repo": { 531 | "id": 2997378, 532 | "url": "https://api.github.com/repos/pangratz/ember.js", 533 | "name": "pangratz/ember.js" 534 | }, 535 | "id": "1547856892" 536 | }, 537 | { 538 | "created_at": "2012-05-02T07:38:10Z", 539 | "type": "PushEvent", 540 | "actor": { 541 | "id": 341877, 542 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 543 | "url": "https://api.github.com/users/pangratz", 544 | "login": "pangratz", 545 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 546 | }, 547 | "payload": { 548 | "commits": [ 549 | { 550 | "distinct": true, 551 | "author": { 552 | "email": "cmueller.418@gmail.com", 553 | "name": "pangratz" 554 | }, 555 | "url": "https://api.github.com/repos/pangratz/ember.js/commits/ec6b08d0fa57f6eb4a3570338c069d000e4dddb8", 556 | "message": "Modify branch output", 557 | "sha": "ec6b08d0fa57f6eb4a3570338c069d000e4dddb8" 558 | } 559 | ], 560 | "push_id": 76022217, 561 | "ref": "refs/heads/add_task_to_upload_latest_build", 562 | "head": "ec6b08d0fa57f6eb4a3570338c069d000e4dddb8", 563 | "size": 1 564 | }, 565 | "public": true, 566 | "repo": { 567 | "id": 2997378, 568 | "url": "https://api.github.com/repos/pangratz/ember.js", 569 | "name": "pangratz/ember.js" 570 | }, 571 | "id": "1547856888" 572 | }, 573 | { 574 | "created_at": "2012-05-02T07:37:14Z", 575 | "type": "DownloadEvent", 576 | "actor": { 577 | "id": 341877, 578 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 579 | "url": "https://api.github.com/users/pangratz", 580 | "login": "pangratz", 581 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 582 | }, 583 | "payload": { 584 | "download": { 585 | "content_type": "application/json", 586 | "created_at": "2012-05-02T07:37:14Z", 587 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.min.js", 588 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230836", 589 | "id": 230836, 590 | "download_count": 0, 591 | "name": "ember-latest.min.js", 592 | "description": "Ember.js Master (minified)", 593 | "size": 151552 594 | } 595 | }, 596 | "public": true, 597 | "repo": { 598 | "id": 2997378, 599 | "url": "https://api.github.com/repos/pangratz/ember.js", 600 | "name": "pangratz/ember.js" 601 | }, 602 | "id": "1547856674" 603 | }, 604 | { 605 | "created_at": "2012-05-02T07:37:12Z", 606 | "type": "DownloadEvent", 607 | "actor": { 608 | "id": 341877, 609 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 610 | "url": "https://api.github.com/users/pangratz", 611 | "login": "pangratz", 612 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 613 | }, 614 | "payload": { 615 | "download": { 616 | "content_type": "application/json", 617 | "created_at": "2012-05-02T07:37:12Z", 618 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 619 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230835", 620 | "id": 230835, 621 | "download_count": 0, 622 | "name": "ember-latest.js", 623 | "description": "Ember.js Master", 624 | "size": 581632 625 | } 626 | }, 627 | "public": true, 628 | "repo": { 629 | "id": 2997378, 630 | "url": "https://api.github.com/repos/pangratz/ember.js", 631 | "name": "pangratz/ember.js" 632 | }, 633 | "id": "1547856665" 634 | }, 635 | { 636 | "created_at": "2012-05-02T07:34:35Z", 637 | "type": "PushEvent", 638 | "actor": { 639 | "id": 341877, 640 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 641 | "url": "https://api.github.com/users/pangratz", 642 | "login": "pangratz", 643 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 644 | }, 645 | "payload": { 646 | "commits": [ 647 | { 648 | "distinct": true, 649 | "author": { 650 | "email": "cmueller.418@gmail.com", 651 | "name": "pangratz" 652 | }, 653 | "url": "https://api.github.com/repos/pangratz/ember.js/commits/f405b03ccc2e07a83b88b17a94c6eb70b641782e", 654 | "message": "Show current branch", 655 | "sha": "f405b03ccc2e07a83b88b17a94c6eb70b641782e" 656 | } 657 | ], 658 | "push_id": 76021781, 659 | "ref": "refs/heads/add_task_to_upload_latest_build", 660 | "head": "f405b03ccc2e07a83b88b17a94c6eb70b641782e", 661 | "size": 1 662 | }, 663 | "public": true, 664 | "repo": { 665 | "id": 2997378, 666 | "url": "https://api.github.com/repos/pangratz/ember.js", 667 | "name": "pangratz/ember.js" 668 | }, 669 | "id": "1547855998" 670 | }, 671 | { 672 | "created_at": "2012-05-02T07:24:38Z", 673 | "type": "DownloadEvent", 674 | "actor": { 675 | "id": 341877, 676 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 677 | "url": "https://api.github.com/users/pangratz", 678 | "login": "pangratz", 679 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 680 | }, 681 | "payload": { 682 | "download": { 683 | "content_type": "application/json", 684 | "created_at": "2012-05-02T07:24:38Z", 685 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.min.js", 686 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230831", 687 | "id": 230831, 688 | "download_count": 0, 689 | "name": "ember-latest.min.js", 690 | "description": "Ember.js Master (minified)", 691 | "size": 151552 692 | } 693 | }, 694 | "public": true, 695 | "repo": { 696 | "id": 2997378, 697 | "url": "https://api.github.com/repos/pangratz/ember.js", 698 | "name": "pangratz/ember.js" 699 | }, 700 | "id": "1547853962" 701 | }, 702 | { 703 | "created_at": "2012-05-02T07:24:35Z", 704 | "type": "DownloadEvent", 705 | "actor": { 706 | "id": 341877, 707 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 708 | "url": "https://api.github.com/users/pangratz", 709 | "login": "pangratz", 710 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 711 | }, 712 | "payload": { 713 | "download": { 714 | "content_type": "application/json", 715 | "created_at": "2012-05-02T07:24:35Z", 716 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 717 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230830", 718 | "id": 230830, 719 | "download_count": 0, 720 | "name": "ember-latest.js", 721 | "description": "Ember.js Master", 722 | "size": 581632 723 | } 724 | }, 725 | "public": true, 726 | "repo": { 727 | "id": 2997378, 728 | "url": "https://api.github.com/repos/pangratz/ember.js", 729 | "name": "pangratz/ember.js" 730 | }, 731 | "id": "1547853953" 732 | }, 733 | { 734 | "created_at": "2012-05-02T07:23:51Z", 735 | "type": "DownloadEvent", 736 | "actor": { 737 | "id": 341877, 738 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 739 | "url": "https://api.github.com/users/pangratz", 740 | "login": "pangratz", 741 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 742 | }, 743 | "payload": { 744 | "download": { 745 | "content_type": "application/json", 746 | "created_at": "2012-05-02T07:23:51Z", 747 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.min.js", 748 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230828", 749 | "id": 230828, 750 | "download_count": 0, 751 | "name": "ember-latest.min.js", 752 | "description": "Ember.js Master (minified)", 753 | "size": 151552 754 | } 755 | }, 756 | "public": true, 757 | "repo": { 758 | "id": 2997378, 759 | "url": "https://api.github.com/repos/pangratz/ember.js", 760 | "name": "pangratz/ember.js" 761 | }, 762 | "id": "1547853671" 763 | }, 764 | { 765 | "created_at": "2012-05-02T07:23:51Z", 766 | "type": "DownloadEvent", 767 | "actor": { 768 | "id": 341877, 769 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 770 | "url": "https://api.github.com/users/pangratz", 771 | "login": "pangratz", 772 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 773 | }, 774 | "payload": { 775 | "download": { 776 | "content_type": "application/json", 777 | "created_at": "2012-05-02T07:23:50Z", 778 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 779 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230827", 780 | "id": 230827, 781 | "download_count": 0, 782 | "name": "ember-latest.js", 783 | "description": "Ember.js Master", 784 | "size": 581632 785 | } 786 | }, 787 | "public": true, 788 | "repo": { 789 | "id": 2997378, 790 | "url": "https://api.github.com/repos/pangratz/ember.js", 791 | "name": "pangratz/ember.js" 792 | }, 793 | "id": "1547853653" 794 | }, 795 | { 796 | "created_at": "2012-05-02T07:21:07Z", 797 | "type": "PushEvent", 798 | "actor": { 799 | "id": 341877, 800 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 801 | "url": "https://api.github.com/users/pangratz", 802 | "login": "pangratz", 803 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 804 | }, 805 | "payload": { 806 | "commits": [ 807 | { 808 | "distinct": true, 809 | "author": { 810 | "email": "cmueller.418@gmail.com", 811 | "name": "pangratz" 812 | }, 813 | "url": "https://api.github.com/repos/pangratz/ember.js/commits/474b13c606a8c1b9c6da5265e8c76464e8434a1c", 814 | "message": "Change to test upload_to_url task", 815 | "sha": "474b13c606a8c1b9c6da5265e8c76464e8434a1c" 816 | } 817 | ], 818 | "push_id": 76020236, 819 | "ref": "refs/heads/add_task_to_upload_latest_build", 820 | "head": "474b13c606a8c1b9c6da5265e8c76464e8434a1c", 821 | "size": 1 822 | }, 823 | "public": true, 824 | "repo": { 825 | "id": 2997378, 826 | "url": "https://api.github.com/repos/pangratz/ember.js", 827 | "name": "pangratz/ember.js" 828 | }, 829 | "id": "1547853063" 830 | }, 831 | { 832 | "created_at": "2012-05-02T07:16:04Z", 833 | "type": "DownloadEvent", 834 | "actor": { 835 | "id": 341877, 836 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 837 | "url": "https://api.github.com/users/pangratz", 838 | "login": "pangratz", 839 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 840 | }, 841 | "payload": { 842 | "download": { 843 | "content_type": "application/json", 844 | "created_at": "2012-05-02T07:16:03Z", 845 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.min.js", 846 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230826", 847 | "id": 230826, 848 | "download_count": 0, 849 | "name": "ember-latest.min.js", 850 | "description": "Ember.js Master (minified)", 851 | "size": 151552 852 | } 853 | }, 854 | "public": true, 855 | "repo": { 856 | "id": 2997378, 857 | "url": "https://api.github.com/repos/pangratz/ember.js", 858 | "name": "pangratz/ember.js" 859 | }, 860 | "id": "1547852018" 861 | }, 862 | { 863 | "created_at": "2012-05-02T07:16:02Z", 864 | "type": "DownloadEvent", 865 | "actor": { 866 | "id": 341877, 867 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 868 | "url": "https://api.github.com/users/pangratz", 869 | "login": "pangratz", 870 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 871 | }, 872 | "payload": { 873 | "download": { 874 | "content_type": "application/json", 875 | "created_at": "2012-05-02T07:16:02Z", 876 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 877 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230825", 878 | "id": 230825, 879 | "download_count": 0, 880 | "name": "ember-latest.js", 881 | "description": "Ember.js Master", 882 | "size": 581632 883 | } 884 | }, 885 | "public": true, 886 | "repo": { 887 | "id": 2997378, 888 | "url": "https://api.github.com/repos/pangratz/ember.js", 889 | "name": "pangratz/ember.js" 890 | }, 891 | "id": "1547852013" 892 | }, 893 | { 894 | "created_at": "2012-05-02T07:16:00Z", 895 | "type": "DownloadEvent", 896 | "actor": { 897 | "id": 341877, 898 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 899 | "url": "https://api.github.com/users/pangratz", 900 | "login": "pangratz", 901 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 902 | }, 903 | "payload": { 904 | "download": { 905 | "content_type": "application/json", 906 | "created_at": "2012-05-02T07:15:59Z", 907 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.min.js", 908 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230824", 909 | "id": 230824, 910 | "download_count": 0, 911 | "name": "ember-latest.min.js", 912 | "description": "Ember.js Master (minified)", 913 | "size": 151552 914 | } 915 | }, 916 | "public": true, 917 | "repo": { 918 | "id": 2997378, 919 | "url": "https://api.github.com/repos/pangratz/ember.js", 920 | "name": "pangratz/ember.js" 921 | }, 922 | "id": "1547851991" 923 | }, 924 | { 925 | "created_at": "2012-05-02T07:15:57Z", 926 | "type": "DownloadEvent", 927 | "actor": { 928 | "id": 341877, 929 | "avatar_url": "https://secure.gravatar.com/avatar/97c57e7e99431e76fbc04173cca51eab?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png", 930 | "url": "https://api.github.com/users/pangratz", 931 | "login": "pangratz", 932 | "gravatar_id": "97c57e7e99431e76fbc04173cca51eab" 933 | }, 934 | "payload": { 935 | "download": { 936 | "content_type": "application/json", 937 | "created_at": "2012-05-02T07:15:57Z", 938 | "html_url": "https://github.com/downloads/pangratz/ember.js/ember-latest.js", 939 | "url": "https://api.github.com/repos/pangratz/ember.js/downloads/230823", 940 | "id": 230823, 941 | "download_count": 0, 942 | "name": "ember-latest.js", 943 | "description": "Ember.js Master", 944 | "size": 581632 945 | } 946 | }, 947 | "public": true, 948 | "repo": { 949 | "id": 2997378, 950 | "url": "https://api.github.com/repos/pangratz/ember.js", 951 | "name": "pangratz/ember.js" 952 | }, 953 | "id": "1547851988" 954 | } 955 | ] 956 | --------------------------------------------------------------------------------