├── .gitignore ├── client ├── styles │ ├── style.scss │ └── router.css ├── data │ ├── app.js │ ├── config.js │ ├── posts.js │ └── comments.js ├── reducers │ ├── index.js │ ├── route.js │ ├── posts.js │ ├── app.js │ └── comments.js ├── components │ ├── PhotoGrid.js │ ├── Banbu.js │ ├── Single.js │ ├── Comment.js │ └── Photo.js ├── page │ ├── App.js │ ├── Main.js │ └── home │ │ └── index.js ├── common │ ├── ravenConfig.js │ ├── routeHook.js │ ├── ddApiConfig.js │ ├── ddPlugin.js │ └── axiosConfig.js ├── actions │ └── actionCreator.js ├── store.js └── reduxstagram.js ├── readme.md ├── .eslintrc ├── devServer.js ├── react.md ├── .babelrc ├── webpack.config.dev.js ├── webpack.config.prod.js ├── index.html └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules 3 | env.js -------------------------------------------------------------------------------- /client/styles/style.scss: -------------------------------------------------------------------------------- 1 | body { 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | img{ 8 | max-width: 100%; 9 | } 10 | } -------------------------------------------------------------------------------- /client/data/app.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const app = { 4 | isLoading: false, //路由加载标志位 5 | 6 | ddConfig: null, 7 | ddConfigStatus: null, 8 | code: null, 9 | 10 | user: null 11 | } 12 | export default app; -------------------------------------------------------------------------------- /client/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux-immutable' 2 | 3 | 4 | import app from './app' 5 | import route from './route' 6 | 7 | const rootReducer = combineReducers({ 8 | app, 9 | routing: route 10 | }); 11 | 12 | export default rootReducer; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Learn Redux 2 | 3 | A simple React + Redux implementation. This will be turned into a free video series once the app is totally fleshed out. 4 | 5 | ## Running 6 | 7 | First `npm install` to grab all the necessary dependencies. 8 | 9 | Then run `npm start` and open in your browser. 10 | 11 | ## Production Build 12 | 13 | Run `npm build` to create a distro folder and a bundle.js file. 14 | 15 | -------------------------------------------------------------------------------- /client/reducers/route.js: -------------------------------------------------------------------------------- 1 | import { LOCATION_CHANGE } from 'react-router-redux' 2 | import Immutable from 'immutable' 3 | 4 | const initialState = Immutable.fromJS({ 5 | locationBeforeTransitions: null 6 | }); 7 | 8 | export default (state = initialState, action) => { 9 | if (action.type === LOCATION_CHANGE) { 10 | return state.set('locationBeforeTransitions', action.payload); 11 | } 12 | 13 | return state; 14 | }; -------------------------------------------------------------------------------- /client/reducers/posts.js: -------------------------------------------------------------------------------- 1 | import { Map, List } from 'immutable' 2 | 3 | function posts(state = List([]), aciton) { 4 | 5 | // console.log('post',aciton) 6 | switch (aciton.type){ 7 | case 'INCREMENT_LIKES': 8 | let i = aciton.index; 9 | return state.update(i, function ($$post) { 10 | return $$post.set('likes', $$post.get('likes') + 1) 11 | }); 12 | default: 13 | return state; 14 | } 15 | } 16 | 17 | export default posts; -------------------------------------------------------------------------------- /client/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Router, { Link } from 'react-router' 3 | import Photo from './Photo' 4 | 5 | 6 | let PhotoGrid = React.createClass({ 7 | 8 | render(){ 9 | return ( 10 |
11 | {this.props.$$posts.map((post, i)=> 12 | 13 | )} 14 |
15 | ) 16 | } 17 | }); 18 | 19 | export default PhotoGrid; -------------------------------------------------------------------------------- /client/page/App.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux' 2 | import { connect } from 'react-redux' 3 | import * as actionCreators from '../actions/actionCreator' 4 | import Main from './Main' 5 | 6 | function mapStateToProps(state) { 7 | return { 8 | $$app: state.get('app') 9 | } 10 | } 11 | 12 | function mapDispachToProps(dispatch) { 13 | return bindActionCreators(actionCreators, dispatch) 14 | } 15 | 16 | const App = connect(mapStateToProps, mapDispachToProps)(Main); 17 | 18 | export default App; -------------------------------------------------------------------------------- /client/common/ravenConfig.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'a90e5256eb14444a80f12bd0ea44652e'; 4 | const sentry_app = '123088'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | 15 | export function setRavenUser(user = {}) { 16 | Raven.setUserContext({ 17 | email: 'matt@example.com', 18 | id: '123', 19 | name: '张三' 20 | }) 21 | } -------------------------------------------------------------------------------- /client/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'a90e5256eb14444a80f12bd0ea44652e'; 4 | const sentry_app = '123088'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | 15 | export function setRavenUser(user = {}) { 16 | Raven.setUserContext({ 17 | email: 'matt@example.com', 18 | id: '123', 19 | name: '张三' 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "jsx": true, 4 | "modules": true 5 | }, 6 | "env": { 7 | "browser": true, 8 | "node": true 9 | }, 10 | "parser": "babel-eslint", 11 | "rules": { 12 | "quotes": [2, "single"], 13 | "strict": [2, "never"], 14 | "babel/generator-star-spacing": 1, 15 | "babel/new-cap": 1, 16 | "babel/object-shorthand": 1, 17 | "babel/arrow-parens": 1, 18 | "babel/no-await-in-loop": 1, 19 | "react/jsx-uses-react": 2, 20 | "react/jsx-uses-vars": 2, 21 | "react/react-in-jsx-scope": 2 22 | }, 23 | "plugins": [ 24 | "babel", 25 | "react" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /client/reducers/app.js: -------------------------------------------------------------------------------- 1 | import { Map, List } from 'immutable' 2 | 3 | function app(state = Map({}), aciton) { 4 | 5 | switch (aciton.type){ 6 | case 'DDCONFIG_SUCCESS': 7 | return state.set('ddConfig', aciton.ddConfig) 8 | case 'DDCONFIG_ERROR': 9 | return state.set('ddConfig', false) 10 | 11 | //axios中间件生成的type 12 | case 'LOAD': 13 | return state.set('isLoading', true) 14 | case 'LOAD_SUCCESS': 15 | return state.set('isLoading', false) 16 | case 'LOAD_FAIL': 17 | return state.set('isLoading', false) 18 | 19 | default: 20 | return state; 21 | } 22 | 23 | 24 | } 25 | 26 | 27 | export default app; -------------------------------------------------------------------------------- /devServer.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var webpack = require('webpack'); 4 | var config = require('./webpack.config.dev'); 5 | 6 | var app = express(); 7 | var compiler = webpack(config); 8 | 9 | app.use(require('webpack-dev-middleware')(compiler, { 10 | noInfo: true, 11 | publicPath: config.output.publicPath 12 | })); 13 | 14 | app.use(require('webpack-hot-middleware')(compiler)); 15 | 16 | app.get('*', function(req, res) { 17 | res.sendFile(path.join(__dirname, 'index.html')); 18 | }); 19 | 20 | app.listen(3000, '172.16.3.59', function(err) { 21 | if (err) { 22 | console.log(err); 23 | return; 24 | } 25 | 26 | console.log('Listening at http://localhost:7770'); 27 | }); 28 | -------------------------------------------------------------------------------- /client/reducers/comments.js: -------------------------------------------------------------------------------- 1 | import { Map, List } from 'immutable' 2 | 3 | function postComments(state = List([]), action) { 4 | 5 | switch (action.type){ 6 | case 'ADD_COMMENT': 7 | return state.push(Map({ 8 | user: action.author, 9 | text: action.comment 10 | })); 11 | case 'REMOVE_COMMENT': 12 | return state.delete(action.i,1) 13 | default: 14 | return state; 15 | } 16 | 17 | } 18 | 19 | 20 | function comments(state = Map({}), aciton) { 21 | 22 | if(typeof aciton.postId !== 'undefined'){ 23 | return state.set(aciton.postId, postComments(state.get(aciton.postId),aciton)) 24 | } 25 | return state; 26 | 27 | } 28 | 29 | export default comments; -------------------------------------------------------------------------------- /react.md: -------------------------------------------------------------------------------- 1 | ## 钉钉微应用 技术栈 2 | 3 | #### [react](https://www.baidu.com/s?wd=react) 4 | 5 | #### [react-router - 路由](https://www.baidu.com/s?wd=react-router) 6 | 7 | #### [react-redux - 数据](https://www.baidu.com/s?wd=react-redux) 8 | 9 | #### [immutable.js - 不可变数据类型](http://facebook.github.io/immutable-js/docs/#/) 10 | 11 | #### [redux-immutable - redux中使用immutable](https://github.com/gajus/redux-immutable) 12 | 13 | #### [react-router-redux - 连接路由和数据](https://www.baidu.com/s?wd=react-router-redux) 14 | 15 | #### [redux-axios-middleware - 异步数据中间件](https://github.com/svrcekmichal/redux-axios-middleware) 16 | 17 | #### [antd-mobile - 蚂蚁金服开源UI](https://mobile.ant.design/docs/react/introduce) 18 | 19 | #### [sass - css预处理器](https://www.baidu.com/s?wd=sass) 20 | 21 | #### [axios - ajax请求](https://www.baidu.com/s?wd=axios) 22 | 23 | #### [q - promise库](https://www.baidu.com/s?wd=q.js) -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015"], 3 | "env": { 4 | "development": { 5 | "plugins": [ 6 | ["transform-object-rest-spread"], 7 | ["transform-react-display-name"], 8 | ["react-transform", { 9 | "transforms": [{ 10 | "transform": "react-transform-hmr", 11 | "imports": ["react"], 12 | "locals": ["module"] 13 | }, { 14 | "transform": "react-transform-catch-errors", 15 | "imports": ["react", "redbox-react"] 16 | }] 17 | }], 18 | ["import", [ 19 | { "style": "css", "libraryName": "antd-mobile" } 20 | ]] 21 | ] 22 | }, 23 | "production": { 24 | "plugins": [ 25 | ["transform-object-rest-spread"], 26 | ["transform-react-display-name"], 27 | ["import", [ 28 | { "style": "css", "libraryName": "antd-mobile" } 29 | ]] 30 | ] 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/components/Banbu.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react' 2 | import { createForm } from 'rc-form'; 3 | import { is } from 'immutable'; 4 | import { List, InputItem, Switch } from 'antd-mobile'; 5 | 6 | 7 | let Banbu = React.createClass({ 8 | 9 | propTypes: { 10 | form: PropTypes.object, 11 | }, 12 | render(){ 13 | let { getFieldProps } = this.props.form; 14 | 15 | return ( 16 | 17 | } 24 | >默认开 25 | 标题 29 | 30 | ) 31 | } 32 | }) 33 | 34 | Banbu = createForm()(Banbu) 35 | 36 | export default Banbu -------------------------------------------------------------------------------- /client/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Photo from './Photo' 3 | import Comment from './Comment' 4 | 5 | const Single = React.createClass({ 6 | 7 | getPost(props){ 8 | const postId = props.params.postId; 9 | 10 | const i = props.$$posts.findIndex((post)=> { 11 | return post.get('code') === postId 12 | }) 13 | 14 | const $$post = props.$$posts.get(i) 15 | 16 | return { 17 | postId, 18 | i, 19 | $$post 20 | } 21 | }, 22 | render(){ 23 | const { $$post, i, postId } = this.getPost(this.props) 24 | 25 | const postComments = ( this.props.$$comments.get && this.props.$$comments.get(postId) ? this.props.$$comments.get(postId).toJS() :[]) 26 | 27 | return ( 28 |
29 | 30 | 31 | 32 |
33 | ) 34 | } 35 | }); 36 | 37 | export default Single; -------------------------------------------------------------------------------- /client/actions/actionCreator.js: -------------------------------------------------------------------------------- 1 | export function increment(index) { 2 | return { 3 | type: 'INCREMENT_LIKES', 4 | index 5 | } 6 | } 7 | 8 | export function addComment(postId, author, comment) { 9 | return { 10 | type: 'ADD_COMMENT', 11 | postId, 12 | author, 13 | comment 14 | } 15 | } 16 | 17 | export function removeComment(postId, i) { 18 | return { 19 | type: 'REMOVE_COMMENT', 20 | i, 21 | postId 22 | } 23 | } 24 | 25 | export function ddConfigSuccess(ddConfig) { 26 | return{ 27 | type: 'DDCONFIG_SUCCESS', 28 | ddConfig 29 | } 30 | } 31 | 32 | export function ddConfigError() { 33 | return{ 34 | type: 'DDCONFIG_ERROR', 35 | } 36 | } 37 | 38 | export function updateCode(code) { 39 | return{ 40 | type: 'DDCONFIG_ERROR', 41 | code 42 | } 43 | } 44 | 45 | export function getConfig() { 46 | return { 47 | type: 'LOAD', 48 | payload: { 49 | request:{ 50 | url:'/categories' 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /client/common/routeHook.js: -------------------------------------------------------------------------------- 1 | import callJsApi from './ddPlugin' 2 | 3 | 4 | let sessionStorage = window.sessionStorage 5 | sessionStorage.clear() 6 | let historyCount = sessionStorage.getItem('count') * 1 || 0 7 | sessionStorage.setItem('/', 0) 8 | 9 | let to = '/'; //当前路径 10 | let from = null; 11 | 12 | export function onEnter(nextState, replaceState){ 13 | to = nextState.location.pathname; 14 | 15 | const toIndex = sessionStorage.getItem(to) 16 | const fromIndex = sessionStorage.getItem(from) 17 | 18 | if (toIndex) { 19 | if (toIndex > fromIndex || !fromIndex) { 20 | window.direction = 'forward' 21 | } else { 22 | window.direction = 'reverse' 23 | } 24 | } else { 25 | ++historyCount 26 | sessionStorage.setItem('count', historyCount) 27 | to !== '/' && sessionStorage.setItem(to, historyCount) 28 | window.direction = 'forward' 29 | } 30 | callJsApi('biz.navigation.setRight',{ show: false}) 31 | } 32 | 33 | export function onLeave(currentState, replaceState){ 34 | from = currentState.location.pathname; 35 | callJsApi('biz.navigation.setRight',{ show: false}) 36 | } -------------------------------------------------------------------------------- /client/styles/router.css: -------------------------------------------------------------------------------- 1 | :global(.transitionWrapper-in-enter) { 2 | transform: translateX(100%); 3 | transition: all 500ms ; 4 | } 5 | 6 | :global(.transitionWrapper-in-enter.transitionWrapper-in-enter-active) { 7 | transform: translateX(0); 8 | z-index: 1; 9 | } 10 | 11 | :global(.transitionWrapper-in-leave) { 12 | transform: translateX(0); 13 | opacity: 1; 14 | transition: all 500ms ; 15 | } 16 | 17 | :global(.transitionWrapper-in-leave.transitionWrapper-in-leave-active) { 18 | transform: translateX(-10%); 19 | opacity: 0; 20 | } 21 | 22 | .transitionWrapper-in { 23 | position: relative; 24 | } 25 | 26 | :global(.transitionWrapper-out-enter) { 27 | transform: translateX(-10%); 28 | transition: all 500ms ; 29 | } 30 | 31 | :global(.transitionWrapper-out-enter.transitionWrapper-out-enter-active) { 32 | transform: translateX(0) 33 | } 34 | 35 | :global(.transitionWrapper-out-leave) { 36 | transform: translateX(0); 37 | transition: all 500ms ; 38 | } 39 | 40 | :global(.transitionWrapper-out-leave.transitionWrapper-out-leave-active) { 41 | transform: translateX(100%) 42 | } 43 | 44 | .transitionWrapper-out { 45 | position: relative; 46 | } -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devtool: 'source-map', 6 | entry: [ 7 | 'webpack-hot-middleware/client', 8 | './client/reduxstagram' 9 | ], 10 | output: { 11 | path: path.join(__dirname, 'dist'), 12 | filename: 'bundle.js', 13 | publicPath: '/static/' 14 | }, 15 | plugins: [ 16 | new webpack.HotModuleReplacementPlugin(), 17 | new webpack.NoErrorsPlugin() 18 | ], 19 | resolve: { 20 | modulesDirectories: ['node_modules', path.join(__dirname, '../node_modules')], 21 | extensions: ['', '.web.js', '.js', '.json'], 22 | }, 23 | module: { 24 | loaders: [ 25 | { 26 | test: /\.json$/, 27 | loaders: ['raw'], 28 | }, 29 | // js 30 | { 31 | test: /\.js$/, 32 | loaders: ['babel'], 33 | include: path.join(__dirname, 'client') 34 | }, 35 | // CSS 36 | { 37 | test: /\.scss|\.sass/, 38 | include: path.join(__dirname, 'client'), 39 | loader: 'style-loader!css-loader!sass-loader' 40 | }, 41 | { 42 | test: /\.css/, 43 | loader: 'style-loader!css-loader', 44 | } 45 | ] 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /client/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose, applyMiddleware } from 'redux' 2 | import { syncHistoryWithStore } from 'react-router-redux' 3 | import { hashHistory } from 'react-router' 4 | 5 | import axios from 'axios'; 6 | import axiosMiddleware from 'redux-axios-middleware'; 7 | 8 | //import thi root reducer 9 | import rootReducer from './reducers/index' 10 | 11 | import Immutable from 'immutable' 12 | import app from './data/app' 13 | 14 | 15 | //create an object on the default data 16 | const defaultState = Immutable.fromJS({ 17 | app, 18 | }); 19 | 20 | const enhancers = compose( 21 | applyMiddleware( 22 | axiosMiddleware(axios), //axios 中间件 23 | ), 24 | window.devToolsExtension ? window.devToolsExtension() : f=>f 25 | ); 26 | export const store = createStore( 27 | rootReducer, 28 | defaultState, 29 | enhancers 30 | ); 31 | 32 | export const history = syncHistoryWithStore(hashHistory, store, { 33 | selectLocationState (state) { 34 | return state.get('routing').toJS(); 35 | } 36 | }); 37 | 38 | if(module.hot){ 39 | module.hot.accept('./reducers/', ()=>{ 40 | const nextRootReducer = require('./reducers/index').default; 41 | store.replaceReducer(nextRootReducer); 42 | }) 43 | } -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devtool: 'source-map', 6 | entry: [ 7 | 8 | './client/reduxstagram' 9 | ], 10 | output: { 11 | path: path.join(__dirname, 'static'), 12 | filename: 'bundle.js', 13 | publicPath: '/static/' 14 | }, 15 | plugins: [ 16 | new webpack.optimize.OccurenceOrderPlugin(), 17 | new webpack.DefinePlugin({ 18 | 'process.env': { 19 | 'NODE_ENV': "'production'" 20 | } 21 | }), 22 | new webpack.optimize.UglifyJsPlugin({ 23 | compressor: { 24 | warnings: false 25 | } 26 | }) 27 | ], 28 | resolve: { 29 | modulesDirectories: ['node_modules', path.join(__dirname, '../node_modules')], 30 | extensions: ['', '.web.js', '.js', '.json'], 31 | }, 32 | module: { 33 | loaders: [ 34 | // js 35 | { 36 | test: /\.js$/, 37 | loaders: ['babel'], 38 | include: path.join(__dirname, 'client') 39 | }, 40 | // CSS 41 | { 42 | test: /\.scss|\.sass/, 43 | include: path.join(__dirname, 'client'), 44 | loader: 'style-loader!css-loader!sass-loader' 45 | }, 46 | { 47 | test: /\.css/, 48 | loader: 'style-loader!css-loader', 49 | } 50 | ] 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /client/page/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CSSTransitionGroup from 'react-addons-css-transition-group' 3 | import style from '../styles/router.css' 4 | import '../styles/style.scss' 5 | import { is } from 'immutable'; 6 | 7 | 8 | const Main = React.createClass({ 9 | 10 | shouldComponentUpdate: function(nextProps, nextState) { 11 | return !(this.props === nextProps || is(this.props, nextProps)) || 12 | !(this.state === nextState || is(this.state, nextState)); 13 | }, 14 | componentWillMount() { 15 | document.body.style.margin = "0px"; 16 | // 这是防止页面被拖拽 17 | document.body.addEventListener('touchmove', (ev) => { 18 | ev.preventDefault(); 19 | }); 20 | }, 21 | 22 | render(){ 23 | let direction = window.direction; 24 | let routerTransition = 'transitionWrapper-'+(direction === 'forward' ? 'in' : 'out') 25 | console.log(routerTransition) 26 | return ( 27 | 33 | 34 |
37 | {React.cloneElement(this.props.children, this.props)} 38 |
39 |
40 | ) 41 | } 42 | }); 43 | 44 | export default Main; -------------------------------------------------------------------------------- /client/components/Comment.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { is } from 'immutable'; 3 | 4 | const Comment = React.createClass({ 5 | 6 | shouldComponentUpdate: function(nextProps, nextState) { 7 | return !(this.props.$$comments === nextProps.$$comments || is(this.props.$$comments, nextProps.$$comments)) || 8 | !(this.state === nextState || is(this.state, nextState)); 9 | }, 10 | renderComment(comment, i){ 11 | return ( 12 |
13 |

14 | {comment.user} 15 | {comment.text} 16 | 17 |

18 |
19 | ) 20 | }, 21 | handleSubmit(e){ 22 | e.preventDefault(); 23 | const {postId} = this.props.params; 24 | const author = this.refs.author.value; 25 | const comment = this.refs.comment.value; 26 | this.props.addComment(postId, author, comment) 27 | this.refs.commentForm.reset(); 28 | }, 29 | render(){ 30 | return ( 31 |
32 | {this.props.postComments.map(this.renderComment)} 33 |
34 | 35 | 36 | 37 |
38 |
39 | ) 40 | } 41 | }) 42 | 43 | export default Comment -------------------------------------------------------------------------------- /client/components/Photo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router' 3 | import CSSTransitionGroup from 'react-addons-css-transition-group' 4 | import Immutable from 'immutable' 5 | 6 | const Photo = React.createClass({ 7 | render(){ 8 | const {$$post, i, $$comments} = this.props; 9 | 10 | let post = $$post.toJS(); 11 | let comments = $$comments.toJS(); 12 | 13 | 14 | return ( 15 |
16 |
17 | 18 | {post.caption} 19 | 20 | 21 | 24 | 25 | {post.likes} 26 | 27 | 28 |
29 | 30 |
31 |

