[
  {
    "path": ".babelrc",
    "content": "//babel配置文件，不需要做修改，因为都配置好了\n{\n  \"presets\": [\n    [\"env\", {\"modules\": false}],\n    \"react\",\n    \"stage-0\"\n  ],\n  \"plugins\": [\n\n    \"react-hot-loader/babel\",\n    \n    [\"transform-runtime\", {\n      \"helpers\": false,\n      \"polyfill\": false,\n      \"regenerator\": true,\n      \"moduleName\": \"babel-runtime\"\n    }],\n    // ['import', {libraryName: 'antd', style:true}],\n    \"transform-decorators-legacy\",\n    \"transform-async-to-generator\",\n    \"transform-do-expressions\",\n    \"syntax-do-expressions\",\n    [\"babel-plugin-react-transform\", {\n           transforms: [\n             {\n               transform: 'react-transform-catch-errors',\n               imports: ['react','redbox-react','redBoxBlackStyle']\n             }\n           ],\n    }]\n  ]\n}"
  },
  {
    "path": ".gitignore",
    "content": "/node_modules/ "
  },
  {
    "path": "README.md",
    "content": "## 项目简介\n一个WebApp版的cnode客户端，项目采用react技术栈构建。组件选用的是[Material-UI](http://www.material-ui.com/)，让界面更适合触控操作。\n- 感谢来自[cnodejs论坛](https://cnodejs.org/)官方提供的api！\n\n## 功能\n- 首页列表，下拉时自动加载下一页，在顶端上拉刷新\n- 主题详情，登陆后能够收藏，评论和点赞\n- 消息提醒，能查看消息详情和清空所有未读消息\n- 个人主页，包括最近参与，回复，以及收藏的主题\n- 发表主题，成功后能跳转到相应主题页面\n- 页面后退，能还原数据和滚动位置\n\n## 运用的技术主要有:\n- 采用react技术栈，通过Redux来管理页面状态，通过Router来设置页面路由\n- 组件选用的是Material-UI，不再自己造轮子，既美观又能方便触控操作\n- 使用react-route v4 和 react原生的react-transition-group v2 来实现路由切换动画\n- 使用react-flip-move插件来实现list的加载动画\n- 应用`isomorphic-fetch`库代替`XMLHttpRequest`实现网络请求\n- 使用`PostCSS`对CSS进行预处理\n- 通过`CSSModules`处理模块内部的类名\n\n## 预览\n[DEMO](https://lumia2046.github.io/cnode/)\n\n## 运行项目\n```\n  git clone https://github.com/lumia2046/cnode.git\n  cd cnode\n  npm install\n  npm start\n  打开浏览器访问：http://localhost:5678\n```\n\n## 生产项目\n```\n  windows下\n  npm run build-win\n  linux、mac下\n  npm run build-win\n```\n<!-- \n## 状态树\n本项目使用redux管理状态,状态树如图:\n![截图](https://github.com/lumia2046/cnode/blob/master/stateTree/stateTree.png)  \n基本思路是每个页面对应一个reducer,管理本页面的状态。其中:\n- `homePage`对应主页信息，还包括了浏览的主题类别等状态\n- `article`对应文章内容页面，能缓存多篇，所以状态信息中提供了当前正在阅读的主题信息\n- `login`对应登陆账号的信息页面，包括是否登录成功等状态\n- `profile`代表用户的信息页面，比如用户名，积分情况等，还包括发表、回复和收藏的主题\n- `publishTopic`对应发表主题页面，包括主题是否发送成功等状态\n- `message`对应登陆账号的消息界面，还包括了未读消息是否被标记已读等状态\n\n## 总结\n\n- 对react组件及其生命周期有了更深入的了解\n  - 如果需要在组件更新的生命周期里setState()，应该在componentWillReceiveProps(或者componentWillUpdate)里通过对this.props和newProps里面的属性做出准确判断后再去setState()，否则会导致组件更新死循环以致页面卡死\n  - 在组件的生命周期里调用dispatch发送不带异步的action时，每发送一个action都会更新一次store。但是在事件回调的方法里多次调用dispatch发送不带异步的action时，只会在最后一个action发送完毕才更新一次store，想要每次action都更新store，那么必须手动将其封装为异步操作\n  - 公共组件最好不要设置自己的状态，应该由父组件管理其状态\n\n- 对es6有了更深入的了解\n  - 当采用es5写法时，React 自动将组件绑定给所有的事件回调方法中的this，这种自动绑定的行为只适用于当组件是用 React.createClass 创建时。如果用 ES6 的类来定义组件，那么事件方法中 this 的值就是 undefined，除非你自己显式绑定它\n  - 为了省略es6中事件方法的绑定，可以将事件方法写成箭头函数的形式，这种写法在react文档中标注为es7+，eg: 把handleClick(){}写成handleClick = () => {}即可,但是react生命周期函数不支es7+这种写法\n  - 利用扩展运算符和对象的解构赋值极大的简化了react组件间props的传递的书写。\n  eg: < Reply  {...({login,dispatch,profile})} / >只需这样，就可以把login,dispatch,profile三个变量当作props传递给Reply组件，需要注意的是解构对象本身时必须用括号括起来，否则解析器会把{}里的内容理解成一个代码块，而不是赋值语句\n\n- 对React技术栈有了深入的了解\n  - react-router的history属性，当设置为browerHistory时，需要对服务器改造。否则用户直接向服务器请求某个子路由，会显示网页找不到的404错误。\n  - redux通过connect连接react组件，只有mapStateToProps里return的对象的属性发生改变，组件才会去更新。store中有的但是return的对象里不存在的属性改变时，组件不会去更新，因为这样没有意义\n\n- 使用了模块化编程后，页面整体逻辑变得清晰很多，每个模块里的css和js都只负责管理一个对应的组件。尤其是用了css这一块，用css-loader开启CSS-Modules后，就再也不用为类名的语义化和重复去烦恼 -->\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-cn\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <title>Cnode</title>\n    <!-- <link rel=\"stylesheet\" href=\"./build/ui.css\"> -->\n    <link rel=\"stylesheet\" href=\"./build/module.css\">\n    <link rel=\"stylesheet\" href=\"./build/global.css\">\n</head>\n<body>\n    <div id=\"root\"></div>\n    <script src=\"./build/vendor.js\"></script>\n    <script src=\"./build/app.js\"></script>\n</body>\n\n</html>"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"cnode\",\n  \"version\": \"2.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start\": \"webpack-dev-server\",\n    \"lint\": \"eslint src\",\n    \"build-mac\": \"export NODE_ENV=production && webpack --progress --hide-modules --config webpack.config.js\",\n    \"build-win\": \"set NODE_ENV=production && webpack --progress --hide-modules --config webpack.config.js\",\n    \"test\": \"jest\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"babel-polyfill\": \"^6.20.0\",\n    \"github-markdown-css\": \"^2.4.1\",\n    \"history\": \"^4.6.1\",\n    \"isomorphic-fetch\": \"^2.2.1\",\n    \"lazy-load-component\": \"^1.1.2\",\n    \"material-ui\": \"^0.20.0\",\n    \"normalize.css\": \"^7.0.0\",\n    \"pullhelper\": \"^1.1.8\",\n    \"react\": \"^16.0.0\",\n    \"react-dom\": \"^16.0.0\",\n    \"react-flip-move\": \"^3.0.0\",\n    \"react-modal\": \"^3.1.0\",\n    \"react-motion\": \"^0.5.2\",\n    \"react-redux\": \"^5.0.6\",\n    \"react-router\": \"^4.2.0\",\n    \"react-router-dom\": \"^4.1.2\",\n    \"react-router-redux\": \"^4.0.8\",\n    \"react-swipeable-views\": \"^0.12.9\",\n    \"react-tap-event-plugin\": \"^3.0.2\",\n    \"react-transition-group\": \"^2.2.1\",\n    \"redux\": \"^3.6.0\",\n    \"redux-logger\": \"^3.0.6\",\n    \"redux-thunk\": \"^2.1.0\"\n  },\n  \"devDependencies\": {\n    \"autoprefixer\": \"^7.1.6\",\n    \"babel-cli\": \"^6.24.1\",\n    \"babel-core\": \"^6.25.0\",\n    \"babel-loader\": \"^7.1.1\",\n    \"babel-plugin-import\": \"^1.2.1\",\n    \"babel-plugin-react-transform\": \"^3.0.0\",\n    \"babel-plugin-transform-decorators-legacy\": \"^1.3.4\",\n    \"babel-plugin-transform-runtime\": \"^6.23.0\",\n    \"babel-polyfill\": \"^6.23.0\",\n    \"babel-preset-env\": \"^1.6.1\",\n    \"babel-preset-react\": \"^6.24.1\",\n    \"babel-preset-react-hmre\": \"^1.1.1\",\n    \"babel-preset-stage-0\": \"^6.16.0\",\n    \"babel-preset-stage-1\": \"^6.16.0\",\n    \"babel-preset-stage-2\": \"^6.17.0\",\n    \"babel-preset-stage-3\": \"^6.17.0\",\n    \"cross-env\": \"^5.1.1\",\n    \"css-loader\": \"^0.28.7\",\n    \"eventsource-polyfill\": \"^0.9.6\",\n    \"express\": \"^4.13.3\",\n    \"extract-text-webpack-plugin\": \"^3.0.2\",\n    \"file-loader\": \"^1.1.5\",\n    \"html-webpack-plugin\": \"^2.24.1\",\n    \"node-sass\": \"^4.6.0\",\n    \"open\": \"0.0.5\",\n    \"postcss-loader\": \"^2.0.8\",\n    \"postcss-scss\": \"^1.0.2\",\n    \"react-hot-loader\": \"^4.0.0-beta.14\",\n    \"react-transform-catch-errors\": \"^1.0.2\",\n    \"redbox-react\": \"^1.3.3\",\n    \"redux-devtools-extension\": \"^2.13.2\",\n    \"rimraf\": \"^2.4.3\",\n    \"sass-loader\": \"^6.0.6\",\n    \"style-loader\": \"^0.19.0\",\n    \"url-loader\": \"^0.6.2\",\n    \"webpack\": \"^3.8.1\",\n    \"webpack-dev-server\": \"^2.9.4\",\n    \"moment\": \"^2.18.1\"\n  }\n}\n"
  },
  {
    "path": "redBoxBlackStyle.js",
    "content": "module.exports = {\n    style: {\n        redbox: {\n            boxSizing: 'border-box',\n            fontFamily: 'sans-serif',\n            position: 'fixed',\n            padding: 10,\n            top: '0px',\n            left: '0px',\n            bottom: '0px',\n            right: '0px',\n            width: '100%',\n            background: 'rgba(0, 0, 0, 0.75)',\n            color: 'red',\n            zIndex: 9999,\n            textAlign: 'left',\n            fontSize: '16px',\n            lineHeight: 1.2\n        }\n    }\n};"
  },
  {
    "path": "src/actions/fetchError.js",
    "content": "import urlPrefix from '../utils/urlPrefix'\n\nexport const FETCH_ERROR = 'FETCH_ERROR'\nexport const CLEAR_ERROR = 'CLEAR_ERROR'\nexport const FETCH_START = 'FETCH_START'\nexport const FETCH_END = 'FETCH_END'\n\nexport const fetchStart = () => ({\n    type: FETCH_START\n})\n\nexport const fetchEnd = () => ({\n    type: FETCH_END\n})\n\nexport const fetchError = e => ({\n    type: FETCH_ERROR,\n    data: e\n})\n\nexport const clearError = () => ({\n    type: CLEAR_ERROR\n})"
  },
  {
    "path": "src/actions/hashUrl.js",
    "content": "export const SET_TRANSITION = 'SET_TRANSITION'\nexport const SET_HASH_URL = 'SET_HASH_URL'\n\n\nexport const setHashUrl = (hashUrl) => {\n  return {\n    type:SET_HASH_URL,\n    data:hashUrl\n  }\n}\n\nexport const setTransition = (transiton) => {\n  return {\n    type:SET_TRANSITION,\n    data:transiton\n  }\n}\n\n"
  },
  {
    "path": "src/actions.js",
    "content": "import fetch from 'isomorphic-fetch'\n\nexport const REQUEST_TOPICS = 'REQUEST_TOPICS'\nexport const RECEIVE_TOPICS = 'RECEIVE_TOPICS'\nexport const SELECT_TAB= 'SELECT_TAB'\nexport const RECORD_SCROLLT='RECORD_SCROLLT'\nexport const REQUEST_ARTICLE = 'REQUEST_ARTICLE'\nexport const RECEIVE_ARTICLE = 'RECEIVE_ARTICLE'\nexport const CHANGE_CURRENT_TOPICID = 'CHANGE_CURRENT_TOPICID'\nexport const CURRENT_ROUTER = 'CURRENT_ROUTER'\nexport const LOGIN_SUCCESS = 'LOGIN_SUCCESS'\nexport const LOGIN_FAILED = 'LOGIN_FAILED'\nexport const LOGOUT = 'LOGOUT'\nexport const REQUEST_PROFILE = 'REQUEST_PROFILE'\nexport const RECEIVE_PROFILE = 'RECEIVE_PROFILE'\nexport const SWITCH_SUPPORT = 'SWITCH_SUPPORT'\nexport const FETCH_COMMENT = 'FETCH_COMMENT'\nexport const SWITCH_COLLECTED = 'SWITCH_COLLECTED'\nexport const RECORD_ARTICLE_SCROLLT = 'RECORD_ARTICLE_SCROLLT'\nexport const PUBLISH_TOPIC = 'PUBLISH_TOPIC'\nexport const FETCH_MESSAGE = 'FETCH_MESSAGE'\nexport const MARK_ALL_MESSAGES = 'MARK_ALL_MESSAGES'\nexport const GET_COLLECTED_TOPICS = 'GET_COLLECTED_TOPICS'\n// HomePage\nexport const selectTab = tab => ({\n  type:SELECT_TAB,\n  tab\n})\n\nconst requestTopics = tab => ({\n  type:REQUEST_TOPICS,\n  tab\n})\n\nconst receiveTopics = (tab,topics,page,limit) => ({\n  type:RECEIVE_TOPICS,\n  tab,\n  topics,\n  page,\n  limit\n})\n\nexport const fetchTopics = (tab,page=1,limit=20) => {\n  return dispatch => {\n    dispatch(requestTopics(tab))\n    fetch(`https://cnodejs.org/api/v1/topics?tab=${tab}&page=${page}&limit=${limit}`)\n    .then(response => response.json())\n    .then(json => dispatch(receiveTopics(tab,json.data,page,limit)))\n  }\n}\n\nexport const recordScrollT = (tab,scrollT) => {\n  return ({\n    type:RECORD_SCROLLT,\n    tab,\n    scrollT\n  })\n}\n\n\n// Article\nconst requestArticle = (topicId) => ({\n  type:REQUEST_ARTICLE,\n  topicId\n})\nconst receiveArticle = (topicId,article) => ({\n  type:RECEIVE_ARTICLE,\n  topicId,\n  article\n})\nconst changeCurrentTopicId = topicId => ({\n  type:CHANGE_CURRENT_TOPICID,\n  topicId\n})\n\nexport const recordArticleScrollT = (topicId,scrollT) => ({\n  type:RECORD_ARTICLE_SCROLLT,\n  topicId,\n  scrollT\n})\nexport const fetchArticle = (topicId,request=true) => {\n  return dispatch => {\n    if(request){\n      dispatch(requestArticle(topicId))\n      fetch(`https://cnodejs.org/api/v1/topic/${topicId}`)\n      .then(response => response.json())\n      .then(json => dispatch(receiveArticle(topicId,json.data)))\n    }else{\n      dispatch(changeCurrentTopicId(topicId))\n    }\n  }\n}\n\nexport const switchSupport = (accessToken,replyId,index) => {\n  return dispatch => {\n    fetch(`https://cnodejs.org/api/v1/reply/${replyId}/ups`, {\n            method: 'POST',\n            headers: {\n                \"Content-Type\": \"application/x-www-form-urlencoded\"\n            },\n            body: `accesstoken=${accessToken}`\n        })\n    .then(response => response.json())\n    .then(json => dispatch({\n      type:SWITCH_SUPPORT,\n      replyId,\n      index,\n      success:json.success,\n      action:json.action\n    }))\n  }\n}\n\nexport const fetchComment = (accessToken,topicId,content,replyId) => {\n  return dispatch => {\n    const postConent = replyId ? `accesstoken=${accessToken}&content=${content}&replyId=${replyId}`:`accesstoken=${accessToken}&content=${content}`\n    fetch(`https://cnodejs.org/api/v1/topic/${topicId}/replies`, {\n            method: 'POST',\n            headers: {\n                \"Content-Type\": \"application/x-www-form-urlencoded\"\n            },\n            body: postConent\n        })\n    .then(response => response.json())\n    .then(json => dispatch({\n      type:FETCH_COMMENT,\n      success:json.success,\n      replyId:json.reply_id\n    }))\n  }\n}\n\nexport const switchCollected = (isCollected,accessToken,articleId) => {\n  return dispatch => {\n    fetch(`https://cnodejs.org/api/v1/topic_collect/${isCollected?'de_collect':'collect'}`, {\n            method: 'POST',\n            headers: {\n                \"Content-Type\": \"application/x-www-form-urlencoded\"\n            },\n            body: `accesstoken=${accessToken}&topic_Id=${articleId}`\n        })\n    .then(response => response.json())\n    .then(json => dispatch({\n      type:SWITCH_COLLECTED,\n      success:json.success\n    }))\n  }\n}\n\n// Footer\nexport const setCurrentRouter = router => ({\n  type:CURRENT_ROUTER,\n  router\n})\n\n// Login\nexport const fetchAccess = accessToken => {\n  return dispatch => {\n    fetch('https://cnodejs.org/api/v1/accesstoken', {\n                    method: 'POST',\n                    headers: {\n                        \"Content-Type\": \"application/x-www-form-urlencoded\"\n                    },\n                    body: `accesstoken=${accessToken}`\n                    })\n                .then(response => response.json())\n                .then(json => {\n                    if (json.success){\n                        dispatch(loginSucceed(json.loginname,json.id,accessToken))\n                    } else {\n                        dispatch(loginFailed(json.error_msg));\n                    }\n                 })\n  }\n}\n\nconst loginSucceed = (loginName,loginId,accessToken) => ({\n  type:LOGIN_SUCCESS,\n  loginName,\n  loginId,\n  accessToken\n})\n\nconst loginFailed = failedMessage => ({\n  type:LOGIN_FAILED,\n  failedMessage\n})\n\nexport const logout = () => ({\n  type:LOGOUT\n})\n\n\n// profile\nconst requestProfile = loginname => ({\n  type:REQUEST_PROFILE,\n  loginname\n})\nconst receiveProfile = (loginname,profile) => ({\n  type:RECEIVE_PROFILE,\n  loginname,\n  profile\n})\nconst getCollectedTopics = (userName) => {\n  return dispatch => {\n    fetch(`https://cnodejs.org/api/v1/topic_collect/${userName}`)\n    .then(response => response.json())\n    .then(json => dispatch({\n      type:GET_COLLECTED_TOPICS,\n      success:json.success,\n      data:json.data\n    }))\n  }\n}\nexport const fetchProfile = (loginname) => {\n  return dispatch => {\n    dispatch(requestProfile(loginname))\n    dispatch(getCollectedTopics(loginname))\n    fetch(`https://cnodejs.org/api/v1/user/${loginname}`)\n    .then(response => response.json())\n    .then(json => dispatch(receiveProfile(loginname,json.data)))\n  }\n}\n\n// publishTopic\nexport const fetchPublishTopic = (accessToken,tab,title,content) => {\n  return dispatch => {\n    const postConent =  `accesstoken=${accessToken}&tab=${tab}&content=${content}&title=${title}`\n    fetch(`https://cnodejs.org/api/v1/topics`, {\n            method: 'POST',\n            headers: {\n                \"Content-Type\": \"application/x-www-form-urlencoded\"\n            },\n            body: postConent\n        })\n    .then(response => response.json())\n    .then(json => dispatch({\n      type:PUBLISH_TOPIC,\n      success:json.success,\n      topicId:json.topic_id\n    }))\n  }\n}\n\n// message\nexport const fetchMessage = (accessToken) => {\n  return dispatch => {\n    fetch(`https://cnodejs.org/api/v1/messages?accesstoken=${accessToken}`)\n    .then(response => response.json())\n    .then(json => dispatch({\n      type:FETCH_MESSAGE,\n      hasReadMessage:json.data.has_read_messages,\n      hasNotReadMessage:json.data.hasnot_read_messages\n    }))\n  }\n}\n\nexport const markAllMessages = (accessToken) => {\n  return dispatch => {\n    fetch(`https://cnodejs.org/api/v1/message/mark_all`, {\n            method: 'POST',\n            headers: {\n                \"Content-Type\": \"application/x-www-form-urlencoded\"\n            },\n            body: `accesstoken=${accessToken}`\n        })\n    .then(response => response.json())\n    .then(json => dispatch({\n      type:MARK_ALL_MESSAGES,\n      isMarked:json.success\n    }))\n  }\n}"
  },
  {
    "path": "src/components/Article/Content/Content.js",
    "content": "import React, { PropTypes, Component } from 'react'\nimport { Link } from 'react-router-dom'\nimport { connect } from 'react-redux'\nimport prefix from '../../../utils/routePrefix'\nimport { setTransition } from '../../../actions/hashUrl'\nimport { fetchProfile, switchCollected } from '../../../actions'\nimport styles from './styles.scss'\nimport LinkToLogin from '../../common/LinkToLogin/LinkToLogin'\nimport transformDate from '../../../utils/transformDate'\nimport classnames from 'classnames'\nimport { List, ListItem } from 'material-ui/List'\nimport Divider from 'material-ui/Divider'\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'\nimport fetch from 'isomorphic-fetch'\n\n@connect()\nclass Content extends Component {\n\tconstructor() {\n\t\tsuper()\n\t\tthis.state = {\n\t\t\tisCollected: false\n\t\t}\n\t}\n\tcomponentWillMount() {\n\t\tthis.update(this.props)\n\t}\n\tcomponentWillReceiveProps(newProps) {\n\t\tif (this.props.collectedTopics.userName !== newProps.collectedTopics.userName) {\n\t\t\tthis.update(newProps);\n\t\t}\n\t}\n\tupdate(props) {\n\t\tconst { article, login, collectedTopics } = props\n\t\tif (login.succeed && collectedTopics.length !== 0) {\n\t\t\tlet isCollected = collectedTopics.some(topic => {\n\t\t\t\treturn article.id === topic.id\n\t\t\t})\n\t\t\tthis.setState({\n\t\t\t\tisCollected: isCollected\n\t\t\t})\n\t\t}\n\t}\n\trender() {\n\t\tconst { article, dispatch, fetchProfile, login, collectedTopics, profile } = this.props\n\t\treturn (\n\t\t\t<MuiThemeProvider>\n\t\t\t\t<List>\n\t\t\t\t\t<div style={{ margin: '-8px 0' }}>\n\t\t\t\t\t\t<ListItem>\n\t\t\t\t\t\t\t<div style={{ margin: -16 }}>\n\t\t\t\t\t\t\t\t<div className={styles.head}>\n\t\t\t\t\t\t\t\t\t<div className={styles.imgbox}>\n\t\t\t\t\t\t\t\t\t\t<img src={article.author.avatar_url} alt={article.author.loginname} />\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<div className={styles.info}>\n\t\t\t\t\t\t\t\t\t\t<Link to={`${prefix}/profile`} onClick={e => {\n\t\t\t\t\t\t\t\t\t\t\tthis.props.dispatch(setTransition({ transition: 'up' }))\n\t\t\t\t\t\t\t\t\t\t\tif (profile.loginname !== article.author.loginname) {\n\t\t\t\t\t\t\t\t\t\t\t\tdispatch(fetchProfile(article.author.loginname))\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}}>\n\t\t\t\t\t\t\t\t\t\t\t<span>{article.author.loginname}</span>\n\t\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t\t\t<span style={{ float: 'right' }}>发表于{transformDate(article.create_at)}</span>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t<div className={styles.info}>\n\t\t\t\t\t\t\t\t\t\t{login.succeed &&\n\t\t\t\t\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t\t\t\t\t<span style={{ marginRight: 5 }}>收藏</span>\n\t\t\t\t\t\t\t\t\t\t\t\t<i className=\"iconfont\" dangerouslySetInnerHTML={{ __html: '&#xe600;' }} style={{\n\t\t\t\t\t\t\t\t\t\t\t\t\theight: 16, lineHeight: '16px', fontSize: '15px',\n\t\t\t\t\t\t\t\t\t\t\t\t\tcolor: this.state.isCollected ? 'red' : 'black'\n\t\t\t\t\t\t\t\t\t\t\t\t}} onClick={e => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tisCollected: !this.state.isCollected\n\t\t\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\t\t\tdispatch(switchCollected(this.state.isCollected, login.accessToken, article.id))\n\t\t\t\t\t\t\t\t\t\t\t\t\t// fetch(`https://cnodejs.org/api/v1/topic_collect/${this.state.isCollected?'de_collect':'collect'}`, {\n\t\t\t\t\t\t\t\t\t\t\t\t\t//         method: 'POST',\n\t\t\t\t\t\t\t\t\t\t\t\t\t//         headers: {\n\t\t\t\t\t\t\t\t\t\t\t\t\t//             \"Content-Type\": \"application/x-www-form-urlencoded\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t//         },\n\t\t\t\t\t\t\t\t\t\t\t\t\t//         body: `accesstoken=${login.accessToken}&topic_Id=${article.id}`\n\t\t\t\t\t\t\t\t\t\t\t\t\t//     })\n\t\t\t\t\t\t\t\t\t\t\t\t}}></i>\n\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t<span style={{ float: 'right' }}>\n\t\t\t\t\t\t\t\t\t\t\t<i className=\"iconfont\" dangerouslySetInnerHTML={{ __html: '&#xe6ae;' }} style={{ fontSize: '18px', color: 'grey' }}></i>\n\t\t\t\t\t\t\t\t\t\t\t{article.visit_count}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t<span style={{ float: 'right', marginRight: 10 }}>\n\t\t\t\t\t\t\t\t\t\t\t<i className=\"iconfont\" dangerouslySetInnerHTML={{ __html: '&#xe63f;' }} style={{ fontSize: '14px', color: 'grey' }}></i>\n\t\t\t\t\t\t\t\t\t\t\t{article.reply_count}\n\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</ListItem>\n\t\t\t\t\t\t<ListItem>\n\t\t\t\t\t\t\t<div className={styles.title}>{article.title}</div>\n\t\t\t\t\t\t</ListItem>\n\t\t\t\t\t\t<Divider />\n\t\t\t\t\t\t<ListItem style={{ lineHeight: 'auto' }}>\n\t\t\t\t\t\t\t<div style={{ margin: -16 }}>\n\t\t\t\t\t\t\t\t<div className={`${styles.main} markdown-body`} dangerouslySetInnerHTML={{ __html: article.content }}></div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</ListItem>\n\t\t\t\t\t</div>\n\t\t\t\t</List>\n\n\t\t\t</MuiThemeProvider>\n\t\t)\n\t}\n\n}\n\nexport default Content\n"
  },
  {
    "path": "src/components/Article/Content/styles.scss",
    "content": ".head{\n\tmargin-top: 64px;\n\tbackground: #ccc;\n\toverflow: hidden;\n\tpadding: 10px;\n\timg{\n\t\twidth: 50px;\n\t\theight: 50px;\n\t\tborder-radius: 50px;\n\t}\n}\n\n.imgbox{\n\tfloat: left;\n\ttext-align: center;\n\tmargin-right: 10px;\n}\n.info{\n\theight: 25px;\n\tline-height: 25px;\n}\n.title{\n\ttext-align: center;\n\tfont-weight: bold;\n\tfont-size: 24px;\n\tline-height: 32px;\n}\n.main{\n\tpadding: 20px;\n\tp{\n\t\ttext-indent: 2em;\n\t}\n\timg{\n\t\tdisplay: block;\n\t\tmargin: 20px auto;\n\t\twidth: 600px;\n\t}\n}\n\n@media (max-width: 750px){\n\t.main{\n\t\timg{\n\t\t\twidth: 80%;\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/components/Article/Reply/Reply.js",
    "content": "import React, { PropTypes,Component } from 'react'\nimport {Link} from 'react-router-dom'\nimport { connect } from 'react-redux'\nimport prefix from '../../../utils/routePrefix'\nimport styles from './styles.scss'\nimport { setTransition } from '../../../actions/hashUrl'\nimport {fetchComment,fetchArticle,recordArticleScrollT,switchSupport,fetchProfile} from '../../../actions'\nimport transformDate from '../../../utils/transformDate'\nimport getSize from '../../../utils/getSize'\nimport LinkToLogin from '../../common/LinkToLogin/LinkToLogin'\nimport Dialog from '../../common/Dialog'\nimport classnames from 'classnames'\nimport {List, ListItem} from 'material-ui/List';\nimport Divider from 'material-ui/Divider';\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';\nimport TextField from 'material-ui/TextField';\nimport RaisedButton from 'material-ui/RaisedButton';\n\n@connect()\nclass Reply extends Component {\n\tconstructor(){\n\t\tsuper();\n\t\tthis.state = {\n\t\t\tisSupported:[],\n\t\t\tsupportNum:[],\n\t\t\theight:[],\n\t\t\tname:[],\n\t\t\topenDialog:false\n\t\t}\n\t}\n\tsupportState = (replies,login) => {\n\t\tlet isSupported = replies.map(reply => {\n\t\t\treturn reply.ups.some(up => up === login.loginId)\n\t\t})\n\t\tlet supportNum = replies.map(reply => reply.ups.length)\n\t\tthis.setState({isSupported,supportNum})\n\t}\n\tcloseDialog = () => {\n\t\tthis.setState({\n\t\t\topenDialog:false\n\t\t})\n\t}\n\tcomponentWillMount(){\n\t\tconst {replies,login} = this.props\n\t\tthis.supportState(replies,login)\n\t}\n\n\tcomponentWillReceiveProps(newProps){\t\n\t\tconst {switchSupportInfo,replies,login,isCommented,currentTopicId,dispatch} = newProps\n\t\tif(this.state.height.length !== 0){\n\t\t\tthis.setState({\n\t\t\t\theight:[],\n\t\t\t\tname:[]\n\t\t\t})\n\t\t}\n\t\tif(replies.length !== this.props.replies.length){\n\t\t\tthis.supportState(replies,login)\n\t\t}\n\t\tif(isCommented && this.props.isCommented !== isCommented){\n\t\t\tdispatch(fetchArticle(currentTopicId))\n\t\t}\n\t\t// if(switchSupportInfo){\n\t\t// \tif(!this.props.switchSupportInfo || !(this.props.switchSupportInfo.action===switchSupportInfo.action && this.props.switchSupportInfo.index===switchSupportInfo.index)){\n\t\t// \t\tif(switchSupportInfo.success){\n\t\t// \t\t\tlet isSupported = this.state.isSupported;\n\t\t// \t\t\tlet supportNum = this.state.supportNum;\n\t\t// \t\t\tisSupported[switchSupportInfo.index] = switchSupportInfo.action === 'up';\n\t\t// \t\t\tswitchSupportInfo.action === 'up' ? ++supportNum[switchSupportInfo.index] : --supportNum[switchSupportInfo.index]\n\t\t// \t\t\tthis.setState({\n\t\t// \t\t\t\tisSupported,\n\t\t// \t\t\t\tsupportNum\n\t\t// \t\t\t})\n\t\t// \t\t}else{\t\n\t\t// \t\t}\n\t\t// \t}\n\t\t// }\n\t}\n\n\trender(){\n\t\tlet {profile,replies,dispatch,login,switchSupportInfo,currentTopicId}= this.props\n\t\treturn (\n\t\t\t<div className={styles.reply}>\n\t\t\t\t<Dialog isOpen={this.state.openDialog} close={this.closeDialog} singleButton={true}>\n\t\t\t\t\t不能给自己点赞！\n\t\t\t\t</Dialog>\n\t\t\t    <div style={{margin:'-8px 0'}}>\n\t\t\t      <MuiThemeProvider>\n\t\t\t      \t<List>\n\t\t\t      \t\t<ListItem>\n\t\t\t      \t\t\t<div style={{margin:-16}}>\n\t\t\t      \t\t\t\t<h2>共有{replies.length}条回复</h2>\n\t\t\t      \t\t\t</div>\n\t\t\t      \t\t</ListItem>\n\t\t\t      \t\t{replies.map((reply,index) => (\n\t\t\t      \t\t\t<div key={index}>\n\t\t\t      \t\t\t\t<ListItem>\n\t\t\t      \t\t\t\t\t<div className={styles.author}>\n\t\t\t      \t\t\t\t\t\t<img src={reply.author.avatar_url} alt={reply.author.loginname}/>\n\t\t\t      \t\t\t\t\t\t<div style={{textAlign:'center',paddingTop:10}}>{index+1}楼</div>\n\t\t\t      \t\t\t\t\t</div>\n\t\t\t      \t\t\t\t\t<div className={styles.main}>\n\t\t\t      \t\t\t\t\t\t<div className={styles.item}>\n\t\t\t      \t\t\t\t\t\t\t<Link to={`${prefix}/profile`} onClick={e => {\n\t\t\t\t\t\t\t\t\t\t\t\t  this.props.dispatch(setTransition({ transition: 'up' }))\n\t\t\t      \t\t\t\t\t\t\t\tif(profile.loginname !== reply.author.loginname){\n\t\t\t      \t\t\t\t\t\t\t\t\tdispatch(fetchProfile(reply.author.loginname))\n\t\t\t      \t\t\t\t\t\t\t\t}\n\t\t\t      \t\t\t\t\t\t\t}}>\n\t\t\t      \t\t\t\t\t\t\t {reply.author.loginname}\n\t\t\t      \t\t\t\t\t\t\t</Link>\n\t\t\t      \t\t\t\t\t\t\t<span style={{float:'right'}}>{transformDate(reply.create_at)}</span>\n\t\t\t      \t\t\t\t\t\t</div>\n\t\t\t      \t\t\t\t\t\t<div className={`${styles.item} markdown-text`} dangerouslySetInnerHTML={{__html: reply.content}} style={{lineHeight:'21px'}}></div>\n\t\t\t      \t\t\t\t\t\t<div className={styles.item}>\n\t\t\t      \t\t\t\t\t\t\t<div style={{float:'right'}}>\n\t\t\t      \t\t\t\t\t\t\t\t<span  onClick={e => {\n\t\t\t      \t\t\t\t\t\t\t\t\tlet heightArr = [];\n\t\t\t      \t\t\t\t\t\t\t\t\tlet nameArr = [];\n\t\t\t      \t\t\t\t\t\t\t\t\theightArr[index] = this.state.height[index] ? 0 : 120\n\t\t\t      \t\t\t\t\t\t\t\t\tnameArr[index] = `@${reply.author.loginname} `\n\t\t\t      \t\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t      \t\t\t\t\t\t\t\t\t\theight:heightArr,\n\t\t\t      \t\t\t\t\t\t\t\t\t\tname:nameArr\n\t\t\t      \t\t\t\t\t\t\t\t\t})\n\t\t\t      \t\t\t\t\t\t\t\t}} style={{cursor:'pointer',marginRight:10}}>\n\t\t\t      \t\t\t\t\t\t\t\t\t回复 \n\t\t\t      \t\t\t\t\t\t\t\t</span>\n\t\t\t      \t\t\t\t\t\t\t\t<span>\n\t\t\t      \t\t\t\t\t\t\t\t\t<i className=\"iconfont\" dangerouslySetInnerHTML={{__html: '&#xe610;'}} onClick={ e => {\n\t\t\t      \t\t\t\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t      \t\t\t\t\t\t\t\t\t\tif(login.loginId){\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\tif(reply.author.loginname !== login.loginName){\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t\tconst {scrollT} = getSize()\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t    // 点赞的时候也会发送数据请求，所以Article组件会刷新，如果不保存位置的话，Article的位置会变成上次记录的位置或者默认位置0\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t    dispatch(recordArticleScrollT(currentTopicId,scrollT))\n\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t    let isSupported = this.state.isSupported;\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t    let supportNum = this.state.supportNum;\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t    isSupported[index]? --supportNum[index] : ++supportNum[index]\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t    isSupported[index] = !isSupported[index]\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t    this.setState({\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t    \tisSupported,\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t    \tsupportNum\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t    })\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t    dispatch(switchSupport(login.accessToken,reply.id,index))\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t}else{\n\t\t\t      \t\t\t\t\t\t\t\t\t\t    \tthis.setState({\n\t\t\t      \t\t\t\t\t\t\t\t\t\t    \t\topenDialog:true\n\t\t\t      \t\t\t\t\t\t\t\t\t\t    \t})\n\t\t\t      \t\t\t\t\t\t\t\t\t\t    }\n\t\t\t      \t\t\t\t\t\t\t\t\t\t}else{\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\tlet heightArr = [];\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\theightArr[index] = this.state.height[index] ? 0 : 150\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t\theight:heightArr\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t      \t\t\t\t\t\t\t\t\t\t}\n\t\t\t      \t\t\t\t\t\t\t\t\t\t\n\t\t\t      \t\t\t\t\t\t\t\t\t}} style={{color:this.state.isSupported[index] ? 'red':'black',cursor:'pointer'}}\n\t\t\t      \t\t\t\t\t\t\t\t\t></i>\n\t\t\t      \t\t\t\t\t\t\t\t\t{this.state.supportNum[index]}\n\t\t\t      \t\t\t\t\t\t\t\t</span>\n\t\t\t      \t\t\t\t\t\t\t</div>\n\t\t\t      \t\t\t\t\t\t</div>\n\t\t\t      \t\t\t\t\t\t<NeedComment {...({login,dispatch,currentTopicId})} \n\t\t\t      \t\t\t\t\t\tpHeight={this.state.height[index]} defaultValue={this.state.name[index]}/>\n\t\t\t      \t\t\t\t\t</div>\n\t\t\t      \t\t\t\t</ListItem>\n\t\t\t      \t\t\t\t<Divider/>\n\t\t\t      \t\t\t</div>\n\t\t\t      \t\t))}\n\t\t\t      \t</List>\n\t\t\t      </MuiThemeProvider>\n\t\t\t    </div>\n\t\t\t\t<MuiThemeProvider>\n\t\t\t\t\t<ListItem>\n\t\t\t\t\t\t<NeedComment {...({login,dispatch,currentTopicId})} pHeight='150px'/>\n\t\t\t\t\t</ListItem>\n\t\t\t\t</MuiThemeProvider>\n\t\t\t</div>\n\t\t)\n\t}\n}\n\nclass NeedComment extends Component{\n\trender(){\n\t\tconst {login,dispatch,currentTopicId,pHeight} = this.props\n\t\tconst sHeight = pHeight ? pHeight : 0 \n\t\tconst style = pHeight ? {overflow:'hidden',minHeight:pHeight} : {overflow:'hidden',height:0}\n\t\t// const tail = '<b>—— —— 来自lumia2046专版客户端</b>'\n\t\tconst tail = '<p style=\"text-align:right\"><a href=\"https://github.com/lumia2046/cnode\"> — — 来自lumia2046-react-cnode</a></p>'\n\t\tif(login.loginId){\n\t\t\treturn (<div style={style} className={styles.textarea}>\n\t\t\t\t\t\t<MuiThemeProvider>\n\t\t\t    \t\t<form className={styles.form} >\n\t\t\t\t\t\t\t<TextField hintText={this.props.defaultValue || '请输入内容'}  multiLine={true} ref='textarea'\n\t\t\t\t\t\t\tunderlineStyle={{color:'#00BCD4'}} onClick={e => {\n\t\t\t\t\t\t\t\tconst defaultValue = this.props.defaultValue || ''\n\t\t\t\t\t\t\t\te.target.value = e.target.value || defaultValue\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t/><br />\n\t\t\t\t\t\t\t<RaisedButton label=\"回复\" primary={true} onClick={e => {\n\t\t\t    \t\t\t\te.preventDefault();\n\t\t\t    \t\t\t\tconst textarea = this.refs.textarea.input.refs.input.value + tail\n\t\t\t    \t\t\t\tif(!textarea.trim()){\n\t\t\t    \t\t\t\t  return null;\n\t\t\t    \t\t\t\t}\n\t\t\t    \t\t\t\tconst {scrollT,contentH,windowH} = getSize()\n\t\t\t    \t\t\t\tdispatch(fetchComment(login.accessToken,currentTopicId,textarea))\n\t\t\t    \t\t\t\tif(pHeight === 120){\n\t\t\t    \t\t\t\t\tdispatch(recordArticleScrollT(currentTopicId,contentH-windowH))\n\t\t\t    \t\t\t\t}else{\n\t\t\t    \t\t\t\t\tdispatch(recordArticleScrollT(currentTopicId,scrollT))\n\t\t\t    \t\t\t\t}\n\t\t\t    \t\t\t\tthis.refs.textarea.input.refs.input.value = ''\n\t\t\t    \t\t\t}}/>\n\t\t\t    \t\t</form>\n\t\t\t\t\t\t</MuiThemeProvider>\n\t\t\t    \t</div>)\n\t\t}else{\n\t\t\treturn  (<div style={{overflow:'hidden',height:sHeight,lineHeight:`${sHeight}px`}} className={styles.textarea}>\n\t\t\t\t<LinkToLogin  dispatch={dispatch}/> \n\t\t\t</div>)\n\t\t}\n\t}\n}\nexport default Reply"
  },
  {
    "path": "src/components/Article/Reply/styles.scss",
    "content": ".reply{\n\tli{\n\tpadding: 10px 20px;\n\toverflow: hidden;\n\tborder-bottom: 1px solid #ccc;\n\t}\n\th2{\n\t\tpadding: 10px 20px;\n\t\tfont-size: 18px;\n\t\tbackground: #ccc;\n\t}\n\timg{\n\twidth: 50px;\n\theight: 50px;\n\tborder-radius: 50%;\n\t}\n\n\tb{\n\t\tdisplay: block;\n\t\ttext-align: right;\n\t}\n}\n\n.author{\n\tfloat: left;\n\ttext-align: center;\n\tpadding-top: 10px;\n}\n\n.main{\n\tpadding: 0 0 0 60px;\n}\n\n.item{\n\tpadding: 5px;\n\toverflow: hidden;\n}\n\n.form{\n\tpadding: 20px;\n}\n\n.textarea{\n\ttransition: all 0.3s ease-out;\n}"
  },
  {
    "path": "src/components/HomePage/Drawer/Drawer.js",
    "content": "import React from 'react';\nimport { Link } from 'react-router-dom'\nimport { connect } from 'react-redux'\nimport prefix from '../../../utils/routePrefix'\nimport Drawer from 'material-ui/Drawer'\nimport MenuItem from 'material-ui/MenuItem'\nimport RaisedButton from 'material-ui/RaisedButton'\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'\nimport Divider from 'material-ui/Divider'\nimport Avatar from 'material-ui/Avatar'\nimport { logout, fetchProfile } from '../../../actions'\nimport { setTransition } from '../../../actions/hashUrl'\nimport styles from './styles.scss'\nimport getSize from '../../../utils/getSize'\nimport transformDate from '../../../utils/transformDate'\nimport Dialog from '../../common/Dialog'\n\n@connect()\nexport default class myDrawer extends React.Component {\n  constructor() {\n    super();\n    this.state = {\n      isOpen: false\n    }\n  }\n\n  excuteLogout = () => {\n    const { dispatch } = this.props\n    window.localStorage.removeItem('masterInfo')\n    window.sessionStorage.removeItem('masterProfile')\n    dispatch(logout())\n  }\n\n  close = () => {\n    this.setState({\n      isOpen: false\n    })\n  }\n\n  componentWillUpdate(nextProps) {\n\n  }\n\n  render() {\n    let { contentW } = getSize()\n    if (contentW > 800) {\n      contentW = 640\n    } else {\n      contentW = 0.8 * contentW\n    }\n    let { login, profile } = this.props\n    const { succeed } = login\n    if (login.loginName !== profile.loginname && window.sessionStorage.masterProfile) {\n      profile = JSON.parse(window.sessionStorage.masterProfile)\n    }\n    const { avatar_url, loginname, score, create_at } = profile\n    return (\n      <MuiThemeProvider>\n        <Drawer\n          docked={false}\n          width={contentW}\n          open={this.props.openDrawer}\n          onRequestChange={this.props.toggleDrawer}\n        >\n          {succeed && <div>\n            <div className={styles.header}>\n              <Link to={`${prefix}/login`} className={styles.link}>\n                <Avatar src={avatar_url} size={80} />\n                <p>{loginname}</p>\n              </Link>\n              <p>积分:{score}</p>\n              <p>注册于:{transformDate(create_at)}</p>\n              <RaisedButton label=\"注销登陆\" primary={true} onClick={() => {\n                this.setState({\n                  isOpen: true\n                })\n              }} />\n              <Dialog isOpen={this.state.isOpen} action={this.excuteLogout} close={this.close}>\n                确定要注销登陆？\n              </Dialog>\n            </div>\n            <MenuItem onTouchTap={this.props.toggleDrawer}>\n              <Link to={`${prefix}/login`} className={styles.link}>\n                <i className=\"iconfont\" dangerouslySetInnerHTML={{ __html: '&#xe60f;' }} style={{ color: '#00BCD4' }}></i>\n                <span style={{ color: '#00BCD4' }}> 个人主页</span>\n              </Link>\n            </MenuItem>\n            <Divider />\n            <MenuItem onTouchTap={this.props.toggleDrawer}>\n              <Link to={`${prefix}/message`} className={styles.link}>\n                <i className=\"iconfont\" dangerouslySetInnerHTML={{ __html: '&#xe617;' }} style={{ color: '#00BCD4' }}></i>\n                <span style={{ color: '#00BCD4' }}> 消息</span>\n              </Link>\n            </MenuItem>\n            <Divider />\n          </div>}\n          {!succeed && <div className={styles.header}>\n            <Link to={`${prefix}/login`} className={styles.link} onClick={() => this.props.dispatch(setTransition({ transition: 'up' }))}>\n              <Avatar backgroundColor={'rgb(0,188,212)'} size={80}>\n                <i className=\"iconfont\" dangerouslySetInnerHTML={{ __html: '&#xe60f;' }}></i>\n              </Avatar>\n            </Link>\n            <p>点击头像登陆</p>\n          </div>}\n        </Drawer>\n      </MuiThemeProvider>\n    );\n  }\n}"
  },
  {
    "path": "src/components/HomePage/Drawer/styles.scss",
    "content": ".header{\n\ttext-align: center;\n\tpadding: 100px 10px 50px;\n\tbackground: #DCE775;\n\tp{\n\t\tmargin: 10px;\n\t}\n}\n\n.link{\n\tvertical-align: center;\n\tdisplay: block;\n\tborder-bottom: 1px solid #DCE775;\n}"
  },
  {
    "path": "src/components/HomePage/FloatingActionButton.js",
    "content": "import React from 'react'\nimport { connect } from 'react-redux'\nimport { setTransition } from '../../actions/hashUrl'\nimport FloatingActionButton from 'material-ui/FloatingActionButton'\nimport ContentAdd from 'material-ui/svg-icons/content/add'\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'\nimport { Link } from 'react-router-dom'\nimport prefix from '../../utils/routePrefix'\n\nconst style = {\n  position: 'fixed',\n  bottom: 50,\n  right: 50\n}\n\n@connect()\nclass FloatActionButton extends React.Component {\n  render() {\n    return (\n      <Link to={`${prefix}/publishTopic`} onClick={() => this.props.dispatch(setTransition({ transition: 'up' }))}>\n        <MuiThemeProvider>\n          <FloatingActionButton style={style} secondary={true}>\n            <ContentAdd />\n          </FloatingActionButton>\n        </MuiThemeProvider>\n      </Link>\n    )\n  }\n\n}\n\n\nexport default FloatActionButton"
  },
  {
    "path": "src/components/HomePage/Header/Header.js",
    "content": "import React, { Component } from 'react'\nimport { connect } from 'react-redux'\nimport styles from './styles.scss'\nimport { setTransition } from '../../../actions/hashUrl'\nimport { Link } from 'react-router-dom'\nimport prefix from '../../../utils/routePrefix'\nimport { Tabs, Tab } from 'material-ui/Tabs'\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'\nimport AppBar from 'material-ui/AppBar'\nimport Badge from 'material-ui/Badge'\nimport NotificationsIcon from 'material-ui/svg-icons/social/notifications'\nimport IconButton from 'material-ui/IconButton'\nimport SwipeableViews from 'react-swipeable-views'\nimport getSize from '../../../utils/getSize'\n\n@connect()\nclass Header extends Component {\n  constructor(props) {\n    super(props);\n    this.state = {\n      slideIndex: 0\n    }\n\n  }\n  componentWillMount() {\n    const { tabs, filter } = this.props;\n    let slideIndex;\n    tabs.forEach((tab, index) => {\n      if (tab.filter === filter) {\n        slideIndex = index;\n      }\n    })\n    this.setState({\n      slideIndex: slideIndex\n    })\n  }\n  handleChange = (value) => {\n    this.setState({\n      slideIndex: value,\n    });\n    this.props.onClick(this.props.tabs[value].filter)\n  };\n\n  render() {\n    // this.props.tabs.forEach(tab => {\n    //     tab.cn = classnames({[styles.active] : tab.filter === this.props.filter})\n    // })\n    return (\n      <MuiThemeProvider>\n        <div>\n          <div className={styles.header} style={{ top: -this.props.fixedTop, width: window.width||'100%' }}>\n            <AppBar title={<p style={{ textAlign: 'center' }}>NodeJS论坛</p>} onLeftIconButtonClick={this.props.toggleDrawer}\n              iconElementRight={<div style={{ marginTop: -8 }}>\n                <Badge badgeContent={this.props.unreadMessageCount} secondary={true} style={{ top: 3 }}>\n                  <Link to={`${prefix}/message`} onClick={() => this.props.dispatch(setTransition({ transition: 'up' }))}>\n                    <IconButton tooltip=\"未读消息\" style={{ padding: 0, width: 25, height: 25 }}>\n                      <div>\n                        <NotificationsIcon style={{ color: 'white' }} />\n                      </div>\n                    </IconButton>\n                  </Link>\n                </Badge>\n              </div>} />\n            <Tabs onChange={this.handleChange} value={this.state.slideIndex}>\n              {this.props.tabs.map((tab, i) =>\n                <Tab key={i} label={tab.title} value={i}>\n                </Tab>\n              )}\n            </Tabs>\n          </div>\n          <SwipeableViews index={this.state.slideIndex} onChangeIndex={this.handleChange}>\n            {this.props.children}\n          </SwipeableViews>\n        </div>\n      </MuiThemeProvider>\n    )\n  }\n}\n// Header.propTypes = {\n//   filter: PropTypes.string.isRequired,\n//   onClick: PropTypes.func.isRequired\n// }\n\nexport default Header"
  },
  {
    "path": "src/components/HomePage/Header/styles.scss",
    "content": ".header{\n\tposition: fixed;\n\toverflow: hidden;\n\tz-index: 10;\n\t// width: 100%;\n\ttransition: all 0.3s ;\n}\n\n\n"
  },
  {
    "path": "src/components/HomePage/Lists/Lists.js",
    "content": "import React from 'react'\nimport FlipMove from 'react-flip-move'\nimport transformDate from '../../../utils/transformDate'\nimport styles from './styles.scss'\nimport { setTransition } from '../../../actions/hashUrl'\nimport { Link } from 'react-router-dom'\nimport prefix from '../../../utils/routePrefix'\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'\nimport { List, ListItem } from 'material-ui/List'\nimport Divider from 'material-ui/Divider'\nimport Avatar from 'material-ui/Avatar'\nimport getSize from '../../../utils/getSize'\n\nconst Lists = props => {\n\tconst tabChn = { all: '全部', good: '精华', share: '分享', ask: '问答', job: '招聘' }\n\tconst { topics, fetchArticle, dispatch, article, isFetching, selectedTab, history } = props\n\tlet disableAllAnimations = topics.length === 20 ? false : true\n\t// disableAllAnimations从启用到禁用时enterAnimation设定的动画会不起作用，原因不明。\n\tlet enterAnimation = {\n\t\tfrom: { transform: 'translateY(-80px)', opacity: 0 },\n\t\tto: { transform: 'translateY(0)', opacity: 1 }\n\t}\n\treturn (\n\t\t<div style={{ position: 'relative' }}>\n\t\t\t<div className={styles.lists}>\n\t\t\t\t<MuiThemeProvider>\n\t\t\t\t\t<List>\n\t\t\t\t\t\t<FlipMove disableAllAnimations={disableAllAnimations} enterAnimation={enterAnimation}\n\t\t\t\t\t\t\teasing='ease-out' duration='400' staggerDelayBy='40' staggerDurationBy='4'>\n\t\t\t\t\t\t\t{topics.map((topic, i) =>\n\t\t\t\t\t\t\t\t<Link to={`${prefix}/topic/${topic.id}`} key={i} onClick={() => {\n\t\t\t\t\t\t\t\t\tdispatch(setTransition({ transition: 'move' }))\n\t\t\t\t\t\t\t\t\tif (!article[topic.id]) {\n\t\t\t\t\t\t\t\t\t\tdispatch(fetchArticle(topic.id))\n\t\t\t\t\t\t\t\t\t} else if (article.currentTopicId !== topic.id) {\n\t\t\t\t\t\t\t\t\t\tdispatch(fetchArticle(topic.id, false))\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}}>\n\t\t\t\t\t\t\t\t\t<ListItem\n\t\t\t\t\t\t\t\t\t\tleftAvatar={<Avatar src={topic.author.avatar_url} />}\n\t\t\t\t\t\t\t\t\t\tprimaryText={\n\t\t\t\t\t\t\t\t\t\t\t<div className={styles.text}>\n\t\t\t\t\t\t\t\t\t\t\t\t{topic.top && <span style={{ color: 'blue' }}>顶</span>}\n\t\t\t\t\t\t\t\t\t\t\t\t{topic.good && <span style={{ color: 'red' }}>精</span>}\n\t\t\t\t\t\t\t\t\t\t\t\t<span className={styles.title}>{topic.title}</span>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tsecondaryText={\n\t\t\t\t\t\t\t\t\t\t\t<div className={styles.text}>\n\t\t\t\t\t\t\t\t\t\t\t\t<span>{topic.reply_count + '/' + topic.visit_count}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t<span>{tabChn[topic.tab]}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t<span style={{ float: 'right' }}>{transformDate(topic.create_at)}</span>\n\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t<Divider inset={true} />\n\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t</FlipMove>\n\t\t\t\t\t</List>\n\t\t\t\t</MuiThemeProvider>\n\t\t\t\t<div className={styles.spinner}>\n\t\t\t\t\t<div className={styles.bounce1}></div>\n\t\t\t\t\t<div className={styles.bounce2}></div>\n\t\t\t\t\t<div className={styles.bounce3}></div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n// Lists.propTypes = {\n//   topics: PropTypes.array.isRequired,\n//   fetchArticle: PropTypes.func.isRequired\n// }\n\nexport default Lists\n\n\n\n\n\n"
  },
  {
    "path": "src/components/HomePage/Lists/styles.scss",
    "content": ".lists{\n  position: relative;\n\tmargin-top: 100px;\n}\n.li{\n  position: relative;\n  top:0;\n\theight: 61px;\n\tpadding: 5px 15px;\n  border-bottom: 1px solid #ccc;\n \n}\n\n.link{\n  display: block;\n}\n\n.text{\n  margin: 0 0 10px;\n  span{\n    padding-right: 10px;\n    vertical-align: middle;\n    font-weight:bold;\n  }\n\n  .title{\n    display: inline-block;\n    padding: 0;\n    width:80%;\n    overflow: hidden;\n    text-overflow:ellipsis;\n    white-space: nowrap;\n  }\n}\n.spinner {\n  margin: 24px auto;\n  width: 100%;\n  text-align: center;\n}\n \n.spinner > div {\n  width: 30px;\n  height: 30px;\n  margin: 0 20px;\n  background-color: #00BCD4;\n \n  border-radius: 100%;\n  display: inline-block;\n  -webkit-animation: bouncedelay 1.4s infinite ease-in-out;\n  animation: bouncedelay 1.4s infinite ease-in-out;\n  /* Prevent first frame from flickering when animation starts */\n  -webkit-animation-fill-mode: both;\n  animation-fill-mode: both;\n}\n \n.spinner .bounce1 {\n  -webkit-animation-delay: -0.32s;\n  animation-delay: -0.32s;\n}\n \n.spinner .bounce2 {\n  -webkit-animation-delay: -0.16s;\n  animation-delay: -0.16s;\n}\n \n@-webkit-keyframes bouncedelay {\n  0%, 80%, 100% { -webkit-transform: scale(0.0) }\n  40% { -webkit-transform: scale(1.0) }\n}\n \n@keyframes bouncedelay {\n  0%, 80%, 100% {\n    transform: scale(0.0);\n    -webkit-transform: scale(0.0);\n  } 40% {\n    transform: scale(1.0);\n    -webkit-transform: scale(1.0);\n  }\n}"
  },
  {
    "path": "src/components/Message/Content/Content.js",
    "content": "import React, { PropTypes, Component } from 'react'\nimport { connect } from 'react-redux'\nimport transformDate from '../../../utils/transformDate'\nimport styles from './styles.scss'\nimport classnames from 'classnames'\nimport { Link } from 'react-router-dom'\nimport { setTransition } from '../../../actions/hashUrl'\nimport prefix from '../../../utils/routePrefix'\nimport { Tabs, Tab } from 'material-ui/Tabs';\nimport SwipeableViews from 'react-swipeable-views';\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';\nimport { List, ListItem } from 'material-ui/List';\nimport Divider from 'material-ui/Divider';\nimport Avatar from 'material-ui/Avatar';\nimport RaisedButton from 'material-ui/RaisedButton';\nimport Dialog from '../../common/Dialog'\nimport CircleLoading from '../../common/CircleLoading'\nimport { markAllMessages, fetchMessage } from '../../../actions'\n\n@connect()\nclass Content extends Component {\n\tconstructor(props) {\n\t\tsuper(props);\n\t\tthis.state = {\n\t\t\tslideIndex: 0,\n\t\t\tisOpen: false,\n\t\t\tisUpdating: false\n\t\t}\n\t}\n\n\thandleChange = (value) => {\n\t\tthis.setState({\n\t\t\tslideIndex: value,\n\t\t});\n\t};\n\tmarkMessages = () => {\n\t\tlet { dispatch, login } = this.props\n\t\tlet accessToken = login.accessToken\n\t\tdispatch(markAllMessages(accessToken))\n\t\tthis.setState({\n\t\t\tisUpdating: true\n\t\t})\n\t}\n\tclose = () => {\n\t\tthis.setState({\n\t\t\tisOpen: false\n\t\t})\n\t}\n\tcomponentWillUpdate(newProps) {\n\t\tlet { dispatch, login, isMarked, hasNotReadMessage } = newProps\n\t\tlet accessToken = login.accessToken\n\t\tif (isMarked && isMarked !== this.props.isMarked) {\n\t\t\tdispatch(fetchMessage(accessToken))\n\t\t}\n\t\tif (isMarked && hasNotReadMessage.length === 0 &&\n\t\t\thasNotReadMessage.length !== this.props.hasNotReadMessage.length) {\n\t\t\tthis.setState({\n\t\t\t\tisUpdating: false\n\t\t\t})\n\t\t}\n\t}\n\trender() {\n\t\tconst { dispatch, hasNotReadMessage, hasReadMessage, article, fetchArticle } = this.props\n\t\treturn (\n\t\t\t<div>\n\t\t\t\t{this.state.isUpdating && <div><CircleLoading /></div>}\n\t\t\t\t{!this.state.isUpdating && <MuiThemeProvider>\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<Tabs onChange={this.handleChange} value={this.state.slideIndex}>\n\t\t\t\t\t\t\t<Tab label={<h2>未读消息:{hasNotReadMessage && hasNotReadMessage.length}</h2>} value={0} />\n\t\t\t\t\t\t\t<Tab label={<h2>已读消息:{hasReadMessage && hasReadMessage.length}</h2>} value={1} />\n\t\t\t\t\t\t</Tabs>\n\t\t\t\t\t\t<SwipeableViews index={this.state.slideIndex} onChangeIndex={this.handleChange}>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{hasNotReadMessage && hasNotReadMessage.length === 0 &&\n\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t<div className={styles.msg}>暂无未读消息</div>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t{hasNotReadMessage.length > 0 &&\n\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t<List>\n\t\t\t\t\t\t\t\t\t\t\t{hasNotReadMessage.map((msg, index) =>\n\t\t\t\t\t\t\t\t\t\t\t\t<Link key={index} to={`${prefix}/topic/${msg.topic.id}`} className={styles.link} onClick={(e) => {\n\t\t\t\t\t\t\t\t\t\t\t\t\tdispatch(setTransition({ transition: 'up' }))\n\t\t\t\t\t\t\t\t\t\t\t\t\tif (!article[msg.topic.id]) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdispatch(fetchArticle(msg.topic.id))\n\t\t\t\t\t\t\t\t\t\t\t\t\t} else if (article.currentTopicId !== topic.id) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tdispatch(fetchArticle(msg.topic.id, false))\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t}}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<ListItem\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tleftAvatar={<Avatar src={msg.author.avatar_url} />}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tprimaryText={msg.author.loginname}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsecondaryText={\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className={styles.oneline} dangerouslySetInnerHTML={{ __html: msg.reply.content }}></div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p style={{ fontSize: '14px' }}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span>来自:{msg.topic.title}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span style={{ float: 'right' }}>{transformDate(msg.reply.create_at)}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsecondaryTextLines={2}\n\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Divider inset={true} />\n\t\t\t\t\t\t\t\t\t\t\t\t</Link>)}\n\t\t\t\t\t\t\t\t\t\t</List>\n\t\t\t\t\t\t\t\t\t\t<div style={{ textAlign: 'center' }}>\n\t\t\t\t\t\t\t\t\t\t\t<RaisedButton label=\"清空未读消息\" primary={true} onClick={e => {\n\t\t\t\t\t\t\t\t\t\t\t\tthis.setState({\n\t\t\t\t\t\t\t\t\t\t\t\t\tisOpen: true\n\t\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t\t}} />\n\t\t\t\t\t\t\t\t\t\t</div>\n\n\t\t\t\t\t\t\t\t\t\t<Dialog isOpen={this.state.isOpen} title='注意' action={this.markMessages} close={this.close}>\n\t\t\t\t\t\t\t\t\t\t\t是否将所有未读消息标记为已读？\n\t\t\t\t        \t</Dialog>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t{hasReadMessage.length === 0 &&\n\t\t\t\t\t\t\t\t\t<div className={styles.msg}>您还没有查看过任何消息哦</div>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t{hasReadMessage.length > 0 &&\n\t\t\t\t\t\t\t\t\t<List>\n\t\t\t\t\t\t\t\t\t\t{hasReadMessage.map((msg, index) =>\n\t\t\t\t\t\t\t\t\t\t\t<Link key={index} to={`${prefix}/topic/${msg.topic.id}`} className={styles.link} onClick={(e) => {\n\t\t\t\t\t\t\t\t\t\t\t\tdispatch(setTransition({ transition: 'up' }))\n\t\t\t\t\t\t\t\t\t\t\t\tif (!article[msg.topic.id]) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tdispatch(fetchArticle(msg.topic.id))\n\t\t\t\t\t\t\t\t\t\t\t\t} else if (article.currentTopicId !== topic.id) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tdispatch(fetchArticle(msg.topic.id, false))\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}}>\n\t\t\t\t\t\t\t\t\t\t\t\t<ListItem\n\t\t\t\t\t\t\t\t\t\t\t\t\tleftAvatar={<Avatar src={msg.author.avatar_url} />}\n\t\t\t\t\t\t\t\t\t\t\t\t\tprimaryText={msg.author.loginname}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsecondaryText={\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div className={styles.oneline} dangerouslySetInnerHTML={{ __html: msg.reply.content }}></div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<p style={{ fontSize: '14px' }}>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span>来自:{msg.topic.title}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span style={{ float: 'right' }}>{transformDate(msg.reply.create_at)}</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\tsecondaryTextLines={2}\n\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t<Divider inset={true} />\n\t\t\t\t\t\t\t\t\t\t\t</Link>\n\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t</List>}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</SwipeableViews>\n\t\t\t\t\t</div>\n\t\t\t\t</MuiThemeProvider>}\n\t\t\t</div>\n\t\t);\n\t}\n\n}\n\nexport default Content\n\n\n\n\n"
  },
  {
    "path": "src/components/Message/Content/styles.scss",
    "content": ".content{\n\tmargin-top: 64px;\n\toverflow: hidden;\n}\n\n.link{\n\tdisplay: block;\n}\n.msg{\n\tpadding: 20px;\n\ttext-align: center;\n\tfont-size: 24px;\n}\n\n.oneline{\n\tfont-weight: bold;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}"
  },
  {
    "path": "src/components/PublishTopic/Form/Form.js",
    "content": "import React, { PropTypes,Component } from 'react'\nimport getStrLength from '../../../utils/getStrLength'\nimport styles from './styles.scss'\nimport classnames from 'classnames'\nimport TextField from 'material-ui/TextField';\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'\nimport RaisedButton from 'material-ui/RaisedButton'\nimport DropDownMenu from 'material-ui/DropDownMenu';\nimport MenuItem from 'material-ui/MenuItem';\n\nclass Form extends Component {\n\tconstructor(props) {\n\t  super(props);\n\t  this.state = {value: \"ask\"};\n\t}\n\thandleChange = (event, index, value) => this.setState({value});\n\trender(){\n\t\tconst {ifTitleErr,ifContentErr,showDialog,fetchPublishTopic,dispatch,login,state} = this.props\n\t\treturn (\n\t\t\t<MuiThemeProvider>\n\t\t\t\t<form className={styles.form}>\n\t\t\t\t  <div className={styles.content}>\n\t\t\t\t    <span className={styles.title}>请选择主题类别:</span>\n\t\t\t\t    <DropDownMenu value={this.state.value} onChange={this.handleChange} ref='select'>\n\t\t\t\t\t\t<MenuItem value=\"ask\" primaryText=\"问答\" />\n\t\t\t\t\t\t<MenuItem value=\"share\" primaryText=\"分享\" />\n\t\t\t\t\t\t<MenuItem value=\"job\" primaryText=\"招聘\" />\n\t\t\t\t    </DropDownMenu>\n\t\t\t\t  </div>\n\t\t\t\t  <div className={styles.content}>\n\t\t\t\t  \t<TextField ref='input' hintText=' 请输入标题，不少于十个字符'  floatingLabelText=' 请输入标题，不少于十个字符' onChange={e => {\n\t\t\t\t        let titleErr = getStrLength(e.target.value)<10 ? true:false\n\t\t\t\t        ifTitleErr(titleErr)\n\t\t\t\t    }}/>\n\t\t\t\t    <div style={{height:state.titleErr ? 20 : 0 }} className={styles.errorInfo}>标题不得少于十个字符！</div>\n\t\t\t\t  </div>\n\t\t\t\t  <div className={styles.content}>\n\t\t\t\t  \t  <TextField ref='textarea' hintText=' 请输入内容'  floatingLabelText='请输入内容' style={{textAlign:'left'}}\n\t\t\t\t  \t  multiLine={true} rows={2} onChange={e => {\n\t\t\t\t  \t      let contentErr = getStrLength(e.target.value)===0 ? true:false\n\t\t\t\t  \t      ifContentErr(contentErr)\n\t\t\t\t  \t  }}/>\n\t\t\t\t    <div style={{height:state.contentErr ? 20 : 0 }} className={styles.errorInfo}>内容不能为空！</div>\n\t\t\t\t  </div>\n\t\t\t\t  <RaisedButton label=\"提交\" primary={true} onClick={e => {\n\t\t\t\t  e.preventDefault()\n\t\t\t\t  const input = this.refs.input.input.value\n\t\t\t\t  const textarea = this.refs.textarea.input.refs.input.value\n\t\t\t\t  const select = this.refs.select.props.value\n\t\t\t\t  if(getStrLength(input) < 10 || !textarea.trim()){\n\t\t\t\t    return null\n\t\t\t\t  }\n\t\t\t\t  dispatch(fetchPublishTopic(login.accessToken,select,input,textarea))\n\t\t\t\t  showDialog()\n\t\t\t\t}} />\n\t\t\t\t</form>\n\t\t\t</MuiThemeProvider>\n\t\t)\n\t}\n}\n\nexport default Form"
  },
  {
    "path": "src/components/PublishTopic/Form/styles.scss",
    "content": ".form{\n\ttext-align: center;\n\tmargin-top: 80px;\n}\n\n.errorInfo{\n\tcolor: red;\n\tmargin: 5px;\n\toverflow: hidden;\n\ttransition: all 0.3s;\n}\n\n.content{\n\tmargin: 10px;\n}\n.title{\n\tposition: relative;\n\ttop: -20px;\n\t/*vertical-align: top;*/\n}"
  },
  {
    "path": "src/components/common/AsyncContainer.js",
    "content": "import React, { Component } from 'react'\nimport CircleLoading from './CircleLoading'\n\nclass AsyncContainer extends Component {\n    state = { mounted: false }\n\n    componentDidMount() {\n        this.timeOut = setTimeout(() => {\n            this.setState({ mounted: true })\n        }, this.props.delay || 500)\n    }\n\n    componentWillUnmount() {\n        clearTimeout(this.timeOut)\n    }\n\n    render() {\n        return (\n            <div>\n                <div style={{ height: this.state.mounted ? 0 : 'auto', overflow: 'hidden' }}>\n                    <CircleLoading />\n                </div>\n                {this.state.mounted && this.props.children}\n            </div>\n        )\n    }\n    componentWillUnmount() {\n        clearTimeout(this.timeOut)\n    }\n}\n\nexport default AsyncContainer"
  },
  {
    "path": "src/components/common/CircleLoading.js",
    "content": "import React from 'react'\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'\nimport CircularProgress from 'material-ui/CircularProgress'\nimport getSize from '../../utils/getSize'\n\nconst { windowH, windowW } = getSize()\nconst style = {\n\tposition:'relative',\n\tpaddingTop: 0.5 * windowH - 40,\n\ttextAlign: 'center'\n\n}\nconst CircleLoading = () => (\n\t<div style={style}>\n\t\t<MuiThemeProvider>\n\t\t\t<CircularProgress size={60} thickness={7} />\n\t\t</MuiThemeProvider>\n\t</div>\n\n);\n\nexport default CircleLoading;"
  },
  {
    "path": "src/components/common/Dialog.js",
    "content": "import React from 'react';\n// import { hashHistory,browserHistory } from 'react-router';\nimport Dialog from 'material-ui/Dialog';\nimport FlatButton from 'material-ui/FlatButton';\nimport RaisedButton from 'material-ui/RaisedButton';\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';\n\nconst DialogExample = (props) => {\n  const handleClose = () => {\n    props.close()\n  };\n  const handleJump = () => {\n    props.close()\n    if(props.link){\n      // hashHistory.push(props.link)\n      // browserHistory.push(props.link)\n      // pros.location = props.link\n      alert('a')\n    }\n    if(props.action){\n      props.action()\n    }\n    \n  }\n  const actions = props.singleButton ? [<FlatButton label=\"确定\" primary={true} onTouchTap={handleClose}/>] : \n  [<FlatButton label=\"取消\" primary={true} onTouchTap={handleClose}/>,\n    <FlatButton label=\"确定\" primary={true} onTouchTap={handleJump}/>];\n  return (\n    <MuiThemeProvider>\n      <Dialog\n        title={props.title || ''}\n        actions={actions}\n        modal={false}\n        open={props.isOpen}\n        onRequestClose={handleClose}>\n        {props.children}\n      </Dialog>\n    </MuiThemeProvider>\n  );\n}\n\nexport default DialogExample"
  },
  {
    "path": "src/components/common/Header/Header.js",
    "content": "import React, { Component } from 'react'\nimport { connect } from 'react-redux'\nimport { withRouter } from 'react-router-dom'\nimport { setTransition } from '../../../actions/hashUrl'\nimport styles from './styles.scss'\nimport AppBar from 'material-ui/AppBar'\nimport IconButton from 'material-ui/IconButton'\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'\nimport getSize from '../../../utils/getSize'\n\n// @withRouter\n@connect(store => ({ hashUrl: store.hashUrl }))\nclass Header extends Component {\n\tconstructor() {\n\t\tsuper()\n\t\tthis.state = { position: 'static' }\n\n\t}\n\tcomponentWillMount() {\n\t\tif (this.props.position) {\n\t\t\tthis.state.position = 'absolute'\n\t\t\tthis.state.left = '100%'\n\t\t}\n\t}\n\n\tcomponentDidMount() {\n\t\tif (this.props.position) {\n\t\t\tthis.setState({ left: 0 })\n\t\t\tsetTimeout(() => this.setState({ position: 'fixed' }), 500)\n\t\t}\n\t}\n\n\tcomponentWillUnmount() {\n\n\t}\n\trender() {\n\t\tconst { isFetching, title, history, hashUrl } = this.props\n\t\treturn (\n\t\t\t<div className={styles.header} style={{ position: this.state.position, left: this.state.left, width: window.width || '100%' }}>\n\t\t\t\t<MuiThemeProvider>\n\t\t\t\t\t<AppBar\n\t\t\t\t\t\ttitle={<p className={styles.title}>\n\t\t\t\t\t\t\t{isFetching ? '加载中' : title}\n\t\t\t\t\t\t</p>}\n\t\t\t\t\t\ticonElementLeft={<IconButton>\n\t\t\t\t\t\t\t<i className=\"iconfont\" dangerouslySetInnerHTML={{ __html: '&#xe611;' }}></i>\n\t\t\t\t\t\t</IconButton>}\n\t\t\t\t\t\tonLeftIconButtonClick={() => {\n\t\t\t\t\t\t\tthis.props.dispatch(setTransition({ transition: 'left' }))\n\t\t\t\t\t\t\thistory.goBack()\n\t\t\t\t\t\t\tif (this.props.position) {\n\t\t\t\t\t\t\t\tsetTimeout(() => this.setState({ left: '100%' }), 450)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// window.location.href = `${window.location.href.split('#')[0]}#${hashUrl.oldUrl}`\n\t\t\t\t\t\t}}\n\t\t\t\t\t/>\n\t\t\t\t</MuiThemeProvider>\n\t\t\t</div>\n\t\t)\n\t}\n}\n\n\nexport default withRouter(Header)"
  },
  {
    "path": "src/components/common/Header/styles.scss",
    "content": ".header{\n\t// position: fixed;\n\twidth: 100%;\n\tz-index: 10;\n\ttransition: all 0.5s ease-out;\n}\n\n.title{\n\tpadding-right: 30px;\n\ttext-align: center;\n}\n\n\n"
  },
  {
    "path": "src/components/common/LinkToLogin/LinkToLogin.js",
    "content": "import React, { PropTypes } from 'react'\nimport { Link } from 'react-router-dom'\nimport { setTransition } from '../../../actions/hashUrl'\nimport prefix from '../../../utils/routePrefix'\nimport classnames from 'classnames'\nimport { setCurrentRouter } from '../../../actions'\nimport styles from './styles.scss'\nimport FlatButton from 'material-ui/FlatButton';\nimport RefreshIndicator from 'material-ui/RefreshIndicator';\nimport CircularProgress from 'material-ui/CircularProgress';\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';\n\n\nconst LinkToLogin = props => {\n\tlet { dispatch } = props\n\tconst masterInfo = window.localStorage.getItem('masterInfo') ? true : false\n\treturn (\n\t\t<div className={styles.linkToLogin} >\n\t\t\t{!masterInfo &&\n\t\t\t\t<Link to={`${prefix}/login`} className={styles.link} onClick={() => {\n\t\t\t\t\tdispatch(setTransition({ transition: 'up' }))\n\t\t\t\t\tdispatch(setCurrentRouter('login'))\n\t\t\t\t}}>\n\t\t\t\t\t<MuiThemeProvider>\n\t\t\t\t\t\t<FlatButton label=\"点击登陆\" primary={true} />\n\t\t\t\t\t</MuiThemeProvider>\n\t\t\t\t</Link>\n\t\t\t}\n\t\t\t{masterInfo &&\n\t\t\t\t<div style={{ paddingTop: 100 }}>\n\t\t\t\t\t<MuiThemeProvider>\n\t\t\t\t\t\t<CircularProgress size={60} thickness={7} />\n\t\t\t\t\t</MuiThemeProvider>\n\t\t\t\t</div>\n\t\t\t}\n\t\t</div>\n\t)\n}\n\nexport default LinkToLogin"
  },
  {
    "path": "src/components/common/LinkToLogin/styles.scss",
    "content": ".linkToLogin{\n\tdisplay: block;\n\ttext-align: center;\n}"
  },
  {
    "path": "src/components/common/Profile/Profile.js",
    "content": "import React from 'react'\nimport {Link} from 'react-router-dom'\nimport prefix from '../../../utils/routePrefix'\nimport styles from './styles.scss'\nimport { setTransition } from '../../../actions/hashUrl'\nimport transformDate from '../../../utils/transformDate'\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';\nimport Avatar from 'material-ui/Avatar';\nimport {List, ListItem} from 'material-ui/List';\nimport Subheader from 'material-ui/Subheader';\nimport Divider from 'material-ui/Divider';\n\nconst Profile = props => {\n\tlet {collectedTopics,profile} = props\n\tlet {avatar_url,create_at,loginname,recent_replies,recent_topics,score} = profile;\n\trecent_replies = recent_replies ? recent_replies : [];\n\trecent_topics = recent_topics ? recent_topics : [];\n\n\treturn (\n\t\t<div className={styles.profile}>\n\t\t\t<div className={styles.header}>\n\t\t\t\t<img src={avatar_url} alt={loginname}/>\n\t\t\t\t<p>{loginname}</p>\n\t\t\t\t<p>积分:{score}</p>\n\t\t\t\t<p>注册于:{transformDate(create_at)}</p>\n\t\t\t</div>\n\t\t\t<div className={styles.boxs}>\n\t\t\t\t<div className={styles.box}>\n\t\t\t\t\t<MuiThemeProvider>\n\t\t\t\t\t\t<List>\n\t\t\t\t\t\t  <Subheader>收藏的话题</Subheader>\n\t\t\t\t\t\t  <Divider/>\n\t\t\t\t\t\t  <TopicList {...props} topics={collectedTopics}/>\n\t\t\t\t\t\t</List>\n\t\t\t\t\t</MuiThemeProvider>\n\t\t\t\t</div>\n\t\t\t\t<div className={styles.box}>\n\t\t\t\t\t<MuiThemeProvider>\n\t\t\t\t\t\t<List>\n\t\t\t\t\t\t  <Subheader>最近参与的话题</Subheader>\n\t\t\t\t\t\t  <Divider/>\n\t\t\t\t\t\t  <TopicList {...props} topics={recent_replies}/>\n\t\t\t\t\t\t</List>\n\t\t\t\t\t</MuiThemeProvider>\n\t\t\t\t</div>\n\t\t\t\t<div className={styles.box}>\n\t\t\t\t\t<MuiThemeProvider>\n\t\t\t\t\t\t<List>\n\t\t\t\t\t\t  <Subheader>最近创建的话题</Subheader>\n\t\t\t\t\t\t  <Divider/>\n\t\t\t\t\t\t  <TopicList {...props} topics={recent_topics}/>\n\t\t\t\t\t\t</List>\n\t\t\t\t\t</MuiThemeProvider>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\nconst TopicList = props => {\n\tconst {dispatch,article,fetchArticle,topics} = props;\n\n\treturn (\n\t\t<div>\n\t\t    {topics.length === 0 && <ListItem primaryText=\"还没有相关话题\" />}\n\t\t    {topics.length > 0 && \n\t\t\t\ttopics.map((topic,index) => \n\t\t\t\t\t<Link key={index} to={`${prefix}/topic/${topic.id}`} className={styles.link} onClick={() => {\n\t\t\t\t\t\tdispatch(setTransition({ transition: 'up' }))\n\t\t\t\t\t  if(!article[topic.id]){\n\t\t\t\t\t    dispatch(fetchArticle(topic.id))\n\t\t\t\t\t  }else if(article.currentTopicId !== topic.id){\n\t\t\t\t\t    dispatch(fetchArticle(topic.id,false))\n\t\t\t\t\t  }\n\t\t\t\t\t  }}>\n\t\t\t\t\t  <ListItem primaryText={topic.title} leftAvatar={<Avatar src={topic.author.avatar_url} />}/>\n\t\t\t\t\t  <Divider/>\n\t\t\t\t\t</Link>\n\t\t\t\t\t\n\t\t\t\t)\n\t\t\t}\n\t\t</div>\n\t)\n}\n\n\nconst ListExampleChat = () => (\n    <List>\n      <Subheader>Recent chats</Subheader>\n      <ListItem\n        primaryText=\"Brendan Lim\"\n        leftAvatar={<Avatar src=\"images/ok-128.jpg\" />}\n      />\n      <Divider />\n    </List>\n);\n\n\nexport default Profile"
  },
  {
    "path": "src/components/common/Profile/styles.scss",
    "content": ".header{\n\ttext-align: center;\n\tpadding:  0 20px 20px;\n\timg{\n\t\twidth: 50px;\n\t\theight: 50px;\n\t\tborder-radius: 50%;\n\t}\n\tp{\n\t\tmargin: 10px;\n\t}\n}\n\n.boxs{\n\tpadding: 20px;\n\toverflow: hidden;\n}\n\n.box{\n\tfloat: left;\n\tmargin: 1.6%;\n\twidth: 30%;\n\t/*padding: 0 20px 20px;*/\n\tbox-shadow: 0 0 5px grey;\n\tbackground: white;\n\th2{\n\t\tpadding: 10px;\n\t}\n}\n.title{\n\tfont-weight:bold;\n\tpadding: 5px 10px;\n\tborder-bottom: 1px solid grey;\n\ttext-align: left;\n\toverflow: hidden;\n\ttext-overflow:ellipsis;\n\twhite-space: nowrap;\n}\n.link{\n\tdisplay: block;\n}\n@media only screen and (max-width: 768px) {\n    .box {\n        margin: 5%;\n        width: 90%;\n    }\n}"
  },
  {
    "path": "src/components/common/Snackbar.js",
    "content": "import React from 'react';\nimport Snackbar from 'material-ui/Snackbar';\nimport RaisedButton from 'material-ui/RaisedButton';\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';\n\nconst SnackbarExample = (props) => {\n\n    return (\n      <MuiThemeProvider>\n        <Snackbar\n          open={props.isOpened}\n          message={props.message}\n          autoHideDuration={3000}\n          style={{textAlign:'center'}}\n        />\n      </MuiThemeProvider>\n    );\n}\n\nexport default SnackbarExample"
  },
  {
    "path": "src/components/common/react-pullrefresh.js",
    "content": "import React, { Component } from 'react'\nimport RefreshIndicator from 'material-ui/RefreshIndicator';\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';\n// import './rotate.less'\n// import image from './pull.svg'\n\nconst MAX_DEFAULT = 100\n\nclass Pull extends Component {\n  static defaultProps = {\n    max: MAX_DEFAULT\n  }\n\n  constructor(props) {\n    super(props)\n    this.state = {\n      pulled:0\n    }\n  }\n\n  componentDidMount() {\n    this.pullhelper = require('pullhelper')\n\n    let { disabled,onRefresh,max } = this.props\n    let maxPull = max || MAX_DEFAULT\n    let that= this\n\n    this.pullhelper\n    .on('start',function(pulled) {\n      that.setState({\n        pulling:true\n      })\n    })\n    .on('stepback',function(pulled,next) {\n      that.setState({\n        pulled:pulled\n      })\n      let nextPulled = Math.min(pulled - Math.min(pulled/2,10),max)\n      next(nextPulled)\n    })\n    .on('step',function(pulled) {\n      that.setState({\n        pulled:pulled\n      })\n    })\n    .on('pull',function(pulled,next) {\n      that.setState({\n        pulling:false\n      })\n      if(!onRefresh || pulled < maxPull) {\n        next()\n        return\n      }\n      that.setState({\n        loading:true\n      })\n      onRefresh(_ => {\n        that.setState({\n          loading:false\n        })\n        next()\n      })\n    })\n    .load()\n    if(disabled) {\n      this.pullhelper.pause()\n    }\n  }\n  componentWillReceiveProps(nextProps) {\n    let { disabled } = this.props\n    if(disabled !== nextProps.disabled) {\n      if(nextProps.disabled) {\n        this.pullhelper.pause()\n      } else {\n        this.pullhelper.resume()\n      }\n    }\n  }\n  componentWillUnmount() {\n    this.pullhelper.unload()\n  }\n  render() {\n    let { pulling,loading,pulled } = this.state\n    let maxPull = this.props.max || MAX_DEFAULT\n    let size = this.props.size || 30\n    let color = this.props.color || '#00BCD4'\n    let style = this.props.style || {}\n    return (\n      <div>\n        <div style={{\n          display:pulling ? 'block' : 'none',\n          position:'fixed',\n          top:0,\n          left:0,\n          right:0,\n          bottom:0,\n          zIndex:this.props.zIndex,\n          userSelect:'none'\n        }} />\n        <div style={Object.assign({\n          background:'white',\n          width: size,\n          height: size,\n          position:'fixed',\n          zIndex:this.props.zIndex,\n          top:-size+Math.min(pulled,maxPull),\n          left:'50%',\n          borderRadius:size/2,\n          transform:'translate(-50%,-50%)',\n          boxShadow:'0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2)',\n          userSelect:'none'\n        },style)}>\n          <MuiThemeProvider>\n            <RefreshIndicator\n                  percentage={80}\n                  size={size}\n                  left={0}\n                  top={0}\n                  color={color}\n                  loadingColor={color}\n                  status= {pulled/maxPull > 0.9999 ? 'loading':'ready'}\n                  style={{display:'inline-block',\n                          position:'relative',\n                          opacity:pulled/maxPull}}\n                />\n          </MuiThemeProvider>\n        </div>\n      </div>\n    )\n  }\n}\n\nexport default Pull;\n\n\n\n\n\n\n// <i style={{\n//   display:'block',\n//   width:'100%',\n//   height:'100%',\n//   lineHeight:`${size}px`,\n//   fontSize:`${size*0.6}px`,\n//   color: color,\n//   opacity:pulled/maxPull,\n//   textAlign:'center',\n//   transform:`rotate(${pulled/maxPull*360}deg)`,\n//   animation:'rotating 2s linear infinite'\n// }} className=\"iconfont\" dangerouslySetInnerHTML={{__html: '&#xe603;'}}> \n// </i>"
  },
  {
    "path": "src/configureStore.js",
    "content": "import { createStore, applyMiddleware } from 'redux';\nimport { composeWithDevTools } from 'redux-devtools-extension';\nimport thunk from 'redux-thunk';\nimport { createLogger } from 'redux-logger';\nimport { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux';\nimport createHistory from 'history/createBrowserHistory';\nimport rootReducer from './reducers/index'\n\nconst history = createHistory();\nconst middleware = routerMiddleware(history)\nconst logger = createLogger({ collapsed: true })\nconst middlewares = [thunk, middleware]\n\nif(process.env.NODE_ENV === 'development') {\n        middlewares.push(logger)\n}\nconst store = createStore(rootReducer, composeWithDevTools(applyMiddleware(...middlewares)))\n    \nexport default store\n\n\n"
  },
  {
    "path": "src/containers/App.js",
    "content": "import React, { Component, PropTypes } from 'react'\nimport { connect } from 'react-redux'\nimport {fetchAccess,fetchMessage,fetchProfile} from '../actions'\nimport Header from '../components/common/Header/Header'\nimport Content from '../components/Article/Content/Content'\nimport Reply from '../components/Article/Reply/Reply'\nimport getSize from '../utils/getSize'\n\n\nclass App extends Component {\n  componentWillMount(){\n    // console.log('componentWillMount')\n    const {dispatch} = this.props;\n    const LoadingAction = (accessToken,loginName) => {\n      dispatch(fetchAccess(accessToken))\n      dispatch(fetchMessage(accessToken))\n      dispatch(fetchProfile(loginName))\n    }\n    if(window.localStorage.getItem('masterInfo')){\n      let masterInfo = window.localStorage.getItem('masterInfo')\n      masterInfo = JSON.parse(masterInfo)\n      const accessToken = masterInfo.accessToken\n      const loginName = masterInfo.loginName\n      LoadingAction(accessToken,loginName)\n    }else{\n      // const accessToken = '1cbc2a58-6c1b-426f-971d-070676fb849d'\n      // const loginName = 'lumia2046'\n      // LoadingAction(accessToken,loginName)\n    }\n  }\n\n  render() {\n      return (\n          <div>{this.props.children}</div>\n      )\n  }\n}\n\nApp.propTypes = {\n  // currentTopicId: PropTypes.string.isRequired,\n  // article: PropTypes.object.isRequired,\n  // isFetching: PropTypes.bool.isRequired,\n  // dispatch: PropTypes.func.isRequired\n}\n\nfunction mapStateToProps(state) {\n  const {login,profile} = state\n  return {login,profile}\n}\n\n\nexport default connect(mapStateToProps)(App)"
  },
  {
    "path": "src/containers/Article.js",
    "content": "import React, { Component, PropTypes } from 'react'\nimport { connect } from 'react-redux'\nimport { switchSupport, fetchComment, fetchArticle, recordArticleScrollT, fetchProfile } from '../actions'\nimport Header from '../components/common/Header/Header'\nimport CircleLoading from '../components/common/CircleLoading'\nimport AsyncContainer from '../components/common/AsyncContainer'\nimport Content from '../components/Article/Content/Content'\nimport Reply from '../components/Article/Reply/Reply'\nimport getSize from '../utils/getSize'\n\n\nclass Article extends Component {\n  constructor() {\n    super()\n    this.state = {\n      fadeIn: true\n    }\n  }\n  componentWillMount() {\n\n    const { scrollT, dispatch, article, isFetching } = this.props\n\n    if (scrollT) {\n      // window.scrollTo(0, scrollT)\n    }\n    // window.scrollTo(0, scrollT)\n\n    if (!article.author && !isFetching) {\n      const topicId = window.location.href.split('topic/')[1].split('?_')[0]\n      dispatch(fetchArticle(topicId))\n    }\n  }\n\n  componentWillReceiveProps(newProps) {\n    const { scrollT } = newProps\n    document.getElementById('articleContainer').scrollTop = scrollT\n  }\n\n  componentDidMount() {\n    const { scrollT } = this.props\n    document.getElementById('articleContainer').scrollTop = scrollT\n  }\n\n  componentWillUnmount() {\n    const { currentTopicId, dispatch, profile, login } = this.props\n    dispatch(recordArticleScrollT(currentTopicId, document.getElementById('articleContainer').scrollTop))\n    if (!window.sessionStorage.masterProfile && login.loginName === profile.loginname) {\n      window.sessionStorage.masterProfile = JSON.stringify(profile)\n    }\n  }\n\n  render() {\n    let { isFetching, article, currentTopicId, login, switchSupportInfo, isCommented, dispatch, collectedTopics, profile } = this.props\n    if (login.loginName !== profile.loginname && window.sessionStorage.masterProfile) {\n      collectedTopics = JSON.parse(window.sessionStorage.masterProfile).collectedTopics\n    }\n    return (\n\n      <div id='articleContainer' style={{ width: window.width, height: window.height, overflow: 'auto' }}>\n        <Header isFetching={isFetching} title='详情' showBack={true} position={true} />\n        {Object.keys(article).length === 0 && <CircleLoading />}\n        {Object.keys(article).length !== 0 &&\n          <div>\n            <Content {...({ article, dispatch, fetchProfile, login, collectedTopics, profile })} />\n\n            <Reply replies={article.replies}\n              {...({ login, dispatch, switchSupportInfo, currentTopicId, profile, isCommented })} />\n\n          </div>\n        }\n      </div>\n\n    )\n  }\n}\n\nfunction mapStateToProps(state) {\n  const { currentRouter, login, profile } = state;\n  const { currentTopicId, switchSupportInfo, isCommented } = state.article;\n  const { collectedTopics } = profile\n  const isFetching = state.article[currentTopicId] ? state.article[currentTopicId].isFetching : false;\n  const scrollT = state.article[currentTopicId] ? state.article[currentTopicId].scrollT : '0';\n  const article = state.article[currentTopicId] && state.article[currentTopicId].article ? state.article[currentTopicId].article : {};\n  return { isFetching, scrollT, article, currentTopicId, login, switchSupportInfo, currentRouter, collectedTopics, profile, isCommented }\n}\n\n\nexport default connect(mapStateToProps)(Article)"
  },
  {
    "path": "src/containers/HomePage.js",
    "content": "import React, { Component, PropTypes } from 'react'\nimport { connect } from 'react-redux'\nimport { withRouter } from 'react-router-dom'\nimport { selectTab, fetchTopics, recordScrollT, fetchArticle, fetchAccess, fetchMessage } from '../actions'\nimport Header from '../components/HomePage/Header/Header'\nimport FloatingActionButton from '../components/HomePage/FloatingActionButton'\nimport CircleLoading from '../components/common/CircleLoading'\nimport Drawer from '../components/HomePage/Drawer/Drawer'\nimport Lists from '../components/HomePage/Lists/Lists'\nimport Snackbar from '../components/common/Snackbar'\nimport getSize from '../utils/getSize'\nimport { setTransition } from '../actions/hashUrl'\nimport Pull from '../components/common/react-pullrefresh'\n\n// @withRouter\n@connect(state => {\n  const { homePage, article, login, profile, message, hashUrl } = state\n  const { selectedTab, tabData } = homePage;\n  const unreadMessageCount = message.hasNotReadMessage.length\n  // 当组件第一次render时,还未进行任何action,初始化的state里没有tabData[selectedTab]，所以这里需要初始化\n  const { isFetching, page, scrollT, topics } = tabData[selectedTab] || { isFetching: false, page: 0, scrollT: 0, topics: [] }\n  return { isFetching, page, scrollT, topics, selectedTab, article, login, profile, tabData, unreadMessageCount, hashUrl }\n})\nclass HomePage extends Component {\n  constructor() {\n    super()\n    this.state = {\n      fadeIn: true,\n      openDrawer: false,\n      openSnackbar: false,\n      isFreshing: false,\n      fixedTop: 0,\n      scrollT: 0\n    }\n  }\n\n  onRefresh = (next) => {\n    if (!this.state.isFreshing) {\n      this.setState({\n        isFreshing: true\n      })\n      this.fresh()\n      setTimeout(_ => {\n        next()\n        this.openSnackbar()\n        this.setState({\n          isFreshing: false\n        })\n      }, 3000)\n    }\n  }\n  fresh = () => {\n    const { selectedTab, login, dispatch } = this.props;\n    dispatch(fetchTopics(selectedTab, 1))\n    dispatch(fetchMessage(login.accessToken))\n  }\n  openSnackbar = () => {\n    this.setState({\n      openSnackbar: true\n    })\n    setTimeout(() => {\n      this.setState({\n        openSnackbar: false\n      })\n    }, 2500)\n  }\n  handleClick = (tab) => {\n    let homeContainerDom = document.getElementById('homeContainer')\n    let scrollT = homeContainerDom.scrollTop\n    const { selectedTab, dispatch, tabData } = this.props\n    dispatch(recordScrollT(selectedTab, scrollT))\n    dispatch(selectTab(tab))\n    homeContainerDom.scrollTop = tabData[tab].scrollT || 0\n\n    const ua = navigator.userAgent\n    if (!tabData[tab] && ua.indexOf('Mobile') === -1) {\n      if (scrollT >= 64) {\n        dispatch(recordScrollT(tab, 64))\n        this.setState({\n          scrollT: 64\n        })\n      } else {\n        dispatch(recordScrollT(tab, scrollT))\n        this.setState({\n          scrollT: scrollT\n        })\n      }\n    }\n\n    // 在事件的回调函数中的action，是将action都发送并改变state完毕，再更新state\n    // 也就是说事件回调函数中发送多个不带异步的action,只在最后一个action更新完state进行一次状态更新\n    // 要想每次action更新state后，都更新一次状态(方便在更新时做一些操作)，就要把action封装成异步操作\n    // 但是在react生命周期里的一个函数里发送多个action,每个action更新state都会对相应组件进行一次状态更新，不需要封装成异步操作\n    // let asyncDispatch = async function(){\n    //   await dispatch(recordScrollT(selectedTab,scrollT))\n    //   await dispatch(selectTab(tab))\n    // }\n    // asyncDispatch()\n  }\n  loadMore = () => {\n    const { selectedTab, page, isFetching, dispatch } = this.props;\n    let ipage = page\n    if (!isFetching) {\n      dispatch(fetchTopics(selectedTab, ++ipage))\n    }\n  }\n  toggleDrawer = () => {\n    this.setState({\n      openDrawer: !this.state.openDrawer\n    })\n  }\n\n  componentWillMount() {\n    const { scrollT } = this.props\n  }\n  componentWillUpdate(newProps, newState) {\n\n\n    const { topics, isFetching, selectedTab, scrollT, dispatch } = newProps\n    // 去除刷新时记住的滚动条位置\n    if (topics.length === 0 && this.props.scrollT === 0) {\n      window.scrollTo(0, 0)\n    }\n    // fetchTopics开始后会先发送一个request的action,这个ation也会改变state从而触发该方法。如果不对isFetching进行判断，会再次进行fetchTopics从而进行了不必要的重复数据请求\n    if (!isFetching && topics.length === 0) {\n      dispatch(fetchTopics(selectedTab));\n    }\n    if (selectedTab !== this.props.selectedTab) {\n      if (scrollT) {\n        // console.log(scrollT, \" window.scrollTo\",'componentWillUpdate')\n        window.scrollTo(0, scrollT)\n      }\n    }\n  }\n\n  tabs = [\n    {\n      title: '全部',\n      filter: 'all',\n    },\n    {\n      title: '精华',\n      filter: 'good',\n    },\n    {\n      title: '分享',\n      filter: 'share',\n    },\n    {\n      title: '问答',\n      filter: 'ask',\n    },\n    {\n      title: '招聘',\n      filter: 'job',\n    }\n  ]\n\n  render() {\n\n    const { selectedTab, isFetching, page, topics, dispatch, article, currentRouter, login, profile, unreadMessageCount, tabData, history } = this.props\n    return (\n      <div id='homeContainer' style={{ width: window.width, height: window.height, overflow: 'auto' }}>\n        <Header filter={selectedTab} onClick={this.handleClick} toggleDrawer={this.toggleDrawer}\n          fixedTop={this.state.fixedTop} unreadMessageCount={unreadMessageCount} tabs={this.tabs}>\n          {this.tabs.map((tab, index) =>\n            <div key={index}>\n              {((isFetching && page === 0) || (tab.filter !== selectedTab && !tabData[tab.filter])) && <CircleLoading />}\n              {tab.filter === selectedTab && <div style={{ opacity: (!isFetching || page >= 1) ? 1 : 0 }}>\n                <Lists topics={topics} fetchArticle={fetchArticle} dispatch={dispatch} article={article} isFetching={isFetching} selectedTab={selectedTab} history={history} />\n              </div>}\n            </div>\n          )}\n        </Header>\n        {!isFetching && <FloatingActionButton />}\n        <Drawer toggleDrawer={this.toggleDrawer} openDrawer={this.state.openDrawer}\n          {...({ login, dispatch, profile })} />\n        <Snackbar isOpened={this.state.openSnackbar} message='刷新成功' />\n      </div>\n    )\n  }\n\n  componentDidMount() {\n    const { selectedTab, page, scrollT, dispatch } = this.props\n    if (page === 0) {\n      dispatch(fetchTopics(selectedTab))\n    }\n\n    let homeContainerDom = document.getElementById('homeContainer')\n    if (scrollT) {\n      homeContainerDom.scrollTop = scrollT\n    }\n\n    homeContainerDom.onscroll = () => {\n      let scrollT = homeContainerDom.scrollTop\n      let contentH = homeContainerDom.scrollHeight\n      let { windowH } = getSize()\n      if (windowH + scrollT + 10 > contentH) {\n        this.loadMore()\n      }\n\n\n      // 由于下面的操作比较费cpu,所以进行判断是否为手机端\n      let lastScrollY = this.lastScrollY\n      const ua = navigator.userAgent;\n      if (ua.indexOf('Mobile') === -1) {\n        if (!lastScrollY || !scrollT) {\n          lastScrollY = scrollT\n        }\n        let diff = scrollT - lastScrollY\n        if (diff >= 0) {\n          if (scrollT > 64 && this.state.fixedTop !== 64) {\n            this.setState({\n              fixedTop: 64\n            })\n          }\n          if (scrollT <= 64) {\n            this.setState({\n              fixedTop: scrollT\n            })\n          }\n        } else {\n          this.setState({\n            scrollT: 0\n          })\n          if (scrollT > 64 && this.state.fixedTop !== 0) {\n            this.setState({\n              fixedTop: 0\n            })\n          }\n        }\n        lastScrollY = scrollT\n      }\n    }\n  }\n\n  componentDidUpdate() {\n    let { windowH, contentH, scrollT } = getSize();\n    const { topics } = this.props\n\n    // 第一次切换到没有加载数据的tab时，在willReceiveProp中已经将页面滚动了一定距离，在render中打印scrollT也不为0\n    // 但是一进入这个函数scrollT就变为0,而且也未触发onscroll事件（问题待解决），所以目前只能去重新判断这种情况\n    if (scrollT === 0 && this.state.scrollT > 0) {\n      // window.scrollTo(0, this.state.scrollT)\n    }\n\n    // 判断内容加载后，内容的高度是否填满屏幕，若网页太高，加载一次内容的高度不能填满整个网页，则继续请求数据\n    if (topics.length > 0 && windowH + 200 > contentH) {\n      // this.loadMore()\n    }\n  }\n  componentWillUnmount() {\n    const { selectedTab, dispatch } = this.props\n    dispatch(recordScrollT(selectedTab, document.getElementById('homeContainer').scrollTop))\n    // 必须解绑事件，否则当组件再次被加载时，该事件会监听两个组件\n    window.onscroll = null\n  }\n}\n\n\n// 用connect方法连接HomePage组件，实际上是在HomePage组件上加上了Connect(HomePage)这个父组件，HomePage里有关Connect的state变化的props就是通过mapStateToProps设置的\n// connect方法的第二个参数如果不传的话就会默认将store.dispatch默认作为了dispatch参数传进HomePage的props，所以HomePage的props里就有一个dispatch\nexport default withRouter(HomePage)"
  },
  {
    "path": "src/containers/Login.js",
    "content": "import React, { Component, PropTypes } from 'react'\nimport { connect } from 'react-redux'\nimport {fetchAccess,fetchMessage,logout,fetchArticle,fetchProfile} from '../actions'\nimport Header from '../components/common/Header/Header'\nimport Profile from '../components/common/Profile/Profile'\nimport getSize from '../utils/getSize'\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';\nimport CircularProgress from 'material-ui/CircularProgress';\nimport RaisedButton from 'material-ui/RaisedButton';\nimport TextField from 'material-ui/TextField';\nimport Toggle from 'material-ui/Toggle';\n\nclass Login extends Component {\n  constructor(){\n    super();\n    this.state = {\n      toggleOn:true\n    }\n  }\n  componentWillReceiveProps(newProps){\n    let {succeed,loginName,accessToken,dispatch,profile} = newProps;\n    if(succeed && !profile.isFetching && profile.loginname !== loginName){\n      if(this.state.toggleOn && !window.localStorage.getItem('masterInfo')){\n        accessToken = accessToken.trim()\n        loginName = loginName.trim()\n        let masterInfo = {accessToken,loginName}\n        masterInfo = JSON.stringify(masterInfo)\n        window.localStorage.setItem('masterInfo',masterInfo)\n      }\n      dispatch(fetchProfile(loginName))\n      dispatch(fetchMessage(accessToken))\n    }\n  }\n  onToggle = () => {\n    this.setState({\n      toggleOn:!this.state.toggleOn\n    })\n  }\n  render(){\n    let {dispatch,article,profile,failedMessage,succeed,loginName,loginId,accessToken,collectedTopics} = this.props;\n    if(loginName !== profile.loginname && window.sessionStorage.masterProfile){\n      profile = JSON.parse(window.sessionStorage.masterProfile)\n      collectedTopics = profile.collectedTopics\n    }\n    const masterInfo = window.localStorage.getItem('masterInfo') ? true : false\n    return (\n      <div>\n        <Header isFetching={profile.loginname?false:true} title='个人中心' showBack={false}/>\n        <div style={{textAlign:'center',paddingTop:100}}>\n          {!masterInfo && !succeed && \n            <MuiThemeProvider>\n              <div>\n                <div>\n                  <TextField hintText=\"请输入Access Token\" floatingLabelText=\"请输入Access Token\" ref='input'/>\n                </div>\n                <div style={{display:'inline-block',margin:'0 auto'}}>\n                  <Toggle label=\"记住登陆信息\" defaultToggled={true} onToggle={this.onToggle} style={{maxWidth: 200}}/>\n                </div>\n                <div>\n                    <RaisedButton  label=\"登陆\" primary={true} onClick={() => {\n                    const input = this.refs.input.input.value\n                    if(!input.trim()){\n                      return null;\n                    }\n                    dispatch(fetchAccess(input))\n                  }}/>\n                </div>\n              </div>\n            </MuiThemeProvider>\n          }\n          {!succeed && failedMessage && \n            <h2 style={{color:'red'}}>{failedMessage}</h2>\n          }\n          {succeed && !profile.loginname &&\n            <MuiThemeProvider>\n              <CircularProgress size={60} thickness={7} />\n            </MuiThemeProvider>\n          }\n          {succeed && profile.loginname  &&\n            <div>\n              <Profile {...({collectedTopics,profile,dispatch,fetchArticle,article})}/>\n            </div>\n          }\n        </div>\n      </div>\n  )\n  }\n}\n\n\nfunction mapStateToProps(state) {\n  const {article,profile,login,currentRouter} = state;\n  const {failedMessage,succeed,loginName,loginId,accessToken} = login;\n  const {collectedTopics} = profile\n  return {article,profile,succeed,loginName,loginId,accessToken,failedMessage,collectedTopics}\n}\n\n// 用connect方法连接HomePage组件，实际上是在HomePage组件上加上了Connect(HomePage)这个父组件，HomePage里有关Connect的state变化的props就是通过mapStateToProps设置的\n// connect方法的第二个参数如果不传的话就会默认将store.dispatch默认作为了dispatch参数传进HomePage的props，所以HomePage的props里就有一个dispatch\nexport default connect(mapStateToProps)(Login)"
  },
  {
    "path": "src/containers/Message.js",
    "content": "import React, { Component, PropTypes } from 'react'\nimport { connect } from 'react-redux'\nimport {fetchMessage,fetchArticle} from '../actions'\nimport Content from '../components/Message/Content/Content'\nimport LinkToLogin from '../components/common/LinkToLogin/LinkToLogin'\nimport Header from '../components/common/Header/Header'\nimport getSize from '../utils/getSize'\n\n\nclass Message extends Component {\n\n  componentDidMount(){\n    const {login,dispatch,message} = this.props\n    if(login.accessToken && message.hasReadMessage.length === 0){\n      dispatch(fetchMessage(login.accessToken))\n    }\n  }\n  render(){\n    const {dispatch,currentRouter,message,article,login} = this.props;\n    return (\n      <div>\n            <div>\n              <Header  title='详情'/>\n              <div style={{paddingTop:64}}>\n                {login.succeed && <Content {...message} {...({dispatch,fetchArticle,article,login})}/>}\n                {!login.succeed && <LinkToLogin dispatch={dispatch}/>}\n              </div>\n            </div>\n      </div>\n  )\n  }\n}\n\n\n\nfunction mapStateToProps(state) {\n  const {login,message,article} = state;\n  return {login,message,article}\n}\n\nexport default connect(mapStateToProps)(Message)"
  },
  {
    "path": "src/containers/Profile.js",
    "content": "import React, { Component, PropTypes } from 'react'\nimport { connect } from 'react-redux'\nimport {fetchArticle} from '../actions'\nimport Header from '../components/common/Header/Header'\nimport ProfileComponent from '../components/common/Profile/Profile'\nimport getSize from '../utils/getSize'\n\n\nclass Profile extends Component {\n  render(){\n    const {profile} = this.props\n    return (\n      <div>\n        <div>\n          <Header title='详情' showBack={true}/>\n          {profile.loginname &&\n            <div style={{paddingTop:100}}>\n              <ProfileComponent  {...({...this.props,fetchArticle})}/>\n            </div>\n            \n          }\n        </div>\n      </div>\n  )\n  }\n}\n\n// HomePage.propTypes = {\n//   selectedTab: PropTypes.string.isRequired,\n//   topics: PropTypes.array.isRequired,\n//   isFetching: PropTypes.bool.isRequired,\n//   page:PropTypes.number.isRequired,\n//   scrollT:PropTypes.number.isRequired,\n//   dispatch: PropTypes.func.isRequired\n// }\n\nfunction mapStateToProps(state) {\n  const {profile,article} = state;\n  const {collectedTopics} = profile;\n  return {profile,article,collectedTopics}\n}\n\n\nexport default connect(mapStateToProps)(Profile)"
  },
  {
    "path": "src/containers/PublishTopic.js",
    "content": "import React, { Component, PropTypes } from 'react'\nimport { connect } from 'react-redux'\nimport {Link} from 'react-router-dom'\nimport prefix from '../utils/routePrefix'\nimport {fetchArticle,fetchPublishTopic} from '../actions'\nimport Header from '../components/common/Header/Header'\nimport Dialog from '../components/common/Dialog'\nimport LinkToLogin from '../components/common/LinkToLogin/LinkToLogin'\nimport Form from '../components/PublishTopic/Form/Form'\n\n\nclass PublishTopic extends Component {\n  constructor(){\n    super();\n    this.state = {\n      isOpen:false,\n      isFetching: false,\n      titleErr:false,\n      contentErr:false\n    }\n  }\n\n  componentWillReceiveProps(newProps){\n    const {publishTopic,dispatch} = newProps;\n    if(!this.props.publishTopic.topicId || this.props.publishTopic.topicId !== publishTopic.topicId){\n      this.setState({isFetching:false})\n      dispatch(fetchArticle(publishTopic.topicId))\n    }\n  }\n\n  showDialog() {\n    this.setState({\n      isOpen:true,\n      isFetching:true\n    })\n  }\n  close = () => {\n    this.setState({\n      isOpen:false\n    })\n  }\n\n  ifTitleErr(boolean) {\n    this.setState({\n      titleErr: boolean\n    })\n  }\n  ifContentErr(boolean) {\n    this.setState({\n      contentErr: boolean\n    })\n  }\n  render(){\n    const {dispatch,publishTopic,currentRouter,login} = this.props;\n    const ifTitleErr = this.ifTitleErr.bind(this)\n    const ifContentErr = this.ifContentErr.bind(this)\n    const showDialog = this.showDialog.bind(this)\n    const state = this.state\n    return (\n      <div>\n        <Header title='发布新话题'/>\n        <div style={{paddingTop:100}}>\n          {login.succeed && <Form {...({ifTitleErr,ifContentErr,showDialog,fetchPublishTopic,dispatch,login,state})}/>}\n          {!login.succeed && <LinkToLogin dispatch={dispatch}/>}\n        </div>\n        <Dialog isOpen={this.state.isOpen} link={`${prefix}/topic/${publishTopic.topicId}`} close={this.close}>\n          {state.isFetching && <div>加载中</div>}\n          {!state.isFetching && <div>发送成功，去查看</div>}\n        </Dialog>\n      </div>\n    )\n  }\n}\n\n// HomePage.propTypes = {\n//   selectedTab: PropTypes.string.isRequired,\n//   topics: PropTypes.array.isRequired,\n//   isFetching: PropTypes.bool.isRequired,\n//   page:PropTypes.number.isRequired,\n//   scrollT:PropTypes.number.isRequired,\n//   dispatch: PropTypes.func.isRequired\n// }\n\nfunction mapStateToProps(state) {\n  const {publishTopic,login} = state;\n  // const {selectedTab,tabData} = state.homePage;\n  // // 当组件第一次render时,还未进行任何action,初始化的state里没有tabData[selectedTab]，所以这里需要初始化\n  // const {isFetching,page,scrollT,topics} = tabData[selectedTab] || {isFetching:false,page:0,scrollT:0,topics:[]}\n  return {publishTopic,login}\n}\n\n// 用connect方法连接HomePage组件，实际上是在HomePage组件上加上了Connect(HomePage)这个父组件，HomePage里有关Connect的state变化的props就是通过mapStateToProps设置的\n// connect方法的第二个参数如果不传的话就会默认将store.dispatch默认作为了dispatch参数传进HomePage的props，所以HomePage的props里就有一个dispatch\nexport default connect(mapStateToProps)(PublishTopic)"
  },
  {
    "path": "src/index.js",
    "content": "import 'babel-polyfill';\nimport React from 'react';\nimport ReactDOM from 'react-dom'\nimport store from './configureStore'\nimport { Provider } from 'react-redux'\n// import { Router,hashHistory } from 'react-router'\nimport Routes from './Routes'\nimport './styles/index.css'\nimport { AppContainer } from 'react-hot-loader'\n\nimport injectTapEventPlugin from 'react-tap-event-plugin'\ninjectTapEventPlugin()\n\n\nconst render = (Component) => {\n    ReactDOM.render(\n    \t<AppContainer>\n        \t<Provider store={store}>\n        \t    <Component />\n        \t</Provider>\n        </AppContainer>,\n        document.getElementById('root')\n    )\n}\n\nrender(Routes)\n\nif(module.hot) {\n    module.hot.accept('./Routes', () => { render(Routes) });\n}\n\n    // ReactDOM.render(\n    // \t<AppContainer>\n    //     \t<Provider store={store}>\n    //     \t    <div>aaaaaaaa</div>\n    //     \t</Provider>\n    //     </AppContainer>,\n    //     document.getElementById('root')\n    // )\n"
  },
  {
    "path": "src/reducers/article.js",
    "content": "import {\n\tREQUEST_ARTICLE, RECEIVE_ARTICLE, CHANGE_CURRENT_TOPICID, SWITCH_SUPPORT, FETCH_COMMENT, RECORD_ARTICLE_SCROLLT\n} from '../actions'\n\nconst initState = sessionStorage.getItem('store') ? JSON.parse(sessionStorage.getItem('store')).article : {\n\tcurrentTopicId: ''\n}\n\nconst article = (state = initState, action) => {\n\tlet stateItem = state[action.topicId] || {}\n\tswitch (action.type) {\n\t\tcase CHANGE_CURRENT_TOPICID:\n\t\t\treturn { ...state, currentTopicId: action.topicId }\n\t\tcase SWITCH_SUPPORT:\n\t\t\treturn { ...state, switchSupportInfo: { replyId: action.replyId, index: action.index, success: action.success, action: action.action } }\n\t\tcase FETCH_COMMENT:\n\t\t\treturn { ...state, isCommented: action.success }\n\t\tcase RECORD_ARTICLE_SCROLLT:\n\t\t\tstateItem = { ...stateItem, scrollT: action.scrollT }\n\t\t\treturn { ...state, [action.topicId]: stateItem, currentTopicId: action.topicId }\n\t\tcase REQUEST_ARTICLE:\n\t\t\tstateItem = { ...stateItem, isFetching: true }\n\t\t\treturn { ...state, [action.topicId]: stateItem, currentTopicId: action.topicId, isCommented: false }\n\t\tcase RECEIVE_ARTICLE:\n\t\t\tstateItem = { ...stateItem, isFetching: false, article: action.article }\n\t\t\treturn { ...state, [action.topicId]: stateItem }\n\t\tdefault:\n\t\t\treturn state\n\t}\n}\n\nexport default article"
  },
  {
    "path": "src/reducers/collectedTopics.js",
    "content": "import {\n\tGET_COLLECTED_TOPICS\n} from '../actions'\n\nconst initState = sessionStorage.getItem('store') ? JSON.parse(sessionStorage.getItem('store')).collectedTopics : {\n\tsuccess: false\n}\n\n\nconst collectedTopics = (state = initState, action) => {\n\tswitch (action.type) {\n\t\tcase GET_COLLECTED_TOPICS:\n\t\t\treturn { ...state, success: action.success, data: action.data, userName: action.userName }\n\t\tdefault:\n\t\t\treturn state\n\t}\n}\n\nexport default collectedTopics"
  },
  {
    "path": "src/reducers/fetchError.js",
    "content": "import {\n  FETCH_START, FETCH_END, FETCH_ERROR, CLEAR_ERROR\n} from '../actions/fetchError'\n\nconst initState = sessionStorage.getItem('store') ? JSON.parse(sessionStorage.getItem('store')).fetchError : {\n  error: null, fetched: null\n}\n\nconst fetchError = (state = initState, action) => {\n  switch (action.type) {\n    case FETCH_START:\n      return { ...state, fetched: 'start' }\n    case FETCH_END:\n      return { ...state, fetched: 'end' }\n    case FETCH_ERROR:\n      return { ...state, error: action.data, fetched: 'failed' }\n    case CLEAR_ERROR:\n      return { ...state, error: null }\n    default:\n      return state\n  }\n\n}\n\nexport default fetchError"
  },
  {
    "path": "src/reducers/hashUrl.js",
    "content": "import {\n  SET_HASH_URL, SET_TRANSITION\n} from '../actions/hashUrl'\n\nconst initState = sessionStorage.getItem('store') ? JSON.parse(sessionStorage.getItem('store')).hashUrl : {\n  oldUrl: '/', currentUrl: '/', transition: 'none'\n}\n\nconst hashUrl = (state = initState, action) => {\n  switch (action.type) {\n    case SET_HASH_URL:\n    case SET_TRANSITION:\n      return { ...state, ...action.data }\n    default:\n      return state\n  }\n\n}\n\nexport default hashUrl;"
  },
  {
    "path": "src/reducers/homePage.js",
    "content": "import {\n  SELECT_TAB, RECORD_SCROLLT,\n  REQUEST_TOPICS, RECEIVE_TOPICS\n} from '../actions'\n\n\nconst selectedTab = (state, action) => {\n  switch (action.type) {\n    case SELECT_TAB:\n      return action.tab\n    default:\n      return state\n  }\n}\n\n// 当组件第一次发出REQUEST_TOPICS后，需要对其返回的state进行初始化，否则没有topics等属性会报错\nfunction tabDataItem(state = { isFetching: false, page: 0, scrollT: 0, topics: [] }, action) {\n  switch (action.type) {\n    case REQUEST_TOPICS:\n      return {\n        ...state,\n        isFetching: true\n      }\n    case RECEIVE_TOPICS:\n      if (state.page < action.page) {\n        let topics = state.topics\n        action.topics = topics.concat(action.topics)\n      }\n      return {\n        ...state,\n        isFetching: false,\n        page: action.page,\n        topics: action.topics,\n        limit: action.limit\n      }\n    case RECORD_SCROLLT:\n      return {\n        ...state,\n        scrollT: action.scrollT\n      }\n    default:\n      return state\n  }\n}\n\nconst tabData = (state = {}, action) => {\n  switch (action.type) {\n    case RECEIVE_TOPICS:\n    case REQUEST_TOPICS:\n    case RECORD_SCROLLT:\n      return {\n        ...state,\n        [action.tab]: tabDataItem(state[action.tab], action)\n      }\n    default:\n      return state\n  }\n}\n\nconst initState = sessionStorage.getItem('store') ? JSON.parse(sessionStorage.getItem('store')).homePage : {\n  selectedTab: 'all', tabData: {}\n}\n\nconst homePage = (state = initState, action) => {\n  if (state) {\n    const sTab = selectedTab(state.selectedTab, action);\n    const tData = tabData(state.tabData, action);\n    // 返回的一定要是一个新的对象，如果只是改变原来的state,返回的还是原来的state对象,就无法被store.subscribe监听到，从而不会对组件状态进行更新\n    return { ...state, selectedTab: sTab, tabData: tData }\n  }\n  return state\n\n}\n\nexport default homePage;\n\n"
  },
  {
    "path": "src/reducers/index.js",
    "content": "import { combineReducers } from 'redux'\nimport homePage from './homePage'\nimport article from './article'\nimport login from './login'\nimport profile from './profile'\nimport message from './message'\nimport publishTopic from './publishTopic'\nimport hashUrl from './hashUrl'\nimport fetchError from './fetchError'\n\nconst rootReducer = combineReducers({\n    homePage,\n    article,\n    login,\n    profile,\n    publishTopic,\n    message,\n    hashUrl,\n    fetchError\n})\n\nexport default rootReducer"
  },
  {
    "path": "src/reducers/login.js",
    "content": "import {\n\tLOGIN_SUCCESS, LOGIN_FAILED, LOGOUT\n} from '../actions'\n\n\nconst initState = sessionStorage.getItem('store') ? JSON.parse(sessionStorage.getItem('store')).login : {\n\tsucceed: false\n}\n\nconst login = (state = initState, action) => {\n\tswitch (action.type) {\n\t\tcase LOGIN_SUCCESS:\n\t\t\treturn { ...state, succeed: true, loginName: action.loginName, loginId: action.loginId, accessToken: action.accessToken }\n\t\tcase LOGIN_FAILED:\n\t\t\treturn { ...state, succeed: false, failedMessage: action.failedMessage }\n\t\tcase LOGOUT:\n\t\t\treturn { succeed: false }\n\t\tdefault:\n\t\t\treturn state\n\t}\n}\n\nexport default login"
  },
  {
    "path": "src/reducers/message.js",
    "content": "import {\n\tFETCH_MESSAGE, MARK_ALL_MESSAGES\n} from '../actions'\n\nconst initState = sessionStorage.getItem('store') ? JSON.parse(sessionStorage.getItem('store')).message : {\n\tisMarked: false, hasReadMessage: [], hasNotReadMessage: []\n}\n\nconst message = (state = initState, action) => {\n\tswitch (action.type) {\n\t\tcase FETCH_MESSAGE:\n\t\t\treturn { ...state, hasReadMessage: action.hasReadMessage, hasNotReadMessage: action.hasNotReadMessage }\n\t\tcase MARK_ALL_MESSAGES:\n\t\t\treturn { ...state, isMarked: action.isMarked }\n\t\tdefault:\n\t\t\treturn state\n\t}\n}\n\nexport default message"
  },
  {
    "path": "src/reducers/profile.js",
    "content": "import {\n\tREQUEST_PROFILE, RECEIVE_PROFILE, GET_COLLECTED_TOPICS\n} from '../actions'\n\nconst initState = sessionStorage.getItem('store') ? JSON.parse(sessionStorage.getItem('store')).profile : {\n\tisFetching: false, collectedTopics: []\n}\n\nconst profile = (state = initState, action) => {\n\tswitch (action.type) {\n\t\tcase REQUEST_PROFILE:\n\t\t\treturn { ...state, isFetching: true }\n\t\tcase RECEIVE_PROFILE:\n\t\t\treturn { ...state, ...action.profile, isFetching: false }\n\t\tcase GET_COLLECTED_TOPICS:\n\t\t\treturn { ...state, collectedTopics: action.data }\n\t\tdefault:\n\t\t\treturn state\n\t}\n}\n\nexport default profile"
  },
  {
    "path": "src/reducers/publishTopic.js",
    "content": "import {\n\tPUBLISH_TOPIC\n} from '../actions'\n\nconst initState = sessionStorage.getItem('store') ? JSON.parse(sessionStorage.getItem('store')).publishTopic : {\n\tsuccess: false\n}\n\n\nconst publishTopic = (state = initState, action) => {\n\tswitch (action.type) {\n\t\tcase PUBLISH_TOPIC:\n\t\t\treturn { ...state, success: action.success, topicId: action.topicId }\n\t\tdefault:\n\t\t\treturn state\n\t}\n}\n\nexport default publishTopic\n\n\n\n"
  },
  {
    "path": "src/routes.js",
    "content": "import React, { Component } from 'react'\nimport { Route, Router, Redirect, Switch, withRouter } from 'react-router-dom'\nimport { connect } from 'react-redux'\nimport createHistory from 'history/createHashHistory'\nimport lazyLoadComponent from 'lazy-load-component'\nimport App from './containers/App'\nimport HomePage from './containers/HomePage'\nimport Article from './containers/Article'\nimport Message from './containers/Message'\nimport Login from './containers/Login'\nimport Profile from './containers/Profile'\nimport PublishTopic from './containers/PublishTopic'\nimport prefix from './utils/routePrefix'\nimport getSize from './utils/getSize'\nimport { setHashUrl, setTransition } from './actions/hashUrl'\n// import { clearUserInfo } from './actions/login'\nimport { clearError } from './actions/fetchError'\nimport { CSSTransition, TransitionGroup, Transition } from 'react-transition-group'\n\nconst history = createHistory()\n\n// const Article = lazyLoadComponent(() => import(/*webpackChunkName:\"Article\" */'./containers/Article'))\n// const Message = lazyLoadComponent(() => import(/*webpackChunkName:\"Message\" */'./containers/Message'))\n// const Login = lazyLoadComponent(() => import(/*webpackChunkName:\"Login\" */'./containers/Login'))\n// const Profile = lazyLoadComponent(() => import(/*webpackChunkName:\"Profile\" */'./containers/Profile'))\n// const PublishTopic = lazyLoadComponent(() => import(/*webpackChunkName:\"PublishTopic\" */'./containers/PublishTopic'))\n\n\n\n\n\n\n@connect(store => ({ store }))\nclass Routes extends Component {\n    constructor() {\n        super()\n        this.state = {}\n    }\n\n    hashChange = (ev) => {\n        if (this.props.store.hashUrl.oldURL != '/') {\n            this.setState({ overflow: 'hidden' })\n            setTimeout(() => this.setState({ overflow: 'visible' }), 500)\n        }\n\n        const dispatch = this.props.dispatch\n        let hashUrl = null;\n        if (ev.oldURL) {\n            hashUrl = { oldUrl: ev.oldURL.split('#')[1], currentUrl: ev.newURL.split('#')[1] }\n        } else {\n            this.oldUrl = this.currentUrl\n            this.currentUrl = window.location.href.split('#')[1]\n            hashUrl = { oldUrl: this.oldUrl, currentUrl: this.currentUrl }\n        }\n        dispatch(setHashUrl(hashUrl))\n\n        // if (this.props.hashUrl.transition != 'none') {\n        //     clearTimeout(this.transitionTimeOut)\n        //     this.transitionTimeOut = setTimeout(() => {\n        //         dispatch(setTransition({ transition: 'none' }))\n        //     }, 50)\n        // }\n    }\n\n    oldUrl = '/'\n    currentUrl = '/'\n\n    // changeTransition = (transition) => {\n    //     this.setState({ transition: transition })\n    // }\n\n    componentWillMount() {\n        let dispatch = this.props.dispatch\n        window.myDispatch = dispatch\n        window.width = getSize().windowW\n        window.height = getSize().windowH\n        let menu = window.location.href.split('#')[1].split('/')\n        if (menu[1]) {\n            this.currentUrl = window.location.href.split('#')[1]\n            dispatch(setHashUrl({ oldUrl: this.oldUrl, currentUrl: window.location.href.split('#')[1] }))\n        } else {\n            dispatch(setHashUrl({ oldUrl: this.oldUrl, currentUrl: this.currentUrl }))\n        }\n        window.addEventListener('hashchange', this.hashChange)\n        // 由于头部组件fix定位，在路由切换时，width:100%在手机上的判定会有问题，暂时采取全局变量储存页面加载时的宽度\n        // window.width = document.getElementById('root').offsetWidth\n        // console.log('****************',document.getElementById('root').offsetWidth)\n    }\n\n    saveState = () => {\n        let store = this.props.store\n        sessionStorage.setItem('store', JSON.stringify(store))\n    }\n\n\n\n    componentWillUpdate(nextProps) {\n        // if (this.props.store.hashUrl.oldUrl == nextProps.store.hashUrl.currentUrl) {\n        //     this.props.dispatch(setTransition({ transition: 'left' }))\n        // }\n    }\n\n    render() {\n        this.transition = this.props.store.hashUrl.transition\n        // let transition = this.props.store.hashUrl.transition\n        return (\n            <Router path={`${prefix}/`} history={history}>\n                <Route render={({ location }) => (\n                    <div style={{ position: 'relative', width: window.width, height: window.height, overflow: 'hidden' }}>\n                        <TransitionGroup>\n                            <Transition timeout={500} key={location.pathname}\n                                onEnter={() => this.enterCN = `${this.transition}-enter`}\n                                onEntering={() => this.enterCN = `${this.transition}-enter ${this.transition}-enter-active`}\n                                onEntered={() => this.enterCN = ``}\n                                onExit={() => this.exitCN = `${this.transition}-exit`}\n                                onExiting={() => this.exitCN = `${this.transition}-exit ${this.transition}-exit-active`}\n                                onExited={() => this.exitCN = ``}\n                            >\n                                {(status) => (\n                                    <div className={status.includes('enter') ? this.enterCN : this.exitCN} style={{ width: window.width, height: window.height, overflow: 'auto' }}>\n                                        <Switch location={location}>\n                                            <Route exact path='/' render={() => <Redirect to='/home' />} />\n                                            <Route path='/home' render={() => <HomePage />} />\n                                            <Route path='/topic/:id' render={() => <Article />} />\n                                            <Route path='/message' render={() => <Message />} />\n                                            <Route path='/login' render={() => <Login />} />\n                                            <Route path='/profile' render={() => <Profile />} />\n                                            <Route path='/publishTopic' render={() => <PublishTopic />} />\n                                        </Switch>\n                                    </div>\n                                )}\n                            </Transition>\n                        </TransitionGroup>\n                    </div>\n                )} />\n            </Router>\n        )\n    }\n\n\n    componentDidMount() {\n        window.width = document.getElementById('root').offsetWidth\n        window.addEventListener('beforeunload', this.saveState)\n    }\n\n    componentWillUnmount() {\n        window.removeEventListener('beforeunload', this.saveState)\n        window.removeEventListener('hashchange', () => { })\n    }\n\n\n}\n\nexport default Routes"
  },
  {
    "path": "src/styles/iconfont/demo.css",
    "content": "*{margin: 0;padding: 0;list-style: none;}\n/*\nKISSY CSS Reset\n理念：1. reset 的目的不是清除浏览器的默认样式，这仅是部分工作。清除和重置是紧密不可分的。\n2. reset 的目的不是让默认样式在所有浏览器下一致，而是减少默认样式有可能带来的问题。\n3. reset 期望提供一套普适通用的基础样式。但没有银弹，推荐根据具体需求，裁剪和修改后再使用。\n特色：1. 适应中文；2. 基于最新主流浏览器。\n维护：玉伯<lifesinger@gmail.com>, 正淳<ragecarrier@gmail.com>\n */\n\n/** 清除内外边距 **/\nbody, h1, h2, h3, h4, h5, h6, hr, p, blockquote, /* structural elements 结构元素 */\ndl, dt, dd, ul, ol, li, /* list elements 列表元素 */\npre, /* text formatting elements 文本格式元素 */\nform, fieldset, legend, button, input, textarea, /* form elements 表单元素 */\nth, td /* table elements 表格元素 */ {\n  margin: 0;\n  padding: 0;\n}\n\n/** 设置默认字体 **/\nbody,\nbutton, input, select, textarea /* for ie */ {\n  font: 12px/1.5 tahoma, arial, \\5b8b\\4f53, sans-serif;\n}\nh1, h2, h3, h4, h5, h6 { font-size: 100%; }\naddress, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */\ncode, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */\nsmall { font-size: 12px; } /* 小于 12px 的中文很难阅读，让 small 正常化 */\n\n/** 重置列表元素 **/\nul, ol { list-style: none; }\n\n/** 重置文本格式元素 **/\na { text-decoration: none; }\na:hover { text-decoration: underline; }\n\n\n/** 重置表单元素 **/\nlegend { color: #000; } /* for ie6 */\nfieldset, img { border: 0; } /* img 搭车：让链接里的 img 无边框 */\nbutton, input, select, textarea { font-size: 100%; } /* 使得表单元素在 ie 下能继承字体大小 */\n/* 注：optgroup 无法扶正 */\n\n/** 重置表格元素 **/\ntable { border-collapse: collapse; border-spacing: 0; }\n\n/* 清除浮动 */\n.ks-clear:after, .clear:after {\n  content: '\\20';\n  display: block;\n  height: 0;\n  clear: both;\n}\n.ks-clear, .clear {\n  *zoom: 1;\n}\n\n.main {\n  padding: 30px 100px;\nwidth: 960px;\nmargin: 0 auto;\n}\n.main h1{font-size:36px; color:#333; text-align:left;margin-bottom:30px; border-bottom: 1px solid #eee;}\n\n.helps{margin-top:40px;}\n.helps pre{\n  padding:20px;\n  margin:10px 0;\n  border:solid 1px #e7e1cd;\n  background-color: #fffdef;\n  overflow: auto;\n}\n\n.icon_lists{\n  width: 100% !important;\n\n}\n\n.icon_lists li{\n  float:left;\n  width: 100px;\n  height:180px;\n  text-align: center;\n  list-style: none !important;\n}\n.icon_lists .icon{\n  font-size: 42px;\n  line-height: 100px;\n  margin: 10px 0;\n  color:#333;\n  -webkit-transition: font-size 0.25s ease-out 0s;\n  -moz-transition: font-size 0.25s ease-out 0s;\n  transition: font-size 0.25s ease-out 0s;\n\n}\n.icon_lists .icon:hover{\n  font-size: 100px;\n}\n\n\n\n.markdown {\n  color: #666;\n  font-size: 14px;\n  line-height: 1.8;\n}\n\n.highlight {\n  line-height: 1.5;\n}\n\n.markdown img {\n  vertical-align: middle;\n  max-width: 100%;\n}\n\n.markdown h1 {\n  color: #404040;\n  font-weight: 500;\n  line-height: 40px;\n  margin-bottom: 24px;\n}\n\n.markdown h2,\n.markdown h3,\n.markdown h4,\n.markdown h5,\n.markdown h6 {\n  color: #404040;\n  margin: 1.6em 0 0.6em 0;\n  font-weight: 500;\n  clear: both;\n}\n\n.markdown h1 {\n  font-size: 28px;\n}\n\n.markdown h2 {\n  font-size: 22px;\n}\n\n.markdown h3 {\n  font-size: 16px;\n}\n\n.markdown h4 {\n  font-size: 14px;\n}\n\n.markdown h5 {\n  font-size: 12px;\n}\n\n.markdown h6 {\n  font-size: 12px;\n}\n\n.markdown hr {\n  height: 1px;\n  border: 0;\n  background: #e9e9e9;\n  margin: 16px 0;\n  clear: both;\n}\n\n.markdown p,\n.markdown pre {\n  margin: 1em 0;\n}\n\n.markdown > p,\n.markdown > blockquote,\n.markdown > .highlight,\n.markdown > ol,\n.markdown > ul {\n  width: 80%;\n}\n\n.markdown ul > li {\n  list-style: circle;\n}\n\n.markdown > ul li,\n.markdown blockquote ul > li {\n  margin-left: 20px;\n  padding-left: 4px;\n}\n\n.markdown > ul li p,\n.markdown > ol li p {\n  margin: 0.6em 0;\n}\n\n.markdown ol > li {\n  list-style: decimal;\n}\n\n.markdown > ol li,\n.markdown blockquote ol > li {\n  margin-left: 20px;\n  padding-left: 4px;\n}\n\n.markdown code {\n  margin: 0 3px;\n  padding: 0 5px;\n  background: #eee;\n  border-radius: 3px;\n}\n\n.markdown pre {\n  border-radius: 6px;\n  background: #f7f7f7;\n  padding: 20px;\n}\n\n.markdown pre code {\n  border: none;\n  background: #f7f7f7;\n  margin: 0;\n}\n\n.markdown strong,\n.markdown b {\n  font-weight: 600;\n}\n\n.markdown > table {\n  border-collapse: collapse;\n  border-spacing: 0px;\n  empty-cells: show;\n  border: 1px solid #e9e9e9;\n  width: 95%;\n  margin-bottom: 24px;\n}\n\n.markdown > table th {\n  white-space: nowrap;\n  color: #333;\n  font-weight: 600;\n\n}\n\n.markdown > table th,\n.markdown > table td {\n  border: 1px solid #e9e9e9;\n  padding: 8px 16px;\n  text-align: left;\n}\n\n.markdown > table th {\n  background: #F7F7F7;\n}\n\n.markdown blockquote {\n  font-size: 90%;\n  color: #999;\n  border-left: 4px solid #e9e9e9;\n  padding-left: 0.8em;\n  margin: 1em 0;\n  font-style: italic;\n}\n\n.markdown blockquote p {\n  margin: 0;\n}\n\n.markdown .anchor {\n  opacity: 0;\n  transition: opacity 0.3s ease;\n  margin-left: 8px;\n}\n\n.markdown .waiting {\n  color: #ccc;\n}\n\n.markdown h1:hover .anchor,\n.markdown h2:hover .anchor,\n.markdown h3:hover .anchor,\n.markdown h4:hover .anchor,\n.markdown h5:hover .anchor,\n.markdown h6:hover .anchor {\n  opacity: 1;\n  display: inline-block;\n}\n\n.markdown > br,\n.markdown > p > br {\n  clear: both;\n}\n\n\n.hljs {\n  display: block;\n  background: white;\n  padding: 0.5em;\n  color: #333333;\n  overflow-x: auto;\n}\n\n.hljs-comment,\n.hljs-meta {\n  color: #969896;\n}\n\n.hljs-string,\n.hljs-variable,\n.hljs-template-variable,\n.hljs-strong,\n.hljs-emphasis,\n.hljs-quote {\n  color: #df5000;\n}\n\n.hljs-keyword,\n.hljs-selector-tag,\n.hljs-type {\n  color: #a71d5d;\n}\n\n.hljs-literal,\n.hljs-symbol,\n.hljs-bullet,\n.hljs-attribute {\n  color: #0086b3;\n}\n\n.hljs-section,\n.hljs-name {\n  color: #63a35c;\n}\n\n.hljs-tag {\n  color: #333333;\n}\n\n.hljs-title,\n.hljs-attr,\n.hljs-selector-id,\n.hljs-selector-class,\n.hljs-selector-attr,\n.hljs-selector-pseudo {\n  color: #795da3;\n}\n\n.hljs-addition {\n  color: #55a532;\n  background-color: #eaffea;\n}\n\n.hljs-deletion {\n  color: #bd2c00;\n  background-color: #ffecec;\n}\n\n.hljs-link {\n  text-decoration: underline;\n}\n\npre{\n  background: #fff;\n}\n\n\n\n\n\n"
  },
  {
    "path": "src/styles/iconfont/demo_fontclass.html",
    "content": "\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\"/>\n    <title>IconFont</title>\n    <link rel=\"stylesheet\" href=\"demo.css\">\n    <link rel=\"stylesheet\" href=\"iconfont.css\">\n</head>\n<body>\n    <div class=\"main markdown\">\n        <h1>IconFont 图标</h1>\n        <ul class=\"icon_lists clear\">\n            \n                <li>\n                <i class=\"icon iconfont icon-xiai\"></i>\n                    <div class=\"name\">喜爱</div>\n                    <div class=\"fontclass\">.icon-xiai</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont icon-ding\"></i>\n                    <div class=\"name\">顶</div>\n                    <div class=\"fontclass\">.icon-ding</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont icon-user\"></i>\n                    <div class=\"name\">用户</div>\n                    <div class=\"fontclass\">.icon-user</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont icon-back\"></i>\n                    <div class=\"name\">返回</div>\n                    <div class=\"fontclass\">.icon-back</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont icon-informatiom\"></i>\n                    <div class=\"name\">信息</div>\n                    <div class=\"fontclass\">.icon-informatiom</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont icon-huifu\"></i>\n                    <div class=\"name\">回复</div>\n                    <div class=\"fontclass\">.icon-huifu</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont icon-chakanguo\"></i>\n                    <div class=\"name\">查看过</div>\n                    <div class=\"fontclass\">.icon-chakanguo</div>\n                </li>\n            \n        </ul>\n\n        <h2 id=\"font-class-\">font-class引用</h2>\n        <hr>\n\n        <p>font-class是unicode使用方式的一种变种，主要是解决unicode书写不直观，语意不明确的问题。</p>\n        <p>与unicode使用方式相比，具有如下特点：</p>\n        <ul>\n        <li>兼容性良好，支持ie8+，及所有现代浏览器。</li>\n        <li>相比于unicode语意明确，书写更直观。可以很容易分辨这个icon是什么。</li>\n        <li>因为使用class来定义图标，所以当要替换图标时，只需要修改class里面的unicode引用。</li>\n        <li>不过因为本质上还是使用的字体，所以多色图标还是不支持的。</li>\n        </ul>\n        <p>使用步骤如下：</p>\n        <h3 id=\"-fontclass-\">第一步：引入项目下面生成的fontclass代码：</h3>\n\n\n        <pre><code class=\"lang-js hljs javascript\"><span class=\"hljs-comment\">&lt;link rel=\"stylesheet\" type=\"text/css\" href=\"./iconfont.css\"&gt;</span></code></pre>\n        <h3 id=\"-\">第二步：挑选相应图标并获取类名，应用于页面：</h3>\n        <pre><code class=\"lang-css hljs\">&lt;<span class=\"hljs-selector-tag\">i</span> <span class=\"hljs-selector-tag\">class</span>=\"<span class=\"hljs-selector-tag\">iconfont</span> <span class=\"hljs-selector-tag\">icon-xxx</span>\"&gt;&lt;/<span class=\"hljs-selector-tag\">i</span>&gt;</code></pre>\n        <blockquote>\n        <p>\"iconfont\"是你项目下的font-family。可以通过编辑项目查看，默认是\"iconfont\"。</p>\n        </blockquote>\n    </div>\n</body>\n</html>\n"
  },
  {
    "path": "src/styles/iconfont/demo_symbol.html",
    "content": "\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\"/>\n    <title>IconFont</title>\n    <link rel=\"stylesheet\" href=\"demo.css\">\n    <script src=\"iconfont.js\"></script>\n\n    <style type=\"text/css\">\n        .icon {\n          /* 通过设置 font-size 来改变图标大小 */\n          width: 1em; height: 1em;\n          /* 图标和文字相邻时，垂直对齐 */\n          vertical-align: -0.15em;\n          /* 通过设置 color 来改变 SVG 的颜色/fill */\n          fill: currentColor;\n          /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示\n             normalize.css 中也包含这行 */\n          overflow: hidden;\n        }\n\n    </style>\n</head>\n<body>\n    <div class=\"main markdown\">\n        <h1>IconFont 图标</h1>\n        <ul class=\"icon_lists clear\">\n            \n                <li>\n                    <svg class=\"icon\" aria-hidden=\"true\">\n                        <use xlink:href=\"#icon-xiai\"></use>\n                    </svg>\n                    <div class=\"name\">喜爱</div>\n                    <div class=\"fontclass\">#icon-xiai</div>\n                </li>\n            \n                <li>\n                    <svg class=\"icon\" aria-hidden=\"true\">\n                        <use xlink:href=\"#icon-ding\"></use>\n                    </svg>\n                    <div class=\"name\">顶</div>\n                    <div class=\"fontclass\">#icon-ding</div>\n                </li>\n            \n                <li>\n                    <svg class=\"icon\" aria-hidden=\"true\">\n                        <use xlink:href=\"#icon-user\"></use>\n                    </svg>\n                    <div class=\"name\">用户</div>\n                    <div class=\"fontclass\">#icon-user</div>\n                </li>\n            \n                <li>\n                    <svg class=\"icon\" aria-hidden=\"true\">\n                        <use xlink:href=\"#icon-back\"></use>\n                    </svg>\n                    <div class=\"name\">返回</div>\n                    <div class=\"fontclass\">#icon-back</div>\n                </li>\n            \n                <li>\n                    <svg class=\"icon\" aria-hidden=\"true\">\n                        <use xlink:href=\"#icon-informatiom\"></use>\n                    </svg>\n                    <div class=\"name\">信息</div>\n                    <div class=\"fontclass\">#icon-informatiom</div>\n                </li>\n            \n                <li>\n                    <svg class=\"icon\" aria-hidden=\"true\">\n                        <use xlink:href=\"#icon-huifu\"></use>\n                    </svg>\n                    <div class=\"name\">回复</div>\n                    <div class=\"fontclass\">#icon-huifu</div>\n                </li>\n            \n                <li>\n                    <svg class=\"icon\" aria-hidden=\"true\">\n                        <use xlink:href=\"#icon-chakanguo\"></use>\n                    </svg>\n                    <div class=\"name\">查看过</div>\n                    <div class=\"fontclass\">#icon-chakanguo</div>\n                </li>\n            \n        </ul>\n\n\n        <h2 id=\"symbol-\">symbol引用</h2>\n        <hr>\n\n        <p>这是一种全新的使用方式，应该说这才是未来的主流，也是平台目前推荐的用法。相关介绍可以参考这篇<a href=\"\">文章</a>\n        这种用法其实是做了一个svg的集合，与另外两种相比具有如下特点：</p>\n        <ul>\n          <li>支持多色图标了，不再受单色限制。</li>\n          <li>通过一些技巧，支持像字体那样，通过<code>font-size</code>,<code>color</code>来调整样式。</li>\n          <li>兼容性较差，支持 ie9+,及现代浏览器。</li>\n          <li>浏览器渲染svg的性能一般，还不如png。</li>\n        </ul>\n        <p>使用步骤如下：</p>\n        <h3 id=\"-symbol-\">第一步：引入项目下面生成的symbol代码：</h3>\n        <pre><code class=\"lang-js hljs javascript\"><span class=\"hljs-comment\">&lt;script src=\"./iconfont.js\"&gt;&lt;/script&gt;</span></code></pre>\n        <h3 id=\"-css-\">第二步：加入通用css代码（引入一次就行）：</h3>\n        <pre><code class=\"lang-js hljs javascript\">&lt;style type=<span class=\"hljs-string\">\"text/css\"</span>&gt;\n.icon {\n   width: <span class=\"hljs-number\">1</span>em; height: <span class=\"hljs-number\">1</span>em;\n   vertical-align: <span class=\"hljs-number\">-0.15</span>em;\n   fill: currentColor;\n   overflow: hidden;\n}\n&lt;<span class=\"hljs-regexp\">/style&gt;</span></code></pre>\n        <h3 id=\"-\">第三步：挑选相应图标并获取类名，应用于页面：</h3>\n        <pre><code class=\"lang-js hljs javascript\">&lt;svg <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span></span>=<span class=\"hljs-string\">\"icon\"</span> aria-hidden=<span class=\"hljs-string\">\"true\"</span>&gt;<span class=\"xml\"><span class=\"hljs-tag\">\n  &lt;<span class=\"hljs-name\">use</span> <span class=\"hljs-attr\">xlink:href</span>=<span class=\"hljs-string\">\"#icon-xxx\"</span>&gt;</span><span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">use</span>&gt;</span>\n</span>&lt;<span class=\"hljs-regexp\">/svg&gt;\n        </span></code></pre>\n    </div>\n</body>\n</html>\n"
  },
  {
    "path": "src/styles/iconfont/demo_unicode.html",
    "content": "\n<!DOCTYPE html>\n<html>\n<head>\n    <meta charset=\"utf-8\"/>\n    <title>IconFont</title>\n    <link rel=\"stylesheet\" href=\"demo.css\">\n\n    <style type=\"text/css\">\n\n        @font-face {font-family: \"iconfont\";\n          src: url('iconfont.eot'); /* IE9*/\n          src: url('iconfont.eot#iefix') format('embedded-opentype'), /* IE6-IE8 */\n          url('iconfont.woff') format('woff'), /* chrome, firefox */\n          url('iconfont.ttf') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/\n          url('iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */\n        }\n\n        .iconfont {\n          font-family:\"iconfont\" !important;\n          font-size:16px;\n          font-style:normal;\n          -webkit-font-smoothing: antialiased;\n          -webkit-text-stroke-width: 0.2px;\n          -moz-osx-font-smoothing: grayscale;\n        }\n\n    </style>\n</head>\n<body>\n    <div class=\"main markdown\">\n        <h1>IconFont 图标</h1>\n        <ul class=\"icon_lists clear\">\n            \n                <li>\n                <i class=\"icon iconfont\">&#xe600;</i>\n                    <div class=\"name\">喜爱</div>\n                    <div class=\"code\">&amp;#xe600;</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont\">&#xe610;</i>\n                    <div class=\"name\">顶</div>\n                    <div class=\"code\">&amp;#xe610;</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont\">&#xe60f;</i>\n                    <div class=\"name\">用户</div>\n                    <div class=\"code\">&amp;#xe60f;</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont\">&#xe611;</i>\n                    <div class=\"name\">返回</div>\n                    <div class=\"code\">&amp;#xe611;</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont\">&#xe617;</i>\n                    <div class=\"name\">信息</div>\n                    <div class=\"code\">&amp;#xe617;</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont\">&#xe63f;</i>\n                    <div class=\"name\">回复</div>\n                    <div class=\"code\">&amp;#xe63f;</div>\n                </li>\n            \n                <li>\n                <i class=\"icon iconfont\">&#xe6ae;</i>\n                    <div class=\"name\">查看过</div>\n                    <div class=\"code\">&amp;#xe6ae;</div>\n                </li>\n            \n        </ul>\n        <h2 id=\"unicode-\">unicode引用</h2>\n        <hr>\n\n        <p>unicode是字体在网页端最原始的应用方式，特点是：</p>\n        <ul>\n        <li>兼容性最好，支持ie6+，及所有现代浏览器。</li>\n        <li>支持按字体的方式去动态调整图标大小，颜色等等。</li>\n        <li>但是因为是字体，所以不支持多色。只能使用平台里单色的图标，就算项目里有多色图标也会自动去色。</li>\n        </ul>\n        <blockquote>\n        <p>注意：新版iconfont支持多色图标，这些多色图标在unicode模式下将不能使用，如果有需求建议使用symbol的引用方式</p>\n        </blockquote>\n        <p>unicode使用步骤如下：</p>\n        <h3 id=\"-font-face\">第一步：拷贝项目下面生成的font-face</h3>\n        <pre><code class=\"lang-js hljs javascript\">@font-face {\n  font-family: <span class=\"hljs-string\">'iconfont'</span>;\n  src: url(<span class=\"hljs-string\">'iconfont.eot'</span>);\n  src: url(<span class=\"hljs-string\">'iconfont.eot?#iefix'</span>) format(<span class=\"hljs-string\">'embedded-opentype'</span>),\n  url(<span class=\"hljs-string\">'iconfont.woff'</span>) format(<span class=\"hljs-string\">'woff'</span>),\n  url(<span class=\"hljs-string\">'iconfont.ttf'</span>) format(<span class=\"hljs-string\">'truetype'</span>),\n  url(<span class=\"hljs-string\">'iconfont.svg#iconfont'</span>) format(<span class=\"hljs-string\">'svg'</span>);\n}\n</code></pre>\n        <h3 id=\"-iconfont-\">第二步：定义使用iconfont的样式</h3>\n        <pre><code class=\"lang-js hljs javascript\">.iconfont{\n  font-family:<span class=\"hljs-string\">\"iconfont\"</span> !important;\n  font-size:<span class=\"hljs-number\">16</span>px;font-style:normal;\n  -webkit-font-smoothing: antialiased;\n  -webkit-text-stroke-width: <span class=\"hljs-number\">0.2</span>px;\n  -moz-osx-font-smoothing: grayscale;\n}\n</code></pre>\n        <h3 id=\"-\">第三步：挑选相应图标并获取字体编码，应用于页面</h3>\n        <pre><code class=\"lang-js hljs javascript\">&lt;i <span class=\"hljs-class\"><span class=\"hljs-keyword\">class</span></span>=<span class=\"hljs-string\">\"iconfont\"</span>&gt;&amp;#x33;<span class=\"xml\"><span class=\"hljs-tag\">&lt;/<span class=\"hljs-name\">i</span>&gt;</span></span></code></pre>\n\n        <blockquote>\n        <p>\"iconfont\"是你项目下的font-family。可以通过编辑项目查看，默认是\"iconfont\"。</p>\n        </blockquote>\n    </div>\n\n\n</body>\n</html>\n"
  },
  {
    "path": "src/styles/iconfont/iconfont.css",
    "content": "\n@font-face {font-family: \"iconfont\";\n  src: url('iconfont.eot?t=1481708482838'); /* IE9*/\n  src: url('iconfont.eot?t=1481708482838#iefix') format('embedded-opentype'), /* IE6-IE8 */\n  url('iconfont.woff?t=1481708482838') format('woff'), /* chrome, firefox */\n  url('iconfont.ttf?t=1481708482838') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/\n  url('iconfont.svg?t=1481708482838#iconfont') format('svg'); /* iOS 4.1- */\n}\n\n.iconfont {\n  font-family:\"iconfont\" !important;\n  font-size:16px;\n  font-style:normal;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.icon-xiai:before { content: \"\\e600\"; }\n\n.icon-ding:before { content: \"\\e610\"; }\n\n.icon-user:before { content: \"\\e60f\"; }\n\n.icon-back:before { content: \"\\e611\"; }\n\n.icon-informatiom:before { content: \"\\e617\"; }\n\n.icon-huifu:before { content: \"\\e63f\"; }\n\n.icon-chakanguo:before { content: \"\\e6ae\"; }\n\n"
  },
  {
    "path": "src/styles/iconfont/iconfont.js",
    "content": ";(function(window) {\n\n  var svgSprite = '<svg>' +\n    '' +\n    '<symbol id=\"icon-xiai\" viewBox=\"0 0 1024 1024\">' +\n    '' +\n    '<path d=\"M533.504 268.288q33.792-41.984 71.68-75.776 32.768-27.648 74.24-50.176t86.528-19.456q63.488 5.12 105.984 30.208t67.584 63.488 34.304 87.04 6.144 99.84-17.92 97.792-36.864 87.04-48.64 74.752-53.248 61.952q-40.96 41.984-85.504 78.336t-84.992 62.464-73.728 41.472-51.712 15.36q-20.48 1.024-52.224-14.336t-69.632-41.472-79.872-61.952-82.944-75.776q-26.624-25.6-57.344-59.392t-57.856-74.24-46.592-87.552-21.504-100.352 11.264-99.84 39.936-83.456 65.536-61.952 88.064-35.328q24.576-5.12 49.152-1.536t48.128 12.288 45.056 22.016 40.96 27.648q45.056 33.792 86.016 80.896z\"  ></path>' +\n    '' +\n    '</symbol>' +\n    '' +\n    '<symbol id=\"icon-ding\" viewBox=\"0 0 1024 1024\">' +\n    '' +\n    '<path d=\"M898.799192 421.647515 652.021657 421.647515C747.635071 68.468364 586.127515 50.424242 586.127515 50.424242c-68.477414 0-54.265535 54.137535-59.434667 63.158303 0 172.721131-183.462788 308.06497-183.462788 308.06497l0 489.810747c0 48.333576 65.887677 65.737697 91.729455 65.737697L805.774222 977.19596c34.885818 0 63.308283-91.52 63.308283-91.52C960.822303 573.74901 960.822303 480.943838 960.822303 480.943838 960.822303 416.496485 898.799192 421.647515 898.799192 421.647515L898.799192 421.647515M898.799192 421.647515 898.799192 421.647515zM241.961374 421.807838 94.18602 421.807838c-30.526061 0-31.008323 29.972687-31.008323 29.972687l30.526061 493.994667c0 31.420768 31.489293 31.420768 31.489293 31.420768l127.910788 0c26.647273 0 26.406788-20.783838 26.406788-20.783838L279.510626 459.267879C279.511919 421.326869 241.961374 421.807838 241.961374 421.807838L241.961374 421.807838M241.961374 421.807838 241.961374 421.807838z\"  ></path>' +\n    '' +\n    '</symbol>' +\n    '' +\n    '<symbol id=\"icon-user\" viewBox=\"0 0 1024 1024\">' +\n    '' +\n    '<path d=\"M512 239.36m-239.36 0a239.36 239.36 0 1 0 478.72 0 239.36 239.36 0 1 0-478.72 0Z\" fill=\"\" ></path>' +\n    '' +\n    '<path d=\"M512 478.72a512 512 0 0 0-512 512V1024h1024v-33.28a512 512 0 0 0-512-512z\" fill=\"\" ></path>' +\n    '' +\n    '</symbol>' +\n    '' +\n    '<symbol id=\"icon-back\" viewBox=\"0 0 1024 1024\">' +\n    '' +\n    '<path d=\"M691.84 140.16a40.96 40.96 0 0 0-58.24 0L295.04 483.2a40.96 40.96 0 0 0 0 58.24l338.56 342.4a40.96 40.96 0 0 0 58.24-58.24L384 512l307.84-314.24a40.96 40.96 0 0 0 0-57.6z\" fill=\"\" ></path>' +\n    '' +\n    '</symbol>' +\n    '' +\n    '<symbol id=\"icon-informatiom\" viewBox=\"0 0 1024 1024\">' +\n    '' +\n    '<path d=\"M512 640L0 320v576c0-29.44 28.16 0 64 0h896a64 64 0 0 0 64-64V320z\" fill=\"\" ></path>' +\n    '' +\n    '<path d=\"M960 192H64a64 64 0 0 0-64 64l512 315.52 512-320A64 64 0 0 0 960 192z\" fill=\"\" ></path>' +\n    '' +\n    '</symbol>' +\n    '' +\n    '<symbol id=\"icon-huifu\" viewBox=\"0 0 1024 1024\">' +\n    '' +\n    '<path d=\"M979.861 238.897l0 394.758c0 76.758-62.138 138.896-138.896 138.896l-208.346 0-10.964 179.102-204.69-175.449-230.275 0c-76.758 0-138.896-62.138-138.896-138.896l0-398.412c0-76.758 62.138-138.896 138.896-138.896l654.274 0c76.758 0 138.896 62.138 138.896 138.896z\"  ></path>' +\n    '' +\n    '</symbol>' +\n    '' +\n    '<symbol id=\"icon-chakanguo\" viewBox=\"0 0 1024 1024\">' +\n    '' +\n    '<path d=\"M509.618255 415.794891c-42.695405 0-77.253478 38.371936-77.253478 85.73771 0 47.352471 34.558074 86.22071 77.253478 86.22071 42.666752 0 76.716242-38.867216 76.716242-86.22071C586.333474 454.181153 552.285007 415.794891 509.618255 415.794891zM511.518534 237.895155c-211.54809 0-383.266034 159.858848-383.266034 274.586822 0 114.726951 171.717943 273.623891 383.266034 273.623891 212.484416 0 384.227941-162.52149 384.227941-273.623891C895.746476 401.393903 724.00295 237.895155 511.518534 237.895155zM510.071579 689.900759c-89.698928 0-162.133657-81.708953-162.133657-181.90291 0-101.156888 72.434729-182.397167 162.133657-182.397167 90.182952 0 163.099658 81.241302 163.099658 182.397167C673.171238 608.204086 600.254531 689.900759 510.071579 689.900759z\"  ></path>' +\n    '' +\n    '</symbol>' +\n    '' +\n    '</svg>'\n  var script = function() {\n    var scripts = document.getElementsByTagName('script')\n    return scripts[scripts.length - 1]\n  }()\n  var shouldInjectCss = script.getAttribute(\"data-injectcss\")\n\n  /**\n   * document ready\n   */\n  var ready = function(fn) {\n    if (document.addEventListener) {\n      if (~[\"complete\", \"loaded\", \"interactive\"].indexOf(document.readyState)) {\n        setTimeout(fn, 0)\n      } else {\n        var loadFn = function() {\n          document.removeEventListener(\"DOMContentLoaded\", loadFn, false)\n          fn()\n        }\n        document.addEventListener(\"DOMContentLoaded\", loadFn, false)\n      }\n    } else if (document.attachEvent) {\n      IEContentLoaded(window, fn)\n    }\n\n    function IEContentLoaded(w, fn) {\n      var d = w.document,\n        done = false,\n        // only fire once\n        init = function() {\n          if (!done) {\n            done = true\n            fn()\n          }\n        }\n        // polling for no errors\n      var polling = function() {\n        try {\n          // throws errors until after ondocumentready\n          d.documentElement.doScroll('left')\n        } catch (e) {\n          setTimeout(polling, 50)\n          return\n        }\n        // no errors, fire\n\n        init()\n      };\n\n      polling()\n        // trying to always fire before onload\n      d.onreadystatechange = function() {\n        if (d.readyState == 'complete') {\n          d.onreadystatechange = null\n          init()\n        }\n      }\n    }\n  }\n\n  /**\n   * Insert el before target\n   *\n   * @param {Element} el\n   * @param {Element} target\n   */\n\n  var before = function(el, target) {\n    target.parentNode.insertBefore(el, target)\n  }\n\n  /**\n   * Prepend el to target\n   *\n   * @param {Element} el\n   * @param {Element} target\n   */\n\n  var prepend = function(el, target) {\n    if (target.firstChild) {\n      before(el, target.firstChild)\n    } else {\n      target.appendChild(el)\n    }\n  }\n\n  function appendSvg() {\n    var div, svg\n\n    div = document.createElement('div')\n    div.innerHTML = svgSprite\n    svgSprite = null\n    svg = div.getElementsByTagName('svg')[0]\n    if (svg) {\n      svg.setAttribute('aria-hidden', 'true')\n      svg.style.position = 'absolute'\n      svg.style.width = 0\n      svg.style.height = 0\n      svg.style.overflow = 'hidden'\n      prepend(svg, document.body)\n    }\n  }\n\n  if (shouldInjectCss && !window.__iconfont__svg__cssinject__) {\n    window.__iconfont__svg__cssinject__ = true\n    try {\n      document.write(\"<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>\");\n    } catch (e) {\n      console && console.log(e)\n    }\n  }\n\n  ready(appendSvg)\n\n\n})(window)"
  },
  {
    "path": "src/styles/index.css",
    "content": "/*Normalize.css 只是一个很小的CSS文件，但它在默认的HTML元素样式上提供了跨浏览器的高度一致性*/\n@import '~normalize.css/normalize.css';\n@import \"~github-markdown-css/github-markdown.css\";\n\n@font-face {font-family: \"iconfont\";\n  src: url('iconfont/iconfont.eot'); /* IE9*/\n  src: url('iconfont/iconfont.eot#iefix') format('embedded-opentype'), /* IE6-IE8 */\n  url('iconfont/iconfont.woff') format('woff'), /* chrome, firefox */\n  url('iconfont/iconfont.ttf') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/\n  url('iconfont/iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */\n}\n\n.iconfont {\n  font-family:\"iconfont\" !important;\n  font-size:20px;\n  font-style:normal;\n  -webkit-font-smoothing: antialiased;\n  -webkit-text-stroke-width: 0.2px;\n  -moz-osx-font-smoothing: grayscale;\n}\n\nbody,h2,p,ul,li{\n    margin:0;\n    padding:0;\n}\nul{\n\tpadding-left:0;\n}\nli{\n\tlist-style-type: none;\n}\na:hover,a:link,a:visited,a:active{\n\ttext-decoration: none;\n}\n\n/* .fade-in{\n\tanimation: fadeIn 0.5s ease-out;\n}\n\n@keyframes fadeIn{\n    0%{opacity:0;}\n    100%{opacity:1;}\n} */\n\n\n/* .move-appear {\n  transform: scale(50);\n  background: grey;\n}\n\n.move-appear.move-appear-active {\n  transform: scale(1);\n  background: grey;\n  transition: 60s;\n} */\n\n.move-enter {\n  position: absolute;\n  z-index: 100;\n  width: 100%;\n  height: 100%;\n  background: white;\n  top: 0;\n  left: 100%;\n  \n}\n\n.move-enter.move-enter-active {\n  left: 0%;\n  transition: 0.5s;\n}\n\n.move-exit {\n  position: absolute;\n  z-index: 50;\n  width: 100%;\n  height: 100%;\n  opacity: 50;\n  background: white;\n  top: 0;\n  left: 0;\n}\n\n.move-exit.move-exit-active {\n  opacity: 50;\n  left: -100%;\n  transition: 0.5s;\n}\n\n.left-enter {\n  position: absolute;\n  z-index: 100;\n  width: 100%;\n  height: 100%;\n  background: white;\n  top: 0;\n  left: 0;\n  \n}\n\n.left-enter.left-enter-active {\n  left: 0;\n}\n\n.left-exit {\n  position: absolute;\n  z-index: 100;\n  width: 100%;\n  height: 100%;\n  background: white;\n  top: 0;\n  left: 0;\n}\n\n.left-exit.left-exit-active {\n  left: 100%;\n  transition: 0.5s;\n}\n\n\n\n.up-enter {\n  position: absolute;\n  z-index: 10000;\n  width: 100%;\n  height: 100%;\n  background: white;\n  top: 100%;\n  left: 0;\n  \n}\n\n.up-enter.up-enter-active {\n  top: 0;\n  transition: 0.5s;\n}\n\n.up-exit {\n  position: absolute;\n  z-index: 100;\n  width: 100%;\n  height: 100%;\n  background: white;\n  top: 0;\n  left: 0;\n  \n}\n\n.up-exit.up-exit-active {\n  top: 0;\n}"
  },
  {
    "path": "src/utils/getOS.js",
    "content": "export const os = 'win32';export const host = '192.168.6.183'"
  },
  {
    "path": "src/utils/getPosition.js",
    "content": "const getPosition = (direction, DOMNode, className) => {\n    switch(document.getElementsByClassName(className).length){\n        case 0:\n        alert('注意：传入的className对应的元素不存在！')\n        break\n        case 1:\n        break\n        default:\n        alert('注意：传入的className对应多个元素！请给该元素唯一className')\n\n    }\n    let offset = DOMNode[`offset${direction}`]\n    let parent = DOMNode.offsetParent\n    if (parent) {\n        if (className) {\n            if (parent.className != className) {\n                offset += getPosition(direction, parent, className);\n            }\n        } else {\n            if (parent.nodeName != 'BODY') {\n                offset += getPosition(direction, parent);\n            }\n        }\n    }\n\n    return offset\n}\n\nexport const getTop = (DOMNode, className) => {\n    return getPosition('Top', DOMNode, className)\n}\n\nexport const getLeft = (DOMNode, className) => {\n    return getPosition('Left', DOMNode, className)\n}\n\nexport const getBottom = (DOMNode, className) => {\n    let containerHeight = 0;\n    let selfHeight = DOMNode.offsetHeight\n    if (className) {\n        containerHeight = document.getElementsByClassName(className)[0].offsetHeight\n    } else {\n        containerHeight = document.getElementsByTagName('body')[0].offsetHeight\n    }\n    return containerHeight - selfHeight - getTop(DOMNode, className)\n}\n\nexport const getRight = (DOMNode, className) => {\n    let containerWidth = 0;\n    let selfWidth = DOMNode.offsetWidth\n    if (className) {\n        containerWidth = document.getElementsByClassName(className)[0].offsetWidth\n    } else {\n        containerWidth = document.getElementsByTagName('body')[0].offsetWidth\n    }\n    return containerWidth - selfWidth - getLeft(DOMNode, className)\n}"
  },
  {
    "path": "src/utils/getSize.js",
    "content": "const getSize = () => {\n\tlet windowW,windowH,contentH,contentW,scrollT;\n\twindowH = window.innerHeight;\n\twindowW = window.innerWidth;\n\tscrollT = document.documentElement.scrollTop || document.body.scrollTop;\n\tcontentH = (document.documentElement.scrollHeight > document.body.scrollHeight) ? document.documentElement.scrollHeight : document.body.scrollHeight;\n\tcontentW = (document.documentElement.scrollWidth > document.body.scrollWidth) ? document.documentElement.scrollWidth : document.body.scrollWidth;\n\treturn {windowW,windowH,contentH,contentW,scrollT}\n}\n\nexport default getSize;"
  },
  {
    "path": "src/utils/getStrLength.js",
    "content": "// GBK字符集实际长度计算\nconst getStrLength = str => {\n    let realLength = 0;\n    let len = str.length;\n    let charCode = -1;\n    for(let i = 0; i < len; i++){\n        charCode = str.charCodeAt(i);\n        if (charCode >= 0 && charCode <= 128) { \n            realLength += 1;\n        }else{ \n             // 如果是中文则长度加2\n            realLength += 2;\n        }\n    } \n    return realLength;\n}\n\nexport default getStrLength"
  },
  {
    "path": "src/utils/myFetch.js",
    "content": "import originFetch from 'isomorphic-fetch'\nimport { fetchError, fetchStart, fetchEnd } from '../actions/fetchError'\n\nconst getData = async (url, option) => {\n    try {\n        let response = await originFetch(url, option)\n        if (response.ok) {\n            return response\n        } else {\n            let error = await response.json()\n            throw error\n        }\n    } catch (error) {\n        myDispatch(fetchError(error))\n    }\n    return new Promise(() => { })\n}\n\nconst myFetch = (url, option) => {\n\n    let myOption = {\n        credentials: 'include',\n        headers: {\n            \"Content-type\": \"application/json;charset=UTF-8\"\n        }\n    }\n    option = option || {}\n    option = { ...myOption, ...option }\n\n    // 方案1\n    // return new Promise((resolve, reject) => {\n    //     originFetch(url, option)\n    //         .then(response => {\n    //             if (response.ok) {\n    //                 resolve(response)\n    //             } else {\n    //                 response.json().then(json => {\n    //                     //response的then里产生或抛出的异常，无法直接抛出到该链式调用外面去被originFetch的catch捕获，除非获取到了originFetch的reject\n    //                     reject(json)//直接拿到这个return的new Promise的reject方法，能被它的catch方法（即最外面那个catch方法）捕捉到\n    //                 })\n    //             }\n    //         })\n    //         .catch(error => myDispatch(fetchError(error)))//获取originFetch链式调用里产生的异常，这里能获取到的是fetch请求失败，无返回response，服务器无响应的异常\n    // })\n    //     .catch(error => {   //获取的是reject(json)里的json数据\n    //         myDispatch(fetchError(error))\n    //         return new Promise(() => { }) //catch后面如果有then,then里面的方法执行可能会报错，要想其不执行的办法是返回一个promise对象携带空的方法\n    //     })\n\n    \n    //方案2\n    // return new Promise((resolve, reject) => {\n    //     originFetch(url, option)\n    //         .then(response => {\n    //             if (response.ok) {\n    //                 resolve(response)\n    //             } else {\n    //                 response.json().then(json => {\n    //                    throw json\n    //                 })\n    //                 .catch(error => myDispatch(fetchError(error)))//也可以直接在这里获取response里抛出的错误\n    //                 //promise对象如果不调用resolve方法，后面的then链路里的函数是不会被执行的\n    //             }\n    //         })\n    //         .catch(error => myDispatch(fetchError(error)))//获取originFetch链式调用里产生的异常，这里能获取到的是fetch请求失败，无返回response，服务器无响应的异常\n    // })\n\n    //方案3\n    //getData方法在最上面定义\n    return getData(url, option)\n}\n\nexport default myFetch"
  },
  {
    "path": "src/utils/routePrefix.js",
    "content": "// 使用browserHistory需要进行判断,在生产环境下，如果编译的文件不是根目录文件,而是在子文件夹内,子文件夹的地址部分会被browserHistory解析成路由\n// 比如：编译的文件放在站点www文件夹的cnode文件夹里,访问时用github.com/cnode/,但是此时cnode/被browserHistory解析成路由,这个路由不存在所以会出问题\n// let prefix = process.env.NODE_ENV === 'production' ? '/cnode' : '';\n\n// 使用hashHistory则不需要这样的判断，因为router被放在hash中，而浏览器能正确解析hash\nlet prefix = '';\nexport default prefix;"
  },
  {
    "path": "src/utils/transformDate.js",
    "content": "export default function (date) {\n    var createAt = new Date(date);\n    var time = new Date().getTime() - createAt.getTime(); //现在的时间-传入的时间 = 相差的时间（单位 = 毫秒）\n    if (time < 0) {\n        return '';\n    } else if (time / 1000 < 60) {\n        return '刚刚';\n    } else if ((time / 60000) < 60) {\n        return parseInt((time / 60000)) + '分钟前';\n    } else if ((time / 3600000) < 24) {\n        return parseInt(time / 3600000) + '小时前';\n    } else if ((time / 86400000) < 31) {\n        return parseInt(time / 86400000) + '天前';\n    } else if ((time / 2592000000) < 12) {\n        return parseInt(time / 2592000000) + '月前';\n    } else {\n        return parseInt(time / 31536000000) + '年前';\n    }\n}"
  },
  {
    "path": "src/utils/urlPrefix.js",
    "content": "import { os, host } from './getOS'\n\nlet prefix = os == 'win32' ? `http://${host}:8081` : '/api'\n// let prefix = 'http://192.168.30.90:8080/s';\n\n// let prefix = '/api'\n\nexport default prefix"
  },
  {
    "path": "webpack.config.js",
    "content": "var path = require('path');\nvar webpack = require('webpack')\nvar autoprefixer = require('autoprefixer')\nvar ExtractTextPlugin = require('extract-text-webpack-plugin')\nvar redBox = require('redbox-react')\nvar host = 'localhost'\nvar port = '5678'\n\n//判断当前运行环境是开发模式还是生产模式\nconst nodeEnv = process.env.NODE_ENV || 'development';\nconst isPro = nodeEnv !== 'development';\n\nconst os = require('os').platform()\nconst network = require('os').networkInterfaces()\n\nconsole.log(\"当前运行系统：\", os)\nconsole.log(\"当前运行环境：\", isPro ? 'production' : 'development')\n\n// Object.keys(network).forEach(item => {\n//     if (network[item] && network[item][0] && network[item][0].internal == false && ['以太网', '本地连接'].includes(item)) {\n//         network[item].forEach(ips => {\n//             if (ips.family == 'IPv4') {\n//                 host = ips.address\n//             }\n//         })\n//     }\n// })\n\n\n// var fs = require(\"fs\")\n// var data = `export const os = '${os}';export const host = '${host}'`\n// var writerStream = fs.createWriteStream('src/utils/getOS.js')\n// writerStream.write(data, 'UTF8')\n// writerStream.end()\n// writerStream.on('finish', () => {\n//     console.log(\"成功写入：src/utils/getOS.js\")\n// })\n\n// writerStream.on('error', err => {\n//     console.log(err.stack)\n// })\n\nconst moduleCss = new ExtractTextPlugin('module.css')\nconst globalCss = new ExtractTextPlugin('global.css')\nconst uiCss = new ExtractTextPlugin('ui.css')\n\nvar plugins = [\n    globalCss,\n    moduleCss,\n    uiCss,\n    // 这个插件的作用是将打包的js文件拆分，默认拆分出2个\n    new webpack.optimize.CommonsChunkPlugin({\n        // 这个vendor就是打包后的文件名字，需要写在index.html里面\n        name: 'vendor',\n        minChunks: function (module) {\n            // 该配置假定你引入的 vendor 存在于 node_modules 目录中\n            return module.context && module.context.indexOf('node_modules') !== -1;\n        }\n    }),\n    //DefinePlugin 在原始的源码中执行查找和替换操作，在导入的代码中，\n    // 任何出现 process.env.NODE_ENV的地方都会被替换为nodeEnv变量转成的json字符串\n    new webpack.DefinePlugin({\n        // 定义全局变量\n        'process.env': {\n            'NODE_ENV': JSON.stringify(nodeEnv)\n        }\n    })\n]\n\nvar app = ['./index.js']\n\nif (isPro) {\n    plugins.push(\n        new webpack.optimize.UglifyJsPlugin({\n            sourceMap: true,\n            comments: false,\n            compress: {\n                warnings: false,\n                // 去掉debugger和console\n                drop_debugger: true,\n                drop_console: true\n            }\n        })\n    )\n} else {\n    app.unshift('react-hot-loader/patch', `webpack-dev-server/client?http://${host}:${port}`, 'webpack/hot/only-dev-server')\n    plugins.push(\n        new webpack.HotModuleReplacementPlugin(),\n        new webpack.NamedModulesPlugin(),\n        new webpack.NoEmitOnErrorsPlugin()\n    )\n}\n\n// // 开启服务器后不能用相对路径\n// var outpath = path.resolve(__dirname, 'build');\n\nmodule.exports = {\n    context: path.resolve(__dirname, 'src'),\n    devtool: isPro ? 'source-map' : 'inline-source-map',\n    entry: {\n        app: app\n    },\n    output: {\n        filename: '[name].js',\n        path: path.join(__dirname, 'build'),\n        publicPath: isPro ? './build/' : '/build/',\n        chunkFilename: '[name].js'\n    },\n    module: {\n        rules: [\n            {\n                test: /\\.js$/,\n                exclude: /node_modules/,\n                use: ['babel-loader?cacheDirectory=true']\n            },\n            // {\n            //     test: /\\.scss$/,\n            //     // exclude: [nodeModulesPath]用来排除不处理的目录\n            //     exclude: path.resolve(__dirname, 'src/styles'),\n            //     // webpack配置loader时是可以不写loader的后缀明-loader，因此css-loader可以写为css\n            //     // css-loader使你能够使用类似@import 和 url(...)的方法实现 require()的功能\n            //     // style-loader将所有的计算后的样式通过<style>标签的形式加入到dom的head中，二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中\n            //     // CSS的规则都是全局的，任何一个组件的样式规则，都对整个页面有效。\n            //     // 产生局部作用域的唯一方法，就是使用一个独一无二的class的名字，不会与其他选择器重名（或者在css中类名前面加:local()）。这就是 CSS Modules 的做法。\n            //     // css-loader支持CSS Modules，在css-loader后面加了一个查询参数modules，表示打开 CSS Modules 功能\n            //     // 使用css-loader就可以支持css模块化，但此时css中的类名还是不能重复，必须加上参数modules，编译时就会自动将类名换成哈希值类型的类名，不担心重复了\n            //     // loader处理文件的加载顺序是从右到左，即先通过sass-loader将scss转成css,然后再用postcss预处理，添加上css3动画兼容性前缀等\n            //     // 然后在通过css-loader将css引入js文件,如果css-loader加了一个查询参数modules，则会将css中类名MD5化，使其变成唯一\n            //     // 最后通过styleloader将计算好的样式文件以style标签的形式加入到dom头部\n            //     use: ['style-loader', 'css-loader?modules', 'sass-loader']\n            // }, \n            {\n                test: /\\.scss$/,\n                // linux中\n                include: os === 'win32' ? /(src\\\\components|src\\\\containers)/ : /(src\\/components|src\\/containers)/,\n                use: moduleCss.extract({\n                    use: [\n                        {\n                            loader: \"css-loader?modules&localIdentName=[path][name]-[local]-[hash:base64:5]\"\n                        }, {\n                            loader: 'sass-loader'\n                        }]\n                })\n\n\n            }, {\n                test: /\\.css$/,\n                include: os === 'win32' ? /(src\\\\styles)/ : /(src\\/styles)/,\n                use: globalCss.extract({\n                    use: [\n                        {\n                            loader: \"css-loader\"\n                        }\n                        // , {\n                        //     loader: \"postcss-loader\"\n                        // }\n                    ]\n                })\n            }, {\n                test: /\\.css$/,\n                include: /node_modules/,\n                use: uiCss.extract({\n                    use: [\n                        {\n                            loader: \"css-loader\" // translates CSS into CommonJS\n                        }\n                        // , {\n                        //     loader: \"less-loader\" // compiles Less to CSS\n                        // }\n                    ]\n                })\n            }, {\n                test: /\\.(gif|jpg|png|woff|svg|eot|ttf)\\??.*$/,\n                use: ['url-loader?limit=50000&name=[path][name].[ext]']\n            }\n        ]\n    },\n    resolve: {\n        extensions: ['.js', '.jsx', '.less', '.scss', '.css'],\n        // alias里面设置的文件跟node_modules里面的文件等价，直接通过文件名导入，webpack就能找到相应文件。\n\n        alias: {\n            //当import 'redBoxBlackStyle' 时，webpack就会自动找根文件夹下面的redBoxBlackStyle.js\n            redBoxBlackStyle: path.join(__dirname, 'redBoxBlackStyle.js')\n        }\n    },\n    plugins: plugins,\n    // postcss: [autoprefixer],\n    devServer: {\n        hot: true,\n        host: host,\n        compress: true,\n        port,\n        historyApiFallback: true,\n        contentBase: path.resolve(__dirname),\n        publicPath: '/build/',\n        stats: {\n            modules: false,\n            chunks: false\n        },\n    },\n};\n"
  }
]