├── .editorconfig
├── .gitignore
├── README.md
├── actions
└── user.js
├── api
└── index.js
├── components
├── app
│ ├── corner.vue
│ ├── index.vue
│ └── style.css
├── computeOften
│ ├── index.vue
│ └── style.css
└── inputBox
│ ├── index.vue
│ └── style.css
├── css
└── vars.css
├── index.html
├── index.js
├── package.json
├── reducers
├── computeOften.js
├── index.js
└── loadStatus.js
└── store.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = crlf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
3 | # Logs
4 | logs
5 | *.log
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
19 | .grunt
20 |
21 | # node-waf configuration
22 | .lock-wscript
23 |
24 | # Compiled binary addons (http://nodejs.org/api/addons.html)
25 | build/Release
26 |
27 | # Dependency directory
28 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
29 | node_modules
30 | .DS_Store
31 | .AppleDouble
32 | .LSOverride
33 |
34 | # Icon must end with two \r
35 | Icon
36 |
37 |
38 | # Thumbnails
39 | ._*
40 |
41 | # Files that might appear on external disk
42 | .Spotlight-V100
43 | .Trashes
44 |
45 | # Directories potentially created on remote AFP share
46 | .AppleDB
47 | .AppleDesktop
48 | Network Trash Folder
49 | Temporary Items
50 | .apdisk
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # how often ?
2 |
3 | Made with [Tooling](https://github.com/egoist/tooling), [Vue](https://github.com/vuejs/vue) and [Redux](https://github.com/rackt/redux)([Revue](https://github.com/egoist/revue)) http://often.surge.sh/
4 |
5 | ## Run
6 |
7 | clone the repo and cd to the directory:
8 |
9 | ```bash
10 | npm install
11 | # dev
12 | npm start
13 | # bundle
14 | npm run build
15 | ```
16 |
17 | ## License
18 |
19 | MIT © [EGOIST](https://github.com/egoist)
20 |
--------------------------------------------------------------------------------
/actions/user.js:
--------------------------------------------------------------------------------
1 | import { createAction } from 'redux-actions'
2 | import { fetchUser as fetchUserApi } from '../api'
3 |
4 | export const loading = createAction('loading')
5 | export const fetchUser = createAction('compute often')
6 | export const loaded = createAction('loaded')
7 | export const notFound = createAction('not found')
8 |
9 | export function computeOften(username) {
10 | return async dispatch => {
11 | dispatch(loading())
12 | const user = await fetchUserApi(username)
13 | if (user.status === 404) {
14 | dispatch(notFound({ username }))
15 | } else {
16 | console.log()
17 | dispatch(fetchUser(await user.json()))
18 | }
19 | dispatch(loaded())
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/api/index.js:
--------------------------------------------------------------------------------
1 | /* global fetch */
2 |
3 | export async function fetchUser(username) {
4 | return await fetch(`https://api.github.com/users/${username}`)
5 | }
6 |
--------------------------------------------------------------------------------
/components/app/corner.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/components/app/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 |
27 |
--------------------------------------------------------------------------------
/components/app/style.css:
--------------------------------------------------------------------------------
1 | @import '../../css/vars.css';
2 |
3 | body {
4 | margin: 0;
5 | font: 14px/1.4 "Helvetica Neue", arial, serif;
6 | }
7 |
8 | h1,h2,h3,h4,h5 {
9 | margin: 0;
10 | }
11 |
12 | .app {
13 | .section-a {
14 | background-color: #f9f9f9;
15 | height: 50vmin;
16 | text-align: center;
17 | .sitename {
18 | padding: 100px 0 50px 0;
19 | font-size: 3rem;
20 | a {
21 | color: $linkColor;
22 | text-decoration: none;
23 | }
24 | }
25 | }
26 | .section-b {
27 | height: 50vmin;
28 | text-align: center;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/components/computeOften/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
16 |
17 |
18 |
30 |
--------------------------------------------------------------------------------
/components/computeOften/style.css:
--------------------------------------------------------------------------------
1 | @import '../../css/vars.css';
2 |
3 | .compute-often {
4 | font-size: 3rem;
5 | padding-top: 50px;
6 | a {
7 | color: $linkColor;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/components/inputBox/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 | Loading...
9 |
10 |
11 |
39 |
--------------------------------------------------------------------------------
/components/inputBox/style.css:
--------------------------------------------------------------------------------
1 | .input {
2 | border: 1px solid #ccc;
3 | outline: none;
4 | border-radius: 2px;
5 | padding: 8px 12px;
6 | font-size: 1rem;
7 | transition: box-shadow .3s;
8 | background-color: white;
9 | width: 240px;
10 | &:focus {
11 | box-shadow: 0 0 0 3px rgba(48,144,228,.1);
12 | }
13 | }
14 |
15 | .loading {
16 | margin-left: 5px;
17 | }
18 |
--------------------------------------------------------------------------------
/css/vars.css:
--------------------------------------------------------------------------------
1 | $linkColor: #2c97e8;
2 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 | manifest="<%=htmlWebpackPlugin.files.manifest %>"<% } %>>
3 |
4 |
5 |
6 | <%=htmlWebpackPlugin.options.title || 'Webpack App'%>
7 | <% if (htmlWebpackPlugin.files.favicon) { %>
8 |
9 | <% } %>
10 | <% for (var css in htmlWebpackPlugin.files.css) { %>
11 |
12 | <% } %>
13 |
14 |
15 |
16 |
17 | <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
18 |
19 | <% } %>
20 |
21 |
22 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Revue from 'revue'
3 | import app from './components/app'
4 | import store from './store'
5 |
6 | Vue.use(Revue, {
7 | store
8 | })
9 |
10 | if (__DEV__) {
11 | Vue.config.debug = true
12 | window.Vue = Vue
13 | window.store = store
14 | }
15 |
16 | new Vue({
17 | el: 'body',
18 | components: {
19 | app
20 | }
21 | })
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "how-often",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "npm run clean && tooling watch -e index",
9 | "build": "npm run clean && tooling build -e index",
10 | "clean": "rm -rf ./build",
11 | "release": "npm run build && surge -d often.surge.sh -p ./build"
12 | },
13 | "author": "",
14 | "license": "ISC",
15 | "tooling": {
16 | "use": "vue",
17 | "index": {
18 | "title": "how often?",
19 | "template": "index.html"
20 | }
21 | },
22 | "devDependencies": {
23 | "redux": "^3.0.5",
24 | "redux-actions": "^0.9.0",
25 | "redux-thunk": "^1.0.3",
26 | "revue": "^1.2.5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/reducers/computeOften.js:
--------------------------------------------------------------------------------
1 | import { handleActions } from 'redux-actions'
2 |
3 | const initialState = {
4 | username: null,
5 | often: 0,
6 | found: true
7 | }
8 |
9 | export default handleActions({
10 | 'not found' (state, action) {
11 | return {
12 | ...state,
13 | ...action.payload,
14 | found: false
15 | }
16 | },
17 | 'compute often' (state, action) {
18 | const user = action.payload
19 | if (user.created_at) {
20 | let duration = Date.now() - new Date(user.created_at).getTime()
21 | duration = duration / 1000 / 86400 / user.public_repos
22 | const often = user.public_repos ? duration.toFixed(2) : 0
23 | return {
24 | username: user.login,
25 | often,
26 | found: true,
27 | repos: user.public_repos
28 | }
29 | }
30 | return state
31 | }
32 | }, initialState)
33 |
--------------------------------------------------------------------------------
/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import computeOften from './computeOften'
3 | import loadStatus from './loadStatus'
4 |
5 | export default combineReducers({
6 | computeOften,
7 | loadStatus
8 | })
9 |
--------------------------------------------------------------------------------
/reducers/loadStatus.js:
--------------------------------------------------------------------------------
1 | import { handleActions } from 'redux-actions'
2 |
3 | export default handleActions({
4 | 'loading' () {
5 | return 'loading'
6 | },
7 | 'loaded' () {
8 | return 'loaded'
9 | }
10 | }, '')
11 |
--------------------------------------------------------------------------------
/store.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, createStore, compose } from 'redux'
2 | import rootReducer from './reducers'
3 | import thunk from 'redux-thunk'
4 |
5 | const finalCreateStore = applyMiddleware(
6 | thunk
7 | )(createStore)
8 | const store = finalCreateStore(rootReducer)
9 |
10 | function configureStore () {
11 | if (module.hot) {
12 | module.hot.accept('./reducers', () => {
13 | const nextRootReducer = require('./reducers').default
14 | store.replaceReducer(nextRootReducer)
15 | })
16 | }
17 | return store
18 | }
19 |
20 | export default configureStore()
21 |
--------------------------------------------------------------------------------