├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── README.md
├── app
├── dist
│ ├── 1-856466cc648a18903c62.chunk.js
│ ├── 1-856466cc648a18903c62.chunk.js.map
│ ├── 2-5eefedc12a6ccb7c4823.chunk.js
│ ├── 2-5eefedc12a6ccb7c4823.chunk.js.map
│ ├── css
│ │ ├── app-921080b3e7a4930d6a9d.css
│ │ └── app-921080b3e7a4930d6a9d.css.map
│ ├── index.html
│ └── js
│ │ ├── app-921080b3e7a4930d6a9d.js
│ │ ├── app-921080b3e7a4930d6a9d.js.map
│ │ ├── lib-7f943a2a0e638403017e.js
│ │ └── lib-7f943a2a0e638403017e.js.map
└── src
│ ├── README.md
│ ├── actions
│ ├── README.md
│ ├── apis.js
│ ├── auth.js
│ ├── post.js
│ └── posts.js
│ ├── app.js
│ ├── components
│ ├── customEditor
│ │ ├── base
│ │ │ ├── index.js
│ │ │ └── styles
│ │ │ │ └── index.styl
│ │ ├── index.js
│ │ └── text
│ │ │ ├── index.js
│ │ │ └── styles
│ │ │ └── index.styl
│ ├── customPosts
│ │ ├── base
│ │ │ ├── index.js
│ │ │ └── styles
│ │ │ │ └── index.styl
│ │ ├── image
│ │ │ ├── index.js
│ │ │ └── styles
│ │ │ │ └── index.styl
│ │ ├── index.js
│ │ ├── music
│ │ │ ├── index.js
│ │ │ └── styles
│ │ │ │ └── index.styl
│ │ └── text
│ │ │ ├── index.js
│ │ │ └── styles
│ │ │ └── index.styl
│ ├── highlight
│ │ └── index.js
│ ├── me
│ │ ├── index.js
│ │ └── styles
│ │ │ └── index.styl
│ ├── modal
│ │ ├── index.js
│ │ └── styles
│ │ │ └── index.styl
│ ├── more
│ │ ├── index.js
│ │ └── styles
│ │ │ └── index.styl
│ ├── navbar
│ │ ├── index.js
│ │ ├── styles
│ │ │ └── navbar.styl
│ │ └── test
│ │ │ └── index.js
│ ├── post
│ │ ├── index.js
│ │ └── styles
│ │ │ └── index.styl
│ └── textarea
│ │ ├── index.js
│ │ └── styles
│ │ └── index.styl
│ ├── config
│ └── client.js
│ ├── constants
│ ├── auth.js
│ ├── post.js
│ └── posts.js
│ ├── containers
│ ├── admin
│ │ └── adminContainer
│ │ │ └── index.js
│ ├── indexContainer
│ │ ├── index.js
│ │ └── styles
│ │ │ └── index.styl
│ ├── loginContainer
│ │ ├── index.js
│ │ └── styles
│ │ │ └── index.styl
│ ├── postContainer
│ │ ├── index.js
│ │ └── styles
│ │ │ └── index.styl
│ └── rootContainer
│ │ ├── index.js
│ │ └── styles
│ │ └── index.styl
│ ├── index.template.html
│ ├── middleware
│ ├── createMiddle.js
│ └── req.js
│ ├── reducers
│ ├── auth.js
│ ├── index.js
│ ├── post.js
│ └── posts.js
│ ├── routes.js
│ ├── sockets
│ └── index.js
│ ├── styles
│ └── app.styl
│ └── utils.js
├── package.json
├── server
├── app.js
├── assets
│ ├── favicon.ico
│ ├── images
│ │ ├── 03016_strossmayer_2880x1800.jpg
│ │ ├── 03333_spinout_2880x1800.jpg
│ │ ├── IMG_5053.jpg
│ │ ├── bg.jpg
│ │ └── me.png
│ └── mp3
│ │ ├── Hillsong Young And Free - Wake.mp3
│ │ └── 金玟岐 - 腻味.mp3
├── bin
│ ├── _init.js
│ ├── run.js
│ ├── server.babel.js
│ └── www.js
├── db
│ ├── Post.js
│ ├── User.js
│ ├── connection.js
│ ├── index.js
│ └── schema
│ │ ├── post.js
│ │ └── user.js
├── routes
│ ├── api.js
│ └── index.js
└── sockets
│ └── index.js
└── webpack
├── hot.js
├── webpack.dev.config.js
└── webpack.prod.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0,
3 | "loose": "all",
4 | "optional": [
5 | "bluebirdCoroutines",
6 | "runtime"
7 | ],
8 | "blacklist": [
9 | "regenerator"
10 | ],
11 | "highlightCode": true,
12 | "env": {
13 | "development": {
14 | "plugins": [
15 | "react-transform"
16 | ],
17 | "extra": {
18 | "react-transform": {
19 | "transforms": [
20 | {
21 | "transform": "react-transform-catch-errors",
22 | "imports": [
23 | "react",
24 | "redbox-react"
25 | ]
26 | },
27 | {
28 | "transform": "react-transform-hmr",
29 | "imports": ["react"],
30 | "locals": ["module"]
31 | }
32 | ]
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | indent_style = space
3 | end_of_line = lf
4 | indent_size = 2
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 |
8 | [*.md]
9 | max_line_length = 0
10 | trim_trailing_whitespace = false
11 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | webpack/*
2 | app/dist/*
3 | server/bin/www.js
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "env": {
4 | "browser": true,
5 | "node": true
6 | },
7 | "ecmaFeatures": {
8 | "jsx": true,
9 | "modules": true
10 | },
11 | "parser": "babel-eslint",
12 | "rules": {
13 | "react/jsx-uses-react": 2,
14 | "react/jsx-uses-vars": 2,
15 | "react/react-in-jsx-scope": 2,
16 | "react/prop-types": 0,
17 | "react/jsx-boolean-value": 0,
18 | "react/jsx-quotes": 0,
19 | "react/no-multi-comp": 0,
20 | "import/default": 0,
21 | "import/no-duplicates": 0,
22 | "import/named": 0,
23 | "import/namespace": 0,
24 | "import/no-unresolved": 0,
25 | "import/no-named-as-default": 2,
26 | "no-unused-vars": 1,
27 | "jsx-quotes": 2,
28 | "id-length": 0,
29 | "func-names": 0,
30 | "block-scoped-var": 0,
31 | "padded-blocks": 0,
32 | "comma-dangle": 0,
33 | "indent": [2, 2, {
34 | "SwitchCase": 1
35 | }],
36 | "no-console": 0,
37 | "no-alert": 0
38 | },
39 | "plugins": [
40 | "react", "import"
41 | ],
42 | "settings": {
43 | "import/parser": "babel-eslint",
44 | "import/resolve": {
45 | moduleDirectory: ["node_modules", "src"]
46 | }
47 | },
48 | "globals": {
49 | "__DEVELOPMENT__": true,
50 | "__CLIENT__": true,
51 | "__SERVER__": true,
52 | "socket": true,
53 | "io": true
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | .tern-project
4 | *.log
5 | *.sublime-project
6 | *.sublime-workspace
7 |
8 | node_modules
9 | dump.rdb
10 |
11 | app/dist
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ddx
2 |
3 | 这是一个简单的日记web程序,记录,分享自己喜欢的文字,音乐,照片
4 | 总之还有很多问题,我会继续完善,也会尝试多增加点功能,主要是前端react,学习嘛,就是得折腾
5 |
6 | ## 主要使用到的技术
7 | - React.js
8 | - webpack
9 | - redux
10 | - react-router
11 | - Node.js
12 | - koa
13 | - Mongodb
14 |
15 | 还要感谢以下
16 | - css-modules
17 | - cssnext
18 | - postcss
19 | - babel
20 | - .....
21 | (这个b装得可以)
22 |
23 | ## Demo
24 | [看这里](http://121.42.195.128/)
25 |
26 | ## 怎样启动?
27 | 1. 依赖mongodb,所以您需要先安装mongodb,并且启动,***建立一个数据库***
28 | 2. 进入项目目录,执行```bash npm install // 也可以使用cnpm install 来安装 ```
29 | 3. 😎进入下一步
30 |
31 | ### 开发模式
32 | - 服务端自动重启
33 | - 前端监控项目文件,有任何改动则会自动重启,无需浏览器自动F5。
34 |
35 | ***
36 |
37 | 服务端:
38 | ```bash
39 | npm run server-watch
40 | ```
41 |
42 | 前端:
43 | ```bash
44 | npm run client-watch
45 | ```
46 |
47 | ### 发布模式
48 | - 服务端使用pm2 or 其他等等启动 (我这里仅仅是很简单的启动而已)
49 | - 前端各种编译打包压缩代码。
50 |
51 | ***
52 |
53 | 服务端:
54 | ```bash
55 | npm run server-start
56 | ```
57 | 前端:
58 | ```bash
59 | npm run client-build
60 | ```
61 |
62 | ## 第一次运行
63 | 第一次数据库是没有东西的所有我们需要来点初始数据看看效果
64 | 新建两个命令行窗口,第一个窗口执行
65 | 如果你是想看 ***打包压缩*** 后的版本
66 | 先编译前端项目
67 | ```bash
68 | npm run client-build
69 | ```
70 | 然后运行服务端
71 | ```base
72 | npm run server-start init
73 | ```
74 | 这里init是执行一个js写点初始数据到数据库里,***以后不用加!!***
75 | 打开浏览器localhost:3000
76 | ok 这就是你的第一次😀。
77 |
78 | 下面是你想进行开发了,自动重启啊什么的
79 | 把前面那些统统关掉
80 | ```bash
81 | npm run client-watch
82 | ```
83 | 第二个窗口执行
84 | 如果不开发node端,可以用server-start
85 | ```bash
86 | npm run server-watch
87 | ```
88 | 各方面细节还不是很完善,css-modules第一次使用,纯属乱用,导致现在写css比较难受。
89 | 本来html、css很渣,所有页面都是乱拼出来的。
90 | Chrome版本 48.0.2547.0 dev (64-bit)
91 |
92 | webpack打包后:
93 | - js有1.54MB
94 | - css有18.4KB
95 |
96 | gzip压缩后:
97 | - js有460.82KB
98 | - css有5.06KB
99 |
100 | ## TODOS
101 | - [x] 发表文章
102 | - [x] 浏览文章
103 | - [x] 评论文章
104 | - [x] 实时通知新评论,新文章
105 | - [] 上传图片
106 | - [] 上传音乐
107 | - [] 个人页面
108 | - [] 注册
109 | - [] .....
110 |
111 | 最后,以上开发都在Mac上进行。 windows能try就try吧😂......
112 |
--------------------------------------------------------------------------------
/app/dist/2-5eefedc12a6ccb7c4823.chunk.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([2],{179:function(e,t){e.exports=function(e){return{aliases:["js"],keywords:{keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},contains:[{className:"pi",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE,{className:"subst",begin:"\\$\\{",end:"\\}"}]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",variants:[{begin:"\\b(0[bB][01]+)"},{begin:"\\b(0[oO][0-7]+)"},{begin:e.C_NUMBER_RE}],relevance:0},{begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.REGEXP_MODE,{begin:/,end:/>\s*[);\]]/,relevance:0,subLanguage:"xml"}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]}],illegal:/\[|%/},{begin:/\$[(.]/},{begin:"\\."+e.IDENT_RE,relevance:0},{beginKeywords:"import",end:"[;$]",keywords:"import from as",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},e.UNDERSCORE_TITLE_MODE]}],illegal:/#/}}},382:function(e,t,n){"use strict";var r=n(4)["default"];t.__esModule=!0;var s=n(484),i=r(s);n(450),i["default"].registerLanguage("markdown",n(486)),i["default"].registerLanguage("javascript",n(179)),i["default"].registerLanguage("js",n(179)),i["default"].registerLanguage("css",n(485)),t["default"]=i["default"],e.exports=t["default"]},450:451,484:function(e,t,n){!function(e){e(t)}(function(e){function t(e){return e.replace(/&/gm,"&").replace(//gm,">")}function n(e){return e.nodeName.toLowerCase()}function r(e,t){var n=e&&e.exec(t);return n&&0==n.index}function s(e){return/^(no-?highlight|plain|text)$/i.test(e)}function i(e){var t,n,r,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",n=/\blang(?:uage)?-([\w-]+)\b/i.exec(i))return x(n[1])?n[1]:"no-highlight";for(i=i.split(/\s+/),t=0,r=i.length;r>t;t++)if(x(i[t])||s(i[t]))return i[t]}function a(e,t){var n,r={};for(n in e)r[n]=e[n];if(t)for(n in t)r[n]=t[n];return r}function l(e){var t=[];return function r(e,s){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?s+=i.nodeValue.length:1==i.nodeType&&(t.push({event:"start",offset:s,node:i}),s=r(i,s),n(i).match(/br|hr|img|input/)||t.push({event:"stop",offset:s,node:i}));return s}(e,0),t}function o(e,r,s){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function l(e){c+=""+n(e)+">"}function o(e){("start"==e.event?a:l)(e.node)}for(var u=0,c="",h=[];e.length||r.length;){var g=i();if(c+=t(s.substr(u,g[0].offset-u)),u=g[0].offset,g==e){h.reverse().forEach(l);do o(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==u);h.reverse().forEach(a)}else"start"==g[0].event?h.push(g[0].node):h.pop(),o(g.splice(0,1)[0])}return c+t(s.substr(u))}function u(e){function t(e){return e&&e.source||e}function n(n,r){return new RegExp(t(n),"m"+(e.case_insensitive?"i":"")+(r?"g":""))}function r(s,i){if(!s.compiled){if(s.compiled=!0,s.keywords=s.keywords||s.beginKeywords,s.keywords){var l={},o=function(t,n){e.case_insensitive&&(n=n.toLowerCase()),n.split(" ").forEach(function(e){var n=e.split("|");l[n[0]]=[t,n[1]?Number(n[1]):1]})};"string"==typeof s.keywords?o("keyword",s.keywords):Object.keys(s.keywords).forEach(function(e){o(e,s.keywords[e])}),s.keywords=l}s.lexemesRe=n(s.lexemes||/\b\w+\b/,!0),i&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")\\b"),s.begin||(s.begin=/\B|\b/),s.beginRe=n(s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(s.endRe=n(s.end)),s.terminator_end=t(s.end)||"",s.endsWithParent&&i.terminator_end&&(s.terminator_end+=(s.end?"|":"")+i.terminator_end)),s.illegal&&(s.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]);var u=[];s.contains.forEach(function(e){e.variants?e.variants.forEach(function(t){u.push(a(e,t))}):u.push("self"==e?s:e)}),s.contains=u,s.contains.forEach(function(e){r(e,s)}),s.starts&&r(s.starts,i);var c=s.contains.map(function(e){return e.beginKeywords?"\\.?("+e.begin+")\\.?":e.begin}).concat([s.terminator_end,s.illegal]).map(t).filter(Boolean);s.terminators=c.length?n(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function c(e,n,s,i){function a(e,t){for(var n=0;n";return i+=e+'">',i+t+a}function d(){if(!N.keywords)return t(O);var e="",n=0;N.lexemesRe.lastIndex=0;for(var r=N.lexemesRe.exec(O);r;){e+=t(O.substr(n,r.index-n));var s=g(N,r);s?(M+=s[1],e+=p(s[0],t(r[0]))):e+=t(r[0]),n=N.lexemesRe.lastIndex,r=N.lexemesRe.exec(O)}return e+t(O.substr(n))}function f(){var e="string"==typeof N.subLanguage;if(e&&!v[N.subLanguage])return t(O);var n=e?c(N.subLanguage,O,!0,w[N.subLanguage]):h(O,N.subLanguage.length?N.subLanguage:void 0);return N.relevance>0&&(M+=n.relevance),e&&(w[N.subLanguage]=n.top),p(n.language,n.value,!1,!0)}function b(){return void 0!==N.subLanguage?f():d()}function m(e,n){var r=e.className?p(e.className,"",!0):"";e.returnBegin?(S+=r,O=""):e.excludeBegin?(S+=t(n)+r,O=""):(S+=r,O=n),N=Object.create(e,{parent:{value:N}})}function _(e,n){if(O+=e,void 0===n)return S+=b(),0;var r=a(n,N);if(r)return S+=b(),m(r,n),r.returnBegin?0:n.length;var s=l(N,n);if(s){var i=N;i.returnEnd||i.excludeEnd||(O+=n),S+=b();do N.className&&(S+=""),M+=N.relevance,N=N.parent;while(N!=s.parent);return i.excludeEnd&&(S+=t(n)),O="",s.starts&&m(s.starts,""),i.returnEnd?0:n.length}if(o(n,N))throw new Error('Illegal lexeme "'+n+'" for mode "'+(N.className||"")+'"');return O+=n,n.length||1}var E=x(e);if(!E)throw new Error('Unknown language: "'+e+'"');u(E);var y,N=i||E,w={},S="";for(y=N;y!=E;y=y.parent)y.className&&(S=p(y.className,"",!0)+S);var O="",M=0;try{for(var R,C,L=0;N.terminators.lastIndex=L,R=N.terminators.exec(n),R;)C=_(n.substr(L,R.index-L),R[0]),L=R.index+C;for(_(n.substr(L)),y=N;y.parent;y=y.parent)y.className&&(S+="");return{relevance:M,value:S,language:e,top:N}}catch(A){if(-1!=A.message.indexOf("Illegal"))return{relevance:0,value:t(n)};throw A}}function h(e,n){n=n||k.languages||Object.keys(v);var r={relevance:0,value:t(e)},s=r;return n.forEach(function(t){if(x(t)){var n=c(t,e,!1);n.language=t,n.relevance>s.relevance&&(s=n),n.relevance>r.relevance&&(s=r,r=n)}}),s.language&&(r.second_best=s),r}function g(e){return k.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,t){return t.replace(/\t/g,k.tabReplace)})),k.useBR&&(e=e.replace(/\n/g,"
")),e}function p(e,t,n){var r=t?y[t]:n,s=[e.trim()];return e.match(/\bhljs\b/)||s.push("hljs"),-1===e.indexOf(r)&&s.push(r),s.join(" ").trim()}function d(e){var t=i(e);if(!s(t)){var n;k.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/
/g,"\n")):n=e;var r=n.textContent,a=t?c(t,r,!0):h(r),u=l(n);if(u.length){var d=document.createElementNS("http://www.w3.org/1999/xhtml","div");d.innerHTML=a.value,a.value=o(u,l(d),r)}a.value=g(a.value),e.innerHTML=a.value,e.className=p(e.className,t,a.language),e.result={language:a.language,re:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance})}}function f(e){k=a(k,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function _(t,n){var r=v[t]=n(e);r.aliases&&r.aliases.forEach(function(e){y[e]=t})}function E(){return Object.keys(v)}function x(e){return e=(e||"").toLowerCase(),v[e]||v[y[e]]}var k={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},v={},y={};return e.highlight=c,e.highlightAuto=h,e.fixMarkup=g,e.highlightBlock=d,e.configure=f,e.initHighlighting=b,e.initHighlightingOnLoad=m,e.registerLanguage=_,e.listLanguages=E,e.getLanguage=x,e.inherit=a,e.IDENT_RE="[a-zA-Z]\\w*",e.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*",e.NUMBER_RE="\\b\\d+(\\.\\d+)?",e.C_NUMBER_RE="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BINARY_NUMBER_RE="\\b(0b[01]+)",e.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BACKSLASH_ESCAPE={begin:"\\\\[\\s\\S]",relevance:0},e.APOS_STRING_MODE={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},e.QUOTE_STRING_MODE={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},e.PHRASAL_WORDS_MODE={begin:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.COMMENT=function(t,n,r){var s=e.inherit({className:"comment",begin:t,end:n,contains:[]},r||{});return s.contains.push(e.PHRASAL_WORDS_MODE),s.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0}),s},e.C_LINE_COMMENT_MODE=e.COMMENT("//","$"),e.C_BLOCK_COMMENT_MODE=e.COMMENT("/\\*","\\*/"),e.HASH_COMMENT_MODE=e.COMMENT("#","$"),e.NUMBER_MODE={className:"number",begin:e.NUMBER_RE,relevance:0},e.C_NUMBER_MODE={className:"number",begin:e.C_NUMBER_RE,relevance:0},e.BINARY_NUMBER_MODE={className:"number",begin:e.BINARY_NUMBER_RE,relevance:0},e.CSS_NUMBER_MODE={className:"number",begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},e.REGEXP_MODE={className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0,contains:[e.BACKSLASH_ESCAPE]}]},e.TITLE_MODE={className:"title",begin:e.IDENT_RE,relevance:0},e.UNDERSCORE_TITLE_MODE={className:"title",begin:e.UNDERSCORE_IDENT_RE,relevance:0},e})},485:function(e,t){e.exports=function(e){var t="[a-zA-Z-][a-zA-Z0-9_-]*",n={className:"function",begin:t+"\\(",returnBegin:!0,excludeEnd:!0,end:"\\("},r={className:"rule",begin:/[A-Z\_\.\-]+\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{className:"value",endsWithParent:!0,excludeEnd:!0,contains:[n,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"hexcolor",begin:"#[0-9A-Fa-f]+"},{className:"important",begin:"!important"}]}}]};return{case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"id",begin:/\#[A-Za-z0-9_-]+/},{className:"class",begin:/\.[A-Za-z0-9_-]+/},{className:"attr_selector",begin:/\[/,end:/\]/,illegal:"$"},{className:"pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"']+/},{className:"at_rule",begin:"@(font-face|page)",lexemes:"[a-z-]+",keywords:"font-face page"},{className:"at_rule",begin:"@",end:"[{;]",contains:[{className:"keyword",begin:/\S+/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,contains:[n,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"tag",begin:t,relevance:0},{className:"rules",begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,r]}]}}},486:function(e,t){e.exports=function(e){return{aliases:["md","mkdown","mkd"],contains:[{className:"header",variants:[{begin:"^#{1,6}",end:"$"},{begin:"^.+?\\n[=-]{2,}$"}]},{begin:"<",end:">",subLanguage:"xml",relevance:0},{className:"bullet",begin:"^([*+-]|(\\d+\\.))\\s+"},{className:"strong",begin:"[*_]{2}.+?[*_]{2}"},{className:"emphasis",variants:[{begin:"\\*.+?\\*"},{begin:"_.+?_",relevance:0}]},{className:"blockquote",begin:"^>\\s+",end:"$"},{className:"code",variants:[{begin:"`.+?`"},{begin:"^( {4}| )",end:"$",relevance:0}]},{className:"horizontal_rule",begin:"^[-\\*]{3,}",end:"$"},{begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"link_label",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link_url",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"link_reference",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},{begin:"^\\[.+\\]:",returnBegin:!0,contains:[{className:"link_reference",begin:"\\[",end:"\\]:",excludeBegin:!0,excludeEnd:!0,starts:{className:"link_url",end:"$"}}]}]}}},551:function(e,t,n){(function(t){(function(){function t(e){this.tokens=[],this.tokens.links={},this.options=e||c.defaults,this.rules=h.normal,this.options.gfm&&(this.options.tables?this.rules=h.tables:this.rules=h.gfm)}function n(e,t){if(this.options=t||c.defaults,this.links=e,this.rules=g.normal,this.renderer=this.options.renderer||new r,this.renderer.options=this.options,!this.links)throw new Error("Tokens array requires a `links` property.");this.options.gfm?this.options.breaks?this.rules=g.breaks:this.rules=g.gfm:this.options.pedantic&&(this.rules=g.pedantic)}function r(e){this.options=e||{}}function s(e){this.tokens=[],this.token=null,this.options=e||c.defaults,this.options.renderer=this.options.renderer||new r,this.renderer=this.options.renderer,this.renderer.options=this.options}function i(e,t){return e.replace(t?/&/g:/&(?!#?\w+;)/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function a(e){return e.replace(/&([#\w]+);/g,function(e,t){return t=t.toLowerCase(),"colon"===t?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}function l(e,t){return e=e.source,t=t||"",function n(r,s){return r?(s=s.source||s,s=s.replace(/(^|[^\[])\^/g,"$1"),e=e.replace(r,s),n):new RegExp(e,t)}}function o(){}function u(e){for(var t,n,r=1;rAn error occured:
"+i(g.message+"",!0)+"
";throw g}}var h={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:o,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:o,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:o,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};h.bullet=/(?:[*+-]|\d+\.)/,h.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,h.item=l(h.item,"gm")(/bull/g,h.bullet)(),h.list=l(h.list)(/bull/g,h.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+h.def.source+")")(),h.blockquote=l(h.blockquote)("def",h.def)(),h._tag="(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b",h.html=l(h.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,h._tag)(),h.paragraph=l(h.paragraph)("hr",h.hr)("heading",h.heading)("lheading",h.lheading)("blockquote",h.blockquote)("tag","<"+h._tag)("def",h.def)(),h.normal=u({},h),h.gfm=u({},h.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/}),h.gfm.paragraph=l(h.paragraph)("(?!","(?!"+h.gfm.fences.source.replace("\\1","\\2")+"|"+h.list.source.replace("\\1","\\3")+"|")(),h.tables=u({},h.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/}),t.rules=h,t.lex=function(e,n){var r=new t(n);return r.lex(e)},t.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0)},t.prototype.token=function(e,t,n){for(var r,s,i,a,l,o,u,c,g,e=e.replace(/^ +$/gm,"");e;)if((i=this.rules.newline.exec(e))&&(e=e.substring(i[0].length),i[0].length>1&&this.tokens.push({type:"space"})),i=this.rules.code.exec(e))e=e.substring(i[0].length),i=i[0].replace(/^ {4}/gm,""),this.tokens.push({type:"code",text:this.options.pedantic?i:i.replace(/\n+$/,"")});else if(i=this.rules.fences.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"code",lang:i[2],text:i[3]||""});else if(i=this.rules.heading.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"heading",depth:i[1].length,text:i[2]});else if(t&&(i=this.rules.nptable.exec(e))){for(e=e.substring(i[0].length),o={type:"table",header:i[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:i[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:i[3].replace(/\n$/,"").split("\n")},c=0;c ?/gm,""),this.token(i,t,!0),this.tokens.push({type:"blockquote_end"});else if(i=this.rules.list.exec(e)){for(e=e.substring(i[0].length),a=i[2],this.tokens.push({type:"list_start",ordered:a.length>1}),i=i[0].match(this.rules.item),r=!1,g=i.length,c=0;g>c;c++)o=i[c],u=o.length,o=o.replace(/^ *([*+-]|\d+\.) +/,""),~o.indexOf("\n ")&&(u-=o.length,o=this.options.pedantic?o.replace(/^ {1,4}/gm,""):o.replace(new RegExp("^ {1,"+u+"}","gm"),"")),this.options.smartLists&&c!==g-1&&(l=h.bullet.exec(i[c+1])[0],a===l||a.length>1&&l.length>1||(e=i.slice(c+1).join("\n")+e,c=g-1)),s=r||/\n\n(?!\s*$)/.test(o),c!==g-1&&(r="\n"===o.charAt(o.length-1),s||(s=r)),this.tokens.push({type:s?"loose_item_start":"list_item_start"}),this.token(o,!1,n),this.tokens.push({type:"list_item_end"});this.tokens.push({type:"list_end"})}else if(i=this.rules.html.exec(e))e=e.substring(i[0].length),this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&("pre"===i[1]||"script"===i[1]||"style"===i[1]),text:i[0]});else if(!n&&t&&(i=this.rules.def.exec(e)))e=e.substring(i[0].length),this.tokens.links[i[1].toLowerCase()]={href:i[2],title:i[3]};else if(t&&(i=this.rules.table.exec(e))){for(e=e.substring(i[0].length),o={type:"table",header:i[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:i[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:i[3].replace(/(?: *\| *)?\n$/,"").split("\n")},c=0;c])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:o,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:o,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/,g.link=l(g.link)("inside",g._inside)("href",g._href)(),g.reflink=l(g.reflink)("inside",g._inside)(),g.normal=u({},g),g.pedantic=u({},g.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/}),g.gfm=u({},g.normal,{escape:l(g.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:l(g.text)("]|","~]|")("|","|https?://|")()}),g.breaks=u({},g.gfm,{br:l(g.br)("{2,}","*")(),text:l(g.gfm.text)("{2,}","*")()}),n.rules=g,n.output=function(e,t,r){var s=new n(t,r);return s.output(e)},n.prototype.output=function(e){for(var t,n,r,s,a="";e;)if(s=this.rules.escape.exec(e))e=e.substring(s[0].length),a+=s[1];else if(s=this.rules.autolink.exec(e))e=e.substring(s[0].length),"@"===s[2]?(n=":"===s[1].charAt(6)?this.mangle(s[1].substring(7)):this.mangle(s[1]),r=this.mangle("mailto:")+n):(n=i(s[1]),r=n),a+=this.renderer.link(r,null,n);else if(this.inLink||!(s=this.rules.url.exec(e))){if(s=this.rules.tag.exec(e))!this.inLink&&/^/i.test(s[0])&&(this.inLink=!1),e=e.substring(s[0].length),a+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(s[0]):i(s[0]):s[0];else if(s=this.rules.link.exec(e))e=e.substring(s[0].length),this.inLink=!0,a+=this.outputLink(s,{href:s[2],title:s[3]}),this.inLink=!1;else if((s=this.rules.reflink.exec(e))||(s=this.rules.nolink.exec(e))){if(e=e.substring(s[0].length),t=(s[2]||s[1]).replace(/\s+/g," "),t=this.links[t.toLowerCase()],!t||!t.href){a+=s[0].charAt(0),e=s[0].substring(1)+e;continue}this.inLink=!0,a+=this.outputLink(s,t),this.inLink=!1}else if(s=this.rules.strong.exec(e))e=e.substring(s[0].length),a+=this.renderer.strong(this.output(s[2]||s[1]));else if(s=this.rules.em.exec(e))e=e.substring(s[0].length),a+=this.renderer.em(this.output(s[2]||s[1]));else if(s=this.rules.code.exec(e))e=e.substring(s[0].length),a+=this.renderer.codespan(i(s[2],!0));else if(s=this.rules.br.exec(e))e=e.substring(s[0].length),a+=this.renderer.br();else if(s=this.rules.del.exec(e))e=e.substring(s[0].length),a+=this.renderer.del(this.output(s[1]));else if(s=this.rules.text.exec(e))e=e.substring(s[0].length),a+=this.renderer.text(i(this.smartypants(s[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else e=e.substring(s[0].length),n=i(s[1]),r=n,a+=this.renderer.link(r,null,n);return a},n.prototype.outputLink=function(e,t){var n=i(t.href),r=t.title?i(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,i(e[1]))},n.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014\/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014\/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},n.prototype.mangle=function(e){if(!this.options.mangle)return e;for(var t,n="",r=e.length,s=0;r>s;s++)t=e.charCodeAt(s),Math.random()>.5&&(t="x"+t.toString(16)),n+=""+t+";";return n},r.prototype.code=function(e,t,n){if(this.options.highlight){var r=this.options.highlight(e,t);null!=r&&r!==e&&(n=!0,e=r)}return t?''+(n?e:i(e,!0))+"\n
\n":""+(n?e:i(e,!0))+"\n
"},r.prototype.blockquote=function(e){return"\n"+e+"
\n"},r.prototype.html=function(e){return e},r.prototype.heading=function(e,t,n){return"\n"},r.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"},r.prototype.list=function(e,t){var n=t?"ol":"ul";return"<"+n+">\n"+e+""+n+">\n"},r.prototype.listitem=function(e){return""+e+"\n"},r.prototype.paragraph=function(e){return""+e+"
\n"},r.prototype.table=function(e,t){return"\n"},r.prototype.tablerow=function(e){return"\n"+e+"
\n"},r.prototype.tablecell=function(e,t){var n=t.header?"th":"td",r=t.align?"<"+n+' style="text-align:'+t.align+'">':"<"+n+">";return r+e+""+n+">\n"},r.prototype.strong=function(e){return""+e+""},r.prototype.em=function(e){return""+e+""},r.prototype.codespan=function(e){return""+e+"
"},r.prototype.br=function(){return this.options.xhtml?"
":"
"},r.prototype.del=function(e){return""+e+""},r.prototype.link=function(e,t,n){if(this.options.sanitize){try{var r=decodeURIComponent(a(e)).replace(/[^\w:]/g,"").toLowerCase()}catch(s){return""}if(0===r.indexOf("javascript:")||0===r.indexOf("vbscript:"))return""}var i='"+n+""},r.prototype.image=function(e,t,n){var r='
":">"},r.prototype.text=function(e){return e},s.parse=function(e,t,n){var r=new s(t,n);return r.parse(e)},s.prototype.parse=function(e){this.inline=new n(e.links,this.options,this.renderer),this.tokens=e.reverse();for(var t="";this.next();)t+=this.tok();return t},s.prototype.next=function(){return this.token=this.tokens.pop()},s.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},s.prototype.parseText=function(){for(var e=this.token.text;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)},s.prototype.tok=function(){switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text);case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var e,t,n,r,s,i="",a="";for(n="",e=0;espan::selection,.cm-s-base16-light .CodeMirror-line>span>span::selection,.cm-s-base16-light div.CodeMirror-selected{background:#e0e0e0}.cm-s-base16-light .CodeMirror-line::-moz-selection,.cm-s-base16-light .CodeMirror-line>span::-moz-selection,.cm-s-base16-light .CodeMirror-line>span>span::-moz-selection{background:#e0e0e0}.cm-s-base16-light .CodeMirror-gutters{background:#f5f5f5;border-right:0}.cm-s-base16-light .CodeMirror-guttermarker{color:#ac4142}.cm-s-base16-light .CodeMirror-guttermarker-subtle,.cm-s-base16-light .CodeMirror-linenumber{color:#b0b0b0}.cm-s-base16-light .CodeMirror-cursor{border-left:1px solid #505050}.cm-s-base16-light span.cm-comment{color:#8f5536}.cm-s-base16-light span.cm-atom,.cm-s-base16-light span.cm-number{color:#aa759f}.cm-s-base16-light span.cm-attribute,.cm-s-base16-light span.cm-property{color:#90a959}.cm-s-base16-light span.cm-keyword{color:#ac4142}.cm-s-base16-light span.cm-string{color:#f4bf75}.cm-s-base16-light span.cm-variable{color:#90a959}.cm-s-base16-light span.cm-variable-2{color:#6a9fb5}.cm-s-base16-light span.cm-def{color:#d28445}.cm-s-base16-light span.cm-bracket{color:#202020}.cm-s-base16-light span.cm-tag{color:#ac4142}.cm-s-base16-light span.cm-link{color:#aa759f}.cm-s-base16-light span.cm-error{background:#ac4142;color:#505050}.cm-s-base16-light .CodeMirror-activeline-background{background:#dddcdc}.cm-s-base16-light .CodeMirror-matchingbracket{text-decoration:underline;color:#fff!important}.CodeMirror{font-family:monospace;height:300px;color:#000}.CodeMirror-lines{padding:4px 0}.CodeMirror pre{padding:0 4px}.CodeMirror-gutter-filler,.CodeMirror-scrollbar-filler{background-color:#fff}.CodeMirror-gutters{border-right:1px solid #ddd;background-color:#f7f7f7;white-space:nowrap}.CodeMirror-linenumber{padding:0 3px 0 5px;min-width:20px;text-align:right;color:#999;white-space:nowrap}.CodeMirror-guttermarker{color:#000}.CodeMirror-guttermarker-subtle{color:#999}.CodeMirror-cursor{border-left:1px solid #000;border-right:none;width:0}.CodeMirror div.CodeMirror-secondarycursor{border-left:1px solid silver}.cm-fat-cursor .CodeMirror-cursor{width:auto;border:0;background:#7e7}.cm-fat-cursor div.CodeMirror-cursors{z-index:1}.cm-animate-fat-cursor{width:auto;border:0;-webkit-animation:blink 1.06s steps(1) infinite;animation:blink 1.06s steps(1) infinite;background-color:#7e7}@-webkit-keyframes blink{50%{background-color:transparent}}@keyframes blink{50%{background-color:transparent}}.cm-tab{display:inline-block;text-decoration:inherit}.CodeMirror-ruler{border-left:1px solid #ccc;position:absolute}.cm-s-default .cm-header{color:blue}.cm-s-default .cm-quote{color:#090}.cm-negative{color:#d44}.cm-positive{color:#292}.cm-header,.cm-strong{font-weight:700}.cm-em{font-style:italic}.cm-link{text-decoration:underline}.cm-strikethrough{text-decoration:line-through}.cm-s-default .cm-keyword{color:#708}.cm-s-default .cm-atom{color:#219}.cm-s-default .cm-number{color:#164}.cm-s-default .cm-def{color:#00f}.cm-s-default .cm-variable-2{color:#05a}.cm-s-default .cm-variable-3{color:#085}.cm-s-default .cm-comment{color:#a50}.cm-s-default .cm-string{color:#a11}.cm-s-default .cm-string-2{color:#f50}.cm-s-default .cm-meta,.cm-s-default .cm-qualifier{color:#555}.cm-s-default .cm-builtin{color:#30a}.cm-s-default .cm-bracket{color:#997}.cm-s-default .cm-tag{color:#170}.cm-s-default .cm-attribute{color:#00c}.cm-s-default .cm-hr{color:#999}.cm-s-default .cm-link{color:#00c}.cm-invalidchar,.cm-s-default .cm-error{color:red}.CodeMirror-composing{border-bottom:2px solid}div.CodeMirror span.CodeMirror-matchingbracket{color:#0f0}div.CodeMirror span.CodeMirror-nonmatchingbracket{color:#f22}.CodeMirror-matchingtag{background:rgba(255,150,0,.3)}.CodeMirror-activeline-background{background:#e8f2ff}.CodeMirror{position:relative;overflow:hidden;background:#fff}.CodeMirror-scroll{overflow:scroll!important;margin-bottom:-30px;margin-right:-30px;padding-bottom:30px;height:100%;outline:none;position:relative}.CodeMirror-sizer{position:relative;border-right:30px solid transparent}.CodeMirror-gutter-filler,.CodeMirror-hscrollbar,.CodeMirror-scrollbar-filler,.CodeMirror-vscrollbar{position:absolute;z-index:6;display:none}.CodeMirror-vscrollbar{right:0;top:0;overflow-x:hidden;overflow-y:scroll}.CodeMirror-hscrollbar{bottom:0;left:0;overflow-y:hidden;overflow-x:scroll}.CodeMirror-scrollbar-filler{right:0;bottom:0}.CodeMirror-gutter-filler{left:0;bottom:0}.CodeMirror-gutters{position:absolute;left:0;top:0;z-index:3}.CodeMirror-gutter{white-space:normal;height:100%;display:inline-block;margin-bottom:-30px;*zoom:1;*display:inline}.CodeMirror-gutter-wrapper{position:absolute;z-index:4;background:none!important;border:none!important}.CodeMirror-gutter-background{position:absolute;top:0;bottom:0;z-index:4}.CodeMirror-gutter-elt{position:absolute;cursor:default;z-index:4}.CodeMirror-gutter-wrapper{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.CodeMirror-lines{cursor:text;min-height:1px}.CodeMirror pre{border-radius:0;border-width:0;background:transparent;font-family:inherit;font-size:inherit;margin:0;white-space:pre;word-wrap:normal;line-height:inherit;color:inherit;z-index:2;position:relative;overflow:visible;-webkit-tap-highlight-color:transparent}.CodeMirror-wrap pre{word-wrap:break-word;white-space:pre-wrap;word-break:normal}.CodeMirror-linebackground{position:absolute;left:0;right:0;top:0;bottom:0;z-index:0}.CodeMirror-linewidget{position:relative;z-index:2;overflow:auto}.CodeMirror-code{outline:none}.CodeMirror-gutter,.CodeMirror-gutters,.CodeMirror-linenumber,.CodeMirror-scroll,.CodeMirror-sizer{box-sizing:content-box}.CodeMirror-measure{position:absolute;width:100%;height:0;overflow:hidden;visibility:hidden}.CodeMirror-cursor{position:absolute}.CodeMirror-measure pre{position:static}div.CodeMirror-cursors{visibility:hidden;position:relative;z-index:3}.CodeMirror-focused div.CodeMirror-cursors,div.CodeMirror-dragcursors{visibility:visible}.CodeMirror-selected{background:#d9d9d9}.CodeMirror-focused .CodeMirror-selected{background:#d7d4f0}.CodeMirror-crosshair{cursor:crosshair}.CodeMirror-line::selection,.CodeMirror-line>span::selection,.CodeMirror-line>span>span::selection{background:#d7d4f0}.CodeMirror-line::-moz-selection,.CodeMirror-line>span::-moz-selection,.CodeMirror-line>span>span::-moz-selection{background:#d7d4f0}.cm-searching{background:#ffa;background:rgba(255,255,0,.4)}.CodeMirror span{*vertical-align:text-bottom}.cm-force-border{padding-right:.1px}@media print{.CodeMirror div.CodeMirror-cursors{visibility:hidden}}.cm-tab-wrap-hack:after{content:''}span.CodeMirror-selectedtext{background:none}.CodeMirror{height:400px;padding:5px}.index__newPost___UGDJu{padding:5px}.index__newPost___UGDJu button{margin-left:5px}.index__box___sRfME{padding:13px;padding-left:20px}.index__posts___1fCCL{background:#fff;border-radius:3px}.index__panel___2Nuv2{width:100%;height:46px;line-height:46px}.index__header___r7cTO{font-size:13px;color:#343434}.index__header___r7cTO>div{padding-left:10px}.index__time___37rwE{padding-left:5px;border-bottom:1px solid #d0cfcf}.index__actions___3sQJl{float:right;padding-right:20px}.index__text___1iFXw{color:#000!important}.index__actions___3sQJl{font-size:12px}.index__out___yUXHd{color:#444;font-family:Georgia Neue,Hiragino Sans GB,Microsoft Yahei,WenQuanYi Micro Hei,serif;font-size:16px;line-height:1.5em}.index__out___yUXHd a{color:#0645ad;text-decoration:none}.index__out___yUXHd a:visited{color:#0b0080}.index__out___yUXHd a:hover{color:#06e}.index__out___yUXHd a:active{color:#faa700}.index__out___yUXHd a:focus{outline:thin dotted}.index__out___yUXHd a:active,.index__out___yUXHd a:hover{outline:0}.index__out___yUXHd p{margin:1em 0}.index__out___yUXHd img{max-width:100%}.index__out___yUXHd h1,.index__out___yUXHd h2,.index__out___yUXHd h3,.index__out___yUXHd h4,.index__out___yUXHd h5,.index__out___yUXHd h6{font-weight:400;color:#111;line-height:1em}.index__out___yUXHd h4,.index__out___yUXHd h5,.index__out___yUXHd h6{font-weight:700}.index__out___yUXHd h1{font-size:2.5em}.index__out___yUXHd h2{font-size:2em;border-bottom:1px solid silver;padding-bottom:5px}.index__out___yUXHd h3{font-size:1.5em}.index__out___yUXHd h4{font-size:1.2em}.index__out___yUXHd h5{font-size:1em}.index__out___yUXHd h6{font-size:.9em}.index__out___yUXHd blockquote{color:#666;margin:0;padding-left:3em;border-left:.5em solid #eee}.index__out___yUXHd hr{display:block;height:2px;border:0;border-top:1px solid #aaa;border-bottom:1px solid #eee;margin:1em 0;padding:0}.index__out___yUXHd code,.index__out___yUXHd pre{color:#000;font-family:Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:.94em;border-radius:3px;background-color:#f8f8f8}.index__out___yUXHd pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word;padding:5px}.index__out___yUXHd pre code{border:0!important;background:transparent!important;line-height:1.3em}.index__out___yUXHd code{padding:0 3px;display:block}.index__out___yUXHd sub,.index__out___yUXHd sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.index__out___yUXHd sup{top:-.5em}.index__out___yUXHd sub{bottom:-.25em}.index__out___yUXHd ol,.index__out___yUXHd ul{margin:1em 0;padding:0 0 0 2em}.index__out___yUXHd li p:last-child{margin:0}.index__out___yUXHd dd{margin:0 0 0 2em}.index__out___yUXHd img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle}.index__out___yUXHd table{border-collapse:collapse;border-spacing:0}.index__out___yUXHd td,.index__out___yUXHd th{vertical-align:top;padding:4px 10px;border:1px solid #bbb}.index__out___yUXHd tr:nth-child(even) td,.index__out___yUXHd tr:nth-child(even) th{background:#eee}.hljs{display:block;overflow-x:auto;padding:.5em;background:#002b36;color:#839496;-webkit-text-size-adjust:none}.diff .hljs-header,.hljs-comment,.hljs-doctype,.hljs-pi,.lisp .hljs-string{color:#586e75}.css .hljs-tag,.hljs-addition,.hljs-keyword,.hljs-request,.hljs-status,.hljs-winutils,.method,.nginx .hljs-title{color:#859900}.hljs-command,.hljs-doctag,.hljs-hexcolor,.hljs-link_url,.hljs-number,.hljs-regexp,.hljs-rule .hljs-value,.hljs-string,.hljs-tag .hljs-value,.tex .hljs-formula{color:#2aa198}.css .hljs-function,.hljs-built_in,.hljs-chunk,.hljs-decorator,.hljs-id,.hljs-identifier,.hljs-localvars,.hljs-name,.hljs-title,.vhdl .hljs-literal{color:#268bd2}.hljs-attribute,.hljs-class .hljs-title,.hljs-constant,.hljs-link_reference,.hljs-parent,.hljs-type,.hljs-variable,.lisp .hljs-body,.smalltalk .hljs-number{color:#b58900}.css .hljs-pseudo,.diff .hljs-change,.hljs-attr_selector,.hljs-cdata,.hljs-header,.hljs-pragma,.hljs-preprocessor,.hljs-preprocessor .hljs-keyword,.hljs-shebang,.hljs-special,.hljs-subst,.hljs-symbol,.hljs-symbol .hljs-string{color:#cb4b16}.hljs-deletion,.hljs-important{color:#dc322f}.hljs-link_label{color:#6c71c4}.tex .hljs-formula{background:#073642}.index__close___2U2gZ{height:50px;border-bottom:1px solid #96919e}.index__content___2JJNO{padding:10px}.navbar__navbar___1ZvrZ{color:#fff;height:70px;text-align:center;padding-left:200px;line-height:70px;border-bottom:1px solid #e1dbdb}.navbar__logo___2clf8{font-size:26px;font-family:cursive}.navbar__logo___2clf8>a{color:#fff;text-decoration:none}.navbar__left___3SBQ6{float:right;text-align:right;width:200px;font-size:14px}.navbar__left___3SBQ6>ul{list-style:none;margin:0;padding:0}.navbar__left___3SBQ6>ul>li{display:inline-block;float:left;text-align:center;height:100%;width:70px;text-decoration:none;color:#fff;border-bottom:1px solid transparent;box-sizing:border-box;transition:all .2s;cursor:pointer}.navbar__left___3SBQ6>ul>li>a{color:#fff}.navbar__left___3SBQ6>ul>li:hover{color:#b2b3b2;color:#bbb;border-bottom:1px solid #696464}.navbar__login___1sbIl{transition:all .2s}.navbar__create___2er15{display:block}.navbar__show___11gWe{visibility:hidden}.navbar__types___3Ub9Q{margin-left:135px}.navbar__type___1cxjL{position:relative;width:100px;height:100px;float:left;cursor:pointer;margin-right:30px;background:#02af84;border-radius:50%;text-align:center;color:#fff;line-height:100px}.navbar__text___f4Qkb{background:#f98888}.navbar__music___3lOy1{background:coral}.navbar__image___2IQOz{background:#02af84}.index__menu___2S5DL{font-size:22px;margin:50px 0;text-align:center;position:relative}.index__menus___3e4Na{transition:all .5s;position:absolute;top:-40px;left:188px}.index__menus___3e4Na>div>span{color:#fff;display:inline-block;width:80px;position:relative;font-size:17px;cursor:pointer;border-bottom:1px solid transparent}.index__menus___3e4Na>div>span:hover{color:#e6e6e6;border-bottom:1px solid #e6e6e6;transition:all .5s}.index__selectd___2RtAt{display:inline-block;border-radius:3px;background:#4dd0a5;height:39px;width:86px;color:#fff;line-height:37px;font-size:19px}.index__header___2gN8S{text-align:center;margin:20px 0}.index__me___eYYD5{width:114px;border-radius:50%}.index__name___1-Qqg{color:#fff;font-size:42px;margin-top:10px;margin-bottom:10px;font-family:cursive}.index__img___3BMI6{padding:13px;padding-left:20px}.index__cover___30U5N img{max-height:700px;max-width:100%;background-size:cover}.index__text___a5S8t{padding:13px;padding-left:20px}.index__musicBox___1QDyg{background:#fff}.index__music___3P_0Q{padding:13px 0 0 20px}.index__progress___mjSCT{background:#48aeea}.index__progressBar___3hUW3{height:70px;background:#51d5a1}.index__button___2zeqC{position:absolute;top:10px;left:10px}.index__play___pJNVu{float:left;border-radius:50%;border:0;height:50px;width:50px;color:#fff;background:#ff7070;box-shadow:0 1px 2px #706969}.index__text___2JWWj{color:#fff;position:relative;top:15px;left:18px;float:left;width:600px;padding-left:5px}.index__time___2aG4B{float:right}.index__post___9sg2n{margin-bottom:20px;margin-top:20px}.index__loginForm___3nvNY{width:100%;color:#fff;position:relative;margin-top:100px}.index__ctrl___HStLB{width:300px;margin:0 auto}.index__input___gBsPF{margin-top:15px}.index__input___gBsPF input[type=password],.index__input___gBsPF input[type=text]{padding:5px;width:290px;margin-top:3px;color:#444;height:29px;border-radius:2px;border:0}.index__loginBtn___IRjmd{margin-top:10px}.index__views___3F4M_{height:60px;font-size:13px;line-height:55px;color:#444}.index__views___3F4M_>ul{list-style:none;height:100%;padding:0;margin:0}.index__views___3F4M_>ul>li{display:inline-block;width:33.333333%;height:100%;transition:all .4s;border-bottom:1px solid transparent;text-align:center}.index__views___3F4M_>ul>li:hover{border-bottom:1px solid #adaaaa}.index__commentBox___2_-kZ{padding:10px;line-height:2}.index__commentBox___2_-kZ .comments{height:30px;margin:5px 0}.index__commentAction___3hSaK{height:45px;border-bottom:1px solid #d0d0d0}.index__commentPost___1G9hQ{float:right}.index__comment___3UwQV{background:#83c783;border-bottom:2px solid #527152}.index__commentArea___30Qtv{max-width:100%}.index__addCommentBox___27Nex{padding:10px;width:500px;height:130px}
2 | /*# sourceMappingURL=app-921080b3e7a4930d6a9d.css.map*/
--------------------------------------------------------------------------------
/app/dist/css/app-921080b3e7a4930d6a9d.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"css/app-921080b3e7a4930d6a9d.css","sourceRoot":""}
--------------------------------------------------------------------------------
/app/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ddx
6 |
7 |
8 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
397 |
400 |
403 |
406 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
--------------------------------------------------------------------------------
/app/src/README.md:
--------------------------------------------------------------------------------
1 | # 前端开发注意事项
2 |
3 | 判断当前是否登录:
4 | redux@connect取得auth
5 | 使用this.props.auth.logind->boolean
6 |
--------------------------------------------------------------------------------
/app/src/actions/README.md:
--------------------------------------------------------------------------------
1 | 自己写得中间件很low,写起来很麻烦还很复杂,建议用redux-thunk来控制吧
2 |
--------------------------------------------------------------------------------
/app/src/actions/apis.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/21.
3 | */
4 | import axios from 'axios';
5 | const host = __DEVELOPMENT__ ? '//localhost:3000/api' : '/api';
6 |
7 | export default function apis({ url, method, ...others }) {
8 | return axios({
9 | url: host + url,
10 | method: method,
11 | ...others
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/actions/auth.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/28.
3 | */
4 | import {
5 | AUTH_INIT,
6 | AUTH_INIT_SUCCESS,
7 | AUTH_INIT_FAIL,
8 | AUTH_LOGIN,
9 | AUTH_LOGIN_SUCCESS,
10 | AUTH_LOGIN_FAIL,
11 | } from '../constants/auth';
12 | import ajax from './apis';
13 | import tokenDecode from 'jwt-decode';
14 |
15 | /**
16 | * @param {String} token
17 | * */
18 | function decodeUser(token) {
19 | return tokenDecode(token);
20 | }
21 |
22 | export function init(callback) {
23 | return {
24 | types: [AUTH_INIT, AUTH_INIT_SUCCESS, AUTH_INIT_FAIL],
25 | promise: ()=>{
26 | const token = localStorage.getItem('jwt');
27 | if (!token) {
28 | return Promise.resolve({
29 | data: {
30 | error: 'no login'
31 | }
32 | });
33 | }
34 | return ajax({
35 | url: '/valid',
36 | method: 'GET',
37 | headers: {
38 | 'x-access-token': 'Bearer ' + token
39 | }
40 | });
41 | },
42 | after: ()=>{
43 | if (typeof callback === 'function') {
44 | callback();
45 | }
46 | },
47 | onData: result=>{
48 | const { token, error, msg } = result.data;
49 | try {
50 | if (token) {
51 | return {
52 | msg,
53 | user: decodeUser(token).user
54 | };
55 | }
56 | return {
57 | error
58 | };
59 | } catch (e) {
60 | console.warn('token decode error');
61 | return Promise.reject({
62 | data: {
63 | error: 'token decode error' // 有服务器端返回的token,但解析出错,也就是有问题的token
64 | }
65 | });
66 | }
67 | },
68 | onError: error=>{
69 | const err = error.data.error || '初始化失败 ——网络好像出现了问题';
70 | // 验证不通过,没必要将错误显示到页面上
71 | return err;
72 | }
73 | };
74 | }
75 |
76 | export function login(data, callback) {
77 | return {
78 | types: [AUTH_LOGIN, AUTH_LOGIN_SUCCESS, AUTH_LOGIN_FAIL],
79 | promise: ()=>{
80 | return ajax({
81 | url: '/login',
82 | method: 'POST',
83 | data: data
84 | });
85 | },
86 | after: ()=>{
87 | if (typeof callback === 'function') {
88 | callback();
89 | }
90 | },
91 | onData: result=>{
92 | const { token, msg } = result.data;
93 | const { user } = decodeUser(token);
94 | try {
95 | localStorage.setItem('jwt', token);
96 | return {
97 | msg,
98 | user: user
99 | };
100 | } catch (e) {
101 | console.warn('token decode error');
102 | return Promise.reject({
103 | data: {
104 | error: '登陆失败, 请重新尝试' // token decode error
105 | }
106 | });
107 | }
108 | },
109 | onError: error=>{
110 | const err = error.data.error || '登陆失败,请重新尝试 ——网络好像出现了问题';
111 | return err;
112 | }
113 | };
114 | }
115 |
116 | export function isLogin() {
117 | return (dispatch, getState)=> {
118 | const { auth } = getState();
119 | if (auth.logind) {
120 | return true;
121 | }
122 | return false;
123 | };
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/actions/post.js:
--------------------------------------------------------------------------------
1 | import {
2 | POST_INFO_LOAD,
3 | POST_INFO_LOAD_SUCCESS,
4 | POST_INFO_LOAD_FAIL,
5 | POST_COMMENT_CREATE_LOAD,
6 | POST_COMMENT_CREATE_SUCCESS,
7 | POST_COMMENT_CREATE_FAIL
8 | } from 'constants/post';
9 | import ajax from './apis';
10 |
11 | export function loadInfo(_id, callback) {
12 | return {
13 | types: [POST_INFO_LOAD, POST_INFO_LOAD_SUCCESS, POST_INFO_LOAD_FAIL],
14 | promise: ()=> ajax({
15 | url: `/posts/${_id}`,
16 | method: 'GET'
17 | }),
18 | after: ()=>{
19 | if (typeof callback === 'function') {
20 | callback();
21 | }
22 | },
23 | onData: result=>{
24 | return result.data;
25 | },
26 | onError: error=>{
27 | const err = error.data.error || '文章信息加载失败 ——网络好像出现了问题';
28 | return err;
29 | }
30 | };
31 | }
32 | export function onNewComment(comment) {
33 | return {
34 | type: POST_COMMENT_CREATE_SUCCESS,
35 | result: comment
36 | };
37 | }
38 | export function createComment(comment, callback) {
39 | return {
40 | types: [POST_COMMENT_CREATE_LOAD, POST_COMMENT_CREATE_SUCCESS, POST_COMMENT_CREATE_FAIL],
41 | promise: ()=>{
42 | return ajax({
43 | url: `/comments/${comment.post_id}`,
44 | method: 'POST',
45 | data: comment
46 | });
47 | },
48 | before: ()=>{
49 | if (!comment.from) {
50 | return Promise.reject({
51 | data: {
52 | error: '填写不完整,请重试'
53 | }
54 | });
55 | }
56 | },
57 | after: ()=>{
58 | if (typeof callback === 'function') {
59 | callback();
60 | }
61 | },
62 | onData: ()=>{
63 | // 返回提交的comment
64 | return comment;
65 | },
66 | onError: error=>{
67 | const err = error.data.error || '发表评论失败 ——网络好像出现了问题';
68 | return err;
69 | }
70 | };
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/actions/posts.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/28.
3 | */
4 | import {
5 | POSTS_LOAD,
6 | POSTS_LOAD_SUCCESS,
7 | POSTS_LOAD_FAIL,
8 | POSTS_CREATE,
9 | POSTS_CREATE_SUCCESS,
10 | POSTS_CREATE_FAIL
11 | } from '../constants/posts';
12 | import ajax from './apis';
13 |
14 | export function load(showType, callback) {
15 | return {
16 | types: [POSTS_LOAD, POSTS_LOAD_SUCCESS, POSTS_LOAD_FAIL],
17 | promise: ()=> {
18 | return ajax({
19 | url: '/posts',
20 | method: 'GET',
21 | params: {
22 | showType: showType
23 | }
24 | });
25 | },
26 | after: ()=>{
27 | if (typeof callback === 'function') {
28 | callback();
29 | }
30 | },
31 | onData: result=>{
32 | const data = result.data;
33 | return data;
34 | },
35 | onError: error=>{
36 | const err = error.data.error || '加载文章失败 ——网络好像出现了问题';
37 | return err;
38 | }
39 | };
40 | }
41 | export function onNewPost(post) {
42 | return {
43 | type: POSTS_CREATE_SUCCESS,
44 | result: post
45 | };
46 | }
47 | export function createPost(post, callback) {
48 | return {
49 | types: [POSTS_CREATE, POSTS_CREATE_SUCCESS, POSTS_CREATE_FAIL],
50 | promise: ()=> {
51 | return ajax({
52 | url: `/posts`,
53 | method: 'POST',
54 | data: post
55 | });
56 | },
57 | after: (result)=>{
58 | if (typeof callback === 'function') {
59 | callback(result);
60 | }
61 | },
62 | onData: result=>{
63 | return result.data;
64 | },
65 | onError: error=>{
66 | const err = error.data.error || '创建文章失败 ——网络好像出现了问题';
67 | return err;
68 | }
69 | };
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/22.
3 | */
4 | import 'normalize.css';
5 | import 'styles/app.styl';
6 | import React from 'react';
7 | import { render } from 'react-dom';
8 | import { createStore, combineReducers, applyMiddleware, compose, bindActionCreators } from 'redux';
9 | import { Provider } from 'react-redux';
10 | import { Router } from 'react-router';
11 | import { createHistory } from 'history';
12 | import * as authActions from './actions/auth';
13 | import * as reducers from './reducers/index';
14 | import createMiddle from './middleware/createMiddle';
15 | import createRoutes from './routes';
16 |
17 | const history = createHistory();
18 | // const location = history.createLocation(window.location.pathname);
19 | const routes = createRoutes();
20 | const middle = createMiddle();
21 |
22 | const DEVTOOLS = false;
23 | let finalCreateStore;
24 | let reduxTools = null;
25 |
26 | if (DEVTOOLS) {
27 | const { devTools, persistState } = require('redux-devtools');
28 | finalCreateStore = compose(
29 | applyMiddleware(...middle),
30 | devTools(),
31 | persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
32 | )(createStore);
33 | } else {
34 | finalCreateStore = applyMiddleware(...middle)(createStore);
35 | }
36 |
37 | const reducer = combineReducers(reducers);
38 | const store = finalCreateStore(reducer);
39 |
40 | if (DEVTOOLS) {
41 | const { DevTools, DebugPanel, LogMonitor } = require('redux-devtools/lib/react');
42 | // redux 调试工具
43 | reduxTools = (
44 |
45 |
46 |
47 | );
48 | }
49 |
50 | const init = bindActionCreators(authActions, store.dispatch).init;
51 |
52 | const provider = (
53 |
54 |
55 |
56 | {reduxTools}
57 |
58 |
59 | );
60 |
61 | render(provider, document.getElementById('app'));
62 |
63 | // ajax验证用是否登录
64 | init();
65 |
--------------------------------------------------------------------------------
/app/src/components/customEditor/base/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/11/06.
3 | */
4 | import React, { Component, PropTypes as Types } from 'react';
5 | import styles from './styles/index.styl';
6 |
7 | export default class Defaults extends Component {
8 | static PropTypes = {
9 | onClose: Types.func.isRequired,
10 | header: Types.any
11 | }
12 | render() {
13 | return (
14 |
15 |
16 | {this.props.header}
17 |
18 |
19 |
20 | {this.props.children}
21 |
22 |
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/components/customEditor/base/styles/index.styl:
--------------------------------------------------------------------------------
1 | .header
2 | height:40px;
3 | padding: 10px 12px 5px 10px;
4 | border-top-left-radius: 3px;
5 | border-top-right-radius: 3px;
6 | background: #2A9AD8;
7 | color:white;
8 |
9 | .editorArea
10 | margin:0;
11 |
12 | .text
13 | line-height: 35px;
14 |
--------------------------------------------------------------------------------
/app/src/components/customEditor/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/29.
3 | */
4 | import { getTypeComponent } from '../../utils';
5 | import text from './text';
6 |
7 | const components = {
8 | text
9 | };
10 |
11 | export default getTypeComponent(components);
12 |
--------------------------------------------------------------------------------
/app/src/components/customEditor/text/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/28.
3 | */
4 | import React, { Component, PropTypes as Types } from 'react';
5 | import { connect } from 'react-redux';
6 | import { bindActionCreators } from 'redux';
7 | import Defaults from '../base';
8 | import TextArea from '../../textarea';
9 | import * as postsActions from '../../../actions/posts';
10 | import * as authActions from '../../../actions/auth';
11 | import styles from './styles/index.styl';
12 | import { Markdown } from '../../customPosts/base';
13 |
14 | @connect(state=>({
15 | posts: state.posts,
16 | auth: state.auth,
17 | user: state.auth.user,
18 | config: state.auth.config
19 | }), dispatch=>({
20 | postsActions: bindActionCreators(postsActions, dispatch),
21 | authActions: bindActionCreators(authActions, dispatch)
22 | }))
23 | class TextEditor extends Component {
24 | static propTypes = {
25 | posts: Types.object.isRequired,
26 | auth: Types.object.isRequired,
27 | user: Types.object,
28 | config: Types.object.isRequired
29 | }
30 | state = {
31 | isPreview: false,
32 | content: ''
33 | }
34 | onCloseEditor(e) {
35 | // TODO https://github.com/rackt/react-modal/pull/91
36 | if (this.state.content.trim().length > 0) {
37 | if (window.confirm('您有正在编辑的内容确定要关闭吗?')) {
38 | this.props.onClose();
39 | return true;
40 | }
41 | // e.preventDefault()
42 | return false;
43 | }
44 | this.props.onClose();
45 | }
46 | _preview() {
47 | this.setState({
48 | isPreview: !this.state.isPreview
49 | });
50 | }
51 | _post() {
52 | if (!this.props.authActions.isLogin()) {
53 | return alert('请先登录');
54 | }
55 | const newPost = {
56 | content: this.state.content,
57 | type: 'text',
58 | author: {
59 | name: this.props.user.name,
60 | email: this.props.user.email
61 | },
62 | create_time: Date.now()
63 | };
64 | this.props.postsActions.createPost(newPost, (post)=>{
65 | socket.emit('create:post', post);
66 | alert('创建成功');
67 | });
68 | }
69 | update(e) {
70 | this.setState({
71 | content: e.getValue()
72 | });
73 | }
74 | renderHeader() {
75 | return (
76 |
77 | 创建你的新文章
78 |
79 | );
80 | }
81 | render() {
82 | return (
83 |
84 |
85 | {this.state.isPreview ? (
86 |
87 |
88 |
89 | ) : (
90 |
92 | )}
93 |
94 |
95 |
97 |
100 |
101 |
102 | );
103 | }
104 | }
105 |
106 | export default TextEditor;
107 |
--------------------------------------------------------------------------------
/app/src/components/customEditor/text/styles/index.styl:
--------------------------------------------------------------------------------
1 | .newPost
2 | padding:5px;
3 |
4 | .newPost
5 | button
6 | margin-left:5px;
7 |
--------------------------------------------------------------------------------
/app/src/components/customPosts/base/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/30.
3 | */
4 | import React, { Component, PropTypes as Types } from 'react';
5 | import moment from 'moment';
6 | import { connect } from 'react-redux';
7 | import { Link } from 'react-router';
8 | import styles from './styles/index.styl';
9 |
10 | function getMarkdownIt(done) {
11 | require.ensure([], require=>{
12 | const marked = require('marked');
13 | const hljs = require('../../highlight');
14 | // Synchronous highlighting with highlight.js
15 | marked.setOptions({
16 | gfm: true,
17 | tables: true,
18 | breaks: false,
19 | pedantic: false,
20 | sanitize: true,
21 | smartLists: true,
22 | smartypants: false,
23 | highlight: function(code, lang) {
24 | if (lang && hljs.getLanguage(lang)) {
25 | try {
26 | return hljs.highlight(lang, code).value;
27 | } catch (e) {
28 | console.error(e);
29 | }
30 | }
31 | }
32 | });
33 | return done({
34 | hljs,
35 | marked
36 | });
37 | });
38 | }
39 |
40 | class Markdown extends Component {
41 | static propTypes = {
42 | content: Types.string.isRequired
43 | }
44 | state = {
45 | loading: true,
46 | html: {
47 | __html: ''
48 | }
49 | }
50 | componentDidMount() {
51 | getMarkdownIt(({marked})=>{
52 | this.setState({
53 | loading: false
54 | });
55 | const renderMark = (content)=> {
56 | return {
57 | __html: marked(content)
58 | };
59 | };
60 | this.setState({
61 | html: renderMark(this.props.content)
62 | });
63 | });
64 | }
65 | render() {
66 | return (
67 |
68 | {this.state.loading ? (
69 |
loading
70 | ) : (
71 |
72 | )}
73 |
74 | );
75 | }
76 | }
77 |
78 | @connect(state=>({
79 | user: state.auth.user
80 | }))
81 | export default class Defaults extends Component {
82 | static propTypes = {
83 | post: Types.object.isRequired,
84 | params: Types.object,
85 | header: Types.bool,
86 | footer: Types.bool
87 | }
88 | static defaultProps = {
89 | params: {},
90 | header: true,
91 | footer: true
92 | }
93 | renderHeader() {
94 | let style = {};
95 | if (this.props.post.type === 'text') {
96 | style = {
97 | borderBottom: '1px solid #dad3d3'
98 | };
99 | }
100 | const { create_time, _id, author } = this.props.post;
101 | const time = moment(create_time).format('MMMM D - YYYY');
102 | return (
103 |
104 |
105 | {author ? author.name + ' /' : null }
106 | {time}
107 |
108 |
109 |
110 | );
111 | }
112 | renderFooter() {
113 | const { _id } = this.props.post;
114 | return (
115 |
116 |
117 | {!this.props.params._id ? (
118 | 详情
119 | ) : null}
120 |
121 |
122 | );
123 | }
124 | render() {
125 | return (
126 |
127 | {this.props.header ? this.renderHeader() : null}
128 | {this.props.children}
129 | {this.props.footer ? this.renderFooter() : null}
130 |
131 | );
132 | }
133 | }
134 |
135 | export {
136 | Markdown
137 | };
138 |
--------------------------------------------------------------------------------
/app/src/components/customPosts/base/styles/index.styl:
--------------------------------------------------------------------------------
1 | .box
2 | padding:13px;
3 | padding-left: 20px;
4 |
5 | .posts
6 | background: #fff;
7 | border-radius: 3px;
8 |
9 | .panel
10 | width: 100%;
11 | height: 46px;
12 | line-height: 46px;
13 |
14 | .header
15 | & > div
16 | padding-left: 10px;
17 |
18 | font-size: 13px;
19 | color:#343434;
20 |
21 | .time
22 | padding-left: 5px;
23 | border-bottom: 1px solid #d0cfcf;
24 |
25 | .actions
26 | float: right;
27 | padding-right: 20px;
28 |
29 | .text
30 | color: black !important;
31 |
32 | .actions
33 | font-size: 12px;
34 |
35 |
36 | .out
37 | color: #444;
38 | font-family: Georgia Neue,Hiragino Sans GB, Microsoft Yahei, WenQuanYi Micro Hei, serif;
39 | font-size: 16px;
40 | line-height: 1.5em;
41 | a
42 | color: #0645ad;
43 | text-decoration: none;
44 |
45 | a:visited
46 | color: #0b0080;
47 |
48 | a:hover
49 | color: #06e;
50 |
51 | a:active
52 | color: #faa700;
53 |
54 | a:focus
55 | outline: thin dotted;
56 |
57 | a:hover,
58 | a:active
59 | outline: 0;
60 |
61 | p
62 | margin: 1em 0;
63 |
64 | img
65 | max-width: 100%;
66 |
67 | h1,
68 | h2,
69 | h3,
70 | h4,
71 | h5,
72 | h6
73 | font-weight: normal;
74 | color: #111;
75 | line-height: 1em;
76 |
77 | h4,
78 | h5,
79 | h6
80 | font-weight: bold;
81 |
82 | h1
83 | font-size: 2.5em;
84 |
85 | h2
86 | font-size: 2em;
87 | border-bottom: 1px solid silver;
88 | padding-bottom: 5px;
89 |
90 | h3
91 | font-size: 1.5em;
92 |
93 | h4
94 | font-size: 1.2em;
95 |
96 | h5
97 | font-size: 1em;
98 |
99 | h6
100 | font-size: 0.9em;
101 |
102 | blockquote
103 | color: #666;
104 | margin: 0;
105 | padding-left: 3em;
106 | border-left: 0.5em #eee solid;
107 |
108 | hr
109 | display: block;
110 | height: 2px;
111 | border: 0;
112 | border-top: 1px solid #aaa;
113 | border-bottom: 1px solid #eee;
114 | margin: 1em 0;
115 | padding: 0;
116 |
117 | pre,
118 | code
119 | color: #000;
120 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
121 | font-size: 0.94em;
122 | /* 0.94 = 0.88 + (1.00 - 0.88) / 2 */
123 | border-radius: 3px;
124 | background-color: #f8f8f8;
125 |
126 | // border: 1px solid #CCC;
127 |
128 | pre
129 | white-space: pre;
130 | white-space: pre-wrap;
131 | word-wrap: break-word;
132 | padding: 5px;
133 |
134 | pre code
135 | border: 0 !important;
136 | background: transparent !important;
137 | line-height: 1.3em;
138 |
139 | code
140 | padding: 0 3px;
141 | display: block;
142 |
143 | sub,
144 | sup
145 | font-size: 75%;
146 | line-height: 0;
147 | position: relative;
148 | vertical-align: baseline;
149 |
150 | sup
151 | top: -0.5em;
152 |
153 | sub
154 | bottom: -0.25em;
155 |
156 | ul,
157 | ol
158 | margin: 1em 0;
159 | padding: 0 0 0 2em;
160 |
161 | li p:last-child
162 | margin: 0;
163 |
164 | dd
165 | margin: 0 0 0 2em;
166 |
167 | img
168 | border: 0;
169 | -ms-interpolation-mode: bicubic;
170 | vertical-align: middle;
171 |
172 | table
173 | border-collapse: collapse;
174 | border-spacing: 0;
175 |
176 | td,
177 | th
178 | vertical-align: top;
179 | padding: 4px 10px;
180 | border: 1px solid #bbb;
181 |
182 | tr:nth-child(even) td,
183 | tr:nth-child(even) th
184 | background: #eee;
185 |
--------------------------------------------------------------------------------
/app/src/components/customPosts/image/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/30.
3 | */
4 | import React from 'react';
5 | import { Link } from 'react-router';
6 | import Defaults, { Markdown } from '../base';
7 | import styles from './styles/index.styl';
8 |
9 | export default (props)=>{
10 | const { content, cover, _id } = props.post;
11 | return (
12 |
13 |
14 |
15 |

16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/app/src/components/customPosts/image/styles/index.styl:
--------------------------------------------------------------------------------
1 | .img
2 | padding:13px;
3 | padding-left: 20px;
4 |
5 | .cover img
6 | max-height: 700px;
7 | max-width: 100%;
8 | background-size: cover;
9 |
--------------------------------------------------------------------------------
/app/src/components/customPosts/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/30.
3 | */
4 | import { getTypeComponent } from '../../utils';
5 | import image from './image';
6 | import text from './text';
7 | import music from './music';
8 |
9 | const components = {
10 | image,
11 | text,
12 | music
13 | };
14 |
15 | export default getTypeComponent(components);
16 |
--------------------------------------------------------------------------------
/app/src/components/customPosts/music/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/30.
3 | */
4 | import React, { Component, PropTypes as Types } from 'react';
5 | import { Howl } from 'howler';
6 | import { Link } from 'react-router';
7 | import { RotateLoader } from 'halogen';
8 | import { VelocityComponent } from 'velocity-react';
9 | import { Markdown } from '../base';
10 | import base from '../base/styles/index.styl';
11 | import styles from './styles/index.styl';
12 |
13 |
14 | function timeUnitFormat(time) {
15 | if (time < 1) {
16 | return '00';
17 | } else if (time < 10) {
18 | return '0' + time;
19 | }
20 | return time;
21 | }
22 |
23 | function secondsToTime(secs) {
24 | const _secs = Math.round(secs);
25 | const hours = Math.floor(_secs / (60 * 60));
26 | const divisorForMinutes = _secs % (60 * 60);
27 | const minutes = Math.floor(divisorForMinutes / 60);
28 | const divisorForSeconds = divisorForMinutes % 60;
29 | const seconds = Math.ceil(divisorForSeconds);
30 | let time = '';
31 | if (hours > 0) {
32 | time += hours + ':';
33 | }
34 |
35 | time += timeUnitFormat(minutes) + ':';
36 | time += timeUnitFormat(seconds);
37 | return time;
38 | }
39 |
40 |
41 | class Audio extends Component {
42 | static defaultProps = {
43 | src: '',
44 | volume: 0.5
45 | }
46 | state = {
47 | play: false,
48 | pause: false,
49 | loading: false,
50 | seek: 0,
51 | duration: 0,
52 | percent: 0
53 | }
54 | componentWillUnmount() {
55 | if (this.howler) {
56 | this._pause();
57 | this.clearsound();
58 | }
59 | }
60 | initHowler() {
61 | this.setState({
62 | loading: true
63 | });
64 | this.clearsound();
65 | this.howler = new Howl({
66 | src: [this.props.src],
67 | volume: this.props.volume,
68 | onload: this.soundLoadDone.bind(this),
69 | onend: this.playEnd.bind(this)
70 | });
71 | }
72 | soundLoadDone() {
73 | this.setState({
74 | duration: this.howler.duration(),
75 | loading: false
76 | });
77 | this.play();
78 | }
79 | playEnd() {
80 | this.setState({
81 | loading: false,
82 | play: false,
83 | pause: false
84 | });
85 | }
86 | play() {
87 | this.howler.play();
88 | this.interval = setInterval(()=>{
89 | this.setState({
90 | seek: this.howler.seek()
91 | });
92 | if (this.state.seek && this.state.duration) {
93 | const percent = (this.state.seek / this.state.duration) * 100;
94 | this.setState({
95 | percent: percent
96 | });
97 | }
98 | }, 1000);
99 | }
100 | _play() {
101 | this.setState({
102 | play: true,
103 | pause: false
104 | });
105 | if (!this.howler) {
106 | this.initHowler();
107 | } else {
108 | this.play();
109 | }
110 | }
111 | _pause() {
112 | this.setState({
113 | play: false,
114 | pause: true
115 | });
116 | this.howler.pause();
117 | clearInterval(this.interval);
118 | }
119 | clearsound() {
120 | if (this.howler) {
121 | this.howler.stop();
122 | this.howler = null;
123 | }
124 | }
125 | renderButton() {
126 | let text = '';
127 | if (this.state.loading) {
128 | text =
;
129 | } else {
130 | text = this.state.play ? '暂停' : '播放';
131 | }
132 | const percent = this.state.percent + '%';
133 | const seek = secondsToTime(this.state.seek);
134 | const duration = secondsToTime(this.state.duration);
135 | const arrowAnimation = {
136 | width: percent
137 | };
138 | return (
139 |
140 |
141 |
142 |
143 |
144 |
147 |
148 | {this.props.author} - {this.props.title}
149 | {seek + ' / ' + duration}
150 |
151 |
152 |
153 | );
154 | }
155 | render() {
156 | return (
157 |
158 | {this.renderButton()}
159 |
160 | );
161 | }
162 | }
163 |
164 | export default class Music extends Component {
165 | static propTypes = {
166 | post: Types.object.isRequired,
167 | params: Types.object
168 | }
169 | static defaultProps = {
170 | params: {}
171 | }
172 | render() {
173 | const { content, music, _id } = this.props.post;
174 | return (
175 |
176 |
179 |
180 |
181 |
182 |
183 | {!this.props.params._id ? (
184 | 详情
185 | ) : null}
186 |
187 |
188 |
189 |
190 | );
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/app/src/components/customPosts/music/styles/index.styl:
--------------------------------------------------------------------------------
1 | .musicBox
2 | background: #fff;
3 |
4 | .music
5 | padding: 13px 0 0 20px;
6 |
7 | .progress
8 | background: #48AEEA;
9 |
10 | .progressBar
11 | height: 70px;
12 | background: rgb(81, 213, 161);
13 |
14 | .button
15 | position: absolute;
16 | top: 10px;
17 | left: 10px;
18 |
19 | .play
20 | float: left;
21 | border-radius: 50%;
22 | border: 0;
23 | height: 50px;
24 | width: 50px;
25 | color: white;
26 | background: #FF7070;
27 | box-shadow: 0 1px 2px #706969;
28 |
29 | .text
30 | color: white;
31 | position: relative;
32 | top: 15px;
33 | left: 18px;
34 | float: left;
35 | width: 600px;
36 | padding-left:5px;
37 |
38 | .time
39 | float: right;
40 |
--------------------------------------------------------------------------------
/app/src/components/customPosts/text/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/30.
3 | */
4 | import React from 'react';
5 | import Defaults, { Markdown } from '../base';
6 | import styles from './styles/index.styl';
7 |
8 | export default (props)=>{
9 | const { content } = props.post;
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/app/src/components/customPosts/text/styles/index.styl:
--------------------------------------------------------------------------------
1 | .text
2 | padding: 13px;
3 | padding-left: 20px;
4 |
--------------------------------------------------------------------------------
/app/src/components/highlight/index.js:
--------------------------------------------------------------------------------
1 | import hljs from 'highlight.js/lib/highlight.js';
2 | import 'highlight.js/styles/solarized_dark.css';
3 |
4 | // register you languages
5 | hljs.registerLanguage('markdown', require(`highlight.js/lib/languages/markdown`));
6 | hljs.registerLanguage('javascript', require(`highlight.js/lib/languages/javascript`));
7 | hljs.registerLanguage('js', require(`highlight.js/lib/languages/javascript`));
8 | hljs.registerLanguage('css', require(`highlight.js/lib/languages/css`));
9 |
10 | export default hljs;
11 |
--------------------------------------------------------------------------------
/app/src/components/me/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/27.
3 | */
4 | import React, { Component } from 'react';
5 | import styles from './styles/index.styl';
6 |
7 | class Me extends Component {
8 | render() {
9 | return (
10 |
11 |
19 |
20 | );
21 | }
22 | }
23 |
24 | export default Me;
25 |
--------------------------------------------------------------------------------
/app/src/components/me/styles/index.styl:
--------------------------------------------------------------------------------
1 | .header
2 | text-align: center;
3 | margin: 20px 0;
4 |
5 | .me
6 | width: 114px;
7 | border-radius: 50%;
8 |
9 | .name
10 | color: #fff;
11 | font-size: 42px;
12 | margin-top: 10px;
13 | margin-bottom: 10px;
14 | font-family: cursive;
15 |
--------------------------------------------------------------------------------
/app/src/components/modal/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/28.
3 | */
4 | import React, { Component } from 'react';
5 | import { assign } from 'lodash';
6 | import Modal from 'react-modal';
7 | import styles from './styles/index.styl';
8 |
9 |
10 | const createModalStyle = {
11 | overlay: {
12 | backgroundColor: 'rgba(255, 255, 255, 0.73)'
13 | },
14 | content: {
15 | border: 'none',
16 | borderRadius: '3px',
17 | transition: 'all .5s'
18 | }
19 | };
20 |
21 | Modal.setAppElement('#app');
22 |
23 | class createModal extends Component {
24 | render() {
25 | const { children, style, ...others } = this.props;
26 | const styled = assign({}, style, createModalStyle);
27 | return (
28 |
29 |
30 |
31 |
32 |
33 | {children}
34 |
35 |
36 | );
37 | }
38 | }
39 |
40 | const replyModalStyle = {
41 | overlay: {
42 | backgroundColor: 'rgba(255, 255, 255, 0.73)'
43 | },
44 | content: {
45 | border: 'none',
46 | borderRadius: '3px',
47 | transition: 'all .5s',
48 | top: '50%',
49 | left: '50%',
50 | right: 'auto',
51 | bottom: 'auto',
52 | marginLeft: '-20%',
53 | marginTop: '-20%'
54 | }
55 | };
56 | class replyModal extends Component {
57 | render() {
58 | const { children, style, ...others } = this.props;
59 | const styled = assign({}, replyModalStyle, style);
60 | return (
61 |
62 | {children}
63 |
64 | );
65 | }
66 | }
67 | export {
68 | createModal,
69 | replyModal
70 | };
71 | export default replyModal;
72 |
--------------------------------------------------------------------------------
/app/src/components/modal/styles/index.styl:
--------------------------------------------------------------------------------
1 | .close
2 | height:50px;
3 | border-bottom: 1px solid rgb(150, 145, 158);
4 |
5 | .content
6 | padding:10px;
7 |
--------------------------------------------------------------------------------
/app/src/components/more/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes as Types } from 'react';
2 | import cls from 'classnames';
3 | import QueueAnim from 'rc-queue-anim';
4 | import styles from './styles/index.styl';
5 |
6 | class More extends Component {
7 | static propTypes = {
8 | menus: Types.array.isRequired,
9 | done: Types.func.isRequired
10 | }
11 | state = {
12 | show: false,
13 | showType: 0
14 | }
15 | showPanel() {
16 | this.setState({
17 | show: !this.state.show
18 | });
19 | }
20 | _switchType(i) {
21 | const selectType = this.props.menus[i];
22 | this.setState({
23 | showType: i
24 | });
25 | this.props.done(selectType.type);
26 | this.showPanel();
27 | }
28 | render() {
29 | const menus = this.props.menus;
30 | const showType = menus[this.state.showType];
31 | const types = [];
32 | const show = cls({
33 | [styles.menus]: true
34 | });
35 | if (this.state.show ) {
36 | menus.map((k, i)=>{
37 | if (i === 0) {
38 | return;
39 | }
40 | types.push(
41 |
{k.text}
42 | );
43 | });
44 | const reload = (
45 |
47 | Reload
48 |
49 | );
50 | // 将reload放到最后
51 | types.push(reload);
52 | }
53 | return (
54 |
55 |
{showType.text}
56 |
57 | {this.state.show ?
58 |
59 | {types}
60 |
61 | : null}
62 |
63 |
64 | );
65 | }
66 | }
67 |
68 | export default More;
69 |
--------------------------------------------------------------------------------
/app/src/components/more/styles/index.styl:
--------------------------------------------------------------------------------
1 | .menu
2 | font-size: 22px;
3 | margin: 50px 0;
4 | text-align: center;
5 | position: relative;
6 |
7 | .menus
8 | transition: all 0.5s;
9 | position: absolute;
10 | top: -40px;
11 | left: 188px;
12 | & > div > span
13 | color: white;
14 | display: inline-block;
15 | width: 80px;
16 | position: relative;
17 | font-size: 17px;
18 | cursor: pointer;
19 | border-bottom: 1px solid transparent;
20 | &:hover
21 | color: #e6e6e6;
22 | border-bottom: 1px solid #e6e6e6;
23 | transition: all 0.5s;
24 |
25 | .selectd
26 | display: inline-block;
27 | border-radius: 3px;
28 | background: #4dd0a5;
29 | height: 39px;
30 | width: 86px;
31 | color: white;
32 | line-height: 37px;
33 | font-size: 19px;
34 |
--------------------------------------------------------------------------------
/app/src/components/navbar/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/25.
3 | */
4 | import React, { Component, PropTypes as Types } from 'react';
5 | import { connect } from 'react-redux';
6 | import { Link } from 'react-router';
7 | import QueueAnim from 'rc-queue-anim';
8 | import cx from 'classnames';
9 | import getTypeComponent from '../customEditor';
10 | import Modal from '../modal';
11 | import styles from './styles/navbar.styl';
12 |
13 | @connect(state=>({
14 | auth: state.auth,
15 | config: state.auth.config
16 | }))
17 | class Navbar extends Component {
18 | static propTypes = {
19 | auth: Types.object.isRequired,
20 | path: Types.string.isRequired
21 | };
22 | // showEditorType 默认为配置项的第一个
23 | state = {
24 | showCreateModal: false,
25 | showEditorModal: false,
26 | showEditorType: this.props.config.menuTypes[0].type
27 | }
28 | showCreateModal() {
29 | this.setState({
30 | showCreateModal: true
31 | });
32 | }
33 | closeCreateModal() {
34 | this.setState({
35 | showCreateModal: false
36 | });
37 | }
38 | showCreateEditor() {
39 | this.setState({
40 | showEditorModal: true
41 | });
42 | }
43 | closeEditorModal() {
44 | this.setState({
45 | showEditorModal: false
46 | });
47 | }
48 | renderEditor(item) {
49 | this.closeCreateModal();
50 | this.showCreateEditor();
51 | this.setState({
52 | showEditorType: item.type
53 | });
54 | }
55 | renderModal() {
56 | const editorModal = {
57 | overlay: {
58 | backgroundColor: 'rgba(103, 109, 128, 0.73)'
59 | },
60 | content: {
61 | backgroundColor: 'white',
62 | border: 'none',
63 | borderRadius: '5px',
64 | transition: 'all .5s',
65 | width: '600px',
66 | minHeight: '500px',
67 | padding: '0 0 20px 0',
68 | top: '50%',
69 | left: '50%',
70 | right: 'auto',
71 | bottom: 'auto',
72 | marginLeft: '-25%',
73 | marginTop: '-20%'
74 | }
75 | };
76 | const typeModal = {
77 | overlay: {
78 | backgroundColor: 'rgba(103, 109, 128, 0.73)'
79 | },
80 | content: {
81 | backgroundColor: 'transparent',
82 | border: 'none',
83 | borderRadius: '5px',
84 | transition: 'all .5s',
85 | width: '600px',
86 | textAlign: 'center',
87 | padding: '0 0 20px 0',
88 | top: '50%',
89 | left: '50%',
90 | right: 'auto',
91 | bottom: 'auto',
92 | marginLeft: '-25%',
93 | marginTop: '-10%'
94 | }
95 | };
96 | return (
97 |
98 | {this.state.showEditorModal ? (
99 |
102 |
103 | {getTypeComponent(this.state.showEditorType, {onClose: ()=>{
104 | this.closeEditorModal();
105 | }})}
106 |
107 |
108 | ) : null }
109 | {this.state.showCreateModal ? (
110 |
113 |
114 |
115 | {this.props.config.menuTypes.map((k, i)=> {
116 | const { text, type } = k;
117 | return (
118 |
121 | {text}
122 |
123 | );
124 | })}
125 |
126 |
127 |
128 | ) : null }
129 |
130 | );
131 | }
132 | render() {
133 | const show = cx({
134 | [styles.show]: this.props.path === '/login'
135 | });
136 | let nav = '';
137 | if (this.props.auth.loading) {
138 | nav = (
139 |
140 | loading
141 |
142 | );
143 | } else {
144 | nav = [];
145 | if (this.props.auth.logind) {
146 | nav.push(
147 |
148 | +
149 |
150 | );
151 | } else {
152 | nav.push(
153 |
154 | 登陆
155 |
156 | );
157 | }
158 | }
159 | return (
160 |
161 |
162 |
163 | Ddx
164 |
165 |
170 |
171 |
172 | {this.renderModal()}
173 |
174 |
175 | );
176 | }
177 | }
178 | export default Navbar;
179 |
--------------------------------------------------------------------------------
/app/src/components/navbar/styles/navbar.styl:
--------------------------------------------------------------------------------
1 | .navbar
2 | color: #fff;
3 | height: 70px;
4 | text-align: center;
5 | padding-left: 200px;
6 | line-height: 70px;
7 | border-bottom: 1px solid #e1dbdb;
8 |
9 | .logo
10 | font-size: 26px;
11 | font-family: cursive;
12 |
13 | .logo > a
14 | color: #fff;
15 | text-decoration: none;
16 |
17 | .left
18 | float: right;
19 | text-align: right;
20 | width: 200px;
21 | font-size: 14px;
22 |
23 | .left > ul
24 | list-style: none;
25 | margin: 0;
26 | padding: 0;
27 |
28 | .left > ul > li
29 | display: inline-block;
30 | float: left;
31 | text-align: center;
32 | height: 100%;
33 | width: 70px;
34 | text-decoration: none;
35 | color: #fff;
36 | border-bottom: 1px solid transparent;
37 | box-sizing: border-box;
38 | transition: all 0.2s;
39 | cursor: pointer;
40 | & > a{
41 | color: #fff;
42 | };
43 | &:hover
44 | color: rgb(178, 179, 178)
45 |
46 | .left > ul > li:hover
47 | color: #bbb;
48 | border-bottom: 1px solid #696464;
49 |
50 | .login
51 | transition: all 0.2s;
52 |
53 | .create
54 | display: block;
55 |
56 | .show
57 | visibility: hidden;
58 |
59 | .types
60 | margin-left: 135px;
61 |
62 | .type
63 | position: relative;
64 | width: 100px;
65 | height: 100px;
66 | float: left;
67 | cursor: pointer;
68 | margin-right: 30px;
69 | background: #02AF84;
70 | border-radius: 50%;
71 | text-align: center;
72 | color: white;
73 | line-height: 100px;
74 |
75 | .text
76 | background: #F98888;
77 |
78 | .music
79 | background: coral;
80 |
81 | .image
82 | background: #02AF84;
83 |
--------------------------------------------------------------------------------
/app/src/components/navbar/test/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/26.
3 | */
4 |
--------------------------------------------------------------------------------
/app/src/components/post/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/27.
3 | */
4 | import React, {Component, PropTypes as Types} from 'react';
5 | import getTypeComponent from '../customPosts';
6 | import styles from './styles/index.styl';
7 |
8 | export default class Post extends Component {
9 | static propTypes = {
10 | post: Types.object.isRequired,
11 | params: Types.object
12 | }
13 | render() {
14 | const { type } = this.props.post;
15 | return (
16 |
17 | {getTypeComponent(type, this.props)}
18 |
19 | );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/components/post/styles/index.styl:
--------------------------------------------------------------------------------
1 | .post
2 | margin-bottom: 20px;
3 | margin-top: 20px;
4 |
--------------------------------------------------------------------------------
/app/src/components/textarea/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes as Types } from 'react';
2 |
3 | function getCodemirror(done) {
4 | require.ensure([], require=>{
5 | const Codemirror = require('codemirror/lib/codemirror');
6 | require('codemirror/addon/mode/overlay');
7 | require('codemirror/mode/gfm/gfm');
8 | require('codemirror/mode/javascript/javascript');
9 | require('codemirror/mode/markdown/markdown');
10 | require('codemirror/theme/base16-light.css');
11 | require('codemirror/lib/codemirror.css');
12 | require('./styles/index.styl');
13 |
14 | return done({
15 | Codemirror
16 | });
17 | });
18 | }
19 |
20 | function renderEditor(Codemirror, ele) {
21 | return Codemirror.fromTextArea(ele, {
22 | mode: 'gfm',
23 | lineNumbers: false,
24 | matchBrackets: true,
25 | lineWrapping: true,
26 | theme: 'base16-light',
27 | extraKeys: {'Enter': 'newlineAndIndentContinueMarkdownList'}
28 | });
29 | }
30 |
31 | export default class Editor extends Component {
32 | static propTypes = {
33 | content: Types.string.isRequired,
34 | onChange: Types.func.isRequired,
35 | id: Types.string.isRequired
36 | }
37 | state = {
38 | loading: true
39 | }
40 | componentDidMount() {
41 | getCodemirror(({Codemirror})=>{
42 | this.setState({
43 | loading: false
44 | }, ()=>{
45 | if (!this.editor) {
46 | this.editor = renderEditor(Codemirror, this.refs[this.props.id]);
47 | }
48 | this.editor.on('change', (e)=>{
49 | this.props.onChange(e);
50 | });
51 | });
52 | });
53 | }
54 | render() {
55 | return (
56 |
57 | {this.state.loading ? (
58 |
loading
59 | ) : (
60 |
63 | )}
64 |
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/components/textarea/styles/index.styl:
--------------------------------------------------------------------------------
1 | :global .CodeMirror
2 | height: 400px;
3 | padding: 5px;
4 |
--------------------------------------------------------------------------------
/app/src/config/client.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/28.
3 | */
4 | export default {
5 | menuTypes: [{
6 | type: 'text',
7 | text: '文字'
8 | }, {
9 | type: 'image',
10 | text: '照片'
11 | }, {
12 | type: 'music',
13 | text: '音乐'
14 | }]
15 | };
16 |
--------------------------------------------------------------------------------
/app/src/constants/auth.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/28.
3 | */
4 | export const AUTH_INIT = 'AUTH_INIT';
5 | export const AUTH_INIT_SUCCESS = 'AUTH_INIT_SUCCESS';
6 | export const AUTH_INIT_FAIL = 'AUTH_INIT_FAIL';
7 | export const AUTH_LOGIN = 'AUTH_LOGIN';
8 | export const AUTH_LOGIN_SUCCESS = 'AUTH_LOGIN_SUCCESS';
9 | export const AUTH_LOGIN_FAIL = 'AUTH_LOGIN_FAIL';
10 |
--------------------------------------------------------------------------------
/app/src/constants/post.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/09.
3 | */
4 | export const POST_INFO_LOAD = 'POST_INFO_LOAD';
5 | export const POST_INFO_LOAD_SUCCESS = 'POST_INFO_LOAD_SUCCESS';
6 | export const POST_INFO_LOAD_FAIL = 'POST_INFO_LOAD_FAIL';
7 | export const POST_COMMENT_CREATE_LOAD = 'POST_COMMENT_CREATE_LOAD';
8 | export const POST_COMMENT_CREATE_SUCCESS = 'POST_COMMENT_CREATE_SUCCESS';
9 | export const POST_COMMENT_CREATE_FAIL = 'POST_COMMENT_CREATE_FAIL';
10 |
--------------------------------------------------------------------------------
/app/src/constants/posts.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/28.
3 | */
4 | export const POSTS_LOAD = 'POSTS_LOAD';
5 | export const POSTS_LOAD_SUCCESS = 'POSTS_LOAD_SUCCESS';
6 | export const POSTS_LOAD_FAIL = 'POSTS_LOAD_FAIL';
7 | export const POSTS_CREATE = 'POSTS_CREATE';
8 | export const POSTS_CREATE_SUCCESS = 'POSTS_CREATE_SUCCESS';
9 | export const POSTS_CREATE_FAIL = 'POSTS_CREATE_FAIL';
10 |
--------------------------------------------------------------------------------
/app/src/containers/admin/adminContainer/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/30.
3 | */
4 | import React, { Component, PropTypes as Types } from 'react';
5 | import { connect } from 'react-redux';
6 |
7 | @connect(state=>({
8 | auth: state.auth
9 | }))
10 | class AdminContainer extends Component {
11 | static propTypes = {
12 | auth: Types.object.isRequired
13 | }
14 | render() {
15 | return (
16 |
17 | hello admin
18 |
19 | );
20 | }
21 | }
22 | export default AdminContainer;
23 |
--------------------------------------------------------------------------------
/app/src/containers/indexContainer/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/25.
3 | */
4 | import React, { Component, PropTypes as Types } from 'react';
5 | import { bindActionCreators } from 'redux';
6 | import { connect } from 'react-redux';
7 | import QueueAnim from 'rc-queue-anim';
8 | import { ClipLoader } from 'halogen';
9 | import socket from '../../sockets';
10 | import * as postsActions from '../../actions/posts';
11 | import More from '../../components/more';
12 | import Me from '../../components/me';
13 | import Post from '../../components/post';
14 | import base from '../../styles/app.styl';
15 |
16 | @connect(state=>({
17 | posts: state.posts,
18 | config: state.auth.config
19 | }), dispatch=>({
20 | actions: bindActionCreators(postsActions, dispatch)
21 | }))
22 | class IndexContainer extends Component {
23 | static propTypes = {
24 | posts: Types.object.isRequired,
25 | config: Types.object.isRequired
26 | }
27 | state = {
28 | menus: [{
29 | type: 'all',
30 | text: '所有'
31 | }, ...this.props.config.menuTypes]
32 | }
33 | componentDidMount() {
34 | this.loadPosts();
35 | socket.on('new:post', (newPost)=>{
36 | this.props.actions.onNewPost(newPost);
37 | });
38 | }
39 | loadPosts(type) {
40 | this.props.actions.load(type);
41 | }
42 | renderPosts(posts) {
43 | if (posts.length < 1) {
44 | return (
45 |
oh no 啥也木有
46 | );
47 | }
48 | const item = posts.map((post, i )=>{
49 | return (
50 |
53 | );
54 | });
55 | return (
56 |
57 |
60 | {item}
61 |
62 |
63 | );
64 | }
65 | render() {
66 | const posts = this.props.posts.posts;
67 | let content = '';
68 | if (!this.props.posts.posts_loading) {
69 | content = this.renderPosts(posts);
70 | }
71 | return (
72 |
73 |
74 |
75 | this.loadPosts(type) } />
77 |
78 |
79 |
82 | {content}
83 |
84 |
85 | );
86 | }
87 | }
88 | export default IndexContainer;
89 |
--------------------------------------------------------------------------------
/app/src/containers/indexContainer/styles/index.styl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xesrevinu/ddx/fa19191319fad3ecda42512c46dd3921836b8063/app/src/containers/indexContainer/styles/index.styl
--------------------------------------------------------------------------------
/app/src/containers/loginContainer/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/25.
3 | */
4 | import React, { Component, PropTypes as Types } from 'react';
5 | import { connect } from 'react-redux';
6 | import { bindActionCreators } from 'redux';
7 | import * as authActions from '../../actions/auth';
8 | import base from '../../styles/app.styl';
9 | import styles from './styles/index.styl';
10 |
11 | @connect(state=>({
12 | auth: state.auth
13 | }))
14 | class LoginContainer extends Component {
15 | static propTypes = {
16 | auth: Types.object.isRequired
17 | }
18 | componentDidMount() {
19 | const { dispatch } = this.props;
20 | this.authActions = bindActionCreators(authActions, dispatch);
21 | }
22 | login() {
23 | let { username, password } = this.refs;
24 | username = username.value;
25 | password = password.value;
26 | this.authActions.login({
27 | username,
28 | password,
29 | time: new Date()
30 | }, ()=>{
31 | this.props.history.pushState(null, '/');
32 | });
33 | }
34 | render() {
35 | return (
36 |
59 | );
60 | }
61 | }
62 | export default LoginContainer;
63 |
--------------------------------------------------------------------------------
/app/src/containers/loginContainer/styles/index.styl:
--------------------------------------------------------------------------------
1 | .loginForm
2 | width: 100%;
3 | color: white;
4 | position: relative;
5 | margin-top: 100px;
6 |
7 | .ctrl
8 | width: 300px;
9 | margin: 0 auto;
10 |
11 | .input
12 | margin-top: 15px;
13 |
14 | .input
15 | input[type="text"]
16 | input[type="password"]
17 | padding: 5px;
18 | width: 290px;
19 | margin-top: 3px;
20 | color: #444;
21 | height: 29px;
22 | border-radius: 2px;
23 | border: 0;
24 |
25 | .loginBtn
26 | margin-top: 10px;
27 |
--------------------------------------------------------------------------------
/app/src/containers/postContainer/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/27.
3 | */
4 | import React, { Component, PropTypes as Types } from 'react';
5 | import { bindActionCreators } from 'redux';
6 | import { ClipLoader } from 'halogen';
7 | import { connect } from 'react-redux';
8 | import QueueAnim from 'rc-queue-anim';
9 | import { assign } from 'lodash';
10 | import moment from 'moment';
11 | import socket from '../../sockets';
12 | import Post from '../../components/post';
13 | import { replyModal as Modal } from '../../components/modal';
14 | import * as postActions from '../../actions/post';
15 | import base from '../../styles/app.styl';
16 | import styles from './styles/index.styl';
17 |
18 | @connect(state=>({
19 | post: state.post,
20 | auth: state.auth,
21 | user: state.auth.user
22 | }), dispatch=>({
23 | actions: bindActionCreators(postActions, dispatch),
24 | dispatch
25 | }))
26 | class PostContainer extends Component {
27 | static propTypes = {
28 | user: Types.object.isRequired,
29 | auth: Types.object.isRequired,
30 | actions: Types.object.isRequired,
31 | post: Types.object.isRequired,
32 | }
33 | state = {
34 | showComment: false,
35 | k: 0,
36 | reply: {}
37 | }
38 | componentDidMount() {
39 | const { _id } = this.props.params;
40 | this.props.actions.loadInfo(_id);
41 | socket.on('new:comment', (comment)=>{
42 | if (comment.post_id === _id) {
43 | this.props.actions.onNewComment(comment);
44 | }
45 | });
46 | }
47 | _showComment() {
48 | if (!this.props.auth.logind) {
49 | return alert('请先登录');
50 | }
51 | this.setState({
52 | showComment: true
53 | });
54 | }
55 | _comment() {
56 | this._requestComment(this.state.reply, this._closeComment.bind(this));
57 | }
58 | _requestComment(reply, callback) {
59 | const { email, _id, name} = this.props.user;
60 | const { commentContent } = this.refs;
61 | if (!commentContent.value) {
62 | return false;
63 | }
64 | const comment = {
65 | from: name || '',
66 | from_id: _id,
67 | from_email: email,
68 | post_id: this.props.params._id,
69 | content: commentContent.value.trim(),
70 | create_time: Date.now()
71 | };
72 | if (reply.from) comment.to = reply;
73 | this.props.actions.createComment(comment, ()=>{
74 | socket.emit('create:comment', comment);
75 | return callback();
76 | });
77 | }
78 | _closeComment() {
79 | this.setState({
80 | showComment: false
81 | });
82 | }
83 | _replyComment() {
84 | this._requestComment(this.state.reply, this._closeReply.bind(this));
85 | }
86 | _showReply(comment) {
87 | if (!this.props.auth.logind) {
88 | return alert('请先登录');
89 | }
90 | const rep = assign({}, comment);
91 | if (rep.from_id === this.props.user._id) {
92 | return false;
93 | }
94 | delete rep.to;
95 | this.setState({
96 | showReply: true,
97 | reply: rep
98 | });
99 | }
100 | _closeReply() {
101 | this.setState({
102 | showReply: false,
103 | reply: {}
104 | });
105 | }
106 | _tabs(k) {
107 | this.setState({
108 | k: k
109 | });
110 | }
111 | renderComments(comments) {
112 | /* TODO 评论样式 */
113 | if (comments.length < 1) {
114 | return (
115 |
还没有评论~
116 | );
117 | }
118 | return (
119 |
120 | {comments.map((comment, key)=>{
121 | return (
122 |
123 |
124 | {comment.from }: { comment.to ? '@' + comment.to.from : null } {comment.content}
125 | {' '}--{moment(comment.create_time).format('lll')}
126 |
127 |
128 | );
129 | })}
130 |
131 | );
132 | }
133 | render() {
134 | const { error, post } = this.props.post;
135 | let content = null;
136 | let bb = null;
137 | if (this.props.post.post_loading) {
138 | bb =
;
139 | content = bb;
140 | }
141 | if (error) {
142 | bb =
{error}
;
143 | content =
{error}
;
144 | } else {
145 | content =
;
146 | // TODO 登陆验证
147 | const about = post.about.length < 1 ? ' 还没有相关的内容 ' : post.about.map((dd, key)=>{
148 | return (
149 |
150 | {dd}
151 |
152 | );
153 | });
154 | switch (this.state.k) {
155 | case 0:
156 | bb = (
157 |
158 |
159 | {this.state.showComment ? (
160 |
161 | 评论
162 | {post.createComment_loading ? '创建中' : null}
163 | {post.createComment_error}
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 | ) : null}
173 | {this.state.showReply ? (
174 |
175 | 回复-> {this.state.reply.from}
176 |
177 | `{this.state.reply.content}`
178 |
179 | {post.create_comment_loading ? '创建中' : null}
180 | {post.create_comment_error}
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | ) : null }
190 |
191 |
192 | {this.renderComments(post.comments)}
193 |
194 | );
195 | break;
196 | case 1:
197 | bb = (
198 |
199 | 此文章被查看{post.views}次
200 |
201 | );
202 | break;
203 | case 2:
204 | bb = (
205 |
206 | {about}
207 |
208 | );
209 | break;
210 | default:
211 | return null;
212 | }
213 | }
214 | return (
215 |
216 |
217 |
218 | {!this.props.post.post_loading ? (
219 |
222 |
223 | {content}
224 |
225 |
226 |
227 |
228 | - {`评论<${post.comments.length}>`}
229 | - {`浏览<${post.views}> `}
230 | - {`相关<${post.about.length}> `}
231 |
232 |
233 |
234 |
235 | {bb}
236 |
237 |
238 | ) : null}
239 |
240 |
241 | );
242 | }
243 | }
244 | export default PostContainer;
245 |
--------------------------------------------------------------------------------
/app/src/containers/postContainer/styles/index.styl:
--------------------------------------------------------------------------------
1 | .views
2 | height: 60px;
3 | font-size: 13px;
4 | line-height: 55px;
5 | color: #444;
6 |
7 | .views>ul
8 | list-style: none;
9 | height:100%;
10 | padding:0;
11 | margin:0;
12 |
13 | .views>ul>li
14 | display: inline-block;
15 | width:33.333333%;
16 | height:100%;
17 | transition: all 0.4s;
18 | border-bottom: 1px solid transparent;
19 | text-align: center;
20 |
21 | .views>ul>li:hover
22 | border-bottom: 1px solid #ADAAAA;
23 |
24 | .commentBox
25 | padding:10px;
26 | line-height: 2;
27 |
28 | .commentBox :global .comments
29 | height:30px;
30 | margin:5px 0;
31 |
32 | .commentAction
33 | height:45px;
34 | border-bottom: 1px solid #d0d0d0;
35 |
36 | .commentPost
37 | float:right;
38 |
39 | .comment
40 | background: #83C783;
41 | border-bottom: 2px solid #527152;
42 |
43 | .commentArea
44 | max-width: 100%;
45 |
46 | .addCommentBox
47 | padding:10px;
48 | width:500px;
49 | height:130px;
50 |
--------------------------------------------------------------------------------
/app/src/containers/rootContainer/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/27.
3 | */
4 | import 'moment/locale/zh-cn';
5 | import React, { Component, PropTypes as Types } from 'react';
6 | import QueueAnim from 'rc-queue-anim';
7 | import Navbar from '../../components/navbar';
8 |
9 | class RootContainer extends Component {
10 | static propTypes = {
11 | children: Types.element.isRequired
12 | }
13 | render() {
14 | const { pathname } = this.props.location;
15 | return (
16 |
19 |
20 |
21 |
22 |
23 | {this.props.children}
24 |
25 |
29 |
30 | );
31 | }
32 | }
33 | export default RootContainer;
34 |
--------------------------------------------------------------------------------
/app/src/containers/rootContainer/styles/index.styl:
--------------------------------------------------------------------------------
1 | .example-enter
2 | opacity: 0.01;
3 | transition: opacity 0.5s ease-in;
4 |
5 | .example-enter.example-enter-active
6 | opacity: 1;
7 |
8 | .example-leave
9 | opacity: 1;
10 | transition: opacity 0.5s ease-in;
11 |
12 | .example-leave.example-leave-active
13 | opacity: 0;
14 |
--------------------------------------------------------------------------------
/app/src/index.template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {%=o.htmlWebpackPlugin.options.title %}
6 |
7 |
8 |
387 | {% for (var css in o.htmlWebpackPlugin.files.css) { %}
388 |
389 | {% } %}
390 |
391 |
392 |
393 |
394 |
397 |
400 |
403 |
406 |
409 |
410 |
411 | {% for (var chunk in o.htmlWebpackPlugin.files.chunks) { %}
412 |
413 | {% } %}
414 |
415 |
416 |
--------------------------------------------------------------------------------
/app/src/middleware/createMiddle.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/22.
3 | */
4 | import thunk from 'redux-thunk';
5 | import createLogger from 'redux-logger';
6 | import req from './req';
7 |
8 | const logger = createLogger();
9 |
10 | export default ()=>{
11 | const middle = [
12 | req,
13 | thunk
14 | ];
15 | if (__DEVELOPMENT__) {
16 | middle.push(logger);
17 | }
18 | return middle;
19 | };
20 |
--------------------------------------------------------------------------------
/app/src/middleware/req.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/22.
3 | */
4 | export default function req(ref) {
5 | const { dispatch, getState } = ref;
6 | return (next)=> (action)=>{
7 | if (typeof action === 'function') {
8 | return action(dispatch, getState);
9 | }
10 | const { promise, types, before, after, onData, onError, ...others } = action;
11 | if (!promise || !types) {
12 | return next(action);
13 | }
14 | const [REQUEST, SUCCESS, FAILURE] = types;
15 | next({...others, type: REQUEST});
16 | const run = ()=>{
17 | return promise().then((data)=>{
18 | return onData(data);
19 | }).then(result=>{
20 | next({...others, result, type: SUCCESS});
21 | return after ? after(result) : null;
22 | }, err=>{
23 | const error = onError(err);
24 | return next({...others, error, type: FAILURE});
25 | }).catch(err=>{
26 | console.error('MIDDLEWARE ERROR:', err);
27 | });
28 | };
29 | if (before) {
30 | let _before = before({
31 | dispatch,
32 | getState
33 | });
34 | if (!_before) {
35 | _before = Promise.resolve();
36 | }
37 | return _before.then(()=>{
38 | return run();
39 | }, err=>{
40 | const error = onError(err);
41 | return next({...others, error, type: FAILURE});
42 | }).catch(err=>{
43 | console.error('MIDDLEWARE ERROR:', err);
44 | });
45 | }
46 | run();
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/reducers/auth.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/28.
3 | */
4 | import {
5 | AUTH_INIT,
6 | AUTH_INIT_SUCCESS,
7 | AUTH_INIT_FAIL,
8 | AUTH_LOGIN,
9 | AUTH_LOGIN_SUCCESS,
10 | AUTH_LOGIN_FAIL
11 | } from '../constants/auth';
12 | import clientInitialConfig from '../config/client';
13 |
14 | const initialState = {
15 | loading: false,
16 | msg: '',
17 | error: '',
18 | config: clientInitialConfig,
19 | logind: false,
20 | user: {}
21 | };
22 |
23 | export default function auth(state = initialState, actions = {}) {
24 | // result为请求成功时的结果
25 | // 未成功的请求错误都在erro里
26 | const { type, result, error } = actions;
27 | switch (type) {
28 | case AUTH_INIT:
29 | return {
30 | ...state,
31 | loading: true
32 | };
33 | case AUTH_INIT_SUCCESS:
34 | return {
35 | ...state,
36 | loading: false,
37 | msg: result.msg,
38 | error: '',
39 | logind: result.user ? true : false,
40 | user: result.user || {}
41 | };
42 | case AUTH_INIT_FAIL:
43 | return {
44 | ...state,
45 | loading: false,
46 | error: error
47 | };
48 | case AUTH_LOGIN:
49 | return {
50 | ...state,
51 | loading: true
52 | };
53 | case AUTH_LOGIN_SUCCESS:
54 | return {
55 | ...state,
56 | loading: false,
57 | error: '',
58 | logind: true,
59 | msg: result.msg,
60 | user: result.user
61 | };
62 | case AUTH_LOGIN_FAIL:
63 | return {
64 | ...state,
65 | loading: false,
66 | error: error
67 | };
68 | default:
69 | return state;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/28.
3 | */
4 | import posts from './posts';
5 | import post from './post';
6 | import auth from './auth';
7 |
8 | export default { posts, auth, post};
9 |
--------------------------------------------------------------------------------
/app/src/reducers/post.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/19.
3 | */
4 | import {
5 | POST_INFO_LOAD,
6 | POST_INFO_LOAD_SUCCESS,
7 | POST_INFO_LOAD_FAIL,
8 | POST_COMMENT_CREATE_LOAD,
9 | POST_COMMENT_CREATE_SUCCESS,
10 | POST_COMMENT_CREATE_FAIL
11 | } from 'constants/post';
12 |
13 | const initialState = {
14 | post_loading: false,
15 | msg: '',
16 | error: '',
17 | create_comment_loading: false,
18 | create_comment_error: '',
19 | post: {
20 | title: '',
21 | content: '',
22 | comments: [],
23 | views: 0,
24 | about: []
25 | }
26 | };
27 |
28 | export default function post(state = initialState, action = {}) {
29 | const { type, error, result } = action;
30 | switch (type) {
31 | case POST_INFO_LOAD:
32 | return {
33 | ...state,
34 | post_loading: true,
35 | post: {
36 | title: '',
37 | content: '',
38 | comments: [],
39 | views: 0,
40 | about: []
41 | }
42 | };
43 | case POST_INFO_LOAD_SUCCESS:
44 | return {
45 | ...state,
46 | post_loading: false,
47 | msg: result.msg,
48 | error: '',
49 | post: result.post
50 | };
51 | case POST_INFO_LOAD_FAIL:
52 | return {
53 | ...state,
54 | post_loading: false,
55 | msg: '',
56 | error: error
57 | };
58 | case POST_COMMENT_CREATE_LOAD:
59 | return {
60 | ...state,
61 | create_comment_loading: true
62 | };
63 | case POST_COMMENT_CREATE_SUCCESS:
64 | const originPost = state.post;
65 | return {
66 | ...state,
67 | create_comment_loading: false,
68 | create_comment_error: '',
69 | post: {
70 | ...originPost,
71 | comments: [
72 | result,
73 | ...state.post.comments
74 | ]
75 | }
76 | };
77 | case POST_COMMENT_CREATE_FAIL:
78 | return {
79 | ...state,
80 | create_comment_loading: false,
81 | create_comment_error: error
82 | };
83 | default:
84 | return state;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/reducers/posts.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/28.
3 | */
4 | import {
5 | POSTS_LOAD,
6 | POSTS_LOAD_SUCCESS,
7 | POSTS_LOAD_FAIL,
8 | POSTS_CREATE,
9 | POSTS_CREATE_SUCCESS,
10 | POSTS_CREATE_FAIL
11 | } from '../constants/posts';
12 |
13 | const initialState = {
14 | posts_loading: false,
15 | posts_create_loading: false,
16 | msg: '',
17 | error: '',
18 | posts: [],
19 | modTime: new Date()
20 | };
21 |
22 | export default function posts(state = initialState, actions = {} ) {
23 | const { type, result, error } = actions;
24 | switch (type) {
25 | case POSTS_LOAD:
26 | return {
27 | ...state,
28 | posts_loading: true
29 | };
30 | case POSTS_LOAD_SUCCESS:
31 | return {
32 | ...state,
33 | posts_loading: false,
34 | msg: result.msg,
35 | posts: result.posts
36 | };
37 | case POSTS_LOAD_FAIL:
38 | return {
39 | ...state,
40 | posts_loading: false,
41 | error: error
42 | };
43 | case POSTS_CREATE:
44 | return {
45 | ...state,
46 | posts_create_loading: true
47 | };
48 | case POSTS_CREATE_SUCCESS:
49 | return {
50 | ...state,
51 | msg: result.msg,
52 | posts_create_loading: false,
53 | posts: [
54 | result.post,
55 | ...state.posts
56 | ]
57 | };
58 | case POSTS_CREATE_FAIL:
59 | return {
60 | ...state,
61 | posts_create_loading: false,
62 | error: error
63 | };
64 | default:
65 | return state;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/routes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/25.
3 | */
4 | import React from 'react';
5 | import { Route } from 'react-router';
6 | import rootContainer from './containers/rootContainer';
7 | import indexContainer from './containers/indexContainer';
8 | import loginContainer from './containers/loginContainer';
9 | import postContainer from './containers/postContainer';
10 |
11 | // admin
12 | import adminContainer from './containers/admin/adminContainer';
13 |
14 | // let ensureComponent = (component)=> (location, cb)=>{
15 | // require.ensure([], dd=>{
16 | // return cb(null, dd(component));
17 | // });
18 | // };
19 |
20 | export default ()=>{
21 | return (
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/app/src/sockets/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/28.
3 | */
4 | import io from 'socket.io-client';
5 | const socketHost = __DEVELOPMENT__ ? 'http://localhost:3000' : '';
6 | const socket = io(socketHost);
7 |
8 | global.io = io;
9 | global.socket = socket;
10 |
11 | socket.on('connect', ()=>{
12 | // console.log('socket connect');
13 | });
14 | socket.on('disconnect', ()=>{
15 | // console.log('socket connect');
16 | });
17 |
18 | export default socket;
19 |
--------------------------------------------------------------------------------
/app/src/styles/app.styl:
--------------------------------------------------------------------------------
1 | html, body
2 | font-family: Helvetica Neue,Hiragino Sans GB,Microsoft Yahei,WenQuanYi Micro Hei,sans-serif;
3 | height: 100%;
4 | width: 100%;
5 | color: #444;
6 | margin: 0;
7 |
8 | body
9 | background: url("/images/bg.jpg");
10 | background-color: #989898;
11 | background-repeat: no-repeat;
12 | background-attachment: fixed;
13 | background-size: cover;
14 | -webkit-font-smoothing: antialiased;
15 | overflow-y: scroll;
16 | transition: all 0.3s;
17 | overflow-x: hidden;
18 |
19 | a
20 | color: #444;
21 | text-decoration: none;
22 | &:hover,
23 | &:focus
24 | outline: none;
25 | color: #2d2c2c;
26 |
27 | button:hover,
28 | button:focus,
29 | input:hover,
30 | input:focus
31 | outline: none;
32 |
33 | .content
34 | margin-left: auto;
35 | margin-right: auto;
36 | padding: 1px 20px;
37 | max-width: 702px;
38 | min-width: 240px;
39 | margin-bottom: 30px;
40 |
41 | :global .clearify
42 | clear: both;
43 |
44 | :global .box
45 | background: #fff;
46 | border-radius: 3px;
47 | min-height: 50px;
48 |
49 | :global .btn
50 | color: white;
51 | border-radius: 3px;
52 | border: none;
53 | height: 35px;
54 | min-width: 65px;
55 | text-align: center;
56 | text-decoration: none;
57 | position: relative;
58 | display: inline-block;
59 | &:hover,
60 | &:focus
61 | outline: none;
62 | ;
63 | &.btn-success
64 | background: rgb(21, 205, 144);
65 |
66 | &.btn-close
67 | background: #B7B1B2;
68 |
69 | &.btn-done
70 | background: rgb(36, 156, 233);
71 |
72 | :global .textarea
73 | width: 100%;
74 | height: 100px;
75 | border-radius: 3px;
76 | border: 1px solid #ccc;
77 | &:hover
78 | outline: none;
79 |
80 |
81 | :global .input
82 | border-radius: 3px;
83 | border: 1px solid #ccc;
84 | height: 30px;
85 |
86 | :global .ctrl
87 | margin-top: 10px;
88 |
--------------------------------------------------------------------------------
/app/src/utils.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | /**
4 | * 判断组件是否是react的Component
5 | * @param {Function} Component 自定义组件
6 | * @return {Boolean}
7 | */
8 | function isReactComponent(component) {
9 | return !!component.prototype.render;
10 | }
11 |
12 | /**
13 | * 获取对应type的组件
14 | * @param {Array} components
15 | * @return {Function}
16 | */
17 | function getTypeComponent(components) {
18 | return (type, props)=>{
19 | const custom = components[type];
20 | if (!custom) {
21 | return console.warn(`自定义组件${type}不存在`);
22 | }
23 | return isReactComponent(custom) ? (
24 | React.createElement(custom, props)
25 | ) : custom(props);
26 | };
27 | }
28 |
29 | export {
30 | getTypeComponent
31 | };
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ddx",
3 | "version": "1.0.0",
4 | "private": false,
5 | "description": "A Node.js blog using react+redux+react-router",
6 | "main": "server/bin/www.js",
7 | "scripts": {
8 | "server-start": "NODE_ENV=production node ./server/bin/www",
9 | "server-start-win": "set NODE_ENV=production && node ./server/bin/www",
10 | "server-watch": "node_modules/.bin/nodemon -w ./server ./server/bin/www.js",
11 | "client-watch": "node ./webpack/hot.js",
12 | "client-build": "NODE_ENV=production node_modules/.bin/webpack -p --config ./webpack/webpack.prod.config",
13 | "client-build-win": "set NODE_ENV=production && node ./node_modules/webpack/bin/webpack.js -p --config ./webpack/webpack.prod.config"
14 | },
15 | "dependencies": {
16 | "axios": "^0.7.0",
17 | "babel": "6.0.12",
18 | "bluebird": "^2.10.1",
19 | "classnames": "^2.1.3",
20 | "codemirror": "^5.8.0",
21 | "cssnext": "^1.8.4",
22 | "halogen": "^0.1.10",
23 | "highlight.js": "^8.9.1",
24 | "history": "^1.12.3",
25 | "howler": "^2.0.0-beta3",
26 | "jsonwebtoken": "^5.0.4",
27 | "jwt-decode": "^1.4.0",
28 | "koa": "^1.0.0",
29 | "koa-bodyparser": "^2.0.1",
30 | "koa-compress": "^1.0.8",
31 | "koa-cors": "^0.0.16",
32 | "koa-ejs": "^2.0.1",
33 | "koa-logger": "^1.3.0",
34 | "koa-router": "^5.1.2",
35 | "koa-static": "^1.4.9",
36 | "lodash": "^3.10.1",
37 | "marked": "^0.3.5",
38 | "moment": "^2.10.6",
39 | "mongoose": "^4.2.6",
40 | "normalize.css": "^3.0.3",
41 | "rc-queue-anim": "^0.9.0",
42 | "react": "^0.14.3",
43 | "react-dom": "^0.14.1",
44 | "react-modal": "^0.6.1",
45 | "react-redux": "^3.1.0",
46 | "react-router": "^1.0.0-rc2",
47 | "redux": "^3.0.3",
48 | "redux-thunk": "^1.0.0",
49 | "socket.io": "^1.3.7",
50 | "socket.io-client": "^1.3.7",
51 | "velocity-react": "^1.1.1"
52 | },
53 | "devDependencies": {
54 | "autoprefixer": "^6.0.3",
55 | "babel-core": "^5.8.25",
56 | "babel-eslint": "^4.1.3",
57 | "babel-loader": "^5.3.2",
58 | "babel-plugin-react-transform": "^1.1.1",
59 | "babel-runtime": "^6.0.12",
60 | "clean-webpack-plugin": "^0.1.4",
61 | "css-loader": "^0.19.0",
62 | "debug": "^2.2.0",
63 | "eslint": "^1.6.0",
64 | "eslint-config-airbnb": "^0.1.0",
65 | "eslint-loader": "^1.1.1",
66 | "eslint-plugin-import": "^0.8.1",
67 | "eslint-plugin-react": "^3.5.1",
68 | "extract-text-webpack-plugin": "^0.8.2",
69 | "file-loader": "^0.8.4",
70 | "html-webpack-plugin": "^1.6.1",
71 | "json-loader": "^0.5.3",
72 | "nodemon": "^1.7.0",
73 | "postcss-color-rebeccapurple": "^2.0.0",
74 | "postcss-loader": "^0.6.0",
75 | "react-transform-catch-errors": "^1.0.0",
76 | "react-transform-hmr": "^1.0.1",
77 | "redbox-react": "^1.1.1",
78 | "redux-devtools": "^2.1.5",
79 | "redux-logger": "^2.0.1",
80 | "style-loader": "^0.12.4",
81 | "stylus-loader": "^1.4.2",
82 | "url-loader": "^0.5.6",
83 | "webpack": "^1.12.2",
84 | "webpack-dev-server": "^1.12.0",
85 | "webpack-hot-middleware": "^2.4.1"
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/25.
3 | */
4 | import http from 'http';
5 | import koa from 'koa';
6 | import serve from 'koa-static';
7 | import Router from 'koa-router';
8 | import render from 'koa-ejs';
9 | import cors from 'koa-cors';
10 | import parser from 'koa-bodyparser';
11 | import logger from 'koa-logger';
12 | import compress from 'koa-compress';
13 | import path from 'path';
14 |
15 | const app = koa();
16 |
17 | app.experimental = true;
18 | app.use(logger());
19 | app.use(cors());
20 | app.use(parser());
21 | // app.use(compress({
22 | // level: require('zlib').Z_BEST_COMPRESSION
23 | // }));
24 | app.use(serve('./app'));
25 | app.use(serve('./server/assets', {
26 | maxage: 1296000
27 | }));
28 |
29 | render(app, {
30 | root: path.join(__dirname, '..', 'app/dist'),
31 | layout: false,
32 | viewExt: 'html',
33 | debug: false,
34 | cache: true
35 | });
36 |
37 | const api = require('./routes/api');
38 | app.use(api(Router));
39 |
40 | const routes = require('./routes/index');
41 | app.use(routes(Router));
42 |
43 | app.on('error', error=>{
44 | console.error(error);
45 | });
46 |
47 | const server = http.createServer(app.callback());
48 | const io = require('socket.io')(server);
49 |
50 | const sockets = require('./sockets');
51 | sockets(io);
52 |
53 | export default server;
54 |
--------------------------------------------------------------------------------
/server/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xesrevinu/ddx/fa19191319fad3ecda42512c46dd3921836b8063/server/assets/favicon.ico
--------------------------------------------------------------------------------
/server/assets/images/03016_strossmayer_2880x1800.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xesrevinu/ddx/fa19191319fad3ecda42512c46dd3921836b8063/server/assets/images/03016_strossmayer_2880x1800.jpg
--------------------------------------------------------------------------------
/server/assets/images/03333_spinout_2880x1800.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xesrevinu/ddx/fa19191319fad3ecda42512c46dd3921836b8063/server/assets/images/03333_spinout_2880x1800.jpg
--------------------------------------------------------------------------------
/server/assets/images/IMG_5053.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xesrevinu/ddx/fa19191319fad3ecda42512c46dd3921836b8063/server/assets/images/IMG_5053.jpg
--------------------------------------------------------------------------------
/server/assets/images/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xesrevinu/ddx/fa19191319fad3ecda42512c46dd3921836b8063/server/assets/images/bg.jpg
--------------------------------------------------------------------------------
/server/assets/images/me.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xesrevinu/ddx/fa19191319fad3ecda42512c46dd3921836b8063/server/assets/images/me.png
--------------------------------------------------------------------------------
/server/assets/mp3/Hillsong Young And Free - Wake.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xesrevinu/ddx/fa19191319fad3ecda42512c46dd3921836b8063/server/assets/mp3/Hillsong Young And Free - Wake.mp3
--------------------------------------------------------------------------------
/server/assets/mp3/金玟岐 - 腻味.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xesrevinu/ddx/fa19191319fad3ecda42512c46dd3921836b8063/server/assets/mp3/金玟岐 - 腻味.mp3
--------------------------------------------------------------------------------
/server/bin/_init.js:
--------------------------------------------------------------------------------
1 | import { Post, User } from '../db';
2 |
3 | const posts = [
4 | {
5 | 'content': '# 晚安\n\n就酱紫',
6 | 'type': 'text',
7 | 'create_time': '2015-10-29T14:41:17.316Z',
8 | 'author': {
9 | 'name': '肖菀月',
10 | 'email': 'loveyou@qq.com'
11 | }
12 | }, {
13 | 'content': '来上课了,早饭还没吃...饿!',
14 | 'type': 'image',
15 | 'create_time': '2015-10-29T00:31:14.759Z',
16 | 'author': {
17 | 'name': 'xiaokekeT',
18 | 'email': '121441909@qq.com'
19 | },
20 | 'cover': '/images/03333_spinout_2880x1800.jpg'
21 | }, {
22 | 'content': "# ES6有话说\n\n喜欢ES6的新特性,箭头函数(Arrow functions),简单粗暴。 几个特点如下:\n\n- 自动绑定this\n- 更简洁,少打那么几个字母\n- 看上去更牛逼,更函数化\n- 写着也是十分简单,30秒上手\n\n```js\nconst funca = (name, age) =>{\n // es6\n}\n\nconst funca = function(name, age) {\n // 传统 \n}\n```\n\n对吧,新语法看着就很酷炫\n\n```js\nsocket.on('papapa', (data)=>{\n // xx\n})\n\n如果只有一个参数,则还可以省略掉括号,则变成如下形式\nsocket.on('papapa', data=>{\n // xx\n})\n\n//传统\nsocket.on('hehe', function(data) {\n // da\n})\n```\n\n当然了,酷炫不仅如此为什么说会更函数呢? 在单行没有花括号情况下,隐式return\n\n```js\nconst arr = [1,2,3].map(num=> num+1)\n// arr [2,3,4]\n```\n\n恩,差不多就酱紫,自己所用到的也就这些,深感es6特性带来的方便,当然坑是有不少的。",
23 | 'type': 'text',
24 | 'create_time': '2015-10-29T00:21:03.759Z',
25 | 'author': {
26 | 'name': 'xiaokekeT',
27 | 'email': '121441909@qq.com'
28 | },
29 | 'cover': '/images/03333_spinout_2880x1800.jpg'
30 | }, {
31 | 'content': 'Hello',
32 | 'type': 'image',
33 | 'create_time': '2015-10-30T00:54:23.113Z',
34 | 'author': {
35 | 'name': 'xiaokekeT',
36 | 'email': '121441909@qq.com'
37 | },
38 | 'cover': '/images/03016_strossmayer_2880x1800.jpg'
39 | }, {
40 | 'content': '最爱的一首歌, 想去现场High',
41 | 'type': 'music',
42 | 'create_time': '2015-10-30T02:51:32.668Z',
43 | 'author': {
44 | 'name': 'xiaokekeT',
45 | 'email': '121441909@qq.com'
46 | },
47 | 'music': {
48 | 'play': 0,
49 | 'src': '/mp3/Hillsong Young And Free - Wake.mp3',
50 | 'title': 'Wake',
51 | 'author': 'Hillsong Young And Free',
52 | 'cover': ''
53 | }
54 | }, {
55 | 'content': '金大哥',
56 | 'type': 'music',
57 | 'create_time': '2015-10-31T02:37:39.123Z',
58 | 'author': {
59 | 'name': 'xiaokekeT',
60 | 'email': '121441909@qq.com'
61 | },
62 | 'music': {
63 | 'play': 0,
64 | 'src': '/mp3/金玟岐 - 腻味.mp3',
65 | 'title': '腻味',
66 | 'author': '金玟岐',
67 | 'cover': ''
68 | }
69 | }
70 | ];
71 | const user = [
72 | {
73 | 'email': '123123@qq.com',
74 | 'name': 'xiaokekeT',
75 | 'password': '123123'
76 | }, {
77 | 'email': 'loveyou@qq.com',
78 | 'name': '肖菀月',
79 | 'password': '123123'
80 | }
81 | ];
82 |
83 | async function addUser() {
84 | for (const item of user) {
85 | await User.createUser(item);
86 | }
87 | }
88 | async function addPost() {
89 | for (const item of posts) {
90 | await Post.createPost(item);
91 | }
92 | }
93 | (async()=>{
94 | try {
95 | await Promise.all([addUser(), addPost()]);
96 | } catch (e) {
97 | console.error(e);
98 | }
99 | })();
100 |
--------------------------------------------------------------------------------
/server/bin/run.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/21.
3 | */
4 | import app from '../app';
5 | import mongoConnection from '../db/connection';
6 |
7 | (async ()=>{
8 | try {
9 | await mongoConnection();
10 | } catch (e) {
11 | console.error('ERROR:', e);
12 | return;
13 | }
14 | app.listen(3000, '127.0.0.1', ()=>{
15 | console.log('server listen');
16 | });
17 | })();
18 |
--------------------------------------------------------------------------------
/server/bin/server.babel.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | var babelrc = fs.readFileSync('./.babelrc', 'utf-8');
4 | var config;
5 |
6 | try {
7 | config = JSON.parse(babelrc);
8 | } catch (err) {
9 | console.error('==> ERROR: Error parsing your .babelrc.');
10 | console.error(err);
11 | }
12 | require('babel-core/register')(config);
13 |
--------------------------------------------------------------------------------
/server/bin/www.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/25.
3 | */
4 | var arg = process.argv.slice(2);
5 | require('babel-core/register');
6 | require('./run');
7 |
8 | if (arg[0] === 'init'){
9 | require('./_init');
10 | }
11 |
--------------------------------------------------------------------------------
/server/db/Post.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/26.
3 | */
4 | import PostModel from './schema/post';
5 |
6 | /**
7 | * 文章数据操作方法
8 | *
9 | * */
10 | const Post = {
11 | /**
12 | * 获取所有文章
13 | * @param {String} type 文章类型
14 | * @api public
15 | * */
16 | getPosts(type) {
17 | const query = {};
18 | if (type) query.type = type;
19 | const sort = {
20 | _id: -1
21 | };
22 | return PostModel
23 | .find(query, {
24 | about: 0,
25 | comments: 0
26 | })
27 | .sort(sort)
28 | .exec();
29 | },
30 | /**
31 | * 根据id获取某文章信息
32 | * @param {ObjectId} id
33 | * @return {PostSchema} post
34 | * @api public
35 | * */
36 | getPost(id) {
37 | const query = {
38 | _id: id
39 | };
40 | return PostModel
41 | .findOne(query)
42 | .exec();
43 | },
44 | /**
45 | * 创建文章
46 | * @param {Object} body
47 | * @api public
48 | * */
49 | async createPost(body) {
50 | return await new PostModel(body).save();
51 | },
52 | /**
53 | * 增加评论
54 | * @param {ObjectId} id
55 | * @param {Object} comment
56 | * @api public
57 | * */
58 | addComments(id, comment) {
59 | const query = {
60 | _id: id
61 | };
62 | /* TODO push会导致评论会以相反的时间显示出来 */
63 | const update = {
64 | $push: {
65 | comments: comment
66 | }
67 | };
68 | return PostModel
69 | .update(query, update)
70 | .exec();
71 | }
72 | };
73 |
74 | export default Post;
75 |
--------------------------------------------------------------------------------
/server/db/User.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/2.
3 | */
4 | import UserModel from './schema/user';
5 |
6 | /**
7 | * 用户数据操作方法
8 | * */
9 | const User = {
10 | /**
11 | * 获取用户认证信息
12 | * @param {String} email
13 | * @return {UserSchema} user 用户信息
14 | * */
15 | getAuth(email) {
16 | const query = {
17 | email: email
18 | };
19 | return UserModel
20 | .findOne(query, {
21 | isAdmin: 0
22 | })
23 | .exec();
24 | },
25 | /**
26 | * 获取用户信息
27 | * @param {ObjectId} _id
28 | * @return {UserSchema} user 用户信息
29 | * */
30 | getInfo(_id) {
31 | const query = {
32 | _id: _id
33 | };
34 | return UserModel
35 | .findOne(query, {
36 | password: 0
37 | }, {
38 | isAdmin: 0
39 | })
40 | .exec();
41 | },
42 | /**
43 | * 创建新用户
44 | * @param {Object} body
45 | * @return {userSchema} user
46 | * */
47 | async createUser(body) {
48 | return await new UserModel(body).save();
49 | },
50 | /**
51 | * 用户邮箱是否存在
52 | * @param {String} email
53 | * @return {Boolean}
54 | * */
55 | userExist(email) {
56 | const query = {
57 | email: email
58 | };
59 | return UserModel
60 | .findOne(query)
61 | .exec();
62 | },
63 | /**
64 | * 是否为管理员
65 | * @param {ObjectId}
66 | * @return {Boolean}
67 | */
68 | async isAdmin(_id) {
69 | const query = {
70 | _id: _id
71 | };
72 | const _isAdmin = await UserModel
73 | .findOne(query, {
74 | isAdmin: 1
75 | })
76 | .exec();
77 | return _isAdmin ? true : false;
78 | }
79 | };
80 |
81 | export default User;
82 |
--------------------------------------------------------------------------------
/server/db/connection.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/26.
3 | */
4 | import mongoose from 'mongoose';
5 |
6 | export default async ()=> new Promise((resolve, reject)=>{
7 | mongoose.connect('mongodb://localhost/ddxblog', (error)=>{
8 | if (error) {
9 | reject(error.message);
10 | }
11 | resolve();
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/server/db/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/26.
3 | */
4 | import Post from './Post';
5 | import User from './User';
6 |
7 | export default { Post, User };
8 |
--------------------------------------------------------------------------------
/server/db/schema/post.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/26.
3 | */
4 | import mongoose, { Schema } from 'mongoose';
5 |
6 | /**
7 | * 文章模型
8 | * @param {String} title 标题
9 | * @param {String} content 内容
10 | * @param {Number} views 被浏览次数
11 | * @param {Object} author 作者
12 | * @param {Array} comments 评论
13 | * @param {Array} about 相关文章
14 | * @param {String} type 类型
15 | * @param {String} cover 封面图片
16 | * @param {Array} images 图片
17 | * @param {Object} music 音乐
18 | * @param {Date} create_time 创建日期
19 | * */
20 | const PostSchema = new Schema({
21 | title: String,
22 | content: String,
23 | views: {
24 | type: Number,
25 | default: 0
26 | },
27 | author: {
28 | name: '',
29 | email: ''
30 | },
31 | comments: {
32 | type: Array,
33 | default: []
34 | },
35 | about: {
36 | type: Array,
37 | default: []
38 | },
39 | type: {
40 | type: String,
41 | default: 'text'
42 | },
43 | cover: {
44 | type: String,
45 | default: ''
46 | },
47 | images: {
48 | type: Array,
49 | default: []
50 | },
51 | music: {
52 | cover: {
53 | type: String,
54 | default: '' /* TODO 默认封面 */
55 | },
56 | author: {
57 | type: String,
58 | default: ''
59 | },
60 | title: {
61 | type: String,
62 | default: ''
63 | },
64 | src: {
65 | type: String,
66 | default: ''
67 | },
68 | play: {
69 | type: Number,
70 | default: 0
71 | }
72 | },
73 | create_time: {
74 | type: Date,
75 | default: new Date()
76 | }
77 | });
78 |
79 | export default mongoose.model('Post', PostSchema);
80 |
--------------------------------------------------------------------------------
/server/db/schema/user.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/26.
3 | */
4 | import mongoose, { Schema } from 'mongoose';
5 |
6 | /**
7 | * 用户模型
8 | * @param {String} name 昵称
9 | * @param {String} email 邮箱
10 | * @param {String} password 密码
11 | * @param {String} create_time 创建日期
12 | * */
13 | const UserSchema = new Schema({
14 | name: {
15 | type: String,
16 | index: true,
17 | required: true,
18 | trim: true
19 | },
20 | email: {
21 | type: String,
22 | required: true,
23 | index: {
24 | unique: true,
25 | sparse: true
26 | }
27 | },
28 | isAdmin: {
29 | type: String,
30 | default: 0
31 | },
32 | password: {
33 | type: String,
34 | required: true,
35 | trim: true
36 | },
37 | create_time: Date
38 | });
39 |
40 | export default mongoose.model('User', UserSchema);
41 |
--------------------------------------------------------------------------------
/server/routes/api.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/25.
3 | */
4 | import { Post, User } from '../db/index';
5 | import moment from 'moment';
6 | import webtoken from 'jsonwebtoken';
7 |
8 | /**
9 | * Api路由
10 | * @param {Function} Router
11 | * @return {Function}
12 | */
13 | export default function(Router) {
14 | const router = new Router({
15 | prefix: '/api'
16 | });
17 |
18 | router.get('/posts', async function() {
19 | let { showType } = this.query;
20 | if (showType === 'all') {
21 | showType = null;
22 | }
23 | if (showType) {
24 | showType.toLowerCase();
25 | }else {
26 | showType = null;
27 | }
28 | try {
29 | const posts = await Post.getPosts(showType);
30 | this.body = {
31 | posts: posts,
32 | msg: 'ok'
33 | };
34 | }catch (e) {
35 | this.body = {
36 | error: 'error'
37 | };
38 | }
39 | });
40 |
41 | router.post('/posts', async function() {
42 | const body = this.request.body;
43 | try {
44 | const newPost = await Post.createPost(body);
45 | this.body = {
46 | msg: '创建成功',
47 | post: newPost
48 | };
49 | } catch (e) {
50 | console.error(e);
51 | this.body = {
52 | error: 'create error'
53 | };
54 | }
55 | });
56 |
57 | router.get('/posts/:_id', async function() {
58 | const { _id } = this.params;
59 | const post = await Post.getPost(_id);
60 | if (!post) {
61 | this.body = {
62 | error: 'post not found'
63 | };
64 | return;
65 | }
66 | // 增加浏览次数
67 | post.views++;
68 | try {
69 | const body = {
70 | post: post,
71 | msg: 'ok'
72 | };
73 | this.body = body;
74 | post.save();
75 | } catch (e) {
76 | this.status = 403;
77 | this.body = 'err';
78 | }
79 | });
80 |
81 | router.post('/comments/:id', async function() {
82 | const { id } = this.params;
83 | const body = this.request.body;
84 | try {
85 | this.body = await Post.addComments(id, body);
86 | } catch (e) {
87 | console.error(e);
88 | }
89 | });
90 |
91 | router.get('/valid', async function() {
92 | const accessToken = this.headers['x-access-token'];
93 | const token = accessToken.split(' ')[1];
94 | // TODO 验证token过期时间
95 | try {
96 | const decoded = webtoken.verify(token, 'keys');
97 | // 是否过期
98 | if (decoded.exp <= Date.now() ) {
99 | this.body = {
100 | error: '登录状态已过期,请重新登录'
101 | };
102 | return;
103 | }
104 | if (decoded) {
105 | // token is ok
106 | this.body = {
107 | token,
108 | error: '',
109 | msg: '登陆成功'
110 | };
111 | return;
112 | }
113 | } catch (err) {
114 | this.body = {
115 | error: 'no login'
116 | };
117 | }
118 | });
119 |
120 | router.post('/login', async function() {
121 | const body = this.request.body;
122 | const { username, password } = body;
123 | const exist = await User.userExist(username);
124 | if (!exist) {
125 | this.body = {
126 | error: '账号不存在'
127 | };
128 | this.status = 401;
129 | return;
130 | }
131 | const userAuthInfo = await User.getAuth(username);
132 | if (password !== userAuthInfo.password) {
133 | this.body = {
134 | error: '密码错误'
135 | };
136 | this.status = 403;
137 | return;
138 | }
139 | const userInfo = await User.getInfo(userAuthInfo._id);
140 | const playload = {
141 | user: userInfo
142 | };
143 | const expires = moment().add(7, 'days').valueOf();
144 | const token = webtoken.sign(playload, 'keys', {
145 | issuer: userInfo._id,
146 | expiresIn: expires
147 | });
148 | this.body = {
149 | token,
150 | error: '',
151 | msg: '登陆成功'
152 | };
153 | });
154 |
155 |
156 | return router.routes();
157 | }
158 |
--------------------------------------------------------------------------------
/server/routes/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/25.
3 | */
4 | export default function(Router) {
5 | const router = new Router();
6 |
7 | /**
8 | * 首页路由
9 | * @param {Function} Router
10 | * @return {Function}
11 | */
12 | router.get('/', function* () {
13 | yield this.render('index');
14 | });
15 |
16 | router.get('*', function* () {
17 | yield this.render('index');
18 | });
19 |
20 | return router.routes();
21 | }
22 |
--------------------------------------------------------------------------------
/server/sockets/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/10/30.
3 | */
4 | export default (io)=>{
5 |
6 | io.on('connection', (socket)=>{
7 | socket.on('create:comment', (data)=>{
8 | socket.broadcast.emit('new:comment', data);
9 | });
10 | socket.on('create:post', (data)=>{
11 | socket.broadcast.emit('new:post', data);
12 | });
13 | socket.on('disconnect', ()=>{
14 | console.warn('socket disconnect');
15 | });
16 | });
17 |
18 | };
19 |
--------------------------------------------------------------------------------
/webpack/hot.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/25.
3 | */
4 | var webpack = require('webpack');
5 | var path = require('path')
6 | var WebpackDevServer = require('webpack-dev-server');
7 | var config = require('./webpack.dev.config');
8 | var compiler = webpack(config);
9 |
10 | var server = new WebpackDevServer(compiler, {
11 | contentBase: path.join(__dirname, '../dev'),
12 | publicPath: '',
13 | quiet: !true,
14 | noInfo: true,
15 | hot: true,
16 | inline: true,
17 | lazy: false,
18 | historyApiFallback: true,
19 | headers: {
20 | "Access-Control-Allow-Origin": "*"
21 | },
22 | stats: {
23 | colors: true
24 | },
25 | proxy: {
26 | "/api/*": "http://localhost:3000/api",
27 | "/*.*": "http://localhost:3000"
28 | }
29 | });
30 | server.use(require('webpack-hot-middleware')(compiler));
31 | server.listen(4000, 'localhost', function (err) {
32 | if (err) {
33 | console.log(err);
34 | }
35 | console.log('Listening at localhost:4000');
36 | });
37 |
--------------------------------------------------------------------------------
/webpack/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/22.
3 | */
4 | var path = require('path');
5 | var webpack = require('webpack');
6 | var htmlWebpackPlugin = require('html-webpack-plugin');
7 | var host = 4000;
8 |
9 | module.exports = {
10 | devtool: 'inline-source-map',
11 | target: 'web',
12 | debug: false,
13 | context: path.resolve(__dirname, '..'),
14 | entry: {
15 | app: [
16 | 'webpack-hot-middleware/client',
17 | './app/src/app.js'
18 | ]
19 | },
20 | output: {
21 | path: path.join(__dirname, '..', '/app/dev'),
22 | filename: 'js/bundle.js',
23 | publicPath: '//localhost:'+host+'/',
24 | chunkFilename: '[id].bundle.js'
25 | },
26 | plugins: [
27 | new webpack.HotModuleReplacementPlugin(),
28 | new webpack.NoErrorsPlugin(),
29 | new webpack.DefinePlugin({
30 | 'process.env': {
31 | // Useful to reduce the size of client-side libraries, e.g. react
32 | NODE_ENV: JSON.stringify('development')
33 | },
34 | __CLIENT__: true,
35 | __SERVER__: false,
36 | __DEVELOPMENT__: true
37 | }),
38 | new htmlWebpackPlugin({
39 | title: 'Ddx',
40 | filename: 'index.html',
41 | template: './app/src/index.template.html'
42 | })
43 | ],
44 | postcss: [
45 | require('autoprefixer'),
46 | require('postcss-color-rebeccapurple'),
47 | require('cssnext')
48 | ],
49 | resolve: {
50 | modulesDirectories: [
51 | 'src',
52 | 'bower_components',
53 | 'node_modules'
54 | ],
55 | extensions: ['', '.js', '.json']
56 | },
57 | module: {
58 | loaders: [
59 | {
60 | test: /\.js$/,
61 | loaders: ['babel', 'eslint'], exclude: /node_modules/
62 | },
63 | {
64 | test: /\.json$/,
65 | loader: 'json'},
66 | {
67 | test: /\.css$/,
68 | loaders: ['style','css', 'postcss']
69 | },
70 | {
71 | test: /\.styl$/,
72 | loaders: ['style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' ,'stylus']
73 | },
74 | {
75 | test: /\.(png|jpg)$/,
76 | loader: "url-loader?mimetype=image/png"
77 | },
78 | {
79 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
80 | loader: "url?limit=10000&mimetype=application/font-woff"
81 | },
82 | {
83 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
84 | loader: "url?limit=10000&mimetype=application/font-woff"
85 | },
86 | {
87 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
88 | loader: "url?limit=10000&mimetype=application/octet-stream"
89 | },
90 | {
91 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
92 | loader: "file"
93 | },
94 | {
95 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
96 | loader: "url?limit=10000&mimetype=image/svg+xml"
97 | }
98 | ]
99 | }
100 | };
101 |
--------------------------------------------------------------------------------
/webpack/webpack.prod.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kee on 15/9/22.
3 | */
4 | var path = require('path');
5 | var webpack = require('webpack');
6 | var htmlWebpackPlugin = require('html-webpack-plugin');
7 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
8 | var CleanPlugin = require('clean-webpack-plugin');
9 |
10 | module.exports = {
11 | devtool: 'source-map',
12 | target: 'web',
13 | context: path.resolve(__dirname, '..'),
14 | entry: {
15 | lib: [
16 | 'react',
17 | 'react-dom',
18 | 'react-modal',
19 | 'react-redux',
20 | 'react-router',
21 | 'redux',
22 | 'redux-thunk',
23 | 'moment',
24 | 'rc-queue-anim',
25 | 'jwt-decode',
26 | 'halogen'
27 | ],
28 | app: [
29 | './app/src/app.js'
30 | ]
31 | },
32 | output: {
33 | path: path.join(__dirname, '..', '/app/dist'),
34 | filename: 'js/[name]-[chunkhash].js',
35 | publicPath: '/dist/', // or your host
36 | chunkFilename: '[name]-[chunkhash].chunk.js'
37 | },
38 | plugins: [
39 | new webpack.optimize.OccurenceOrderPlugin(),
40 | new webpack.optimize.DedupePlugin(),
41 | new webpack.optimize.UglifyJsPlugin({
42 | compress: {
43 | warnings: false
44 | }
45 | }),
46 | new webpack.DefinePlugin({
47 | 'process.env': {
48 | // Useful to reduce the size of client-side libraries, e.g. react
49 | NODE_ENV: JSON.stringify('production')
50 | },
51 | __CLIENT__: true,
52 | __SERVER__: false,
53 | __DEVELOPMENT__: false
54 | }),
55 | new webpack.optimize.CommonsChunkPlugin({
56 | name: "lib",
57 | minChunks: Infinity
58 | }),
59 | new ExtractTextPlugin('css/[name]-[chunkhash].css', {
60 | allChunks: true
61 | }),
62 | new htmlWebpackPlugin({
63 | title: 'Ddx',
64 | filename: 'index.html',
65 | template: './app/src/index.template.html'
66 | }),
67 | new CleanPlugin(['../app/dist'])
68 | ],
69 | postcss: [
70 | require('autoprefixer'),
71 | require('postcss-color-rebeccapurple'),
72 | require('cssnext')
73 | ],
74 | resolve: {
75 | modulesDirectories: [
76 | 'src',
77 | 'bower_components',
78 | 'node_modules'
79 | ],
80 | extensions: ['', '.json', '.js']
81 | },
82 | module: {
83 | loaders: [
84 | {
85 | test: /\.js$/, loader: 'babel', exclude: /node_modules/
86 | },
87 | {
88 | test: /\.json$/, loader: 'json'
89 | },
90 | {
91 | test: /\.css$/,
92 | loader: ExtractTextPlugin.extract('style', 'css!postcss')
93 | },
94 | {
95 | test: /\.styl$/,
96 | loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!stylus')
97 | },
98 | {
99 | test: /\.(png|jpg)$/,
100 | loader: "url-loader?mimetype=image/png"
101 | },
102 | {
103 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
104 | loader: "url?limit=10000&mimetype=application/font-woff"
105 | },
106 | {
107 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/font-woff"
108 | },
109 | {
110 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=application/octet-stream"
111 | },
112 | {
113 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file"
114 | },
115 | {
116 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: "url?limit=10000&mimetype=image/svg+xml"
117 | }
118 | ]
119 | }
120 | };
121 |
--------------------------------------------------------------------------------