├── .bowerrc ├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── bin └── www ├── bower.json ├── db.js ├── gulpfile.js ├── package.json ├── public └── bundle │ └── bundle.js ├── serve ├── app.js ├── config │ └── index.js ├── controllers │ └── index.js ├── models │ └── index.js ├── register.js └── routes │ └── index.js ├── server ├── config.js ├── model │ ├── Article.js │ ├── User.js │ └── test │ │ ├── Article.js │ │ └── User.js └── routes │ └── index.js ├── webpack.config.js └── www ├── index.jsx └── views ├── app.jsx └── app ├── actions ├── ArticleServerActionCreators.js ├── ArticleViewActionCreators.js ├── AuthServerActionCreators.js └── AuthViewActionCreators.js ├── components ├── About │ └── About.jsx ├── Articles │ ├── Article.jsx │ ├── ArticleItem.jsx │ ├── ArticleItemByTag.jsx │ ├── Articles.jsx │ ├── Tag.jsx │ ├── TagItem.jsx │ └── Tags.jsx ├── Footer │ └── Footer.jsx ├── Home │ └── Home.jsx ├── Index.jsx ├── Login │ └── Login.jsx ├── Nav │ └── Nav.jsx ├── Post │ └── Post.jsx └── Say.jsx ├── constants └── AppConstants.js ├── dispatcher └── AppDispatcher.js ├── router.js ├── stores ├── ArticleStore.js └── AuthStore.js └── utils ├── ajax.js └── webApiUtils.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory" : "public/vendor" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | # webstorm 31 | .idea 32 | 33 | # bin 34 | 35 | # Express public 36 | public/build 37 | public/javascripts 38 | public/vendor 39 | 40 | public/stylesheets/style.min.css 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 zwhu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blog 2 | blog 3 | 4 | 5 | ### 测试 6 | 7 | 在 ./server/model/ 目录下运行测试 8 | 9 | ``` 10 | NODE_ENV=test mocha 11 | ``` -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var session = require("express-session"); 8 | var mongoStore = require("connect-mongo")(session); 9 | 10 | var routes = require('./server/routes/index'); 11 | 12 | var dbConfig = require('./db'); 13 | 14 | var app = express(); 15 | 16 | // view engine setup 17 | app.set('views', [path.join(__dirname, './www'), path.join(__dirname, './www/views') ]); 18 | app.set('view engine', 'jsx'); 19 | 20 | var options = { 21 | jsx: { 22 | harmony: true 23 | } 24 | }; 25 | app.engine('jsx', require('express-react-views').createEngine(options)); 26 | 27 | //app.use(favicon(__dirname + '/public/favicon.ico')); 28 | app.use(logger('dev')); 29 | app.use(bodyParser.json()); 30 | app.use(bodyParser.urlencoded({ extended: false })); 31 | app.use(cookieParser()); 32 | app.use(session({ 33 | secret: 'jackhu.me', 34 | cookie: {maxAge: 1000 * 60 * 60 * 24 * 30}, 35 | key: 'zwhu', 36 | proxy: true, 37 | resave: true, 38 | saveUninitialized: true, 39 | store: new mongoStore({ 40 | db: dbConfig.db, 41 | host: dbConfig.host, 42 | port: dbConfig.port 43 | }) 44 | 45 | })); 46 | 47 | app.use(express.static(path.join(__dirname, 'public'))); 48 | 49 | app.use('/', routes); 50 | 51 | // catch 404 and forward to error handler 52 | app.use(function(req, res, next) { 53 | var err = new Error('Not Found'); 54 | err.status = 404; 55 | next(err); 56 | }); 57 | 58 | // error handlers 59 | 60 | // development error handler 61 | // will print stacktrace 62 | if (app.get('env') === 'development') { 63 | app.use(function(err, req, res, next) { 64 | res.status(err.status || 500); 65 | res.render('error', { 66 | message: err.message, 67 | error: err 68 | }); 69 | }); 70 | } 71 | 72 | // production error handler 73 | // no stacktraces leaked to user 74 | app.use(function(err, req, res, next) { 75 | res.status(err.status || 500); 76 | res.render('error', { 77 | message: err.message, 78 | error: {} 79 | }); 80 | }); 81 | 82 | 83 | module.exports = app; 84 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('blog:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | 16 | var env = app.get('env'); 17 | var port = normalizePort(env === 'production' ? '80' : '3000'); 18 | app.set('port', port); 19 | 20 | /** 21 | * Create HTTP server. 22 | */ 23 | 24 | var server = http.createServer(app); 25 | 26 | 27 | /** 28 | * Listen on provided port, on all network interfaces. 29 | */ 30 | 31 | server.listen(port); 32 | server.on('error', onError); 33 | server.on('listening', onListening); 34 | 35 | /** 36 | * Normalize a port into a number, string, or false. 37 | */ 38 | 39 | function normalizePort(val) { 40 | var port = parseInt(val, 10); 41 | 42 | if (isNaN(port)) { 43 | // named pipe 44 | return val; 45 | } 46 | 47 | if (port >= 0) { 48 | // port number 49 | return port; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | /** 56 | * Event listener for HTTP server "error" event. 57 | */ 58 | 59 | function onError(error) { 60 | if (error.syscall !== 'listen') { 61 | throw error; 62 | } 63 | 64 | var bind = typeof port === 'string' 65 | ? 'Pipe ' + port 66 | : 'Port ' + port; 67 | 68 | // handle specific listen errors with friendly messages 69 | switch (error.code) { 70 | case 'EACCES': 71 | console.error(bind + ' requires elevated privileges'); 72 | process.exit(1); 73 | break; 74 | case 'EADDRINUSE': 75 | console.error(bind + ' is already in use'); 76 | process.exit(1); 77 | break; 78 | default: 79 | throw error; 80 | } 81 | } 82 | 83 | /** 84 | * Event listener for HTTP server "listening" event. 85 | */ 86 | 87 | function onListening() { 88 | var addr = server.address(); 89 | var bind = typeof addr === 'string' 90 | ? 'pipe ' + addr 91 | : 'port ' + addr.port; 92 | debug('Listening on ' + bind); 93 | console.log('Listening on ' + bind); 94 | } 95 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog", 3 | "version": "0.0.1", 4 | "authors": [ 5 | "zwhu " 6 | ], 7 | "description": "blog", 8 | "keywords": [ 9 | "blog" 10 | ], 11 | "license": "MIT", 12 | "homepage": "jackhu.me", 13 | "private": true, 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "public/vendor", 19 | "test", 20 | "tests" 21 | ], 22 | "dependencies": { 23 | "highlight-js": "~7.0.1", 24 | "bootstrap": "~3.3.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /db.js: -------------------------------------------------------------------------------- 1 | var app = require('express')(); 2 | 3 | module.exports = { 4 | MongoClient: require('mongodb').MongoClient, 5 | url: app.get('env') === 'test' ? 'mongodb://localhost:27017/test-blog' : 'mongodb://localhost:27017/blog', 6 | db: app.get('env') === 'test' ? 'test-blog' : 'blog', 7 | host: 'localhost', 8 | port: 27017 9 | }; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 引入 gulp 4 | var gulp = require('gulp'); 5 | 6 | var $ = require('gulp-load-plugins')(); 7 | 8 | // 引入组件 9 | var minifyCSS = require('gulp-minify-css'); 10 | var del = require('del'); 11 | var mocha = require('gulp-mocha'); 12 | var runSequence = require('run-sequence'); 13 | 14 | var argv = require('minimist')(process.argv.slice(2)); 15 | 16 | var webpack = require('webpack'); 17 | 18 | var WebpackDevServer = require('webpack-dev-server'); 19 | 20 | var watch = false; 21 | 22 | // clean 23 | gulp.task('clean', function (cb) { 24 | del(['./public/bundle/', './public/javascripts/**.*', './public/stylesheets/**.css'], cb); 25 | }); 26 | 27 | 28 | // Bundle 29 | gulp.task('bundle', function (cb) { 30 | var started = false; 31 | var config = require('./webpack.config.js'); 32 | var bundler = webpack(config); 33 | var verbose = !!argv.verbose; 34 | 35 | function bundle(err, stats) { 36 | if (err) { 37 | throw new $.util.PluginError('webpack', err); 38 | } 39 | 40 | console.log(stats.toString({ 41 | colors: $.util.colors.supportsColor, 42 | hash: verbose, 43 | version: verbose, 44 | timings: verbose, 45 | chunks: verbose, 46 | chunkModules: verbose, 47 | cached: verbose, 48 | cachedAssets: verbose 49 | })); 50 | 51 | if (!started) { 52 | started = true; 53 | return cb(); 54 | } 55 | } 56 | 57 | if (watch) { 58 | bundler.watch(200, bundle); 59 | } else { 60 | bundler.run(bundle); 61 | } 62 | }); 63 | 64 | // watch 65 | gulp.task('watch', function (cb) { 66 | watch = true; 67 | runSequence('bundle', 'livereload', cb); 68 | }); 69 | 70 | // liveload 71 | gulp.task('livereload', function() { 72 | var server = $.livereload(); 73 | gulp.watch(__dirname + '/public/', function() { 74 | server.changed(file.path); 75 | }); 76 | 77 | }); 78 | 79 | 80 | //// 合并 81 | //gulp.task('concat', ['bundle'], function () { 82 | // gulp.src('./public/javascripts/*.js') 83 | // .pipe($.concat('app.js')) 84 | // .pipe(gulp.dest('./public/javascripts')) 85 | //}); 86 | 87 | // 压缩 js 88 | gulp.task('minify-js', ['bundle'], function () { 89 | gulp.src('./public/bundle/bundle.js') 90 | .pipe($.sourcemaps.init()) 91 | .pipe($.uglify()) 92 | .pipe($.rename('app.min.js')) 93 | .pipe($.sourcemaps.write('../javascripts')) 94 | .pipe(gulp.dest('./public/javascripts')); 95 | }); 96 | 97 | // 压缩 css 98 | gulp.task('minify-css', function () { 99 | gulp.src('./public/stylesheets/style.css') 100 | .pipe(minifyCSS({keepBreaks: true})) 101 | .pipe($.rename('style.min.css')) 102 | .pipe(gulp.dest('./public/stylesheets/')) 103 | }); 104 | 105 | // 测试 106 | gulp.task('test', function () { 107 | process.env.NODE_ENV = 'test'; 108 | return gulp.src('./server/model/test/*.js') 109 | .pipe(mocha({})); 110 | }); 111 | 112 | // 默认任务 113 | gulp.task('default', function (cb) { 114 | runSequence(['watch'], cb); 115 | }); 116 | 117 | // build 118 | gulp.task('build', function (cb) { 119 | runSequence(['clean', 'bundle', 'minify-js', 'minify-css'], cb); 120 | }); 121 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "start": "node ./bin/www" 6 | }, 7 | "dependencies": { 8 | "highlight.js": "^8.4.0", 9 | "keymirror": "^0.1.1", 10 | "marked": "^0.3.3", 11 | "mongoose": "^4.2.8", 12 | "object-assign": "^2.0.0" 13 | }, 14 | "devDependencies": { 15 | "koa": "^1.1.2", 16 | "body-parser": "^1.10.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /serve/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by zwhu on 15/11/30. 3 | */ 4 | -------------------------------------------------------------------------------- /serve/config/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by zwhu on 15/11/30. 3 | */ 4 | -------------------------------------------------------------------------------- /serve/controllers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by zwhu on 15/11/30. 3 | */ 4 | -------------------------------------------------------------------------------- /serve/models/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by zwhu on 15/11/30. 3 | */ 4 | -------------------------------------------------------------------------------- /serve/register.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by zwhu on 15/11/30. 3 | */ 4 | //TODO: babel 5 | -------------------------------------------------------------------------------- /serve/routes/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by zwhu on 15/11/30. 3 | */ 4 | -------------------------------------------------------------------------------- /server/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | title: '测试', 4 | link: '/stylesheets/style.css', 5 | src: '/bundle/bundle.js' 6 | }, 7 | production: { 8 | title: 'JackHu', 9 | link: '/stylesheets/style.min.css', 10 | src: '/javascripts/app.min.js' 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /server/model/Article.js: -------------------------------------------------------------------------------- 1 | var dbConfig = require('./../../db'); 2 | var MongoClient = dbConfig.MongoClient; 3 | var ObjectID = require('mongodb').ObjectID; 4 | var url = dbConfig.url; 5 | 6 | 7 | var assign = require('object-assign'); 8 | 9 | 10 | function Article() { 11 | } 12 | 13 | //存储一篇文章及其相关信息 14 | Article.prototype.post = function (article, callback) { 15 | var date = new Date(); 16 | //存储各种时间格式,方便以后扩展 17 | var time = { 18 | date: date, 19 | year: date.getFullYear(), 20 | month: date.getFullYear() + "-" + (date.getMonth() + 1), 21 | day: date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate(), 22 | minute: date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + 23 | date.getHours() + ":" + (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) 24 | }; 25 | //要存入数据库的文档 26 | var post = { 27 | time: time, 28 | title: article.title, 29 | content: article.content, 30 | tags: article.tags, 31 | summary: article.summary, 32 | titlePic: article.titlePic, 33 | displayContent: article.displayContent 34 | }; 35 | //打开数据库 36 | MongoClient.connect(url, function (err, db) { 37 | if (err) { 38 | return callback(err); 39 | } 40 | //读取 posts 集合 41 | db.collection('articles', function (err, collection) { 42 | if (err) { 43 | db.close(); 44 | return callback(err); 45 | } 46 | //将文档插入 posts 集合 47 | collection.insert(post, { 48 | safe: true 49 | }, function (err) { 50 | db.close(); 51 | if (err) { 52 | return callback(err);//失败!返回 err 53 | } 54 | return callback(null);//返回 err 为 null 55 | }); 56 | }); 57 | }); 58 | }; 59 | 60 | //读取文章及其相关信息 61 | Article.prototype.get = function (id, callback) { 62 | //打开数据库 63 | MongoClient.connect(url, function (err, db) { 64 | if (err) { 65 | return callback(err); 66 | } 67 | //读取 posts 集合 68 | db.collection('articles', function (err, collection) { 69 | if (err) { 70 | db.close(); 71 | return callback(err); 72 | } 73 | var query = {}; 74 | if (id) { 75 | query._id = new ObjectID(id); 76 | } 77 | //根据 query 对象查询文章 78 | collection.find(query).sort({ 79 | time: -1 80 | }).toArray(function (err, docs) { 81 | db.close(); 82 | if (err) { 83 | return callback(err);//失败!返回 err 84 | } 85 | callback(null, docs);//成功!以数组形式返回查询的结果 86 | }); 87 | }); 88 | }); 89 | }; 90 | 91 | 92 | // 读取所有的 tags 93 | Article.prototype.getTags = function(callback) { 94 | //打开数据库 95 | MongoClient.connect(url, function (err, db) { 96 | if (err) { 97 | return callback(err); 98 | } 99 | //读取 posts 集合 100 | db.collection('articles', function (err, collection) { 101 | if (err) { 102 | db.close(); 103 | return callback(err); 104 | } 105 | var query = 'tags'; 106 | collection.distinct(query, function(err, tags) { 107 | db.close(); 108 | if (err) { 109 | return callback(err);//失败!返回 err 110 | } 111 | callback(null, tags);//成功!以数组形式返回查询的结果 112 | }); 113 | }); 114 | }); 115 | }; 116 | 117 | // 通过 tags 读取相应文章 118 | Article.prototype.getByTag = function(tag, callback) { 119 | //打开数据库 120 | MongoClient.connect(url, function (err, db) { 121 | if (err) { 122 | return callback(err); 123 | } 124 | //读取 posts 集合 125 | db.collection('articles', function (err, collection) { 126 | if (err) { 127 | db.close(); 128 | return callback(err); 129 | } 130 | var query = {}; 131 | if (tag) { 132 | query.tags = { 133 | $in: [tag] 134 | }; 135 | } 136 | //根据 query 对象查询文章 137 | collection.find(query).sort({ 138 | time: -1 139 | }).toArray(function (err, docs) { 140 | db.close(); 141 | if (err) { 142 | return callback(err);//失败!返回 err 143 | } 144 | callback(null, docs);//成功!以数组形式返回查询的结果 145 | }); 146 | }); 147 | }); 148 | }; 149 | 150 | 151 | // 除了get方法,其他的article内的方法都必须有权限判断 152 | Article.prototype.update = function(id, update, callback) { 153 | //打开数据库 154 | MongoClient.connect(url, function (err, db) { 155 | if (err) { 156 | return callback(err); 157 | } 158 | //读取 posts 集合 159 | db.collection('articles', function (err, collection) { 160 | if (err) { 161 | db.close(); 162 | return callback(err); 163 | } 164 | var query = {}; 165 | if (id) { 166 | query._id = new ObjectID(id); 167 | } else { 168 | db.close(); 169 | return callback('id 不存在!'); 170 | } 171 | //根据 query 对象查询文章 172 | var set = assign({}, update); 173 | 174 | collection.update(query, { 175 | $set : set 176 | }, function(err) { 177 | db.close(); 178 | if (err) { 179 | return callback(err);//失败!返回 err 180 | } 181 | callback(null); 182 | }); 183 | }); 184 | }); 185 | }; 186 | 187 | 188 | Article.prototype.delete = function(id, callback) { 189 | //打开数据库 190 | MongoClient.connect(url, function (err, db) { 191 | if (err) { 192 | return callback(err); 193 | } 194 | db.collection('articles', function (err, collection) { 195 | if (err) { 196 | db.close(); 197 | return callback(err); 198 | } 199 | var query = {}; 200 | if (id) { 201 | query._id = new ObjectID(id); 202 | } else { 203 | db.close(); 204 | return callback('id 不存在!'); 205 | } 206 | //根据 query 对象查询文章 207 | collection.remove(query, function(err) { 208 | db.close(); 209 | if (err) { 210 | return callback(err);//失败!返回 err 211 | } 212 | callback(null); 213 | }); 214 | }); 215 | }); 216 | }; 217 | 218 | module.exports = Article; -------------------------------------------------------------------------------- /server/model/User.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by huzhengwei on 15/1/27. 3 | */ 4 | var dbConfig = require('./../../db'); 5 | var MongoClient = dbConfig.MongoClient; 6 | var url = dbConfig.url; 7 | 8 | function User() { 9 | }; 10 | 11 | //读取用户信息 12 | User.prototype.get = function (name, callback) { 13 | 14 | MongoClient.connect(url, function (err, db) { 15 | if (err) { 16 | return callback(err);//错误,返回 err 信息 17 | } 18 | 19 | //读取 users 集合 20 | db.collection('user', function (err, collection) { 21 | if (err) { 22 | db.close(); 23 | return callback(err);//错误,返回 err 信息 24 | } 25 | //查找用户名(name键)值为 name 一个文档 26 | collection.findOne({ 27 | name: name 28 | }, function (err, user) { 29 | db.close(); 30 | if (err) { 31 | return callback(err);//失败!返回 err 信息 32 | } 33 | callback(null, user);//成功!返回查询的用户信息 34 | }); 35 | }); 36 | }); 37 | 38 | }; 39 | 40 | //设置token 41 | User.prototype.setToken = function (name, token, callback) { 42 | 43 | MongoClient.connect(url, function (err, db) { 44 | if (err) { 45 | return callback(err);//错误,返回 err 信息 46 | } 47 | 48 | //读取 users 集合 49 | db.collection('user', function (err, collection) { 50 | if (err) { 51 | db.close(); 52 | return callback(err);//错误,返回 err 信息 53 | } 54 | //查找用户名(name键)值为 name 一个文档 55 | collection.update({ 56 | name: name 57 | }, { 58 | $set: { 59 | token: token 60 | } 61 | }, function (err) { 62 | db.close(); 63 | if (err) { 64 | return callback(err);//失败!返回 err 信息 65 | } 66 | callback(null); 67 | }); 68 | }); 69 | }); 70 | 71 | }; 72 | 73 | 74 | module.exports = User; 75 | -------------------------------------------------------------------------------- /server/model/test/Article.js: -------------------------------------------------------------------------------- 1 | var Article = require('../Article'); 2 | 3 | 4 | //TODO: 构建测试环境 5 | // 1. before 向数据库中插入数据 6 | // 2. after 将插入的数据清除 7 | describe('Article 测试', function () { 8 | var article = new Article(); 9 | var _id; 10 | 11 | var posts = { 12 | title : 'test', 13 | content : '这是我的第一篇文章,我一定写的很酷的,你信不信呢', 14 | tags : ['test', 'first'], 15 | titlePic : '/img/test.png', 16 | summary : '我的文章一直都写的很酷的' 17 | }; 18 | 19 | it('Article.post 测试, 应该不返回错误信息', function (done) { 20 | article.post(posts, function(err) { 21 | if(err) { 22 | return done(err); 23 | } 24 | done(); 25 | }); 26 | 27 | }); 28 | 29 | 30 | it('Article.getAll 测试, 应该返回所有Article的基本信息', function (done) { 31 | article.get(null, function(err, docs) { 32 | if(err) { 33 | return done(err); 34 | } else { 35 | if(docs.length) { 36 | _id = docs[0]._id; 37 | done(); 38 | } 39 | } 40 | }); 41 | }); 42 | 43 | 44 | it('Article.getById 测试, 应该返回article的基本信息', function (done) { 45 | if(_id) { 46 | article.get(_id, function(err, docs) { 47 | if(err) { 48 | return done(err); 49 | } else { 50 | if(docs.length) { 51 | done(); 52 | } 53 | } 54 | }); 55 | } 56 | 57 | }); 58 | 59 | 60 | it('Article.update 测试, 应该不返回错误信息', function (done) { 61 | var _update = { 62 | title : 'test-test', 63 | content : '这是我的第一篇文章,我一定写的很酷的,你信不信呢!!!!!!', 64 | tags : ['test', 'first', 'update'], 65 | titlePic : '/img/test.png', 66 | summary : '我的文章一直都写的很酷的' 67 | }; 68 | article.update(_id, _update, function(err) { 69 | if(err) { 70 | return done(err); 71 | } else { 72 | article.get(_id, function(err, docs) { 73 | if(err) { 74 | return done(err); 75 | } else { 76 | if(docs[0].title === _update.title) { 77 | done(); 78 | } 79 | } 80 | }); 81 | } 82 | }) 83 | 84 | }); 85 | 86 | it('Article.getTags 测试, 应该返回所有的tags', function (done) { 87 | article.getTags(function(err, tags) { 88 | if(err) { 89 | return done(err); 90 | } else { 91 | if(tags[2] = 'update') { 92 | done(); 93 | } 94 | } 95 | }); 96 | }); 97 | 98 | 99 | it('Article.getByTag 测试, 应该返回符合tags的所有文章信息', function (done) { 100 | article.getByTag('update', function(err, docs) { 101 | if(err) { 102 | return done(err); 103 | } else { 104 | if(docs.length > 0) { 105 | done(); 106 | } 107 | } 108 | }); 109 | }); 110 | 111 | it('Article.delete 测试, 应该不返回错误信息', function (done) { 112 | article.delete(_id, function(err) { 113 | if(err) { 114 | return done(err); 115 | } else { 116 | article.get(_id, function(err, docs) { 117 | if(err) { 118 | return done(err); 119 | } else { 120 | if(!docs.length) { 121 | done(); 122 | } 123 | } 124 | }); 125 | } 126 | }); 127 | 128 | }); 129 | 130 | }); -------------------------------------------------------------------------------- /server/model/test/User.js: -------------------------------------------------------------------------------- 1 | var dbConfig = require('../../../db'); 2 | var MongoClient = dbConfig.MongoClient; 3 | var url = dbConfig.url; 4 | 5 | var User = require('../User'); 6 | 7 | describe('user 测试', function() { 8 | var user = new User(); 9 | 10 | before(function(done) { 11 | MongoClient.connect(url, function (err, db) { 12 | if (err) { 13 | return done(err); 14 | } 15 | db.collection('user', function (err, collection) { 16 | if (err) { 17 | db.close(); 18 | return done(err); 19 | } 20 | collection.insert({ 21 | name: 'test', 22 | password: '123456' 23 | }, { 24 | safe: true 25 | }, function (err) { 26 | db.close(); 27 | if (err) { 28 | return done(err);//失败!返回 err 29 | } 30 | done();//返回 err 为 null 31 | }); 32 | }); 33 | }); 34 | }); 35 | 36 | 37 | after(function(done) { 38 | MongoClient.connect(url, function(err, db) { 39 | if (err) { 40 | return done(err); 41 | } 42 | db.collection('user', function(err, collection) { 43 | if (err) { 44 | db.close(); 45 | return done(err); 46 | } 47 | collection.remove(function(err) { 48 | db.close(); 49 | if (err) { 50 | return done(err); 51 | } 52 | done(); 53 | }); 54 | }); 55 | }); 56 | }); 57 | 58 | 59 | it('user.get 测试, 应该返回user的基本信息', function(done){ 60 | user.get('test', function(err, obj) { 61 | if(err) { 62 | return done(err); 63 | } else { 64 | if(obj.name === 'test') { 65 | done(); 66 | } 67 | } 68 | }); 69 | }); 70 | 71 | it('user.setToken 测试', function(done) { 72 | user.setToken('test', 'token12345', function(err) { 73 | if(err) { 74 | return done(err); 75 | } else { 76 | user.get('test', function(err, obj) { 77 | if(err) { 78 | return done(err); 79 | } else { 80 | if(obj.token === 'token12345') { 81 | done(); 82 | } 83 | } 84 | }); 85 | } 86 | }); 87 | }) 88 | 89 | }); -------------------------------------------------------------------------------- /server/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var app = express(); 3 | var router = express.Router(); 4 | var marked = require('marked'); 5 | 6 | var User = require('../model/User'); 7 | var Article = require('../model/Article'); 8 | var config = require('../config.js'); 9 | 10 | 11 | /* GET home page. */ 12 | router.get('/', function(req, res, next) { 13 | res.render('index', config[app.get('env') || 'development']); 14 | }); 15 | 16 | router.post('/login', function(req, res, next) { 17 | var user = new User(); 18 | user.get(req.body.name, function(e, v) { 19 | if (!e && v && v.password === req.body.password) { 20 | var token = Math.random().toString(36).substring(7); 21 | user.setToken(req.body.name, token, function(err) { 22 | if(!err) { 23 | return res. 24 | cookie('user', 'zwhu', { maxAge: 24 * 60 * 60 * 10000}). 25 | cookie('token', token, { maxAge: 24 * 60 * 60 * 10000, httpOnly: true }). 26 | status(200). 27 | end(); 28 | } else { 29 | return res.status(404).end(); 30 | } 31 | }); 32 | } else { 33 | return res.status(404).end(); 34 | } 35 | }); 36 | }); 37 | 38 | router.param('id', function(req, res, next, id) { 39 | req.id = id; 40 | next(); 41 | }); 42 | 43 | //TODO: 获取所有帖子的缩略信息 44 | //TODO: 根据id获取帖子的详细信息 45 | //TODO: 对错误统一处理 46 | 47 | router.get('/articles/:id', function(req, res, next) { 48 | var article = new Article(); 49 | article.get(req.id, function(e, v) { 50 | if (!e) { 51 | return res.json(v); 52 | } 53 | //TODO: 以后要对HTTP的请求返回错误做出规范 54 | return res.status(404).end(); 55 | }); 56 | }); 57 | 58 | //TODO: 中间件做 user 的验证 59 | router.delete('/articles/:id', function(req, res, next) { 60 | // 从数据库中取出token 61 | if(!req.cookies.token) { 62 | return res.status(404).end(); 63 | } 64 | 65 | var user = new User(); 66 | user.get(req.cookies.user, function(e, v) { 67 | if(v && v.token !== req.cookies.token) { 68 | return res.status(404).end(); 69 | } 70 | 71 | var article = new Article(); 72 | article.delete(req.id, function(e) { 73 | if (!e) { 74 | return res.status(200).end(); 75 | } 76 | //TODO: 以后要对HTTP的请求返回错误做出规范 77 | return res.status(404).end(); 78 | }); 79 | }); 80 | }); 81 | 82 | router.get('/posts', function(req, res, next) { 83 | var article = new Article(req.article); 84 | 85 | // 取所有 86 | article.get(null, function(e, v) { 87 | if (!e) { 88 | return res.json(v); 89 | } 90 | //TODO: 以后要对HTTP的请求返回错误做出规范 91 | return res.status(404).end(); 92 | }); 93 | }); 94 | 95 | router.post('/posts', function(req, res, next) { 96 | 97 | // 从数据库中取出token 98 | if(!req.cookies.token) { 99 | return res.status(404).end(); 100 | } 101 | 102 | var user = new User(); 103 | 104 | user.get(req.cookies.user, function(e, v) { 105 | if(v && v.token !== req.cookies.token) { 106 | return res.status(404).end(); 107 | } 108 | 109 | if(!req.body) { 110 | return res.status(404).end(); 111 | } 112 | 113 | marked.setOptions({ 114 | highlight: function (code) { 115 | return require('highlight.js').highlightAuto(code).value; 116 | } 117 | }); 118 | 119 | req.body.displayContent = marked(req.body.content); 120 | 121 | var article = new Article(); 122 | article.post(req.body, function(e) { 123 | if (!e) { 124 | return res.status(200).end(); 125 | } 126 | //TODO: 以后要对HTTP的请求返回错误做出规范 127 | return res.status(404).end(); 128 | }); 129 | }); 130 | }); 131 | 132 | router.get('/tags', function(req, res, next) { 133 | var article = new Article(); 134 | article.getTags(function(e, v) { 135 | if (!e) { 136 | return res.json(v); 137 | } 138 | return res.status(404).end(); 139 | }); 140 | }); 141 | 142 | router.param('tag', function(req, res, next, tag) { 143 | req.tag = tag; 144 | next(); 145 | }); 146 | router.get('/tags/:tag', function(req, res, next) { 147 | var article = new Article(); 148 | article.getByTag(req.tag, function(e, v) { 149 | if (!e) { 150 | return res.json(v); 151 | } 152 | return res.status(404).end(); 153 | }); 154 | }); 155 | 156 | module.exports = router; 157 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // webpack.config.js 2 | 3 | 4 | var webpack = require('webpack'); 5 | 6 | 7 | module.exports = { 8 | entry: './www/views/app.jsx', 9 | output: { 10 | filename: './public/bundle/bundle.js' 11 | }, 12 | resolve: { 13 | extensions: ['', '.js', '.jsx'] 14 | }, 15 | module: { 16 | loaders: [ 17 | {test: /\.jsx?$/, loader: 'jsx-loader?harmony'}, 18 | {test: /\.less$/, loader: 'style-loader!css-loader!less-loader'}, // 用 ! 来连接多个人 loader 19 | {test: /\.css$/, loader: 'style-loader!css-loader'}, 20 | {test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'} // 内联 base64 URLs, 限定 <=8k 的图片, 其他的用 URL 21 | ] 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /www/index.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var index = React.createClass({ 4 | render: function() { 5 | return ( 6 | 7 | 8 | {this.props.title} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | }); 20 | 21 | module.exports = index; 22 | -------------------------------------------------------------------------------- /www/views/app.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //Define app architecture here 4 | 5 | var React = require('react'); 6 | var Router = require('react-router'); 7 | var Route = Router.Route; 8 | //var NotFoundRoute = require('Router/NotFoundRoute'); 9 | //var DefaultRoute = require('Router/DefaultRoute'); 10 | var run = Router.run; 11 | var DefaultRoute = Router.DefaultRoute; 12 | 13 | var Index = require('./app/components/Index.jsx'); 14 | //var NotFound = require('./app/components/routes/NotFound.jsx'); 15 | var About = require('./app/components/About/About.jsx'); 16 | var Login = require('./app/components/Login/Login.jsx'); 17 | var Home = require('./app/components/Home/Home.jsx'); 18 | var Post = require('./app/components/Post/Post.jsx'); 19 | var Articles = require('./app/components/Articles/Articles.jsx'); 20 | var Article = require('./app/components/Articles/Article.jsx'); 21 | var Tags = require('./app/components/Articles/Tags.jsx'); 22 | var ArticleItemByTag = require('./app/components/Articles/ArticleItemByTag.jsx'); 23 | var ArticleItem = require('./app/components/Articles/ArticleItem.jsx'); 24 | 25 | var routes = ( 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | 41 | 42 | run(routes, function (Handler) { 43 | React.render(, document.body); 44 | }); 45 | -------------------------------------------------------------------------------- /www/views/app/actions/ArticleServerActionCreators.js: -------------------------------------------------------------------------------- 1 | var AppDispatcher = require('../dispatcher/AppDispatcher'); 2 | var AppConstants = require('../constants/AppConstants'); 3 | 4 | 5 | var ArticleServerActionCreators = { 6 | getArticlesSuccess: function(data) { 7 | AppDispatcher.handleServerAction({ 8 | actionType: AppConstants.GET_ARTICLES_SUCCESS, 9 | data: data 10 | }); 11 | }, 12 | getArticlesFail: function() { 13 | AppDispatcher.handleServerAction({ 14 | actionType: AppConstants.GET_ARTICLES_FAIL 15 | }); 16 | }, 17 | getArticleSuccess: function(data) { 18 | AppDispatcher.handleServerAction({ 19 | actionType: AppConstants.GET_ARTICLE_SUCCESS, 20 | data: data 21 | }); 22 | }, 23 | getArticleFail: function() { 24 | AppDispatcher.handleServerAction({ 25 | actionType: AppConstants.GET_ARTICLES_FAIL 26 | }); 27 | }, 28 | postArticleSuccess: function() { 29 | AppDispatcher.handleServerAction({ 30 | actionType: AppConstants.POST_ARTICLE_SUCCESS 31 | }) 32 | }, 33 | postArticleFail: function(errMsg) { 34 | AppDispatcher.handleServerAction({ 35 | actionType: AppConstants.POST_ARTICLE_FAIL, 36 | errMsg: errMsg 37 | }) 38 | }, 39 | getTagsSuccess: function(data) { 40 | AppDispatcher.handleServerAction({ 41 | actionType: AppConstants.GET_TAGS_SUCCESS, 42 | data: data 43 | }) 44 | }, 45 | getTagsFail: function() { 46 | AppDispatcher.handleServerAction({ 47 | actionType: AppConstants.GET_TAGS_FAIL 48 | }) 49 | }, 50 | getByTagSuccess: function(data) { 51 | AppDispatcher.handleServerAction({ 52 | actionType: AppConstants.GET_BY_TAG_SUCCESS, 53 | data: data 54 | }) 55 | }, 56 | getByTagFail: function() { 57 | AppDispatcher.handleServerAction({ 58 | actionType: AppConstants.GET_BY_TAG_FAIL 59 | }) 60 | } 61 | }; 62 | 63 | 64 | module.exports = ArticleServerActionCreators; -------------------------------------------------------------------------------- /www/views/app/actions/ArticleViewActionCreators.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by huzhengwei on 15/2/7. 3 | */ 4 | var AppDispatcher = require('../dispatcher/AppDispatcher'); 5 | var AppConstants = require('../constants/AppConstants'); 6 | var webApiUtils = require('../utils/webApiUtils'); 7 | 8 | var ArticleViewActionCreators = { 9 | getArticles: function() { 10 | AppDispatcher.handleViewAction({ 11 | actionType: AppConstants.GET_ARTICLES 12 | }); 13 | webApiUtils.getArticles(); 14 | }, 15 | getArticle: function(id) { 16 | AppDispatcher.handleViewAction({ 17 | actionType: AppConstants.GET_ARTICLE, 18 | id: id 19 | }); 20 | webApiUtils.getArticle(id); 21 | }, 22 | postArticle: function(data) { 23 | AppDispatcher.handleViewAction({ 24 | actionType: AppConstants.POST_ARTICLE, 25 | data: data 26 | }); 27 | webApiUtils.postArtilces(data); 28 | }, 29 | getTags: function() { 30 | AppDispatcher.handleViewAction({ 31 | actionType: AppConstants.GET_TAGS 32 | }); 33 | webApiUtils.getTags(); 34 | }, 35 | getByTag: function(tag) { 36 | AppDispatcher.handleViewAction({ 37 | actionType: AppConstants.GET_BY_TAG 38 | }); 39 | webApiUtils.getByTag(tag); 40 | } 41 | }; 42 | 43 | 44 | module.exports = ArticleViewActionCreators; 45 | -------------------------------------------------------------------------------- /www/views/app/actions/AuthServerActionCreators.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by huzhengwei on 15/1/27. 3 | */ 4 | var AppDispatcher = require('../dispatcher/AppDispatcher'); 5 | var AppConstants = require('../constants/AppConstants'); 6 | 7 | var AuthServerActionCreators = { 8 | signinSuccess: function() { 9 | AppDispatcher.handleServerAction({ 10 | actionType: AppConstants.SIGNIN_SUCCESS 11 | }); 12 | }, 13 | signinFail: function(errMsg) { 14 | AppDispatcher.handleServerAction({ 15 | actionType: AppConstants.SIGNIN_FAIL, 16 | errMsg: errMsg 17 | }); 18 | } 19 | 20 | }; 21 | 22 | 23 | module.exports = AuthServerActionCreators; -------------------------------------------------------------------------------- /www/views/app/actions/AuthViewActionCreators.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by huzhengwei on 15/1/27. 3 | */ 4 | var AppDispatcher = require('../dispatcher/AppDispatcher'); 5 | var AppConstants = require('../constants/AppConstants'); 6 | var webApiUtils = require('../utils/webApiUtils'); 7 | 8 | 9 | var AuthViewActionCreators = { 10 | signin: function(data) { 11 | AppDispatcher.handleViewAction({ 12 | actionType: AppConstants.SIGNIN, 13 | data: data 14 | }); 15 | webApiUtils.signin(data); 16 | } 17 | }; 18 | 19 | 20 | module.exports = AuthViewActionCreators; -------------------------------------------------------------------------------- /www/views/app/components/About/About.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var About = React.createClass({ 6 | render: function () { 7 | return ( 8 |
9 | 「他很懒,暂时什么都没留下....」 10 |
11 | ); 12 | } 13 | }); 14 | 15 | module.exports = About; -------------------------------------------------------------------------------- /www/views/app/components/Articles/Article.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var ArticleStore = require('../../stores/ArticleStore'); 7 | var ArticleViewActionCreators = require('../../actions/ArticleViewActionCreators'); 8 | 9 | var Link = Router.Link; 10 | 11 | var Article = React.createClass({ 12 | mixins: [Router.State], 13 | 14 | getInitialState: function () { 15 | return ({ 16 | article: ArticleStore.getArticle() 17 | }); 18 | }, 19 | componentWillMount: function () { 20 | var id = this.getParams().articleId; 21 | ArticleViewActionCreators.getArticle(id); 22 | ArticleStore.addChangeListener(this._setArticle); 23 | }, 24 | componentWillUnmount: function () { 25 | ArticleStore.removeChangeListener(this._setArticle); 26 | }, 27 | _setArticle: function () { 28 | var status = ArticleStore.getStatus(); 29 | switch (status) { 30 | case 'loading': 31 | break; 32 | case 'success': 33 | this.setState({ 34 | article: ArticleStore.getArticle() 35 | }); 36 | break; 37 | case 'false': 38 | alert(ArticleStore.getErrorMsg()); 39 | break; 40 | default : 41 | break; 42 | } 43 | }, 44 | render: function () { 45 | var article = this.state.article; 46 | if (article.length) { 47 | article = article[0]; 48 | return ( 49 |
50 |
51 |

{article.title}

55 |
56 |
61 |
62 |
63 |
64 |
68 | 69 | { article.tags.map(function (result) { 70 | return ( 71 | 74 | {result} 75 | 76 | ); 77 | })} 78 |
79 |

本篇文章由 zwhu 发表于 {article.time.minute}

83 |
84 | 85 |
86 | ); 87 | } else { 88 | return (
'loading...'
); 89 | } 90 | 91 | } 92 | }); 93 | 94 | module.exports = Article; -------------------------------------------------------------------------------- /www/views/app/components/Articles/ArticleItem.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var RouteHandler = Router.RouteHandler; 7 | 8 | var ArticleItem = React.createClass({ 9 | render: function () { 10 | return ( 11 |
12 | 「此功能正在开发中...」 13 |
14 | ); 15 | } 16 | }); 17 | 18 | module.exports = ArticleItem; -------------------------------------------------------------------------------- /www/views/app/components/Articles/ArticleItemByTag.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | var Link = Router.Link; 6 | 7 | 8 | var ArticleStore = require('../../stores/ArticleStore'); 9 | var ArticleViewActionCreators = require('../../actions/ArticleViewActionCreators'); 10 | 11 | var ArticleItemByTag = React.createClass({ 12 | mixins: [Router.State], 13 | 14 | getInitialState: function() { 15 | return ({ 16 | articles: [] 17 | }); 18 | }, 19 | componentWillMount: function () { 20 | var tagName = this.getParams().tagName; 21 | ArticleViewActionCreators.getByTag(tagName); 22 | ArticleStore.addChangeListener(this._setArticles); 23 | }, 24 | componentWillUnmount: function () { 25 | ArticleStore.removeChangeListener(this._setArticles); 26 | }, 27 | _setArticles: function () { 28 | var status = ArticleStore.getStatus(); 29 | switch (status) { 30 | case 'loading': 31 | break; 32 | case 'success': 33 | this.setState({ 34 | articles: ArticleStore.getArticles() 35 | }); 36 | break; 37 | case 'false': 38 | alert(ArticleStore.getErrorMsg()); 39 | break; 40 | default : 41 | break; 42 | } 43 | }, 44 | render: function() { 45 | return ( 46 |
    47 | {this.state.articles.map(function(result, index) { 48 | return (
  • 49 | {result.title} 51 |
  • ) 52 | })} 53 |
54 | ) 55 | } 56 | }); 57 | 58 | module.exports = ArticleItemByTag; 59 | -------------------------------------------------------------------------------- /www/views/app/components/Articles/Articles.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var RouteHandler = Router.RouteHandler; 7 | 8 | var Articles = React.createClass({ 9 | render: function () { 10 | return ( 11 | 12 | ); 13 | } 14 | }); 15 | 16 | module.exports = Articles; -------------------------------------------------------------------------------- /www/views/app/components/Articles/Tag.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | 6 | var RouteHandler = Router.RouteHandler; 7 | 8 | var Tag = React.createClass({ 9 | render: function () { 10 | return ( 11 | 12 | ); 13 | } 14 | }); 15 | 16 | module.exports = Tag; -------------------------------------------------------------------------------- /www/views/app/components/Articles/TagItem.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | var Link = Router.Link; 6 | 7 | 8 | //TODO: tags 点击事件 9 | // 研究下 once 10 | var TagItem = React.createClass({ 11 | render: function () { 12 | return ( 13 | {this.props.data} 16 | ); 17 | } 18 | }); 19 | 20 | module.exports = TagItem; 21 | -------------------------------------------------------------------------------- /www/views/app/components/Articles/Tags.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | var RouteHandler = Router.RouteHandler; 6 | 7 | 8 | var ArticleStore = require('../../stores/ArticleStore'); 9 | var ArticleViewActionCreators = require('../../actions/ArticleViewActionCreators'); 10 | 11 | var Say = require('../Say.jsx'); 12 | var Tag = require('./TagItem.jsx'); 13 | 14 | var Tags = React.createClass({ 15 | mixins: [Router.State], 16 | 17 | getInitialState: function () { 18 | return ({ 19 | tags: ArticleStore.getTags() 20 | }); 21 | }, 22 | componentWillMount: function () { 23 | ArticleViewActionCreators.getTags(); 24 | ArticleStore.addChangeListener(this._setTags); 25 | }, 26 | componentWillUnmount: function () { 27 | ArticleStore.removeChangeListener(this._setTags); 28 | }, 29 | _setTags: function () { 30 | var status = ArticleStore.getStatus(); 31 | switch (status) { 32 | case 'loading': 33 | break; 34 | case 'success': 35 | this.setState({ 36 | tags: ArticleStore.getTags() 37 | }); 38 | break; 39 | case 'false': 40 | alert('出错了吧'); 41 | break; 42 | default : 43 | break; 44 | } 45 | }, 46 | getHandlerKey: function () { 47 | var key = this.getRoutes()[this.getRoutes().length - 1].name; 48 | var id = this.getParams().tagName; 49 | if (id) { key += id; } 50 | return key; 51 | }, 52 | render: function () { 53 | var isShow = this.getRoutes().length > 2 ? (
54 | 55 |
) : null; 56 | return ( 57 |
58 |
59 |
60 | { 61 | this.state.tags.map(function(result, index) { 62 | return () 63 | }) 64 | } 65 |
66 | {isShow} 67 |
68 | 69 |
70 | ); 71 | } 72 | }); 73 | 74 | module.exports = Tags; 75 | -------------------------------------------------------------------------------- /www/views/app/components/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var Footer = React.createClass({ 6 | getInitialState: function () { 7 | return null; 8 | }, 9 | render: function () { 10 | return ( 11 |
12 |
13 |
14 |
15 |
16 |

Copyright ©2015 回家种地去__

17 |

power by BootStrap ❤ React ❤ MongoDb ❤ NodeJs

18 |
19 |
20 |
21 |
22 |
23 | ) 24 | } 25 | }); 26 | 27 | module.exports = Footer; -------------------------------------------------------------------------------- /www/views/app/components/Home/Home.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | var Link = Router.Link; 6 | 7 | var ArticleStore = require('../../stores/ArticleStore'); 8 | var ArticleViewActionCreators = require('../../actions/ArticleViewActionCreators'); 9 | 10 | var Say = require('../Say.jsx'); 11 | 12 | var Home = React.createClass({ 13 | getInitialState: function () { 14 | return ({ 15 | articles: ArticleStore.getArticles() 16 | }); 17 | }, 18 | componentWillMount: function () { 19 | ArticleViewActionCreators.getArticles(); 20 | ArticleStore.addChangeListener(this._setArticles); 21 | }, 22 | componentWillUnmount: function () { 23 | ArticleStore.removeChangeListener(this._setArticles); 24 | }, 25 | _setArticles: function () { 26 | var status = ArticleStore.getStatus(); 27 | switch (status) { 28 | case 'loading': 29 | break; 30 | case 'success': 31 | this.setState({ 32 | articles: ArticleStore.getArticles() 33 | }); 34 | break; 35 | case 'false': 36 | alert(ArticleStore.getErrorMsg()); 37 | break; 38 | default : 39 | break; 40 | } 41 | }, 42 | render: function () { 43 | function getImg(pic) { 44 | if(!!pic) 45 | return () 46 | } 47 | return ( 48 |
49 |
50 | {this.state.articles.map(function (v) { 51 | return ( 52 |
53 |
54 |
55 |

56 | {v.title} 57 |

58 | {getImg(v.titlePic)} 59 |

{v.summary}

60 |
61 |
62 |
63 | -Read More- 64 |
65 |
66 | ) 67 | })} 68 |
69 | 70 |
71 | ); 72 | } 73 | }); 74 | 75 | module.exports = Home; 76 | -------------------------------------------------------------------------------- /www/views/app/components/Index.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | var Nav = require('./Nav/Nav.jsx'); 6 | var Footer = require('./Footer/Footer.jsx'); 7 | 8 | var RouteHandler = Router.RouteHandler; 9 | 10 | var Index = React.createClass({ 11 | getInitialState: function() { 12 | return {} 13 | }, 14 | componentDidMount: function() { 15 | 16 | }, 17 | render: function () { 18 | return ( 19 |
20 |
26 | ); 27 | } 28 | }); 29 | 30 | module.exports = Index; -------------------------------------------------------------------------------- /www/views/app/components/Login/Login.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | var AuthViewActionCreators = require('../../actions/AuthViewActionCreators'); 6 | var AuthStore = require('../../stores/AuthStore'); 7 | 8 | var Login = React.createClass({ 9 | mixins: [Router.Navigation], 10 | statics: { 11 | willTransitionTo: function (transition) { 12 | if (AuthStore.getToken()) { 13 | transition.redirect('Home'); 14 | } 15 | } 16 | }, 17 | getInitialState: function () { 18 | return null; 19 | }, 20 | componentWillMount: function () { 21 | AuthStore.addChangeListener(this._signin); 22 | }, 23 | componentWillUnmount: function () { 24 | AuthStore.removeChangeListener(this._signin); 25 | }, 26 | _handleSubmit: function (e) { 27 | e.preventDefault(); 28 | var name = this.refs.name.getDOMNode().value; 29 | var password = this.refs.password.getDOMNode().value; 30 | AuthViewActionCreators.signin({ 31 | name: name, 32 | password: password 33 | }); 34 | }, 35 | _signin: function () { 36 | if (AuthStore.getToken()) { 37 | this.replaceWith('/'); 38 | } else { 39 | alert(AuthStore.getErrorMsg()); 40 | } 41 | }, 42 | render: function () { 43 | return ( 44 |
45 |
46 | 47 |
48 |
49 | 50 |
51 |
52 | 53 |
54 |
55 | ); 56 | } 57 | }); 58 | 59 | module.exports = Login; -------------------------------------------------------------------------------- /www/views/app/components/Nav/Nav.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | 4 | var AuthStore = require('../../stores/AuthStore'); 5 | 6 | var Link = Router.Link; 7 | 8 | var Nav = React.createClass({ 9 | getInitialState: function () { 10 | return { 11 | post: !!AuthStore.getToken() 12 | } 13 | }, 14 | mixins: [Router.State], 15 | render: function () { 16 | var routeConfig = ["Home", "Tags", "About"]; 17 | if (this.state.post) { 18 | routeConfig.push('Post'); 19 | } 20 | return ( 21 |
22 |
23 | 28 |
29 |
30 | ) 31 | } 32 | }); 33 | 34 | module.exports = Nav; -------------------------------------------------------------------------------- /www/views/app/components/Post/Post.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | var ArticleViewActionCreators = require('../../actions/ArticleViewActionCreators'); 6 | var ArticleStore = require('../../stores/ArticleStore'); 7 | var AuthStore = require('../../stores/AuthStore'); 8 | 9 | 10 | var Login = React.createClass({ 11 | mixins: [Router.Navigation], 12 | statics: { 13 | willTransitionTo: function (transition) { 14 | if (!AuthStore.getToken()) { 15 | transition.redirect('Home'); 16 | } 17 | } 18 | }, 19 | getInitialState: function () { 20 | return (null); 21 | }, 22 | componentWillMount: function () { 23 | ArticleStore.addChangeListener(this._postArticle); 24 | }, 25 | componentWillUnmount: function () { 26 | ArticleStore.removeChangeListener(this._postArticle); 27 | }, 28 | _postArticle: function () { 29 | var status = ArticleStore.getStatus(); 30 | switch (status) { 31 | case 'loading': 32 | break; 33 | case 'success': 34 | this.replaceWith('Home'); 35 | break; 36 | case 'false': 37 | alert(ArticleStore.getErrorMsg()); 38 | } 39 | }, 40 | _handleSubmit: function (e) { 41 | e.preventDefault(); 42 | ArticleViewActionCreators.postArticle({ 43 | title: this.refs.title.getDOMNode().value, 44 | tags: this.refs.tags.getDOMNode().value.split(','), 45 | titlePic: this.refs.titlePic.getDOMNode().value, 46 | summary: this.refs.summary.getDOMNode().value, 47 | content: this.refs.content.getDOMNode().value 48 | }); 49 | }, 50 | render: function () { 51 | return ( 52 |
53 |
54 | 58 |
59 |
60 | 64 |
65 |
66 | 70 |
71 |
72 |

摘要

73 |