├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── config.js ├── config ├── admin_config.js ├── akismet_config.js ├── blog_config.js ├── db_config.js ├── marked_config.js └── rss_config.js ├── libs └── util.js ├── models ├── admin.js ├── comment.js ├── db.js ├── link.js ├── page.js ├── post.js └── tag.js ├── package.json ├── public ├── images │ ├── admin │ │ ├── created.png │ │ ├── next.png │ │ ├── slug.png │ │ ├── tags.png │ │ ├── title.png │ │ ├── wmd-buttons.png │ │ └── write.png │ ├── blog │ │ ├── backtop.gif │ │ ├── bg.jpg │ │ ├── error404.gif │ │ ├── footersub.png │ │ ├── header.png │ │ └── icons.png │ └── favicon.ico ├── javascripts │ ├── cal.js │ ├── pagedown │ │ ├── LICENSE.txt │ │ ├── Markdown.Converter.js │ │ ├── Markdown.Editor.js │ │ └── Markdown.Sanitizer.js │ └── prettify │ │ ├── lang-apollo.js │ │ ├── lang-clj.js │ │ ├── lang-css.js │ │ ├── lang-go.js │ │ ├── lang-hs.js │ │ ├── lang-lisp.js │ │ ├── lang-lua.js │ │ ├── lang-ml.js │ │ ├── lang-n.js │ │ ├── lang-proto.js │ │ ├── lang-scala.js │ │ ├── lang-sql.js │ │ ├── lang-tex.js │ │ ├── lang-vb.js │ │ ├── lang-vhdl.js │ │ ├── lang-wiki.js │ │ ├── lang-xq.js │ │ ├── lang-yaml.js │ │ └── prettify.js └── stylesheets │ ├── admin │ └── style.css │ └── blog │ └── style.css ├── route ├── admin.js └── blog.js ├── routes.js ├── test ├── _init.js ├── factories.js ├── mocha.opts └── models │ └── admin.js └── views ├── admin ├── add.html ├── comment │ └── index.html ├── common │ ├── extend_js.html │ ├── javascripts.html │ ├── stylesheets.html │ └── title.html ├── home.html ├── install.html ├── login.html ├── page │ ├── edit.html │ ├── index.html │ └── write.html ├── post │ ├── edit.html │ ├── index.html │ └── write.html ├── public │ └── error.html └── verify_akismet.html └── blog ├── archives.html ├── common ├── back_to_top.html ├── footer.html ├── header.html ├── javascripts.html ├── menu.html └── stylesheets.html ├── index.html ├── layout.html ├── links.html ├── page.html ├── post.html ├── public ├── 404.html ├── 500.html └── error.html └── tag.html /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | 17 | .idea 18 | *.iml 19 | .DS_Store 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2014 happen-zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node.js-blog-starter # 2 | 3 | 本项目是一个使用Node.js编写的个人博客系统,同时也使用了其他一些第三方Node.js开源框架和模块,如Express,Mongoose和Mocha等。数据库存储使用的是Mongodb。 4 | 5 | 本博客系统的功能比较简单,拥有最基本的博客系统的管理功能,前后端都有代码实现。同时,文章和页面管理都支持实时Markdown风格的编辑器,普通游客的评论也支持Markdown。本博客系统还提供了Akismet来帮助你管理评论。 6 | 7 | 就如本项目命所见那样,这个博客系统是提供给大家作为学习参考的,你可以从里面学到一些不错的知识点: 8 | 9 | * 使用Express来搭建Web应用的框架 10 | * 使用Mongoose来组织你的数据模型 11 | * 使用Mongodb来存储session 12 | * 使用Mocha和Chai进行测试 13 | 14 | 当然,知识点远远不止上面这些。最后,我希望这个博客系统能帮你入门Node.js。 15 | 16 | 演示地址:[node-blog-starter](http://112.124.35.12:3000); 17 | 18 | ## 特别说明 ## 19 | 20 | 本博客系统的前台和后台的页面是由 @willerce 设计和提供的,而且本博客的原版来自于 [@willerce](http://willerce.com/) 的[noderce](https://github.com/willerce/noderce)。 21 | 22 | 与[noderce](https://github.com/willerce/noderce)的不同点在于,本博客系统的代码基本都是重写的,使用Mongoose来做数据库存储,Session使用Mongodb来作存储介质,添加了一些新的模块等等。为想学习Node.js的同学提供一点帮助,仅此而已。 23 | 24 | ## 安装 ## 25 | 26 | ### 克隆项目到本地 ### 27 | 28 | ``` 29 | git clone https://github.com/happen-zhang/node.js-blog-starter.git path-to-your-dir 30 | ``` 31 | 32 | ### 安装依赖 ### 33 | 34 | ``` 35 | cd path-to-your-dir 36 | 37 | npm install 38 | ``` 39 | 40 | ### 修改配置 ### 41 | 42 | 博客系统的配置文件都存放在`/config`目录下,你可以在里面找到需要修改的配置文件。 43 | 44 | ### 运行程序 ### 45 | 46 | 请先确保你的Mongodb数据库服务是开启的,然后执行下面的命令运行服务:, 47 | 48 | ``` 49 | npm start 50 | ``` 51 | 52 | ### 初始化数据 ### 53 | 54 | 服务启动后,请访问网站的更目录`/`,或者是`/admin/install`,系统会提示你进行初始化。 55 | 56 | ## 测试 ## 57 | 58 | 请先确保安装了Mocha: 59 | 60 | ``` 61 | npm install -g mocha 62 | ``` 63 | 64 | 然后在项目的根目录下运行下面的命令: 65 | 66 | ``` 67 | npm test 68 | ``` 69 | 70 | ## License ## 71 | 72 | (The MIT License) 73 | 74 | Copyright (c) 2014 happen-zhang 75 | 76 | Permission is hereby granted, free of charge, to any person obtaining 77 | a copy of this software and associated documentation files (the 78 | 'Software'), to deal in the Software without restriction, including 79 | without limitation the rights to use, copy, modify, merge, publish, 80 | distribute, sublicense, and/or sell copies of the Software, and to 81 | permit persons to whom the Software is furnished to do so, subject to 82 | the following conditions: 83 | 84 | The above copyright notice and this permission notice shall be 85 | included in all copies or substantial portions of the Software. 86 | 87 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 88 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 89 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 90 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 91 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 92 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 93 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 94 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * app.js 3 | */ 4 | 5 | var http = require('http'); 6 | var express = require('express'); 7 | var partials = require('express-partials'); 8 | var MongoStore = require('connect-mongo')(express); 9 | 10 | var blogConfig = require('./config').blogConfig; 11 | var dbConfig = require('./config').dbConfig; 12 | var route = require('./routes'); 13 | var db = require('./models/db'); 14 | 15 | var app = express(); 16 | // 静态文件目录 17 | var staticDir = __dirname + '/public/'; 18 | // 视图文件目录 19 | var viewsDir = __dirname + '/views/'; 20 | // favicon 21 | var faviconPath = staticDir + 'images/favicon.ico'; 22 | 23 | /** 24 | * setting 25 | */ 26 | 27 | app.set('port', blogConfig.port); 28 | app.set('views', viewsDir); 29 | // app.set('view engine', 'ejs'); 30 | // 更改模板引擎 31 | app.engine('.html', require('ejs').__express); 32 | app.set('view engine', 'html'); 33 | 34 | /** 35 | * midware 36 | */ 37 | 38 | // static assets 39 | app.use(express.static(staticDir)); 40 | // partials 41 | app.use(partials()); 42 | // favicon 43 | app.use(express.favicon(faviconPath)); 44 | // bodyParser 45 | app.use(express.bodyParser()); 46 | // cookieParser 47 | app.use(express.cookieParser()); 48 | // session 49 | app.use(express.session({ 50 | secret: blogConfig.sessionSecret, 51 | // 1 Hour 52 | cookie: { maxAge: blogConfig.cookieMaxAge }, 53 | store: new MongoStore({ 54 | url: dbConfig.development.dbUrl 55 | }) 56 | })); 57 | 58 | /** 59 | * helper 60 | */ 61 | 62 | app.locals.moment= require('moment'); 63 | app.locals.gravatar = require('gravatar'); 64 | app.locals.marked = require('./config.js').markedConfig; 65 | 66 | // 路由 67 | route.handle(app); 68 | 69 | // 开发环境 70 | if ('development' == app.get('env')) { 71 | app.use(express.errorHandler({ 72 | dumpExceptions: true, 73 | showStack: true 74 | })); 75 | } 76 | 77 | // 生产环境 78 | if ('production' == app.get('env')) { 79 | // 404 80 | app.use(function handleNotFound(req, res) { 81 | route.handleNotFound(req, res); 82 | }); 83 | 84 | // 500 85 | app.use(function handlerError(err, req, res, next) { 86 | route.handleNotFound(err, req, res, next); 87 | }); 88 | 89 | // 视图缓存 90 | app.set('view cache', true); 91 | } 92 | 93 | // 启动服务 94 | http.createServer(app).listen(app.get('port'), function () { 95 | console.log("Express server listening on port " + app.get('port')); 96 | }); 97 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * config.js 3 | */ 4 | 5 | // blog configuration 6 | exports.blogConfig = require('./config/blog_config'); 7 | 8 | // database configuration 9 | exports.dbConfig = require('./config/db_config'); 10 | 11 | // rss configuration 12 | exports.rssConfig = require('./config/rss_config'); 13 | 14 | // marked configuration 15 | exports.markedConfig = require('./config/marked_config'); 16 | 17 | // akismet configuration 18 | exports.akismetConfig = require('./config/akismet_config'); 19 | 20 | // admin configuration 21 | exports.adminConfig = require('./config/admin_config'); 22 | -------------------------------------------------------------------------------- /config/admin_config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /config/admin_config.js 3 | */ 4 | 5 | // admin config 6 | var config = { 7 | // 页面标题 8 | pageTitle: 'Fun with Node.js', 9 | // 添加管理员的token,''则表示不使用 10 | token: '924271b7fb0b0a997c1464b0b163dda066ee78a6', 11 | // auth key 12 | authKey: 'mysecretkey', 13 | // 最大登陆数 14 | maxAttempts: 5, 15 | // locked时间,单位毫秒 16 | lockedExpire: 7200000 17 | }; 18 | 19 | module.exports = config; 20 | -------------------------------------------------------------------------------- /config/akismet_config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /config/akismet_config.js 3 | */ 4 | 5 | // akismet options 6 | var config = { 7 | // akismet key 留空则不使用akismet 8 | apiKey: '', 9 | // blog的url地址 10 | blog: 'your-blog-url', 11 | }; 12 | 13 | module.exports = config; 14 | -------------------------------------------------------------------------------- /config/blog_config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /config/blog_config.js 3 | */ 4 | 5 | // Blog配置 6 | var config = { 7 | // 博客名称 8 | blogname: 'happen', 9 | // 分页行数 10 | listRows: 5, 11 | // 访问端口 12 | port: 3000, 13 | // session secret 14 | sessionSecret: '560b9047', 15 | // cookie max age 1 hour 16 | cookieMaxAge: 3600000 17 | }; 18 | 19 | module.exports = config; 20 | -------------------------------------------------------------------------------- /config/db_config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /config/db_config.js 3 | */ 4 | 5 | var mongoose = require('mongoose'); 6 | 7 | // util 8 | var util = require('../libs/util'); 9 | 10 | var development = { 11 | host: 'localhost', 12 | port: 27017, 13 | name: 'blog', 14 | username: '', 15 | password: '' 16 | }; 17 | development.dbUrl = util.generateMongoUrl(development); 18 | exports.development = development; 19 | 20 | var test = { 21 | host: 'localhost', 22 | port: 27017, 23 | name: 'blog_test', 24 | username: '', 25 | password: '', 26 | }; 27 | test.dbUrl = util.generateMongoUrl(test); 28 | exports.test = test; 29 | -------------------------------------------------------------------------------- /config/marked_config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /config/marked_config.js 3 | */ 4 | 5 | var marked = require('marked'); 6 | 7 | marked.setOptions({ 8 | renderer: new marked.Renderer(), 9 | gfm: true, 10 | tables: true, 11 | breaks: false, 12 | pedantic: false, 13 | sanitize: true, 14 | smartLists: true, 15 | smartypants: false 16 | }); 17 | 18 | module.exports = marked; 19 | -------------------------------------------------------------------------------- /config/rss_config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /config/rss_config.js 3 | */ 4 | 5 | // Rss Config 6 | var config = { 7 | rssRows: 5, 8 | title: 'happen', 9 | description: '更多的是兴趣', 10 | link: 'http://your-website-link.com', 11 | language: "zh-cn", 12 | managingEditor: "zhanghaipeng404@gmail.com (happen)", 13 | webMaster: "zhanghaipeng404@gmail.com (happen)", 14 | generator: "your website name", 15 | author: { 16 | name: "happen", 17 | uri: "http://your-website-link.com" 18 | } 19 | }; 20 | 21 | module.exports = config; 22 | -------------------------------------------------------------------------------- /libs/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /libs/util.js 3 | */ 4 | 5 | var crypto = require('crypto'); 6 | 7 | /** 8 | * 生成mongodb url 9 | * @param Object config 10 | * @return string 11 | */ 12 | exports.generateMongoUrl = function(config) { 13 | host = (config.host || 'localhost'); 14 | port = (config.port || 27017); 15 | dbName = (config.name || 'test'); 16 | 17 | var target = host + ':' + port + '/' + dbName; 18 | var user = ''; 19 | if (config.username && config.password) { 20 | user += config.username + ':' + config.password + '@'; 21 | } 22 | 23 | return 'mongodb://' + user + target; 24 | }; 25 | 26 | /** 27 | * 生成token值 28 | * @return string 29 | */ 30 | exports.generateToken = function() { 31 | var currentDate = (new Date()).valueOf().toString(); 32 | var random = Math.random().toString(); 33 | 34 | return crypto.createHash('sha1').update(currentDate + random).digest('hex'); 35 | }; 36 | 37 | /** 38 | * md5加密 39 | * @param string src 40 | * @return string desc 41 | */ 42 | exports.md5 = function md5(src) { 43 | var md5sum = crypto.createHash('md5'); 44 | md5sum.update(src); 45 | src = md5sum.digest('hex'); 46 | return src; 47 | }; 48 | -------------------------------------------------------------------------------- /models/admin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /models/admin.js 3 | */ 4 | 5 | var mongoose = require('mongoose'); 6 | var bcrypt = require('bcrypt'); 7 | 8 | var adminConfig = require('../config').adminConfig; 9 | 10 | // salt factor 11 | var SALT_WORK_FACTOR = 10; 12 | // 最大试图登陆次数 13 | var MAX_LOGIN_ATTEMPTS = adminConfig.maxAttempts; 14 | // 登陆锁时间 15 | var LOCK_TIME = adminConfig.lockedExpire; 16 | 17 | /** 18 | * 管理员模型 19 | */ 20 | var AdminSchema = new mongoose.Schema({ 21 | // 登录名 22 | loginname: { 23 | type: String, 24 | required: '{PATH} is required!', 25 | index: { 26 | unique: true 27 | } 28 | }, 29 | // 密码 30 | password: { 31 | type: String, 32 | required: '{PATH} is required!' 33 | }, 34 | // login locks 35 | loginAttempts: { 36 | type: Number, 37 | require: true, 38 | default: 0 39 | }, 40 | // lock until 41 | lockUntil: { 42 | type: Number 43 | }, 44 | // 创建时间 45 | created: { 46 | type: Date, 47 | default: Date.now 48 | }, 49 | // 更新时间 50 | updated: { 51 | type: Date, 52 | default: Date.now 53 | } 54 | }); 55 | 56 | /** 57 | * virtual field:检查当前状态是否为locked 58 | */ 59 | AdminSchema.virtual('isLocked').get(function() { 60 | return (this.lockUntil && this.lockUntil > Date.now()); 61 | }); 62 | 63 | /** 64 | * 登陆失败状态值 65 | */ 66 | var reasons = AdminSchema.statics.failedLogin = { 67 | NOT_FOUND: 0, 68 | PASSWORD_INCORRECT: 1, 69 | MAX_ATTEMPTS: 2 70 | }; 71 | 72 | /** 73 | * 当执行save操作时,它将会被先执行 74 | * @param Function next 75 | * @return 76 | */ 77 | AdminSchema.pre('save', function(next) { 78 | var admin = this; 79 | 80 | // 如果password被修改过,则需要重新hash 81 | if (!admin.isModified('password')) { 82 | return next(); 83 | } 84 | 85 | // 生成salt 86 | bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) { 87 | if (err) { 88 | return next(err); 89 | } 90 | 91 | // 生成hash 92 | bcrypt.hash(admin.password, salt, function(err, hash) { 93 | if (err) { 94 | return next(err); 95 | } 96 | 97 | admin.password = hash; 98 | next(); 99 | }); 100 | }); 101 | }); 102 | 103 | /** 104 | * 按loginname得到admin 105 | * @param string loginname 106 | * @param Function callback 107 | * @return 108 | */ 109 | AdminSchema.static('findByLoginname', function(loginname, callback) { 110 | return this.findOne({ loginname: loginname }, callback); 111 | }); 112 | 113 | /** 114 | * 验证登陆 115 | * @param string loginname 116 | * @param string password 117 | * @param Function cb 118 | * @return 119 | */ 120 | AdminSchema.static('getAuthenticated', function(loginname, password, cb) { 121 | this.findOne({ loginname: loginname }, function(err, admin) { 122 | if (err) { 123 | return cb(err); 124 | } 125 | 126 | // loginname不存在 127 | if (null === admin) { 128 | return cb(null, null, reasons.NOT_FOUND); 129 | } 130 | 131 | // 当前状态是否为locked 132 | if (admin.isLocked) { 133 | return admin.incLoginAttempts(function(err) { 134 | if (err) { 135 | return cb(err); 136 | } 137 | 138 | return cb(null, null, reasons.MAX_LOGIN_ATTEMPTS); 139 | }); 140 | } 141 | 142 | // 检查password是否正确 143 | admin.comparePassword(password, function(err, isMatch) { 144 | if (err) { 145 | return cb(err); 146 | } 147 | 148 | if (isMatch) { 149 | if (!admin.loginAttempts && !admin.lockUntil) { 150 | return cb(null, admin); 151 | } 152 | 153 | // 重新更新locked状态 154 | var updates = { 155 | $set: { loginAttempts: 0 }, 156 | $unset: { lockUntil: 1 } 157 | }; 158 | 159 | return admin.update(updates, function(err) { 160 | if (err) { 161 | return cb(err); 162 | } 163 | 164 | return cb(null, admin); 165 | }); 166 | } 167 | 168 | // password错误 169 | admin.incLoginAttempts(function(err) { 170 | if (err) { 171 | return cb(err); 172 | } 173 | 174 | return cb(null, null, reasons.PASSWORD_INCORRECT); 175 | }); 176 | }); 177 | }); 178 | }); 179 | 180 | /** 181 | * 检查password 182 | * @param string candidatePassword 183 | * @param Function callback 184 | * @return 185 | */ 186 | AdminSchema.methods.comparePassword = function(candidatePassword, callback) { 187 | bcrypt.compare(candidatePassword, this.password, function(err, isMatch) { 188 | if (err) { 189 | return callback(err); 190 | } 191 | 192 | callback(null, isMatch); 193 | }); 194 | }; 195 | 196 | /** 197 | * 增加attempts 198 | * @param Function callback 199 | * @return 200 | */ 201 | AdminSchema.methods.incLoginAttempts = function(callback) { 202 | if (this.lockUntil && this.lockUntil < Date.now()) { 203 | // 之前没被locked或者之前的locked已过期 204 | return this.update({ 205 | // 设置loginAttempts为1 206 | $set: { loginAttempts: 1 }, 207 | $unset: { lockUntil: 1 } 208 | }, callback); 209 | } 210 | 211 | var updates = { $inc: { loginAttempts: 1 } }; 212 | 213 | if (this.loginAttempts + 1 >= MAX_LOGIN_ATTEMPTS && !this.isLocked) { 214 | // locked 215 | updates.$set = { lockUntil: Date.now() + LOCK_TIME }; 216 | } 217 | 218 | return this.update(updates, callback); 219 | } 220 | 221 | module.exports = mongoose.model('Admin', AdminSchema); 222 | -------------------------------------------------------------------------------- /models/comment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /models/comment.js 3 | */ 4 | 5 | var mongoose = require('mongoose'); 6 | 7 | 8 | // email正则 9 | var emailReg = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 10 | 11 | /** 12 | * website验证器 13 | * @param string url 需验证的url 14 | * @return boolean 15 | */ 16 | var websiteValidation = function(url) { 17 | if (!url) { 18 | return true; 19 | } 20 | 21 | var urlReg = /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w\- .\/?%&=]*)?/; 22 | 23 | return urlReg.test(url); 24 | } 25 | 26 | /** 27 | * 评论模型 28 | */ 29 | var CommentSchema = new mongoose.Schema({ 30 | // 评论者 31 | author: { 32 | type: String, 33 | required: '{PATH} is required!' 34 | }, 35 | // 邮箱 36 | email: { 37 | type: String, 38 | required: '{PATH} is required!', 39 | validate: [emailReg, '{PATH} was incorrectly formed!'] 40 | }, 41 | // 网站 42 | website: { 43 | type: String, 44 | validate: [websiteValidation, '{PATH} was incorrectly formed!'] 45 | }, 46 | // 内容 47 | content: { 48 | type: String, 49 | required: '{PATH} is required!' 50 | }, 51 | // 评论者ip 52 | authorIp: String, 53 | // 是否垃圾评论 54 | isSpam: { 55 | type: Boolean, 56 | default: false 57 | }, 58 | // 创建时间 59 | created: { 60 | type: Date, 61 | default: Date.now 62 | }, 63 | // 更新时间 64 | updated: { 65 | type: Date, 66 | default: Date.now 67 | } 68 | }); 69 | 70 | module.exports = mongoose.model('Comment', CommentSchema); 71 | -------------------------------------------------------------------------------- /models/db.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /models/db.js 3 | */ 4 | 5 | var mongoose = require('mongoose'); 6 | 7 | var dbConfig = require('../config/db_config').development; 8 | 9 | mongoose.connect(dbConfig.dbUrl); 10 | 11 | // when successfully connected 12 | mongoose.connection.on('connected', function() { 13 | console.log('Mongoose default connection open to ' + dbConfig.dbUrl); 14 | }); 15 | 16 | // if the connection throws an error 17 | mongoose.connection.on('error', function(err) { 18 | console.log('Mongoose default connection error: ' + err); 19 | }); 20 | 21 | // when the connection is disconnected 22 | mongoose.connection.on('disconnected', function() { 23 | console.log('Mongoose default connection disconnected'); 24 | }); 25 | 26 | // if the Node process ends, close the Mongoose connection 27 | process.on('SIGINT', function() { 28 | mongoose.connection.close(function () { 29 | console.log('Mongoose default connection disconnected' 30 | + 'through app termination'); 31 | process.exit(0); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /models/link.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /models/link.js 3 | */ 4 | 5 | var mongoose = require('mongoose'); 6 | 7 | /** 8 | * 友情链接模型 9 | */ 10 | var LinkSchema = new mongoose.Schema({ 11 | // 名称 12 | name: String, 13 | // url 14 | url: String, 15 | // 描述 16 | description: String, 17 | // 创建时间 18 | created: { 19 | type: Date, 20 | default: Date.now 21 | }, 22 | // 更新时间 23 | updated: { 24 | type: Date, 25 | default: Date.now 26 | } 27 | }); 28 | 29 | /** 30 | * 得到所有的友情链接 31 | * @param string fields 32 | * @param Function callback 33 | * @return 34 | */ 35 | LinkSchema.static('findAll', function(fields, callback) { 36 | return this.find(null, fields, null, callback); 37 | }); 38 | 39 | module.exports = mongoose.model('Link', LinkSchema); 40 | -------------------------------------------------------------------------------- /models/page.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /models/page.js 3 | */ 4 | 5 | var mongoose = require('mongoose'); 6 | 7 | /** 8 | * 页面模型 9 | */ 10 | var PageSchema = new mongoose.Schema({ 11 | // 标题 12 | title: String, 13 | // slug 14 | slug: String, 15 | // 内容 16 | content: String, 17 | // 创建时间 18 | created: { 19 | type: Date, 20 | default: Date.now 21 | }, 22 | // 更新时间 23 | updated: { 24 | type: Date, 25 | default: Date.now 26 | } 27 | }); 28 | 29 | /** 30 | * 按slug得到page 31 | * @param string slug 32 | * @param string fields 33 | * @param Function callback 34 | * @return 35 | */ 36 | PageSchema.static('findBySlug', function(slug, fields, callback) { 37 | return this.findOne({ slug: slug }, fields, null, callback); 38 | }); 39 | 40 | /** 41 | * 按id更新page 42 | * @param int id 43 | * @param Object page 44 | * @param Function callback 45 | * @return 46 | */ 47 | PageSchema.static('updateById', function(id, page, callback) { 48 | return this.findByIdAndUpdate(id, { $set: page }, callback); 49 | }); 50 | 51 | module.exports = mongoose.model('Page', PageSchema); 52 | -------------------------------------------------------------------------------- /models/post.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /models/post.js 3 | */ 4 | 5 | var mongoose = require('mongoose'); 6 | 7 | var Comment = require('./comment'); 8 | var Tag = require('./tag'); 9 | 10 | /** 11 | * 文章模型 12 | */ 13 | var PostSchema = new mongoose.Schema({ 14 | // 标题 15 | title: String, 16 | // slugify 17 | slug: String, 18 | // 内容 19 | content: String, 20 | // 评论 21 | comments: [Comment.schema], 22 | // 标签 23 | tags: [Tag.schema], 24 | // 创建时间 25 | created: { 26 | type: Date, 27 | default: Date.now 28 | }, 29 | // 更新时间 30 | updated: { 31 | type: Date, 32 | default: Date.now 33 | } 34 | }); 35 | 36 | /** 37 | * briefContent 38 | * @return string 39 | */ 40 | PostSchema.virtual('briefContent').get(function() { 41 | var more = this.content.indexOf(''); 42 | if (more > 0) { 43 | return this.content.substr(0, more); 44 | } 45 | 46 | return ''; 47 | }); 48 | 49 | /** 50 | * 得到post数据 51 | * @param int skip 52 | * @param int limit 53 | * @param string fields 54 | * @param Function callback 55 | * @return 56 | */ 57 | PostSchema.static('findAll', function(skip, limit, fields, callback) { 58 | var options = { 59 | skip: skip, 60 | limit: limit, 61 | sort: { 62 | created: -1, 63 | _id: -1 64 | } 65 | }; 66 | 67 | return this.find(null, fields, options, callback); 68 | }); 69 | 70 | /** 71 | * 按slug得到post 72 | * @param string slug 73 | * @param string fields 74 | * @param Function callback 75 | * @return 76 | */ 77 | PostSchema.static('findBySlug', function(slug, fields, callback) { 78 | return this.findOne({ slug: slug }, fields, null, callback); 79 | }); 80 | 81 | /** 82 | * 按tag得到post 83 | * @param string tag 84 | * @param string fields 85 | * @param Function callback 86 | * @return 87 | */ 88 | PostSchema.static('findByTag', function(tag, fields, callback) { 89 | return this.find({ 'tags.name': tag }, fields, null, callback); 90 | }); 91 | 92 | /** 93 | * 按id更新post 94 | * @param int id 95 | * @param Object post 96 | * @param Function callback 97 | * @return 98 | */ 99 | PostSchema.static('updateById', function(id, post, callback) { 100 | return this.findByIdAndUpdate(id, { $set: post }, callback); 101 | }); 102 | 103 | /** 104 | * 添加评论到指定id的post 105 | * @param int id 106 | * @param Comment comment 107 | * @param Function callback 108 | * @return 109 | */ 110 | PostSchema.static('addCommentById', function(id, comment, callback) { 111 | return this.update({ _id: id }, { $push: { comments: comment } }, callback); 112 | }); 113 | 114 | /** 115 | * 删除指定id post中的指定commentId的评论 116 | * @param int id 117 | * @param int commentId 118 | * @param Function callback 119 | * @return 120 | */ 121 | PostSchema.static('deleteCommentById', function(id, commentId, callback) { 122 | var pull = { comments: { _id: commentId } }; 123 | return this.findByIdAndUpdate(id, { $pull: pull }, callback); 124 | }); 125 | 126 | /** 127 | * spam/unspam 128 | * @param string commentId 129 | * @param Boolean isSpam 130 | * @param Function cb 131 | * @return 132 | */ 133 | PostSchema.static('spamCommentById', function(commentId, isSpam, cb) { 134 | var set = { 'comments.$.isSpam': isSpam }; 135 | 136 | return this.update({ 'comments._id': commentId }, { $set: set }, cb); 137 | }); 138 | 139 | module.exports = mongoose.model('Post', PostSchema); 140 | -------------------------------------------------------------------------------- /models/tag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /models/tag.js 3 | */ 4 | 5 | var mongoose = require('mongoose'); 6 | 7 | /** 8 | * 标签模型 9 | */ 10 | var TagSchema = new mongoose.Schema({ 11 | // 名称 12 | name: String, 13 | // 创建时间 14 | created: { 15 | type: Date, 16 | default: Date.now 17 | }, 18 | // 更新时间 19 | updated: { 20 | type: Date, 21 | default: Date.now 22 | } 23 | }); 24 | 25 | module.exports = mongoose.model('Tag', TagSchema); 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "happen-blog", 3 | "preferGlobal": "true", 4 | "version": "0.0.1", 5 | "private": true, 6 | "author": "happen-Zhang ", 7 | "description": "a simple nodejs blog program", 8 | "scripts": { 9 | "start": "node app", 10 | "test": "mocha" 11 | }, 12 | "engines": { 13 | "node": "0.10.x", 14 | "npm": "1.2.x" 15 | }, 16 | "dependencies": { 17 | "express": "3.4.0", 18 | "ejs": "0.8.4", 19 | "express-partials": "~0.1.1", 20 | "mongoose": "3.8.8", 21 | "connect-mongo": "0.3.3", 22 | "moment": "2.5.1", 23 | "gravatar": "1.0.6", 24 | "data2xml": "0.9.0", 25 | "marked": "0.3.2", 26 | "akismet": "0.0.11", 27 | "bcrypt": "0.7.7", 28 | "factory-lady": "0.1.0" 29 | }, 30 | "devDependencies": { 31 | "mocha": "1.17.1", 32 | "chai": "1.9.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/images/admin/created.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/admin/created.png -------------------------------------------------------------------------------- /public/images/admin/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/admin/next.png -------------------------------------------------------------------------------- /public/images/admin/slug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/admin/slug.png -------------------------------------------------------------------------------- /public/images/admin/tags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/admin/tags.png -------------------------------------------------------------------------------- /public/images/admin/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/admin/title.png -------------------------------------------------------------------------------- /public/images/admin/wmd-buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/admin/wmd-buttons.png -------------------------------------------------------------------------------- /public/images/admin/write.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/admin/write.png -------------------------------------------------------------------------------- /public/images/blog/backtop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/blog/backtop.gif -------------------------------------------------------------------------------- /public/images/blog/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/blog/bg.jpg -------------------------------------------------------------------------------- /public/images/blog/error404.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/blog/error404.gif -------------------------------------------------------------------------------- /public/images/blog/footersub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/blog/footersub.png -------------------------------------------------------------------------------- /public/images/blog/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/blog/header.png -------------------------------------------------------------------------------- /public/images/blog/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/blog/icons.png -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/images/favicon.ico -------------------------------------------------------------------------------- /public/javascripts/cal.js: -------------------------------------------------------------------------------- 1 | /** 2 | the script only works on "input [type=text]" 3 | 4 | **/ 5 | 6 | // don't declare anything out here in the global namespace 7 | 8 | (function ($) { // create private scope (inside you can use $ instead of jQuery) 9 | 10 | // functions and vars declared here are effectively 'singletons'. there will be only a single 11 | // instance of them. so this is a good place to declare any immutable items or stateless 12 | // functions. for example: 13 | 14 | var today = new Date(); // used in defaults 15 | var months = 'January,February,March,April,May,June,July,August,September,October,November,December'.split(','); 16 | var monthlengths = '31,28,31,30,31,30,31,31,30,31,30,31'.split(','); 17 | var dateRegEx = /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/; 18 | var yearRegEx = /^\d{4,4}$/; 19 | 20 | // next, declare the plugin function 21 | $.fn.simpleDatepicker = function (options) { 22 | 23 | // functions and vars declared here are created each time your plugn function is invoked 24 | 25 | // you could probably refactor your 'build', 'load_month', etc, functions to be passed 26 | // the DOM element from below 27 | 28 | var opts = jQuery.extend({}, jQuery.fn.simpleDatepicker.defaults, options); 29 | 30 | // replaces a date string with a date object in opts.startdate and opts.enddate, if one exists 31 | // populates two new properties with a ready-to-use year: opts.startyear and opts.endyear 32 | 33 | setupYearRange(); 34 | /** extracts and setup a valid year range from the opts object **/ 35 | function setupYearRange() { 36 | 37 | var startyear, endyear; 38 | if (opts.startdate.constructor == Date) { 39 | startyear = opts.startdate.getFullYear(); 40 | } else if (opts.startdate) { 41 | if (yearRegEx.test(opts.startdate)) { 42 | startyear = opts.startdate; 43 | } else if (dateRegEx.test(opts.startdate)) { 44 | opts.startdate = new Date(opts.startdate); 45 | startyear = opts.startdate.getFullYear(); 46 | } else { 47 | startyear = today.getFullYear(); 48 | } 49 | } else { 50 | startyear = today.getFullYear(); 51 | } 52 | opts.startyear = startyear; 53 | 54 | if (opts.enddate.constructor == Date) { 55 | endyear = opts.enddate.getFullYear(); 56 | } else if (opts.enddate) { 57 | if (yearRegEx.test(opts.enddate)) { 58 | endyear = opts.enddate; 59 | } else if (dateRegEx.test(opts.enddate)) { 60 | opts.enddate = new Date(opts.enddate); 61 | endyear = opts.enddate.getFullYear(); 62 | } else { 63 | endyear = today.getFullYear(); 64 | } 65 | } else { 66 | endyear = today.getFullYear(); 67 | } 68 | opts.endyear = endyear; 69 | } 70 | 71 | /** HTML factory for the actual datepicker table element **/ 72 | // has to read the year range so it can setup the correct years in our HTML '; 88 | for (var i in months) monthselect += ''; 89 | monthselect += ''; 90 | 91 | // year select field 92 | var yearselect = ''; 95 | 96 | jQuery("thead", table).append('« ' + monthselect + yearselect + ' »'); 97 | jQuery("thead", table).append('SMTWTFS'); 98 | jQuery("tfoot", table).append('today close'); 99 | for (var i = 0; i < 6; i++) jQuery("tbody", table).append(''); 100 | return table; 101 | } 102 | 103 | /** get the real position of the input (well, anything really) **/ 104 | //http://www.quirksmode.org/js/findpos.html 105 | function findPosition(obj) { 106 | var curleft = curtop = 0; 107 | if (obj.offsetParent) { 108 | do { 109 | curleft += obj.offsetLeft; 110 | curtop += obj.offsetTop; 111 | } while (obj = obj.offsetParent); 112 | return [curleft+14, curtop+14]; 113 | } else { 114 | return false; 115 | } 116 | } 117 | 118 | /** load the initial date and handle all date-navigation **/ 119 | // initial calendar load (e is null) 120 | // prevMonth & nextMonth buttons 121 | // onchange for the select fields 122 | function loadMonth(e, el, datepicker, chosendate) { 123 | 124 | // reference our years for the nextMonth and prevMonth buttons 125 | var mo = jQuery("select[name=month]", datepicker).get(0).selectedIndex; 126 | var yr = jQuery("select[name=year]", datepicker).get(0).selectedIndex; 127 | var yrs = jQuery("select[name=year] option", datepicker).get().length; 128 | 129 | // first try to process buttons that may change the month we're on 130 | if (e && jQuery(e.target).hasClass('prevMonth')) { 131 | if (0 == mo && yr) { 132 | yr -= 1; 133 | mo = 11; 134 | jQuery("select[name=month]", datepicker).get(0).selectedIndex = 11; 135 | jQuery("select[name=year]", datepicker).get(0).selectedIndex = yr; 136 | } else { 137 | mo -= 1; 138 | jQuery("select[name=month]", datepicker).get(0).selectedIndex = mo; 139 | } 140 | } else if (e && jQuery(e.target).hasClass('nextMonth')) { 141 | if (11 == mo && yr + 1 < yrs) { 142 | yr += 1; 143 | mo = 0; 144 | jQuery("select[name=month]", datepicker).get(0).selectedIndex = 0; 145 | jQuery("select[name=year]", datepicker).get(0).selectedIndex = yr; 146 | } else { 147 | mo += 1; 148 | jQuery("select[name=month]", datepicker).get(0).selectedIndex = mo; 149 | } 150 | } 151 | 152 | // maybe hide buttons 153 | if (0 == mo && !yr) jQuery("span.prevMonth", datepicker).hide(); 154 | else jQuery("span.prevMonth", datepicker).show(); 155 | if (yr + 1 == yrs && 11 == mo) jQuery("span.nextMonth", datepicker).hide(); 156 | else jQuery("span.nextMonth", datepicker).show(); 157 | 158 | // clear the old cells 159 | var cells = jQuery("tbody td", datepicker).unbind().empty().removeClass('date'); 160 | 161 | // figure out what month and year to load 162 | var m = jQuery("select[name=month]", datepicker).val(); 163 | var y = jQuery("select[name=year]", datepicker).val(); 164 | var d = new Date(y, m, 1); 165 | var startindex = d.getDay(); 166 | var numdays = monthlengths[m]; 167 | 168 | // http://en.wikipedia.org/wiki/Leap_year 169 | if (1 == m && ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0)) numdays = 29; 170 | 171 | // test for end dates (instead of just a year range) 172 | if (opts.startdate.constructor == Date) { 173 | var startMonth = opts.startdate.getMonth(); 174 | var startDate = opts.startdate.getDate(); 175 | } 176 | if (opts.enddate.constructor == Date) { 177 | var endMonth = opts.enddate.getMonth(); 178 | var endDate = opts.enddate.getDate(); 179 | } 180 | 181 | // walk through the index and populate each cell, binding events too 182 | for (var i = 0; i < numdays; i++) { 183 | 184 | var cell = jQuery(cells.get(i + startindex)).removeClass('chosen'); 185 | 186 | // test that the date falls within a range, if we have a range 187 | if ( 188 | (yr || ((!startDate && !startMonth) || ((i + 1 >= startDate && mo == startMonth) || mo > startMonth))) && 189 | (yr + 1 < yrs || ((!endDate && !endMonth) || ((i + 1 <= endDate && mo == endMonth) || mo < endMonth)))) { 190 | 191 | cell 192 | .text(i + 1) 193 | .addClass('date') 194 | .hover( 195 | function () { 196 | jQuery(this).addClass('over'); 197 | }, 198 | function () { 199 | jQuery(this).removeClass('over'); 200 | }) 201 | .click(function () { 202 | var chosenDateObj = new Date(jQuery("select[name=year]", datepicker).val(), jQuery("select[name=month]", datepicker).val(), jQuery(this).text()); 203 | closeIt(el, datepicker, chosenDateObj); 204 | }); 205 | 206 | // highlight the previous chosen date 207 | if (i + 1 == chosendate.getDate() && m == chosendate.getMonth() && y == chosendate.getFullYear()) cell.addClass('chosen'); 208 | } 209 | } 210 | } 211 | 212 | /** closes the datepicker **/ 213 | // sets the currently matched input element's value to the date, if one is available 214 | // remove the table element from the DOM 215 | // indicate that there is no datepicker for the currently matched input element 216 | function closeIt(el, datepicker, dateObj) { 217 | if (dateObj && dateObj.constructor == Date) 218 | el.val(jQuery.fn.simpleDatepicker.formatOutput(dateObj)); 219 | datepicker.remove(); 220 | datepicker = null; 221 | jQuery.data(el.get(0), "simpleDatepicker", { hasDatepicker:false }); 222 | } 223 | 224 | // iterate the matched nodeset 225 | return this.each(function () { 226 | 227 | // functions and vars declared here are created for each matched element. so if 228 | // your functions need to manage or access per-node state you can defined them 229 | // here and use $this to get at the DOM element 230 | 231 | if (jQuery(this).is('input') && 'text' == jQuery(this).attr('type')) { 232 | 233 | var datepicker; 234 | jQuery.data(jQuery(this).get(0), "simpleDatepicker", { hasDatepicker:false }); 235 | 236 | // open a datepicker on the click event 237 | jQuery(this).click(function (ev) { 238 | 239 | var $this = jQuery(ev.target); 240 | 241 | if (false == jQuery.data($this.get(0), "simpleDatepicker").hasDatepicker) { 242 | 243 | // store data telling us there is already a datepicker 244 | jQuery.data($this.get(0), "simpleDatepicker", { hasDatepicker:true }); 245 | 246 | // validate the form's initial content for a date 247 | var initialDate = $this.val(); 248 | 249 | if (initialDate && dateRegEx.test(initialDate)) { 250 | var chosendate = new Date(initialDate); 251 | } else if (opts.chosendate.constructor == Date) { 252 | var chosendate = opts.chosendate; 253 | } else if (opts.chosendate) { 254 | var chosendate = new Date(opts.chosendate); 255 | } else { 256 | var chosendate = today; 257 | } 258 | 259 | // insert the datepicker in the DOM 260 | datepicker = newDatepickerHTML(); 261 | jQuery("body").prepend(datepicker); 262 | 263 | // position the datepicker 264 | var elPos = findPosition($this.get(0)); 265 | var x = (parseInt(opts.x) ? parseInt(opts.x) : 0) + elPos[0]; 266 | var y = (parseInt(opts.y) ? parseInt(opts.y) : 0) + elPos[1]; 267 | jQuery(datepicker).css({ position:'absolute', left:x, top:y }); 268 | 269 | // bind events to the table controls 270 | jQuery("span", datepicker).css("cursor", "pointer"); 271 | jQuery("select", datepicker).bind('change', function () { 272 | loadMonth(null, $this, datepicker, chosendate); 273 | }); 274 | jQuery("span.prevMonth", datepicker).click(function (e) { 275 | loadMonth(e, $this, datepicker, chosendate); 276 | }); 277 | jQuery("span.nextMonth", datepicker).click(function (e) { 278 | loadMonth(e, $this, datepicker, chosendate); 279 | }); 280 | jQuery("span.today", datepicker).click(function () { 281 | closeIt($this, datepicker, new Date()); 282 | }); 283 | jQuery("span.close", datepicker).click(function () { 284 | closeIt($this, datepicker); 285 | }); 286 | 287 | // set the initial values for the month and year select fields 288 | // and load the first month 289 | jQuery("select[name=month]", datepicker).get(0).selectedIndex = chosendate.getMonth(); 290 | jQuery("select[name=year]", datepicker).get(0).selectedIndex = Math.max(0, chosendate.getFullYear() - opts.startyear); 291 | loadMonth(null, $this, datepicker, chosendate); 292 | } 293 | 294 | }); 295 | } 296 | 297 | }); 298 | 299 | }; 300 | 301 | // finally, I like to expose default plugin options as public so they can be manipulated. one 302 | // way to do this is to add a property to the already-public plugin fn 303 | 304 | jQuery.fn.simpleDatepicker.formatOutput = function (dateObj) { 305 | return (dateObj.getMonth() + 1) + "/" + dateObj.getDate() + "/" + dateObj.getFullYear(); 306 | }; 307 | 308 | jQuery.fn.simpleDatepicker.defaults = { 309 | // date string matching /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/ 310 | chosendate:today, 311 | 312 | // date string matching /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/ 313 | // or four digit year 314 | startdate:today.getFullYear(), 315 | enddate:today.getFullYear() + 1, 316 | 317 | // offset from the top left corner of the input element 318 | x:18, // must be in px 319 | y:18 // must be in px 320 | }; 321 | 322 | })(jQuery); -------------------------------------------------------------------------------- /public/javascripts/pagedown/LICENSE.txt: -------------------------------------------------------------------------------- 1 | A javascript port of Markdown, as used on Stack Overflow 2 | and the rest of Stack Exchange network. 3 | 4 | Largely based on showdown.js by John Fraser (Attacklab). 5 | 6 | Original Markdown Copyright (c) 2004-2005 John Gruber 7 | 8 | 9 | 10 | Original Showdown code copyright (c) 2007 John Fraser 11 | 12 | Modifications and bugfixes (c) 2009 Dana Robinson 13 | Modifications and bugfixes (c) 2009-2011 Stack Exchange Inc. 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | 33 | -------------------------------------------------------------------------------- /public/javascripts/pagedown/Markdown.Sanitizer.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var output, Converter; 3 | if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module 4 | output = exports; 5 | Converter = require("./Markdown.Converter").Converter; 6 | } else { 7 | output = window.Markdown; 8 | Converter = output.Converter; 9 | } 10 | 11 | output.getSanitizingConverter = function () { 12 | var converter = new Converter(); 13 | converter.hooks.chain("postConversion", sanitizeHtml); 14 | converter.hooks.chain("postConversion", balanceTags); 15 | return converter; 16 | } 17 | 18 | function sanitizeHtml(html) { 19 | return html.replace(/<[^>]*>?/gi, sanitizeTag); 20 | } 21 | 22 | // (tags that can be opened/closed) | (tags that stand alone) 23 | var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i; 24 | // | 25 | var a_white = /^(]+")?\s?>|<\/a>)$/i; 26 | 27 | // ]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i; 29 | 30 | function sanitizeTag(tag) { 31 | if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white)) 32 | return tag; 33 | else 34 | return ""; 35 | } 36 | 37 | /// 38 | /// attempt to balance HTML tags in the html string 39 | /// by removing any unmatched opening or closing tags 40 | /// IMPORTANT: we *assume* HTML has *already* been 41 | /// sanitized and is safe/sane before balancing! 42 | /// 43 | /// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593 44 | /// 45 | function balanceTags(html) { 46 | 47 | if (html == "") 48 | return ""; 49 | 50 | var re = /<\/?\w+[^>]*(\s|$|>)/g; 51 | // convert everything to lower case; this makes 52 | // our case insensitive comparisons easier 53 | var tags = html.toLowerCase().match(re); 54 | 55 | // no HTML tags present? nothing to do; exit now 56 | var tagcount = (tags || []).length; 57 | if (tagcount == 0) 58 | return html; 59 | 60 | var tagname, tag; 61 | var ignoredtags = "



  • "; 62 | var match; 63 | var tagpaired = []; 64 | var tagremove = []; 65 | var needsRemoval = false; 66 | 67 | // loop through matched tags in forward order 68 | for (var ctag = 0; ctag < tagcount; ctag++) { 69 | tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1"); 70 | // skip any already paired tags 71 | // and skip tags in our ignore list; assume they're self-closed 72 | if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1) 73 | continue; 74 | 75 | tag = tags[ctag]; 76 | match = -1; 77 | 78 | if (!/^<\//.test(tag)) { 79 | // this is an opening tag 80 | // search forwards (next tags), look for closing tags 81 | for (var ntag = ctag + 1; ntag < tagcount; ntag++) { 82 | if (!tagpaired[ntag] && tags[ntag] == "") { 83 | match = ntag; 84 | break; 85 | } 86 | } 87 | } 88 | 89 | if (match == -1) 90 | needsRemoval = tagremove[ctag] = true; // mark for removal 91 | else 92 | tagpaired[match] = true; // mark paired 93 | } 94 | 95 | if (!needsRemoval) 96 | return html; 97 | 98 | // delete all orphaned tags from the string 99 | 100 | var ctag = 0; 101 | html = html.replace(re, function (match) { 102 | var res = tagremove[ctag] ? "" : match; 103 | ctag++; 104 | return res; 105 | }); 106 | return html; 107 | } 108 | })(); 109 | -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-apollo.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/javascripts/prettify/lang-apollo.js -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-clj.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2011 Google Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | var a=null; 17 | PR.registerLangHandler(PR.createSimpleLexer([["opn",/^[([{]+/,a,"([{"],["clo",/^[)\]}]+/,a,")]}"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \xa0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|monitor-exit|defmacro|defn|defn-|macroexpand|macroexpand-1|for|doseq|dosync|dotimes|and|or|when|not|assert|doto|proxy|defstruct|first|rest|cons|defprotocol|deftype|defrecord|reify|defmulti|defmethod|meta|with-meta|ns|in-ns|create-ns|import|intern|refer|alias|namespace|resolve|ref|deref|refset|new|set!|memfn|to-array|into-array|aset|gen-class|reduce|map|filter|find|nil?|empty?|hash-map|hash-set|vec|vector|seq|flatten|reverse|assoc|dissoc|list|list?|disj|get|union|difference|intersection|extend|extend-type|extend-protocol|prn)\b/,a], 18 | ["typ",/^:[\dA-Za-z-]+/]]),["clj"]); 19 | -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-css.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", 2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); 3 | -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-go.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/javascripts/prettify/lang-go.js -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-hs.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t-\r ]+/,null,"\t\n \r "],["str",/^"(?:[^\n\f\r"\\]|\\[\S\s])*(?:"|$)/,null,'"'],["str",/^'(?:[^\n\f\r'\\]|\\[^&])'?/,null,"'"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)/i,null,"0123456789"]],[["com",/^(?:--+[^\n\f\r]*|{-(?:[^-]|-+[^}-])*-})/],["kwd",/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^\d'A-Za-z]|$)/, 2 | null],["pln",/^(?:[A-Z][\w']*\.)*[A-Za-z][\w']*/],["pun",/^[^\d\t-\r "'A-Za-z]+/]]),["hs"]); 3 | -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-lisp.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,a,"("],["clo",/^\)+/,a,")"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \xa0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/,a], 3 | ["lit",/^[+-]?(?:[#0]x[\da-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[de][+-]?\d+)?)/i],["lit",/^'(?:-*(?:\w|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?)?/],["pln",/^-*(?:[_a-z]|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?/i],["pun",/^[^\w\t\n\r "'-);\\\xa0]+/]]),["cl","el","lisp","scm"]); 4 | -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-lua.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/javascripts/prettify/lang-lua.js -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-ml.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/javascripts/prettify/lang-ml.js -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-n.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["str",/^(?:'(?:[^\n\r'\\]|\\.)*'|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,a,'"'],["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,a,"#"],["pln",/^\s+/,a," \r\n\t\xa0"]],[["str",/^@"(?:[^"]|"")*(?:"|$)/,a],["str",/^<#[^#>]*(?:#>|$)/,a],["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,a],["com",/^\/\/[^\n\r]*/,a],["com",/^\/\*[\S\s]*?(?:\*\/|$)/, 3 | a],["kwd",/^(?:abstract|and|as|base|catch|class|def|delegate|enum|event|extern|false|finally|fun|implements|interface|internal|is|macro|match|matches|module|mutable|namespace|new|null|out|override|params|partial|private|protected|public|ref|sealed|static|struct|syntax|this|throw|true|try|type|typeof|using|variant|virtual|volatile|when|where|with|assert|assert2|async|break|checked|continue|do|else|ensures|for|foreach|if|late|lock|new|nolate|otherwise|regexp|repeat|requires|return|surroundwith|unchecked|unless|using|while|yield)\b/, 4 | a],["typ",/^(?:array|bool|byte|char|decimal|double|float|int|list|long|object|sbyte|short|string|ulong|uint|ufloat|ulong|ushort|void)\b/,a],["lit",/^@[$_a-z][\w$@]*/i,a],["typ",/^@[A-Z]+[a-z][\w$@]*/,a],["pln",/^'?[$_a-z][\w$@]*/i,a],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,a,"0123456789"],["pun",/^.[^\s\w"-$'./@`]*/,a]]),["n","nemerle"]); 5 | -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-proto.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.sourceDecorator({keywords:"bytes,default,double,enum,extend,extensions,false,group,import,max,message,option,optional,package,repeated,required,returns,rpc,service,syntax,to,true",types:/^(bool|(double|s?fixed|[su]?int)(32|64)|float|string)\b/,cStyleComments:!0}),["proto"]); 2 | -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-scala.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/javascripts/prettify/lang-scala.js -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-sql.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/javascripts/prettify/lang-sql.js -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-tex.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/javascripts/prettify/lang-tex.js -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-vb.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/javascripts/prettify/lang-vb.js -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-vhdl.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/javascripts/prettify/lang-vhdl.js -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-wiki.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanghaipeng404/node.js-blog-starter/959f0ca10c11ad919e8d9ceb52fc349eae3e6fc3/public/javascripts/prettify/lang-wiki.js -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-xq.js: -------------------------------------------------------------------------------- 1 | PR.registerLangHandler(PR.createSimpleLexer([["var pln",/^\$[\w-]+/,null,"$"]],[["pln",/^[\s=][<>][\s=]/],["lit",/^@[\w-]+/],["tag",/^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["com",/^\(:[\S\s]*?:\)/],["pln",/^[(),/;[\]{}]$/],["str",/^(?:"(?:[^"\\{]|\\[\S\s])*(?:"|$)|'(?:[^'\\{]|\\[\S\s])*(?:'|$))/,null,"\"'"],["kwd",/^(?:xquery|where|version|variable|union|typeswitch|treat|to|then|text|stable|sortby|some|self|schema|satisfies|returns|return|ref|processing-instruction|preceding-sibling|preceding|precedes|parent|only|of|node|namespace|module|let|item|intersect|instance|in|import|if|function|for|follows|following-sibling|following|external|except|every|else|element|descending|descendant-or-self|descendant|define|default|declare|comment|child|cast|case|before|attribute|assert|ascending|as|ancestor-or-self|ancestor|after|eq|order|by|or|and|schema-element|document-node|node|at)\b/], 2 | ["typ",/^(?:xs:yearMonthDuration|xs:unsignedLong|xs:time|xs:string|xs:short|xs:QName|xs:Name|xs:long|xs:integer|xs:int|xs:gYearMonth|xs:gYear|xs:gMonthDay|xs:gDay|xs:float|xs:duration|xs:double|xs:decimal|xs:dayTimeDuration|xs:dateTime|xs:date|xs:byte|xs:boolean|xs:anyURI|xf:yearMonthDuration)\b/,null],["fun pln",/^(?:xp:dereference|xinc:node-expand|xinc:link-references|xinc:link-expand|xhtml:restructure|xhtml:clean|xhtml:add-lists|xdmp:zip-manifest|xdmp:zip-get|xdmp:zip-create|xdmp:xquery-version|xdmp:word-convert|xdmp:with-namespaces|xdmp:version|xdmp:value|xdmp:user-roles|xdmp:user-last-login|xdmp:user|xdmp:url-encode|xdmp:url-decode|xdmp:uri-is-file|xdmp:uri-format|xdmp:uri-content-type|xdmp:unquote|xdmp:unpath|xdmp:triggers-database|xdmp:trace|xdmp:to-json|xdmp:tidy|xdmp:subbinary|xdmp:strftime|xdmp:spawn-in|xdmp:spawn|xdmp:sleep|xdmp:shutdown|xdmp:set-session-field|xdmp:set-response-encoding|xdmp:set-response-content-type|xdmp:set-response-code|xdmp:set-request-time-limit|xdmp:set|xdmp:servers|xdmp:server-status|xdmp:server-name|xdmp:server|xdmp:security-database|xdmp:security-assert|xdmp:schema-database|xdmp:save|xdmp:role-roles|xdmp:role|xdmp:rethrow|xdmp:restart|xdmp:request-timestamp|xdmp:request-status|xdmp:request-cancel|xdmp:request|xdmp:redirect-response|xdmp:random|xdmp:quote|xdmp:query-trace|xdmp:query-meters|xdmp:product-edition|xdmp:privilege-roles|xdmp:privilege|xdmp:pretty-print|xdmp:powerpoint-convert|xdmp:platform|xdmp:permission|xdmp:pdf-convert|xdmp:path|xdmp:octal-to-integer|xdmp:node-uri|xdmp:node-replace|xdmp:node-kind|xdmp:node-insert-child|xdmp:node-insert-before|xdmp:node-insert-after|xdmp:node-delete|xdmp:node-database|xdmp:mul64|xdmp:modules-root|xdmp:modules-database|xdmp:merging|xdmp:merge-cancel|xdmp:merge|xdmp:md5|xdmp:logout|xdmp:login|xdmp:log-level|xdmp:log|xdmp:lock-release|xdmp:lock-acquire|xdmp:load|xdmp:invoke-in|xdmp:invoke|xdmp:integer-to-octal|xdmp:integer-to-hex|xdmp:http-put|xdmp:http-post|xdmp:http-options|xdmp:http-head|xdmp:http-get|xdmp:http-delete|xdmp:hosts|xdmp:host-status|xdmp:host-name|xdmp:host|xdmp:hex-to-integer|xdmp:hash64|xdmp:hash32|xdmp:has-privilege|xdmp:groups|xdmp:group-serves|xdmp:group-servers|xdmp:group-name|xdmp:group-hosts|xdmp:group|xdmp:get-session-field-names|xdmp:get-session-field|xdmp:get-response-encoding|xdmp:get-response-code|xdmp:get-request-username|xdmp:get-request-user|xdmp:get-request-url|xdmp:get-request-protocol|xdmp:get-request-path|xdmp:get-request-method|xdmp:get-request-header-names|xdmp:get-request-header|xdmp:get-request-field-names|xdmp:get-request-field-filename|xdmp:get-request-field-content-type|xdmp:get-request-field|xdmp:get-request-client-certificate|xdmp:get-request-client-address|xdmp:get-request-body|xdmp:get-current-user|xdmp:get-current-roles|xdmp:get|xdmp:function-name|xdmp:function-module|xdmp:function|xdmp:from-json|xdmp:forests|xdmp:forest-status|xdmp:forest-restore|xdmp:forest-restart|xdmp:forest-name|xdmp:forest-delete|xdmp:forest-databases|xdmp:forest-counts|xdmp:forest-clear|xdmp:forest-backup|xdmp:forest|xdmp:filesystem-file|xdmp:filesystem-directory|xdmp:exists|xdmp:excel-convert|xdmp:eval-in|xdmp:eval|xdmp:estimate|xdmp:email|xdmp:element-content-type|xdmp:elapsed-time|xdmp:document-set-quality|xdmp:document-set-property|xdmp:document-set-properties|xdmp:document-set-permissions|xdmp:document-set-collections|xdmp:document-remove-properties|xdmp:document-remove-permissions|xdmp:document-remove-collections|xdmp:document-properties|xdmp:document-locks|xdmp:document-load|xdmp:document-insert|xdmp:document-get-quality|xdmp:document-get-properties|xdmp:document-get-permissions|xdmp:document-get-collections|xdmp:document-get|xdmp:document-forest|xdmp:document-delete|xdmp:document-add-properties|xdmp:document-add-permissions|xdmp:document-add-collections|xdmp:directory-properties|xdmp:directory-locks|xdmp:directory-delete|xdmp:directory-create|xdmp:directory|xdmp:diacritic-less|xdmp:describe|xdmp:default-permissions|xdmp:default-collections|xdmp:databases|xdmp:database-restore-validate|xdmp:database-restore-status|xdmp:database-restore-cancel|xdmp:database-restore|xdmp:database-name|xdmp:database-forests|xdmp:database-backup-validate|xdmp:database-backup-status|xdmp:database-backup-purge|xdmp:database-backup-cancel|xdmp:database-backup|xdmp:database|xdmp:collection-properties|xdmp:collection-locks|xdmp:collection-delete|xdmp:collation-canonical-uri|xdmp:castable-as|xdmp:can-grant-roles|xdmp:base64-encode|xdmp:base64-decode|xdmp:architecture|xdmp:apply|xdmp:amp-roles|xdmp:amp|xdmp:add64|xdmp:add-response-header|xdmp:access|trgr:trigger-set-recursive|trgr:trigger-set-permissions|trgr:trigger-set-name|trgr:trigger-set-module|trgr:trigger-set-event|trgr:trigger-set-description|trgr:trigger-remove-permissions|trgr:trigger-module|trgr:trigger-get-permissions|trgr:trigger-enable|trgr:trigger-disable|trgr:trigger-database-online-event|trgr:trigger-data-event|trgr:trigger-add-permissions|trgr:remove-trigger|trgr:property-content|trgr:pre-commit|trgr:post-commit|trgr:get-trigger-by-id|trgr:get-trigger|trgr:document-scope|trgr:document-content|trgr:directory-scope|trgr:create-trigger|trgr:collection-scope|trgr:any-property-content|thsr:set-entry|thsr:remove-term|thsr:remove-synonym|thsr:remove-entry|thsr:query-lookup|thsr:lookup|thsr:load|thsr:insert|thsr:expand|thsr:add-synonym|spell:suggest-detailed|spell:suggest|spell:remove-word|spell:make-dictionary|spell:load|spell:levenshtein-distance|spell:is-correct|spell:insert|spell:double-metaphone|spell:add-word|sec:users-collection|sec:user-set-roles|sec:user-set-password|sec:user-set-name|sec:user-set-description|sec:user-set-default-permissions|sec:user-set-default-collections|sec:user-remove-roles|sec:user-privileges|sec:user-get-roles|sec:user-get-description|sec:user-get-default-permissions|sec:user-get-default-collections|sec:user-doc-permissions|sec:user-doc-collections|sec:user-add-roles|sec:unprotect-collection|sec:uid-for-name|sec:set-realm|sec:security-version|sec:security-namespace|sec:security-installed|sec:security-collection|sec:roles-collection|sec:role-set-roles|sec:role-set-name|sec:role-set-description|sec:role-set-default-permissions|sec:role-set-default-collections|sec:role-remove-roles|sec:role-privileges|sec:role-get-roles|sec:role-get-description|sec:role-get-default-permissions|sec:role-get-default-collections|sec:role-doc-permissions|sec:role-doc-collections|sec:role-add-roles|sec:remove-user|sec:remove-role-from-users|sec:remove-role-from-role|sec:remove-role-from-privileges|sec:remove-role-from-amps|sec:remove-role|sec:remove-privilege|sec:remove-amp|sec:protect-collection|sec:privileges-collection|sec:privilege-set-roles|sec:privilege-set-name|sec:privilege-remove-roles|sec:privilege-get-roles|sec:privilege-add-roles|sec:priv-doc-permissions|sec:priv-doc-collections|sec:get-user-names|sec:get-unique-elem-id|sec:get-role-names|sec:get-role-ids|sec:get-privilege|sec:get-distinct-permissions|sec:get-collection|sec:get-amp|sec:create-user-with-role|sec:create-user|sec:create-role|sec:create-privilege|sec:create-amp|sec:collections-collection|sec:collection-set-permissions|sec:collection-remove-permissions|sec:collection-get-permissions|sec:collection-add-permissions|sec:check-admin|sec:amps-collection|sec:amp-set-roles|sec:amp-remove-roles|sec:amp-get-roles|sec:amp-doc-permissions|sec:amp-doc-collections|sec:amp-add-roles|search:unparse|search:suggest|search:snippet|search:search|search:resolve-nodes|search:resolve|search:remove-constraint|search:parse|search:get-default-options|search:estimate|search:check-options|prof:value|prof:reset|prof:report|prof:invoke|prof:eval|prof:enable|prof:disable|prof:allowed|ppt:clean|pki:template-set-request|pki:template-set-name|pki:template-set-key-type|pki:template-set-key-options|pki:template-set-description|pki:template-in-use|pki:template-get-version|pki:template-get-request|pki:template-get-name|pki:template-get-key-type|pki:template-get-key-options|pki:template-get-id|pki:template-get-description|pki:need-certificate|pki:is-temporary|pki:insert-trusted-certificates|pki:insert-template|pki:insert-signed-certificates|pki:insert-certificate-revocation-list|pki:get-trusted-certificate-ids|pki:get-template-ids|pki:get-template-certificate-authority|pki:get-template-by-name|pki:get-template|pki:get-pending-certificate-requests-xml|pki:get-pending-certificate-requests-pem|pki:get-pending-certificate-request|pki:get-certificates-for-template-xml|pki:get-certificates-for-template|pki:get-certificates|pki:get-certificate-xml|pki:get-certificate-pem|pki:get-certificate|pki:generate-temporary-certificate-if-necessary|pki:generate-temporary-certificate|pki:generate-template-certificate-authority|pki:generate-certificate-request|pki:delete-template|pki:delete-certificate|pki:create-template|pdf:make-toc|pdf:insert-toc-headers|pdf:get-toc|pdf:clean|p:status-transition|p:state-transition|p:remove|p:pipelines|p:insert|p:get-by-id|p:get|p:execute|p:create|p:condition|p:collection|p:action|ooxml:runs-merge|ooxml:package-uris|ooxml:package-parts-insert|ooxml:package-parts|msword:clean|mcgm:polygon|mcgm:point|mcgm:geospatial-query-from-elements|mcgm:geospatial-query|mcgm:circle|math:tanh|math:tan|math:sqrt|math:sinh|math:sin|math:pow|math:modf|math:log10|math:log|math:ldexp|math:frexp|math:fmod|math:floor|math:fabs|math:exp|math:cosh|math:cos|math:ceil|math:atan2|math:atan|math:asin|math:acos|map:put|map:map|map:keys|map:get|map:delete|map:count|map:clear|lnk:to|lnk:remove|lnk:insert|lnk:get|lnk:from|lnk:create|kml:polygon|kml:point|kml:interior-polygon|kml:geospatial-query-from-elements|kml:geospatial-query|kml:circle|kml:box|gml:polygon|gml:point|gml:interior-polygon|gml:geospatial-query-from-elements|gml:geospatial-query|gml:circle|gml:box|georss:point|georss:geospatial-query|georss:circle|geo:polygon|geo:point|geo:interior-polygon|geo:geospatial-query-from-elements|geo:geospatial-query|geo:circle|geo:box|fn:zero-or-one|fn:years-from-duration|fn:year-from-dateTime|fn:year-from-date|fn:upper-case|fn:unordered|fn:true|fn:translate|fn:trace|fn:tokenize|fn:timezone-from-time|fn:timezone-from-dateTime|fn:timezone-from-date|fn:sum|fn:subtract-dateTimes-yielding-yearMonthDuration|fn:subtract-dateTimes-yielding-dayTimeDuration|fn:substring-before|fn:substring-after|fn:substring|fn:subsequence|fn:string-to-codepoints|fn:string-pad|fn:string-length|fn:string-join|fn:string|fn:static-base-uri|fn:starts-with|fn:seconds-from-time|fn:seconds-from-duration|fn:seconds-from-dateTime|fn:round-half-to-even|fn:round|fn:root|fn:reverse|fn:resolve-uri|fn:resolve-QName|fn:replace|fn:remove|fn:QName|fn:prefix-from-QName|fn:position|fn:one-or-more|fn:number|fn:not|fn:normalize-unicode|fn:normalize-space|fn:node-name|fn:node-kind|fn:nilled|fn:namespace-uri-from-QName|fn:namespace-uri-for-prefix|fn:namespace-uri|fn:name|fn:months-from-duration|fn:month-from-dateTime|fn:month-from-date|fn:minutes-from-time|fn:minutes-from-duration|fn:minutes-from-dateTime|fn:min|fn:max|fn:matches|fn:lower-case|fn:local-name-from-QName|fn:local-name|fn:last|fn:lang|fn:iri-to-uri|fn:insert-before|fn:index-of|fn:in-scope-prefixes|fn:implicit-timezone|fn:idref|fn:id|fn:hours-from-time|fn:hours-from-duration|fn:hours-from-dateTime|fn:floor|fn:false|fn:expanded-QName|fn:exists|fn:exactly-one|fn:escape-uri|fn:escape-html-uri|fn:error|fn:ends-with|fn:encode-for-uri|fn:empty|fn:document-uri|fn:doc-available|fn:doc|fn:distinct-values|fn:distinct-nodes|fn:default-collation|fn:deep-equal|fn:days-from-duration|fn:day-from-dateTime|fn:day-from-date|fn:data|fn:current-time|fn:current-dateTime|fn:current-date|fn:count|fn:contains|fn:concat|fn:compare|fn:collection|fn:codepoints-to-string|fn:codepoint-equal|fn:ceiling|fn:boolean|fn:base-uri|fn:avg|fn:adjust-time-to-timezone|fn:adjust-dateTime-to-timezone|fn:adjust-date-to-timezone|fn:abs|feed:unsubscribe|feed:subscription|feed:subscribe|feed:request|feed:item|feed:description|excel:clean|entity:enrich|dom:set-pipelines|dom:set-permissions|dom:set-name|dom:set-evaluation-context|dom:set-domain-scope|dom:set-description|dom:remove-pipeline|dom:remove-permissions|dom:remove|dom:get|dom:evaluation-context|dom:domains|dom:domain-scope|dom:create|dom:configuration-set-restart-user|dom:configuration-set-permissions|dom:configuration-set-evaluation-context|dom:configuration-set-default-domain|dom:configuration-get|dom:configuration-create|dom:collection|dom:add-pipeline|dom:add-permissions|dls:retention-rules|dls:retention-rule-remove|dls:retention-rule-insert|dls:retention-rule|dls:purge|dls:node-expand|dls:link-references|dls:link-expand|dls:documents-query|dls:document-versions-query|dls:document-version-uri|dls:document-version-query|dls:document-version-delete|dls:document-version-as-of|dls:document-version|dls:document-update|dls:document-unmanage|dls:document-set-quality|dls:document-set-property|dls:document-set-properties|dls:document-set-permissions|dls:document-set-collections|dls:document-retention-rules|dls:document-remove-properties|dls:document-remove-permissions|dls:document-remove-collections|dls:document-purge|dls:document-manage|dls:document-is-managed|dls:document-insert-and-manage|dls:document-include-query|dls:document-history|dls:document-get-permissions|dls:document-extract-part|dls:document-delete|dls:document-checkout-status|dls:document-checkout|dls:document-checkin|dls:document-add-properties|dls:document-add-permissions|dls:document-add-collections|dls:break-checkout|dls:author-query|dls:as-of-query|dbk:convert|dbg:wait|dbg:value|dbg:stopped|dbg:stop|dbg:step|dbg:status|dbg:stack|dbg:out|dbg:next|dbg:line|dbg:invoke|dbg:function|dbg:finish|dbg:expr|dbg:eval|dbg:disconnect|dbg:detach|dbg:continue|dbg:connect|dbg:clear|dbg:breakpoints|dbg:break|dbg:attached|dbg:attach|cvt:save-converted-documents|cvt:part-uri|cvt:destination-uri|cvt:basepath|cvt:basename|cts:words|cts:word-query-weight|cts:word-query-text|cts:word-query-options|cts:word-query|cts:word-match|cts:walk|cts:uris|cts:uri-match|cts:train|cts:tokenize|cts:thresholds|cts:stem|cts:similar-query-weight|cts:similar-query-nodes|cts:similar-query|cts:shortest-distance|cts:search|cts:score|cts:reverse-query-weight|cts:reverse-query-nodes|cts:reverse-query|cts:remainder|cts:registered-query-weight|cts:registered-query-options|cts:registered-query-ids|cts:registered-query|cts:register|cts:query|cts:quality|cts:properties-query-query|cts:properties-query|cts:polygon-vertices|cts:polygon|cts:point-longitude|cts:point-latitude|cts:point|cts:or-query-queries|cts:or-query|cts:not-query-weight|cts:not-query-query|cts:not-query|cts:near-query-weight|cts:near-query-queries|cts:near-query-options|cts:near-query-distance|cts:near-query|cts:highlight|cts:geospatial-co-occurrences|cts:frequency|cts:fitness|cts:field-words|cts:field-word-query-weight|cts:field-word-query-text|cts:field-word-query-options|cts:field-word-query-field-name|cts:field-word-query|cts:field-word-match|cts:entity-highlight|cts:element-words|cts:element-word-query-weight|cts:element-word-query-text|cts:element-word-query-options|cts:element-word-query-element-name|cts:element-word-query|cts:element-word-match|cts:element-values|cts:element-value-ranges|cts:element-value-query-weight|cts:element-value-query-text|cts:element-value-query-options|cts:element-value-query-element-name|cts:element-value-query|cts:element-value-match|cts:element-value-geospatial-co-occurrences|cts:element-value-co-occurrences|cts:element-range-query-weight|cts:element-range-query-value|cts:element-range-query-options|cts:element-range-query-operator|cts:element-range-query-element-name|cts:element-range-query|cts:element-query-query|cts:element-query-element-name|cts:element-query|cts:element-pair-geospatial-values|cts:element-pair-geospatial-value-match|cts:element-pair-geospatial-query-weight|cts:element-pair-geospatial-query-region|cts:element-pair-geospatial-query-options|cts:element-pair-geospatial-query-longitude-name|cts:element-pair-geospatial-query-latitude-name|cts:element-pair-geospatial-query-element-name|cts:element-pair-geospatial-query|cts:element-pair-geospatial-boxes|cts:element-geospatial-values|cts:element-geospatial-value-match|cts:element-geospatial-query-weight|cts:element-geospatial-query-region|cts:element-geospatial-query-options|cts:element-geospatial-query-element-name|cts:element-geospatial-query|cts:element-geospatial-boxes|cts:element-child-geospatial-values|cts:element-child-geospatial-value-match|cts:element-child-geospatial-query-weight|cts:element-child-geospatial-query-region|cts:element-child-geospatial-query-options|cts:element-child-geospatial-query-element-name|cts:element-child-geospatial-query-child-name|cts:element-child-geospatial-query|cts:element-child-geospatial-boxes|cts:element-attribute-words|cts:element-attribute-word-query-weight|cts:element-attribute-word-query-text|cts:element-attribute-word-query-options|cts:element-attribute-word-query-element-name|cts:element-attribute-word-query-attribute-name|cts:element-attribute-word-query|cts:element-attribute-word-match|cts:element-attribute-values|cts:element-attribute-value-ranges|cts:element-attribute-value-query-weight|cts:element-attribute-value-query-text|cts:element-attribute-value-query-options|cts:element-attribute-value-query-element-name|cts:element-attribute-value-query-attribute-name|cts:element-attribute-value-query|cts:element-attribute-value-match|cts:element-attribute-value-geospatial-co-occurrences|cts:element-attribute-value-co-occurrences|cts:element-attribute-range-query-weight|cts:element-attribute-range-query-value|cts:element-attribute-range-query-options|cts:element-attribute-range-query-operator|cts:element-attribute-range-query-element-name|cts:element-attribute-range-query-attribute-name|cts:element-attribute-range-query|cts:element-attribute-pair-geospatial-values|cts:element-attribute-pair-geospatial-value-match|cts:element-attribute-pair-geospatial-query-weight|cts:element-attribute-pair-geospatial-query-region|cts:element-attribute-pair-geospatial-query-options|cts:element-attribute-pair-geospatial-query-longitude-name|cts:element-attribute-pair-geospatial-query-latitude-name|cts:element-attribute-pair-geospatial-query-element-name|cts:element-attribute-pair-geospatial-query|cts:element-attribute-pair-geospatial-boxes|cts:document-query-uris|cts:document-query|cts:distance|cts:directory-query-uris|cts:directory-query-depth|cts:directory-query|cts:destination|cts:deregister|cts:contains|cts:confidence|cts:collections|cts:collection-query-uris|cts:collection-query|cts:collection-match|cts:classify|cts:circle-radius|cts:circle-center|cts:circle|cts:box-west|cts:box-south|cts:box-north|cts:box-east|cts:box|cts:bearing|cts:arc-intersection|cts:and-query-queries|cts:and-query-options|cts:and-query|cts:and-not-query-positive-query|cts:and-not-query-negative-query|cts:and-not-query|css:get|css:convert|cpf:success|cpf:failure|cpf:document-set-state|cpf:document-set-processing-status|cpf:document-set-last-updated|cpf:document-set-error|cpf:document-get-state|cpf:document-get-processing-status|cpf:document-get-last-updated|cpf:document-get-error|cpf:check-transition|alert:spawn-matching-actions|alert:rule-user-id-query|alert:rule-set-user-id|alert:rule-set-query|alert:rule-set-options|alert:rule-set-name|alert:rule-set-description|alert:rule-set-action|alert:rule-remove|alert:rule-name-query|alert:rule-insert|alert:rule-id-query|alert:rule-get-user-id|alert:rule-get-query|alert:rule-get-options|alert:rule-get-name|alert:rule-get-id|alert:rule-get-description|alert:rule-get-action|alert:rule-action-query|alert:remove-triggers|alert:make-rule|alert:make-log-action|alert:make-config|alert:make-action|alert:invoke-matching-actions|alert:get-my-rules|alert:get-all-rules|alert:get-actions|alert:find-matching-rules|alert:create-triggers|alert:config-set-uri|alert:config-set-trigger-ids|alert:config-set-options|alert:config-set-name|alert:config-set-description|alert:config-set-cpf-domain-names|alert:config-set-cpf-domain-ids|alert:config-insert|alert:config-get-uri|alert:config-get-trigger-ids|alert:config-get-options|alert:config-get-name|alert:config-get-id|alert:config-get-description|alert:config-get-cpf-domain-names|alert:config-get-cpf-domain-ids|alert:config-get|alert:config-delete|alert:action-set-options|alert:action-set-name|alert:action-set-module-root|alert:action-set-module-db|alert:action-set-module|alert:action-set-description|alert:action-remove|alert:action-insert|alert:action-get-options|alert:action-get-name|alert:action-get-module-root|alert:action-get-module-db|alert:action-get-module|alert:action-get-description|zero-or-one|years-from-duration|year-from-dateTime|year-from-date|upper-case|unordered|true|translate|trace|tokenize|timezone-from-time|timezone-from-dateTime|timezone-from-date|sum|subtract-dateTimes-yielding-yearMonthDuration|subtract-dateTimes-yielding-dayTimeDuration|substring-before|substring-after|substring|subsequence|string-to-codepoints|string-pad|string-length|string-join|string|static-base-uri|starts-with|seconds-from-time|seconds-from-duration|seconds-from-dateTime|round-half-to-even|round|root|reverse|resolve-uri|resolve-QName|replace|remove|QName|prefix-from-QName|position|one-or-more|number|not|normalize-unicode|normalize-space|node-name|node-kind|nilled|namespace-uri-from-QName|namespace-uri-for-prefix|namespace-uri|name|months-from-duration|month-from-dateTime|month-from-date|minutes-from-time|minutes-from-duration|minutes-from-dateTime|min|max|matches|lower-case|local-name-from-QName|local-name|last|lang|iri-to-uri|insert-before|index-of|in-scope-prefixes|implicit-timezone|idref|id|hours-from-time|hours-from-duration|hours-from-dateTime|floor|false|expanded-QName|exists|exactly-one|escape-uri|escape-html-uri|error|ends-with|encode-for-uri|empty|document-uri|doc-available|doc|distinct-values|distinct-nodes|default-collation|deep-equal|days-from-duration|day-from-dateTime|day-from-date|data|current-time|current-dateTime|current-date|count|contains|concat|compare|collection|codepoints-to-string|codepoint-equal|ceiling|boolean|base-uri|avg|adjust-time-to-timezone|adjust-dateTime-to-timezone|adjust-date-to-timezone|abs)\b/], 3 | ["pln",/^[\w:-]+/],["pln",/^[\t\n\r \xa0]+/]]),["xq","xquery"]); 4 | -------------------------------------------------------------------------------- /public/javascripts/prettify/lang-yaml.js: -------------------------------------------------------------------------------- 1 | var a=null; 2 | PR.registerLangHandler(PR.createSimpleLexer([["pun",/^[:>?|]+/,a,":|>?"],["dec",/^%(?:YAML|TAG)[^\n\r#]+/,a,"%"],["typ",/^&\S+/,a,"&"],["typ",/^!\S*/,a,"!"],["str",/^"(?:[^"\\]|\\.)*(?:"|$)/,a,'"'],["str",/^'(?:[^']|'')*(?:'|$)/,a,"'"],["com",/^#[^\n\r]*/,a,"#"],["pln",/^\s+/,a," \t\r\n"]],[["dec",/^(?:---|\.\.\.)(?:[\n\r]|$)/],["pun",/^-/],["kwd",/^\w+:[\n\r ]/],["pln",/^\w+/]]),["yaml","yml"]); 3 | -------------------------------------------------------------------------------- /public/javascripts/prettify/prettify.js: -------------------------------------------------------------------------------- 1 | var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; 2 | (function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= 3 | [],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), 9 | l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, 10 | q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, 11 | q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, 12 | "");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), 13 | a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} 14 | for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], 18 | "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], 19 | H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], 20 | J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ 21 | I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), 22 | ["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", 23 | /^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), 24 | ["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", 25 | hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= 26 | !k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p span { background: url(../../images/admin/wmd-buttons.png) no-repeat; width: 20px; height: 20px; display: inline-block; } 373 | .wmd-spacer1 { left: 50px; } 374 | .wmd-spacer2 { left: 175px; } 375 | .wmd-spacer3 { left: 300px; } 376 | .wmd-prompt-background { background-color: Black; } 377 | .wmd-prompt-dialog { border: 1px solid #999999; background-color: #F5F5F5; } 378 | .wmd-prompt-dialog > div { font-size: 0.8em; font-family: arial, helvetica, sans-serif; } 379 | .wmd-prompt-dialog > form > input[type="text"] { border: 1px solid #999999; color: black; } 380 | .wmd-prompt-dialog > form > input[type="button"]{ border: 1px solid #888888; font-family: trebuchet MS, helvetica, sans-serif; font-size: 0.8em; font-weight: bold; } 381 | 382 | .wmd-preview strong {font-weight: bold;} 383 | .wmd-preview em {font-style: italic;} 384 | .wmd-preview ul {list-style-type: disc; margin-left:1.5em;} 385 | .wmd-preview ol {list-style-type: decimal; margin-left:1.5em;} 386 | 387 | /**/ 388 | #comment-status { padding: 0 10px 10px 10px; border-bottom: 1px solid #ccc; } 389 | #comment-status a:link, a.city:visited, a.city:active { text-decoration: none; display: inline-block; font-size: 13px; line-height: 1em; padding: 4px 5px; border-radius: 3px; color: #555; } 390 | #comment-status a:hover { background: #eee; color: #000; text-decoration: none; } 391 | #comment-status a.current:link, a.current:visited, a.current:active { background: #334; color: #fff; } 392 | #comment-status a.current:hover { background: #445; color: #fff; } -------------------------------------------------------------------------------- /public/stylesheets/blog/style.css: -------------------------------------------------------------------------------- 1 | body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0} 2 | table{border-collapse:collapse;border-spacing:0} 3 | fieldset,img{border:0} 4 | address,caption,cite,code,dfn,em,strong,th,var{font-weight:normal;font-style:normal} 5 | ol,ul{list-style:none} 6 | caption,th{text-align:left} 7 | h1,h2,h3,h4,h5,h6{font-weight:normal;font-size:100%} 8 | q:before,q:after{content:''} 9 | abbr,acronym{border:0;font-variant:normal} 10 | input,textarea,select{font-weight:inherit;font-size:inherit;font-family:inherit} 11 | input.textfield, textarea {background-color: white;border: 1px solid #CCC;font-size: 13px;padding: 2px;} 12 | body{background:#e6e6e6;color: #444;font-weight:400;font-size:13px;font-family: "Microsoft Yahei", "Trebuchet MS", Georgia,"Times New Roman",Times,sans-serif;line-height: 1.7;} 13 | a{color:#3d658d;text-decoration:none} 14 | a:hover{text-decoration:underline} 15 | a img:hover{border:0;background:0} 16 | strong{font-weight:bold} 17 | code{padding:3px 4px;background-color:#f5f5f5} 18 | code,pre{padding:0 3px 2px;color:#333;font-size:12px;font-family: Menlo,Monaco,Consolas,"Lucida Console","Courier New","Microsoft Yahei",monospace} 19 | pre{display:block;margin:0 0 18px;padding:5px 7px;background-color:#F0F0F0;white-space:pre-wrap;font-size:12px;line-height:1.5em} 20 | pre.prettyprint{border:none;} 21 | pre code{padding:0;background-color:transparent} 22 | blockquote {margin: 10px 0;padding: 8px 10px 8px 12px;border-left: 5px solid #CCC;background: #F1F1F1;} 23 | 24 | ::selection{background:#c9302f;color:#fff;text-shadow:none} 25 | .alignleft{text-align:left} 26 | .alignright{text-align:right} 27 | .container{position:relative;overflow:hidden;margin:0 auto;padding:0 5px;width:820px} 28 | .block{position:relative;overflow:hidden;background:#fcfcfa;box-shadow:0 2px 6px rgba(100, 100, 100, 0.3);} 29 | .inner {padding:30px;} 30 | [class^="icon-"],[class*=" icon-"]{display:inline-block;width:16px;height:16px;background:url("../../images/blog/icons.png") no-repeat -16px -16px;vertical-align:text-top;line-height:16px} 31 | .icon-feed{background-position:-16px -16px} 32 | .icon-weibo{background-position:-48px -16px} 33 | .icon-twitter{background-position:-80px -16px} 34 | .sep5{height:5px} 35 | .sep10{height:10px} 36 | .alignright{ float: right;} 37 | .alignleft{ float: left;} 38 | 39 | #main-header{margin-bottom:20px;height:45px;background:#bb3131;background:linear-gradient(to bottom,#c63a3a 0,#c43837 29%,#c13635 55%,#bd3232 55%,#ba3030 77%,#b32b2b 100%);box-shadow:0 1px 10px rgba(0,0,0,0.2)} 40 | #logo{float:left;color:#f5f5f5;font-size:30px;line-height:45px;} 41 | #main-header nav{position:absolute;top:0;right:5px} 42 | #main-header nav ul li{display:inline} 43 | #main-header nav ul li a{float:left;display:block;margin-left:20px;color:#ddd;font-weight:600;line-height:45px;font-family: 'Microsoft Yahei';} 44 | #main-header nav ul li a:hover{color:#fff} 45 | #main-footer{clear:both;margin-top:20px;padding:10px 0;background:url("../../images/blog/footersub.png") repeat-x center top #dcdcdc} 46 | #main-footer p{margin-bottom:0;color:#818181;text-align:left} 47 | .title{margin-bottom:5px;font-weight:bold;font-size:1.8em;font-family:Pmingliu,Mingliu;line-height:1em} 48 | #main.page h1.title,#main.tags h1.title{margin-bottom:10px;padding-bottom:15px;} 49 | #main .post .meta{margin-bottom:15px;color:#777;font-size:10px} 50 | #main .post{margin-bottom:20px;padding:30px} 51 | #main.post .tags {font-size: 12px;} 52 | #main.tags section {padding: 30px } 53 | #main.tags article{margin: 10px 0;color: #666;} 54 | #main.tags article time {margin-right: 8px; color: #666; font-size: 13px;} 55 | #main.tags article a {font-size: 13px;} 56 | 57 | 58 | #post-entry .content{margin-bottom:10px;border-bottom:1px dashed #ccc} 59 | .post .entry{font-size: 14px;line-height: 1.9;} 60 | .post .entry p{margin-bottom:1em} 61 | .post .entry a{padding: 0 2px;} 62 | .post .entry a:hover{text-decoration: none;background: #c9302f;color: #fff;border-radius: 3px;} 63 | .post .entry h2,.post .entry h3{margin-bottom: 10px;padding: 4px 0;border-bottom: 1px dashed #ccc;} 64 | .post .entry h3{font-size:18px} 65 | .post .entry h3{font-size: 15px;} 66 | .post .entry ul,.post .entry ol{margin-bottom:1em;margin-left:20px;} 67 | .post .entry ol{list-style:decimal outside none} 68 | .post .entry ul{list-style-type: disc;} 69 | .post .entry hr{display:block;margin:1em 0;padding:0;height:1px;border:0 none #ccc;border-top:1px solid} 70 | .post .entry h1,.post .entry h2,.post .entry h3,.post .entry h4{font-weight:bold} 71 | .post .entry img{max-width:680px} 72 | .post .entry blockquote p {margin:0;} 73 | .entry.archives ul li time{margin-right:8px;color: #666;font-size: 13px;} 74 | .entry.archives h2 {color: #666;font-size: 13px;} 75 | #comments{ margin-bottom: 20px;} 76 | #comments h3{font-size:1.5em} 77 | #reply {margin-bottom: 5px;} 78 | #dsq-subscribe{display:none} 79 | .navigation{padding:8px;background:#fcfcfc;font-size:14px;margin-bottom: 10px;} 80 | #backtop{position:fixed;right:55px;bottom:80px;width:36px;height:36px;background:url('../../images/blog/backtop.gif') no-repeat;cursor:pointer} 81 | #backtop:hover{background-position:0 -36px} 82 | #backtop span{display:none} 83 | 84 | /* respond START */ 85 | #respond{font-size:11px;} 86 | #respond .row{margin-bottom:5px;} 87 | #respond .textfield{width:180px;margin-right:5px;} 88 | #respond .ratings{display:block;float:left;} 89 | #respond .post-ratings{padding-top:5px;} 90 | #author,#email,#url{background-image:url(../../images/blog/icons.png);background-repeat:no-repeat;padding:3px 1px 3px 24px;} 91 | #author{background-position:4px 4px;} 92 | #email{background-position:4px -29px;} 93 | #url{background-position:4px -63px;} 94 | 95 | textarea#comment{width:550px;resize:vertical;} 96 | #comment-submit{visibility:visible;padding:8px 18px;border:1px solid #080;border-bottom-color:#2B562B;font-size:14px;font-weight:700;cursor:pointer;font-weight:700;text-decoration:none;color:#FFF;text-shadow:0 1px 0 #5d4113;background:#080;background:-webkit-gradient(linear, left top, left bottom, from(#C3E5C5), color-stop(0.05, #54B059), to(#080));background:-moz-linear-gradient(top, #C3E5C5, #54B059 5%, #080);} 97 | #comment-submit:hover{border-bottom-color:#3A693A;background:#44A049;background:-webkit-gradient(linear, left top, left bottom, from(#FFF), color-stop(0.05, #63BC68), to(#44A049));background:-moz-linear-gradient(top, #FFF, #63BC68 5%, #44A049);} 98 | #no_author {display: none;} 99 | .commentlist .comment{list-style:none;border-bottom:1px solid #E2E2E2;overflow:hidden;padding:15px 20px 15px 30px;} 100 | .commentlist .comment:hover { background: #f5f5f5;} 101 | .commentlist .comment:last-child{border: none} 102 | 103 | .comment .avatar{float:left;margin-top: 4px;} 104 | .comment .info{font-size:10px;color:#999;} 105 | .comment .info .floor,.comment .info .reply {margin-left:5px;float: right;font-size: 10px;line-height: 10px;display: inline-block;background-color: #BBB;color: #FFF;padding: 3px 5px} 106 | .comment .reviewer { font-weight: 700; font-size: 12px; color: black; margin-right: 10px;} 107 | .comment .content{margin-left:46px;line-height:165%;} 108 | .comment .description p{margin-bottom:5px;} 109 | .comment .description blockquote,.comment .description pre{margin:10px 0;} 110 | .comment .description blockquote p{margin-top:0;margin-bottom:10px;} 111 | .comment .description ul,.comment .description ol{padding-top:10px;padding-left:20px;} 112 | .comment .description ul li,.comment .description ol li{list-style-position:outside;} 113 | 114 | 115 | @media print, screen and (max-width: 830px) { 116 | .container { 117 | width: auto; 118 | margin:0; 119 | } 120 | 121 | .inner { 122 | padding: 15px; 123 | } 124 | 125 | #main .post { 126 | padding: 15px; 127 | } 128 | 129 | #backtop { 130 | right: 10px; 131 | bottom: 10px; 132 | } 133 | 134 | .commentlist .comment { 135 | padding: 10px 15px; 136 | } 137 | 138 | #respond .textfield { 139 | width: 50%; 140 | } 141 | 142 | textarea#comment { 143 | width: 95%; 144 | resize: vertical; 145 | } 146 | } 147 | 148 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}} 149 | @media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}} 150 | pre.prettyprint{padding:10px 10px 7px 10px;border: 1px solid #ddd;}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} -------------------------------------------------------------------------------- /route/admin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /routes/admin.js 3 | */ 4 | 5 | var adminConfig = require('../config').adminConfig; 6 | var akismetConfig = require('../config').akismetConfig; 7 | 8 | var Admin = require('../models/admin'); 9 | var Page = require('../models/page'); 10 | var Post = require('../models/post'); 11 | var Comment = require('../models/comment'); 12 | var Tag = require('../models/tag'); 13 | 14 | var util = require('../libs/util'); 15 | 16 | // akismet 17 | var akismet = require('akismet').client({ 18 | blog: akismetConfig.blog, 19 | apiKey: akismetConfig.apiKey 20 | }); 21 | 22 | /** 23 | * home 24 | */ 25 | exports.home = function(req, res, next) { 26 | var data = { 27 | title: adminConfig.pageTitle 28 | } 29 | 30 | res.render('admin/home', data); 31 | }; 32 | 33 | /** 34 | * page列表页 35 | */ 36 | exports.pageIndex = function(req, res, exceptionHandler) { 37 | Page.find(null, null, null, function(err, pages) { 38 | if (err) { 39 | return exceptionHandler().handleError(err, req, res); 40 | } 41 | 42 | var data = { 43 | title: adminConfig.pageTitle, 44 | pages: pages 45 | } 46 | 47 | res.render('admin/page/index', data); 48 | }); 49 | }; 50 | 51 | /** 52 | * write page 53 | */ 54 | exports.pageWrite = function(req, res, next) { 55 | var data = { 56 | title: adminConfig.pageTitle 57 | } 58 | 59 | res.render('admin/page/write', data); 60 | }; 61 | 62 | /** 63 | * add page 64 | */ 65 | exports.pageCreate = function(req, res, exceptionHandler) { 66 | var created = new Date(); 67 | if (req.body.created) { 68 | created = new Date(req.body.created); 69 | } 70 | 71 | var page = new Page({ title: req.body.title, 72 | slug: req.body.slug, 73 | content: req.body.content, 74 | created: created }); 75 | 76 | page.save(function(err) { 77 | if (err) { 78 | return exceptionHandler().handleError(err, req, res); 79 | } 80 | 81 | res.redirect('/admin/pages'); 82 | }); 83 | }; 84 | 85 | /** 86 | * edit page 87 | */ 88 | exports.pageEdit = function(req, res, exceptionHandler) { 89 | if (!req.params.slug) { 90 | return exceptionHandler().handleNotFound(req, res); 91 | } 92 | 93 | Page.findBySlug(req.params.slug, null, function(err, page) { 94 | if (err) { 95 | return exceptionHandler().handleError(err, req, res); 96 | } 97 | 98 | var data = { 99 | title: adminConfig.pageTitle, 100 | page: page 101 | } 102 | 103 | res.render('admin/page/edit', data); 104 | }); 105 | }; 106 | 107 | /** 108 | * page update 109 | */ 110 | exports.pageUpdate = function(req, res, exceptionHandler) { 111 | if (!req.body.id) { 112 | return exceptionHandler().handleNotFound(req, res); 113 | } 114 | 115 | var page = { title: req.body.title, 116 | slug: req.body.slug, 117 | content: req.body.content }; 118 | if (req.body.created) { 119 | page.created = req.body.created; 120 | } 121 | 122 | Page.updateById(req.body.id, page, function(err) { 123 | if (err) { 124 | return exceptionHandler().handleError(err, req, res); 125 | } 126 | 127 | res.redirect('/admin/pages'); 128 | }); 129 | }; 130 | 131 | /** 132 | * posts列表 133 | */ 134 | exports.postIndex = function(req, res, next) { 135 | Post.findAll(null, null, 'title slug', function(err, posts) { 136 | if (err) { 137 | return exceptionHandler().handleError(err, req, res); 138 | } 139 | 140 | var data = { 141 | title: adminConfig.pageTitle, 142 | posts: posts 143 | } 144 | 145 | res.render('admin/post/index', data); 146 | }); 147 | }; 148 | 149 | /** 150 | * write post 151 | */ 152 | exports.postWrite = function(req, res) { 153 | var data = { 154 | title: adminConfig.pageTitle 155 | } 156 | 157 | res.render('admin/post/write', data); 158 | }; 159 | 160 | /** 161 | * add post 162 | */ 163 | exports.postCreate = function(req, res, exceptionHandler) { 164 | var tags = req.body.tags.split(','); 165 | var created = new Date(); 166 | if (req.body.created) { 167 | created = new Date(req.body.created); 168 | } 169 | 170 | // tags 171 | tags.forEach(function(tag, i) { 172 | tags[i] = { name: tag }; 173 | }); 174 | 175 | var post = new Post({ title: req.body.title, 176 | slug: req.body.slug, 177 | tags: tags, 178 | content: req.body.content, 179 | created: created }); 180 | 181 | post.save(function(err) { 182 | if (err) { 183 | return exceptionHandler().handleError(err, req, res); 184 | } 185 | 186 | res.redirect('/admin/posts'); 187 | }); 188 | } 189 | 190 | /** 191 | * edit post 192 | */ 193 | exports.postEdit = function(req, res, exceptionHandler) { 194 | if (!req.params.slug) { 195 | return exceptionHandler().handleNotFound(req, res); 196 | } 197 | 198 | Post.findBySlug(req.params.slug, null, function(err, post) { 199 | if (err) { 200 | return exceptionHandler().handleError(err, req, res); 201 | } 202 | 203 | // tags 204 | var tags = ''; 205 | post.tags.forEach(function(tag) { 206 | tags += tag.name + ','; 207 | }); 208 | tags = tags.substr(0, tags.length - 1); 209 | 210 | var data = { 211 | title: adminConfig.pageTitle, 212 | post: post, 213 | tags: tags 214 | } 215 | 216 | res.render('admin/post/edit', data); 217 | }); 218 | }; 219 | 220 | /** 221 | * update post 222 | */ 223 | exports.postUpdate = function(req, res, exceptionHandler) { 224 | if (!req.body.id) { 225 | return exceptionHandler().handleNotFound(req, res); 226 | } 227 | 228 | // tags 229 | var tags = req.body.tags.split(','); 230 | tags.forEach(function(tag, i) { 231 | tags[i] = { name: tag }; 232 | }); 233 | 234 | var post = { title: req.body.title, 235 | slug: req.body.slug, 236 | tags: tags, 237 | content: req.body.content }; 238 | if (req.body.created) { 239 | post.created = req.body.created; 240 | } 241 | 242 | Post.updateById(req.body.id, post, function(err) { 243 | if (err) { 244 | return exceptionHandler().handleError(err, req, res); 245 | } 246 | 247 | res.redirect('/admin/posts'); 248 | }); 249 | } 250 | 251 | /** 252 | * comments列表 253 | */ 254 | exports.commentIndex = function(req, res, exceptionHandler) { 255 | Post.findAll(null, '_id title slug comments', function(err, posts) { 256 | if (err) { 257 | return exceptionHandler().handleError(err, req, res); 258 | } 259 | 260 | // isSpam 0 1 261 | if (req.query['isSpam']) { 262 | var isSpam = req.query['isSpam']; 263 | } 264 | 265 | var data = { 266 | title: adminConfig.pageTitle, 267 | posts: posts, 268 | isSpam: isSpam 269 | } 270 | 271 | res.render('admin/comment/index', data); 272 | }); 273 | }; 274 | 275 | /** 276 | * comment delete 277 | */ 278 | exports.commentDelete = function(req, res, exceptionHandler) { 279 | if (!req.params.postId || !req.params.id) { 280 | return exceptionHandler().handleNotFound(req, res); 281 | } 282 | 283 | Post.deleteCommentById(req.params.postId, req.params.id, function(err) { 284 | if (err) { 285 | console.log(err); 286 | return exceptionHandler().handleError(err, req, res); 287 | } 288 | 289 | res.redirect('/admin/comments'); 290 | }); 291 | }; 292 | 293 | /** 294 | * 标记spam 295 | */ 296 | exports.commentSpam = function(req, res, exceptionHandler) { 297 | if (!req.params.id) { 298 | return exceptionHandler().handleNotFound(req, res); 299 | } 300 | 301 | var spam = true; 302 | if ('true' === req.query['spamStatus']) { 303 | console.log('sssss'); 304 | spam = false; 305 | } 306 | 307 | Post.spamCommentById(req.params.id, spam, function(err) { 308 | if (err) { 309 | return exceptionHandler().handleError(err, req, res); 310 | } 311 | 312 | res.redirect('/admin/comments'); 313 | }); 314 | }; 315 | 316 | /** 317 | * install 318 | */ 319 | exports.install = function(req, res, exceptionHandler) { 320 | var data = { 321 | title: 'node.js starter' 322 | } 323 | 324 | Admin.find(null, null, null, function(err, admins) { 325 | if (err) { 326 | return exceptionHandler().handleError(err, req, res); 327 | } 328 | 329 | // 已存在管理员员则说明初始化过了 330 | if (admins.length > 0) { 331 | data.installed = true; 332 | } 333 | 334 | res.render('admin/install', data); 335 | }); 336 | }; 337 | 338 | /** 339 | * do install 340 | */ 341 | exports.doInstall = function(req, res, exceptionHandler) { 342 | var admin = new Admin({ loginname: req.body.loginname, 343 | password: req.body.password }); 344 | 345 | admin.validate(function(err) { 346 | if (err) { 347 | var data = { messages: formatModelErrors(err) }; 348 | return renderError(res, '/admin/install', data); 349 | } 350 | 351 | // save admin 352 | admin.save(function(err) { 353 | if (err) { 354 | return exceptionHandler().handleError(err, req, res); 355 | } 356 | 357 | // new comment 358 | var comment = new Comment({ 359 | author: 'happen-zhang', 360 | email: 'zhanghaipeng404@gmail.com', 361 | website: 'http://github.com/happen-zhang', 362 | content: "```\r\n echo 'Nice to meet you!'\r\n```" 363 | }); 364 | 365 | var tag = new Tag({ 366 | name: 'Node.js' 367 | }); 368 | 369 | // add post 370 | var post = new Post({ 371 | title: 'Hello World!', 372 | slug: 'hello-world', 373 | content: "```\r\n echo 'Hello World'\r\n```", 374 | comments: [comment], 375 | tags: [tag] 376 | }); 377 | 378 | // save post 379 | post.save(function(err) { 380 | if (err) { 381 | return exceptionHandler().handleError(err, req, res); 382 | } 383 | 384 | // save page 385 | var about = new Page({ 386 | title: '关于我', 387 | slug: 'about', 388 | content: "```\r\n echo 'Hello World'\r\n```" 389 | }); 390 | 391 | var links = new Page({ 392 | title: '友情链接', 393 | slug: 'links', 394 | content: "*\r\n [Google](https://www.google.com.hk/)" 395 | }); 396 | 397 | about.save(function(err) { 398 | if (err) { 399 | return exceptionHandler().handleError(err, req, res); 400 | } 401 | }); 402 | 403 | links.save(function(err) { 404 | if (err) { 405 | return exceptionHandler().handleError(err, req, res); 406 | } 407 | }); 408 | 409 | var data = { 410 | msg: 'success', 411 | title: adminConfig.title 412 | }; 413 | 414 | res.render('admin/install', data); 415 | }); 416 | }); 417 | }); 418 | } 419 | 420 | /** 421 | * 验证akismet key 422 | */ 423 | exports.verifyAkismet = function(req, res, exceptionHandler) { 424 | akismet.verifyKey(function(err, isValidate) { 425 | if (err) { 426 | return exceptionHandler().handleError(err, req, res); 427 | } 428 | 429 | var status = false; 430 | if (isValidate) { 431 | status = true; 432 | } 433 | 434 | var data = { 435 | title: adminConfig.pageTitle, 436 | status: status 437 | } 438 | 439 | res.render('admin/verify_akismet', data); 440 | }); 441 | }; 442 | 443 | /** 444 | * login 445 | */ 446 | exports.login = function(req, res, next) { 447 | // 已登录则重定向到home 448 | if (hasLogin(req)) { 449 | return res.redirect('/admin/home'); 450 | } 451 | 452 | var data = { 453 | title: adminConfig.pageTitle 454 | } 455 | 456 | res.render('admin/login', data); 457 | }; 458 | 459 | /** 460 | * do login 461 | */ 462 | exports.doLogin = function(req, res, exceptionHandler) { 463 | var loginname = req.body.loginname.trim(); 464 | var password = req.body.password.trim(); 465 | var admin = new Admin({ loginname: loginname, 466 | password: password }); 467 | 468 | admin.validate(function(err) { 469 | if (err) { 470 | var messages = []; 471 | for (var error in err.errors) { 472 | messages.push(err.errors[error].message); 473 | } 474 | 475 | var data = { messages: messages }; 476 | return renderError(res, '/admin', data); 477 | } 478 | 479 | // 验证登陆 480 | Admin.getAuthenticated(loginname, password, function(err, admin, reason) { 481 | if (err) { 482 | return exceptionHandler().handleError(err, req, res); 483 | } 484 | 485 | if (null !== admin) { 486 | // login ok 487 | generateSession(req, admin); 488 | res.redirect('/admin/home'); 489 | } 490 | 491 | var reasons = Admin.failedLogin; 492 | var data = { messages: [] }; 493 | switch (reason) { 494 | case reasons.NOT_FOUND: 495 | data.messages.push('Login Name不存在'); 496 | break; 497 | 498 | case reasons.PASSWORD_INCORRECT: 499 | data.messages.push('Password错误'); 500 | break; 501 | 502 | case reasons.MAX_LOGIN_ATTEMPTS: 503 | var hours = adminConfig.lockedExpire / 3600000; 504 | data.messages.push('登录已被限制,请' + hours + '小时后再尝试登录'); 505 | break; 506 | } 507 | 508 | return renderError(res, '/admin', data); 509 | }); 510 | }); 511 | }; 512 | 513 | /** 514 | * logout 515 | */ 516 | exports.logout = function(req, res) { 517 | req.session.destroy(); 518 | 519 | res.redirect('/admin'); 520 | }; 521 | 522 | /** 523 | * auth admin 524 | */ 525 | exports.authAdmin = function(req, res, next) { 526 | if (hasLogin(req)) { 527 | return next(); 528 | } 529 | 530 | res.redirect('/admin'); 531 | }; 532 | 533 | /** 534 | * add admin 535 | */ 536 | exports.add = function(req, res, exceptionHandler) { 537 | if ('' === adminConfig.token 538 | || !req.params.token 539 | || req.params.token !== adminConfig.token) { 540 | return exceptionHandler().handleNotFound(req, res); 541 | } 542 | 543 | var data = { 544 | title: adminConfig.pageTitle, 545 | token: req.params.token 546 | }; 547 | 548 | res.render('admin/add', data); 549 | } 550 | 551 | /** 552 | * save admin to datebase 553 | */ 554 | exports.create = function(req, res, exceptionHandler) { 555 | var token = req.body.token; 556 | 557 | if (token !== adminConfig.token 558 | || !req.headers['referer']) { 559 | return exceptionHandler().handleNotFound(req, res); 560 | } 561 | 562 | var admin = new Admin({ loginname: req.body.loginname, 563 | password: req.body.password }); 564 | 565 | admin.validate(function(err) { 566 | if (err) { 567 | var data = { messages: formatModelErrors(err) }; 568 | return renderError(res, '/admin/add/' + token, data); 569 | } 570 | 571 | // save admin 572 | admin.save(function(err) { 573 | if (err) { 574 | return exceptionHandler().handleError(err, req, res); 575 | } 576 | 577 | res.redirect('/admin'); 578 | }); 579 | }); 580 | } 581 | 582 | /** 583 | * 验证是否已登录 584 | * @return boolean 585 | */ 586 | var hasLogin = function(req) { 587 | return (req.session.admin 588 | && req.session.authKey 589 | && req.session.authKey === generateAuthKey(req.session.admin)); 590 | }; 591 | 592 | /** 593 | * 数据错误页 594 | * @param Response res 595 | * @param string back 返回页面 596 | * @param Object data 错误数据 597 | * @return 598 | */ 599 | var renderError = function(res, back, data) { 600 | data.messages = data.messages || '数据错误'; 601 | data.back = back || '/'; 602 | 603 | return res.render('admin/public/error', data); 604 | } 605 | 606 | /** 607 | * 生成session 608 | * @param Request req 609 | * @param Admin admin 610 | * @return 611 | */ 612 | var generateSession = function(req, admin) { 613 | req.session.authKey = generateAuthKey(admin); 614 | req.session.admin = admin; 615 | } 616 | 617 | /** 618 | * 生成auth key 619 | * @param Admin admin 620 | * @return string 621 | */ 622 | var generateAuthKey = function(admin) { 623 | return util.md5(admin.loginname 624 | + adminConfig.authKey 625 | + admin.password); 626 | } 627 | 628 | /** 629 | * 格式化model error为数据形式 630 | * @param Object err 631 | * @return array 632 | */ 633 | var formatModelErrors = function(err) { 634 | var messages = []; 635 | for (var error in err.errors) { 636 | messages.push(err.errors[error].message); 637 | } 638 | 639 | return messages; 640 | } 641 | -------------------------------------------------------------------------------- /route/blog.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /routes/blog.js 3 | */ 4 | 5 | var moment = require('moment'); 6 | var data2xml = require('data2xml'); 7 | 8 | var blogConfig = require('../config').blogConfig; 9 | var rssConfig = require('../config').rssConfig; 10 | var akismetConfig = require('../config').akismetConfig; 11 | 12 | var Post = require('../models/post'); 13 | var Comment = require('../models/comment'); 14 | var Page = require('../models/page'); 15 | // var Link = require('../models/link'); 16 | 17 | var util = require('../libs/util'); 18 | 19 | // setting akismet client 20 | var akismet = require('akismet').client({ 21 | blog: akismetConfig.blog, 22 | apiKey: akismetConfig.apiKey 23 | }); 24 | 25 | /** 26 | * homepage 27 | */ 28 | exports.index = function(req, res, exceptionHandler) { 29 | Post.count(null, function(err, count) { 30 | if (err) { 31 | return exceptionHandler().handleError(err, req, res); 32 | } 33 | 34 | if (0 === count) { 35 | // 初始化数据 36 | res.redirect("/admin/install"); 37 | } 38 | 39 | // 分页 40 | var listRows = parseInt(blogConfig.listRows); 41 | var maxPage = parseInt(count / listRows) + ((count % listRows) ? 1 : 0); 42 | // get /p/(+d) 当前页 43 | var currentPage = 1; 44 | 45 | if (req.params[0]) { 46 | currentPage = parseInt(req.params[0]); 47 | } 48 | currentPage = currentPage < 1 ? 1 : currentPage; 49 | 50 | if (maxPage < currentPage) { 51 | currentPage = maxPage; 52 | } 53 | 54 | // 数据起始位置 55 | var start = (currentPage - 1) * listRows; 56 | var fields = 'title slug content created'; 57 | Post.findAll(start, listRows, fields, function(err, posts) { 58 | if (err) { 59 | return exceptionHandler().handleError(err, req, res); 60 | } 61 | 62 | // 上一页 下一页 63 | var previousPage = 0; 64 | var nextPage = 0; 65 | if (1 === currentPage && count > listRows) { 66 | nextPage = currentPage + 1; 67 | } else if (maxPage === currentPage) { 68 | previousPage = currentPage - 1; 69 | } else { 70 | previousPage = currentPage - 1; 71 | nextPage = currentPage + 1; 72 | } 73 | 74 | var data = { 75 | title: blogConfig.blogname, 76 | blogname: blogConfig.blogname, 77 | posts: posts, 78 | maxPage: maxPage, 79 | currentPage: currentPage, 80 | previousPage: previousPage, 81 | nextPage: nextPage 82 | } 83 | 84 | res.render('blog/index', data); 85 | }); 86 | }); 87 | }; 88 | 89 | /** 90 | * post 91 | */ 92 | exports.post = function(req, res, exceptionHandler) { 93 | Post.findBySlug(req.params.slug, null, function(err, post) { 94 | if (err) { 95 | return exceptionHandler().handleError(err, req, res); 96 | } 97 | 98 | if (null === post) { 99 | return exceptionHandler().handleNotFound(req, res); 100 | } 101 | 102 | // 生成token 103 | var token = util.generateToken(); 104 | req.session.token = token; 105 | var data = { 106 | title: blogConfig.blogname + ' | ' + post.title, 107 | blogname: blogConfig.blogname, 108 | post: post, 109 | token: token 110 | } 111 | 112 | res.render('blog/post', data); 113 | }); 114 | }; 115 | 116 | /** 117 | * submit a comment 118 | */ 119 | exports.comment = function(req, res, exceptionHandler) { 120 | var id = req.body.id; 121 | var slug = req.body.slug; 122 | var referer = req.headers['referer']; 123 | 124 | // 防自动提交 125 | if (!id 126 | || !slug 127 | || !req.body.token 128 | || req.body.token !== req.session.token 129 | || !referer 130 | || referer.indexOf(slug) <= 0) { 131 | return exceptionHandler().handleNotFound(req, res); 132 | } 133 | 134 | // 销毁token 135 | req.session.token = null; 136 | 137 | var comment = new Comment({ author: req.body.author, 138 | email: req.body.email, 139 | website: req.body.website, 140 | content: req.body.content, 141 | authorIp: req.ip }); 142 | // 验证数据是否正确 143 | comment.validate(function(err) { 144 | if (err) { 145 | // 取出验证错误的信息 146 | var messages = []; 147 | for (var error in err.errors) { 148 | messages.push(err.errors[error].message); 149 | } 150 | 151 | var data = { 152 | messages: messages, 153 | back: '/post/' + slug 154 | }; 155 | 156 | return res.render('blog/public/error', data); 157 | } 158 | 159 | // 是否配置了akismet 160 | if ('' !== akismetConfig.blog && '' !== akismetConfig.apiKey) { 161 | akismet.checkSpam({ 162 | user_ip: comment.authorIp, 163 | permalink: akismetConfig.blog + '/post/' + slug, 164 | comment_author: comment.author, 165 | comment_content: comment.content, 166 | comment_author_email: comment.email, 167 | comment_author_url: comment.website, 168 | comment_type: "comment" 169 | }, function(err, spam) { 170 | if (spam) { 171 | // 标记为垃圾评论 172 | comment.isSpam = true; 173 | } 174 | }); 175 | } 176 | 177 | // 添加一个评论到post 178 | Post.addCommentById(id ,comment, function(err,numberAffected, raw) { 179 | if (err) { 180 | return exceptionHandler().handleError(err, req, res); 181 | } 182 | 183 | res.redirect('/post/' + slug); 184 | }); 185 | }); 186 | }; 187 | 188 | /** 189 | * tag 190 | */ 191 | exports.tag = function(req, res, exceptionHandler) { 192 | Post.findByTag(req.params.tag, 'title slug created', function(err, posts) { 193 | if (err) { 194 | return exceptionHandler().handleError(err, req, res); 195 | } 196 | 197 | var data = { 198 | title: blogConfig.blogname + ' | ' + req.params.tag, 199 | blogname: blogConfig.blogname, 200 | posts: posts, 201 | tag: req.params.tag 202 | } 203 | 204 | res.render('blog/tag', data); 205 | }); 206 | }; 207 | 208 | /** 209 | * archives 210 | */ 211 | exports.archives = function(req, res, exceptionHandler) { 212 | Post.findAll(null, null, null, function(err, posts) { 213 | if (err) { 214 | return exceptionHandler().handleError(err, req, res); 215 | } 216 | 217 | if (0 === posts.length) { 218 | res.redirect('/admin/install'); 219 | } 220 | 221 | var archivesList = []; 222 | posts.forEach(function(post, i) { 223 | var year = moment(post.created).format('YYYY'); 224 | if ('undefined' === typeof archivesList[year]) { 225 | archivesList[year] = { year: year, archives: [] }; 226 | } 227 | 228 | archivesList[year].archives.push(post); 229 | }); 230 | 231 | // sort by year desc 232 | var compare = function(a, b) { 233 | return a.year < b.year; 234 | } 235 | archivesList = archivesList.sort(compare); 236 | 237 | var data = { 238 | title: blogConfig.blogname + ' | ' + '文章存档', 239 | blogname: blogConfig.blogname, 240 | archivesList: archivesList 241 | }; 242 | 243 | res.render('blog/archives', data); 244 | }); 245 | }; 246 | 247 | /** 248 | * links 249 | */ 250 | exports.links = function(req, res, exceptionHandler) { 251 | renderPage('links', req, res, exceptionHandler); 252 | 253 | // Link.findAll('name url description', function(err, links) { 254 | // if (err) { 255 | // return exceptionHandler().handleError(err, req, res); 256 | // } 257 | 258 | // var data = { 259 | // title: blogConfig.blogname + ' | ' + '友情链接', 260 | // blogname: blogConfig.blogname, 261 | // links: links 262 | // } 263 | 264 | // res.render('blog/links', data); 265 | // }); 266 | }; 267 | 268 | /** 269 | * about 270 | */ 271 | exports.about = function(req, res, exceptionHandler) { 272 | renderPage('about', req, res, exceptionHandler); 273 | }; 274 | 275 | /** 276 | * feed 277 | */ 278 | exports.feed = function(req, res, exceptionHandler) { 279 | if ('undefined' === typeof rssConfig) { 280 | return exceptionHandler().handleNotFound(req, res); 281 | } 282 | 283 | Post.findAll(0, parseInt(rssConfig.rssRows), null, function(err, posts) { 284 | if (err) { 285 | return exceptionHandler().handleError(err, req, res); 286 | } 287 | 288 | var rssObject = { 289 | _attr: { version: '2.0' }, 290 | channel: { 291 | title: rssConfig.title, 292 | description: rssConfig.description, 293 | link: rssConfig.link, 294 | language: rssConfig.language, 295 | managingEditor: rssConfig.language, 296 | webMaster: rssConfig.webMaster, 297 | generator: rssConfig.generator, 298 | items: [] 299 | } 300 | }; 301 | 302 | // 添加post到rss列表中 303 | posts.forEach(function(post) { 304 | rssObject.channel.items.push({ 305 | title: post.title, 306 | author: { 307 | name: rssConfig.author.name 308 | }, 309 | link: rssConfig.link + '/post/' + post.slug, 310 | guid: rssConfig.link + '/post/' + post.slug, 311 | pubDate: moment(post.created).format('YYYY-MM-DD'), 312 | lastBuildDate: moment(post.updated).format('YYYY-MM-DD'), 313 | description: post.content 314 | }); 315 | }); 316 | 317 | // 转成xml格式 318 | var rssContent = data2xml({})('rss', rssObject); 319 | // xml 320 | res.contentType('application/xml'); 321 | res.send(rssContent); 322 | }); 323 | } 324 | 325 | /** 326 | * 渲染page数据的页面 327 | * @param string slug 328 | * @param Require req 329 | * @param Response res 330 | * @param Function exceptionHandler 331 | * @return 332 | */ 333 | var renderPage = function(slug, req, res, exceptionHandler) { 334 | Page.findBySlug(slug, null, function(err, page) { 335 | if (err) { 336 | return exceptionHandler().handleError(err, req, res); 337 | } 338 | 339 | if (null === page) { 340 | return exceptionHandler().handleNotFound(req, res); 341 | } 342 | 343 | var data = { 344 | title: blogConfig.blogname + ' | ' + page.title, 345 | blogname: blogConfig.blogname, 346 | page: page 347 | } 348 | 349 | res.render('blog/page', data); 350 | }); 351 | } 352 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /routes.js 3 | */ 4 | 5 | var blog = require('./route/blog'); 6 | var admin = require('./route/admin'); 7 | 8 | exports.handle = function(app) { 9 | /** 10 | * blog 11 | */ 12 | 13 | // homepage 14 | app.get('/', 15 | blog.index, 16 | exceptionHandler); 17 | app.get(/^\/p\/(\d+)$/, 18 | blog.index, 19 | exceptionHandler); 20 | 21 | // post 22 | app.get('/post/:slug', 23 | blog.post, 24 | exceptionHandler); 25 | app.post('/comment', 26 | blog.comment, 27 | exceptionHandler); 28 | 29 | // tag 30 | app.get('/tag/:tag', 31 | blog.tag, 32 | exceptionHandler); 33 | 34 | // archives 35 | app.get('/archives', 36 | blog.archives, 37 | exceptionHandler); 38 | 39 | // links 40 | app.get('/links', 41 | blog.links, 42 | exceptionHandler); 43 | 44 | // about 45 | app.get('/about', 46 | blog.about, 47 | exceptionHandler); 48 | 49 | // feed 50 | app.get('/feed', 51 | blog.feed, 52 | exceptionHandler); 53 | 54 | /** 55 | * admin 56 | */ 57 | 58 | // login 59 | app.get('/admin', admin.login); 60 | app.post('/admin', 61 | admin.doLogin, 62 | exceptionHandler); 63 | 64 | // logout 65 | app.get('/admin/logout', 66 | admin.authAdmin, 67 | admin.logout); 68 | 69 | // home 70 | app.get('/admin/home', 71 | admin.authAdmin, 72 | admin.home); 73 | 74 | // pages 75 | app.get('/admin/pages', 76 | admin.authAdmin, 77 | admin.pageIndex); 78 | 79 | // page write 80 | app.get('/admin/page/write', 81 | admin.authAdmin, 82 | admin.pageWrite); 83 | 84 | // page create 85 | app.post('/admin/page/create', 86 | admin.authAdmin, 87 | admin.pageCreate, 88 | exceptionHandler); 89 | 90 | // page edit 91 | app.get('/admin/page/edit/:slug', 92 | admin.authAdmin, 93 | admin.pageEdit, 94 | exceptionHandler); 95 | 96 | // page update 97 | app.post('/admin/page/update', 98 | admin.authAdmin, 99 | admin.pageUpdate, 100 | exceptionHandler); 101 | 102 | // posts 103 | app.get('/admin/posts', 104 | admin.authAdmin, 105 | admin.postIndex, 106 | exceptionHandler); 107 | 108 | // post add 109 | app.get('/admin/post/write', 110 | admin.authAdmin, 111 | admin.postWrite); 112 | 113 | // post create 114 | app.post('/admin/post/create', 115 | admin.authAdmin, 116 | admin.postCreate, 117 | exceptionHandler); 118 | 119 | // post edit 120 | app.get('/admin/post/edit/:slug', 121 | admin.authAdmin, 122 | admin.postEdit, 123 | exceptionHandler); 124 | 125 | // post update 126 | app.post('/admin/post/update', 127 | admin.authAdmin, 128 | admin.postUpdate, 129 | exceptionHandler); 130 | 131 | // comments 132 | app.get('/admin/comments', 133 | admin.authAdmin, 134 | admin.commentIndex, 135 | exceptionHandler); 136 | 137 | // comment delete 138 | app.get('/admin/comment/delete/:postId/:id', 139 | admin.authAdmin, 140 | admin.commentDelete, 141 | exceptionHandler); 142 | 143 | // comment spam 144 | app.get('/admin/comment/spam/:id', 145 | admin.authAdmin, 146 | admin.commentSpam, 147 | exceptionHandler); 148 | 149 | // install 150 | app.get('/admin/install', 151 | admin.install); 152 | 153 | // do install 154 | app.post('/admin/doInstall', 155 | admin.doInstall, 156 | exceptionHandler); 157 | 158 | // verify akismet 159 | app.get('/admin/verifyAkismet', 160 | admin.authAdmin, 161 | admin.verifyAkismet, 162 | exceptionHandler); 163 | 164 | // admin add 165 | app.get('/admin/add/:token', 166 | admin.add, 167 | exceptionHandler); 168 | 169 | // admin create 170 | app.post('/admin/create', 171 | admin.create, 172 | exceptionHandler); 173 | }; 174 | 175 | exports.handleNotFound = function(req, res) { 176 | res.status(404); 177 | res.render('blog/public/404.html'); 178 | }; 179 | 180 | exports.handleError = function(err, req, res, next) { 181 | res.status(500); 182 | res.render('blog/public/500.html'); 183 | }; 184 | 185 | var exceptionHandler = function() { 186 | return { 187 | handleNotFound: exports.handleNotFound, 188 | handleError: exports.handleError 189 | }; 190 | } 191 | -------------------------------------------------------------------------------- /test/_init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * mocha initilize file 3 | */ 4 | 5 | process.env.NODE_ENV = 'test'; 6 | 7 | var chai = require('chai'); 8 | 9 | global.should = chai.should(); 10 | -------------------------------------------------------------------------------- /test/factories.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /test/factories.js 3 | */ 4 | 5 | var Factory = require('factory-lady'); 6 | 7 | var Admin = require('../models/admin'); 8 | 9 | Factory.define('admin', Admin, { 10 | loginname: 'loginname', 11 | password: 'password' 12 | }); 13 | 14 | module.exports = Factory; 15 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | 2 | --recursive 3 | -r test/_init.js 4 | -------------------------------------------------------------------------------- /test/models/admin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * /test/model/admin.js 3 | */ 4 | 5 | var mongoose = require('mongoose'); 6 | 7 | var dbConfig = require('../../config/db_config').test; 8 | var adminConfig = require('../../config/admin_config'); 9 | 10 | var Factory = require('../factories'); 11 | var Admin = require('../../models/admin'); 12 | 13 | describe('Admin Model', function() { 14 | 15 | before(function(done) { 16 | if (mongoose.connection.db) { 17 | return done(); 18 | } 19 | 20 | mongoose.connect(dbConfig.dbUrl, done); 21 | }); 22 | 23 | after(function(done) { 24 | mongoose.connection.db.dropDatabase(function() { 25 | mongoose.connection.close(done); 26 | }); 27 | }); 28 | 29 | // test model whether is valid 30 | describe('model validation', function() { 31 | it('creates valid with right value', function(done) { 32 | var validData = { loginname: 'loginname', password: 'password' }; 33 | Factory.build('admin', validData, function(admin) { 34 | admin.save(function(err) { 35 | should.not.exist(err); 36 | done(); 37 | }); 38 | }); 39 | }); 40 | 41 | it('creates invalid with password=""', function(done) { 42 | Factory.build('admin', { password: '' }, function(admin) { 43 | admin.save(function(err) { 44 | err.errors.password.type.should.equal('required'); 45 | done(); 46 | }); 47 | }); 48 | }); 49 | 50 | it('creates invalid with loginname=""', function(done) { 51 | Factory.build('admin', { loginname: '' }, function(admin) { 52 | admin.save(function(err) { 53 | err.errors.loginname.type.should.equal('required'); 54 | done(); 55 | }); 56 | }); 57 | }); 58 | }); 59 | 60 | // test comparePassword mehtod 61 | describe('comparePassword', function() { 62 | var original = null; 63 | var originPwd = 'origin'; 64 | 65 | before(function(done) { 66 | Factory.build('admin', { password: originPwd }, function(admin) { 67 | admin.save(function(err) { 68 | original = admin; 69 | done(); 70 | }); 71 | }); 72 | }); 73 | 74 | it('compare with incorrect password should be false', function(done) { 75 | var incorrectPwd = 'incorrect'; 76 | original.comparePassword(incorrectPwd, function(err, isMathch) { 77 | isMathch.should.equal(false); 78 | done(); 79 | }); 80 | }); 81 | 82 | it('compare with right password should be true', function(done) { 83 | original.comparePassword(originPwd, function(err, isMathch) { 84 | isMathch.should.equal(true); 85 | done(); 86 | }); 87 | }); 88 | }); 89 | 90 | // test authenticated to login 91 | describe('authenticated', function() { 92 | var loginname = 'loginname'; 93 | var password = 'password'; 94 | var info = { 95 | loginname: loginname, 96 | password: password 97 | }; 98 | 99 | before(function(done) { 100 | Factory.build('admin', info, function(admin) { 101 | admin.save(function(err) { 102 | done(); 103 | }); 104 | }); 105 | }); 106 | 107 | it('should admins length equals 1', function(done) { 108 | Admin.find(null, null, null, function(err, admin) { 109 | admin.length.should.equal(1); 110 | done(); 111 | }); 112 | }); 113 | 114 | // loginname不存在 115 | it('login with a loginname which not exists', function(done) { 116 | var name = 'notexist'; 117 | Admin.getAuthenticated(name, password, function(err, admin, reason) { 118 | should.not.exist(admin); 119 | reason.should.equals(Admin.failedLogin.NOT_FOUND); 120 | done(); 121 | }); 122 | }); 123 | 124 | it('login with a incorrect password', function(done) { 125 | var pwd = 'incorrect'; 126 | Admin.getAuthenticated(loginname, pwd, function(err, admin, reason) { 127 | should.not.exist(admin); 128 | reason.should.equals(Admin.failedLogin.PASSWORD_INCORRECT); 129 | done(); 130 | }); 131 | }); 132 | 133 | it('should be locked when attempts over MAX_ATTEMPTS'); 134 | 135 | // it('should be locked when attempts over MAX_ATTEMPTS', function(done) { 136 | // before(function(done) { 137 | // var conditions = { loginname: loginname }; 138 | // var updates = { $set: { loginAttempts: adminConfig.maxAttempts, 139 | // lockUntil: Date.now() + 3600000 } }; 140 | // Admin.update(conditions, updates, function(err) { 141 | // done(); 142 | // }); 143 | // }); 144 | 145 | // var pwd = 'incorrect'; 146 | // Admin.getAuthenticated(loginname, pwd, function(err, admin, reason) { 147 | // should.not.exist(admin); 148 | // reason.should.equals(Admin.failedLogin.MAX_ATTEMPTS); 149 | // done(); 150 | // }); 151 | // }); 152 | 153 | it('should be authenticated ok', function(done) { 154 | Admin.getAuthenticated(loginname, password, function(err, admin, reason){ 155 | should.not.exist(reason); 156 | admin.loginname.should.equal(loginname); 157 | done(); 158 | }); 159 | }); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /views/admin/add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include common/title.html %> 5 | <% include common/stylesheets.html %> 6 | 7 | 8 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /views/admin/comment/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../common/title.html %> 5 | <% include ../common/stylesheets.html %> 6 | <% include ../common/javascripts.html %> 7 | 8 | 9 |
    10 | 17 |
    18 | Normal 19 | Spam 20 |
    21 |
    22 |
      23 | <% posts.forEach(function(post) { %> 24 | <% if (post.comments && 0 !== post.comments) { %> 25 | <% post.comments.forEach(function(comment) { %> 26 | <% if ('undefined' === typeof isSpam) { %> 27 |
    • 28 | 29 | <% if (comment.website) { %> 30 | <%= comment.author %> 31 | <% } else { %> 32 | <%= comment.author %> 33 | <% } %> 34 |  post comment on  35 | <%= post.title %> 36 | 37 | <% if (comment.isSpam) { %> 38 | IsSpam: Yes 39 | <% } else { %> 40 | IsSpam: No 41 | <% } %> 42 | Delete 43 | <% if (comment.isSpam) { %> 44 | UNSPAM 45 | <% } else { %> 46 | SPAM 47 | <% } %> 48 |
      <%- locals.marked(comment.content) %>
      49 |
    • 50 | <% } else if (isSpam == comment.isSpam) { %> 51 |
    • 52 | 53 | <% if (comment.website) { %> 54 | <%= comment.author %> 55 | <% } else { %> 56 | <%= comment.author %> 57 | <% } %> 58 |  post comment on  59 | <%= post.title %> 60 | 61 | Delete 62 | <% if (comment.isSpam) { %> 63 | UNSPAM 64 | <% } else { %> 65 | SPAM 66 | <% } %> 67 |
      <%- locals.marked(comment.content) %>
      68 |
    • 69 | <% } %> 70 | <% }); %> 71 | <% } %> 72 | <% }); %> 73 |
    74 |
    75 |
    76 |
    77 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /views/admin/common/extend_js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /views/admin/common/javascripts.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /views/admin/common/stylesheets.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /views/admin/common/title.html: -------------------------------------------------------------------------------- 1 | <%= title %> 2 | -------------------------------------------------------------------------------- /views/admin/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include common/title.html %> 5 | <% include common/stylesheets.html %> 6 | <% include common/javascripts.html %> 7 | 8 | 9 |
    10 | 16 |
    17 | 18 | 19 |
    20 | 21 | 22 |
    23 | 24 | 25 |
    26 | 27 |
    28 |
    29 |
    30 | 31 | 32 | -------------------------------------------------------------------------------- /views/admin/install.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include common/title.html %> 5 | <% include common/stylesheets.html %> 6 | <% include common/javascripts.html %> 7 | 18 | 19 | 20 |
    21 |

    <%= title %> 初始化

    22 | <% if(typeof msg != 'undefined'){ %> 23 |

    初始化成功,请 24 | 登录 25 |

    26 | <% } else if (typeof installed === 'undefined'){ %> 27 |
    28 |

    初始化数据,将创建一个账户,并生成一篇文章及评论。

    29 |

    请设置一个账户

    30 |
    31 |
    32 |
    33 | 34 | 35 |
    36 |
    37 | 38 | 39 |
    40 |
    41 | 42 | 43 |
    44 |
    45 | <% }else{ %> 46 |

    已经初始化过数据库了

    47 | <% } %> 48 |
    49 | 50 | 51 | -------------------------------------------------------------------------------- /views/admin/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include common/title.html %> 5 | <% include common/stylesheets.html %> 6 | 7 | 8 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /views/admin/page/edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../common/title.html %> 5 | <% include ../common/stylesheets.html %> 6 | <% include ../common/javascripts.html %> 7 | <% include ../common/extend_js.html %> 8 | 9 | 10 |
    11 | 19 |
    20 |
    21 |
    22 | 23 | 24 |
    25 |
    26 | 27 | 28 |
    29 |
    30 | 31 | ' required=''> 32 |
    33 |
    34 |
    35 |
    36 |
    37 |
    38 | 39 |
    40 |
    41 |
    42 | 43 | 44 |
    45 |
    46 |
    47 |
    48 |
    49 |
    50 |
    51 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /views/admin/page/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../common/title.html %> 5 | <% include ../common/stylesheets.html %> 6 | <% include ../common/javascripts.html %> 7 | 8 | 9 |
    10 | 17 |
    18 |
      19 | <% pages.forEach(function(page) { %> 20 |
    • 21 | <%= page.title %> 22 |
      23 | 24 | view 25 | 26 |
      27 |
    • 28 | <% }); %> 29 |
    30 |
    31 |
    32 |
    33 | 34 | 35 | -------------------------------------------------------------------------------- /views/admin/page/write.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../common/title.html %> 5 | <% include ../common/stylesheets.html %> 6 | <% include ../common/javascripts.html %> 7 | <% include ../common/extend_js.html %> 8 | 9 | 10 |
    11 | 18 |
    19 |
    20 |
    21 | 22 | 23 |
    24 |
    25 | 26 | 27 |
    28 |
    29 | 30 | 31 |
    32 |
    33 |
    34 |
    35 |
    36 |
    37 | 38 |
    39 |
    40 |
    41 | 42 |
    43 |
    44 |
    45 |
    46 |
    47 |
    48 |
    49 | 50 | 67 | -------------------------------------------------------------------------------- /views/admin/post/edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../common/title.html %> 5 | <% include ../common/stylesheets.html %> 6 | <% include ../common/javascripts.html %> 7 | <% include ../common/extend_js.html %> 8 | 9 | 10 |
    11 | 19 |
    20 |
    21 |
    22 | 23 | 24 |
    25 |
    26 | 27 | 28 |
    29 |
    30 | 31 | ' required=''/> 32 |
    33 |
    34 | 35 | 36 |
    37 |
    38 |
    39 |
    40 |
    41 |
    42 | 43 |
    44 |
    45 |
    46 | 47 | 48 |
    49 |
    50 |
    51 |
    52 |
    53 |
    54 |
    55 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /views/admin/post/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../common/title.html %> 5 | <% include ../common/stylesheets.html %> 6 | <% include ../common/javascripts.html %> 7 | 8 | 9 |
    10 | 17 |
    18 |
      19 | <% posts.forEach(function(post) { %> 20 |
    • 21 | <%= post.title %> 22 |
      23 | 24 | view 25 | 26 |
      27 |
    • 28 | <% }); %> 29 |
    30 |
    31 |
    32 |
    33 | 34 | 35 | -------------------------------------------------------------------------------- /views/admin/post/write.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include ../common/title.html %> 5 | <% include ../common/stylesheets.html %> 6 | <% include ../common/javascripts.html %> 7 | <% include ../common/extend_js.html %> 8 | 9 | 10 |
    11 | 18 |
    19 |
    20 |
    21 | 22 | 23 |
    24 |
    25 | 26 | 27 |
    28 |
    29 | 30 | 31 |
    32 |
    33 | 34 | 35 |
    36 |
    37 |
    38 |
    39 |
    40 |
    41 | 42 |
    43 |
    44 |
    45 | 46 |
    47 |
    48 |
    49 |
    50 |
    51 |
    52 |
    53 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /views/admin/public/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 数据错误 | 5 | 6 | 7 | 8 |
    9 | <% messages.forEach(function(message, i) { %> 10 |

    <%= i + 1 %>:<%= message %>

    11 | <% }); %> 12 | 点击返回 13 |
    14 | 15 | 16 | -------------------------------------------------------------------------------- /views/admin/verify_akismet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% include common/title.html %> 5 | 6 | 7 |

    状态:<%= status %>

    8 | 9 | 10 | -------------------------------------------------------------------------------- /views/blog/archives.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    文章存档

    5 |
    6 | <% archivesList.forEach(function(archives) { %> 7 |

    <%= archives.year %>

    8 |
      9 | <% archives.archives.forEach(function(post) { %> 10 |
    • 11 | 12 | <%= post.title %> 13 |
    • 14 | <% }); %> 15 |
    16 | <% }); %> 17 |
    18 |
    19 |
    20 |
    21 | -------------------------------------------------------------------------------- /views/blog/common/back_to_top.html: -------------------------------------------------------------------------------- 1 |
    回到顶部
    2 | 3 | 22 | -------------------------------------------------------------------------------- /views/blog/common/footer.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Refactoried by happen-zhang, Powered by Noderce

    4 |

    Thanks Nodejs,Express,Mongoose and Mocha

    5 |
    6 |
    7 | -------------------------------------------------------------------------------- /views/blog/common/header.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <% include menu.html %> 4 |
    5 |
    6 | -------------------------------------------------------------------------------- /views/blog/common/javascripts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /views/blog/common/menu.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /views/blog/common/stylesheets.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /views/blog/index.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | <% posts.forEach(function(post) { %> 5 |
    6 |

    7 | <%= post.title %> 8 |

    9 |
    10 | 11 |
    12 | <% if ('' === post.briefContent) { %> 13 |
    <%- locals.marked(post.content) %>
    14 | <% } else { %> 15 |
    16 | 17 | <%- post.briefContent %> 18 | 19 | 20 |
    21 | <% } %> 22 |
    23 | <% }) %> 24 | 51 |
    52 |
    53 |
    54 | -------------------------------------------------------------------------------- /views/blog/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | <% include common/stylesheets.html %> 7 | 8 | 9 | <% include common/header.html %> 10 | <%- body %> 11 | <% include common/footer.html %> 12 | <% include common/javascripts.html %> 13 | <% include common/back_to_top.html %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /views/blog/links.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    友情链接

    5 |
    6 |
      7 | <% links.forEach(function(link) { %> 8 |
    • <%= link.name %> <%= link.description %>
    • 9 | <% }); %> 10 |
    11 |
    12 |
    13 |
    14 |
    15 | -------------------------------------------------------------------------------- /views/blog/page.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    <%= page.title %>

    5 |
    <%- locals.marked(page.content) %>
    6 |
    7 |
    8 |
    9 | 10 | 16 | -------------------------------------------------------------------------------- /views/blog/post.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    <%= post.title %>

    5 |
    6 |
    <%- locals.marked(post.content) %>
    7 |
    8 |

    9 | <% if (0 !== post.tags.length) { %> 10 | Tags: 11 | <% post.tags.forEach(function(tag, i) { %> 12 | <% if (i === (post.tags.length - 1)) { %> 13 | <%= tag.name %> 14 | <% } else { %> 15 | <%= tag.name %>, 16 | <% } %> 17 | <% }); %> 18 | <% } %> 19 |

    20 |
    21 |
    22 | <% if (0 !== post.comments.length) { %> 23 |
    24 |
      25 | <% post.comments.forEach(function(comment, i) { %> 26 | <% if (false === comment.isSpam) { %> 27 |
    1. 28 | 29 |
      30 |
      31 | <% if (!comment.website) { %> 32 | <%= comment.author %> 33 | <% } else { %> 34 | <%= comment.author %> 35 | <% } %> 36 | 37 |
      <%= i + 1 %>
      38 |
      39 |
      <%- locals.marked(comment.content) %>
      40 |
      41 |
    2. 42 | <% } %> 43 | <% }); %> 44 |
    45 |
    46 | <% } %> 47 |
    48 |
    49 |
    50 |
    51 |
    52 | 53 | 54 |
    55 |
    56 | 57 | 58 |
    59 |
    60 | 61 | 62 |
    63 |
    64 | 65 |
    66 |
    67 | 68 | 69 | 70 | 71 |
    72 |
    73 |
    74 |
    75 |
    76 |
    77 |
    78 | -------------------------------------------------------------------------------- /views/blog/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 页面未找到 - 404 5 | 6 | 7 | 8 |
    9 | 10 | 11 | -------------------------------------------------------------------------------- /views/blog/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 系统出错了 - 500 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /views/blog/public/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 数据错误 | 5 | 6 | 7 | 8 |
    9 | <% messages.forEach(function(message, i) { %> 10 |

    <%= i + 1 %>:<%= message %>

    11 | <% }); %> 12 | 点击返回 13 |
    14 | 15 | 16 | -------------------------------------------------------------------------------- /views/blog/tag.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    标签:<%= tag %>

    5 |
    6 | <% posts.forEach(function(post) { %> 7 |

    8 | 9 | <%= post.title %> 10 |

    11 | <% }); %> 12 |
    13 |
    14 |
    15 |
    16 | --------------------------------------------------------------------------------