├── .gitignore
├── LICENSE.md
├── README.md
├── bundle.js
├── config.js
├── data.json
├── elements
└── auth.js
├── form.js
├── github
├── blob-get.js
├── blob-update.js
├── branch-create.js
├── commit-create.js
├── fork-and-branch.js
├── fork-create.js
├── fork-list.js
├── index.js
├── latest-commit.js
└── pull-request.js
├── index.html
├── index.js
├── modal.js
├── modify-state.js
├── package.json
├── schema.js
└── style.css
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | *.log
4 | tmp
5 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/editdata/submit-data/ec2f11ff59982ac4d109a397674abca2c2fa750b/LICENSE.md
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # submit-data
2 |
3 | This is a work-in-progress demo: [submitdata.surge.sh](http://submitdata.surge.sh)
4 |
5 | ## Submit data to a JSON file via a form using the GitHub API
6 |
7 | It's useful to keep small datasets in GitHub repositories, particularly for projects that are more content-focused like maps and data visualizations that are hosted on gh-pages.
8 |
9 | It would be very useful to provide a page where users can log in with the GitHub API, fill out a form, and have the fields appended to a JSON file that is an array of objects.
10 |
11 | That's what this project will provide.
12 |
13 | ## What does it do?
14 |
15 | - A user logs in via GitHub on the site.
16 | - They fill out the form with the fields you have specified.
17 | - This project does the following actions:
18 | - creates a fork on the user's account if it doesn't exist already
19 | - creates a branch for their changes
20 | - creates a commit adding an object to the JSON file you specified
21 | - creates a pull request back to the source repository
22 |
23 | ## Todo
24 |
25 | The next stage for this project is to make it reusable, so that you can provide a similar form in your gh-pages hosted repositories.
26 |
27 | Similar functionality will also be added to the [editdata.org](http://github.com/editdata/editdata.org) project.
28 |
29 | ## License
30 | MIT
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | var config = {
2 | development: {
3 | client_id: '3399f4b75617c8ca5876',
4 | redirect_uri: 'http://127.0.0.1:9966',
5 | proxy: 'http://127.0.0.1:5000',
6 | scope: 'repo',
7 | github: {
8 | owner: 'editdata',
9 | repo: 'submit-data',
10 | branch: 'gh-pages'
11 | },
12 | site: {
13 | title: 'Submit Data',
14 | description: 'Submit a row to a csv/json file in a GitHub repo',
15 | slug: 'submit-data-dev-site',
16 | data: 'data.json'
17 | }
18 | },
19 | production: {
20 | client_id: 'e726b293f8f60b2e7170',
21 | redirect_uri: 'http://submitdata.surge.sh',
22 | proxy: 'https://submit-data-demo.herokuapp.com',
23 | scope: 'repo',
24 | github: {
25 | owner: 'editdata',
26 | repo: 'submit-data',
27 | branch: 'gh-pages'
28 | },
29 | site: {
30 | title: 'Submit Data',
31 | description: 'Submit a row to a csv/json file in a GitHub repo',
32 | slug: 'submit-data-demo-site',
33 | data: 'data.json'
34 | }
35 | }
36 | }
37 |
38 | var env = process.env.NODE_ENV || 'development'
39 | module.exports = config[env]
40 |
--------------------------------------------------------------------------------
/data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "title": "example",
4 | "description": "This is an example.",
5 | "url": "http://example.com"
6 | }
7 | ]
--------------------------------------------------------------------------------
/elements/auth.js:
--------------------------------------------------------------------------------
1 | var el = require('yo-yo')
2 |
3 | module.exports = function githubAuth (config, actions) {
4 | var url = 'https://github.com/login/oauth/authorize' +
5 | '?client_id=' + config.client_id +
6 | '&scope=' + config.scope +
7 | '&redirect_uri=' + config.redirect_uri
8 |
9 | function login (state) {
10 | return el`Sign in with GitHub `
11 | }
12 |
13 | function profile (state) {
14 | return el`
15 |
24 | `
25 | }
26 |
27 | function childElement (state) {
28 | if (state.user) return profile(state)
29 | else return login(state)
30 | }
31 |
32 | return {
33 | render: function virtualGithubAuth_render (state) {
34 | var className = 'github-auth'
35 | if (state.user) className += ' active'
36 | return el`${childElement(state)}
`
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/form.js:
--------------------------------------------------------------------------------
1 | var defaults = require('json-schema-defaults')
2 | var el = require('yo-yo')
3 |
4 | var schema = require('./schema')
5 |
6 | module.exports = function renderForm (state, actions) {
7 | var obj = defaults(schema)
8 | var keys = Object.keys(obj)
9 |
10 | function field (key) {
11 | var value = obj[key] ? obj[key] : ''
12 |
13 | return el`
14 |
15 | ${key}
16 |
17 |
18 | `
19 | }
20 |
21 | return el`
22 |
29 | `
30 | }
31 |
--------------------------------------------------------------------------------
/github/blob-get.js:
--------------------------------------------------------------------------------
1 | var request = require('xhr')
2 |
3 | module.exports = function getBlob (options, callback) {
4 | var requestOptions = {
5 | url: 'https://api.github.com/repos/' + options.github.owner + '/' + options.github.repo + '/contents/' + options.path,
6 | headers: { authorization: 'token ' + options.token },
7 | json: {
8 | ref: options.ref
9 | }
10 | }
11 |
12 | request(requestOptions, function (err, res, body) {
13 | if (err) return callback(err)
14 | if (body.message === 'Not Found') return callback(new Error(body.message))
15 | var content = window.atob(body.content)
16 | callback(err, body, JSON.parse(content))
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/github/blob-update.js:
--------------------------------------------------------------------------------
1 | var request = require('xhr')
2 | var base64 = require('base-64')
3 | var utf8 = require('utf8')
4 |
5 | module.exports = function updateBlob (options, callback) {
6 | var opts = {
7 | url: 'https://api.github.com/repos/' + options.github.owner + '/' + options.github.repo + '/contents/' + options.path,
8 | headers: { authorization: 'token ' + options.token },
9 | method: 'put',
10 | json: {
11 | path: options.path,
12 | message: options.message,
13 | content: base64.encode(utf8.encode(options.content)),
14 | branch: options.branch,
15 | sha: options.sha
16 | }
17 | }
18 |
19 | request(opts, function (err, res, body) {
20 | if (err) return callback(err)
21 | var save = {
22 | branch: options.branch,
23 | owner: options.owner,
24 | repo: options.repo,
25 | location: body.content,
26 | source: 'github'
27 | }
28 | callback(null, body, save)
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/github/branch-create.js:
--------------------------------------------------------------------------------
1 | var request = require('xhr')
2 | var moment = require('moment')
3 |
4 | module.exports = function createBranch (options, callback) {
5 | var requestOptions = {
6 | url: 'https://api.github.com/repos/' + options.github.owner + '/' + options.github.repo + '/git/refs',
7 | headers: { authorization: 'token ' + options.token },
8 | method: 'POST',
9 | json: {
10 | ref: 'refs/heads/submit-data-' + moment().format('MMM-Do-YY-h-mm-ss'),
11 | sha: options.commit.sha
12 | }
13 | }
14 |
15 | request(requestOptions, function (err, res, body) {
16 | if (err) return callback(err)
17 | if (body.message === 'Not Found') return callback(new Error(body.message))
18 | callback(err, body)
19 | })
20 | }
21 |
--------------------------------------------------------------------------------
/github/commit-create.js:
--------------------------------------------------------------------------------
1 | var request = require('xhr')
2 |
3 | module.exports = function fork (options, callback) {
4 | var requestOptions = {
5 | url: 'https://api.github.com/repos/' + options.github.owner + '/' + options.github.repo + '/forks',
6 | headers: { authorization: 'token ' + options.token },
7 | method: 'POST',
8 | json: true
9 | }
10 |
11 | request(requestOptions, function (err, res, body) {
12 | if (err) return callback(err)
13 | if (body.message === 'Not Found') return callback(new Error(body.message))
14 | // var content = window.atob(body.content)
15 |
16 | callback(err, body)
17 | })
18 | }
19 |
--------------------------------------------------------------------------------
/github/fork-and-branch.js:
--------------------------------------------------------------------------------
1 | var request = require('xhr')
2 | var list = require('./fork-list')
3 | var createFork = require('./fork-create')
4 | var createBranch = require('./branch-create')
5 | var getCommit = require('./latest-commit')
6 |
7 | /**
8 | * Creates a fork on the user's account if needed.
9 | * Creates a branch if fork already exists, or after the fork was created.
10 | */
11 | module.exports = function forkAndBranch (options, callback) {
12 | getCommit(options, function (err, commit) {
13 | options.commit = commit
14 | list(options, findFork)
15 | })
16 |
17 | function findFork (err, forks) {
18 | if (err) return callback(err)
19 |
20 | var fork = forks.filter(function (f) {
21 | return f.owner.login === options.user
22 | })
23 |
24 | if (fork && fork[0]) {
25 | fork = fork[0]
26 | createBranch({
27 | github: { owner: options.user, repo: options.github.repo },
28 | commit: options.commit,
29 | token: options.token
30 | }, function (err, res) {
31 | if (err) return callback(err)
32 | callback(null, fork, res)
33 | })
34 | } else {
35 | createFork(options, function (err, forkResponse) {
36 | if (err) return callback(err)
37 | fork = forkResponse
38 | createBranch({
39 | github: { owner: fork.owner.login, repo: options.github.repo },
40 | commit: options.commit,
41 | token: options.token
42 | }, function (err, res) {
43 | if (err) return callback(err)
44 | callback(null, fork, res)
45 | })
46 | })
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/github/fork-create.js:
--------------------------------------------------------------------------------
1 | var request = require('xhr')
2 |
3 | module.exports = function fork (options, callback) {
4 | var requestOptions = {
5 | url: 'https://api.github.com/repos/' + options.github.owner + '/' + options.github.repo + '/forks',
6 | headers: { authorization: 'token ' + options.token },
7 | method: 'POST'
8 | }
9 |
10 | request(requestOptions, function (err, res, body) {
11 | if (err) return callback(err)
12 | if (body.message === 'Not Found') return callback(new Error(body.message))
13 | body = JSON.parse(body)
14 | callback(err, body)
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/github/fork-list.js:
--------------------------------------------------------------------------------
1 | var request = require('xhr')
2 |
3 | module.exports = function fork (options, callback) {
4 | var requestOptions = {
5 | url: 'https://api.github.com/repos/' + options.github.owner + '/' + options.github.repo + '/forks',
6 | headers: { authorization: 'token ' + options.token },
7 | json: true
8 | }
9 |
10 | request(requestOptions, function (err, res, body) {
11 | if (err) return callback(err)
12 | if (body.message === 'Not Found') return callback(new Error(body.message))
13 | callback(err, body)
14 | })
15 | }
16 |
--------------------------------------------------------------------------------
/github/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | createFork: require('./fork-create'),
3 | listForks: require('./fork-list'),
4 | createPullRequest: require('./pull-request'),
5 | forkAndBranch: require('./fork-and-branch'),
6 | latestCommit: require('./latest-commit'),
7 | createCommit: require('./commit-create'),
8 | getBlob: require('./blob-get'),
9 | updateBlob: require('./blob-update')
10 | }
11 |
--------------------------------------------------------------------------------
/github/latest-commit.js:
--------------------------------------------------------------------------------
1 | var request = require('xhr')
2 |
3 | module.exports = function fork (options, callback) {
4 | var requestOptions = {
5 | url: 'https://api.github.com/repos/' + options.github.owner + '/' + options.github.repo + '/commits',
6 | headers: { authorization: 'token ' + options.token },
7 | data: { sha: options.github.branch },
8 | json: true
9 | }
10 |
11 | request(requestOptions, function (err, res, body) {
12 | if (err) return callback(err)
13 | if (body.message === 'Not Found') return callback(new Error(body.message))
14 | callback(err, body[0])
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/github/pull-request.js:
--------------------------------------------------------------------------------
1 | var request = require('xhr')
2 |
3 | module.exports = function pullRequest (options, callback) {
4 | var requestOptions = {
5 | url: 'https://api.github.com/repos/' + options.github.owner + '/' + options.github.repo + '/pulls',
6 | headers: { authorization: 'token ' + options.token },
7 | method: 'POST',
8 | json: {
9 | title: options.title,
10 | head: options.user + ':' + options.head,
11 | base: options.base
12 | // TODO: body message, maybe something like: body: 'Pull request created by Submit Data'
13 | }
14 | }
15 |
16 | request(requestOptions, function (err, res, body) {
17 | if (err) return callback(err)
18 | if (body.message === 'Not Found') return callback(new Error(body.message))
19 | callback(err, body)
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Submit Data demo | editdata.org
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var createAuth = require('github-static-auth')
2 | var createStore = require('store-emitter')
3 | var serialize = require('form-serialize')
4 | var cookie = require('cookie-cutter')
5 | var extend = require('xtend')
6 | var el = require('yo-yo')
7 |
8 | var config = require('./config')
9 | var data = require('./data.json')
10 | var modify = require('./modify-state')
11 | var github = require('./github')
12 |
13 | var formUI = require('./form')
14 | var createAuthUI = require('./elements/auth')
15 |
16 | var token = cookie.get(config.site.slug)
17 |
18 | var auth = createAuth({
19 | githubSecretKeeper: config.proxy,
20 | githubClientId: config.client_id
21 | })
22 |
23 | var authUI = createAuthUI(config, {
24 | onLogOut: function (e) {
25 | e.preventDefault()
26 | cookie.set(config.site.slug, '', { expires: new Date(0) })
27 | store({ type: 'user:logout' })
28 | }
29 | })
30 |
31 | var store = createStore(modify, {
32 | site: config.site,
33 | github: config.github,
34 | data: data,
35 | submitted: false,
36 | loading: true,
37 | flash: null,
38 | modal: null,
39 | user: null,
40 | item: {},
41 | origin: null,
42 | fork: null
43 | })
44 |
45 | store.on('*', function (action, state) {
46 | el.update(document.getElementById('app'), render(state))
47 | })
48 |
49 | function render (state) {
50 | return layout(state)
51 | }
52 |
53 | document.body.appendChild(render(store.initialState()))
54 |
55 | if (token) {
56 | auth.getProfile(token, function (err, profile) {
57 | if (err) return store({ type: 'error', error: err })
58 | store({ type: 'loading:complete' })
59 | store({ type: 'user:login', profile: profile, token: token })
60 | })
61 | } else if (auth.getCode()) {
62 | auth.login(function (err, profile, token) {
63 | if (err) return store({ type: 'error', error: err })
64 | store({ type: 'loading:complete' })
65 | store({ type: 'user:login', profile: profile, token: token })
66 | cookie.set(config.site.slug, token)
67 | window.location = config.redirect_uri
68 | })
69 | } else {
70 | store({ type: 'loading:complete' })
71 | }
72 |
73 | function form (state) {
74 | function onsubmit (e) {
75 | e.preventDefault()
76 | store({ type: 'loading' })
77 | var fields = serialize(e.target, { hash: true, empty: true })
78 | store({ type: 'form:submit', fields: fields })
79 | var opts = { github: state.github, token: state.user.token, user: state.user.profile.login }
80 | github.forkAndBranch(opts, getBranch)
81 |
82 | function getBranch (err, fork, branch) {
83 | if (err) return store({ type: 'error', error: err })
84 |
85 | var opts = {
86 | github: { owner: fork.owner.login, repo: state.github.repo },
87 | token: state.user.token,
88 | ref: branch.ref,
89 | path: state.site.data
90 | }
91 |
92 | github.getBlob(opts, writeBlob)
93 |
94 | function writeBlob (err, res, file) {
95 | if (err) return store({ type: 'error', error: err })
96 | file.push(fields)
97 |
98 | var opts = {
99 | github: { owner: fork.owner.login, repo: state.github.repo },
100 | token: state.user.token,
101 | ref: branch.ref,
102 | path: state.site.data,
103 | content: JSON.stringify(file),
104 | sha: res.sha,
105 | branch: branch.ref.split('/')[2],
106 | message: 'Updated ' + state.site.data
107 | }
108 |
109 | github.updateBlob(opts, pullRequest)
110 | }
111 |
112 | function pullRequest (err, res) {
113 |
114 | if (err) return store({ type: 'error', error: err })
115 | var opts = {
116 | github: state.github,
117 | user: state.user.profile.login,
118 | token: state.user.token,
119 | title: 'Add new item to ' + state.site.data,
120 | head: branch.ref,
121 | base: state.github.branch
122 | }
123 |
124 | github.createPullRequest(opts, function (err, res) {
125 | store({ type: 'loading:complete' })
126 | store({
127 | type: 'submitted',
128 | fork: fork,
129 | branch: branch,
130 | pullRequest: res
131 | })
132 | })
133 | }
134 | }
135 | }
136 |
137 | return el`
138 |
139 |
Submit a new item
140 | ${formUI(state, { onsubmit: onsubmit })}
141 |
142 | `
143 | }
144 |
145 | function content (state) {
146 | var elements
147 | if (state.loading) elements = loading(state)
148 | else if (state.user && state.submitted) elements = thanks(state)
149 | else if (state.user) elements = form(state)
150 | else elements = landing(state)
151 |
152 | return el`
153 |
154 |
155 | ${elements}
156 |
158 | `
159 | }
160 |
161 | function loading (state) {
162 | return el`
163 |
164 | `
165 | }
166 |
167 | function thanks (state) {
168 | console.log('thanks state', state)
169 | return el`
170 |
177 | `
178 | }
179 |
180 | function landing (state) {
181 | var url = 'https://github.com/login/oauth/authorize' +
182 | '?client_id=' + config.client_id +
183 | '&scope=' + config.scope +
184 | '&redirect_uri=' + config.redirect_uri
185 |
186 | return el`
187 |
188 |
Create a pull request via the GitHub API
189 |
Log in to add an item to a JSON file by filling out a form!
190 | This site will create a fork of this repo on your account, create a branch for your submission, save your submission, then create a pull request on the source repository.
191 |
Sign in with GitHub
192 |
193 | `
194 | }
195 |
196 | function header (state) {
197 | return el`
198 |
204 | `
205 | }
206 |
207 | function layout (state) {
208 | return el`
209 |
210 | ${header(state)}
211 | ${content(state)}
212 |
213 | `
214 | }
215 |
--------------------------------------------------------------------------------
/modal.js:
--------------------------------------------------------------------------------
1 | var el = require('yo-yo')
2 |
3 | function createModal (state) {
4 | return el`
5 |
6 | `
7 | }
8 |
--------------------------------------------------------------------------------
/modify-state.js:
--------------------------------------------------------------------------------
1 | var extend = require('xtend')
2 |
3 | var modifiers = {
4 | 'loading': function (action, state) {
5 | return extend(state, { loading: true })
6 | },
7 | 'loading:complete': function (action, state) {
8 | return extend(state, { loading: false })
9 | },
10 | 'user:login': function (action, state) {
11 | return extend(state, {
12 | user: {
13 | profile: action.profile,
14 | token: action.token
15 | }
16 | })
17 | },
18 | 'user:logout': function (action, state) {
19 | return extend(state, { user: null })
20 | },
21 | 'form:submit': function (action, state) {
22 | var item = action.fields
23 | var data = state.data
24 | data.push(item)
25 | return extend(state, {
26 | item: item,
27 | data: data
28 | })
29 | },
30 | 'submitted': function (action, state) {
31 | return extend(state, {
32 | submitted: true,
33 | fork: action.fork,
34 | branch: action.branch,
35 | pullRequest: action.pullRequest
36 | })
37 | },
38 | 'error': function (action, state) {
39 | return extend(state, { error: action.error })
40 | }
41 | }
42 |
43 | module.exports = function modifier (action, state) {
44 | console.log('-------------------------------------')
45 | console.log('action:', action.type, action)
46 | var newState = modifiers[action.type](action, state)
47 | console.log('.....................................')
48 | console.log('new state:', newState)
49 | //console.log('-------------------------------------')
50 | return newState
51 | }
52 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "submit-data",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "standard && tape tests/*.js | tap-spec",
8 | "bundle": "browserify index.js -t yo-yoify -t [ envify --NODE_ENV production ] -o bundle.js",
9 | "start": "budo index.js:bundle.js --live --css style.css"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/editdata/submit-data.git"
14 | },
15 | "keywords": [],
16 | "author": "",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/editdata/submit-data/issues"
20 | },
21 | "homepage": "https://github.com/editdata/submit-data#readme",
22 | "devDependencies": {
23 | "browserify": "^13.0.0",
24 | "budo": "^8.1.0",
25 | "envify": "^3.4.0",
26 | "standard": "^6.0.8",
27 | "tap-spec": "^4.1.1",
28 | "tape": "^4.5.1",
29 | "testron": "^1.2.0"
30 | },
31 | "dependencies": {
32 | "base-64": "^0.1.0",
33 | "cookie-cutter": "^0.1.1",
34 | "form-serialize": "^0.7.1",
35 | "github-static-auth": "^1.0.0",
36 | "json-schema-defaults": "^0.1.1",
37 | "moment": "^2.12.0",
38 | "sheet-router": "^2.0.1",
39 | "sheetify": "^5.0.0",
40 | "store-emitter": "^2.1.0",
41 | "utf8": "^2.1.1",
42 | "xhr": "^2.2.0",
43 | "xtend": "^4.0.1",
44 | "yo-yo": "^1.1.1",
45 | "yo-yoify": "^1.0.2"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/schema.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | required: true,
3 | type: 'object',
4 | properties: {
5 | title: {
6 | required: true,
7 | type: 'string',
8 | default: null
9 | },
10 | description: {
11 | type: 'string',
12 | default: null
13 | },
14 | url: {
15 | type: 'string',
16 | default: null
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | font-family: "Source Sans Pro";
3 | width: 100%;
4 | height: 100%;
5 | padding: 0px;
6 | margin: 0px;
7 | }
8 |
9 | #app {
10 | width: 100%;
11 | height: 100%;
12 | }
13 |
14 | .app-header {
15 | background-color: #efefef;
16 | padding: 10px 0px;
17 | height: 40px;
18 | border-bottom: 1px solid #cecece;
19 | }
20 |
21 | .app-header .app-title {
22 | margin: 0px;
23 | display: inline-block;
24 | font-size: 20px;
25 | font-weight: 500;
26 | line-height: 40px;
27 | }
28 |
29 | .app-header .github-auth {
30 | float: right;
31 | }
32 |
33 | .app-header .profile {
34 |
35 | }
36 |
37 | .app-header .profile-avatar {
38 | height: 20px;
39 | width: 20px;
40 | vertical-align: middle;
41 | }
42 |
43 | .field {
44 | margin-bottom: 20px;
45 | }
46 |
47 | .field-text {
48 | width: 100%;
49 | font-size: 18px;
50 | padding: 5px;
51 | }
52 |
53 |
54 | .container {
55 | width: 80%;
56 | max-width: 800px;
57 | margin: 0px auto;
58 | }
59 |
60 | .button,
61 | .button:link,
62 | .button:visited,
63 | button,
64 | input[type="submit"],
65 | input[type="reset"],
66 | input[type="button"] {
67 | background-color: #fff;
68 | border: 1px solid #aaa;
69 | border-radius: 0.2em;
70 | box-sizing: border-box;
71 | color: #666;
72 | cursor: pointer;
73 | display: inline-block;
74 | font-family: sans-serif;
75 | font-size: 0.9em;
76 | font-weight: 500;
77 | height: 40px;
78 | letter-spacing: 0.05em;
79 | line-height: 38px;
80 | margin-bottom: 0em;
81 | padding: 0px 10px 0px 10px;
82 | text-align: center;
83 | text-decoration: none;
84 | vertical-align: top;
85 | white-space: nowrap;
86 | }
87 |
88 | .button:hover,
89 | .button:focus,
90 | button:hover,
91 | input[type="submit"]:hover,
92 | input[type="reset"]:hover,
93 | input[type="button"]:hover,
94 | button:focus,
95 | input[type="submit"]:focus,
96 | input[type="reset"]:focus,
97 | input[type="button"]:focus {
98 | border: 1px solid #444;
99 | color: #555;
100 | }
101 |
102 | .button:active,
103 | button:active,
104 | input[type="submit"]:active,
105 | input[type="reset"]:active,
106 | input[type="button"]:active {
107 | color: #000;
108 | border: 1px solid #000;
109 | }
110 |
111 | .loading {
112 | margin: 0px auto;
113 | }
114 |
115 |
--------------------------------------------------------------------------------