├── .gitignore
├── public
├── assets
│ ├── 404.gif
│ ├── help.gif
│ ├── love.gif
│ └── loader.gif
├── css
│ ├── auth-icons.png
│ ├── font-awesome-4.5.0
│ │ ├── fonts
│ │ │ ├── FontAwesome.otf
│ │ │ ├── fontawesome-webfont.eot
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ ├── fontawesome-webfont.woff
│ │ │ └── fontawesome-webfont.woff2
│ │ ├── less
│ │ │ ├── fixed-width.less
│ │ │ ├── larger.less
│ │ │ ├── list.less
│ │ │ ├── core.less
│ │ │ ├── font-awesome.less
│ │ │ ├── stacked.less
│ │ │ ├── bordered-pulled.less
│ │ │ ├── rotated-flipped.less
│ │ │ ├── path.less
│ │ │ ├── animated.less
│ │ │ └── mixins.less
│ │ ├── scss
│ │ │ ├── _fixed-width.scss
│ │ │ ├── _larger.scss
│ │ │ ├── _list.scss
│ │ │ ├── font-awesome.scss
│ │ │ ├── _core.scss
│ │ │ ├── _stacked.scss
│ │ │ ├── _bordered-pulled.scss
│ │ │ ├── _rotated-flipped.scss
│ │ │ ├── _path.scss
│ │ │ ├── _animated.scss
│ │ │ └── _mixins.scss
│ │ └── HELP-US-OUT.txt
│ ├── login.css
│ ├── dashboard.css
│ ├── main.css
│ ├── roboto-slab-stylesheet.css
│ └── auth-buttons.css
├── js
│ ├── models
│ │ ├── gitcard.js
│ │ ├── nav-stats.js
│ │ └── input.js
│ ├── config.js
│ ├── app
│ │ └── main.js
│ ├── views
│ │ ├── my-profile-view.js
│ │ ├── nav-stats.js
│ │ ├── profile-view.mustache
│ │ └── profile-view.js
│ ├── lib
│ │ ├── countUp.min.js
│ │ ├── require.js
│ │ ├── underscore-min.js
│ │ ├── text.js
│ │ ├── backbone-min.js
│ │ └── mustache.js
│ ├── collection-views
│ │ └── profile-collection-view.js
│ └── collections
│ │ └── profile-collection.js
├── templates
│ ├── empty-search.mustache
│ ├── small-card.mustache
│ ├── base-card.mustache
│ └── large-card.mustache
├── sass
│ ├── login.scss
│ └── main.scss
├── pages
│ └── login.html
└── old-css
│ └── css
│ └── stylesheet.css
├── server
├── config
│ ├── database.js
│ └── passport.js
├── models
│ └── user.js
├── views
│ ├── card.mustache
│ ├── login.mustache
│ └── app.mustache
├── index.js
└── routes.js
├── LICENSE
├── package.json
├── README.md
└── gulpfile.js
/.gitignore:
--------------------------------------------------------------------------------
1 | public/css/*.css*
2 | node_modules
3 | *.DS_Store
4 | *.swp
5 |
--------------------------------------------------------------------------------
/public/assets/404.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shikkic/git-dash/HEAD/public/assets/404.gif
--------------------------------------------------------------------------------
/public/assets/help.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shikkic/git-dash/HEAD/public/assets/help.gif
--------------------------------------------------------------------------------
/public/assets/love.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shikkic/git-dash/HEAD/public/assets/love.gif
--------------------------------------------------------------------------------
/public/assets/loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shikkic/git-dash/HEAD/public/assets/loader.gif
--------------------------------------------------------------------------------
/public/css/auth-icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shikkic/git-dash/HEAD/public/css/auth-icons.png
--------------------------------------------------------------------------------
/server/config/database.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 | 'url' : 'mongodb://127.0.0.1:27017/data/db'
4 |
5 | };
6 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shikkic/git-dash/HEAD/public/css/font-awesome-4.5.0/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/public/js/models/gitcard.js:
--------------------------------------------------------------------------------
1 | define(['backbone'], function(Backbone){
2 |
3 | var GitCard = Backbone.Model.extend({});
4 |
5 | return GitCard;
6 | });
7 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shikkic/git-dash/HEAD/public/css/font-awesome-4.5.0/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shikkic/git-dash/HEAD/public/css/font-awesome-4.5.0/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shikkic/git-dash/HEAD/public/css/font-awesome-4.5.0/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shikkic/git-dash/HEAD/public/css/font-awesome-4.5.0/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/less/fixed-width.less:
--------------------------------------------------------------------------------
1 | // Fixed Width Icons
2 | // -------------------------
3 | .@{fa-css-prefix}-fw {
4 | width: (18em / 14);
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/scss/_fixed-width.scss:
--------------------------------------------------------------------------------
1 | // Fixed Width Icons
2 | // -------------------------
3 | .#{$fa-css-prefix}-fw {
4 | width: (18em / 14);
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/public/templates/empty-search.mustache:
--------------------------------------------------------------------------------
1 |
Could not find "{{searchValue}}" in your followers list!
2 | Press enter to search github
3 |
--------------------------------------------------------------------------------
/public/js/models/nav-stats.js:
--------------------------------------------------------------------------------
1 | define(['backbone'], function(Backbone) {
2 |
3 | var NavStats = Backbone.Model.extend({
4 | url: '/userNavBarStats'
5 | });
6 |
7 | return NavStats;
8 | });
9 |
--------------------------------------------------------------------------------
/public/js/models/input.js:
--------------------------------------------------------------------------------
1 | define(['backbone'], function(Backbone) {
2 |
3 | var InputModel = Backbone.Model.extend({
4 | initialize: function() {
5 | console.log(this);
6 | }
7 | });
8 |
9 | return InputModel;
10 | });
11 |
--------------------------------------------------------------------------------
/server/models/user.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 |
3 | var userSchema = mongoose.Schema({
4 | github : {
5 | id : String,
6 | token : String,
7 | name: String,
8 | username : String,
9 | url : String,
10 | }
11 | });
12 |
13 | module.exports = mongoose.model('User', userSchema);
14 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/HELP-US-OUT.txt:
--------------------------------------------------------------------------------
1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project,
2 | Fonticons (https://fonticons.com). It makes it easy to put the perfect icons on your website. Choose from our awesome,
3 | comprehensive icon sets or copy and paste your own.
4 |
5 | Please. Check it out.
6 |
7 | -Dave Gandy
8 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/less/larger.less:
--------------------------------------------------------------------------------
1 | // Icon Sizes
2 | // -------------------------
3 |
4 | /* makes the font 33% larger relative to the icon container */
5 | .@{fa-css-prefix}-lg {
6 | font-size: (4em / 3);
7 | line-height: (3em / 4);
8 | vertical-align: -15%;
9 | }
10 | .@{fa-css-prefix}-2x { font-size: 2em; }
11 | .@{fa-css-prefix}-3x { font-size: 3em; }
12 | .@{fa-css-prefix}-4x { font-size: 4em; }
13 | .@{fa-css-prefix}-5x { font-size: 5em; }
14 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/scss/_larger.scss:
--------------------------------------------------------------------------------
1 | // Icon Sizes
2 | // -------------------------
3 |
4 | /* makes the font 33% larger relative to the icon container */
5 | .#{$fa-css-prefix}-lg {
6 | font-size: (4em / 3);
7 | line-height: (3em / 4);
8 | vertical-align: -15%;
9 | }
10 | .#{$fa-css-prefix}-2x { font-size: 2em; }
11 | .#{$fa-css-prefix}-3x { font-size: 3em; }
12 | .#{$fa-css-prefix}-4x { font-size: 4em; }
13 | .#{$fa-css-prefix}-5x { font-size: 5em; }
14 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/less/list.less:
--------------------------------------------------------------------------------
1 | // List Icons
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-ul {
5 | padding-left: 0;
6 | margin-left: @fa-li-width;
7 | list-style-type: none;
8 | > li { position: relative; }
9 | }
10 | .@{fa-css-prefix}-li {
11 | position: absolute;
12 | left: -@fa-li-width;
13 | width: @fa-li-width;
14 | top: (2em / 14);
15 | text-align: center;
16 | &.@{fa-css-prefix}-lg {
17 | left: (-@fa-li-width + (4em / 14));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/scss/_list.scss:
--------------------------------------------------------------------------------
1 | // List Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-ul {
5 | padding-left: 0;
6 | margin-left: $fa-li-width;
7 | list-style-type: none;
8 | > li { position: relative; }
9 | }
10 | .#{$fa-css-prefix}-li {
11 | position: absolute;
12 | left: -$fa-li-width;
13 | width: $fa-li-width;
14 | top: (2em / 14);
15 | text-align: center;
16 | &.#{$fa-css-prefix}-lg {
17 | left: -$fa-li-width + (4em / 14);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/scss/font-awesome.scss:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */
5 |
6 | @import "variables";
7 | @import "mixins";
8 | @import "path";
9 | @import "core";
10 | @import "larger";
11 | @import "fixed-width";
12 | @import "list";
13 | @import "bordered-pulled";
14 | @import "animated";
15 | @import "rotated-flipped";
16 | @import "stacked";
17 | @import "icons";
18 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/less/core.less:
--------------------------------------------------------------------------------
1 | // Base Class Definition
2 | // -------------------------
3 |
4 | .@{fa-css-prefix} {
5 | display: inline-block;
6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/scss/_core.scss:
--------------------------------------------------------------------------------
1 | // Base Class Definition
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix} {
5 | display: inline-block;
6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/less/font-awesome.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */
5 |
6 | @import "variables.less";
7 | @import "mixins.less";
8 | @import "path.less";
9 | @import "core.less";
10 | @import "larger.less";
11 | @import "fixed-width.less";
12 | @import "list.less";
13 | @import "bordered-pulled.less";
14 | @import "animated.less";
15 | @import "rotated-flipped.less";
16 | @import "stacked.less";
17 | @import "icons.less";
18 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/less/stacked.less:
--------------------------------------------------------------------------------
1 | // Stacked Icons
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-stack {
5 | position: relative;
6 | display: inline-block;
7 | width: 2em;
8 | height: 2em;
9 | line-height: 2em;
10 | vertical-align: middle;
11 | }
12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x {
13 | position: absolute;
14 | left: 0;
15 | width: 100%;
16 | text-align: center;
17 | }
18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; }
19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; }
20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; }
21 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/scss/_stacked.scss:
--------------------------------------------------------------------------------
1 | // Stacked Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-stack {
5 | position: relative;
6 | display: inline-block;
7 | width: 2em;
8 | height: 2em;
9 | line-height: 2em;
10 | vertical-align: middle;
11 | }
12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x {
13 | position: absolute;
14 | left: 0;
15 | width: 100%;
16 | text-align: center;
17 | }
18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; }
19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; }
20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; }
21 |
--------------------------------------------------------------------------------
/public/templates/small-card.mustache:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{user}}
5 | {{date}}
6 |
7 |
8 | {{#watch}}{{watch}}{{/watch}}
9 |
10 |
11 | {{repoName}}
12 |
13 |
14 | {{commitSha}} {{commitMsg}}
15 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/less/bordered-pulled.less:
--------------------------------------------------------------------------------
1 | // Bordered & Pulled
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-border {
5 | padding: .2em .25em .15em;
6 | border: solid .08em @fa-border-color;
7 | border-radius: .1em;
8 | }
9 |
10 | .@{fa-css-prefix}-pull-left { float: left; }
11 | .@{fa-css-prefix}-pull-right { float: right; }
12 |
13 | .@{fa-css-prefix} {
14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; }
15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; }
16 | }
17 |
18 | /* Deprecated as of 4.4.0 */
19 | .pull-right { float: right; }
20 | .pull-left { float: left; }
21 |
22 | .@{fa-css-prefix} {
23 | &.pull-left { margin-right: .3em; }
24 | &.pull-right { margin-left: .3em; }
25 | }
26 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/less/rotated-flipped.less:
--------------------------------------------------------------------------------
1 | // Rotated & Flipped Icons
2 | // -------------------------
3 |
4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); }
5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); }
6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); }
7 |
8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); }
9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); }
10 |
11 | // Hook for IE8-9
12 | // -------------------------
13 |
14 | :root .@{fa-css-prefix}-rotate-90,
15 | :root .@{fa-css-prefix}-rotate-180,
16 | :root .@{fa-css-prefix}-rotate-270,
17 | :root .@{fa-css-prefix}-flip-horizontal,
18 | :root .@{fa-css-prefix}-flip-vertical {
19 | filter: none;
20 | }
21 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/scss/_bordered-pulled.scss:
--------------------------------------------------------------------------------
1 | // Bordered & Pulled
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-border {
5 | padding: .2em .25em .15em;
6 | border: solid .08em $fa-border-color;
7 | border-radius: .1em;
8 | }
9 |
10 | .#{$fa-css-prefix}-pull-left { float: left; }
11 | .#{$fa-css-prefix}-pull-right { float: right; }
12 |
13 | .#{$fa-css-prefix} {
14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; }
15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; }
16 | }
17 |
18 | /* Deprecated as of 4.4.0 */
19 | .pull-right { float: right; }
20 | .pull-left { float: left; }
21 |
22 | .#{$fa-css-prefix} {
23 | &.pull-left { margin-right: .3em; }
24 | &.pull-right { margin-left: .3em; }
25 | }
26 |
--------------------------------------------------------------------------------
/public/css/login.css:
--------------------------------------------------------------------------------
1 | body {
2 | position: absolute;
3 | height: 100%;
4 | width: 100%;
5 | font-family: 'Roboto Slab';
6 | font-weight: 300;
7 | margin-top: 0px;
8 | margin-left: 0; }
9 | body #title {
10 | font-size: 35px;
11 | margin-bottom: 0px;
12 | margin-top: 20px;
13 | text-align: center; }
14 | body #title h1 {
15 | font-weight: 300;
16 | letter-spacing: 7px;
17 | font-family: 'Roboto Slab';
18 | margin-bottom: 0px;
19 | font-size: 83px; }
20 | body #title h3 {
21 | font-size: 18px;
22 | font-weight: 200;
23 | color: #26C6DA;
24 | margin-bottom: 0px;
25 | margin-top: 20px;
26 | letter-spacing: 1px; }
27 | body #title #login-button {
28 | margin-top: 50px; }
29 |
--------------------------------------------------------------------------------
/public/templates/base-card.mustache:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{user}}
5 |
6 | {{date}}
7 |
8 |
9 | {{#watch}}{{watch}}{{/watch}}
10 |
11 |
12 | {{repoName}}
13 |
14 |
15 | {{commitSha}} {{commitMsg}}
16 |
17 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/scss/_rotated-flipped.scss:
--------------------------------------------------------------------------------
1 | // Rotated & Flipped Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); }
5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); }
6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); }
7 |
8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); }
9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); }
10 |
11 | // Hook for IE8-9
12 | // -------------------------
13 |
14 | :root .#{$fa-css-prefix}-rotate-90,
15 | :root .#{$fa-css-prefix}-rotate-180,
16 | :root .#{$fa-css-prefix}-rotate-270,
17 | :root .#{$fa-css-prefix}-flip-horizontal,
18 | :root .#{$fa-css-prefix}-flip-vertical {
19 | filter: none;
20 | }
21 |
--------------------------------------------------------------------------------
/server/views/card.mustache:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{user}}
5 |
6 | {{date}}
7 |
8 |
9 | {{#watch}}{{watch}}{{/watch}}
10 |
11 |
12 | {{repoName}}
{{commitSha}} {{commitMsg}}
13 |
14 |
15 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/less/path.less:
--------------------------------------------------------------------------------
1 | /* FONT PATH
2 | * -------------------------- */
3 |
4 | @font-face {
5 | font-family: 'FontAwesome';
6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}');
7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'),
8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'),
9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'),
10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'),
11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg');
12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/scss/_path.scss:
--------------------------------------------------------------------------------
1 | /* FONT PATH
2 | * -------------------------- */
3 |
4 | @font-face {
5 | font-family: 'FontAwesome';
6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}');
7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'),
8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'),
9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'),
10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'),
11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg');
12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/less/animated.less:
--------------------------------------------------------------------------------
1 | // Animated Icons
2 | // --------------------------
3 |
4 | .@{fa-css-prefix}-spin {
5 | -webkit-animation: fa-spin 2s infinite linear;
6 | animation: fa-spin 2s infinite linear;
7 | }
8 |
9 | .@{fa-css-prefix}-pulse {
10 | -webkit-animation: fa-spin 1s infinite steps(8);
11 | animation: fa-spin 1s infinite steps(8);
12 | }
13 |
14 | @-webkit-keyframes fa-spin {
15 | 0% {
16 | -webkit-transform: rotate(0deg);
17 | transform: rotate(0deg);
18 | }
19 | 100% {
20 | -webkit-transform: rotate(359deg);
21 | transform: rotate(359deg);
22 | }
23 | }
24 |
25 | @keyframes fa-spin {
26 | 0% {
27 | -webkit-transform: rotate(0deg);
28 | transform: rotate(0deg);
29 | }
30 | 100% {
31 | -webkit-transform: rotate(359deg);
32 | transform: rotate(359deg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/scss/_animated.scss:
--------------------------------------------------------------------------------
1 | // Spinning Icons
2 | // --------------------------
3 |
4 | .#{$fa-css-prefix}-spin {
5 | -webkit-animation: fa-spin 2s infinite linear;
6 | animation: fa-spin 2s infinite linear;
7 | }
8 |
9 | .#{$fa-css-prefix}-pulse {
10 | -webkit-animation: fa-spin 1s infinite steps(8);
11 | animation: fa-spin 1s infinite steps(8);
12 | }
13 |
14 | @-webkit-keyframes fa-spin {
15 | 0% {
16 | -webkit-transform: rotate(0deg);
17 | transform: rotate(0deg);
18 | }
19 | 100% {
20 | -webkit-transform: rotate(359deg);
21 | transform: rotate(359deg);
22 | }
23 | }
24 |
25 | @keyframes fa-spin {
26 | 0% {
27 | -webkit-transform: rotate(0deg);
28 | transform: rotate(0deg);
29 | }
30 | 100% {
31 | -webkit-transform: rotate(359deg);
32 | transform: rotate(359deg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/public/sass/login.scss:
--------------------------------------------------------------------------------
1 | body {
2 | position: absolute;
3 | height: 100%;
4 | width: 100%;
5 | font-family: 'Roboto Slab';
6 | font-weight: 300;
7 | margin-top: 0px;
8 | margin-left: 0;
9 |
10 | #title {
11 | font-size: 35px;
12 | margin-bottom: 0px;
13 | margin-top: 20px;
14 | text-align: center;
15 |
16 | h1 {
17 | font-weight: 300;
18 | letter-spacing: 7px;
19 | font-family: 'Roboto Slab';
20 | margin-bottom: 0px;
21 | font-size: 83px;
22 | }
23 |
24 | h3 {
25 | font-size: 18px;
26 | font-weight: 200;
27 | color: #26C6DA;
28 | margin-bottom: 0px;
29 | margin-top: 20px;
30 | letter-spacing: 1px;
31 | }
32 |
33 | #login-button {
34 | margin-top: 50px;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/public/pages/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Github - Dashboard
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Git Dashboard
14 |
See what your friends are up to on Github in one easy place
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/public/js/config.js:
--------------------------------------------------------------------------------
1 | requirejs.config({
2 |
3 | baseUrl: "js/lib",
4 |
5 | paths: {
6 | "app": "../app",
7 | "text": "text",
8 | "jquery": "jquery-2.1.4.min",
9 | "materialize": "materialize.min",
10 | "moments": "moment",
11 | "mustache": "mustache",
12 | "underscore": "underscore-min",
13 | "backbone": "backbone-min",
14 | "stickit": "backbone.stickit",
15 | "countup": "countUp.min"
16 | },
17 |
18 | shim: {
19 | 'underscore': {
20 | exports: '_'
21 | },
22 | 'backbone': {
23 | //These script dependencies should be loaded before loading
24 | //backbone.js
25 | deps: ['underscore', 'jquery'],
26 | //Once loaded, use the global 'Backbone' as the
27 | //module value.
28 | exports: 'Backbone'
29 | }
30 | }
31 |
32 | });
33 |
34 | // Load the main app module to start the app
35 | requirejs(["app/main"]);
36 |
--------------------------------------------------------------------------------
/public/js/app/main.js:
--------------------------------------------------------------------------------
1 | define([
2 | "jquery",
3 | "moments",
4 | "mustache",
5 | "underscore",
6 | "backbone",
7 | "../../../js/collections/profile-collection",
8 | "../../../js/collection-views/profile-collection-view",
9 | "../../../js/views/nav-stats",
10 | "../../../js/views/my-profile-view"
11 | ], function($, Moments, Mustache, _, Backbone, ProfileCollection, ProfileCollectionView, NavStatsView, MyProfileView) {
12 |
13 | var PageView = Backbone.View.extend({
14 |
15 | initialize: function(options) {
16 | console.log("Creating Collection View");
17 | this.navStatsView = new NavStatsView({});
18 | this.myProfile = new MyProfileView({});
19 | this.profileCollectionView = new ProfileCollectionView({});
20 | }
21 |
22 | });
23 |
24 | // Instantiate our Page View
25 | var pageView = new PageView({el: $('body')});
26 | });
27 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/less/mixins.less:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------
3 |
4 | .fa-icon() {
5 | display: inline-block;
6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
14 | .fa-icon-rotate(@degrees, @rotation) {
15 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation);
16 | -webkit-transform: rotate(@degrees);
17 | -ms-transform: rotate(@degrees);
18 | transform: rotate(@degrees);
19 | }
20 |
21 | .fa-icon-flip(@horiz, @vert, @rotation) {
22 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1);
23 | -webkit-transform: scale(@horiz, @vert);
24 | -ms-transform: scale(@horiz, @vert);
25 | transform: scale(@horiz, @vert);
26 | }
27 |
--------------------------------------------------------------------------------
/public/css/font-awesome-4.5.0/scss/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------
3 |
4 | @mixin fa-icon() {
5 | display: inline-block;
6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
14 | @mixin fa-icon-rotate($degrees, $rotation) {
15 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation});
16 | -webkit-transform: rotate($degrees);
17 | -ms-transform: rotate($degrees);
18 | transform: rotate($degrees);
19 | }
20 |
21 | @mixin fa-icon-flip($horiz, $vert, $rotation) {
22 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation});
23 | -webkit-transform: scale($horiz, $vert);
24 | -ms-transform: scale($horiz, $vert);
25 | transform: scale($horiz, $vert);
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Dan Cadden
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/public/js/views/my-profile-view.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'backbone',
3 | 'mustache',
4 | ], function(Backbone, Mustache) {
5 |
6 | var MyProfileView = Backbone.View.extend({
7 |
8 | el: $('body'),
9 |
10 | events : {
11 | 'click .fa-bars': 'toggleProfile'
12 | },
13 |
14 | toggleProfile: function() {
15 | if ($('#my-profile').hasClass('show-my-profile')) {
16 | this.hideProfile();
17 | } else {
18 | this.showProfile();
19 | }
20 | },
21 |
22 | showProfile: function() {
23 | $('#my-profile').removeClass("hide-my-profile");
24 | $('#container').removeClass('hide-profile');
25 | $('#container').addClass('show-profile');
26 | $('#my-profile').addClass("show-my-profile");
27 | },
28 |
29 | hideProfile: function() {
30 | $('#my-profile').removeClass("show-my-profile");
31 | $('#container').removeClass('show-profile');
32 | $('#my-profile').addClass('hide-my-profile');
33 | $('#container').addClass('hide-profile');
34 | }
35 |
36 | });
37 |
38 | return MyProfileView;
39 |
40 | });
41 |
--------------------------------------------------------------------------------
/public/templates/large-card.mustache:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{user}}
5 |
6 |
7 | {{#company}}
8 | -
9 | {{company}}
10 |
11 | {{/company}}
12 | {{#location}}
13 | -
14 | {{location}}
15 |
16 | {{/location}}
17 | {{#blog}}
18 | -
19 |
20 | {{blog}}
21 |
22 | {{/blog}}
23 |
24 | {{currentStreak}}
25 |
26 |
27 | {{#watch}}{{watch}}{{/watch}}
29 |
30 |
31 |
32 | {{repoName}}
33 |
34 |
35 | {{commitSha}} {{commitMsg}}
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "git-dash",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/Shikkic/git-dash"
12 | },
13 | "author": "",
14 | "license": "ISC",
15 | "bugs": {
16 | "url": "https://github.com/Shikkic/git-dash/issues"
17 | },
18 | "homepage": "https://github.com/Shikkic/git-dash",
19 | "dependencies": {
20 | "async": "^1.2.0",
21 | "body-parser": "^1.13.2",
22 | "bower": "^1.5.2",
23 | "cookie-parser": "^1.3.5",
24 | "express": "^4.12.4",
25 | "express-session": "^1.11.3",
26 | "gh-scrape": "^1.0.3",
27 | "gulp": "^3.9.0",
28 | "gulp-sass": "^2.1.1",
29 | "gulp-util": "^3.0.7",
30 | "moment": "^2.10.6",
31 | "mongoose": "^4.0.7",
32 | "morgan": "^1.5.3",
33 | "mustache": "^2.1.3",
34 | "mustache-express": "^1.2.1",
35 | "passport": "^0.2.2",
36 | "passport-github": "^0.1.5",
37 | "passport-github2": "^0.1.9",
38 | "path": "^0.12.7",
39 | "request": "^2.57.0",
40 | "underscore": "^1.8.3"
41 | },
42 | "devDependencies": {
43 | "gulp": "^3.9.0",
44 | "gulp-jshint": "^1.11.2",
45 | "jshint-stylish": "^2.0.1"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/server/views/login.mustache:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Github - Dashboard
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
Git Dashboard
15 |
See what your friends are up to on Github in one easy place
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Git-dash
2 |
3 | Git dash is a dashboard interface for staying current with what your freinds on Github are working on!
4 | The goal was to make it easier to stay relevant with peoples interests and projects to help make reaching out easier.
5 |
6 | 
7 |
8 | ## TODOS
9 |
10 | - FE should be completely refactor to use AMD
11 | - Implement Collections and actuallly use models/views correctly
12 | - Implement a popup/modal-offclick overlay
13 | - Design and Implemeant large profile cards
14 | - Lookinto left hand column user profile
15 |
16 | ## Dev Installation
17 |
18 | **Note**: Github uses the [Github API](https://developer.github.com/v3/), a [developer account and app application](https://developer.github.com/program/) are required
19 |
20 | [Fork this repo](http://www.github.com/git-dash/fork), clone it and then run:
21 |
22 | ```
23 | npm install
24 | ```
25 |
26 | ... which installs the dependencies, and you should be good to go!
27 |
28 | ```
29 | gulp
30 | ```
31 |
32 | ### Warnings
33 |
34 | - You will need a `Github Client ID` and a `Github Client Secret` stored as enviroment variables.
35 |
36 | Ex: bash_profile
37 | ```bash
38 | export GITHUB_CLIENT_ID='[oiwhpiuqhniqo2y8;ioq2u8oqdhoihakjsdkjasgiuawh'
39 | export GITHUB_CLIENT_SECRET=';epoajslknzlaspidjhqwlkdnwlakdnasndlkasbdl'
40 | ```
41 |
42 | I hope you like it!
43 |
44 | 
45 |
46 |
47 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var gulp = require('gulp'),
4 | exec = require('child_process').exec,
5 | jshint = require('gulp-jshint'),
6 | gutil = require('gulp-util'),
7 | sass = require('gulp-sass');
8 |
9 | gulp.task('default', ['run', 'watch']);
10 |
11 | gulp.task('run', function(cb) {
12 | exec('mongod', function (err, stdout, stderr) {
13 | cb(err);
14 | });
15 | var nodeChild = exec('node ./server/index.js');
16 | nodeChild.stdout.on('data', function(data) {
17 | console.log('node: ' + data);
18 | });
19 | nodeChild.stderr.on('data', function(data) {
20 | console.log('node: ' + data);
21 | });
22 | nodeChild.on('close', function(code) {
23 | console.log('closing code: ' + code);
24 | });
25 | });
26 |
27 | gulp.task('ngrok', function(cb) {
28 | exec('ngrok http 3000', function (err, stdout, stderr) {
29 | cb(err);
30 | });
31 | });
32 |
33 | gulp.task('jshint', function() {
34 | return gulp.src('./public/js/app.js')
35 | .pipe(jshint())
36 | .pipe(jshint.reporter('jshint-stylish', {verbose: true}));
37 | });
38 |
39 | gulp.task('sass:build', function() {
40 | gulp.src('./public/sass/*.scss')
41 | .pipe(sass().on('error', sass.logError))
42 | .pipe(gulp.dest('./public/css'));
43 | });
44 |
45 | gulp.task('watch', function() {
46 | gulp.watch('./public/js/app.js', ['jshint']);
47 | gulp.watch('./public/sass/*.scss', ['sass:build']);
48 | });
49 |
--------------------------------------------------------------------------------
/public/js/views/nav-stats.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'backbone',
3 | 'mustache',
4 | 'countup',
5 | '../models/nav-stats'
6 | ], function(Backbone, Mustache, CountUp, NavStatsModel) {
7 |
8 | var NavStatsView = Backbone.View.extend({
9 |
10 | initialize: function() {
11 | this.model = new NavStatsModel({});
12 |
13 | this.listenTo(this.model, 'change', this.render);
14 |
15 | this.model.fetch();
16 | },
17 |
18 | render: function() {
19 | console.log("MADE IT TO RENDER", this.model);
20 | this.renderTotalContributrions();
21 | this.renderLongestStreak();
22 | this.renderCurrentStreak();
23 | },
24 |
25 | renderTotalContributrions: function() {
26 | this.slowCount("userFollowerCount", this.model.get('totalContributions'));
27 | },
28 |
29 | renderLongestStreak: function() {
30 | this.slowCount("userFollowingCount", this.model.get('longestStreak'));
31 | },
32 |
33 | renderCurrentStreak: function() {
34 | this.slowCount("publicRepoCount", this.model.get('currentStreak'));
35 | },
36 |
37 | slowCount: function(elementName, value) {
38 | var options = {
39 | useEasing : true,
40 | useGrouping : true,
41 | separator : ',',
42 | decimal : '.',
43 | prefix : '',
44 | suffix : ''
45 | };
46 | var demo = new CountUp(elementName, 0, value, 0, 2.5, options);
47 | demo.start();
48 | }
49 | });
50 |
51 | return NavStatsView;
52 |
53 | });
54 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | ///////////////////////////////////
3 | // Import Libraries ///
4 | ///////////////////////////////////
5 | */
6 | var path = require('path'),
7 | express = require('express'),
8 | app = express(),
9 | request = require('request'),
10 | async = require('async'),
11 | passport = require('passport'),
12 | mongoose = require('mongoose'),
13 | morgan = require('morgan'),
14 | cookieParser = require('cookie-parser'),
15 | bodyParser = require('body-parser'),
16 | session = require('express-session'),
17 | mustacheExpress = require('mustache-express'),
18 | gh = require('gh-scrape');
19 |
20 | /*
21 | /////////////////////////////////////
22 | // Set up DB //
23 | /////////////////////////////////////
24 | */
25 | var configDB = require('./config/database.js');
26 | mongoose.connect(configDB.url); // connect to our database
27 | require('./config/passport.js')(passport);
28 |
29 | /*
30 | /////////////////////////////////////
31 | // MIDDLEWARE //
32 | /////////////////////////////////////
33 | */
34 | // Setting up Mustache Rendering
35 | app.engine('mustache', mustacheExpress());
36 | app.set('view engine', 'mustache');
37 | app.set('views', path.join(__dirname, 'views'));
38 |
39 | app.set('port', (process.env.PORT || 3000));
40 | app.use(morgan('dev'));
41 | app.use(express.static('./public'));
42 | app.use(cookieParser());
43 | app.use(bodyParser.json());;
44 | app.use(session({
45 | secret: 'iloveketchup',
46 | resave: false,
47 | saveUninitialized: true
48 | }));
49 | app.use(passport.initialize());
50 | app.use(passport.session());
51 |
52 | /*
53 | //////////////////////////////////////
54 | // ROUTES //
55 | //////////////////////////////////////
56 | */
57 | require('./routes.js')(app, request, async, passport);
58 |
59 | app.listen(app.get('port'), function() {
60 | console.log("listening on port: " + app.get('port'));
61 | });
62 |
--------------------------------------------------------------------------------
/public/css/dashboard.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Base structure
3 | */
4 |
5 | /* Move down content because we have a fixed navbar that is 50px tall */
6 | body {
7 | padding-top: 50px;
8 | }
9 |
10 |
11 | /*
12 | * Global add-ons
13 | */
14 |
15 | .sub-header {
16 | padding-bottom: 10px;
17 | border-bottom: 1px solid #eee;
18 | }
19 |
20 | /*
21 | * Top navigation
22 | * Hide default border to remove 1px line.
23 | */
24 | .navbar-fixed-top {
25 | border: 0;
26 | }
27 |
28 | /*
29 | * Sidebar
30 | */
31 |
32 | /* Hide for mobile, show later */
33 | .sidebar {
34 | display: none;
35 | }
36 | @media (min-width: 768px) {
37 | .sidebar {
38 | position: fixed;
39 | top: 51px;
40 | bottom: 0;
41 | left: 0;
42 | z-index: 1000;
43 | display: block;
44 | padding: 20px;
45 | overflow-x: hidden;
46 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
47 | background-color: #f5f5f5;
48 | border-right: 1px solid #eee;
49 | }
50 | }
51 |
52 | /* Sidebar navigation */
53 | .nav-sidebar {
54 | margin-right: -21px; /* 20px padding + 1px border */
55 | margin-bottom: 20px;
56 | margin-left: -20px;
57 | }
58 | .nav-sidebar > li > a {
59 | padding-right: 20px;
60 | padding-left: 20px;
61 | }
62 | .nav-sidebar > .active > a,
63 | .nav-sidebar > .active > a:hover,
64 | .nav-sidebar > .active > a:focus {
65 | color: #fff;
66 | background-color: #428bca;
67 | }
68 |
69 |
70 | /*
71 | * Main content
72 | */
73 |
74 | .main {
75 | padding: 20px;
76 | }
77 | @media (min-width: 768px) {
78 | .main {
79 | padding-right: 40px;
80 | padding-left: 40px;
81 | }
82 | }
83 | .main .page-header {
84 | margin-top: 0;
85 | }
86 |
87 |
88 | /*
89 | * Placeholder dashboard ideas
90 | */
91 |
92 | .placeholders {
93 | margin-bottom: 30px;
94 | text-align: center;
95 | }
96 | .placeholders h4 {
97 | margin-bottom: 0;
98 | }
99 | .placeholder {
100 | margin-bottom: 20px;
101 | }
102 | .placeholder img {
103 | display: inline-block;
104 | border-radius: 50%;
105 | }
--------------------------------------------------------------------------------
/public/js/views/profile-view.mustache:
--------------------------------------------------------------------------------
1 | define([
2 |
3 | ], function() {
4 |
5 | var ProfileView = Backbone.View.extend({
6 | el: $('#container'),
7 |
8 | events: {
9 | 'dblclick' : 'transform',
10 | 'click .fa-times' : 'normal'
11 | },
12 |
13 | tagName: 'li',
14 |
15 | baseCard: "",
16 |
17 | smallCardTemplate: "",
18 |
19 | largeCardTemplate: "
20 |
21 | initialize: function(){
22 | this.render();
23 | this.setElement('#'+this.model.get('userID'));
24 | },
25 |
26 | render: function() {
27 | this.$el.append((Mustache.render(this.baseCard, this.model.toJSON())));
28 | return this;
29 | },
30 |
31 | smallCardRender: function() {
32 | this.$el.html((Mustache.render(this.smallCardTemplate, this.model.toJSON())));
33 | },
34 |
35 | largeCardRender: function() {
36 | this.$el.html((Mustache.render(this.largeCardTemplate, this.model.toJSON())));
37 | },
38 |
39 | hide: function() {
40 | this.$el.hide();
41 | },
42 |
43 | show: function() {
44 | this.$el.show();
45 | },
46 |
47 | transform: function() {
48 | this.$el.empty();
49 | this.largeCardRender();
50 | this.$el.css("position", "fixed");
51 | this.$el.css("top", "70px");
52 | this.$el.height("600px");
53 | this.$el.width("600px");
54 | /*this.$el.animate({
55 | height: '500px',
56 | width: '600px'
57 | }, 500);*/
58 | this.$el.css("left", "50%");
59 | this.$el.css("margin-left", "-300px");
60 | this.$el.addClass("z-depth-5");
61 | this.$el.removeClass("hoverable");
62 | this.$el.css("z-index", "2");
63 | },
64 |
65 | normal: function() {
66 | //$('#'+this.model.get('userID')).hide();
67 | this.smallCardRender();
68 | this.hide();
69 | console.log(this.$el);
70 | $('#'+this.model.get('userID')).height('210px');
71 | $('#'+this.model.get('userID')).width('410px');
72 | this.$el.css({
73 | "left": "0",
74 | "margin-left": "15px",
75 | });
76 |
77 | this.$el.css("display", "inline-block");
78 | this.$el.removeClass("z-depth-5");
79 | this.$el.css("position", "static");
80 | this.$el.addClass("hoverable");
81 | this.show();
82 | }
83 | });
84 |
85 | return
86 |
--------------------------------------------------------------------------------
/server/config/passport.js:
--------------------------------------------------------------------------------
1 | // config/passport.js
2 |
3 | // load all the things we need
4 | var GithubStrategy = require('passport-github2').Strategy;
5 | // load up the user model
6 | var User = require('../models/user');
7 |
8 | var GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID;
9 | var GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET;
10 |
11 | // expose this function to our app using module.exports
12 | module.exports = function(passport) {
13 |
14 | // =========================================================================
15 | // passport session setup ==================================================
16 | // =========================================================================
17 | // required for persistent login sessions
18 | // passport needs ability to serialize and unserialize users out of session
19 |
20 | // used to serialize the user for the session
21 | passport.serializeUser(function(user, done) {
22 | done(null, user.id);
23 | });
24 |
25 | // used to deserialize the user
26 | passport.deserializeUser(function(id, done) {
27 | User.findById(id, function(err, user) {
28 | done(err, user);
29 | });
30 | });
31 |
32 | passport.use(new GithubStrategy({
33 | clientID: GITHUB_CLIENT_ID,
34 | clientSecret: GITHUB_CLIENT_SECRET,
35 | callbackURL: "http://127.0.0.1:3000/auth/github/callback"
36 | },
37 | function(token, refreshToken, profile, done) {
38 | User.findOne({ githubId: profile.id }, function (err, user) {
39 | if (err) {
40 | return done(err);
41 | }
42 | // if the user is found, then log them in
43 | if (user) {
44 | return done(null, user); // user found, return that user
45 | } else {
46 | // if there is no user found with that facebook id, create them
47 | var newUser = new User();
48 |
49 | // set all of the facebook information in our user model
50 | newUser.github.id = profile.id; // set the users facebook id
51 | newUser.github.token = token; // we will save the token that facebook provides to the user
52 | newUser.github.name = profile.displayName;
53 | newUser.github.username = profile.username;
54 | newUser.github.url = profile.profileUrl;
55 | // save our user to the database
56 | newUser.save(function(err) {
57 | if (err)
58 | throw err;
59 |
60 | // if successful, return the new user
61 | return done(null, newUser);
62 | });
63 | }
64 |
65 |
66 | });
67 | }
68 | ));
69 |
70 | };
71 |
--------------------------------------------------------------------------------
/public/js/views/profile-view.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'backbone',
3 | 'mustache',
4 | "text!../../../templates/base-card.mustache",
5 | "text!../../../templates/small-card.mustache",
6 | "text!../../../templates/large-card.mustache"
7 | ], function(Backbone, Mustache, BaseCardTemplate, SmallCardTemplate, LargeCardTemplate) {
8 |
9 | var ProfileView = Backbone.View.extend({
10 |
11 | el: $('#container'),
12 |
13 | events: {
14 | 'dblclick' : 'transform',
15 | 'click .fa-times' : 'normal'
16 | },
17 |
18 | tagName: 'li',
19 |
20 | baseCard: BaseCardTemplate,
21 |
22 | smallCardTemplate: SmallCardTemplate,
23 |
24 | largeCardTemplate: LargeCardTemplate,
25 |
26 | initialize: function(){
27 | this.render();
28 | this.setElement('#'+this.model.get('userID'));
29 | },
30 |
31 | render: function() {
32 | this.$el.append((Mustache.render(this.baseCard, this.model.toJSON())));
33 | return this;
34 | },
35 |
36 | smallCardRender: function() {
37 | this.$el.html((Mustache.render(this.smallCardTemplate, this.model.toJSON())));
38 | },
39 |
40 | largeCardRender: function() {
41 | this.$el.html((Mustache.render(this.largeCardTemplate, this.model.toJSON())));
42 | },
43 |
44 | hide: function() {
45 | this.$el.hide();
46 | },
47 |
48 | show: function() {
49 | this.$el.show();
50 | },
51 |
52 | transform: function() {
53 | this.$el.empty();
54 | this.largeCardRender();
55 | this.$el.css("position", "fixed");
56 | this.$el.css("top", "70px");
57 | this.$el.height("600px");
58 | this.$el.width("600px");
59 | /*this.$el.animate({
60 | height: '500px',
61 | width: '600px'
62 | }, 500);*/
63 | this.$el.css("left", "50%");
64 | this.$el.css("margin-left", "-300px");
65 | this.$el.addClass("z-depth-5");
66 | this.$el.removeClass("hoverable");
67 | this.$el.css("z-index", "2");
68 | },
69 |
70 | normal: function() {
71 | //$('#'+this.model.get('userID')).hide();
72 | this.smallCardRender();
73 | this.hide();
74 | console.log(this.$el);
75 | $('#'+this.model.get('userID')).height('210px');
76 | $('#'+this.model.get('userID')).width('410px');
77 | this.$el.css({
78 | "left": "0",
79 | "margin-left": "15px",
80 | });
81 |
82 | this.$el.css("display", "inline-block");
83 | this.$el.removeClass("z-depth-5");
84 | this.$el.css("position", "static");
85 | this.$el.addClass("hoverable");
86 | this.show();
87 | }
88 | });
89 |
90 | return ProfileView;
91 |
92 | });
93 |
--------------------------------------------------------------------------------
/server/views/app.mustache:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Github - Dashboard
11 |
12 |
13 |
42 |
59 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/public/js/lib/countUp.min.js:
--------------------------------------------------------------------------------
1 | !function(t,a){"function"==typeof define&&define.amd?define(a):"object"==typeof exports?module.exports=a(require,exports,module):t.CountUp=a()}(this,function(t,a,e){/*
2 |
3 | countUp.js
4 | (c) 2014-2015 @inorganik
5 | Licensed under the MIT license.
6 |
7 | */
8 | var n=function(t,a,e,n,i,r){for(var o=0,s=["webkit","moz","ms","o"],u=0;uthis.endVal,this.frameVal=this.startVal,this.decimals=Math.max(0,n||0),this.dec=Math.pow(10,this.decimals),this.duration=1e3*Number(i)||2e3;var l=this;this.version=function(){return"1.6.1"},this.printValue=function(t){var a=isNaN(t)?"--":l.formatNumber(t);"INPUT"==l.d.tagName?this.d.value=a:"text"==l.d.tagName||"tspan"==l.d.tagName?this.d.textContent=a:this.d.innerHTML=a},this.easeOutExpo=function(t,a,e,n){return e*(-Math.pow(2,-10*t/n)+1)*1024/1023+a},this.count=function(t){l.startTime||(l.startTime=t),l.timestamp=t;var a=t-l.startTime;l.remaining=l.duration-a,l.options.useEasing?l.countDown?l.frameVal=l.startVal-l.easeOutExpo(a,0,l.startVal-l.endVal,l.duration):l.frameVal=l.easeOutExpo(a,l.startVal,l.endVal-l.startVal,l.duration):l.countDown?l.frameVal=l.startVal-(l.startVal-l.endVal)*(a/l.duration):l.frameVal=l.startVal+(l.endVal-l.startVal)*(a/l.duration),l.countDown?l.frameVal=l.frameVall.endVal?l.endVal:l.frameVal,l.frameVal=Math.floor(l.frameVal*l.dec)/l.dec,l.printValue(l.frameVal),al.endVal,l.rAF=requestAnimationFrame(l.count)},this.formatNumber=function(t){t=t.toFixed(l.decimals),t+="";var a,e,n,i;if(a=t.split("."),e=a[0],n=a.length>1?l.options.decimal+a[1]:"",i=/(\d+)(\d{3})/,l.options.useGrouping)for(;i.test(e);)e=e.replace(i,"$1"+l.options.separator+"$2");return l.options.prefix+e+n+l.options.suffix},l.printValue(l.startVal)};return n});
--------------------------------------------------------------------------------
/public/js/collection-views/profile-collection-view.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'backbone',
3 | 'mustache',
4 | 'stickit',
5 | '../../../js/models/input',
6 | "../views/profile-view",
7 | "../collections/profile-collection",
8 | "text!../../../templates/empty-search.mustache"
9 | ], function(Backbone, Mustache, Stickit, InputModel, ProfileView, ProfileCollection, EmptySearchTemplate) {
10 |
11 | var ProfileCollectionView = Backbone.View.extend({
12 |
13 | viewCollection: null,
14 |
15 | el: 'body',
16 |
17 | bindings: {
18 | '#search': 'inputVal'
19 | },
20 |
21 | initialize: function() {
22 | _.bindAll(this, 'visibleItemsEmpty');
23 | this.collection = new ProfileCollection;
24 | this.toggleLoader({init: true});
25 |
26 | // TODO Fix this
27 | this.model = new InputModel({});
28 |
29 | this.listenTo(this.model, {'change:inputVal':this.updateText});
30 | this.listenTo(this.collection, 'update', this.toggleLoader);
31 |
32 |
33 | this.collection.fetch({
34 | success: _.bind(function() {
35 | this.trigger("update");
36 | }, this)
37 | });
38 |
39 | this.inputVal = $("#search").val();
40 | this.stickit();
41 | },
42 |
43 | updateText: function(e) {
44 | this.inputVal = $("#search").val();
45 | this.filter();
46 | },
47 |
48 | render: function() {
49 | var viewCollection = [];
50 | // Use different _.each instead
51 | this.collection.forEach(function(item) {
52 | var view = new ProfileView({model: item});
53 | viewCollection.push(view);
54 | });
55 | this.viewCollection = viewCollection;
56 | },
57 |
58 | filter: function() {
59 | var inputValue = this.inputVal.toLowerCase();
60 | if (this.viewCollection) {
61 | for(var i = 0; i < this.viewCollection.length; i++) {
62 | var item = this.viewCollection[i];
63 | var username = item.model.attributes.userID.toLowerCase();
64 | if(username.indexOf(inputValue) > -1 || username.length === 0) {
65 | $("#"+username).show();
66 | } else {
67 | $("#"+username).hide();
68 | }
69 | }
70 | console.log(this.visibleItemsEmpty());
71 | if (this.visibleItemsEmpty()) {
72 | // RENDER MUSTACHE TEMPLATE HERE
73 | this.renderEmptySearch(inputValue);
74 | } else {
75 | this.removeEmptySearch();
76 | }
77 | }
78 | },
79 |
80 | visibleItemsEmpty: function() {
81 | for(var i = 0; i< this.viewCollection.length; i++) {
82 | var username = this.viewCollection[i].model.get('userID').toLowerCase();
83 | if ($('#'+username).is(':visible')) {
84 | return false;
85 | }
86 | }
87 | return true;
88 | },
89 |
90 | toggleLoader: function(options) {
91 | //TODO Might want these to be properties of the collection-view, not the page view
92 | options = (options || {});
93 | if (!this.collection.length && options.init) {
94 | // Toggle has been called, but it's the initial call
95 | $('#spinner').show();
96 | } else if (!this.collection.length && !options.init) {
97 | // Collection has fetched, but it's empty
98 | this.toggleEmptyView();
99 | } else {
100 | // We have a colletion to render that is NOT Empty
101 | $('#spinner').hide();
102 | this.render();
103 | }
104 | },
105 |
106 | renderEmptySearch(searchValue) {
107 | $('#emptyView').html(Mustache.render(EmptySearchTemplate, {'searchValue': searchValue})).removeClass("no-border");
108 | },
109 |
110 | removeEmptySearch() {
111 | $('#emptyView').html('').addClass("no-border");
112 | },
113 |
114 | toggleEmptyView : function() {
115 | // TODO REFACTOR THIS TO RENDER AN EMPTY TEMPLATE
116 | // TODO Might want these to be properties of the collection-view, not the page view
117 | // TODO DELETE THIS IT"S CAUSING PERFORMANCE ISSUES
118 | $("#spinner").hide();
119 | $("#img-spinner").attr("src", "../assets/help.gif");
120 | $("#img-spinner").attr("id", "no-friends");
121 | $("#info").append("Looks like you don't have any friends on Github, try adding some and come back! <3this.depCount&&!this.defined){if(G(l)){if(this.events.error&&this.map.isDefine||e.onError!==ca)try{f=h.execCb(c,l,b,f)}catch(d){a=d}else f=h.execCb(c,l,b,f);this.map.isDefine&&
19 | void 0===f&&((b=this.module)?f=b.exports:this.usingExports&&(f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(q[c]=f,e.onResourceLoad))e.onResourceLoad(h,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=
20 | !0)}}else t(h.defQueueMap,c)||this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=i(a.prefix);this.depMaps.push(d);s(d,"defined",u(this,function(f){var l,d;d=n(aa,this.map.id);var g=this.map.name,P=this.map.parentMap?this.map.parentMap.name:null,p=h.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(g=f.normalize(g,function(a){return c(a,P,!0)})||""),f=i(a.prefix+"!"+g,this.map.parentMap),s(f,"defined",u(this,function(a){this.init([],function(){return a},
21 | null,{enabled:!0,ignore:!0})})),d=n(m,f.id)){this.depMaps.push(f);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else d?(this.map.url=h.nameToUrl(d),this.load()):(l=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];A(m,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),l.fromText=u(this,function(f,c){var d=a.name,g=i(d),P=M;c&&(f=c);P&&
22 | (M=!1);r(g);t(k.config,b)&&(k.config[d]=k.config[b]);try{e.exec(f)}catch(m){return w(B("fromtexteval","fromText eval for "+b+" failed: "+m,m,[b]))}P&&(M=!0);this.depMaps.push(g);h.completeLoad(d);p([d],l)}),f.load(a.name,p,l,k))}));h.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,u(this,function(a,b){var c,f;if("string"===typeof a){a=i(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=
23 | n(L,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",u(this,function(a){this.undefed||(this.defineDep(b,a),this.check())}));this.errback?s(a,"error",u(this,this.errback)):this.events.error&&s(a,"error",u(this,function(a){this.emit("error",a)}))}c=a.id;f=m[c];!t(L,c)&&(f&&!f.enabled)&&h.enable(a,this)}));A(this.pluginMaps,u(this,function(a){var b=n(m,a.id);b&&!b.enabled&&h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=
24 | []);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};h={config:k,contextName:b,registry:m,defined:q,urlFetched:S,defQueue:C,defQueueMap:{},Module:Z,makeModuleMap:i,nextTick:e.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.shim,c={paths:!0,bundles:!0,config:!0,map:!0};A(a,function(a,b){c[b]?(k[b]||(k[b]={}),U(k[b],a,!0,!0)):k[b]=a});a.bundles&&A(a.bundles,function(a,b){v(a,
25 | function(a){a!==b&&(aa[a]=b)})});a.shim&&(A(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=h.makeShimExports(a);b[c]=a}),k.shim=b);a.packages&&v(a.packages,function(a){var b,a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(k.paths[b]=a.location);k.pkgs[b]=a.name+"/"+(a.main||"main").replace(ha,"").replace(Q,"")});A(m,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=i(b,null,!0))});if(a.deps||a.callback)h.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;
26 | a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,j){function g(c,d,p){var k,n;j.enableBuildCallback&&(d&&G(d))&&(d.__requireJsBuild=!0);if("string"===typeof c){if(G(d))return w(B("requireargs","Invalid require call"),p);if(a&&t(L,c))return L[c](m[a.id]);if(e.get)return e.get(h,c,a,g);k=i(c,a,!1,!0);k=k.id;return!t(q,k)?w(B("notloaded",'Module name "'+k+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):q[k]}J();h.nextTick(function(){J();
27 | n=r(i(null,a));n.skipMap=j.skipMap;n.init(c,d,p,{enabled:!0});D()});return g}j=j||{};U(g,{isBrowser:z,toUrl:function(b){var d,e=b.lastIndexOf("."),j=b.split("/")[0];if(-1!==e&&(!("."===j||".."===j)||1g.attachEvent.toString().indexOf("[native code"))&&!Y?(M=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1));g.src=d;J=g;D?y.insertBefore(g,D):y.appendChild(g);J=null;return g}if(ea)try{importScripts(d),b.completeLoad(c)}catch(i){b.onError(B("importscripts",
35 | "importScripts failed for "+c+" at "+d,i,[c]))}};z&&!s.skipDataMain&&T(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(I=b.getAttribute("data-main"))return r=I,s.baseUrl||(E=r.split("/"),r=E.pop(),O=E.length?E.join("/")+"/":"./",s.baseUrl=O),r=r.replace(Q,""),e.jsExtRegExp.test(r)&&(r=I),s.deps=s.deps?s.deps.concat(r):[r],!0});define=function(b,c,d){var e,g;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(ja,"").replace(ka,
36 | function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(M){if(!(e=J))N&&"interactive"===N.readyState||T(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return N=b}),e=N;e&&(b||(b=e.getAttribute("data-requiremodule")),g=F[e.getAttribute("data-requirecontext")])}g?(g.defQueue.push([b,c,d]),g.defQueueMap[b]=!0):R.push([b,c,d])};define.amd={jQuery:!0};e.exec=function(b){return eval(b)};e(s)}})(this);
37 |
--------------------------------------------------------------------------------
/public/js/lib/underscore-min.js:
--------------------------------------------------------------------------------
1 | // Underscore.js 1.8.3
2 | // http://underscorejs.org
3 | // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
4 | // Underscore may be freely distributed under the MIT license.
5 | (function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this);
6 | //# sourceMappingURL=underscore-min.map
--------------------------------------------------------------------------------
/public/js/lib/text.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license RequireJS text 2.0.14 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
3 | * Available via the MIT or new BSD license.
4 | * see: http://github.com/requirejs/text for details
5 | */
6 | /*jslint regexp: true */
7 | /*global require, XMLHttpRequest, ActiveXObject,
8 | define, window, process, Packages,
9 | java, location, Components, FileUtils */
10 |
11 | define(['module'], function (module) {
12 | 'use strict';
13 |
14 | var text, fs, Cc, Ci, xpcIsWindows,
15 | progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
16 | xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,
17 | bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im,
18 | hasLocation = typeof location !== 'undefined' && location.href,
19 | defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''),
20 | defaultHostName = hasLocation && location.hostname,
21 | defaultPort = hasLocation && (location.port || undefined),
22 | buildMap = {},
23 | masterConfig = (module.config && module.config()) || {};
24 |
25 | text = {
26 | version: '2.0.14',
27 |
28 | strip: function (content) {
29 | //Strips declarations so that external SVG and XML
30 | //documents can be added to a document without worry. Also, if the string
31 | //is an HTML document, only the part inside the body tag is returned.
32 | if (content) {
33 | content = content.replace(xmlRegExp, "");
34 | var matches = content.match(bodyRegExp);
35 | if (matches) {
36 | content = matches[1];
37 | }
38 | } else {
39 | content = "";
40 | }
41 | return content;
42 | },
43 |
44 | jsEscape: function (content) {
45 | return content.replace(/(['\\])/g, '\\$1')
46 | .replace(/[\f]/g, "\\f")
47 | .replace(/[\b]/g, "\\b")
48 | .replace(/[\n]/g, "\\n")
49 | .replace(/[\t]/g, "\\t")
50 | .replace(/[\r]/g, "\\r")
51 | .replace(/[\u2028]/g, "\\u2028")
52 | .replace(/[\u2029]/g, "\\u2029");
53 | },
54 |
55 | createXhr: masterConfig.createXhr || function () {
56 | //Would love to dump the ActiveX crap in here. Need IE 6 to die first.
57 | var xhr, i, progId;
58 | if (typeof XMLHttpRequest !== "undefined") {
59 | return new XMLHttpRequest();
60 | } else if (typeof ActiveXObject !== "undefined") {
61 | for (i = 0; i < 3; i += 1) {
62 | progId = progIds[i];
63 | try {
64 | xhr = new ActiveXObject(progId);
65 | } catch (e) {}
66 |
67 | if (xhr) {
68 | progIds = [progId]; // so faster next time
69 | break;
70 | }
71 | }
72 | }
73 |
74 | return xhr;
75 | },
76 |
77 | /**
78 | * Parses a resource name into its component parts. Resource names
79 | * look like: module/name.ext!strip, where the !strip part is
80 | * optional.
81 | * @param {String} name the resource name
82 | * @returns {Object} with properties "moduleName", "ext" and "strip"
83 | * where strip is a boolean.
84 | */
85 | parseName: function (name) {
86 | var modName, ext, temp,
87 | strip = false,
88 | index = name.lastIndexOf("."),
89 | isRelative = name.indexOf('./') === 0 ||
90 | name.indexOf('../') === 0;
91 |
92 | if (index !== -1 && (!isRelative || index > 1)) {
93 | modName = name.substring(0, index);
94 | ext = name.substring(index + 1);
95 | } else {
96 | modName = name;
97 | }
98 |
99 | temp = ext || modName;
100 | index = temp.indexOf("!");
101 | if (index !== -1) {
102 | //Pull off the strip arg.
103 | strip = temp.substring(index + 1) === "strip";
104 | temp = temp.substring(0, index);
105 | if (ext) {
106 | ext = temp;
107 | } else {
108 | modName = temp;
109 | }
110 | }
111 |
112 | return {
113 | moduleName: modName,
114 | ext: ext,
115 | strip: strip
116 | };
117 | },
118 |
119 | xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/,
120 |
121 | /**
122 | * Is an URL on another domain. Only works for browser use, returns
123 | * false in non-browser environments. Only used to know if an
124 | * optimized .js version of a text resource should be loaded
125 | * instead.
126 | * @param {String} url
127 | * @returns Boolean
128 | */
129 | useXhr: function (url, protocol, hostname, port) {
130 | var uProtocol, uHostName, uPort,
131 | match = text.xdRegExp.exec(url);
132 | if (!match) {
133 | return true;
134 | }
135 | uProtocol = match[2];
136 | uHostName = match[3];
137 |
138 | uHostName = uHostName.split(':');
139 | uPort = uHostName[1];
140 | uHostName = uHostName[0];
141 |
142 | return (!uProtocol || uProtocol === protocol) &&
143 | (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
144 | ((!uPort && !uHostName) || uPort === port);
145 | },
146 |
147 | finishLoad: function (name, strip, content, onLoad) {
148 | content = strip ? text.strip(content) : content;
149 | if (masterConfig.isBuild) {
150 | buildMap[name] = content;
151 | }
152 | onLoad(content);
153 | },
154 |
155 | load: function (name, req, onLoad, config) {
156 | //Name has format: some.module.filext!strip
157 | //The strip part is optional.
158 | //if strip is present, then that means only get the string contents
159 | //inside a body tag in an HTML string. For XML/SVG content it means
160 | //removing the declarations so the content can be inserted
161 | //into the current doc without problems.
162 |
163 | // Do not bother with the work if a build and text will
164 | // not be inlined.
165 | if (config && config.isBuild && !config.inlineText) {
166 | onLoad();
167 | return;
168 | }
169 |
170 | masterConfig.isBuild = config && config.isBuild;
171 |
172 | var parsed = text.parseName(name),
173 | nonStripName = parsed.moduleName +
174 | (parsed.ext ? '.' + parsed.ext : ''),
175 | url = req.toUrl(nonStripName),
176 | useXhr = (masterConfig.useXhr) ||
177 | text.useXhr;
178 |
179 | // Do not load if it is an empty: url
180 | if (url.indexOf('empty:') === 0) {
181 | onLoad();
182 | return;
183 | }
184 |
185 | //Load the text. Use XHR if possible and in a browser.
186 | if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) {
187 | text.get(url, function (content) {
188 | text.finishLoad(name, parsed.strip, content, onLoad);
189 | }, function (err) {
190 | if (onLoad.error) {
191 | onLoad.error(err);
192 | }
193 | });
194 | } else {
195 | //Need to fetch the resource across domains. Assume
196 | //the resource has been optimized into a JS module. Fetch
197 | //by the module name + extension, but do not include the
198 | //!strip part to avoid file system issues.
199 | req([nonStripName], function (content) {
200 | text.finishLoad(parsed.moduleName + '.' + parsed.ext,
201 | parsed.strip, content, onLoad);
202 | });
203 | }
204 | },
205 |
206 | write: function (pluginName, moduleName, write, config) {
207 | if (buildMap.hasOwnProperty(moduleName)) {
208 | var content = text.jsEscape(buildMap[moduleName]);
209 | write.asModule(pluginName + "!" + moduleName,
210 | "define(function () { return '" +
211 | content +
212 | "';});\n");
213 | }
214 | },
215 |
216 | writeFile: function (pluginName, moduleName, req, write, config) {
217 | var parsed = text.parseName(moduleName),
218 | extPart = parsed.ext ? '.' + parsed.ext : '',
219 | nonStripName = parsed.moduleName + extPart,
220 | //Use a '.js' file name so that it indicates it is a
221 | //script that can be loaded across domains.
222 | fileName = req.toUrl(parsed.moduleName + extPart) + '.js';
223 |
224 | //Leverage own load() method to load plugin value, but only
225 | //write out values that do not have the strip argument,
226 | //to avoid any potential issues with ! in file names.
227 | text.load(nonStripName, req, function (value) {
228 | //Use own write() method to construct full module value.
229 | //But need to create shell that translates writeFile's
230 | //write() to the right interface.
231 | var textWrite = function (contents) {
232 | return write(fileName, contents);
233 | };
234 | textWrite.asModule = function (moduleName, contents) {
235 | return write.asModule(moduleName, fileName, contents);
236 | };
237 |
238 | text.write(pluginName, nonStripName, textWrite, config);
239 | }, config);
240 | }
241 | };
242 |
243 | if (masterConfig.env === 'node' || (!masterConfig.env &&
244 | typeof process !== "undefined" &&
245 | process.versions &&
246 | !!process.versions.node &&
247 | !process.versions['node-webkit'] &&
248 | !process.versions['atom-shell'])) {
249 | //Using special require.nodeRequire, something added by r.js.
250 | fs = require.nodeRequire('fs');
251 |
252 | text.get = function (url, callback, errback) {
253 | try {
254 | var file = fs.readFileSync(url, 'utf8');
255 | //Remove BOM (Byte Mark Order) from utf8 files if it is there.
256 | if (file[0] === '\uFEFF') {
257 | file = file.substring(1);
258 | }
259 | callback(file);
260 | } catch (e) {
261 | if (errback) {
262 | errback(e);
263 | }
264 | }
265 | };
266 | } else if (masterConfig.env === 'xhr' || (!masterConfig.env &&
267 | text.createXhr())) {
268 | text.get = function (url, callback, errback, headers) {
269 | var xhr = text.createXhr(), header;
270 | xhr.open('GET', url, true);
271 |
272 | //Allow plugins direct access to xhr headers
273 | if (headers) {
274 | for (header in headers) {
275 | if (headers.hasOwnProperty(header)) {
276 | xhr.setRequestHeader(header.toLowerCase(), headers[header]);
277 | }
278 | }
279 | }
280 |
281 | //Allow overrides specified in config
282 | if (masterConfig.onXhr) {
283 | masterConfig.onXhr(xhr, url);
284 | }
285 |
286 | xhr.onreadystatechange = function (evt) {
287 | var status, err;
288 | //Do not explicitly handle errors, those should be
289 | //visible via console output in the browser.
290 | if (xhr.readyState === 4) {
291 | status = xhr.status || 0;
292 | if (status > 399 && status < 600) {
293 | //An http 4xx or 5xx error. Signal an error.
294 | err = new Error(url + ' HTTP status: ' + status);
295 | err.xhr = xhr;
296 | if (errback) {
297 | errback(err);
298 | }
299 | } else {
300 | callback(xhr.responseText);
301 | }
302 |
303 | if (masterConfig.onXhrComplete) {
304 | masterConfig.onXhrComplete(xhr, url);
305 | }
306 | }
307 | };
308 | xhr.send(null);
309 | };
310 | } else if (masterConfig.env === 'rhino' || (!masterConfig.env &&
311 | typeof Packages !== 'undefined' && typeof java !== 'undefined')) {
312 | //Why Java, why is this so awkward?
313 | text.get = function (url, callback) {
314 | var stringBuffer, line,
315 | encoding = "utf-8",
316 | file = new java.io.File(url),
317 | lineSeparator = java.lang.System.getProperty("line.separator"),
318 | input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)),
319 | content = '';
320 | try {
321 | stringBuffer = new java.lang.StringBuffer();
322 | line = input.readLine();
323 |
324 | // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324
325 | // http://www.unicode.org/faq/utf_bom.html
326 |
327 | // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK:
328 | // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058
329 | if (line && line.length() && line.charAt(0) === 0xfeff) {
330 | // Eat the BOM, since we've already found the encoding on this file,
331 | // and we plan to concatenating this buffer with others; the BOM should
332 | // only appear at the top of a file.
333 | line = line.substring(1);
334 | }
335 |
336 | if (line !== null) {
337 | stringBuffer.append(line);
338 | }
339 |
340 | while ((line = input.readLine()) !== null) {
341 | stringBuffer.append(lineSeparator);
342 | stringBuffer.append(line);
343 | }
344 | //Make sure we return a JavaScript string and not a Java string.
345 | content = String(stringBuffer.toString()); //String
346 | } finally {
347 | input.close();
348 | }
349 | callback(content);
350 | };
351 | } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env &&
352 | typeof Components !== 'undefined' && Components.classes &&
353 | Components.interfaces)) {
354 | //Avert your gaze!
355 | Cc = Components.classes;
356 | Ci = Components.interfaces;
357 | Components.utils['import']('resource://gre/modules/FileUtils.jsm');
358 | xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc);
359 |
360 | text.get = function (url, callback) {
361 | var inStream, convertStream, fileObj,
362 | readData = {};
363 |
364 | if (xpcIsWindows) {
365 | url = url.replace(/\//g, '\\');
366 | }
367 |
368 | fileObj = new FileUtils.File(url);
369 |
370 | //XPCOM, you so crazy
371 | try {
372 | inStream = Cc['@mozilla.org/network/file-input-stream;1']
373 | .createInstance(Ci.nsIFileInputStream);
374 | inStream.init(fileObj, 1, 0, false);
375 |
376 | convertStream = Cc['@mozilla.org/intl/converter-input-stream;1']
377 | .createInstance(Ci.nsIConverterInputStream);
378 | convertStream.init(inStream, "utf-8", inStream.available(),
379 | Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
380 |
381 | convertStream.readString(inStream.available(), readData);
382 | convertStream.close();
383 | inStream.close();
384 | callback(readData.value);
385 | } catch (e) {
386 | throw new Error((fileObj && fileObj.path || '') + ': ' + e);
387 | }
388 | };
389 | }
390 | return text;
391 | });
392 |
--------------------------------------------------------------------------------
/server/routes.js:
--------------------------------------------------------------------------------
1 | var path = require('path'),
2 | fs = require('fs'),
3 | gh = require('gh-scrape'),
4 | moment = require('moment'),
5 | LOGIN_PAGE = path.join(__dirname, '..', 'public', 'pages', 'login.html'),
6 | _ = require('underscore');
7 |
8 | module.exports = function(app, request, async, passport) {
9 |
10 | ///////////////////////////////////////////
11 | // Returns Nav Bar User Stats //
12 | ///////////////////////////////////////////
13 | app.get('/userNavBarStats', function(req, res) {
14 | //var name = req.user.github.username;
15 | gh.scrapeContributionStats("https://github.com/shikkic", function(data) {
16 | res.send(data);
17 | });
18 | });
19 |
20 | ///////////////////////////////////////////
21 | /// Default landing page / login ///
22 | ///////////////////////////////////////////
23 | app.get('/', isLoggedIn, function(req, res) {
24 | // Parse name and user token and set request options
25 | var name = req.user.github.username,
26 | userToken = req.user.github.token,
27 | gitUrl = "https://api.github.com/user?access_token="+userToken,
28 | options = {
29 | url: gitUrl,
30 | headers: {
31 | 'User-Agent': name
32 | }
33 | };
34 |
35 | // Make requests for nav bar
36 | // TODO REMOVE THIS AND MAKE FE GRAB THIS DATA
37 | request.get(options, function(error, response, userData) {
38 | if (!error) {
39 | res.render('app', {data: JSON.parse(userData)});
40 | }
41 | });
42 | });
43 |
44 | //////////////////////////////////////////
45 | /// AUTHENTICATION ///
46 | //////////////////////////////////////////
47 | app.get('/auth/github', passport.authenticate('github', { scope: ['user:email']}));
48 |
49 | /////////////////////////////////////////
50 | /// CALL BACK ///
51 | /////////////////////////////////////////
52 |
53 | app.get('/auth/github/callback',
54 | passport.authenticate('github', { failureRedirect : '/'}),
55 | function(req, res) {
56 | res.redirect('/');
57 | });
58 |
59 | //////////////////////////////////////////
60 | /// LOGOUT ///
61 | //////////////////////////////////////////
62 | app.get('/logout', function(req, res) {
63 | req.logout();
64 | res.redirect('/');
65 | });
66 |
67 | ///////////////////////////////////////////
68 | /// LOGIN ///
69 | ///////////////////////////////////////////
70 | app.get('/login', function(req, res) {
71 | res.status(200).set('Content-Type', 'text/html').sendFile(LOGIN_PAGE);
72 | });
73 |
74 | /////////////////////////////////////
75 | /// Returns a single user's info ///
76 | /////////////////////////////////////
77 | app.get('/git', function(req, res) {
78 | var name = req.query.name;
79 | var gitUrl = "https://api.github.com/users/"+ name +"/events?access_token="+ght;
80 | var options = {
81 | url: gitUrl,
82 | headers: {
83 | 'User-Agent': name
84 | }
85 | };
86 | getRequest(gitUrl, options, 1, function(gitObj) {
87 | res.send(gitObj);
88 | });
89 | });
90 |
91 | ///////////////////////////////////////
92 | /// Returns info of users following ///
93 | ///////////////////////////////////////
94 | app.get('/geet', isLoggedIn, function(req, res) {
95 | var name = req.user.github.username;
96 | var userToken = req.user.github.token;
97 | getFriendsList(name, userToken, function(names) {
98 | var names = names;
99 | getFriendsEventData(name, userToken, names, function(data){
100 | getFriendsProfileData(name, names, data, function(results){
101 | getFriendsPersonalData(name, userToken, names, function(personalData) {
102 | var userList = [];
103 | // TODO create a function for this!
104 | for (var i = 0; i < names.length; i++) {
105 | var user = {
106 | contributions: results[i],
107 | eventData: data[i],
108 | personalData: personalData[i]
109 | };
110 | userList.push(user);
111 | }
112 | res.send(userList);
113 | });
114 | });
115 | });
116 | });
117 | });
118 |
119 |
120 | function getFriendsEventData(name, userToken, friendsList, callback) {
121 | var count = 0;
122 | async.times(friendsList.length, function(_, next) {
123 | var gitUrl = 'https://api.github.com/users/'+friendsList[count]+'/events?access_token='+userToken;
124 | var options = {
125 | url: gitUrl,
126 | headers: {
127 | 'User-Agent': name
128 | }
129 | };
130 | count++;
131 | request.get(options, function(error, response, body) {
132 | var data = JSON.parse(body);
133 | var pushEventNum = findPush(data);
134 | var watchEventNum = findWatch(data);
135 |
136 | var pushEvent = data[pushEventNum];
137 | var watchEventNum = data[watchEventNum];
138 | return next(error, ({pushEvents: pushEvent, watchEvents: watchEventNum}));
139 | });
140 | }, function(err, results) {
141 | callback(results);
142 | });
143 | };
144 |
145 | function getFriendsPersonalData(name, userToken, friendsList, callback) {
146 | var count = 0;
147 | async.times(friendsList.length, function(_, next) {
148 | var gitUrl = 'https://api.github.com/users/'+friendsList[count]+'?access_token='+userToken;
149 | var options = {
150 | url: gitUrl,
151 | headers: {
152 | 'User-Agent': name
153 | }
154 | };
155 | count++;
156 | request.get(options, function(error, response, body) {
157 | var data = JSON.parse(body);
158 | return next(error, ({personalData: data}));
159 | });
160 | }, function(err, results) {
161 | callback(results);
162 | });
163 | };
164 |
165 | function getFriendsProfileData(name, friendsList, eventData, callback) {
166 | var count = 0;
167 | async.times(friendsList.length, function(_, next) {
168 | var name = friendsList[count];
169 | var gitUrl = "https://github.com/"+friendsList[count];
170 | var options = {
171 | url: gitUrl,
172 | headers: {
173 | 'User-Agent': name
174 | }
175 | };
176 | count++;
177 | request.get(options, function(error, response, body) {
178 | scrapeCon(body, function(body) {
179 | var data = body;
180 | var total = data.total;
181 | var longestStreak = data.longestStreak;
182 | var currentStreak = data.currentStreak;
183 | var contributions = {totals: total, longestStreaks: longestStreak, currentStreaks: currentStreak};
184 |
185 | return next(null, contributions);
186 | });
187 | });
188 | }, function(err, results) {
189 | callback(results);
190 | });
191 | };
192 |
193 | function createModels(data) {
194 | var userArray = [];
195 | for(var i in data) {
196 | if(data[i].pushEvents ) {
197 | var gitUser = {
198 | imgUrl: data[i].pushEvents.actor.avatar_url,
199 | user: data[i].pushEvents.actor.login,
200 | userID: data[i].pushEvents.actor.login.toLowerCase(),
201 | repoName: data[i].pushEvents.repo.name,
202 | commitMsg: data[i].pushEvents.payload.commits[0].message,
203 | commitSha: data[i].pushEvents.payload.commits[0].sha.slice(0,5),
204 | commitUrl: 'http://github.com/'+data[i].pushEvents.repo.name+'/commit/'+data[i].pushEvents.payload.commits[0].sha,
205 | date: "Last pushed "+moment(data[i].pushEvents.created_at).fromNow(),
206 | dateString: data[i].pushEvents.created_at
207 | };
208 | if(data[i].watchEvents) {
209 | gitUser.watch = data[i].watchEvents.repo.name;
210 | gitUser.watchUrl = 'http://www.github.com/'+data[i].watchEvents.repo.name;
211 | };
212 | userArray.push(gitUser);
213 | }
214 | }
215 | return userArray;
216 | };
217 |
218 | function userData(name, names, userToken, callback) {
219 | var count = 0;
220 | async.times(names.length, function(_, next) {
221 | var gitUrl = 'https://api.github.com/users/'+names[count]+'/events?access_token='+userToken;
222 | var options = {
223 | url: gitUrl,
224 | headers: {
225 | 'User-Agent': name
226 | }
227 | };
228 | count++;
229 | request.get(options, function(error, response, body) {
230 | var data = JSON.parse(body);
231 | var pushEventNum = findPush(data);
232 | var watchEventNum = findWatch(data);
233 |
234 | var pushEvent = data[pushEventNum];
235 | var watchEventNum = data[watchEventNum];
236 | return next(error, ({pushEvents: pushEvent, watchEvents: watchEventNum}));
237 | });
238 | }, function(err, results) {
239 | //console.log(results);
240 | callback(results);
241 | });
242 | };
243 |
244 | function getFriendsList(name, userToken, callback) {
245 | var page = 1;
246 | var done = false;
247 | var names = [];
248 | async.whilst(
249 | function () { return done === false },
250 | function (next) {
251 | var gitUrl = 'https://api.github.com/users/'+ name +"/following?access_token="+userToken+'&page='+page;
252 | var options = {
253 | url: gitUrl,
254 | headers: {
255 | 'User-Agent': name
256 | }
257 | };
258 | request.get(options, function(error, response, body) {
259 | var data = JSON.parse(body);
260 | for (i in data) {
261 | names.push(data[i].login);
262 | }
263 | if (data.length > 0) {
264 | console.log("Data is NOT empty");
265 | page++;
266 |
267 | } else {
268 | done = true;
269 | }
270 | return next(error, ({friends: names}));
271 | });
272 | },
273 | function (err) {
274 | callback(names);
275 | });
276 | };
277 |
278 | function getRequest(url, options, page, callback) {
279 | request.get(options, function(error, response, body){
280 | if(!error) {
281 | var data = JSON.parse(body);
282 | var count = 0;
283 | var found = false;
284 | while(found != true && count < data.length) {
285 | if(data[count].type == "PushEvent") {
286 | found = true;
287 | break;
288 | }
289 | count++;
290 | }
291 | callback(data[count]);
292 | }
293 | return error;
294 | });
295 | };
296 |
297 | // TODO This is a fucked function remove
298 | function getRequest2(gitUrl, callback) {
299 | var options = {
300 | url: gitUrl
301 | };
302 | request.get(options, function(error, response, body){
303 | if(!error) {
304 | callback(body);
305 | }
306 | return error;
307 | });
308 | };
309 |
310 | function isLoggedIn(req, res, next) {
311 | if (req.isAuthenticated()) {
312 | return next();
313 | }
314 | res.redirect('/login');
315 | };
316 |
317 | function findPush(data) {
318 | var count = 0;
319 | var found = false;
320 | while(found != true && count < data.length) {
321 | if(data[count].type == "PushEvent") {
322 | found = true;
323 | break;
324 | }
325 | count++;
326 | }
327 | return count;
328 | };
329 |
330 | function findWatch(data) {
331 | var count = 0;
332 | var found = false;
333 | while(found != true && count < data.length) {
334 | if(data[count].type == "WatchEvent") {
335 | found = true;
336 | break;
337 | }
338 | count++;
339 | }
340 | return count;
341 | };
342 |
343 | function scrapeCon(html, callback) {
344 | var user = {},
345 | j = 0,
346 | z = 0,
347 | index = 0,
348 | character = '',
349 | word = "",
350 | found = false,
351 | len = html.length - 1;
352 |
353 | for (i = 0; i < len; i++) {
354 | // Loop over every char on the page
355 | character = html[i];
356 | // Finding Contributions in the last year
357 | if (character === 'C') {
358 | index = i + 30;
359 | word = html.slice(i, index);
360 | // Contributions in the last year
361 | if (word === "Contributions in the last year") {
362 | j = i + 30;
363 | found = false;
364 | while (j < len && !found) {
365 | character = html[j];
366 | if (character === 'c') {
367 | word = html.slice(j, j + 14);
368 | if (word === "contrib-number") {
369 | found = true;
370 | word = "";
371 | z = j + 16;
372 | character = html[z];
373 | while (character != '<') {
374 | word += character;
375 | z++;
376 | character = html[z];
377 | }
378 | user.total = word;
379 | //console.log("total = ", user.total);
380 | }
381 | }
382 | j++;
383 | }
384 | }
385 | index = i + 14;
386 | word = html.slice(i, index);
387 | if (word === "Current streak") {
388 | j = i + 14;
389 | found = false;
390 | while (j < len && !found) {
391 | character = html[j];
392 | if (character === 'c') {
393 | word = html.slice(j, j +14);
394 | if (word === "contrib-number") {
395 | found = true;
396 | word = "";
397 | z = j + 16;
398 | character = html[z];
399 | while (character != '<') {
400 | word += character;
401 | z++;
402 | character = html[z];
403 | }
404 | user.currentStreak = word;
405 | //console.log("Current streak = ", user.currentStreak);
406 | }
407 | }
408 | j++;
409 | }
410 | }
411 | }
412 | // Finding Longest Streak
413 | if (character === 'L') {
414 | index = i + 14;
415 | word = html.slice(i, index);
416 | if (word === 'Longest streak') {
417 | j = i + 15;
418 | found = false;
419 | while (j < len && !found) {
420 | character = html[j];
421 | if (character === 'c') {
422 | word = html.slice(j, j +14);
423 | if (word === "contrib-number") {
424 | found = true;
425 | word = "";
426 | z = j + 16;
427 | character = html[z];
428 | while (character != '<') {
429 | word += character;
430 | z++;
431 | character = html[z];
432 | }
433 | user.longestStreak = word;
434 | //console.log("Longest Streak = ", user.streak);
435 | }
436 | }
437 | j++;
438 | }
439 | }
440 | }
441 | }
442 | callback(user);
443 | };
444 |
445 | };
446 |
--------------------------------------------------------------------------------
/public/js/lib/backbone-min.js:
--------------------------------------------------------------------------------
1 | (function(t){var e=typeof self=="object"&&self.self==self&&self||typeof global=="object"&&global.global==global&&global;if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,n){e.Backbone=t(e,n,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore"),r;try{r=require("jquery")}catch(n){}t(e,exports,i,r)}else{e.Backbone=t(e,{},e._,e.jQuery||e.Zepto||e.ender||e.$)}})(function(t,e,i,r){var n=t.Backbone;var s=Array.prototype.slice;e.VERSION="1.2.3";e.$=r;e.noConflict=function(){t.Backbone=n;return this};e.emulateHTTP=false;e.emulateJSON=false;var a=function(t,e,r){switch(t){case 1:return function(){return i[e](this[r])};case 2:return function(t){return i[e](this[r],t)};case 3:return function(t,n){return i[e](this[r],h(t,this),n)};case 4:return function(t,n,s){return i[e](this[r],h(t,this),n,s)};default:return function(){var t=s.call(arguments);t.unshift(this[r]);return i[e].apply(i,t)}}};var o=function(t,e,r){i.each(e,function(e,n){if(i[n])t.prototype[n]=a(e,n,r)})};var h=function(t,e){if(i.isFunction(t))return t;if(i.isObject(t)&&!e._isModel(t))return u(t);if(i.isString(t))return function(e){return e.get(t)};return t};var u=function(t){var e=i.matches(t);return function(t){return e(t.attributes)}};var l=e.Events={};var c=/\s+/;var f=function(t,e,r,n,s){var a=0,o;if(r&&typeof r==="object"){if(n!==void 0&&"context"in s&&s.context===void 0)s.context=n;for(o=i.keys(r);a7);this._useHashChange=this._wantsHashChange&&this._hasHashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.history&&this.history.pushState);this._usePushState=this._wantsPushState&&this._hasPushState;this.fragment=this.getFragment();this.root=("/"+this.root+"/").replace(O,"/");if(this._wantsHashChange&&this._wantsPushState){if(!this._hasPushState&&!this.atRoot()){var e=this.root.slice(0,-1)||"/";this.location.replace(e+"#"+this.getPath());return true}else if(this._hasPushState&&this.atRoot()){this.navigate(this.getHash(),{replace:true})}}if(!this._hasHashChange&&this._wantsHashChange&&!this._usePushState){this.iframe=document.createElement("iframe");this.iframe.src="javascript:0";this.iframe.style.display="none";this.iframe.tabIndex=-1;var r=document.body;var n=r.insertBefore(this.iframe,r.firstChild).contentWindow;n.document.open();n.document.close();n.location.hash="#"+this.fragment}var s=window.addEventListener||function(t,e){return attachEvent("on"+t,e)};if(this._usePushState){s("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){s("hashchange",this.checkUrl,false)}else if(this._wantsHashChange){this._checkUrlInterval=setInterval(this.checkUrl,this.interval)}if(!this.options.silent)return this.loadUrl()},stop:function(){var t=window.removeEventListener||function(t,e){return detachEvent("on"+t,e)};if(this._usePushState){t("popstate",this.checkUrl,false)}else if(this._useHashChange&&!this.iframe){t("hashchange",this.checkUrl,false)}if(this.iframe){document.body.removeChild(this.iframe);this.iframe=null}if(this._checkUrlInterval)clearInterval(this._checkUrlInterval);M.started=false},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();if(e===this.fragment&&this.iframe){e=this.getHash(this.iframe.contentWindow)}if(e===this.fragment)return false;if(this.iframe)this.navigate(e);this.loadUrl()},loadUrl:function(t){if(!this.matchRoot())return false;t=this.fragment=this.getFragment(t);return i.some(this.handlers,function(e){if(e.route.test(t)){e.callback(t);return true}})},navigate:function(t,e){if(!M.started)return false;if(!e||e===true)e={trigger:!!e};t=this.getFragment(t||"");var i=this.root;if(t===""||t.charAt(0)==="?"){i=i.slice(0,-1)||"/"}var r=i+t;t=this.decodeFragment(t.replace(U,""));if(this.fragment===t)return;this.fragment=t;if(this._usePushState){this.history[e.replace?"replaceState":"pushState"]({},document.title,r)}else if(this._wantsHashChange){this._updateHash(this.location,t,e.replace);if(this.iframe&&t!==this.getHash(this.iframe.contentWindow)){var n=this.iframe.contentWindow;if(!e.replace){n.document.open();n.document.close()}this._updateHash(n.location,t,e.replace)}}else{return this.location.assign(r)}if(e.trigger)return this.loadUrl(t)},_updateHash:function(t,e,i){if(i){var r=t.href.replace(/(javascript:|#).*$/,"");t.replace(r+"#"+e)}else{t.hash="#"+e}}});e.history=new M;var q=function(t,e){var r=this;var n;if(t&&i.has(t,"constructor")){n=t.constructor}else{n=function(){return r.apply(this,arguments)}}i.extend(n,r,e);var s=function(){this.constructor=n};s.prototype=r.prototype;n.prototype=new s;if(t)i.extend(n.prototype,t);n.__super__=r.prototype;return n};y.extend=x.extend=$.extend=I.extend=M.extend=q;var F=function(){throw new Error('A "url" property or function must be specified')};var z=function(t,e){var i=e.error;e.error=function(r){if(i)i.call(e.context,t,r,e);t.trigger("error",t,r,e)}};return e});
2 | //# sourceMappingURL=backbone-min.map
--------------------------------------------------------------------------------
/public/js/lib/mustache.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * mustache.js - Logic-less {{mustache}} templates with JavaScript
3 | * http://github.com/janl/mustache.js
4 | */
5 |
6 | /*global define: false Mustache: true*/
7 |
8 | (function defineMustache (global, factory) {
9 | if (typeof exports === 'object' && exports && typeof exports.nodeName !== 'string') {
10 | factory(exports); // CommonJS
11 | } else if (typeof define === 'function' && define.amd) {
12 | define(['exports'], factory); // AMD
13 | } else {
14 | global.Mustache = {};
15 | factory(global.Mustache); // script, wsh, asp
16 | }
17 | }(this, function mustacheFactory (mustache) {
18 |
19 | var objectToString = Object.prototype.toString;
20 | var isArray = Array.isArray || function isArrayPolyfill (object) {
21 | return objectToString.call(object) === '[object Array]';
22 | };
23 |
24 | function isFunction (object) {
25 | return typeof object === 'function';
26 | }
27 |
28 | /**
29 | * More correct typeof string handling array
30 | * which normally returns typeof 'object'
31 | */
32 | function typeStr (obj) {
33 | return isArray(obj) ? 'array' : typeof obj;
34 | }
35 |
36 | function escapeRegExp (string) {
37 | return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
38 | }
39 |
40 | /**
41 | * Null safe way of checking whether or not an object,
42 | * including its prototype, has a given property
43 | */
44 | function hasProperty (obj, propName) {
45 | return obj != null && typeof obj === 'object' && (propName in obj);
46 | }
47 |
48 | // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
49 | // See https://github.com/janl/mustache.js/issues/189
50 | var regExpTest = RegExp.prototype.test;
51 | function testRegExp (re, string) {
52 | return regExpTest.call(re, string);
53 | }
54 |
55 | var nonSpaceRe = /\S/;
56 | function isWhitespace (string) {
57 | return !testRegExp(nonSpaceRe, string);
58 | }
59 |
60 | var entityMap = {
61 | '&': '&',
62 | '<': '<',
63 | '>': '>',
64 | '"': '"',
65 | "'": ''',
66 | '/': '/',
67 | '`': '`',
68 | '=': '='
69 | };
70 |
71 | function escapeHtml (string) {
72 | return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
73 | return entityMap[s];
74 | });
75 | }
76 |
77 | var whiteRe = /\s*/;
78 | var spaceRe = /\s+/;
79 | var equalsRe = /\s*=/;
80 | var curlyRe = /\s*\}/;
81 | var tagRe = /#|\^|\/|>|\{|&|=|!/;
82 |
83 | /**
84 | * Breaks up the given `template` string into a tree of tokens. If the `tags`
85 | * argument is given here it must be an array with two string values: the
86 | * opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of
87 | * course, the default is to use mustaches (i.e. mustache.tags).
88 | *
89 | * A token is an array with at least 4 elements. The first element is the
90 | * mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag
91 | * did not contain a symbol (i.e. {{myValue}}) this element is "name". For
92 | * all text that appears outside a symbol this element is "text".
93 | *
94 | * The second element of a token is its "value". For mustache tags this is
95 | * whatever else was inside the tag besides the opening symbol. For text tokens
96 | * this is the text itself.
97 | *
98 | * The third and fourth elements of the token are the start and end indices,
99 | * respectively, of the token in the original template.
100 | *
101 | * Tokens that are the root node of a subtree contain two more elements: 1) an
102 | * array of tokens in the subtree and 2) the index in the original template at
103 | * which the closing tag for that section begins.
104 | */
105 | function parseTemplate (template, tags) {
106 | if (!template)
107 | return [];
108 |
109 | var sections = []; // Stack to hold section tokens
110 | var tokens = []; // Buffer to hold the tokens
111 | var spaces = []; // Indices of whitespace tokens on the current line
112 | var hasTag = false; // Is there a {{tag}} on the current line?
113 | var nonSpace = false; // Is there a non-space char on the current line?
114 |
115 | // Strips all whitespace tokens array for the current line
116 | // if there was a {{#tag}} on it and otherwise only space.
117 | function stripSpace () {
118 | if (hasTag && !nonSpace) {
119 | while (spaces.length)
120 | delete tokens[spaces.pop()];
121 | } else {
122 | spaces = [];
123 | }
124 |
125 | hasTag = false;
126 | nonSpace = false;
127 | }
128 |
129 | var openingTagRe, closingTagRe, closingCurlyRe;
130 | function compileTags (tagsToCompile) {
131 | if (typeof tagsToCompile === 'string')
132 | tagsToCompile = tagsToCompile.split(spaceRe, 2);
133 |
134 | if (!isArray(tagsToCompile) || tagsToCompile.length !== 2)
135 | throw new Error('Invalid tags: ' + tagsToCompile);
136 |
137 | openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*');
138 | closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1]));
139 | closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1]));
140 | }
141 |
142 | compileTags(tags || mustache.tags);
143 |
144 | var scanner = new Scanner(template);
145 |
146 | var start, type, value, chr, token, openSection;
147 | while (!scanner.eos()) {
148 | start = scanner.pos;
149 |
150 | // Match any text between tags.
151 | value = scanner.scanUntil(openingTagRe);
152 |
153 | if (value) {
154 | for (var i = 0, valueLength = value.length; i < valueLength; ++i) {
155 | chr = value.charAt(i);
156 |
157 | if (isWhitespace(chr)) {
158 | spaces.push(tokens.length);
159 | } else {
160 | nonSpace = true;
161 | }
162 |
163 | tokens.push([ 'text', chr, start, start + 1 ]);
164 | start += 1;
165 |
166 | // Check for whitespace on the current line.
167 | if (chr === '\n')
168 | stripSpace();
169 | }
170 | }
171 |
172 | // Match the opening tag.
173 | if (!scanner.scan(openingTagRe))
174 | break;
175 |
176 | hasTag = true;
177 |
178 | // Get the tag type.
179 | type = scanner.scan(tagRe) || 'name';
180 | scanner.scan(whiteRe);
181 |
182 | // Get the tag value.
183 | if (type === '=') {
184 | value = scanner.scanUntil(equalsRe);
185 | scanner.scan(equalsRe);
186 | scanner.scanUntil(closingTagRe);
187 | } else if (type === '{') {
188 | value = scanner.scanUntil(closingCurlyRe);
189 | scanner.scan(curlyRe);
190 | scanner.scanUntil(closingTagRe);
191 | type = '&';
192 | } else {
193 | value = scanner.scanUntil(closingTagRe);
194 | }
195 |
196 | // Match the closing tag.
197 | if (!scanner.scan(closingTagRe))
198 | throw new Error('Unclosed tag at ' + scanner.pos);
199 |
200 | token = [ type, value, start, scanner.pos ];
201 | tokens.push(token);
202 |
203 | if (type === '#' || type === '^') {
204 | sections.push(token);
205 | } else if (type === '/') {
206 | // Check section nesting.
207 | openSection = sections.pop();
208 |
209 | if (!openSection)
210 | throw new Error('Unopened section "' + value + '" at ' + start);
211 |
212 | if (openSection[1] !== value)
213 | throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
214 | } else if (type === 'name' || type === '{' || type === '&') {
215 | nonSpace = true;
216 | } else if (type === '=') {
217 | // Set the tags for the next time around.
218 | compileTags(value);
219 | }
220 | }
221 |
222 | // Make sure there are no open sections when we're done.
223 | openSection = sections.pop();
224 |
225 | if (openSection)
226 | throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
227 |
228 | return nestTokens(squashTokens(tokens));
229 | }
230 |
231 | /**
232 | * Combines the values of consecutive text tokens in the given `tokens` array
233 | * to a single token.
234 | */
235 | function squashTokens (tokens) {
236 | var squashedTokens = [];
237 |
238 | var token, lastToken;
239 | for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
240 | token = tokens[i];
241 |
242 | if (token) {
243 | if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
244 | lastToken[1] += token[1];
245 | lastToken[3] = token[3];
246 | } else {
247 | squashedTokens.push(token);
248 | lastToken = token;
249 | }
250 | }
251 | }
252 |
253 | return squashedTokens;
254 | }
255 |
256 | /**
257 | * Forms the given array of `tokens` into a nested tree structure where
258 | * tokens that represent a section have two additional items: 1) an array of
259 | * all tokens that appear in that section and 2) the index in the original
260 | * template that represents the end of that section.
261 | */
262 | function nestTokens (tokens) {
263 | var nestedTokens = [];
264 | var collector = nestedTokens;
265 | var sections = [];
266 |
267 | var token, section;
268 | for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
269 | token = tokens[i];
270 |
271 | switch (token[0]) {
272 | case '#':
273 | case '^':
274 | collector.push(token);
275 | sections.push(token);
276 | collector = token[4] = [];
277 | break;
278 | case '/':
279 | section = sections.pop();
280 | section[5] = token[2];
281 | collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
282 | break;
283 | default:
284 | collector.push(token);
285 | }
286 | }
287 |
288 | return nestedTokens;
289 | }
290 |
291 | /**
292 | * A simple string scanner that is used by the template parser to find
293 | * tokens in template strings.
294 | */
295 | function Scanner (string) {
296 | this.string = string;
297 | this.tail = string;
298 | this.pos = 0;
299 | }
300 |
301 | /**
302 | * Returns `true` if the tail is empty (end of string).
303 | */
304 | Scanner.prototype.eos = function eos () {
305 | return this.tail === '';
306 | };
307 |
308 | /**
309 | * Tries to match the given regular expression at the current position.
310 | * Returns the matched text if it can match, the empty string otherwise.
311 | */
312 | Scanner.prototype.scan = function scan (re) {
313 | var match = this.tail.match(re);
314 |
315 | if (!match || match.index !== 0)
316 | return '';
317 |
318 | var string = match[0];
319 |
320 | this.tail = this.tail.substring(string.length);
321 | this.pos += string.length;
322 |
323 | return string;
324 | };
325 |
326 | /**
327 | * Skips all text until the given regular expression can be matched. Returns
328 | * the skipped string, which is the entire tail if no match can be made.
329 | */
330 | Scanner.prototype.scanUntil = function scanUntil (re) {
331 | var index = this.tail.search(re), match;
332 |
333 | switch (index) {
334 | case -1:
335 | match = this.tail;
336 | this.tail = '';
337 | break;
338 | case 0:
339 | match = '';
340 | break;
341 | default:
342 | match = this.tail.substring(0, index);
343 | this.tail = this.tail.substring(index);
344 | }
345 |
346 | this.pos += match.length;
347 |
348 | return match;
349 | };
350 |
351 | /**
352 | * Represents a rendering context by wrapping a view object and
353 | * maintaining a reference to the parent context.
354 | */
355 | function Context (view, parentContext) {
356 | this.view = view;
357 | this.cache = { '.': this.view };
358 | this.parent = parentContext;
359 | }
360 |
361 | /**
362 | * Creates a new context using the given view with this context
363 | * as the parent.
364 | */
365 | Context.prototype.push = function push (view) {
366 | return new Context(view, this);
367 | };
368 |
369 | /**
370 | * Returns the value of the given name in this context, traversing
371 | * up the context hierarchy if the value is absent in this context's view.
372 | */
373 | Context.prototype.lookup = function lookup (name) {
374 | var cache = this.cache;
375 |
376 | var value;
377 | if (cache.hasOwnProperty(name)) {
378 | value = cache[name];
379 | } else {
380 | var context = this, names, index, lookupHit = false;
381 |
382 | while (context) {
383 | if (name.indexOf('.') > 0) {
384 | value = context.view;
385 | names = name.split('.');
386 | index = 0;
387 |
388 | /**
389 | * Using the dot notion path in `name`, we descend through the
390 | * nested objects.
391 | *
392 | * To be certain that the lookup has been successful, we have to
393 | * check if the last object in the path actually has the property
394 | * we are looking for. We store the result in `lookupHit`.
395 | *
396 | * This is specially necessary for when the value has been set to
397 | * `undefined` and we want to avoid looking up parent contexts.
398 | **/
399 | while (value != null && index < names.length) {
400 | if (index === names.length - 1)
401 | lookupHit = hasProperty(value, names[index]);
402 |
403 | value = value[names[index++]];
404 | }
405 | } else {
406 | value = context.view[name];
407 | lookupHit = hasProperty(context.view, name);
408 | }
409 |
410 | if (lookupHit)
411 | break;
412 |
413 | context = context.parent;
414 | }
415 |
416 | cache[name] = value;
417 | }
418 |
419 | if (isFunction(value))
420 | value = value.call(this.view);
421 |
422 | return value;
423 | };
424 |
425 | /**
426 | * A Writer knows how to take a stream of tokens and render them to a
427 | * string, given a context. It also maintains a cache of templates to
428 | * avoid the need to parse the same template twice.
429 | */
430 | function Writer () {
431 | this.cache = {};
432 | }
433 |
434 | /**
435 | * Clears all cached templates in this writer.
436 | */
437 | Writer.prototype.clearCache = function clearCache () {
438 | this.cache = {};
439 | };
440 |
441 | /**
442 | * Parses and caches the given `template` and returns the array of tokens
443 | * that is generated from the parse.
444 | */
445 | Writer.prototype.parse = function parse (template, tags) {
446 | var cache = this.cache;
447 | var tokens = cache[template];
448 |
449 | if (tokens == null)
450 | tokens = cache[template] = parseTemplate(template, tags);
451 |
452 | return tokens;
453 | };
454 |
455 | /**
456 | * High-level method that is used to render the given `template` with
457 | * the given `view`.
458 | *
459 | * The optional `partials` argument may be an object that contains the
460 | * names and templates of partials that are used in the template. It may
461 | * also be a function that is used to load partial templates on the fly
462 | * that takes a single argument: the name of the partial.
463 | */
464 | Writer.prototype.render = function render (template, view, partials) {
465 | var tokens = this.parse(template);
466 | var context = (view instanceof Context) ? view : new Context(view);
467 | return this.renderTokens(tokens, context, partials, template);
468 | };
469 |
470 | /**
471 | * Low-level method that renders the given array of `tokens` using
472 | * the given `context` and `partials`.
473 | *
474 | * Note: The `originalTemplate` is only ever used to extract the portion
475 | * of the original template that was contained in a higher-order section.
476 | * If the template doesn't use higher-order sections, this argument may
477 | * be omitted.
478 | */
479 | Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate) {
480 | var buffer = '';
481 |
482 | var token, symbol, value;
483 | for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
484 | value = undefined;
485 | token = tokens[i];
486 | symbol = token[0];
487 |
488 | if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate);
489 | else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate);
490 | else if (symbol === '>') value = this.renderPartial(token, context, partials, originalTemplate);
491 | else if (symbol === '&') value = this.unescapedValue(token, context);
492 | else if (symbol === 'name') value = this.escapedValue(token, context);
493 | else if (symbol === 'text') value = this.rawValue(token);
494 |
495 | if (value !== undefined)
496 | buffer += value;
497 | }
498 |
499 | return buffer;
500 | };
501 |
502 | Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate) {
503 | var self = this;
504 | var buffer = '';
505 | var value = context.lookup(token[1]);
506 |
507 | // This function is used to render an arbitrary template
508 | // in the current context by higher-order sections.
509 | function subRender (template) {
510 | return self.render(template, context, partials);
511 | }
512 |
513 | if (!value) return;
514 |
515 | if (isArray(value)) {
516 | for (var j = 0, valueLength = value.length; j < valueLength; ++j) {
517 | buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate);
518 | }
519 | } else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') {
520 | buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate);
521 | } else if (isFunction(value)) {
522 | if (typeof originalTemplate !== 'string')
523 | throw new Error('Cannot use higher-order sections without the original template');
524 |
525 | // Extract the portion of the original template that the section contains.
526 | value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);
527 |
528 | if (value != null)
529 | buffer += value;
530 | } else {
531 | buffer += this.renderTokens(token[4], context, partials, originalTemplate);
532 | }
533 | return buffer;
534 | };
535 |
536 | Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate) {
537 | var value = context.lookup(token[1]);
538 |
539 | // Use JavaScript's definition of falsy. Include empty arrays.
540 | // See https://github.com/janl/mustache.js/issues/186
541 | if (!value || (isArray(value) && value.length === 0))
542 | return this.renderTokens(token[4], context, partials, originalTemplate);
543 | };
544 |
545 | Writer.prototype.renderPartial = function renderPartial (token, context, partials) {
546 | if (!partials) return;
547 |
548 | var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];
549 | if (value != null)
550 | return this.renderTokens(this.parse(value), context, partials, value);
551 | };
552 |
553 | Writer.prototype.unescapedValue = function unescapedValue (token, context) {
554 | var value = context.lookup(token[1]);
555 | if (value != null)
556 | return value;
557 | };
558 |
559 | Writer.prototype.escapedValue = function escapedValue (token, context) {
560 | var value = context.lookup(token[1]);
561 | if (value != null)
562 | return mustache.escape(value);
563 | };
564 |
565 | Writer.prototype.rawValue = function rawValue (token) {
566 | return token[1];
567 | };
568 |
569 | mustache.name = 'mustache.js';
570 | mustache.version = '2.2.1';
571 | mustache.tags = [ '{{', '}}' ];
572 |
573 | // All high-level mustache.* functions use this writer.
574 | var defaultWriter = new Writer();
575 |
576 | /**
577 | * Clears all cached templates in the default writer.
578 | */
579 | mustache.clearCache = function clearCache () {
580 | return defaultWriter.clearCache();
581 | };
582 |
583 | /**
584 | * Parses and caches the given template in the default writer and returns the
585 | * array of tokens it contains. Doing this ahead of time avoids the need to
586 | * parse templates on the fly as they are rendered.
587 | */
588 | mustache.parse = function parse (template, tags) {
589 | return defaultWriter.parse(template, tags);
590 | };
591 |
592 | /**
593 | * Renders the `template` with the given `view` and `partials` using the
594 | * default writer.
595 | */
596 | mustache.render = function render (template, view, partials) {
597 | if (typeof template !== 'string') {
598 | throw new TypeError('Invalid template! Template should be a "string" ' +
599 | 'but "' + typeStr(template) + '" was given as the first ' +
600 | 'argument for mustache#render(template, view, partials)');
601 | }
602 |
603 | return defaultWriter.render(template, view, partials);
604 | };
605 |
606 | // This is here for backwards compatibility with 0.4.x.,
607 | /*eslint-disable */ // eslint wants camel cased function name
608 | mustache.to_html = function to_html (template, view, partials, send) {
609 | /*eslint-enable*/
610 |
611 | var result = mustache.render(template, view, partials);
612 |
613 | if (isFunction(send)) {
614 | send(result);
615 | } else {
616 | return result;
617 | }
618 | };
619 |
620 | // Export the escaping function so that the user may override it.
621 | // See https://github.com/janl/mustache.js/issues/244
622 | mustache.escape = escapeHtml;
623 |
624 | // Export these mainly for testing, but also for advanced usage.
625 | mustache.Scanner = Scanner;
626 | mustache.Context = Context;
627 | mustache.Writer = Writer;
628 |
629 | }));
630 |
--------------------------------------------------------------------------------