├── Procfile
├── public
├── robots.txt
├── favicon.ico
├── images
│ ├── favicon.ico
│ └── bg_gray_smoke.jpg
├── jquery.spin.min.js
├── index.css
├── page.js
├── repo.css
└── app.js
├── .gitignore
├── package.json
├── README.md
├── server.coffee
├── styls
└── index.styl
└── views
└── index.jade
/Procfile:
--------------------------------------------------------------------------------
1 | web: coffee server.coffee
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitrun/poll/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitrun/poll/HEAD/public/images/favicon.ico
--------------------------------------------------------------------------------
/public/images/bg_gray_smoke.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gitrun/poll/HEAD/public/images/bg_gray_smoke.jpg
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 |
14 | node_modules
15 | npm-debug.log
--------------------------------------------------------------------------------
/public/jquery.spin.min.js:
--------------------------------------------------------------------------------
1 | $(function(){
2 | $.fn.spin = function(opts) {
3 | this.each(function() {
4 | var $this = $(this),
5 | data = $this.data();
6 |
7 | if (data.spinner) {
8 | data.spinner.stop();
9 | delete data.spinner;
10 | }
11 | if (opts !== false) {
12 | data.spinner = new Spinner($.extend({color: $this.css('color')}, opts)).spin(this);
13 | }
14 | });
15 | return this;
16 | };
17 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "hashobject team (team@hashobject.com)",
3 | "name": "gitpoll",
4 | "description": "GitHub poll app",
5 | "version": "0.1.0",
6 | "main": "server.coffee",
7 | "engines": {
8 | "node": "~0.10.22"
9 | },
10 | "dependencies": {
11 | "express": "=3.4.6",
12 | "jade": "=0.35.0",
13 | "passport": ">= 0.0.0",
14 | "passport-github": ">= 0.0.0",
15 | "coffee-script": ">=1.6.3",
16 | "stylus": "=0.41.0",
17 | "nib": "=1.0.1"
18 | },
19 | "devDependencies": {},
20 | "optionalDependencies": {}
21 | }
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | gitpoll
2 | ==========
3 |
4 | Poll for Github issues.
5 | [poll.gitrun.com](http://poll.gitrun.com).
6 |
7 | LICENCE
8 | ==========
9 |
10 | (The MIT License)
11 |
12 | Copyright © 2012-2014 Hashobject Ltd (team@hashobject.com)
13 |
14 | Permission is hereby granted, free of charge, to any person obtaining a copy
15 | of this software and associated documentation files (the 'Software'), to deal
16 | in the Software without restriction, including without limitation the rights
17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18 | copies of the Software, and to permit persons to whom the Software is
19 | furnished to do so, subject to the following conditions:
20 |
21 | The above copyright notice and this permission notice shall be included in all
22 | copies or substantial portions of the Software.
23 |
24 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 | SOFTWARE.
31 |
--------------------------------------------------------------------------------
/server.coffee:
--------------------------------------------------------------------------------
1 | express = require "express"
2 | stylus = require "stylus"
3 | nib = require "nib"
4 | passport = require "passport"
5 |
6 | GitHubStrategy = require('passport-github').Strategy
7 |
8 | GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || "041e33b4c176a007f627"
9 | GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET || "f70b6f1468f4ef87ba18d5587a2be6d7c6c4ae98";
10 |
11 | app = module.exports = express.createServer()
12 |
13 |
14 | passport.serializeUser (user, done) ->
15 | done(null, user);
16 |
17 |
18 | passport.deserializeUser (obj, done) ->
19 | done(null, obj);
20 |
21 |
22 |
23 | callbackURL = process.env.GITHUB_CALLBACK_URL || "http://localhost:8085/auth/callback"
24 |
25 |
26 | passport.use new GitHubStrategy {
27 | clientID: GITHUB_CLIENT_ID,
28 | clientSecret: GITHUB_CLIENT_SECRET,
29 | callbackURL: callbackURL
30 | },
31 | (accessToken, refreshToken, profile, done) ->
32 |
33 | process.nextTick ->
34 | profile.accessToken = accessToken
35 | profile.avatar = profile._json.avatar_url
36 | return done(null, profile);
37 |
38 |
39 | # stylus compile function
40 | compile = (str, path) ->
41 | return stylus(str)
42 | .define("url", stylus.url({ paths: [__dirname + "/public"] }))
43 | .set("filename", path)
44 | .set("warn", true)
45 | .set("compress", false)
46 | .use(nib())
47 |
48 | app.configure ->
49 | # stylus middleware
50 | app.use stylus.middleware
51 | src : __dirname + "/styls" # styl files should be placed inside this folder
52 | dest : __dirname + "/public" # CSS files will be complied to public directory
53 | compile: compile # compile function
54 | app.set('views', __dirname + '/views');
55 | app.set('view engine', 'jade');
56 | app.set('view options', {layout: false});
57 | app.use(express.favicon(__dirname + "/public/favicon.ico"));
58 | app.use(express.logger());
59 | app.use(express.cookieParser());
60 | app.use(express.bodyParser());
61 | app.use(express.methodOverride());
62 | app.use(express.session({ secret: 'keyboard cat' }));
63 |
64 | app.use(passport.initialize());
65 | app.use(passport.session());
66 | app.use(app.router);
67 | app.use(express.static(__dirname + '/public'));
68 |
69 |
70 | app.get '/auth',
71 | passport.authenticate('github', scope: 'public_repo'),
72 | (req, res) ->
73 |
74 | app.get "/", (req, res) ->
75 |
76 | user = req.user
77 | if not user
78 | user = {username:'guest'}
79 | console.log "user", user
80 | res.render "index", {user: user}
81 |
82 | app.get "/:username/:repoName/issues/:number", (req, res) ->
83 |
84 | user = req.user
85 | if not user
86 | user = {username:'guest'}
87 | res.render "index", {user: user}
88 |
89 | app.get "/logout", (req, res) ->
90 | req.logout()
91 | res.redirect "/"
92 |
93 | app.get '/auth/callback',
94 | passport.authenticate('github', { failureRedirect: '/login' }),
95 | (req, res) ->
96 | res.redirect('/');
97 |
98 | port = process.env.PORT || 8085
99 | app.listen port
100 | console.log "server started on port 8085. Open http://localhost:8085 in your browser"
101 |
--------------------------------------------------------------------------------
/styls/index.styl:
--------------------------------------------------------------------------------
1 | @import 'nib'
2 | global-reset();
3 | *
4 | box-sizing: border-box;
5 |
6 | html
7 | body
8 | background-color: white;
9 | font-family: Helvetica,arial,freesans,clean,sans-serif;
10 | font-size: 13px;
11 | color: #666;
12 | line-height: 1.6;
13 | .container
14 | width: 740px;
15 | margin: 0 auto;
16 | header.container
17 | margin-top: 10px;
18 | margin-bottom: 20px;
19 | height: 30px;
20 | & a
21 | & a:visited
22 | white-space: nowrap;
23 | text-decoration: none;
24 | color: #333;
25 | & a:hover
26 | color: white;
27 | #app-footer
28 | border-top: 1px solid #E0E0E0;
29 | position: fixed;
30 | bottom: 0;
31 | width: 100%;
32 | height: 100px;
33 | & .container
34 | margin-top: 20px;
35 | & p
36 | display: inline-block;
37 | vertical-align: top;
38 | margin: 10px 10px 0 0;
39 | #username
40 | color: #4A3E7B;
41 | line-height: 22px;
42 | font-weight: bold;
43 | text-shadow: 0 1px 0 white;
44 | &:hover
45 | text-decoration: underline;
46 | .page
47 | position: relative;
48 | width: 740px;
49 | .page-container
50 | margin-bottom: 15px;
51 | padding: 3px;
52 | background: #EEE;
53 | border-radius: 4px;
54 | .page-content
55 | padding: 10px;
56 | background: white;
57 | border: 1px solid #DDD;
58 | border-radius: 2px;
59 | position: relative;
60 | min-height: 160px;
61 | .title
62 | font: 13px Helvetica,arial,freesans,clean,sans-serif;
63 | position: relative;
64 | font-size: 14px;
65 | font-weight: bold;
66 | color: #333;
67 | #app-intro-page .title
68 | margin: 10px 0 15px;
69 | #issue-title
70 | width: 49%;
71 | font-size: 16px;
72 | font-weight: bold;
73 | color: #444;
74 | box-sizing: border-box;
75 | margin-right: 5px;
76 | padding: 6px;
77 | color: #666;
78 | background-repeat: no-repeat;
79 | background-position: right center;
80 | #issue-description
81 | margin-top: 15px;
82 | //margin-bottom: 70px;
83 | .info-bar
84 | display: inline-block;
85 | margin: 15px 0 -10px;
86 | width: 100%;
87 | padding: 10px 10px 4px;
88 | border: 1px solid #E5E5E5;
89 | border-left: none;
90 | border-right: none;
91 | background: whiteSmoke;
92 | position: absolute;
93 | left: 0;
94 | right: 0;
95 | bottom: 9px;
96 | textarea
97 | box-sizing: border-box;
98 | width: 100%;
99 | padding: 5px;
100 | height: 150px;
101 | font-size: 12px;
102 | .btn
103 | color: white;
104 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.4);
105 | background: #FF8A66;
106 | background: linear-gradient(#FF8A66, #ED4706);
107 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
108 | display: inline-block;
109 | padding: 8px 15px;
110 | line-height: normal;
111 | position: relative;
112 | font-size: 12px;
113 | font-weight: bold;
114 | border-radius: 3px;
115 | border: 1px solid;
116 | border-color: #E46432;
117 | border-bottom-color: #C13D14;
118 | &:hover
119 | background: #EE6A36;
120 | background: linear-gradient(#EE6A36, #BE410E);
121 | border-color: #D64910;
122 | color: white;
123 | button#create-issue-button
124 | position: absolute;
125 | right: 5px;
126 | margin-top: 15px;
127 | #user-repos-select
128 | width: 49%;
129 | font-size: 16px;
130 | font-weight: bold;
131 | color: #444;
132 | box-sizing: border-box;
133 | padding: 6px 6px;
134 | color: #666;
135 | background-repeat: no-repeat;
136 | background-position: right center;
137 | border-color: rgba(102, 102, 102, 0.15);
138 | & option
139 | font-size: 12px;
140 | font-weight: normal;
141 | header
142 | color: #666;
143 | font-size: 16px;
144 | width: 100%;
145 | position: relative;
146 | & img
147 | width: 22px;
148 | vertical-align: -6px;
149 | margin: 0 3px 0 10px;
150 | border-radius: 11px;
151 | #login, #logout
152 | color: #333;
153 | padding: 0 10px 0 10px;
154 | font-weight: bold;
155 | line-height: 24px;
156 | text-shadow: 0 1px 0 white;
157 | border: 1px solid #D4D4D4;
158 | border-bottom-color: #BCBCBC;
159 | border-radius: 3px;
160 | background: #FAFAFA;
161 | background: linear-gradient(#FAFAFA,#EAEAEA);
162 | cursor: pointer;
163 | #logout
164 | position: absolute;
165 | right: 5px;
166 | bottom: -9px;
167 | #login:hover, #logout:hover
168 | color: white;
169 | text-decoration: none;
170 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3);
171 | border-color: #1B0E53;
172 | border-bottom-color: #150A43;
173 | background: #716896;
174 | background: linear-gradient(#716896,#1D0D5A);
175 | #no-results
176 | margin-bottom: 10px;
177 | margin-top: 20px;
178 | #chart-panel
179 | margin: -35px auto 0;
180 | height: 400px;
181 | position: relative;
182 | #poll-description
183 | margin-top: 10px;
184 | margin-bottom: 10px;
185 | #yes, #no
186 | font-weight: bold;
187 | & span
188 | margin-left: 4px;
189 | .not-valid
190 | border: 1px solid red!important;
191 | ///colors for pie
192 | g.slice:nth-child(1)
193 | fill: #ED4706;
194 | g.slice:nth-child(2)
195 | fill: #232063;
196 | footer a
197 | color: #4A3E7B;
198 | text-decoration: none;
199 | &:hover
200 | text-decoration: underline;
201 | header svg
202 | width: auto;
203 | display: inline-block;
204 | & path
205 | fill: #4A3E7B;
206 | #home
207 | margin-right: 15px;
208 | display: inline-block;
209 | bottom: -8px;
210 | width: 30px;
211 | height: 30px;
212 | position: relative;
213 | &:hover path
214 | fill: #FF8A66;
215 | .visible
216 | display: block;
217 | .inline-visible
218 | display: inline-block;
219 | .invisible
220 | display: none;
221 |
--------------------------------------------------------------------------------
/views/index.jade:
--------------------------------------------------------------------------------
1 | doctype 5
2 | html(lang="en")
3 | head
4 | meta(charset="utf-8")
5 | title GitPoll - vote on GitHub issue
6 | meta(name="description", content="Application for voting on GitHub issues")
7 | meta(name="keywords", content="gitpoll, github, issue, vote, poll, +1, -1")
8 | meta(name="author", content="Hashobject (team@hashobject.com)", itemprop="author")
9 | link(rel="stylesheet", href="/index.css")
10 | script(type="text/javascript")
11 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
12 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
13 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
14 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
15 | ga('create', 'UA-46214133-2', 'gitrun.com');
16 | ga('send', 'pageview');
17 | script
18 | var gp = {};
19 | gp.user = {
20 | username: '#{user.username}',
21 | avatar: '#{user.avatar}',
22 | profileUrl: '#{user.profileUrl}',
23 | displayName: '#{user.displayName}',
24 | accessToken: '#{user.accessToken}'
25 | };
26 | body
27 | header.container
28 | a(href="/")#home.visible
29 | svg(version="1.1", id="Layer_1", xmlns="http://www.w3.org/2000/svg", x="0px", y="0px", width="30px", height="30px", viewBox="0 0 100 100", enable-background="new 0 0 100 100")
30 | path(d="M92,46C92,20.595,71.405,0,46,0S0,20.595,0,46s20.595,46,46,46V46H92z")
31 | path(d="M54,100c25.405,0,46-20.595,46-46H54V100z")
32 | span#welcome.invisible Welcome
33 | img(src="").invisible
34 | a#username.invisible(href="", target="_blank", title="Opens in new window")
35 | button#logout.invisible Logout
36 | a#login.invisible(href="/auth") Login
37 | div.container
38 | div.page.invisible#app-intro-page
39 | div.page-container
40 | section.page-content
41 | h2.title Usage
42 | ol
43 | li 1. Create new issue on GitHub or GitPoll.
44 | li 2. Share link to issue with repository collaborators.
45 | li 3. Vote on issue and see results as on Ember.js repo.
46 | h2.title Feedback
47 | p If you run into some problem with app or need extra feature please create new issue on GitHub and we will vote on it.
48 | div.page.invisible#create-poll-page
49 | div.page-container
50 | section.page-content
51 | form(onsubmit="return false;")
52 | h1.title Title
53 | input#issue-title(placeholder="Name of issue...", required)
54 | select#user-repos-select(required)
55 | option Choose repository
56 | textarea#issue-description(rows="4", cols="50", placeholder="Add some description...")
57 | div.info-bar.invisible
58 | span Want to add somebody personally?
59 | input#mandatory-voter
60 | button#add-mandatory-voter-button Add
61 | div#mandatory-voter-container
62 | span#issue-labels Labels:
63 | input#issue-label
64 | button#add-issue-label-button Add
65 | div#issue-labels-container
66 | button#create-issue-button.btn Create poll
67 | div.page.invisible#poll-page
68 | div.page-container
69 | section.page-content
70 | p.error.invisible Sorry, there is no issue on given URL, please check, if URL is correct.
71 | a(href="", target="_blank")
72 | h1#poll-title.title
73 | div#poll-description
74 | div#no-results.invisible Nobody voted yet. Be the first!
75 | div#results
76 | div#yes Yes:
77 | span
78 | div#no No:
79 | span
80 | div#issue-comments
81 | div#vote-btns
82 | button#yes-btn.btn +1
83 | button#no-btn.btn -1
84 | div#chart-panel.invisible
85 | //- footer#app-footer
86 | //- div.container
87 | //- p GitPoll is not affiliated with GitHub.
88 | //- p Home icon and favicon symbol by The Noun Project team.
89 | //- p GitPoll brought to you by Hashobject team.
90 | //- p GitPoll is open source project hosted on GitHub.
91 | a(href="https://github.com/gitrun/poll")
92 | img(style="position: absolute; top: 0; right: 0; border: 0;", src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png", alt="Fork me on GitHub")
93 | script(src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js")
94 | script(src="/page.js")
95 | script(src="https://code.jquery.com/jquery-1.11.2.min.js")
96 | script(src="https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.0.1/spin.min.js")
97 | script(src="https://cdnjs.cloudflare.com/ajax/libs/showdown/0.3.1/showdown.min.js")
98 | script(src="/jquery.spin.min.js")
99 | script(src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.1/d3.min.js")
100 | script(src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.7/c3.min.js")
101 | script(src="/app.js")
--------------------------------------------------------------------------------
/public/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | div,
4 | span,
5 | applet,
6 | object,
7 | iframe,
8 | h1,
9 | h2,
10 | h3,
11 | h4,
12 | h5,
13 | h6,
14 | p,
15 | blockquote,
16 | pre,
17 | a,
18 | abbr,
19 | acronym,
20 | address,
21 | big,
22 | cite,
23 | code,
24 | del,
25 | dfn,
26 | em,
27 | img,
28 | ins,
29 | kbd,
30 | q,
31 | s,
32 | samp,
33 | small,
34 | strike,
35 | strong,
36 | sub,
37 | sup,
38 | tt,
39 | var,
40 | dl,
41 | dt,
42 | dd,
43 | ol,
44 | ul,
45 | li,
46 | fieldset,
47 | form,
48 | label,
49 | legend,
50 | table,
51 | caption,
52 | tbody,
53 | tfoot,
54 | thead,
55 | tr,
56 | th,
57 | td {
58 | margin: 0;
59 | padding: 0;
60 | border: 0;
61 | outline: 0;
62 | font-weight: inherit;
63 | font-style: inherit;
64 | font-family: inherit;
65 | font-size: 100%;
66 | vertical-align: baseline;
67 | }
68 | body {
69 | line-height: 1;
70 | color: #000;
71 | background: #fff;
72 | }
73 | ol,
74 | ul {
75 | list-style: none;
76 | }
77 | table {
78 | border-collapse: separate;
79 | border-spacing: 0;
80 | vertical-align: middle;
81 | }
82 | caption,
83 | th,
84 | td {
85 | text-align: left;
86 | font-weight: normal;
87 | vertical-align: middle;
88 | }
89 | a img {
90 | border: none;
91 | }
92 | * {
93 | -webkit-box-sizing: border-box;
94 | -moz-box-sizing: border-box;
95 | box-sizing: border-box;
96 | }
97 | html,
98 | body {
99 | background-color: #fff;
100 | font-family: Helvetica, arial, freesans, clean, sans-serif;
101 | font-size: 13px;
102 | color: #666;
103 | line-height: 1.6;
104 | }
105 | .container {
106 | width: 740px;
107 | margin: 0 auto;
108 | }
109 | header.container {
110 | margin-top: 10px;
111 | margin-bottom: 20px;
112 | height: 30px;
113 | }
114 | header.container a,
115 | header.container a:visited {
116 | white-space: nowrap;
117 | text-decoration: none;
118 | color: #333;
119 | }
120 | header.container a:hover {
121 | color: #fff;
122 | }
123 | #app-footer {
124 | border-top: 1px solid #e0e0e0;
125 | position: fixed;
126 | bottom: 0;
127 | width: 100%;
128 | height: 100px;
129 | }
130 | #app-footer .container {
131 | margin-top: 20px;
132 | }
133 | #app-footer .container p {
134 | display: inline-block;
135 | vertical-align: top;
136 | margin: 10px 10px 0 0;
137 | }
138 | #username {
139 | color: #4a3e7b;
140 | line-height: 22px;
141 | font-weight: bold;
142 | text-shadow: 0 1px 0 #fff;
143 | }
144 | #username:hover {
145 | text-decoration: underline;
146 | }
147 | .page {
148 | position: relative;
149 | width: 740px;
150 | }
151 | .page-container {
152 | margin-bottom: 15px;
153 | padding: 3px;
154 | background: #eee;
155 | -webkit-border-radius: 4px;
156 | border-radius: 4px;
157 | }
158 | .page-content {
159 | padding: 10px;
160 | background: #fff;
161 | border: 1px solid #ddd;
162 | -webkit-border-radius: 2px;
163 | border-radius: 2px;
164 | position: relative;
165 | min-height: 160px;
166 | }
167 | .title {
168 | font: 13px Helvetica, arial, freesans, clean, sans-serif;
169 | position: relative;
170 | font-size: 14px;
171 | font-weight: bold;
172 | color: #333;
173 | }
174 | #app-intro-page .title {
175 | margin: 10px 0 15px;
176 | }
177 | #issue-title {
178 | width: 49%;
179 | font-size: 16px;
180 | font-weight: bold;
181 | color: #444;
182 | -webkit-box-sizing: border-box;
183 | -moz-box-sizing: border-box;
184 | box-sizing: border-box;
185 | margin-right: 5px;
186 | padding: 6px;
187 | color: #666;
188 | background-repeat: no-repeat;
189 | background-position: right center;
190 | }
191 | #issue-description {
192 | margin-top: 15px;
193 | }
194 | .info-bar {
195 | display: inline-block;
196 | margin: 15px 0 -10px;
197 | width: 100%;
198 | padding: 10px 10px 4px;
199 | border: 1px solid #e5e5e5;
200 | border-left: none;
201 | border-right: none;
202 | background: whiteSmoke;
203 | position: absolute;
204 | left: 0;
205 | right: 0;
206 | bottom: 9px;
207 | }
208 | textarea {
209 | -webkit-box-sizing: border-box;
210 | -moz-box-sizing: border-box;
211 | box-sizing: border-box;
212 | width: 100%;
213 | padding: 5px;
214 | height: 150px;
215 | font-size: 12px;
216 | }
217 | .btn {
218 | color: #fff;
219 | text-shadow: 0 -1px 0 rgba(0,0,0,0.4);
220 | background: #ff8a66;
221 | background: -webkit-linear-gradient(#ff8a66, #ed4706);
222 | background: -moz-linear-gradient(#ff8a66, #ed4706);
223 | background: -o-linear-gradient(#ff8a66, #ed4706);
224 | background: -ms-linear-gradient(#ff8a66, #ed4706);
225 | background: linear-gradient(#ff8a66, #ed4706);
226 | -webkit-box-shadow: 0 1px 4px rgba(0,0,0,0.2);
227 | box-shadow: 0 1px 4px rgba(0,0,0,0.2);
228 | display: inline-block;
229 | padding: 8px 15px;
230 | line-height: normal;
231 | position: relative;
232 | font-size: 12px;
233 | font-weight: bold;
234 | -webkit-border-radius: 3px;
235 | border-radius: 3px;
236 | border: 1px solid;
237 | border-color: #e46432;
238 | border-bottom-color: #c13d14;
239 | }
240 | .btn:hover {
241 | background: #ee6a36;
242 | background: -webkit-linear-gradient(#ee6a36, #be410e);
243 | background: -moz-linear-gradient(#ee6a36, #be410e);
244 | background: -o-linear-gradient(#ee6a36, #be410e);
245 | background: -ms-linear-gradient(#ee6a36, #be410e);
246 | background: linear-gradient(#ee6a36, #be410e);
247 | border-color: #d64910;
248 | color: #fff;
249 | }
250 | button#create-issue-button {
251 | position: absolute;
252 | right: 5px;
253 | margin-top: 15px;
254 | }
255 | #user-repos-select {
256 | width: 49%;
257 | font-size: 16px;
258 | font-weight: bold;
259 | color: #444;
260 | -webkit-box-sizing: border-box;
261 | -moz-box-sizing: border-box;
262 | box-sizing: border-box;
263 | padding: 6px 6px;
264 | color: #666;
265 | background-repeat: no-repeat;
266 | background-position: right center;
267 | border-color: rgba(102,102,102,0.15);
268 | }
269 | #user-repos-select option {
270 | font-size: 12px;
271 | font-weight: normal;
272 | }
273 | header {
274 | color: #666;
275 | font-size: 16px;
276 | width: 100%;
277 | position: relative;
278 | }
279 | header img {
280 | width: 22px;
281 | vertical-align: -6px;
282 | margin: 0 3px 0 10px;
283 | -webkit-border-radius: 11px;
284 | border-radius: 11px;
285 | }
286 | #login,
287 | #logout {
288 | color: #333;
289 | padding: 0 10px 0 10px;
290 | font-weight: bold;
291 | line-height: 24px;
292 | text-shadow: 0 1px 0 #fff;
293 | border: 1px solid #d4d4d4;
294 | border-bottom-color: #bcbcbc;
295 | -webkit-border-radius: 3px;
296 | border-radius: 3px;
297 | background: #fafafa;
298 | background: -webkit-linear-gradient(#fafafa, #eaeaea);
299 | background: -moz-linear-gradient(#fafafa, #eaeaea);
300 | background: -o-linear-gradient(#fafafa, #eaeaea);
301 | background: -ms-linear-gradient(#fafafa, #eaeaea);
302 | background: linear-gradient(#fafafa, #eaeaea);
303 | cursor: pointer;
304 | }
305 | #logout {
306 | position: absolute;
307 | right: 5px;
308 | bottom: -9px;
309 | }
310 | #login:hover,
311 | #logout:hover {
312 | color: #fff;
313 | text-decoration: none;
314 | text-shadow: 0 -1px 0 rgba(0,0,0,0.3);
315 | border-color: #1b0e53;
316 | border-bottom-color: #150a43;
317 | background: #716896;
318 | background: -webkit-linear-gradient(#716896, #1d0d5a);
319 | background: -moz-linear-gradient(#716896, #1d0d5a);
320 | background: -o-linear-gradient(#716896, #1d0d5a);
321 | background: -ms-linear-gradient(#716896, #1d0d5a);
322 | background: linear-gradient(#716896, #1d0d5a);
323 | }
324 | #no-results {
325 | margin-bottom: 10px;
326 | margin-top: 20px;
327 | }
328 | #chart-panel {
329 | margin: -35px auto 0;
330 | height: 400px;
331 | position: relative;
332 | }
333 | #poll-description {
334 | margin-top: 10px;
335 | margin-bottom: 10px;
336 | }
337 | #yes,
338 | #no {
339 | font-weight: bold;
340 | }
341 | #yes span,
342 | #no span {
343 | margin-left: 4px;
344 | }
345 | .not-valid {
346 | border: 1px solid #f00 !important;
347 | }
348 | g.slice:nth-child(1) {
349 | fill: #ed4706;
350 | }
351 | g.slice:nth-child(2) {
352 | fill: #232063;
353 | }
354 | footer a {
355 | color: #4a3e7b;
356 | text-decoration: none;
357 | }
358 | footer a:hover {
359 | text-decoration: underline;
360 | }
361 | header svg {
362 | width: auto;
363 | display: inline-block;
364 | }
365 | header svg path {
366 | fill: #4a3e7b;
367 | }
368 | #home {
369 | margin-right: 15px;
370 | display: inline-block;
371 | bottom: -8px;
372 | width: 30px;
373 | height: 30px;
374 | position: relative;
375 | }
376 | #home:hover path {
377 | fill: #ff8a66;
378 | }
379 | .visible {
380 | display: block;
381 | }
382 | .inline-visible {
383 | display: inline-block;
384 | }
385 | .invisible {
386 | display: none;
387 | }
388 |
--------------------------------------------------------------------------------
/public/page.js:
--------------------------------------------------------------------------------
1 |
2 | ;(function(){
3 |
4 | /**
5 | * Perform initial dispatch.
6 | */
7 |
8 | var dispatch = true;
9 |
10 | /**
11 | * Base path.
12 | */
13 |
14 | var base = '';
15 |
16 | /**
17 | * Running flag.
18 | */
19 |
20 | var running;
21 |
22 | /**
23 | * Register `path` with callback `fn()`,
24 | * or route `path`, or `page.start()`.
25 | *
26 | * page('/user/:id', load, user);
27 | * page('/user/' + user.id, { some: 'thing' });
28 | * page('/user/' + user.id);
29 | * page();
30 | *
31 | * @param {String} path
32 | * @param {Function} fn...
33 | * @api public
34 | */
35 |
36 | function page(path, fn) {
37 | // route to
38 | if ('function' == typeof fn) {
39 | var route = new Route(path);
40 | for (var i = 1; i < arguments.length; ++i) {
41 | page.callbacks.push(route.middleware(arguments[i]));
42 | }
43 | // show with [state]
44 | } else if ('string' == typeof path) {
45 | page.show(path, fn);
46 | // start [options]
47 | } else {
48 | page.start(path);
49 | }
50 | }
51 |
52 | /**
53 | * Callback functions.
54 | */
55 |
56 | page.callbacks = [];
57 |
58 | /**
59 | * Get or set basepath to `path`.
60 | *
61 | * @param {String} path
62 | * @api public
63 | */
64 |
65 | page.base = function(path){
66 | if (0 == arguments.length) return base;
67 | base = path;
68 | };
69 |
70 | /**
71 | * Bind with the given `options`.
72 | *
73 | * Options:
74 | *
75 | * - `click` bind to click events [true]
76 | * - `popstate` bind to popstate [true]
77 | * - `dispatch` perform initial dispatch [true]
78 | *
79 | * @param {Object} options
80 | * @api public
81 | */
82 |
83 | page.start = function(options){
84 | options = options || {};
85 | if (running) return;
86 | running = true;
87 | if (false === options.dispatch) dispatch = false;
88 | if (false !== options.popstate) addEventListener('popstate', onpopstate, false);
89 | if (false !== options.click) addEventListener('click', onclick, false);
90 | if (!dispatch) return;
91 | page.replace(location.pathname, null, true, dispatch);
92 | };
93 |
94 | /**
95 | * Unbind click and popstate event handlers.
96 | *
97 | * @api public
98 | */
99 |
100 | page.stop = function(){
101 | running = false;
102 | removeEventListener('click', onclick, false);
103 | removeEventListener('popstate', onpopstate, false);
104 | };
105 |
106 | /**
107 | * Show `path` with optional `state` object.
108 | *
109 | * @param {String} path
110 | * @param {Object} state
111 | * @return {Context}
112 | * @api public
113 | */
114 |
115 | page.show = function(path, state){
116 | var ctx = new Context(path, state);
117 | page.dispatch(ctx);
118 | if (!ctx.unhandled) ctx.pushState();
119 | return ctx;
120 | };
121 |
122 | /**
123 | * Replace `path` with optional `state` object.
124 | *
125 | * @param {String} path
126 | * @param {Object} state
127 | * @return {Context}
128 | * @api public
129 | */
130 |
131 | page.replace = function(path, state, init, dispatch){
132 | var ctx = new Context(path, state);
133 | ctx.init = init;
134 | if (null == dispatch) dispatch = true;
135 | if (dispatch) page.dispatch(ctx);
136 | ctx.save();
137 | return ctx;
138 | };
139 |
140 | /**
141 | * Dispatch the given `ctx`.
142 | *
143 | * @param {Object} ctx
144 | * @api private
145 | */
146 |
147 | page.dispatch = function(ctx){
148 | var i = 0;
149 |
150 | function next() {
151 | var fn = page.callbacks[i++];
152 | if (!fn) return unhandled(ctx);
153 | fn(ctx, next);
154 | }
155 |
156 | next();
157 | };
158 |
159 | /**
160 | * Unhandled `ctx`. When it's not the initial
161 | * popstate then redirect. If you wish to handle
162 | * 404s on your own use `page('*', callback)`.
163 | *
164 | * @param {Context} ctx
165 | * @api private
166 | */
167 |
168 | function unhandled(ctx) {
169 | if (window.location.pathname == ctx.canonicalPath) return;
170 | page.stop();
171 | ctx.unhandled = true;
172 | window.location = ctx.canonicalPath;
173 | }
174 |
175 | /**
176 | * Initialize a new "request" `Context`
177 | * with the given `path` and optional initial `state`.
178 | *
179 | * @param {String} path
180 | * @param {Object} state
181 | * @api public
182 | */
183 |
184 | function Context(path, state) {
185 | if ('/' == path[0] && 0 != path.indexOf(base)) path = base + path;
186 | this.canonicalPath = path;
187 | this.path = path.replace(base, '') || '/';
188 | this.title = document.title;
189 | this.state = state || {};
190 | this.state.path = path;
191 | this.params = [];
192 | }
193 |
194 | /**
195 | * Push state.
196 | *
197 | * @api private
198 | */
199 |
200 | Context.prototype.pushState = function(){
201 | history.pushState(this.state, this.title, this.canonicalPath);
202 | };
203 |
204 | /**
205 | * Save the context state.
206 | *
207 | * @api public
208 | */
209 |
210 | Context.prototype.save = function(){
211 | history.replaceState(this.state, this.title, this.canonicalPath);
212 | };
213 |
214 | /**
215 | * Initialize `Route` with the given HTTP `path`,
216 | * and an array of `callbacks` and `options`.
217 | *
218 | * Options:
219 | *
220 | * - `sensitive` enable case-sensitive routes
221 | * - `strict` enable strict matching for trailing slashes
222 | *
223 | * @param {String} path
224 | * @param {Object} options.
225 | * @api private
226 | */
227 |
228 | function Route(path, options) {
229 | options = options || {};
230 | this.path = path;
231 | this.method = 'GET';
232 | this.regexp = pathtoRegexp(path
233 | , this.keys = []
234 | , options.sensitive
235 | , options.strict);
236 | }
237 |
238 | /**
239 | * Return route middleware with
240 | * the given callback `fn()`.
241 | *
242 | * @param {Function} fn
243 | * @return {Function}
244 | * @api public
245 | */
246 |
247 | Route.prototype.middleware = function(fn){
248 | var self = this;
249 | return function(ctx, next){
250 | if (self.match(ctx.path, ctx.params)) return fn(ctx, next);
251 | next();
252 | }
253 | };
254 |
255 | /**
256 | * Check if this route matches `path`, if so
257 | * populate `params`.
258 | *
259 | * @param {String} path
260 | * @param {Array} params
261 | * @return {Boolean}
262 | * @api private
263 | */
264 |
265 | Route.prototype.match = function(path, params){
266 | var keys = this.keys
267 | , m = this.regexp.exec(path);
268 |
269 | if (!m) return false;
270 |
271 | for (var i = 1, len = m.length; i < len; ++i) {
272 | var key = keys[i - 1];
273 |
274 | var val = 'string' == typeof m[i]
275 | ? decodeURIComponent(m[i])
276 | : m[i];
277 |
278 | if (key) {
279 | params[key.name] = undefined !== params[key.name]
280 | ? params[key.name]
281 | : val;
282 | } else {
283 | params.push(val);
284 | }
285 | }
286 |
287 | return true;
288 | };
289 |
290 | /**
291 | * Normalize the given path string,
292 | * returning a regular expression.
293 | *
294 | * An empty array should be passed,
295 | * which will contain the placeholder
296 | * key names. For example "/user/:id" will
297 | * then contain ["id"].
298 | *
299 | * @param {String|RegExp|Array} path
300 | * @param {Array} keys
301 | * @param {Boolean} sensitive
302 | * @param {Boolean} strict
303 | * @return {RegExp}
304 | * @api private
305 | */
306 |
307 | function pathtoRegexp(path, keys, sensitive, strict) {
308 | if (path instanceof RegExp) return path;
309 | if (path instanceof Array) path = '(' + path.join('|') + ')';
310 | path = path
311 | .concat(strict ? '' : '/?')
312 | .replace(/\/\(/g, '(?:/')
313 | .replace(/\+/g, '__plus__')
314 | .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
315 | keys.push({ name: key, optional: !! optional });
316 | slash = slash || '';
317 | return ''
318 | + (optional ? '' : slash)
319 | + '(?:'
320 | + (optional ? slash : '')
321 | + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
322 | + (optional || '');
323 | })
324 | .replace(/([\/.])/g, '\\$1')
325 | .replace(/__plus__/g, '(.+)')
326 | .replace(/\*/g, '(.*)');
327 | return new RegExp('^' + path + '$', sensitive ? '' : 'i');
328 | };
329 |
330 | /**
331 | * Handle "populate" events.
332 | */
333 |
334 | function onpopstate(e) {
335 | if (e.state) {
336 | var path = e.state.path;
337 | page.replace(path, e.state);
338 | }
339 | }
340 |
341 | /**
342 | * Handle "click" events.
343 | */
344 |
345 | function onclick(e) {
346 | if (e.defaultPrevented) return;
347 | var el = e.target;
348 | while (el && 'A' != el.nodeName) el = el.parentNode;
349 | if (!el || 'A' != el.nodeName) return;
350 | var href = el.href;
351 | var path = el.pathname;
352 | if (el.hash) return;
353 | if (!sameOrigin(href)) return;
354 | var orig = path;
355 | path = path.replace(base, '');
356 | if (base && orig == path) return;
357 | e.preventDefault();
358 | page.show(orig);
359 | }
360 |
361 | /**
362 | * Check if `href` is the same origin.
363 | */
364 |
365 | function sameOrigin(href) {
366 | var origin = location.protocol + '//' + location.hostname;
367 | if (location.port) origin += ':' + location.port;
368 | return 0 == href.indexOf(origin);
369 | }
370 |
371 | /**
372 | * Expose `page`.
373 | */
374 |
375 | if ('undefined' == typeof module) {
376 | window.page = page;
377 | } else {
378 | module.exports = page;
379 | }
380 |
381 | })();
--------------------------------------------------------------------------------
/public/repo.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Repo.js
3 | * @author Darcy Clarke
4 | *
5 | * Copyright (c) 2012 Darcy Clarke
6 | * Dual licensed under the MIT and GPL licenses.
7 | * http://darcyclarke.me/
8 | */
9 |
10 | /*!
11 | * @mekwall's .vangogh() for Syntax Highlighting
12 | *
13 | * All code is open source and dual licensed under GPL and MIT.
14 | * Check the individual licenses for more information.
15 | * https://github.com/mekwall/jquery-vangogh/blob/master/GPL-LICENSE.txt
16 | * https://github.com/mekwall/jquery-vangogh/blob/master/MIT-LICENSE.txt
17 | */
18 |
19 | @font-face {
20 | font-family: "Octicons Regular";
21 | src: url("https://a248.e.akamai.net/assets.github.com/fonts/octicons/octicons-regular-webfont.eot?639c50d4");
22 | src: url("https://a248.e.akamai.net/assets.github.com/fonts/octicons/octicons-regular-webfont.eot?639c50d4#iefix") format("embedded-opentype"), url("https://a248.e.akamai.net/assets.github.com/fonts/octicons/octicons-regular-webfont.woff?0605b255") format("woff"), url("https://a248.e.akamai.net/assets.github.com/fonts/octicons/octicons-regular-webfont.ttf?f82fcba7") format("truetype"), url("https://a248.e.akamai.net/assets.github.com/fonts/octicons/octicons-regular-webfont.svg?1f7afa21#newFontRegular") format("svg");
23 | font-weight: normal;
24 | font-style: normal;
25 | }
26 | .repo, .repo * {
27 | -webkit-box-sizing: border-box;
28 | -moz-box-sizing: border-box;
29 | -ms-box-sizing: border-box;
30 | box-sizing: border-box;
31 | }
32 | .repo ul * {
33 | display: block;
34 | font-family: sans-serif;
35 | font-size: 13px;
36 | line-height: 18px;
37 | }
38 | .repo {
39 | width: 100%;
40 | margin: 0 0 15px 0;
41 | position: relative;
42 | padding-bottom: 1px;
43 | color: #555;
44 | overflow: hidden;
45 | height: 300px;
46 | -webkit-transition: height .25s;
47 | -moz-transition: height .25s;
48 | -o-transition: height .25s;
49 | -ms-transition: height .25s;
50 | transition: height .25s;
51 | }
52 | .repo .page {
53 | background: #f8f8f8;
54 | border: 4px solid rgba(0, 0, 0, 0.08);
55 | border-radius: 3px;
56 | -ms-filter: "alpha(opacity=0)";
57 | filter: alpha(opacity=0);
58 | opacity: 0;
59 | left: 100%;
60 | width: 740px;
61 | position: absolute;
62 | -webkit-transition: all .25s;
63 | -moz-transition: all .25s;
64 | -o-transition: all .25s;
65 | -ms-transition: all .25s;
66 | transition: all .25s;
67 | }
68 | .repo .page.visible {
69 | left: 1%!important;
70 | -ms-filter: "alpha(opacity=100)";
71 | filter: alpha(opacity=100);
72 | opacity: 1;
73 | display: block;
74 | padding: 10px;
75 | }
76 | .repo .page.left {
77 | left: -100%}
78 | .repo .loader {
79 | position: absolute;
80 | display: block;
81 | width: 100%;
82 | height: 300px;
83 | top: 0;
84 | left: 0;
85 | background: url(https://a248.e.akamai.net/assets.github.com/images/spinners/octocat-spinner-64.gif?1329872007) no-repeat center 50%}
86 | .repo.loaded .loader {
87 | display: none;
88 | }
89 | .repo h1 {
90 | padding: 0 0 0 10px;
91 | font-family: sans-serif;
92 | font-size: 20px;
93 | line-height: 26px;
94 | color: #000;
95 | font-weight: normal;
96 | }
97 | .repo h1 a:nth-of-type(1), .repo h1 a.active {
98 | font-weight: bold;
99 | }
100 | .repo h1 a.active, .repo h1 a.active:active, .repo h1 a.active:visited, .repo h1 a.active:hover {
101 | color: #000;
102 | }
103 | .repo h1 a, .repo h1 a:active, .repo h1 a:visited, .repo h1 a:hover {
104 | color: #4183c4;
105 | text-decoration: none;
106 | }
107 | .repo h1 a:after {
108 | content: "/";
109 | color: #999;
110 | padding: 0 5px;
111 | font-weight: normal;
112 | }
113 | .repo h1 a:last-child:after {
114 | content: ""}
115 | .repo .page, .repo ul {
116 | zoom: 1;
117 | }
118 | .repo .page:before, .repo .page:after, .repo ul:before, .repo ul:after {
119 | content: "";
120 | display: table;
121 | }
122 | .repo .page:after, .repo ul:after {
123 | clear: both;
124 | }
125 | .repo ul {
126 | border: 1px solid rgba(0, 0, 0, 0.25);
127 | margin: 0;
128 | padding: 0;
129 | }
130 | .repo li {
131 | width: 100%;
132 | margin: 0;
133 | padding: 0;
134 | float: left;
135 | border-bottom: 1px solid #ccc;
136 | position: relative;
137 | white-space: nowrap;
138 | }
139 | .repo li.titles {
140 | background: -webkit-linear-gradient(#fafafa, #eaeaea);
141 | background: -moz-linear-gradient(#fafafa, #eaeaea);
142 | background: -o-linear-gradient(#fafafa, #eaeaea);
143 | background: -ms-linear-gradient(#fafafa, #eaeaea);
144 | background: linear-gradient(#fafafa, #eaeaea);
145 | font-weight: bold;
146 | padding: 10px 10px 8px 36px;
147 | text-shadow: 0 1px 0 #fff;
148 | }
149 | .repo li:before {
150 | content: " ";
151 | font-family: "Octicons Regular";
152 | position: absolute;
153 | top: 10px;
154 | left: 10px;
155 | font-size: 18px;
156 | -webkit-font-smoothing: antialiased;
157 | }
158 | .repo li.dir:before {
159 | content: " ";
160 | color: #80a6cd;
161 | }
162 | .repo li.titles:before, .repo li.back:before {
163 | content: ""}
164 | .repo li:last-child {
165 | border: 0;
166 | padding-bottom: none;
167 | margin: 0;
168 | }
169 | .repo li a, .repo li a:visited, .repo li a:active {
170 | color: #4183c4;
171 | width: 100%;
172 | padding: 10px 10px 8px 36px;
173 | display: block;
174 | text-decoration: none;
175 | }
176 | .repo li a:hover {
177 | text-decoration: underline;
178 | }
179 | .repo li span {
180 | display: inline-block;
181 | }
182 | .repo li span:nth-of-type(1) {
183 | width: 30%}
184 | .repo li span:nth-of-type(2) {
185 | width: 20%}
186 | .repo li span:nth-of-type(3) {
187 | width: 40%}
188 | .repo .vg-container {
189 | position: relative;
190 | overflow: auto;
191 | white-space: pre!important;
192 | word-wrap: normal!important;
193 | }
194 | .repo .vg-container, .repo .vg-code {
195 | border: 0;
196 | margin: 0;
197 | overflow: auto;
198 | }
199 | .repo .vg-code .vg-line, .repo .vg-gutter .vg-number {
200 | display: block;
201 | height: 1.5em;
202 | line-height: 1.5em!important;
203 | }
204 | .repo .vg-gutter {
205 | float: left;
206 | min-width: 20px;
207 | width: auto;
208 | -webkit-user-select: none;
209 | -moz-user-select: none;
210 | -ms-user-select: none;
211 | user-select: none;
212 | }
213 | .repo .vg-number {
214 | cursor: pointer;
215 | }
216 | .repo .vg-container {
217 | font-family: "Bitstream Vera Sans Mono", "Courier New", monospace;
218 | font-size: 13px;
219 | border: 1px solid #ddd;
220 | }
221 | .repo .vg-gutter {
222 | background-color: #ececec;
223 | border-right: 1px solid #ddd;
224 | text-align: right;
225 | color: #aaa;
226 | padding: 1em .5em;
227 | margin-right: .5em;
228 | }
229 | .repo .vg-code *::-moz-selection, .repo .vg-code *::-webkit-selection, .repo .vg-code *::selection, .repo .vg-line.vg-highlight {
230 | background-color: #ffc;
231 | }
232 | .repo .vg-line span.vg-highlight {
233 | color: blue;
234 | font-weight: bold;
235 | text-decoration: underline;
236 | }
237 | .repo .vg-container .vg-code {
238 | display: block;
239 | padding: 1em .5em;
240 | background: #fff;
241 | }
242 | .repo .vg-code {
243 | color: #000;
244 | background: #f8f8ff;
245 | border: 0;
246 | padding: .4em;
247 | }
248 | .repo .vg-code .comment, .repo .vg-code .template_comment, .repo .vg-code .diff .header, .repo .vg-code .javadoc {
249 | color: #998;
250 | font-style: italic;
251 | }
252 | .repo .vg-code .keyword, .repo .vg-code .css .rule .keyword, .repo .vg-code .winutils, .repo .vg-code .javascript .title, .repo .vg-code .lisp .title, .repo .vg-code .subst {
253 | color: #000;
254 | font-weight: bold;
255 | }
256 | .vg-code .number, .vg-code .hexcolor {
257 | color: #40a070;
258 | }
259 | .vg-code .string, .repo .vg-code .tag .value, .repo .vg-code .phpdoc, .repo .vg-code .tex .formula {
260 | color: #d14;
261 | }
262 | .repo .vg-code .title, .repo .vg-code .id {
263 | color: #900;
264 | font-weight: bold;
265 | }
266 | .repo .vg-code .javascript .title, .repo .vg-code .lisp .title, .repo .vg-code .subst {
267 | font-weight: normal;
268 | }
269 | .repo .vg-code .class .title, .repo .vg-code .haskell .label, .repo .vg-code .tex .command {
270 | color: #458;
271 | font-weight: bold;
272 | }
273 | .repo .vg-code .tag, .repo .vg-code .tag .title, .repo .vg-code .rules .property, .repo .vg-code .django .tag .keyword {
274 | color: #000080;
275 | font-weight: normal;
276 | }
277 | .repo .vg-code .attribute, .repo .vg-code .variable, .repo .vg-code .instancevar, .repo .vg-code .lisp .body {
278 | color: #008080;
279 | }
280 | .repo .vg-code .regexp {
281 | color: #009926;
282 | }
283 | .repo .vg-code .class {
284 | color: #458;
285 | font-weight: bold;
286 | }
287 | .repo .vg-code .symbol, .repo .vg-code .ruby .symbol .string, .repo .vg-code .ruby .symbol .keyword, .repo .vg-code .ruby .symbol .keymethods, .repo .vg-code .lisp .keyword, .repo .vg-code .tex .special, .repo .vg-code .input_number {
288 | color: #990073;
289 | }
290 | .repo .vg-code .builtin, .repo .vg-code .built_in, .repo .vg-code .lisp .title {
291 | color: #0086b3;
292 | }
293 | .repo .vg-code .codeprocessor, .repo .vg-code .pi, .repo .vg-code .doctype, .repo .vg-code .shebang, .repo .vg-code .cdata {
294 | color: #999;
295 | font-weight: bold;
296 | }
297 | .repo .vg-code .deletion {
298 | background: #fdd;
299 | }
300 | .repo .vg-code .addition {
301 | background: #dfd;
302 | }
303 | .repo .vg-code .diff .change {
304 | background: #0086b3;
305 | }
306 | .repo .vg-code .chunk {
307 | color: #aaa;
308 | }
309 | .repo .vg-code .tex .formula {
310 | -ms-filter: "alpha(opacity=50)";
311 | filter: alpha(opacity=50);
312 | opacity: .5;
313 | }
314 | .repo .image {
315 | padding: 1em .5em;
316 | text-align: center;
317 | }
318 | .repo .image .border-wrap {
319 | background-color: #fff;
320 | border: 1px solid #999;
321 | line-height: 0;
322 | display: inline-block;
323 | }
324 | .repo .image img {
325 | border: 1px solid #fff;
326 | background: url(https://a248.e.akamai.net/assets.github.com/images/modules/commit/trans_bg.gif?);
327 | }
--------------------------------------------------------------------------------
/public/app.js:
--------------------------------------------------------------------------------
1 | $(function(){
2 | 'use strict';
3 | // LOGIN
4 | function checkIfUserLoggedIn() {
5 | var userInfo = getLocalStorageData("lsUserInfo"); //----- lsUserInfo - localStorage user (name of string in LS)
6 | if (userInfo == undefined) {
7 | if (gp.user.username == "guest") {
8 | return "guest";
9 | }
10 | else {
11 | localStorage.setItem("lsUserInfo", JSON.stringify(gp.user));
12 | }
13 | } else {
14 | gp.user = userInfo;
15 | }
16 | }
17 |
18 | function makeHtml(md){
19 | var mdConverter = new Showdown.converter();
20 | return mdConverter.makeHtml(md);
21 | }
22 |
23 | var userReposContainer = $('#user-repos-select');
24 | function showUserRepos() {
25 | var urlUserRepos = defineUrl("/user/repos", gp.user.accessToken);
26 | $.getJSON(urlUserRepos, function(userRepos){
27 | var html = "";
28 |
29 | var m = 0;
30 | for (; m < userRepos.length; m++) {
31 | html += "";
32 | }
33 | userReposContainer.append(html);
34 | });
35 |
36 | var urlUserOrgs = defineUrl("/user/orgs", gp.user.accessToken);
37 | $.getJSON(urlUserOrgs, function(userOrgs){
38 | var userOrgsArray = [];
39 |
40 | var i = 0;
41 | for (; i < userOrgs.length; i++) {
42 | userOrgsArray.push(userOrgs[i].login);
43 | }
44 |
45 | var k = 0;
46 | for (; k < userOrgsArray.length; k++) {
47 | var urlUserOrgsRepos = defineUrl("/orgs/" + userOrgsArray[k] + "/repos", gp.user.accessToken);
48 | $.getJSON(urlUserOrgsRepos, function(orgRepos){
49 | var html ="";
50 |
51 | var l = 0;
52 | for (; l < orgRepos.length; l++) {
53 | html += "";
54 | }
55 | userReposContainer.append(html);
56 | });
57 | }
58 | });
59 | }
60 |
61 | function checkValidity() {
62 | var isValid = true;
63 | if ($("#issue-title").get(0).checkValidity() == false) {
64 | $("#issue-title").addClass("not-valid");
65 | isValid = false;
66 | } else {
67 | $("#issue-title").removeClass("not-valid");
68 | }
69 | if ($("#user-repos-select").val() == "Choose repository") {
70 | $("#user-repos-select").addClass("not-valid");
71 | isValid = false;
72 | } else {
73 | $("#user-repos-select").removeClass("not-valid");
74 | }
75 | return isValid;
76 | }
77 |
78 | $("#create-issue-button").on("click", function(e) {
79 | e.preventDefault();
80 |
81 | if (checkValidity() == true) {
82 | $("#create-poll-page .page-container").removeClass("visible").addClass("invisible");
83 | $("#create-poll-page").spin();
84 |
85 | var issueTitle = $("#issue-title").val();
86 | var issueDescription = $("#issue-description").val();
87 | var issueRepoFullname = $("#user-repos-select").val();
88 | var issueMandatoryVoter = $("#mandatory-voter").val();
89 | var issueLabel = $("issue-label").val();
90 |
91 | var issueData = {"title": issueTitle, "body": issueDescription};
92 |
93 | var url = defineUrl('/repos/' + issueRepoFullname + '/issues', gp.user.accessToken);
94 |
95 | $.post(url, JSON.stringify(issueData), function(data) {
96 | $("#create-poll-page .page-container").removeClass("invisible").addClass("visible");
97 | $("#create-poll-page").spin(false);
98 |
99 | var url = data.html_url.split("/")
100 |
101 | page("/" + url[3] + "/" + url[4] + "/" + url[5] + "/" + url[6]);
102 | });
103 | }
104 | });
105 |
106 | function logOutUser() {
107 | localStorage.clear();
108 | page('/logout');
109 | }
110 |
111 | $("#logout").on("click", logOutUser);
112 |
113 | $("#home").on("click", function(e){
114 | e.preventDefault()
115 | page("/");
116 | });
117 | // POLL PAGE
118 |
119 | function buildPollPage(urlIssue, urlComments){
120 | $("#poll-page .page-container").removeClass("visible").addClass("invisible");
121 | $("#poll-page").spin();
122 |
123 |
124 | $.getJSON(urlIssue, function(issueData){
125 | $("#poll-page .page-container").removeClass("invisible").addClass("visible");
126 | $("#poll-page").spin(false);
127 |
128 | var pollTitleContainer = $('#poll-title');
129 | var issueLinkContainer = pollTitleContainer.parent();
130 | var pollDescriptionContainer = $("#poll-description");
131 |
132 | pollTitleContainer.html(issueData.title);
133 | issueLinkContainer.attr("href", issueData.html_url);
134 | if (issueData.body) {
135 | var html = makeHtml(issueData.body);
136 | pollDescriptionContainer.html(html);
137 | }
138 | }).error(function() {
139 | $("#results").removeClass("visible").addClass("invisible");
140 | $("#vote-btns").removeClass("visible").addClass("invisible");
141 | $("p.error").removeClass("invisible").addClass("visible");
142 | });
143 |
144 | $.getJSON(urlComments, function(issueCommentsData){
145 | var issueCommentsContainer = $('#issue-comments');
146 |
147 | var yesArray = [];
148 | var noArray = [];
149 |
150 | var i = 0;
151 | for (; i < issueCommentsData.length; i++) {
152 | var commentText = issueCommentsData[i].body;
153 | if (commentText.indexOf("+1") > -1) {
154 | yesArray.push(issueCommentsData[i].user.login);
155 | } else if (commentText.indexOf("-1") > -1) {
156 | noArray.push(issueCommentsData[i].user.login);
157 | }
158 | }
159 |
160 | if ((yesArray.length == 0) && (noArray.length == 0)) {
161 | $("#no-results").removeClass("invisible").addClass("visible");
162 | $("#results, #chart-panel").removeClass("visible").addClass("invisible");
163 | $("#vote-btns").removeClass("invisible").addClass("visible");
164 | } else {
165 | updatePollResultsView(yesArray, noArray);
166 | }
167 |
168 | var yesCommentBody = {"body": "+1"};
169 | var noCommentBody = {"body": "-1"};
170 |
171 | $('#vote-btns').on('click', 'button', function() {
172 | if ($(this).attr('id') == 'yes-btn') {
173 | $.post(urlComments, JSON.stringify(yesCommentBody));
174 |
175 | yesArray.push(gp.user.username);
176 | updatePollResultsView(yesArray, noArray);
177 | } else if ($(this).attr('id') == 'no-btn') {
178 | $.post(urlComments, JSON.stringify(noCommentBody));
179 |
180 | noArray.push(gp.user.username);
181 | updatePollResultsView(yesArray, noArray);
182 | }
183 | });
184 |
185 | }).error(function() {
186 | $("#results").removeClass("visible").addClass("invisible");
187 | $("#vote-btns").removeClass("visible").addClass("invisible");
188 | $("p.error").removeClass("invisible").addClass("visible");
189 | });
190 | }
191 |
192 | // COMMON FUNCTIONS
193 |
194 | function getLocalStorageData(key) {
195 | var key = localStorage.getItem(key);
196 | if (key == undefined) {
197 | return undefined;
198 | } else {
199 | return JSON.parse(key);
200 | }
201 | }
202 |
203 | function showPage(pagename, functionDeclaration) {
204 | $(".page.visible").removeClass('visible').addClass('invisible');
205 |
206 | $("#" + pagename).removeClass('invisible').addClass('visible');
207 |
208 | if (functionDeclaration) {
209 | functionDeclaration();
210 | }
211 | }
212 |
213 | function updatePollResultsView(yesArray, noArray) {
214 | var yesContainer = $('#yes span');
215 | var noContainer = $('#no span');
216 |
217 | yesArray = _.uniq(yesArray);
218 | noArray = _.uniq(noArray);
219 |
220 | yesContainer.text(yesArray.length);
221 | noContainer.text(noArray.length);
222 |
223 | var data = [{ key : "Poll Results",
224 | values : [{"label": "yes", "value": yesArray.length},
225 | {"label": "no", "value": noArray.length}]
226 | }];
227 |
228 | // pie(data, "#pie-chart");
229 | c3.generate({
230 | bindto: "#chart-panel",
231 | data: {
232 | // iris data from R
233 | columns: [
234 | ['yes', yesArray.length],
235 | ['no', noArray.length],
236 | ],
237 | type : 'pie'
238 | }
239 | });
240 | $("#results, #chart-panel").removeClass("invisible").addClass("visible");
241 | }
242 |
243 | function defineUrl(relativePath, accessToken) {
244 | var path = "https://api.github.com" + relativePath;
245 | if (accessToken) {
246 | path += "?access_token=" + gp.user.accessToken;
247 | }
248 | return path;
249 | }
250 |
251 | // ROUTER
252 |
253 | page('/:user/:repoName/issues/:number', function(ctx){
254 | if (checkIfUserLoggedIn() == 'guest') {
255 | $("#login").removeClass("invisible").addClass("inline-visible");
256 |
257 | var urlIssue = defineUrl("/repos/" + ctx.params.user + "/" + ctx.params.repoName + "/issues/" + ctx.params.number);
258 | var urlComments = defineUrl("/repos/" + ctx.params.user + "/" + ctx.params.repoName + "/issues/" + ctx.params.number + "/comments");
259 |
260 | $("#vote-btns").removeClass("visible").addClass("invisible");
261 | } else {
262 | $("#login").removeClass("visible").addClass("invisible");
263 | $("#welcome, header img, #username, #logout").removeClass("invisible").addClass("inline-visible");
264 | $("header img").attr("src", gp.user.avatar);
265 | $("#username").attr("href", gp.user.profileUrl).text(gp.user.username);
266 |
267 | var urlIssue = defineUrl("/repos/" + ctx.params.user + "/" + ctx.params.repoName + "/issues/" + ctx.params.number, gp.user.accessToken);
268 | var urlComments = defineUrl("/repos/" + ctx.params.user + "/" + ctx.params.repoName + "/issues/" + ctx.params.number + "/comments", gp.user.accessToken);
269 |
270 | $("#vote-btns").removeClass("invisible").addClass("visible");
271 | }
272 |
273 | showPage("poll-page", function(){
274 | buildPollPage(urlIssue, urlComments);
275 | });
276 | });
277 |
278 | page('', function(){
279 | if (checkIfUserLoggedIn() == 'guest') {
280 | $("#login").removeClass("invisible").addClass("inline-visible");
281 | showPage("app-intro-page");
282 | } else {
283 | $("#login").removeClass("visible").addClass("invisible");
284 | $("#welcome, header img, #username, #logout").removeClass("invisible").addClass("inline-visible");
285 | $("header img").attr("src", gp.user.avatar);
286 | $("#username").attr("href", gp.user.profileUrl).text(gp.user.username);
287 |
288 | showPage("create-poll-page", showUserRepos);
289 | }
290 | });
291 |
292 | page.start({ click: false });
293 |
294 | });
--------------------------------------------------------------------------------