{post.caption}

32 |
33 | 34 | 35 | 36 | 37 | {comments[post.code]?comments[post.code].length:0} 38 | 39 | 40 |
41 |
42 |
43 | ) 44 | } 45 | }) 46 | 47 | export default Photo; -------------------------------------------------------------------------------- /client/common/ddApiConfig.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'biz.util.open': { 3 | 'chat': 0, 4 | 'call': 0, 5 | 'profile': 0, 6 | 'contactAdd': 4, 7 | 'friendAdd': 4, 8 | }, 9 | 10 | 'device.accelerometer.watchShake': 0, 11 | 'device.accelerometer.clearShake': 0, 12 | 'device.notification.vibrate': 0, 13 | 'biz.contact.choose': 0, 14 | 'biz.navigation.setLeft': 0, 15 | 'biz.navigation.setTitle': 0, 16 | 'biz.navigation.setRight': 0, 17 | 'biz.navigation.back': 0, 18 | 'biz.navigation.close': 0, 19 | 'device.notification.alert': 0, 20 | 'device.notification.confirm': 0, 21 | 'device.notification.prompt': 0, 22 | 'biz.util.share': 0, 23 | 'biz.util.ut': 0, 24 | 'biz.util.datepicker': 0, 25 | 'biz.util.timepicker': 0, 26 | 27 | 'runtime.info': 1, 28 | 'device.geolocation.get': 1, 29 | 'device.notification.toast': 1, 30 | 'device.notification.showPreloader': 1, 31 | 'device.notification.hidePreloader': 1, 32 | 'biz.util.uploadImage': 1, 33 | 'biz.util.previewImage': 1, 34 | 35 | 'device.connection.getNetworkType': 2, 36 | 'biz.util.qrcode': 2, 37 | 'device.notification.actionSheet': 2, 38 | 'ui.input.plain': 2, 39 | 40 | 'biz.ding.post': 3, 41 | 'biz.telephone.call': 3, 42 | 'biz.chat.chooseConversation': 3, 43 | 44 | 'biz.contact.createGroup': 4, 45 | 'biz.util.datetimepicker': 4, 46 | 47 | 'biz.util.chosen': 5, 48 | 'device.base.getUUID': 5, 49 | 'device.base.getInterface': 5, 50 | 'device.launcher.checkInstalledApps': 5, 51 | 'device.launcher.launchApp': 5, 52 | 'runtime.permission.requestAuthCode': 5, 53 | 'runtime.permission.requestOperateAuthCode': 5, 54 | 'runtime.permission.requestJsApis': 5, 55 | 'ui.pullToRefresh.enable': 5, 56 | 'ui.pullToRefresh.stop': 5, 57 | 'ui.pullToRefresh.disable': 5, 58 | 'ui.webViewBounce.disable': 5, 59 | 'ui.webViewBounce.enable': 5, 60 | } -------------------------------------------------------------------------------- /client/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import FastClick from 'fastclick' 4 | 5 | // import css from './styles/style.styl'; 6 | 7 | import Single from './components/Single' 8 | 9 | import App from './page/App' 10 | import Home from './page/home/index' 11 | 12 | import { Router, Route, IndexRoute, browserHistory} from 'react-router' 13 | import { Provider } from 'react-redux' 14 | // import {store, history } from './store' //不在这里引入 会影响dd.config配置 15 | import callJsApi ,{ ddIsReady } from './common/ddPlugin' 16 | import {onEnter, onLeave} from './common/routeHook' 17 | 18 | import { getConfig } from './common/axiosConfig' 19 | 20 | let ddConfig = null; 21 | getConfig() 22 | .then((data)=>{ 23 | ddConfig = data; 24 | console.log(ddConfig) 25 | dd.config(ddConfig); 26 | return ddConfig 27 | }) 28 | .then(ddIsReady) 29 | .then(initReactRender) 30 | .then(()=>{ 31 | document.querySelector('#init-loading').remove(); 32 | console.log('init react 完成') 33 | setTimeout(()=>{ 34 | if(ddConfig != null){ 35 | // commit('DDCONFIG_SUCCESS', ddConfig) 36 | }else{ 37 | // commit('DDCONFIG_ERROR', false); 38 | } 39 | },300) 40 | }) 41 | .catch((err)=>{ 42 | //手动触发dispatch 43 | $r.store.dispatch({ type: 'DDCONFIG_ERROR'}) 44 | console.error(err); 45 | }) 46 | 47 | 48 | 49 | function initReactRender() { 50 | const {store, history} = require('./store') 51 | const router = ( 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ) 63 | render(router, document.getElementById('root')); 64 | FastClick.attach(document.body) 65 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-redux", 3 | "version": "1.0.0", 4 | "description": ":) ", 5 | "scripts": { 6 | "build:webpack": "NODE_ENV=production webpack --config webpack.config.prod.js", 7 | "build": "npm run clean && npm run build:webpack", 8 | "test": "NODE_ENV=production mocha './tests/**/*.spec.js' --compilers js:babel-core/register", 9 | "clean": "rimraf dist", 10 | "start": "node devServer.js", 11 | "tunnel": "browser-sync start --proxy localhost:7770 --tunnel wesbos" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "" 16 | }, 17 | "author": "", 18 | "license": "MIT", 19 | "homepage": "", 20 | "dependencies": { 21 | "antd-mobile": "^0.9.12", 22 | "axios": "^0.15.3", 23 | "fastclick": "^1.0.6", 24 | "immutable": "^3.8.1", 25 | "q": "^1.4.1", 26 | "raven-js": "^3.9.1", 27 | "rc-form": "^1.0.1", 28 | "react": "^15.0.2", 29 | "react-addons-css-transition-group": "^15.0.2", 30 | "react-dom": "^15.0.2", 31 | "react-redux": "^4.4.5", 32 | "react-router": "^2.4.0", 33 | "react-router-redux": "^4.0.4", 34 | "react-transform-catch-errors": "^1.0.2", 35 | "react-transform-hmr": "^1.0.4", 36 | "redbox-react": "^1.2.3", 37 | "redux": "^3.5.2", 38 | "redux-axios-middleware": "^3.0.0", 39 | "redux-immutable": "^3.0.9" 40 | }, 41 | "devDependencies": { 42 | "babel-core": "^6.7.7", 43 | "babel-eslint": "^6.0.4", 44 | "babel-loader": "^6.2.4", 45 | "babel-plugin-import": "^1.1.0", 46 | "babel-plugin-react-transform": "^2.0.2", 47 | "babel-plugin-transform-object-rest-spread": "^6.6.5", 48 | "babel-plugin-transform-react-display-name": "^6.5.0", 49 | "babel-polyfill": "^6.7.4", 50 | "babel-preset-es2015": "^6.6.0", 51 | "babel-preset-react": "^6.5.0", 52 | "css-loader": "^0.23.1", 53 | "expect": "^1.18.0", 54 | "expect-jsx": "^2.5.1", 55 | "eslint": "^2.9.0", 56 | "eslint-plugin-babel": "^3.2.0", 57 | "eslint-plugin-react": "^5.0.1", 58 | "express": "^4.13.4", 59 | "mocha": "^2.4.5", 60 | "node-sass": "^4.1.1", 61 | "react-addons-test-utils": "^15.0.2", 62 | "rimraf": "^2.5.2", 63 | "style-loader": "^0.13.1", 64 | "sass-loader": "^4.1.1", 65 | "webpack": "^1.13.0", 66 | "webpack-dev-middleware": "^1.6.1", 67 | "webpack-hot-middleware": "^2.10.0" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /client/common/ddPlugin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import Q from 'q' 3 | import Raven from 'raven-js' 4 | import jsapi from './ddApiConfig' 5 | import { sentry_url, logException } from './ravenConfig' 6 | 7 | //错误收集 8 | Raven.config(sentry_url, { 9 | environment: process.env.NODE_ENV || 'dev', 10 | release: '0.0.1', 11 | }).install(); 12 | 13 | var getMethod = function(method, ns) { 14 | var arr = method.split('.'); 15 | var namespace = ns || dd; 16 | for (var i = 0, k = arr.length; i < k; i++) { 17 | if (i === k - 1) { 18 | return namespace[arr[i]]; 19 | } 20 | if (typeof namespace[arr[i]] == 'undefined') { 21 | namespace[arr[i]] = {}; 22 | } 23 | namespace = namespace[arr[i]]; 24 | } 25 | }; 26 | 27 | export function ddIsReady(params) { 28 | return Q.Promise((success, error)=>{ 29 | let timeout = setTimeout(()=>{ 30 | error({errCode:-1,msg:'dd.ready初始化超时'}); 31 | logException(new Error('dd.ready初始化超时'), params) 32 | },5000) 33 | dd.ready(function(){ 34 | console.log('初始化钉钉'); 35 | clearTimeout(timeout) 36 | 37 | //获取容器信息 38 | dd.runtime.info({ 39 | onSuccess: function(result) { 40 | window.ability = parseInt(result.ability.replace(/\./g,'')); 41 | console.log('容器版本为'+window.ability) 42 | }, 43 | onFail : function(err) {} 44 | }) 45 | //设置返回按钮 46 | dd.biz.navigation.setLeft({ 47 | show: true,//控制按钮显示, true 显示, false 隐藏, 默认true 48 | control: true,//是否控制点击事件,true 控制,false 不控制, 默认false 49 | showIcon: true,//是否显示icon,true 显示, false 不显示,默认true; 注:具体UI以客户端为准 50 | text: '返回',//控制显示文本,空字符串表示显示默认文本 51 | onSuccess : function(result) { 52 | //如果control为true,则onSuccess将在发生按钮点击事件被回调 53 | console.log('点击了返回按钮'); 54 | window.history.back(); 55 | }, 56 | onFail : function(err) {} 57 | }); 58 | dd.biz.navigation.setRight({ 59 | show:false, 60 | }) 61 | success(true) 62 | }); 63 | dd.error(function(err){ 64 | clearTimeout(timeout) 65 | error({errCode:-1,msg:'dd.error配置信息不对'}) 66 | console.log(err) 67 | logException(new Error('dd.error配置信息不对'), err) 68 | }); 69 | }) 70 | } 71 | 72 | export default function callJsApi(method, param = {}) { 73 | return Q.Promise((success, error)=> { 74 | 75 | if (!window.ability || window.ability < jsapi[method]) { 76 | logException(new Error('容器版本过低,不支持 ' + method), { 77 | method: method, 78 | param: param 79 | }); 80 | return error({errCode: 404, msg: '容器版本过低,不支持' + method}) 81 | } 82 | 83 | param.onSuccess = function (result) { 84 | process.env.NODE_ENV !== 'production' && console.log(method, '调用成功,success', result) 85 | success(result) 86 | }; 87 | param.onFail = function (result) { 88 | process.env.NODE_ENV !== 'production' && console.log(method, '调用失败,fail', result) 89 | error(result) 90 | logException(new Error(method+ '调用失败,fail'), result); 91 | }; 92 | getMethod(method)(param); 93 | }) 94 | } -------------------------------------------------------------------------------- /client/page/home/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Router, { Link, Route } from 'react-router' 3 | import { NavBar, Icon, TabBar } from 'antd-mobile' 4 | // import PhotoGrid from '../../components/PhotoGrid' 5 | import Banbu from '../../components/Banbu' 6 | 7 | let Home = React.createClass({ 8 | 9 | getInitialState() { 10 | return { 11 | selectedTab: 'redTab', 12 | hidden: false, 13 | } 14 | }, 15 | 16 | axiosAction(){ 17 | console.log(this) 18 | this.props.getConfig() 19 | .then((data)=>{ 20 | console.log(data) 21 | }) 22 | .catch((err)=>{ 23 | console.error(err) 24 | }) 25 | }, 26 | 27 | 28 | render(){ 29 | 30 | return ( 31 |
32 | 33 | 86 |
87 | 88 | ) 89 | } 90 | }); 91 | 92 | export default Home; -------------------------------------------------------------------------------- /client/common/axiosConfig.js: -------------------------------------------------------------------------------- 1 | import Q from 'q' 2 | import axios from 'axios' 3 | import jsapi from './ddApiConfig' 4 | import { logException } from './ravenConfig' 5 | 6 | const config = { 7 | 8 | // 请求方法同上 9 | method: 'get', // default 10 | // 基础url前缀 11 | // baseURL: 'http://116.236.230.131:55002', 12 | baseURL: 'http://192.168.1.13:8080/rest/ding', 13 | 14 | 15 | // transformRequest: [function (data) { 16 | // // 这里可以在发送请求之前对请求数据做处理 17 | // 18 | // return data; 19 | // }], 20 | // 21 | // transformResponse: function (response) { 22 | // // 这里提前处理返回的数据 23 | // let res = null; 24 | // try { 25 | // res = JSON.parse(response) 26 | // }catch (err){ 27 | // logException(new Error('接口返回不是一个对象'), err) 28 | // } 29 | // 30 | // console.log(res) 31 | // if(typeof res == 'object'){ 32 | // res.code = parseInt(res.code); 33 | // switch(res.code){ 34 | // case 200: 35 | // 36 | // break; 37 | // default: 38 | // logException(new Error(res.status||res.code+' 错误'), res) 39 | // break; 40 | // } 41 | // } 42 | // return res || null; 43 | // }, 44 | 45 | // 请求头信息 46 | // headers: {'X-Requested-With': 'XMLHttpRequest'}, 47 | 48 | //parameter参数 49 | // params: { 50 | // ID: 12345 51 | // }, 52 | 53 | //post参数,使用axios.post(url,{},config);如果没有额外的也必须要用一个空对象,否则会报错 54 | data: { 55 | 56 | }, 57 | 58 | //设置超时时间 59 | timeout: 5000, 60 | //返回数据类型 61 | // responseType: 'json', // default 62 | 63 | } 64 | 65 | //全局配置axios 66 | for(let key in config){ 67 | axios.defaults[key] = config[key]; 68 | } 69 | //response过滤 70 | axios.interceptors.response.use(function (response) { 71 | // 这里提前处理返回的数据 72 | if(typeof response == 'object'){ 73 | if(response.status == 200){ 74 | 75 | return response.data; 76 | 77 | }else{ 78 | logException(new Error(response.status+' 错误'), response) 79 | } 80 | }else{ 81 | logException(new Error('接口返回不是一个对象'), response) 82 | } 83 | return response; 84 | }, function (error) { 85 | logException(new Error('接口请求失败'), error) 86 | return Q.Promise.reject(error); 87 | }); 88 | 89 | export default config; 90 | 91 | 92 | export function getParamByName(name) { 93 | var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); 94 | var r = window.location.search.substr(1).match(reg); 95 | if (r != null) return unescape(r[2]); 96 | return null; 97 | } 98 | 99 | export function getConfig() { 100 | return Q.Promise((success, error)=>{ 101 | let postData = { 102 | corpid: getParamByName('corpid')||'ding300b6b314632f87a35c2f4657eb6378f', 103 | appid: getParamByName('appid')||'2545', 104 | suitekey: getParamByName('suiteKey')||'suiteiyfdj0dfixywzqwg', 105 | url: window.location.href 106 | } 107 | axios.post('/auth/getConfig', postData).then(function (data) { 108 | console.log(data) 109 | if(data.code == 200){ 110 | let res = data.data; 111 | let jsApiList = []; 112 | for(let key in jsapi){ 113 | jsApiList.push(key) 114 | } 115 | 116 | let ddConfig = { 117 | agentId: res.agentId, // 必填,微应用ID 118 | corpId: postData.corpid,//必填,企业ID 119 | timeStamp: res.timeStamp, // 必填,生成签名的时间戳 120 | nonceStr: res.nonceStr, // 必填,生成签名的随机串 121 | signature: res.signature, // 必填,签名 122 | type: 0, //选填。0表示微应用的jsapi,1表示服务窗的jsapi。不填默认为0。该参数从dingtalk.js的0.8.3版本开始支持 123 | jsApiList: [ 124 | 'runtime.info', 125 | 'runtime.permission.requestAuthCode', 126 | //反馈式操作临时授权码 127 | 'runtime.permission.requestOperateAuthCode', 128 | 129 | 'biz.alipay.pay', 130 | 'biz.contact.choose', 131 | 'biz.contact.complexChoose', 132 | 'biz.contact.complexPicker', 133 | 'biz.contact.createGroup', 134 | 'biz.customContact.choose', 135 | 'biz.customContact.multipleChoose', 136 | 'biz.ding.post', 137 | 'biz.map.locate', 138 | 'biz.map.view', 139 | 'biz.util.openLink', 140 | 'biz.util.open', 141 | 'biz.util.share', 142 | 'biz.util.ut', 143 | 'biz.util.uploadImage', 144 | 'biz.util.previewImage', 145 | 'biz.util.datepicker', 146 | 'biz.util.timepicker', 147 | 'biz.util.datetimepicker', 148 | 'biz.util.chosen', 149 | 'biz.util.encrypt', 150 | 'biz.util.decrypt', 151 | 'biz.chat.pickConversation', 152 | 'biz.telephone.call', 153 | 'biz.navigation.setLeft', 154 | 'biz.navigation.setTitle', 155 | 'biz.navigation.setIcon', 156 | 'biz.navigation.close', 157 | 'biz.navigation.setRight', 158 | 'biz.navigation.setMenu', 159 | 'biz.user.get', 160 | 161 | 'ui.progressBar.setColors', 162 | 163 | 'device.base.getInterface', 164 | 'device.connection.getNetworkType', 165 | 'device.launcher.checkInstalledApps', 166 | 'device.launcher.launchApp', 167 | 'device.notification.confirm', 168 | 'device.notification.alert', 169 | 'device.notification.prompt', 170 | 'device.notification.showPreloader', 171 | 'device.notification.hidePreloader', 172 | 'device.notification.toast', 173 | 'device.notification.actionSheet', 174 | 'device.notification.modal', 175 | 'device.geolocation.get', 176 | ], // 必填,需要使用的jsapi列表,注意:不要带dd。 177 | } 178 | success(ddConfig) 179 | }else{ 180 | error({errCode:-2,msg:'接口请求失败'}) 181 | logException(new Error('config接口请求失败'), this) 182 | } 183 | }).catch(function (err) { 184 | error({errCode:-2,msg:'接口请求失败'}) 185 | logException(new Error('config接口请求失败'), err) 186 | }); 187 | }) 188 | } 189 | 190 | -------------------------------------------------------------------------------- /client/data/posts.js: -------------------------------------------------------------------------------- 1 | const posts = [ 2 | { 3 | "code":"BAcyDyQwcXX", 4 | "caption":"Lunch #hamont", 5 | "likes":56, 6 | "id":"1161022966406956503", 7 | "display_src":"https://scontent.cdninstagram.com/hphotos-xap1/t51.2885-15/e35/12552326_495932673919321_1443393332_n.jpg" 8 | }, 9 | { 10 | "code":"BAcJeJrQca9", 11 | "caption":"Snow! ⛄️🌨❄️ #lifewithsnickers", 12 | "likes":59, 13 | "id":"1160844458347054781", 14 | "display_src":"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/e35/12407344_1283694208323785_735653395_n.jpg" 15 | }, 16 | { 17 | "code":"BAF_KY4wcRY", 18 | "caption":"Cleaned my office and mounted my recording gear overhead. Stoked for 2016!", 19 | "likes":79, 20 | "id":"1154606670337393752", 21 | "display_src":"https://scontent.cdninstagram.com/hphotos-xpf1/t51.2885-15/e35/923995_1704188643150533_1383710275_n.jpg" 22 | }, 23 | { 24 | "code":"BAPIPRjQce9", 25 | "caption":"Making baby pancakes for one early rising baby. ☕️🍴", 26 | "likes":47, 27 | "id":"1157179863266871229", 28 | "display_src":"https://scontent.cdninstagram.com/hphotos-xap1/t51.2885-15/e35/12407480_1654828594805097_152207166_n.jpg" 29 | }, 30 | { 31 | "code":"-hZh6IQcfN", 32 | "caption":"New Stickers just came in. I'll do another mailing in a few weeks if you want some. #javascript", 33 | "likes":66, 34 | "id":"1126293663140399053", 35 | "display_src":"https://scontent.cdninstagram.com/hphotos-xap1/t51.2885-15/e35/11875511_1562439187344831_813588280_n.jpg" 36 | }, 37 | { 38 | "code":"-B3eiIwcYV", 39 | "caption":"Tacos for breakfast. I love you Austin. 🇺🇸", 40 | "likes":33, 41 | "id":"1117418173361145365", 42 | "display_src":"https://scontent.cdninstagram.com/hphotos-xfa1/t51.2885-15/e35/11917950_927755223968499_1198055371_n.jpg" 43 | }, 44 | { 45 | "code":"BAhvZrRwcfu", 46 | "caption":"Tried poke for the first time at @pokehbar. Delicious! It's like a bowl of sushi", 47 | "likes":30, 48 | "id":"1162418651480049646", 49 | "display_src":"https://scontent.cdninstagram.com/hphotos-xpa1/t51.2885-15/e35/12501993_1504179163220771_2060674913_n.jpg" 50 | }, 51 | { 52 | "code":"BAAJqbOQcW5", 53 | "caption":"Brunchin'", 54 | "likes":40, 55 | "id":"1152964002473690553", 56 | "display_src":"https://scontent.cdninstagram.com/hphotos-xap1/t51.2885-15/e35/1516572_445736812276082_2116173059_n.jpg" 57 | }, 58 | { 59 | "code":"_4jHytwcUA", 60 | "caption":"2015 can be summed up with one baby and a many lines of code. And sometimes a coding baby. 👶🏼⌨", 61 | "likes":62, 62 | "id":"1150824171912152320", 63 | "display_src":"https://scontent.cdninstagram.com/hphotos-xfa1/t51.2885-15/e35/10723795_1149927178351091_1859033096_n.jpg" 64 | }, 65 | { 66 | "code":"_zbaOlQcbn", 67 | "caption":"Lekker Chocoladeletter", 68 | "likes":52, 69 | "id":"1149382879529256679", 70 | "display_src":"https://scontent.cdninstagram.com/hphotos-xfp1/t51.2885-15/e35/12346073_1035047523184672_768982339_n.jpg" 71 | }, 72 | { 73 | "code":"_rmvQfQce8", 74 | "caption":"Just discovered the #hamont farmers market has a new ramen place! 🍜", 75 | "likes":35, 76 | "id":"1147180903383025596", 77 | "display_src":"https://scontent.cdninstagram.com/hphotos-xft1/t51.2885-15/e35/12331739_1671776806423597_995664526_n.jpg" 78 | }, 79 | { 80 | "code":"_ep9kiQcVy", 81 | "caption":"⛄️", 82 | "likes":64, 83 | "id":"1143535906423162226", 84 | "display_src":"https://scontent.cdninstagram.com/hphotos-xft1/t51.2885-15/e35/12354078_447337935474115_1484398925_n.jpg" 85 | }, 86 | { 87 | "code":"_XpJcrwcSn", 88 | "caption":"6 page spread on flexbox in this months netmag!", 89 | "likes":74, 90 | "id":"1141561999742846119", 91 | "display_src":"https://scontent.cdninstagram.com/hphotos-xfp1/t51.2885-15/e35/12362588_1688046211438811_1395882545_n.jpg" 92 | }, 93 | { 94 | "code":"_KnU7MwceA", 95 | "caption":"Hanging out in my office waiting for 5:00 beers to come around.", 96 | "likes":54, 97 | "id":"1137894817632733056", 98 | "display_src":"https://scontent.cdninstagram.com/hphotos-xfp1/t51.2885-15/e35/12301208_1533749386944985_1334730917_n.jpg" 99 | }, 100 | { 101 | "code":"_HMejJQcY5", 102 | "caption":"Today I learned that a long pull espresso is called a 'lungo'", 103 | "likes":18, 104 | "id":"1136932306813044281", 105 | "display_src":"https://scontent.cdninstagram.com/hphotos-xft1/t51.2885-15/e35/12357319_493317964181479_310198908_n.jpg" 106 | }, 107 | { 108 | "code":"_Fq2zmwcaz", 109 | "caption":"Awesome hand lettered gift from @eunibae and the HackerYou crew.", 110 | "likes":48, 111 | "id":"1136502965197194931", 112 | "display_src":"https://scontent.cdninstagram.com/hphotos-xfp1/t51.2885-15/e35/12317458_1692845870986430_331905833_n.jpg" 113 | }, 114 | { 115 | "code":"_A2r0aQcfD", 116 | "caption":"Some serious hardware meet JavaScript hacks going down this week at hackeryou. Excited for demo day!", 117 | "likes":57, 118 | "id":"1135147611821557699", 119 | "display_src":"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/e35/12276809_750065668431999_184252508_n.jpg" 120 | }, 121 | { 122 | "code":"-1rhFawccs", 123 | "caption":"Some major audio upgrades coming to my next videos 😍", 124 | "likes":39, 125 | "id":"1132002270913873708", 126 | "display_src":"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/e35/12331333_1650987978502155_1162510634_n.jpg" 127 | }, 128 | { 129 | "code":"-pjx-gQcVi", 130 | "caption":"My baby and me. Thanks to @bearandsparrow for this one.", 131 | "likes":81, 132 | "id":"1128590547628442978", 133 | "display_src":"https://scontent.cdninstagram.com/hphotos-xtf1/t51.2885-15/e35/12298962_863814057068027_460827278_n.jpg" 134 | }, 135 | { 136 | "code":"-oTZ0zQcWt", 137 | "caption":"It's too early. Send coffee.", 138 | "likes":81, 139 | "id":"1128237044221461933", 140 | "display_src":"https://scontent.cdninstagram.com/hphotos-xtf1/t51.2885-15/e35/12328347_990748230999662_1512917342_n.jpg" 141 | }, 142 | { 143 | "code":"-mxKQoQcQh", 144 | "caption":"They both have figured it out. #lifewithsnickers", 145 | "likes":47, 146 | "id":"1127804966031967265", 147 | "display_src":"https://scontent.cdninstagram.com/hphotos-xtp1/t51.2885-15/e35/12256736_1758525004381641_1136705181_n.jpg" 148 | }, 149 | { 150 | "code":"-fasqlQceO", 151 | "caption":"Kaitlin decorated the house for the Christmas. So gezellig! #casabos", 152 | "likes":46, 153 | "id":"1125735850454402958", 154 | "display_src":"https://scontent.cdninstagram.com/hphotos-xpt1/t51.2885-15/e35/12277581_1028556737218368_1184190781_n.jpg" 155 | }, 156 | { 157 | "code":"-VBgtGQcSf", 158 | "caption":"Trying the new Hamilton Brewery beer. Big fan.", 159 | "likes":27, 160 | "id":"1122810327591928991", 161 | "display_src":"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/e35/12224456_175248682823294_1558707223_n.jpg" 162 | }, 163 | { 164 | "code":"-FpTyHQcau", 165 | "caption":"I'm in Austin for a conference and doing some training. Enjoying some local brew with my baby.", 166 | "likes":82, 167 | "id":"1118481761857291950", 168 | "display_src":"https://scontent.cdninstagram.com/hphotos-xpt1/t51.2885-15/e35/11326072_550275398458202_1726754023_n.jpg" 169 | } 170 | ]; 171 | 172 | 173 | export default posts; 174 | -------------------------------------------------------------------------------- /client/data/comments.js: -------------------------------------------------------------------------------- 1 | const comments = { 2 | "BAhvZrRwcfu":[ 3 | { 4 | "text":"Totally need to try this.", 5 | "user": "heavymetaladam" 6 | } 7 | ], 8 | "BAcyDyQwcXX":[ 9 | { 10 | "text":"Wes. WE should have lunch.", 11 | "user": "jdaveknox" 12 | }, 13 | { 14 | "text":"#adults", 15 | "user": "jdaveknox" 16 | }, 17 | { 18 | "text":"@jdaveknox yes!", 19 | "user": "wesbos" 20 | }, 21 | { 22 | "text":"😍 love Hamilton!", 23 | "user": "willowtreemegs" 24 | } 25 | ], 26 | "BAPIPRjQce9":[ 27 | { 28 | "text":"Those are cute! They're like silver dollar pancakes.", 29 | "user": "rrsimonsen" 30 | }, 31 | { 32 | "text":"I like baby pancakes but gluten free please!! I'll bring the coffee!! See you in 6 days!!!!!! 😝😛😝♥️", 33 | "user": "terzisn" 34 | }, 35 | { 36 | "text":"... and apparently growing baby. 👀. Yum.", 37 | "user": "henrihelvetica" 38 | }, 39 | { 40 | "text":"@wesbos 👍 my daughter is a pancake eating machine!", 41 | "user": "brentoage" 42 | }, 43 | { 44 | "text":"Nice stove!", 45 | "user": "haaps" 46 | }, 47 | { 48 | "text":"Genius bottle use! Do you make a single batch of batter or make a lot and freeze it?", 49 | "user": "gobananna" 50 | }, 51 | { 52 | "text":"@gobananna I just made a batch and put in in a FIFO bottle. Keeps in the fridge for a few days.", 53 | "user": "wesbos" 54 | }, 55 | { 56 | "text":"@haaps thanks! It's a Jenn air - so nice to cool with!", 57 | "user": "wesbos" 58 | }, 59 | { 60 | "text":"Where would you go and for how long, if you had location freedom? - Greg 🌎", 61 | "user": "world_greg" 62 | } 63 | ], 64 | "BAF_KY4wcRY":[ 65 | { 66 | "text":"Looking great Wes! I'd like to see the other side of the room too.", 67 | "user": "axcdnt" 68 | }, 69 | { 70 | "text":"I've never caught your podcast. Have one right? Btw - they don't have a Canary pillow? 😝", 71 | "user": "henrihelvetica" 72 | }, 73 | { 74 | "text":"Great way to start the year.", 75 | "user": "pmgllc" 76 | }, 77 | { 78 | "text":"Are there 4k monitors?", 79 | "user": "alexbaumgertner" 80 | }, 81 | { 82 | "text":"@axcdnt that is where I put all the junk. I'll have to clean that side too @henrihelvetica no podcast yet! @pmgllc ohh yeah! @alexbaumgertner yep - the main one is 4K - I'm loving it", 83 | "user": "wesbos" 84 | }, 85 | { 86 | "text":"That chrome pillow. 😉", 87 | "user": "imagesofthisandthat" 88 | }, 89 | { 90 | "text":"@wesbos is that the Dell 4k? The MacBook Pro powers it well? I also have a Retina™ / x1 setup as well. Very handy.", 91 | "user": "henrihelvetica" 92 | }, 93 | { 94 | "text":"#minimalsetups", 95 | "user": "wesbos" 96 | } 97 | ], 98 | "_4jHytwcUA":[ 99 | { 100 | "text":"that is the sound of success!", 101 | "user": "mdxprograms" 102 | } 103 | ], 104 | "_zbaOlQcbn":[ 105 | { 106 | "text":"Did she get to eat her letter?", 107 | "user": "pathiebert" 108 | }, 109 | { 110 | "text":"Nope @pathiebert! She has too much teeth now (8) so that would definitely be her first cavity 😉", 111 | "user": "kaitbos" 112 | } 113 | ], 114 | "_rmvQfQce8":[ 115 | { 116 | "text":"A+", 117 | "user": "mrjoedee" 118 | }, 119 | { 120 | "text":"I recently went to a ramen place in Philly. So amazing!", 121 | "user": "jrtashjian" 122 | } 123 | ], 124 | "_ep9kiQcVy":[ 125 | { 126 | "text":"All bundled up! Where ya goin?", 127 | "user": "sophie_and_sadie" 128 | } 129 | ], 130 | "_XpJcrwcSn":[ 131 | { 132 | "text":"Super congrats! That's wicked cool and looks great.", 133 | "user": "pmgllc" 134 | }, 135 | { 136 | "text":"real live paper magazine? woah haha. flex box is awesome though, could rage quit without it", 137 | "user": "tjholowaychuk2" 138 | }, 139 | { 140 | "text":"@tjholowaychuk2 haha yes! Old school stylez!", 141 | "user": "wesbos" 142 | } 143 | ], 144 | "_KnU7MwceA":[ 145 | 146 | ], 147 | "_HMejJQcY5":[ 148 | { 149 | "text":"👌", 150 | "user": "t_volpe" 151 | }, 152 | { 153 | "text":"Nice shot, mister!", 154 | "user": "imagesofthisandthat" 155 | } 156 | ], 157 | "_Fq2zmwcaz":[ 158 | { 159 | "text":"😍", 160 | "user": "melsariffodeen" 161 | }, 162 | { 163 | "text":"Very cool!", 164 | "user": "ka11away" 165 | } 166 | ], 167 | "_A2r0aQcfD":[ 168 | { 169 | "text":"Uhu!", 170 | "user": "lucascaixeta" 171 | } 172 | ], 173 | "1rhFawccs":[ 174 | { 175 | "text":"What's your lighting source?", 176 | "user": "seth_mcleod" 177 | }, 178 | { 179 | "text":"And what are you using for XLR mix adapter? I found a big price jump between $55 range and $170 range.", 180 | "user": "pmgllc" 181 | }, 182 | { 183 | "text":"I still need a desk boom for mine mic. Nice upgrades", 184 | "user": "stolinski" 185 | } 186 | ], 187 | "pjx-gQcVi":[ 188 | 189 | ], 190 | "oTZ0zQcWt":[ 191 | { 192 | "text":"Love the coat! Where's it from? Lol", 193 | "user": "_lindersss" 194 | } 195 | ], 196 | "mxKQoQcQh":[ 197 | 198 | ], 199 | "hZh6IQcfN":[ 200 | { 201 | "text":"What do we gotta do to get some :)? @wesbos", 202 | "user": "jonasbad" 203 | }, 204 | { 205 | "text":"Might drop by today - real quick. Trade you an illegal pin for these? Lol. @wesbos", 206 | "user": "henrihelvetica" 207 | }, 208 | { 209 | "text":"I'm with @jonasbad on this. What we gotta do? :D", 210 | "user": "datamoshr" 211 | }, 212 | { 213 | "text":"@jonasbad @datamoshr I'll post them up on my blog soon!", 214 | "user": "wesbos" 215 | }, 216 | { 217 | "text":"Want", 218 | "user": "kamuelafranco" 219 | }, 220 | { 221 | "text":"want!", 222 | "user": "josemanuelxyz" 223 | }, 224 | { 225 | "text":"#Top", 226 | "user": "gabrielvieira.me" 227 | } 228 | ], 229 | "fasqlQceO":[ 230 | { 231 | "text":"Where's lux at? 💤?", 232 | "user": "henrihelvetica" 233 | }, 234 | { 235 | "text":"Love this house during the holidays! And all other times of the year...", 236 | "user": "danielleplas" 237 | } 238 | ], 239 | "VBgtGQcSf":[ 240 | { 241 | "text":"@dogsandbrew", 242 | "user": "likea_bos" 243 | }, 244 | { 245 | "text":"Next on my list!", 246 | "user": "tomwalsham" 247 | }, 248 | { 249 | "text":"Is that from collective arts ?", 250 | "user": "trevorb_91" 251 | } 252 | ], 253 | "FpTyHQcau":[ 254 | { 255 | "text":"@kaitbos that vest!!! 😍", 256 | "user": "courtneyeveline" 257 | }, 258 | { 259 | "text":"I just love her! @kaitbos", 260 | "user": "kalibrix" 261 | }, 262 | { 263 | "text":"@courtneyeveline I know! My friend gave it to her and I wanted a matching one but only Lux can pull it off. She's so fancy 😉", 264 | "user": "kaitbos" 265 | }, 266 | { 267 | "text":"Char has that vest!!! Super cute!", 268 | "user": "jennlensink" 269 | }, 270 | { 271 | "text":"You'll have to meet her soon @kalibrix!!", 272 | "user": "kaitbos" 273 | }, 274 | { 275 | "text":"Haha @kaitbos so true, babies these days are sooo stylish. 😎", 276 | "user": "courtneyeveline" 277 | }, 278 | { 279 | "text":"JavaScript 😄😆🙋", 280 | "user": "lucascaixeta" 281 | }, 282 | { 283 | "text":"That hoodie is amazing! Where did you get it?", 284 | "user": "br11x" 285 | }, 286 | { 287 | "text":"@br11x I did a teespring a few months ago - maybe I'll do another one soon", 288 | "user": "wesbos" 289 | } 290 | ], 291 | "B3eiIwcYV":[ 292 | { 293 | "text":"If you get in the mood for authentic Italian pizza, check out @backspaceaustin - it's👌🏻", 294 | "user": "dessie.ann" 295 | }, 296 | { 297 | "text":"😱 jealous", 298 | "user": "jenngbrewer" 299 | } 300 | ] 301 | }; 302 | 303 | export default comments; 304 | --------------------------------------------------------------------------------