├── .gitignore ├── Gulpfile.js ├── README.md ├── api ├── comment.js ├── topic.js └── user.js ├── app.js ├── common ├── db.js ├── mail.js ├── qn.js └── utils.js ├── config.json ├── controllers ├── about.js ├── api.js ├── client.js ├── control-panel.js ├── forget-password.js ├── message.js ├── oauth │ └── github.js ├── profile.js ├── search.js ├── signin.js ├── signup.js ├── topic-edit.js ├── topic-list.js ├── topic-view.js ├── uid.js ├── upload.js ├── user-info.js ├── user-list.js └── verify-mail.js ├── design ├── avatar.afphoto ├── favicon.afphoto └── logo.afphoto ├── filters ├── access.js ├── common-data.js └── ua.js ├── global.js ├── jsconfig.json ├── models ├── access.js ├── comment.js ├── define.js ├── mail.js ├── message.js ├── role.js ├── score.js ├── status.js ├── topic.js └── user.js ├── package.json ├── public ├── ad │ ├── google │ │ └── ads.html │ └── qcloud │ │ └── 300x100.jpg ├── bootstrap │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ └── bootstrap.min.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── npm.js ├── css │ └── common.less ├── favicon.ico ├── favicons │ ├── android-chrome-144x144.png │ ├── android-chrome-192x192.png │ ├── android-chrome-36x36.png │ ├── android-chrome-48x48.png │ ├── android-chrome-72x72.png │ ├── android-chrome-96x96.png │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-precomposed.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── manifest.json │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ └── safari-pinned-tab.svg ├── images │ ├── avatar │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 11.png │ │ ├── 12.png │ │ ├── 13.png │ │ ├── 14.png │ │ ├── 15.png │ │ ├── 16.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ ├── favicon-300.png │ ├── favicon.png │ └── logo.png └── js │ └── jquery.min.js ├── robot └── infoq.js ├── test ├── at-test.js ├── mail-test.js ├── time-test.js └── utils-test.js ├── tmpls ├── message.html ├── reset-password.html └── verify-mail.html └── views ├── about.html ├── api.html ├── client.html ├── control-panel.html ├── forget-password.html ├── layout └── master.html ├── message.html ├── profile.html ├── search.html ├── signin.html ├── signup.html ├── topic-edit.html ├── topic-list.html ├── topic-view.html ├── user-info.html ├── user-list.html └── verify-mail.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.DS_Store 3 | .settings 4 | .vscode 5 | _* 6 | node_modules 7 | bdunion.txt 8 | config.development.* 9 | config.production.* 10 | config.jser.* -------------------------------------------------------------------------------- /Gulpfile.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/Gulpfile.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSER Open Source 2 | JSER 是一个简洁自由的开源社区软件,采用宽松的 MIT 协议开源。 3 | 4 | # 工具与环境 5 | 1. 运行环境 Node.js 6 | 2. 存储 MongoDB + Redis 7 | 3. 编辑器 VSCode/vim/sublime ... 8 | 9 | # 获取与贡献 10 | 1. 注册 GitHub 11 | 2. 安装 Node.js 12 | 3. 安装 MongoDB 13 | 4. 安装 Redis 14 | 5. 从 GitHub clone 代码 15 | 6. 在 “终端” 或 “CMD” 中执行 npm install,安装依赖模块 16 | 7. 利用 VSCode 编辑代码,(当然也可以用任何喜欢的文本编辑工具) 17 | 8. 成为 [jser-dev](https://github.com/jser-dev) 成员 18 | 9. 当然,任何人都可以通过 Pull Request 向 JSER 贡献代码 -------------------------------------------------------------------------------- /api/comment.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/api/comment.js -------------------------------------------------------------------------------- /api/topic.js: -------------------------------------------------------------------------------- 1 | var TopicController = function () { }; 2 | 3 | TopicController.prototype.get = function () { }; 4 | 5 | TopicController.prototype.post = function () { }; 6 | 7 | TopicController.prototype.put = function () { }; 8 | 9 | TopicController.prototype.delete = function () { }; 10 | 11 | module.exports = TopicController; -------------------------------------------------------------------------------- /api/user.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/api/user.js -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /* global __dirname */ 2 | /** 3 | * 自动生成的应用入口程序 4 | * 5 | * 使用 nokit start 命令启动时,会忽略此入口程序 6 | * 通常以下情况使用此入口程序: 7 | * 1) 在使用进程管理工具(pm2等)时 8 | * 2) 目标 "环境" 无法使用 nokit start 命令时 9 | * 10 | * 确保添加了对 nokit 的依赖,或全局安装了 nokit 并设置了 NODE_PATH 环境变量 11 | * 12 | * 安装命令: 13 | * npm install nokitjs [-g] 14 | **/ 15 | 16 | var nokit = require("nokitjs"); 17 | var console = nokit.console; 18 | 19 | var options = {}; 20 | 21 | /** 22 | * 设定应程序的根目录 23 | */ 24 | options.root = __dirname; 25 | 26 | /** 27 | * 启动 server 28 | **/ 29 | var server = new nokit.Server(options); 30 | server.start(function (err, msg) { 31 | if (err) { 32 | console.error(err); 33 | } else { 34 | console.log(msg); 35 | } 36 | }); 37 | 38 | //eof 39 | -------------------------------------------------------------------------------- /common/db.js: -------------------------------------------------------------------------------- 1 | var mg = require("mongoose"); 2 | 3 | var self = module.exports; 4 | 5 | //建立链接 6 | self.connect = function (server, callback) { 7 | //与数据库建立连接 8 | mg.connect(server.configs.connStr); 9 | if (callback) callback(); 10 | }; 11 | 12 | //mongoose 实例 13 | self.mg = mg; 14 | 15 | //mongodb 实例 16 | self.types = mg.Schema.Types; 17 | 18 | //定义一个模型 19 | self.model = function (name, schemaObject) { 20 | if (!schemaObject) { 21 | return mg.model(name); 22 | } else { 23 | var schema = new mg.Schema(schemaObject); 24 | schema.name = name; 25 | var Model = mg.model(name, schema); 26 | Model.schema = schema; 27 | return Model; 28 | } 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /common/mail.js: -------------------------------------------------------------------------------- 1 | var utils = require("./utils"); 2 | var nodemailer = require('nodemailer'); 3 | 4 | var self = module.exports; 5 | var transporter = null; 6 | 7 | /** 8 | * 发送一封邮件 9 | **/ 10 | self.send = function (options, callback) { 11 | var mailConfigs = utils.configs.mail; 12 | if (!transporter) { 13 | transporter = nodemailer.createTransport(mailConfigs.conn); 14 | } 15 | options = options || {}; 16 | options.from = options.from || mailConfigs.from; 17 | transporter.sendMail(options, callback); 18 | }; -------------------------------------------------------------------------------- /common/qn.js: -------------------------------------------------------------------------------- 1 | var qn = require("qn"); 2 | var fs = require("fs"); 3 | var utils = require("../common/utils"); 4 | 5 | var self = module.exports; 6 | 7 | //初始化 8 | self.init = function (server) { 9 | var qnConfigs = server.configs.qiniu; 10 | 11 | self.client = qn.create({ 12 | accessKey: qnConfigs.accessKey, 13 | secretKey: qnConfigs.secretKey, 14 | bucket: qnConfigs.bucket, 15 | origin: qnConfigs.origin 16 | }); 17 | 18 | /** 19 | * 上传图片 20 | **/ 21 | self.client.uploadImage = function (filePath, fileKey, callback) { 22 | self.client.upload(fs.createReadStream(filePath), { 23 | key: fileKey 24 | }, function (err, result) { 25 | if (err) { 26 | return callback(err); 27 | } 28 | callback(null, result); 29 | }); 30 | }; 31 | 32 | self.client.getUrl = function (fileKey) { 33 | return self.client.options.origin + "/" + fileKey; 34 | } 35 | }; -------------------------------------------------------------------------------- /common/utils.js: -------------------------------------------------------------------------------- 1 | /* global nokit */ 2 | var crypto = require("crypto"); 3 | var Mditor = require('mditor'); 4 | var timeago = require('timeago-words'); 5 | var status = require("../models/status"); 6 | 7 | var self = module.exports = (nokit.utils || {}); 8 | 9 | var HASH_SLOT = "JSER"; 10 | 11 | /** 12 | * 产生指定区间的随机数 13 | **/ 14 | self.random = function (begin, end) { 15 | end += 1; //包含 end 16 | var range = end - begin; 17 | var num = Math.random() * range + begin; 18 | return parseInt(num); 19 | }; 20 | 21 | /** 22 | * 对指定字符串进行 sha1 23 | **/ 24 | self.hashDigest = function (value) { 25 | var sha1 = crypto.createHash('sha1'); 26 | sha1.update(value); 27 | sha1.update(HASH_SLOT); 28 | return sha1.digest('hex'); 29 | }; 30 | 31 | /** 32 | * 将 timeago 挂在 utils 上 (utils 的为引用为 nokit.utils 参考 common/utils.js) 33 | * utils 可以在模板中通过 $ 调用 34 | **/ 35 | self.timeago = ((function () { 36 | timeago.settings.suffixAgo = ''; 37 | timeago.settings.suffixFromNow = '距现在'; 38 | timeago.settings.seconds = "数秒前"; 39 | timeago.settings.minute = "1分钟前"; 40 | timeago.settings.minutes = "%d分钟前"; 41 | timeago.settings.hour = "1小时前"; 42 | timeago.settings.hours = "%d小时前"; 43 | timeago.settings.day = "1天前"; 44 | timeago.settings.days = "%d天前"; 45 | timeago.settings.month = "1月前"; 46 | timeago.settings.months = "%d月前"; 47 | timeago.settings.year = "1年前"; 48 | timeago.settings.years = "%d年前"; 49 | return function (dt) { 50 | return timeago(dt); 51 | }; 52 | })()); 53 | 54 | /** 55 | * 初始化 utils 的一属性 56 | **/ 57 | self.init = function (server) { 58 | //server 59 | self.server = server; 60 | //配置 61 | self.configs = server.configs; 62 | }; 63 | 64 | //状态常量 65 | self.status = status; 66 | 67 | /** 68 | * 从文本中提取出@username 标记的用户名数组 69 | * @param {String} text 文本内容 70 | * @return {Array} 用户名数组 71 | */ 72 | self.fetchUsers = function (text) { 73 | if (!text) { 74 | return []; 75 | } 76 | var ignoreRegexs = [ 77 | /```.+?```/g, // 去除单行的 ``` 78 | /^```[\s\S]+?^```/gm, // ``` 里面的是 pre 标签内容 79 | /`[\s\S]+?`/g, // 同一行中,`some code` 中内容也不该被解析 80 | /^ .*/gm, // 4个空格也是 pre 标签,在这里 . 不会匹配换行 81 | /\b\S*?@[^\s]*?\..+?\b/g, // somebody@gmail.com 会被去除 82 | /\[@.+?\]\(\/.+?\)/g, // 已经被 link 的 username 83 | ]; 84 | 85 | ignoreRegexs.forEach(function (ignore_regex) { 86 | text = text.replace(ignore_regex, ''); 87 | }); 88 | 89 | var results = text.match(/@[\u4E00-\u9FFFa-zA-Z0-9\-_]+/igm); 90 | var names = []; 91 | if (results) { 92 | for (var i = 0, l = results.length; i < l; i++) { 93 | var s = results[i]; 94 | //remove leading char @ 95 | s = s.slice(1); 96 | names.push(s); 97 | } 98 | } 99 | //names = _.uniq(names); 100 | return names; 101 | }; 102 | 103 | /** 104 | * 解析 markdown 105 | **/ 106 | self.md2html = function (mdText) { 107 | if (!mdText) return mdText; 108 | self._mdParser = self._mdParser || new Mditor.Parser(); 109 | return self._mdParser.parse(mdText); 110 | }; -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "showErrorDetail": false, 3 | "name": "JSER社区", 4 | "domain": "jser.cc", 5 | "author": "JSER Team", 6 | "keywords": "jser社区,js社区,jser,js,javascript,nodejs,node.js,前端,前端社区,社区", 7 | "description": "JSER 是一个 “纯粹、简单、自由” 的 “JS程序猿” 社区", 8 | "buildYear": "2015", 9 | "port": 443, 10 | "https": { 11 | "enabled": true, 12 | "cert": "./_certs/jser.cc.crt", 13 | "key": "./_certs/jser.cc.key", 14 | "redirectPort": 80 15 | }, 16 | "browseFolder": { 17 | "^/": false 18 | }, 19 | "connStr": "<数据库链接字符串>", 20 | "handlers": { 21 | "^/": "$./handlers/mvc", 22 | ".css$": "nokit-handler-less", 23 | ".less$": "nokit-handler-less" 24 | }, 25 | "filters": { 26 | "^/:access": "./filters/access", 27 | "^/:data": "./filters/common-data", 28 | "^/:ua": "./filters/ua", 29 | "^/:pjax": "nokit-pjax" 30 | }, 31 | "folders": { 32 | "public": { 33 | "^/mditor": "./node_modules", 34 | "^/client/pjax.min.js$": "./node_modules/nokit-pjax", 35 | "^/client/pjax.min.css$": "./node_modules/nokit-pjax" 36 | } 37 | }, 38 | "session": { 39 | "provider": "nokit-session-redis", 40 | "host": "localhost", 41 | "port": 6379, 42 | "timeout": 99999999 43 | }, 44 | "mvc": { 45 | "routes": { 46 | "get /": "./topic-list", 47 | "get /topic": "./topic-list", 48 | "/topic/{type}/p{pageIndex}": "./topic-list", 49 | "/topic/new": { 50 | "action": "new", 51 | "target": "./topic-edit", 52 | "required": [ 53 | "signed" 54 | ] 55 | }, 56 | "/topic/{id}/edit": { 57 | "target": "./topic-edit", 58 | "required": [ 59 | "signed" 60 | ] 61 | }, 62 | "post /topic/{id}/edit/submit": { 63 | "target": "./topic-edit", 64 | "action": "submit", 65 | "required": [ 66 | "signed" 67 | ] 68 | }, 69 | "post /topic/{id}/delete": { 70 | "target": "./topic-view", 71 | "action": "delete", 72 | "required": [ 73 | "signed" 74 | ] 75 | }, 76 | "post /topic/{id}/setTop": { 77 | "target": "./topic-view setTop", 78 | "required": [ 79 | "signed" 80 | ] 81 | }, 82 | "post /topic/{id}/setGood": { 83 | "target": "./topic-view setGood", 84 | "required": [ 85 | "signed" 86 | ] 87 | }, 88 | "post /topic/{id}/removeTop": { 89 | "target": "./topic-view removeTop", 90 | "required": [ 91 | "signed" 92 | ] 93 | }, 94 | "post /topic/{id}/removeGood": { 95 | "target": "./topic-view removeGood", 96 | "required": [ 97 | "signed" 98 | ] 99 | }, 100 | "post /topic/{id}/addComment": { 101 | "target": "./topic-view addComment", 102 | "required": [ 103 | "signed" 104 | ] 105 | }, 106 | "post /topic/{id}/delComment": { 107 | "target": "./topic-view delComment", 108 | "required": [ 109 | "signed" 110 | ] 111 | }, 112 | "/topic/{id}": "./topic-view", 113 | "/signup": "./signup", 114 | "post /signup/submit": "./signup submit", 115 | "/verify/{verifyCode}": "./verify-mail", 116 | "/signin": "./signin", 117 | "post /signin/submit": "./signin submit", 118 | "/forget-password": "./forget-password", 119 | "post /forget-password/submit": "./forget-password submit", 120 | "/profile": { 121 | "target": "./profile", 122 | "required": [ 123 | "signed" 124 | ] 125 | }, 126 | "/control-panel": { 127 | "target": "./control-panel", 128 | "required": [ 129 | "signed", 130 | "admin" 131 | ] 132 | }, 133 | "/upload": { 134 | "target": "./upload", 135 | "required": [ 136 | "signed" 137 | ] 138 | }, 139 | "/users": "./user-list", 140 | "/user/{name}": "./user-info", 141 | "/uid/{id}": "./uid", 142 | "/message": { 143 | "target": "./message", 144 | "required": [ 145 | "signed" 146 | ] 147 | }, 148 | "/about": "./about", 149 | "/api": "./api", 150 | "/client": "./client", 151 | "/oauth/github": "./oauth/github", 152 | "/search": "./search" 153 | } 154 | }, 155 | "topicTypes": { 156 | "good": { 157 | "text": "精华", 158 | "admin": true 159 | }, 160 | "ask": { 161 | "text": "问答" 162 | }, 163 | "share": { 164 | "text": "分享" 165 | }, 166 | "event": { 167 | "text": "活动" 168 | }, 169 | "job": { 170 | "text": "招聘" 171 | } 172 | }, 173 | "mail": { 174 | "conn": { 175 | "host": "", 176 | "port": 465, 177 | "secure": true, 178 | "auth": { 179 | "user": "<邮箱用户名>", 180 | "pass": "<邮箱密码>" 181 | } 182 | }, 183 | "from": "JSER社区 ", 184 | "reg_tmpl": { 185 | "subject": "确认注册", 186 | "body": "./tmpls/verify-mail.html" 187 | }, 188 | "pwd_tmpl": { 189 | "subject": "找回密码", 190 | "body": "./tmpls/reset-password.html" 191 | } 192 | }, 193 | "qiniu": { 194 | "accessKey": "<七牛 accessKey>", 195 | "secretKey": "<七牛 secretKey>", 196 | "bucket": "<七牛 bucket>", 197 | "origin": "<七牛 origin>" 198 | }, 199 | "oauth": { 200 | "github": { 201 | "auth_url": "", 202 | "token_url": "", 203 | "redirect_uri": "", 204 | "user_url": "", 205 | "email_url": "", 206 | "scope": "user,user:email", 207 | "client_id": "", 208 | "client_secret": "" 209 | } 210 | }, 211 | "adminUser": [ 212 | "admin@xhou.net" 213 | ], 214 | "scoreRules": { 215 | "topic-add": 2, 216 | "topic-del": -2, 217 | "comment-add": 2, 218 | "comment-del": -2, 219 | "good-add": 5, 220 | "good-del": -5, 221 | "top-add": 5, 222 | "top-del": -5, 223 | "like-add": 1, 224 | "link-del": -1, 225 | "dislink-add": 0, 226 | "dislink-del": 0 227 | }, 228 | "links": [ 229 | { 230 | "name": "CNodeJs", 231 | "url": "http://www.cnodejs.org" 232 | }, 233 | { 234 | "name": "SegmentFault", 235 | "url": "http://segmentfault.com/" 236 | }, 237 | { 238 | "name": "Ruby China", 239 | "url": "https://ruby-china.org/" 240 | } 241 | ], 242 | "footbar": { 243 | "links": [ 244 | { 245 | "name": "GitHub", 246 | "url": "https://github.com/jser-dev" 247 | }, 248 | { 249 | "name": "微博", 250 | "url": "http://weibo.com/jser-dev" 251 | }, 252 | { 253 | "name": "Twitter", 254 | "url": "http://twitter.com/jser-dev" 255 | } 256 | ], 257 | "info": "一个 “纯粹、简单、自由” 的 “JS程序猿” 社区,豫ICP备15033500" 258 | }, 259 | "guardian": { 260 | "name": "@jser-dev", 261 | "url": "https://github.com/jser-dev" 262 | } 263 | } -------------------------------------------------------------------------------- /controllers/about.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 关于控制器 3 | **/ 4 | var AboutController = function () { }; 5 | 6 | /** 7 | * 默认 action 8 | **/ 9 | AboutController.prototype.index = function () { 10 | var self = this; 11 | 12 | self.render("about.html"); 13 | 14 | }; 15 | 16 | module.exports = AboutController; -------------------------------------------------------------------------------- /controllers/api.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * API 说明页 Controller 5 | **/ 6 | var AboutController = module.exports = function () { }; 7 | 8 | /** 9 | * 默认 action 10 | **/ 11 | AboutController.prototype.index = function () { 12 | var self = this; 13 | 14 | self.render("api.html"); 15 | 16 | }; -------------------------------------------------------------------------------- /controllers/client.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * 客户端页 Controller 5 | **/ 6 | var AboutController = module.exports = function () { }; 7 | 8 | /** 9 | * 默认 action 10 | **/ 11 | AboutController.prototype.index = function () { 12 | var self = this; 13 | 14 | self.render("client.html"); 15 | 16 | }; -------------------------------------------------------------------------------- /controllers/control-panel.js: -------------------------------------------------------------------------------- 1 | var utils = require("../common/utils"); 2 | var Access = require("../models/access"); 3 | 4 | /** 5 | * 活动控制器 6 | **/ 7 | var ControlPanelController = function () { }; 8 | 9 | var convertAccessList = function (accessList) { 10 | var access = {}; 11 | accessList.forEach(function (item) { 12 | access[item.role] = item.users; 13 | }); 14 | return access; 15 | }; 16 | 17 | /** 18 | * 默认 action 19 | **/ 20 | ControlPanelController.prototype.index = function () { 21 | var self = this; 22 | Access.read(function (err, accessList) { 23 | if (err) { 24 | return self.context.error(err); 25 | } 26 | self.render("control-panel.html", { 27 | access: convertAccessList(accessList) 28 | }); 29 | }); 30 | }; 31 | 32 | /** 33 | * 保存权限配置 34 | **/ 35 | ControlPanelController.prototype.saveAccess = function () { 36 | var self = this; 37 | var configs = self.context.configs; 38 | var accessList = []; 39 | utils.each(configs.roles, function (roleName) { 40 | accessList.push({ 41 | role: roleName, 42 | users: self.context.param(roleName) 43 | }); 44 | }); 45 | Access.save(accessList, function (err) { 46 | if (err) { 47 | return self.context.error(err); 48 | } 49 | self.context.redirect("/control-panel"); 50 | }); 51 | }; 52 | 53 | module.exports = ControlPanelController; -------------------------------------------------------------------------------- /controllers/forget-password.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * 活动控制器 5 | **/ 6 | var ForgetPasswordController = module.exports = function () { }; 7 | 8 | /** 9 | * 默认 action 10 | **/ 11 | ForgetPasswordController.prototype.index = function () { 12 | var self = this; 13 | 14 | self.render("forget-password.html"); 15 | 16 | }; -------------------------------------------------------------------------------- /controllers/message.js: -------------------------------------------------------------------------------- 1 | /* global nokit */ 2 | var Message = require("../models/message"); 3 | var Task = nokit.Task; 4 | 5 | /** 6 | * 活动控制器 7 | **/ 8 | var MessageController = function () { }; 9 | 10 | /** 11 | * 初始化 controller 12 | **/ 13 | MessageController.prototype.init = function () { 14 | var self = this; 15 | if (!self.context.user) { 16 | return self.context.forbidden(); 17 | } 18 | self.ready(); 19 | }; 20 | 21 | /** 22 | * 默认 action 23 | **/ 24 | MessageController.prototype.index = function () { 25 | var self = this; 26 | var userId = self.context.user._id; 27 | var task = Task.create(); 28 | task.add(function (done) { 29 | Message.getAllByUserId(userId, function (err, msgList) { 30 | if (err) { 31 | return this.context.error(err); 32 | } 33 | self.msgList = msgList; 34 | done(); 35 | }); 36 | }); 37 | task.end(function () { 38 | self.render("message", self); 39 | }); 40 | }; 41 | 42 | /** 43 | * 标记所有为已读 44 | **/ 45 | MessageController.prototype.markAllAsRead = function () { 46 | var self = this; 47 | Message.markAllAsReadByUserId(self.context.user._id, function (err) { 48 | if (err) { 49 | return self.context.error(err); 50 | } 51 | self.context.redirect("/message"); 52 | }); 53 | }; 54 | 55 | /** 56 | * 清空所有消息 57 | **/ 58 | MessageController.prototype.deleteAll = function () { 59 | var self = this; 60 | Message.deleteAllByUserId(self.context.user._id, function (err) { 61 | if (err) { 62 | return self.context.error(err); 63 | } 64 | self.context.redirect("/message"); 65 | }); 66 | }; 67 | 68 | /** 69 | * 删除一个消息 70 | **/ 71 | MessageController.prototype.delete = function () { 72 | var self = this; 73 | var msgId = self.context.params("msgId"); 74 | Message.deleteById(msgId, function (err) { 75 | if (err) { 76 | return self.context.error(err); 77 | } 78 | self.context.redirect("/message"); 79 | }); 80 | }; 81 | 82 | /** 83 | * 打开一个链接 84 | **/ 85 | MessageController.prototype.openLink = function () { 86 | var self = this; 87 | var msgId = self.context.params("msgId"); 88 | Message.markAsReadById(msgId, function (err, msg) { 89 | if (err) { 90 | return self.context.error(err); 91 | } 92 | if (msg && msg.link) { 93 | self.context.redirect(msg.link); 94 | } else { 95 | self.context.redirect("/message"); 96 | } 97 | }); 98 | }; 99 | 100 | module.exports = MessageController; -------------------------------------------------------------------------------- /controllers/oauth/github.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /* global nokit */ 3 | 4 | var request = require('request'); 5 | var User = require('../../models/user'); 6 | var utils = require("../../common/utils"); 7 | var Task = nokit.Task; 8 | 9 | var OAUTH_STATE_SESSION_KEY = "oauth_state"; 10 | 11 | /** 12 | * GitHub 认证控制器 13 | **/ 14 | var GitHubController = module.exports = function (context) { 15 | var self = this; 16 | self.configs = context.configs.oauth.github; 17 | }; 18 | 19 | /** 20 | * 默认 action 21 | **/ 22 | GitHubController.prototype.index = function () { 23 | var self = this; 24 | var query_args = []; 25 | var state = nokit.utils.newGuid(); 26 | self.context.session.set(OAUTH_STATE_SESSION_KEY, state, function () { 27 | query_args.push("client_id=" + self.configs.client_id); 28 | query_args.push("scope=" + self.configs.scope); 29 | query_args.push("redirect_uri=" + self.configs.redirect_uri); 30 | query_args.push("state=" + state); 31 | self.context.redirect(self.configs.auth_url + '?' + query_args.join('&')); 32 | }); 33 | }; 34 | 35 | /** 36 | * 获取 GitHub 用户的授权 token 37 | **/ 38 | GitHubController.prototype.getToken = function (callback) { 39 | var self = this; 40 | var code = self.context.param('code'); 41 | var state = self.context.param('state'); 42 | self.context.session.get(OAUTH_STATE_SESSION_KEY, function (_state) { 43 | if (_state != state) { 44 | return self.context.error("OAuth 认证在验证 state 时,发现不匹配。"); 45 | } 46 | request.post({ 47 | "url": self.configs.token_url, 48 | "headers": { 49 | "Accept": "application/json", 50 | "X-OAuth-Scopes": self.configs.scope, 51 | "X-Accepted-OAuth-Scopes": self.configs.scope 52 | }, 53 | "formData": { 54 | "client_id": self.configs.client_id, 55 | "client_secret": self.configs.client_secret, 56 | "code": code, 57 | "state": state 58 | } 59 | }, function (err, httpResponse, body) { 60 | var tokenResult = JSON.parse(body); 61 | callback(err, tokenResult || {}); 62 | }); 63 | }); 64 | }; 65 | 66 | /** 67 | * 获取 GitHub 用户的基本信息 68 | **/ 69 | GitHubController.prototype.getUserInfo = function (access_token, callback) { 70 | var self = this; 71 | var userInfoUrl = self.configs.user_url + "?access_token=" + access_token; 72 | request({ 73 | "url": userInfoUrl, 74 | "headers": { 75 | "User-Agent": "JSER",//Awesome-Octocat-App, 76 | "X-OAuth-Scopes": self.configs.scope, 77 | "X-Accepted-OAuth-Scopes": self.configs.scope 78 | } 79 | }, function (err, httpResponse, body) { 80 | var userInfo = JSON.parse(body); 81 | callback(err, userInfo || {}); 82 | }); 83 | }; 84 | 85 | /** 86 | * 获取 GitHub 用户的 email 87 | **/ 88 | GitHubController.prototype.getEmail = function (access_token, callback) { 89 | var self = this; 90 | request({ 91 | "url": self.configs.email_url + "?access_token=" + access_token, 92 | "headers": { 93 | "User-Agent": "JSER",//Awesome-Octocat-App, 94 | "X-OAuth-Scopes": self.configs.scope, 95 | "X-Accepted-OAuth-Scopes": self.configs.scope 96 | } 97 | }, function (err, httpResponse, body) { 98 | var emailArray = JSON.parse(body); 99 | callback(err, emailArray || []); 100 | }); 101 | }; 102 | 103 | //github 回调 104 | GitHubController.prototype.callback = function () { 105 | var self = this; 106 | var task = new Task(); 107 | task.add({ 108 | "getToken": function (done) { 109 | self.getToken(done); 110 | }, 111 | "getUserInfo": function (done) { 112 | var tokenResult = task.result["getToken"]; 113 | self.getUserInfo(tokenResult.access_token, done); 114 | } 115 | }); 116 | task.seq(function (err, result) { 117 | if (err) { 118 | return self.context.error(err); 119 | } 120 | var userInfo = task.result["getUserInfo"]; 121 | if (userInfo.email) { 122 | self.loginUser(userInfo); 123 | } else { 124 | var tokenResult = task.result["getToken"]; 125 | self.getEmail(tokenResult.access_token, function (err, emailArray) { 126 | if (err) { 127 | return self.context.error(err); 128 | } 129 | var emailItem = self.parseEmail(emailArray); 130 | userInfo.email = emailItem.email; 131 | self.loginUser(userInfo); 132 | }); 133 | } 134 | }); 135 | }; 136 | 137 | /** 138 | * 解析 email 139 | **/ 140 | GitHubController.prototype.parseEmail = function (emailArray) { 141 | //查找主 email 142 | var emailItem = emailArray.filter(function (item) { 143 | return item.primary; 144 | })[0]; 145 | if (!emailItem) { 146 | //查找验证过的第一个 email 147 | emailItem = emailArray.filter(function (item) { 148 | return item.verified; 149 | })[0]; 150 | } 151 | return emailItem || emailArray[0] || {};; 152 | }; 153 | 154 | /** 155 | * 登录 OAuth 认证为的用户 156 | **/ 157 | GitHubController.prototype.loginUser = function (userInfo) { 158 | var self = this; 159 | var user = new User(); 160 | user.email = userInfo.email; 161 | user.name = "GitHub-" + (userInfo.login || userInfo.email); 162 | user.avatar = userInfo.avatar_url; 163 | user.password = utils.newGuid(); 164 | user.verifyCode = ""; 165 | User.oAuth(user, function (err, authedUser) { 166 | if (err) { 167 | return self.context.error(err); 168 | } 169 | self.context.session.set('user', authedUser, function () { 170 | self.session.get("referer", function (referer) { 171 | self.context.redirect(referer || "/"); 172 | }); 173 | }); 174 | }); 175 | }; -------------------------------------------------------------------------------- /controllers/profile.js: -------------------------------------------------------------------------------- 1 | var User = require('../models/user'); 2 | 3 | var ProfileController = module.exports = function () { }; 4 | 5 | //默认 action 6 | ProfileController.prototype.index = function () { 7 | var self = this; 8 | self.render("profile.html", { 9 | user: self.context.user 10 | }); 11 | }; 12 | 13 | ProfileController.prototype.saveBaseInfo = function () { 14 | var self = this; 15 | var name = self.context.param("name"); 16 | var avatar = self.context.request.files["avatar"]; 17 | User.saveBaseInfo({ 18 | "id": self.context.user._id, 19 | "name": name, 20 | "avatar": avatar && avatar.size > 0 ? avatar.path : null, 21 | "oldAvatar": self.context.user.avatar 22 | }, function (err, info) { 23 | if (!err && info) { 24 | self.context.user.name = info.name || self.context.user.name; 25 | self.context.user.avatar = info.avatar || self.context.user.avatar; 26 | self.context.session.set("user", self.context.user); 27 | } 28 | self.render("profile.html", { 29 | saveBaseInfoMessage: err || "保存信息完成", 30 | user: self.context.user 31 | }); 32 | }); 33 | }; 34 | 35 | ProfileController.prototype.changePassword = function () { 36 | var self = this; 37 | var password = self.context.param("password"); 38 | var confirmPassword = self.context.param("confirmPassword"); 39 | if (password != confirmPassword) { 40 | return self.render("profile.html", { 41 | changePasswordMessage: "两次密码不一致", 42 | user: self.context.user 43 | }); 44 | } 45 | User.setPassword({ 46 | "id": self.context.user._id, 47 | "password": password 48 | }, function (err) { 49 | self.render("profile.html", { 50 | changePasswordMessage: err || "修改密码完成", 51 | user: self.context.user 52 | }); 53 | }); 54 | }; 55 | 56 | //退出 57 | ProfileController.prototype.signout = function () { 58 | var self = this; 59 | self.context.session.remove("user", function () { 60 | self.context.redirect("/"); 61 | }); 62 | }; -------------------------------------------------------------------------------- /controllers/search.js: -------------------------------------------------------------------------------- 1 | /* global nokit */ 2 | var Task = nokit.Task; 3 | var User = require("../models/user"); 4 | var Topic = require("../models/topic"); 5 | 6 | /** 7 | * 活动控制器 8 | **/ 9 | var SearchController = function () { }; 10 | 11 | /** 12 | * 默认 action 13 | **/ 14 | SearchController.prototype.index = function () { 15 | var self = this; 16 | self.keyword = self.context.param('keyword'); 17 | var task = Task.create(); 18 | task.add(function (done) { 19 | User.search(self.keyword, function (err, userList) { 20 | if (err) { 21 | return self.context.error(err); 22 | } 23 | self.userList = userList; 24 | done(); 25 | }); 26 | }); 27 | task.add(function (done) { 28 | Topic.search(self.keyword, function (err, topicList) { 29 | if (err) { 30 | return self.context.error(err); 31 | } 32 | self.topicList = topicList; 33 | done(); 34 | }); 35 | }); 36 | task.end(function () { 37 | self.render("search.html", self); 38 | }); 39 | }; 40 | 41 | module.exports = SearchController; -------------------------------------------------------------------------------- /controllers/signin.js: -------------------------------------------------------------------------------- 1 | var utils = require('../common/utils'); 2 | var User = require('../models/user'); 3 | 4 | var SignInController = module.exports = function () { }; 5 | 6 | SignInController.prototype.index = function () { 7 | var self = this; 8 | //记录进入登录页之前的页面 9 | var referer = self.request.headers["referer"]; 10 | if (referer && 11 | referer.indexOf("/signin") < 0 && 12 | referer.indexOf("/oauth") < 0 && 13 | referer.indexOf("/verify") < 0) { 14 | //暂存 referer 15 | self.session.set("referer", referer); 16 | } 17 | //-- 18 | if (referer && referer.indexOf("/signup") > -1) { 19 | self.session.set("referer", "/"); 20 | } 21 | self.render("signin.html"); 22 | }; 23 | 24 | SignInController.prototype.submit = function () { 25 | var self = this; 26 | var userInfo = self.getUserInfo(); 27 | User.signIn(userInfo, function (err, user) { 28 | if (err) { 29 | self.render("signin.html", { 30 | status: !err, 31 | message: err, 32 | user: userInfo 33 | }); 34 | } else { 35 | self.session.set("user", user, function () { 36 | self.session.get("referer", function (referer) { 37 | self.context.redirect(referer || "/"); 38 | }); 39 | }); 40 | } 41 | }); 42 | }; 43 | 44 | //通过浏览器 post 过来的数据创建一个 “User” 45 | SignInController.prototype.getUserInfo = function () { 46 | var self = this; 47 | var user = User.create(); 48 | var req = self.context.request; 49 | user.email = req.body.email; 50 | user.password = req.body.password; 51 | return user; 52 | }; -------------------------------------------------------------------------------- /controllers/signup.js: -------------------------------------------------------------------------------- 1 | var User = require('../models/user'); 2 | 3 | //定义 controller 4 | var SignUpController = module.exports = function () { }; 5 | 6 | //默认 action 7 | SignUpController.prototype.index = function () { 8 | var self = this; 9 | self.render("signup.html"); 10 | }; 11 | 12 | //提交注册信息 13 | SignUpController.prototype.submit = function () { 14 | var self = this; 15 | var userInfo = self.getUserInfo(); 16 | User.signUp(userInfo, function (err, user) { 17 | self.render('signup.html', { 18 | status: !err, 19 | message: err || "注册成功", 20 | user: user || userInfo 21 | }); 22 | }); 23 | }; 24 | 25 | //通过浏览器 post 过来的数据创建一个 “User” 26 | SignUpController.prototype.getUserInfo = function () { 27 | var self = this; 28 | var user = User.create(); 29 | var req = self.context.request; 30 | user.email = req.body.email; 31 | user.password = req.body.password; 32 | user.name = req.body.name; 33 | return user; 34 | }; -------------------------------------------------------------------------------- /controllers/topic-edit.js: -------------------------------------------------------------------------------- 1 | var Topic = require('../models/topic'); 2 | var status = require('../models/status').topic; 3 | 4 | /** 5 | * 话是控制器 6 | **/ 7 | var TopicEditController = module.exports = function () { }; 8 | 9 | TopicEditController.prototype.init = function () { 10 | var self = this; 11 | self.topicId = self.context.param('id'); 12 | self.ready(); 13 | }; 14 | 15 | TopicEditController.prototype.new = function () { 16 | var self = this; 17 | Topic.new(self.context.user, function (err, topic) { 18 | if (err) { 19 | return self.context.error(err); 20 | } 21 | self.context.redirect("/topic/" + topic.id + "/edit"); 22 | }); 23 | }; 24 | 25 | TopicEditController.prototype.index = function () { 26 | var self = this; 27 | Topic.get(self.topicId, function (err, topic) { 28 | if (err) { 29 | return self.context.error(err); 30 | } 31 | self.render("topic-edit.html", { 32 | "topic": topic, 33 | "id": self.topicId, 34 | "types": self.topicTypes 35 | }); 36 | }); 37 | }; 38 | 39 | TopicEditController.prototype.submit = function () { 40 | var self = this; 41 | Topic.get(self.topicId, function (err, topic) { 42 | if (err) { 43 | return self.context.error(err); 44 | } 45 | if (self.context.param("publish")) { 46 | topic.status = status.PUBLISH; 47 | } else { 48 | topic.status = status.DRAFT; 49 | } 50 | topic.title = self.context.param('title'); 51 | topic.content = self.context.param('content'); 52 | topic.type = self.context.param('type'); 53 | Topic.save(topic, function (err) { 54 | if (!err && topic.status == status.PUBLISH) { 55 | return self.context.redirect("/topic/" + topic.id); 56 | } 57 | self.render("topic-edit.html", { 58 | "saveMessage": err || "保存成功", 59 | "topic": topic, 60 | "id": self.topicId, 61 | "types": self.topicTypes 62 | }); 63 | }); 64 | }); 65 | }; -------------------------------------------------------------------------------- /controllers/topic-list.js: -------------------------------------------------------------------------------- 1 | /* global nokit */ 2 | var Task = nokit.Task; 3 | var Topic = require('../models/topic'); 4 | 5 | var PAGE_SIZE = 12; 6 | 7 | /** 8 | * 话是控制器 9 | **/ 10 | var TopicListController = module.exports = function () { }; 11 | 12 | TopicListController.prototype.init = function () { 13 | var self = this; 14 | 15 | self.currentType = self.context.param('type') || 'all'; 16 | self.pageIndex = self.context.param('pageIndex') || 1; 17 | //处理查询选项 18 | self.options = { 19 | pageSize: PAGE_SIZE, 20 | pageIndex: self.pageIndex, 21 | }; 22 | //处理查询条件 23 | self.options.conditions = {}; 24 | if (self.currentType == 'good') { 25 | self.options.conditions.good = true; 26 | } else { 27 | self.options.conditions.type = self.currentType; 28 | } 29 | //创建并行查询任务 30 | var task = new Task(); 31 | task.add("count", function (done) { 32 | Topic.getCount(self.options.conditions, function (err, count) { 33 | if (err) { 34 | return self.context.error(err); 35 | } 36 | done(null, count); 37 | }); 38 | }); 39 | task.end(function (err, result) { 40 | if (err) { 41 | return self.context.error(err); 42 | } 43 | self.count = result["count"]; 44 | self.pageCount = parseInt((self.count + (PAGE_SIZE - 1)) / PAGE_SIZE); 45 | self.pageBegin = self.pageIndex - 2; 46 | if (self.pageBegin < 1) self.pageBegin = 1; 47 | self.pageEnd = self.pageBegin + 5; 48 | if (self.pageEnd > self.pageCount) self.pageEnd = self.pageCount; 49 | self.ready(); 50 | }); 51 | }; 52 | 53 | /** 54 | * 默认 action 55 | **/ 56 | TopicListController.prototype.index = function () { 57 | var self = this; 58 | Topic.getList(self.options, function (err, list) { 59 | self.render("topic-list.html", { 60 | types: self.types, 61 | currentType: self.currentType, 62 | pageIndex: self.pageIndex, 63 | pageBegin: self.pageBegin, 64 | pageEnd: self.pageEnd, 65 | pageCount: self.pageCount, 66 | list: list 67 | }); 68 | }); 69 | }; -------------------------------------------------------------------------------- /controllers/topic-view.js: -------------------------------------------------------------------------------- 1 | /* global nokit */ 2 | var Topic = require('../models/topic'); 3 | var Comment = require('../models/comment'); 4 | var status = require('../models/status'); 5 | var utils = require('../common/utils'); 6 | var Task = nokit.Task; 7 | 8 | /** 9 | * 话题控制器 10 | **/ 11 | var TopicViewController = module.exports = function () { }; 12 | 13 | TopicViewController.prototype.init = function () { 14 | var self = this; 15 | self.topicId = self.context.param('id'); 16 | var task = new Task(); 17 | task.add('topic', function (done) { 18 | Topic.get(self.topicId, function (err, topic) { 19 | if (err) { 20 | return self.context.error(err); 21 | } 22 | if (!topic || 23 | (topic.status != status.topic.PUBLISH && 24 | self.context.route.action != 'delete')) { 25 | return self.context.notFound(); 26 | } 27 | self.topic = topic; 28 | done(); 29 | }); 30 | }); 31 | task.end(function () { 32 | self.ready(); 33 | }); 34 | }; 35 | 36 | /** 37 | * 验证当前用户 38 | **/ 39 | TopicViewController.prototype.verifyCurrentUser = function (allowUserId) { 40 | var self = this; 41 | allowUserId = allowUserId || ""; 42 | return (self.context.user && 43 | (self.context.user.isAdmin || 44 | self.context.user._id.toString() == allowUserId.toString())); 45 | }; 46 | 47 | /** 48 | * 默认 action 49 | **/ 50 | TopicViewController.prototype.index = function () { 51 | var self = this; 52 | //阅读数 +1 53 | self.topic.read++; 54 | self.topic.save(); 55 | self.render("topic-view.html", { 56 | id: self.topicId, 57 | topic: self.topic, 58 | user: self.context.user 59 | }); 60 | }; 61 | 62 | /** 63 | * 删除一个贴子 64 | **/ 65 | TopicViewController.prototype.delete = function () { 66 | var self = this; 67 | if (!self.verifyCurrentUser(self.topic.author._id.toString())) { 68 | return self.context.forbidden(); 69 | } 70 | Topic.delete(self.topicId, function (err) { 71 | if (err) { 72 | return self.context.error(err); 73 | } 74 | self.context.redirect("/topic"); 75 | }); 76 | }; 77 | 78 | /** 79 | * 将话题添加精华 80 | **/ 81 | TopicViewController.prototype.setGood = function () { 82 | var self = this; 83 | if (!self.verifyCurrentUser()) { 84 | return self.context.forbidden(); 85 | } 86 | Topic.setGood(self.topicId, function (err) { 87 | if (err) { 88 | return self.context.error(err); 89 | } 90 | self.context.redirect("/topic/" + self.topicId + "#control"); 91 | }); 92 | }; 93 | 94 | /** 95 | * 将话题移除精华 96 | **/ 97 | TopicViewController.prototype.removeGood = function () { 98 | var self = this; 99 | if (!self.verifyCurrentUser()) { 100 | return self.context.forbidden(); 101 | } 102 | Topic.removeGood(self.topicId, function (err) { 103 | if (err) { 104 | return self.context.error(err); 105 | } 106 | self.context.redirect("/topic/" + self.topicId + "#control"); 107 | }); 108 | }; 109 | 110 | /** 111 | * 将话题置顶 112 | **/ 113 | TopicViewController.prototype.setTop = function () { 114 | var self = this; 115 | if (!self.verifyCurrentUser()) { 116 | return self.context.forbidden(); 117 | } 118 | Topic.setTop(self.topicId, function (err) { 119 | if (err) { 120 | return self.context.error(err); 121 | } 122 | self.context.redirect("/topic/" + self.topicId + "#control"); 123 | }); 124 | }; 125 | 126 | /** 127 | * 将话题移除置顶 128 | **/ 129 | TopicViewController.prototype.removeTop = function () { 130 | var self = this; 131 | if (!self.verifyCurrentUser()) { 132 | return self.context.forbidden(); 133 | } 134 | Topic.removeTop(self.topicId, function (err) { 135 | if (err) { 136 | return self.context.error(err); 137 | } 138 | self.context.redirect("/topic/" + self.topicId + "#control"); 139 | }); 140 | }; 141 | 142 | /** 143 | * 添加评论 144 | **/ 145 | TopicViewController.prototype.addComment = function () { 146 | var self = this; 147 | if (!self.context.user) { 148 | return self.context.forbidden(); 149 | } 150 | var content = self.context.param('content'); 151 | var comment = new Comment(); 152 | comment.content = content; 153 | comment.author = self.context.user; 154 | comment._author = self.context.user; 155 | comment.topic = self.topic; 156 | Comment.save(comment, function (err) { 157 | if (!err) { 158 | return self.context.redirect("/topic/" + self.topicId + '#' + comment.id); 159 | } 160 | self.render("topic-view.html", { 161 | "commentMessage": err + "", 162 | "id": self.topicId, 163 | "topic": self.topic, 164 | "user": self.context.user, 165 | "newComment": content 166 | }); 167 | }); 168 | }; 169 | 170 | /** 171 | * 删除一个评论 172 | **/ 173 | TopicViewController.prototype.delComment = function () { 174 | var self = this; 175 | var commentId = self.context.param("commentId"); 176 | Comment.get(commentId, function (err, comment) { 177 | if (err) { 178 | return self.context.error(err); 179 | } 180 | if (!self.verifyCurrentUser(comment.author._id.toString())) { 181 | return self.context.forbidden(); 182 | } 183 | Comment.delete(commentId, function (err) { 184 | if (err) { 185 | return self.context.error(err); 186 | } 187 | self.context.redirect("/topic/" + self.topicId + "#comments"); 188 | }); 189 | }); 190 | }; -------------------------------------------------------------------------------- /controllers/uid.js: -------------------------------------------------------------------------------- 1 | var User = require("../models/user"); 2 | 3 | /** 4 | * UIDController 5 | **/ 6 | var UIDController = function () { }; 7 | 8 | /** 9 | * 初始化 10 | **/ 11 | UIDController.prototype.init = function () { 12 | var self = this; 13 | self.ready(); 14 | } 15 | 16 | /** 17 | * 默认 action 18 | **/ 19 | UIDController.prototype.index = function () { 20 | var self = this; 21 | var userId = self.context.param("id"); 22 | User.getUser(userId, function (err, user) { 23 | if (err) { 24 | return self.context.error(err); 25 | } 26 | if (!user) { 27 | return self.context.notFound(); 28 | } 29 | self.context.redirect("/user/" + encodeURIComponent(user.name)); 30 | }); 31 | } 32 | 33 | module.exports = UIDController; -------------------------------------------------------------------------------- /controllers/upload.js: -------------------------------------------------------------------------------- 1 | var qn = require("../common/qn"); 2 | var utils = require("../common/utils"); 3 | /** 4 | * UploadController 5 | **/ 6 | var UploadController = function () { }; 7 | 8 | /** 9 | * 初始化方法,每次请求都会先执行 init 方法 10 | **/ 11 | UploadController.prototype.init = function () { 12 | var self = this; 13 | self.ready(); 14 | } 15 | 16 | /** 17 | * 默认 action 18 | **/ 19 | UploadController.prototype.index = function () { 20 | var self = this; 21 | var imageFile = self.context.req.files["image"]; 22 | if (!imageFile || imageFile.size < 1) { 23 | return self.context.json({ 24 | err: "图片不合法" 25 | }); 26 | } 27 | var fileKey = "topic-" + utils.newGuid(); 28 | qn.client.uploadImage(imageFile.path, fileKey, function (err, result) { 29 | if (err) { 30 | return self.context.json({ 31 | err: err.message || err 32 | }); 33 | } 34 | var imageUrl = qn.client.getUrl(fileKey); 35 | self.context.json({ 36 | err: false, 37 | url: imageUrl 38 | }); 39 | }); 40 | }; 41 | 42 | module.exports = UploadController; 43 | 44 | -------------------------------------------------------------------------------- /controllers/user-info.js: -------------------------------------------------------------------------------- 1 | /* global nokit */ 2 | var User = require('../models/user'); 3 | var Topic = require("../models/topic"); 4 | var Comment = require("../models/comment"); 5 | var Message = require("../models/message"); 6 | var Task = nokit.Task; 7 | 8 | /** 9 | * 活动控制器 10 | **/ 11 | var UserInfoController = function () { }; 12 | 13 | UserInfoController.prototype.init = function () { 14 | var self = this; 15 | var _name = self.context.param("name"); 16 | User.getUserByName(_name, function (err, user) { 17 | if (err) { 18 | return self.context.error(err); 19 | } 20 | if (!user) { 21 | return self.context.notFound(); 22 | } 23 | self.user = user; 24 | self.userId = user._id; 25 | self.ready(); 26 | }); 27 | }; 28 | 29 | /** 30 | * 默认 action 31 | **/ 32 | UserInfoController.prototype.index = function () { 33 | var self = this; 34 | var task = Task.create(); 35 | task.add("topicList", function (done) { 36 | Topic.getLastByUserId(self.userId, done); 37 | }); 38 | task.add("commentList", function (done) { 39 | Comment.getLastByUserId(self.userId, done); 40 | }); 41 | task.add("draftList", function (done) { 42 | Topic.getDraftList(self.userId, done); 43 | }); 44 | task.end(function (err, rs) { 45 | if (err) { 46 | return self.context.error(err); 47 | } 48 | self.topicList = rs.topicList; 49 | self.commentList = rs.commentList; 50 | self.draftList = rs.draftList; 51 | self.render("user-info.html", self); 52 | }); 53 | }; 54 | 55 | module.exports = UserInfoController; -------------------------------------------------------------------------------- /controllers/user-list.js: -------------------------------------------------------------------------------- 1 | var User = require("../models/user"); 2 | 3 | /** 4 | * 积分榜控制器 5 | **/ 6 | var UserListController = function () { }; 7 | 8 | /** 9 | * 默认 action 10 | **/ 11 | UserListController.prototype.index = function () { 12 | var self = this; 13 | self.top = 100; 14 | User.getList(self.top, function (err, userList) { 15 | if (err) { 16 | return self.context.error(err); 17 | } 18 | self.topUserList = userList; 19 | self.render("user-list", self); 20 | }); 21 | }; 22 | 23 | module.exports = UserListController; -------------------------------------------------------------------------------- /controllers/verify-mail.js: -------------------------------------------------------------------------------- 1 | var User = require("../models/user"); 2 | 3 | /** 4 | * 积分榜控制器 5 | **/ 6 | var VerifyMailController = function () { }; 7 | 8 | /** 9 | * 默认 action 10 | **/ 11 | VerifyMailController.prototype.index = function () { 12 | var self = this; 13 | var verifyCode = self.context.param("verifyCode"); 14 | 15 | User.verifyMail(verifyCode, function (err, user) { 16 | if (err) { 17 | return self.context.error(err); 18 | } 19 | self.render("verify-mail.html", { "user": user }); 20 | }); 21 | }; 22 | 23 | module.exports = VerifyMailController; -------------------------------------------------------------------------------- /design/avatar.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/design/avatar.afphoto -------------------------------------------------------------------------------- /design/favicon.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/design/favicon.afphoto -------------------------------------------------------------------------------- /design/logo.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/design/logo.afphoto -------------------------------------------------------------------------------- /filters/access.js: -------------------------------------------------------------------------------- 1 | var AccessFilter = function () { }; 2 | 3 | //已登录 4 | AccessFilter.SIGNED = 'signed'; 5 | 6 | AccessFilter.prototype.onMvcHandle = function (context, next) { 7 | var self = this; 8 | context.session.get("user", function (user) { 9 | context.user = user; 10 | if (user) { 11 | //检查是不是超极管理员 12 | context.user.isAdmin = context.configs.adminUser.some(function (item) { 13 | return item == user.email; 14 | }); 15 | } 16 | if (!context.user && self.requiredHas(context, AccessFilter.SIGNED)) { 17 | context.redirect('/signin'); 18 | } else { 19 | next(); 20 | } 21 | }); 22 | }; 23 | 24 | AccessFilter.prototype.requiredHas = function (context, name) { 25 | return context.route && 26 | context.route.required && 27 | context.route.required.some(function (item) { 28 | return item == name; 29 | }); 30 | }; 31 | 32 | module.exports = AccessFilter; -------------------------------------------------------------------------------- /filters/common-data.js: -------------------------------------------------------------------------------- 1 | /* global nokit */ 2 | var User = require("../models/user"); 3 | var Message = require("../models/message"); 4 | var Task = nokit.Task; 5 | 6 | var CommonDataFilter = function () { }; 7 | 8 | CommonDataFilter.prototype.onMvcHandle = function (context, next) { 9 | var task = new Task(); 10 | task.add("topUserList", function (done) { 11 | User.getList(5, done); 12 | }); 13 | if (context.user) { 14 | task.add("unreadMsgList", function (done) { 15 | Message.getUnreadByUserId(context.user._id, done); 16 | }); 17 | } 18 | task.end(function (err, rs) { 19 | if (err) { 20 | return context.error(err); 21 | } 22 | context.topUserList = rs.topUserList; 23 | context.unreadMsgCount = 0; 24 | if (rs.unreadMsgList) { 25 | context.unreadMsgCount = rs.unreadMsgList.length; 26 | } 27 | next(); 28 | }); 29 | }; 30 | 31 | module.exports = CommonDataFilter; -------------------------------------------------------------------------------- /filters/ua.js: -------------------------------------------------------------------------------- 1 | var UserAgentFilter = function () { }; 2 | 3 | UserAgentFilter.prototype.onMvcHandle = function (context, next) { 4 | var userAgent = context.request.clientInfo.userAgent || ''; 5 | context.userAgent = userAgent; 6 | context.isWeChat = userAgent.indexOf("MicroMessenger") > -1; 7 | next(); 8 | }; 9 | 10 | module.exports = UserAgentFilter; -------------------------------------------------------------------------------- /global.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 在 global 中 require utils,以确保 utils 一些初始化能够完成 3 | **/ 4 | var utils = require('./common/utils'); 5 | var db = require("./common/db"); 6 | var qn = require("./common/qn"); 7 | 8 | /** 9 | * 全局应用程序类 10 | **/ 11 | var Global = module.exports = function () { }; 12 | 13 | Global.prototype.onStart = function (server, done) { 14 | //初始化 utils 15 | utils.init(server); 16 | //建立数据链接 17 | db.connect(server); 18 | //初始化七牛 19 | qn.init(server); 20 | done(); 21 | }; 22 | 23 | Global.prototype.onStop = function (server, done) { 24 | done(); 25 | }; 26 | 27 | Global.prototype.onError = function (context, done) { 28 | done(); 29 | }; 30 | 31 | Global.prototype.onRequest = function (context, done) { 32 | done(); 33 | }; 34 | 35 | Global.prototype.onReceived = function (context, done) { 36 | done(); 37 | }; 38 | 39 | Global.prototype.onResponse = function (context, done) { 40 | done(); 41 | }; -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6" 4 | } 5 | } -------------------------------------------------------------------------------- /models/access.js: -------------------------------------------------------------------------------- 1 | var define = require('./define'); 2 | var status = require("./status").comment; 3 | 4 | // 因为当前版本简化了权限管理,暂时用不到此文件 5 | 6 | /** 7 | * 定义权限配置模型 8 | **/ 9 | var Access = define.Access; 10 | 11 | /** 12 | * 读取权限配置 13 | **/ 14 | Access.read = function (callback) { 15 | var self = this; 16 | self.find(null, callback); 17 | }; 18 | 19 | /** 20 | * 保存权限配置 21 | **/ 22 | Access.save = function (accessList, callback) { 23 | var self = this; 24 | self.remove({ id: { $ne: "" } }, function (err) { 25 | if (err) { 26 | return callback(err); 27 | } 28 | Access.create(accessList, callback); 29 | }); 30 | }; 31 | 32 | module.exports = Access; -------------------------------------------------------------------------------- /models/comment.js: -------------------------------------------------------------------------------- 1 | var define = require('./define'); 2 | var status = require("./status").comment; 3 | var utils = require('../common/utils'); 4 | var score = require('./score'); 5 | var Message = require("./message"); 6 | var User = require("./user"); 7 | 8 | /** 9 | * 定义评论模型 10 | **/ 11 | var Comment = define.Comment; 12 | 13 | /** 14 | * 根据 topicId 获取评论列表 15 | **/ 16 | Comment.getListByTopicId = function (topicId, callback) { 17 | var self = Comment; 18 | self.find({ "topic": topicId, status: status.PUBLISH }) 19 | .sort({ '_id': 1 }) 20 | .populate('author') 21 | .exec(callback); 22 | }; 23 | 24 | /** 25 | * 删除一个话题下的所有评论 26 | **/ 27 | Comment.deleteByTopicId = function (topicId, callback) { 28 | var self = this; 29 | self.update({ "topic": topicId }, { $set: { "status": status.DELETED } }, callback); 30 | }; 31 | 32 | /** 33 | * 根据 userId 获取用户最近的评论 34 | **/ 35 | Comment.getLastByUserId = function (userId, callback) { 36 | var self = Comment; 37 | self.find({ 38 | author: userId, 39 | status: status.PUBLISH 40 | }).sort({ '_id': -1 }) 41 | .skip(0) 42 | .limit(5) 43 | .populate('author') 44 | .populate('topic') 45 | .exec(callback); 46 | }; 47 | 48 | Comment._sendMessages = function (users, comment) { 49 | //向话题作者发消息 50 | Message.send(null, comment.topic.author.name, { 51 | content: comment._author.name + ' 评论了你的话题 "' + comment.topic.title + '"', 52 | link: "/topic/" + comment.topic._id + "#" + comment._id 53 | }); 54 | //向 @ 的人发送消息 55 | Message.send(null, users, { 56 | content: comment._author.name + ' 在评论 "' + comment.topic.title + '" 时提到了你', 57 | link: "/topic/" + comment.topic._id + "#" + comment._id 58 | }); 59 | }; 60 | 61 | /** 62 | * 处理 @用户 63 | **/ 64 | Comment._handleUserLinks = function (content, callback) { 65 | var names = utils.fetchUsers(content); 66 | User.getUsersByNames(names, function (err, users) { 67 | if (err) { 68 | return callback(err); 69 | } 70 | users.forEach(function (user) { 71 | //@[\u4E00-\u9FFFa-zA-Z0-9\-_]+/igm 72 | content = content.replace(new RegExp('@' + user.name, 'igm'), 73 | '[@' + user.name + '](/uid/' + user._id + ')'); 74 | }); 75 | callback(null, { 76 | "content": content, 77 | "users": names 78 | }); 79 | }); 80 | }; 81 | 82 | /** 83 | * 保存一个评论 84 | **/ 85 | Comment.save = function (comment, callback) { 86 | var self = this; 87 | if (!comment.content || comment.content.length < 1) { 88 | return callback("评论内容不能少于 1 个字"); 89 | } 90 | comment.status = comment.status || status.PUBLISH; 91 | self._handleUserLinks(comment.content, function (err, rs) { 92 | if (err) { 93 | return callback(err); 94 | } 95 | comment.html = utils.md2html(rs.content); 96 | comment.save(function (err) { 97 | if (err) { 98 | return callback(err); 99 | } 100 | comment.topic.replay++; 101 | comment.topic.lastReplayAt = comment.updateAt; 102 | comment.topic.lastReplayAuthor = comment.author; 103 | comment.topic.save(callback); 104 | score.add(comment.author._id || comment.author, "comment-add"); 105 | //向相关人员发送消息 106 | self._sendMessages(rs.users, comment); 107 | }); 108 | }); 109 | }; 110 | 111 | /** 112 | * 获取一条评论 113 | **/ 114 | Comment.get = function (commentId, callback) { 115 | var self = this; 116 | self.findById(commentId) 117 | .populate('author') 118 | .populate('topic') 119 | .exec(callback); 120 | }; 121 | 122 | /** 123 | * 删除一个评论 124 | **/ 125 | Comment.delete = function (commentId, callback) { 126 | var self = this; 127 | self.findById(commentId) 128 | .populate('topic') 129 | .exec(function (err, comment) { 130 | if (err) { 131 | return callback(err); 132 | } 133 | //对应贴子回复数 -1 134 | comment.topic.replay--; 135 | comment.topic.save(); 136 | //逻辑删除 137 | comment.status = status.DELETED; 138 | comment.save(callback); 139 | //计算用户得分 140 | score.add(comment.author._id || comment.author, "comment-del"); 141 | }); 142 | }; 143 | 144 | module.exports = Comment; -------------------------------------------------------------------------------- /models/define.js: -------------------------------------------------------------------------------- 1 | var db = require("../common/db"); 2 | var status = require('./status'); 3 | var self = module.exports; 4 | 5 | //定义用户模型 6 | var User = self.User = db.model('User', { 7 | email: { type: String, unique: true, required: true, trim: true }, // email 8 | password: { type: String, default: '', required: true, trim: true }, //密码 9 | name: { type: String, unique: true, required: true, trim: true }, //名字 10 | avatar: { type: String, default: '', required: true, trim: true }, //头像 11 | score: { type: Number, default: 0 }, //积分, 12 | signUpAt: { type: Date, default: Date.now() },//注册时间 13 | role: [{ type: String, default: '' }], 14 | verifyCode: { type: String, default: '' }, //邮箱验证码 15 | status: { type: Number, default: status.user.NORMAL }// 状态 16 | }); 17 | 18 | //定义话题模型 19 | var Topic = self.Topic = db.model('Topic', { 20 | title: { type: String, default: '', trim: true }, //标题 21 | content: { type: String, default: '' }, //内容 22 | html: { type: String, default: '' }, //html内容 23 | type: [{ type: String, default: '', trim: true }], //类型 24 | author: { type: db.types.ObjectId, ref: User.schema.name, required: true }, //作者 25 | lastReplayAuthor: { type: db.types.ObjectId, ref: User.schema.name }, //回复数量 26 | tags: [{ type: String, default: '', trim: true }], //标签, 27 | createAt: { type: Date, default: Date.now() }, //创建时间 28 | updateAt: { type: Date, default: Date.now() }, //更新时间 29 | lastReplayAt: { type: Date, default: Date.now() }, //最后回复时间 30 | like: { type: Number, default: 0 }, //“赞” 的数量 31 | dislike: { type: Number, default: 0 }, //"踩" 的数量 32 | top: { type: Number, default: 0 }, //置顶, 0: 不置顶,>0: 置顶(值为置顶权重) 33 | read: { type: Number, default: 0 }, //阅读数量 34 | replay: { type: Number, default: 0 }, //回复数量 35 | good: { type: Boolean, default: false }, //是否精华 36 | status: { type: Number, default: status.topic.DRAFT }// 状态, 37 | }); 38 | 39 | //定义评论模型 40 | var Comment = self.Comment = db.model('Comment', { 41 | topic: { type: db.types.ObjectId, ref: Topic.schema.name, required: true }, //所属话题 42 | content: { type: String, default: '', required: true }, //内容 43 | html: { type: String, default: '' }, //html内容 44 | author: { type: db.types.ObjectId, ref: User.schema.name, required: true }, //作者 45 | createAt: { type: Date, default: Date.now() }, //创建时间 46 | updateAt: { type: Date, default: Date.now() }, //更新时间 47 | like: { type: Number, default: 0 }, //“赞” 的数量 48 | dislike: { type: Number, default: 0 }, //"踩" 的数量 49 | status: { type: Number, default: status.comment.PUBLISH }// 状态, 50 | }); 51 | 52 | //定义消息模型 53 | var Message = self.Message = db.model("Message", { 54 | from: { type: String }, //发送者 55 | to: { type: String }, //接收者 56 | type: { type: String },//类型 57 | content: { type: String },//内容 58 | link: { type: String },//链接 59 | sendAt: { type: Date, default: Date.now() }, //发送时间 60 | status: { type: Number, default: status.message.UNREAD }// 状态, 61 | }); 62 | 63 | //定义权限配置模型 64 | var Access = self.Access = db.model("Access", { 65 | role: { type: String }, //角色名称 66 | users: { type: String }, //用户id列表(逗号隔开) 67 | }); 68 | -------------------------------------------------------------------------------- /models/mail.js: -------------------------------------------------------------------------------- 1 | /* global nokit */ 2 | var utils = require("../common/utils"); 3 | var mail = require("../common/mail"); 4 | var tp = nokit.tp; 5 | var fs = require("fs"); 6 | 7 | var self = module.exports; 8 | var tmpls = null; 9 | 10 | /** 11 | * 编辑邮件模板 12 | **/ 13 | self.compileTmpls = function () { 14 | if (!tmpls) { 15 | var mailConfigs = utils.configs.mail; 16 | tmpls = {}; 17 | //reg 18 | tmpls.reg_subject = tp.compile(mailConfigs.reg_tmpl.subject); 19 | var regBodyPath = utils.server.resolvePath(mailConfigs.reg_tmpl.body); 20 | var regBody = fs.readFileSync(regBodyPath).toString(); 21 | tmpls.reg_body = tp.compile(regBody); 22 | //pwd 23 | tmpls.pwd_subject = tp.compile(mailConfigs.pwd_tmpl.subject); 24 | var pwdBodyPath = utils.server.resolvePath(mailConfigs.pwd_tmpl.body); 25 | var pwdBody = fs.readFileSync(pwdBodyPath).toString(); 26 | tmpls.pwd_body = tp.compile(pwdBody); 27 | } 28 | }; 29 | 30 | /** 31 | * 发送注册验证邮件 32 | **/ 33 | self.sendForReg = function (user, callback) { 34 | self.compileTmpls(); 35 | var options = {}; 36 | options.to = user.email; //收件人 37 | options.subject = tmpls.reg_subject(); //主题 38 | options.html = tmpls.reg_body({ 39 | "user": user, 40 | "configs": utils.configs 41 | }); 42 | mail.send(options, callback); 43 | }; 44 | 45 | /** 46 | * 发送密码找回邮件 47 | **/ 48 | self.sendForPwd = function (options, callback) { 49 | 50 | }; -------------------------------------------------------------------------------- /models/message.js: -------------------------------------------------------------------------------- 1 | /* global nokit */ 2 | var define = require('./define'); 3 | var status = require("./status").message; 4 | var utils = require("../common/utils"); 5 | var Task = nokit.Task; 6 | var User = require("./user"); 7 | 8 | /** 9 | * 定义站内消息模型 10 | **/ 11 | var Message = define.Message; 12 | 13 | /** 14 | * 获取所有消息 15 | **/ 16 | Message.getAllByUserId = function (userId, callback) { 17 | var self = this; 18 | self.find({ 19 | "to": userId, 20 | "status": {"$ne":status.DELETED} 21 | }) 22 | .sort({ "status":1, "_id": -1 }) 23 | .exec(callback); 24 | }; 25 | 26 | /** 27 | * 获取所有已读消息 28 | **/ 29 | Message.getReadByUserId = function (userId, callback) { 30 | var self = this; 31 | self.find({ 32 | "to": userId, 33 | "status": status.READ}) 34 | .sort({ '_id': -1 }) 35 | .exec(callback); 36 | }; 37 | 38 | /** 39 | * 获取所有未读消息 40 | **/ 41 | Message.getUnreadByUserId = function (userId, callback) { 42 | var self = this; 43 | self.find({ 44 | "to":userId, 45 | "status": status.UNREAD}) 46 | .sort({ '_id': -1 }) 47 | .exec(callback); 48 | }; 49 | 50 | /** 51 | * 全部标记为已读 52 | **/ 53 | Message.markAllAsReadByUserId = function (userId, callback) { 54 | var self = this; 55 | self.update({ 56 | "to":userId, 57 | "status":{"$ne":status.DELETED} 58 | }, 59 | { $set: { "status":status.READ }}, 60 | callback); 61 | }; 62 | 63 | /** 64 | * 标记为已读 65 | **/ 66 | Message.markAsReadById = function (id, callback) { 67 | var self = this; 68 | self.findById(id,function(err,msg){ 69 | if(err){ 70 | return callback(err); 71 | } 72 | if(msg.status!=status.DELETED){ 73 | msg.status=status.READ; 74 | } 75 | msg.save(); 76 | callback(null,msg); 77 | }); 78 | }; 79 | 80 | /** 81 | * 删除 82 | **/ 83 | Message.deleteAllByUserId = function (userId, callback) { 84 | var self = this; 85 | self.remove({ 86 | "to":userId},callback); 87 | }; 88 | 89 | Message.deleteById=function(id,callback){ 90 | var self = this; 91 | self.remove({ 92 | "_id":id.toString() },callback); 93 | }; 94 | 95 | /** 96 | * 发送消息 97 | **/ 98 | Message.send=function(from,toList,msgInfo,callback){ 99 | if(!utils.isArray(toList)){ 100 | toList=[toList]; 101 | } 102 | var task = new Task(); 103 | User.getUsersByNames(toList,function(err,users){ 104 | users.forEach(function(user){ 105 | task.add(function(done){ 106 | if(err || !user)return; 107 | var msg = new Message(); 108 | msg.to=user._id.toString(); 109 | msg.from = from; 110 | msg.sendAt=new Date(); 111 | msg.status=status.UNREAD; 112 | msg.content = msgInfo.content; 113 | msg.link = msgInfo.link; 114 | msg.save(done); 115 | }); 116 | }); 117 | }); 118 | task.end(callback); 119 | }; 120 | 121 | module.exports = Message; -------------------------------------------------------------------------------- /models/role.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/models/role.js -------------------------------------------------------------------------------- /models/score.js: -------------------------------------------------------------------------------- 1 | var utils = require("../common/utils"); 2 | var self = module.exports; 3 | 4 | /** 5 | * 添加积分 6 | **/ 7 | self.add = function (userId, ruleName, callback) { 8 | var User = require("./user"); 9 | User.findById(userId, function (err, user) { 10 | if (err) { 11 | return callback(err); 12 | } 13 | user.score += utils.configs.scoreRules[ruleName]; 14 | user.save(callback); 15 | }); 16 | }; -------------------------------------------------------------------------------- /models/status.js: -------------------------------------------------------------------------------- 1 | //定议实体状态 2 | module.exports = { 3 | topic: { 4 | DELETED: -2, 5 | DRAFT: -1, 6 | NORMAL: 0, 7 | PUBLISH: 1, 8 | LOCK: 2 9 | }, 10 | comment: { 11 | DELETED: -2, 12 | DRAFT: -1, 13 | NORMAL: 0, 14 | PUBLISH: 1, 15 | LOCK: 2 16 | }, 17 | user: { 18 | DELETED: -1, 19 | NORMAL: 0, 20 | LOCK: 1 21 | }, 22 | message: { 23 | DELETED: -1, 24 | UNREAD: 0, 25 | READ: 1 26 | } 27 | }; -------------------------------------------------------------------------------- /models/topic.js: -------------------------------------------------------------------------------- 1 | /* global nokit */ 2 | var Task = nokit.Task; 3 | var define = require('./define'); 4 | var Comment = require('./comment'); 5 | var status = require('./status').topic; 6 | var score = require('./score'); 7 | var utils = require('../common/utils'); 8 | 9 | //定义话题模型 10 | var Topic = module.exports = define.Topic; 11 | 12 | //新建一个 topic 13 | Topic.new = function (author, callback) { 14 | var self = Topic; 15 | self.findOne({ 16 | "status": status.DRAFT, 17 | "author": author._id 18 | }, function (err, foundTopic) { 19 | if (err) { 20 | return callback(err); 21 | } 22 | if (foundTopic) { 23 | return callback(null, foundTopic); 24 | } 25 | var topic = new Topic(); 26 | topic.title = ""; 27 | topic.content = ""; 28 | topic.author = author._id; 29 | topic.status = status.DRAFT; 30 | topic.datetime = new Date(); 31 | topic.save(callback); 32 | score.add(author._id, "topic-add"); 33 | }); 34 | }; 35 | 36 | //保存一个话题 37 | Topic.save = function (topic, callback) { 38 | if (!topic.title || topic.title.length < 10) { 39 | return callback("话题标题不能少于 10 个字"); 40 | } 41 | if (!topic.content || topic.content.length < 1) { 42 | return callback("话题内容不能少于 1 个字"); 43 | } 44 | if (!topic.type || topic.type.length < 1) { 45 | return callback("请选择话题类型"); 46 | } 47 | topic.html = utils.md2html(topic.content); 48 | topic.createAt = topic.createAt || new Date(); 49 | topic.updateAt = new Date(); 50 | topic.lastReplayAt = topic.lastReplayAt || new Date(); 51 | topic.save(callback); 52 | }; 53 | 54 | //获取一个 topic 55 | Topic.get = function (id, callback) { 56 | var self = Topic; 57 | var task = new Task(); 58 | task.add('topic', function (done) { 59 | self.findById(id) 60 | .populate('author') 61 | .populate('lastReplayAuthor') 62 | .exec(done); 63 | }); 64 | task.add('comments', function (done) { 65 | Comment.getListByTopicId(id, done); 66 | }); 67 | task.end(function (err, rs) { 68 | if (err && err.name == "CastError") { 69 | return callback(); 70 | } else if (err) { 71 | return callback(err); 72 | } 73 | var topic = rs.topic; 74 | topic.comments = rs.comments; 75 | callback(null, topic); 76 | }); 77 | }; 78 | 79 | //处理查询条件 80 | Topic._handleConditions = function (conditions) { 81 | conditions = conditions || {}; 82 | conditions.status = conditions.status || status.PUBLISH; 83 | if (!conditions.type || conditions.type == 'all') { 84 | delete conditions.type; 85 | } 86 | return conditions; 87 | }; 88 | 89 | //加载所有话题 90 | Topic.getList = function (options, callback) { 91 | var self = Topic; 92 | options = options || {}; 93 | options.conditions = self._handleConditions(options.conditions); 94 | self.find(options.conditions) 95 | .sort({ 'top': -1, 'lastReplayAt': -1, '_id': -1 }) 96 | .skip(options.pageSize * (options.pageIndex - 1)) 97 | .limit(options.pageSize) 98 | .populate('author') 99 | .populate('lastReplayAuthor') 100 | .exec(callback); 101 | }; 102 | 103 | /** 104 | * 获取指定条件的记录数量 105 | **/ 106 | Topic.getCount = function (conditions, callback) { 107 | var self = Topic; 108 | conditions = self._handleConditions(conditions); 109 | self.count(conditions, callback); 110 | }; 111 | 112 | /** 113 | * 删除一个话题 114 | **/ 115 | Topic.delete = function (id, callback) { 116 | var self = this; 117 | self.findById(id, function (err, item) { 118 | if (err || !item) { 119 | return callback(err); 120 | } 121 | item.status = status.DELETED; 122 | item.save(callback); 123 | Comment.deleteByTopicId(id);//删除对应的评论 124 | score.add(item.author, "topic-del"); 125 | }); 126 | 127 | }; 128 | 129 | /** 130 | * 设定为精华 131 | **/ 132 | Topic.setGood = function (id, callback) { 133 | var self = this; 134 | self.findById(id, function (err, item) { 135 | if (err || !item) { 136 | return callback(err); 137 | } 138 | item.good = true; 139 | item.save(callback); 140 | score.add(item.author, "good-add"); 141 | }); 142 | }; 143 | 144 | /** 145 | * 移除精华 146 | **/ 147 | Topic.removeGood = function (id, callback) { 148 | var self = this; 149 | self.findById(id, function (err, item) { 150 | if (err || !item) { 151 | return callback(err); 152 | } 153 | item.good = false; 154 | item.save(callback); 155 | score.add(item.author, "good-del"); 156 | }); 157 | }; 158 | 159 | /** 160 | * 设定为精华 161 | **/ 162 | Topic.setTop = function (id, callback) { 163 | var self = this; 164 | self.findById(id, function (err, item) { 165 | if (err || !item) { 166 | return callback(err); 167 | } 168 | item.top = 1; 169 | item.save(callback); 170 | score.add(item.author, "top-add"); 171 | }); 172 | }; 173 | 174 | /** 175 | * 移除精华 176 | **/ 177 | Topic.removeTop = function (id, callback) { 178 | var self = this; 179 | self.findById(id, function (err, item) { 180 | if (err || !item) { 181 | return callback(err); 182 | } 183 | item.top = 0; 184 | item.save(callback); 185 | score.add(item.author, "top-del"); 186 | }); 187 | }; 188 | 189 | /** 190 | * 获取指定用户的最近话题 191 | **/ 192 | Topic.getLastByUserId = function (userId, callback) { 193 | var self = Topic; 194 | self.find({ 195 | author: userId, 196 | status: status.PUBLISH 197 | }).sort({ 'top': -1, '_id': -1 }) 198 | .skip(0) 199 | .limit(10) 200 | .populate('author') 201 | .populate('lastReplayAuthor') 202 | .exec(callback); 203 | }; 204 | 205 | /** 206 | * 搜索匹配的话题 207 | **/ 208 | Topic.search = function (keyword, callback) { 209 | var self = this; 210 | self.find({ 211 | "title": { $regex: keyword, $options: 'i' }, 212 | "status": status.PUBLISH 213 | }).sort({ '_id': 1 }) 214 | .limit(15) 215 | .populate('author') 216 | .populate('lastReplayAuthor') 217 | .exec(callback); 218 | }; 219 | 220 | /** 221 | * 获取草稿列表 222 | **/ 223 | Topic.getDraftList = function (userId, callback) { 224 | var self = Topic; 225 | self.find({ 226 | status: status.DRAFT, 227 | author: userId.toString() 228 | }).sort({ 'top': -1, '_id': -1 }) 229 | .populate('author') 230 | .populate('lastReplayAuthor') 231 | .exec(callback); 232 | }; -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | var utils = require("../common/utils"); 2 | var mail = require("./mail"); 3 | var define = require("./define"); 4 | var qn = require("../common/qn"); 5 | var fs = require("fs"); 6 | 7 | /** 8 | * 定义用户模型 9 | **/ 10 | var User = define.User; 11 | 12 | /** 13 | * 创建一个新用户 14 | **/ 15 | User.create = function () { 16 | return new User(); 17 | }; 18 | 19 | /** 20 | * 加载按积分降序排序的 n 个用户 21 | **/ 22 | User.getList = function (top, callback) { 23 | var self = User; 24 | self.find({ 25 | "verifyCode": "" 26 | }).sort({ 27 | 'score': -1, 28 | '_id': 1 29 | }).limit(top).exec(callback); 30 | }; 31 | 32 | /** 33 | * 登录一个用户 34 | **/ 35 | User.signIn = function (user, callback) { 36 | var self = User; 37 | if (!user.email || !user.password) { 38 | return callback("账号或者密码错误"); 39 | } 40 | self.findOne({ 41 | "email": new RegExp(user.email, "igm"), 42 | "password": utils.hashDigest(user.password) 43 | }, function (err, foundUser) { 44 | if (err) { 45 | return callback(err, user); 46 | } 47 | if (foundUser && foundUser.verifyCode) { 48 | return callback("该账号的邮箱还未验证,请查收邮件完成验证,未验证的邮箱也可以被重新注册", user); 49 | } else if (foundUser) { 50 | return callback(null, foundUser); 51 | } else { 52 | return callback('账号或者密码错误', user); 53 | } 54 | }); 55 | }; 56 | 57 | /** 58 | * 通过 oauth 认证一个用户 59 | **/ 60 | User.oAuth = function (user, callback) { 61 | var self = User; 62 | if (!user || !user.email) { 63 | return callback('oAuth 发生了异常,没有可用 email'); 64 | } 65 | self.findOne({ 66 | "email": user.email 67 | }, function (err, foundUser) { 68 | if (err) { 69 | return callback(err); 70 | } 71 | if (foundUser) { 72 | foundUser.verifyCode = ""; 73 | foundUser.save(function (err) { 74 | if (err) { 75 | return callback(err); 76 | } 77 | return callback(null, foundUser); 78 | }); 79 | } else { 80 | user.avatar = user.avatar || self.getRandomAvatar(); 81 | user.save(function (err) { 82 | if (err) { 83 | return callback(err); 84 | } 85 | return callback(null, user); 86 | }); 87 | } 88 | }); 89 | }; 90 | 91 | /** 92 | * 根据一个字段检查是否存在用户 93 | **/ 94 | User.existsByField = function (field, value, callback) { 95 | var self = this; 96 | var options = {}; 97 | options[field] = new RegExp(value, "igm"); 98 | self.findOne(options, function (err, foundUser) { 99 | if (err) { 100 | return callback(err); 101 | } 102 | return callback(null, foundUser); 103 | }); 104 | }; 105 | 106 | /** 107 | * 根据 email 清除未验证的用户 108 | **/ 109 | User.clearNotVerifiedByEmail = function (email, callback) { 110 | var self = this; 111 | self.remove({ 112 | "email": email, 113 | "verifyCode": { $ne: "" } 114 | }, callback); 115 | }; 116 | 117 | /** 118 | * 检查 email 或 name 是否存在 119 | **/ 120 | User.checkEmailOrName = function (user, callback) { 121 | var self = this; 122 | self.clearNotVerifiedByEmail(user.email, function () { 123 | self.existsByField("email", user.email, function (err, existsUser) { 124 | if (err) { 125 | return callback(err); 126 | } 127 | if (existsUser) { 128 | return callback('邮箱 "' + user.email + '" 已经被使用'); 129 | } 130 | self.existsByField("name", user.name, function (err, existsUser) { 131 | if (err) { 132 | return callback(err); 133 | } 134 | if (existsUser) { 135 | return callback('名字 "' + user.name + '" 已经被使用'); 136 | } else { 137 | return callback(); 138 | } 139 | }); 140 | }); 141 | }); 142 | }; 143 | 144 | /** 145 | * 注册一个用户 146 | **/ 147 | User.signUp = function (user, callback) { 148 | var self = this; 149 | user.avatar = user.avatar || self.getRandomAvatar(); 150 | if (!user.email || user.email.indexOf('@') < 0) { 151 | return callback("请填写正确的邮箱"); 152 | } 153 | if (!user.name || user.name.length < 2) { 154 | return callback("名字最少需要两个字符"); 155 | } 156 | if (!user.password || user.password.length < 6) { 157 | return callback('密码最少需要六个字符'); 158 | } 159 | self.checkEmailOrName(user, function (err) { 160 | if (err) { 161 | return callback(err); 162 | } 163 | user.password = utils.hashDigest(user.password); 164 | user.verifyCode = utils.newGuid(); 165 | user.save(function (err) { 166 | if (err) { 167 | return callback(err); 168 | } 169 | mail.sendForReg(user, function (err) { 170 | if (err) { 171 | return callback(err); 172 | } 173 | return callback(null, user); 174 | }); 175 | }); 176 | }); 177 | }; 178 | 179 | /** 180 | * 随机生成一个用户头像 URL 181 | **/ 182 | User.getRandomAvatar = function () { 183 | var index = utils.random(1, 12); 184 | return "/images/avatar/" + index + ".png"; 185 | }; 186 | 187 | /** 188 | * 获取一个用户 189 | **/ 190 | User.getUser = function (id, callback) { 191 | var self = this; 192 | if (!id.match(/^[0-9a-fA-F]{24}$/)) { 193 | return callback(); 194 | } 195 | self.findById(id, callback); 196 | }; 197 | 198 | /** 199 | * 通过名字列表获取一组数户 200 | **/ 201 | User.getUsersByNames = function (names, callback) { 202 | if (names.length === 0) { 203 | return callback(null, []); 204 | } 205 | User.find({ name: { $in: names } }, callback); 206 | }; 207 | 208 | /** 209 | * 通过名称获取用户 210 | **/ 211 | User.getUserByName = function (name, callback) { 212 | var self = this; 213 | self.findOne({ "name": new RegExp(name, "igm") }, callback); 214 | }; 215 | 216 | /** 217 | * 搜索匹配的人员 218 | **/ 219 | User.search = function (keyword, callback) { 220 | var self = this; 221 | self.find({ 222 | "verifyCode": "", 223 | "name": { 224 | $regex: keyword, 225 | $options: 'i' 226 | } 227 | }).sort({ 228 | 'score': -1, 229 | '_id': 1 230 | }).limit(10).exec(callback); 231 | }; 232 | 233 | /** 234 | * 验证邮箱 235 | **/ 236 | User.verifyMail = function (verifyCode, callback) { 237 | var self = this; 238 | self.findOne({ 239 | "verifyCode": verifyCode 240 | }, function (err, foundUser) { 241 | if (err) { 242 | return callback(err); 243 | } 244 | if (foundUser) { 245 | foundUser.verifyCode = ''; 246 | foundUser.save(function (err) { 247 | if (err) { 248 | return callback(err); 249 | } 250 | return callback(null, foundUser); 251 | }); 252 | } else { 253 | return callback(null, null); 254 | } 255 | }); 256 | }; 257 | 258 | /** 259 | * 设置用户的密码 260 | **/ 261 | User.setPassword = function (opts, callback) { 262 | var self = this; 263 | if (!opts.password || opts.password.length < 6) { 264 | return callback('密码最少需要六个字符'); 265 | } 266 | self.updateUser(opts.id, { password: utils.hashDigest(opts.password) }, callback); 267 | }; 268 | 269 | /** 270 | * 根据 URL 获取头像文件名 271 | **/ 272 | User._getAvatarFileName = function (avatarUrl) { 273 | avatarUrl = avatarUrl || ""; 274 | return avatarUrl.split('?')[0].split('/').pop(); 275 | }; 276 | 277 | /** 278 | * 上传用户头像到 QiQiu 279 | **/ 280 | User._uploadAvatar = function (baseInfo, callback) { 281 | var self = this; 282 | var newFileKey = "avatar-" + baseInfo.id + "-" + Date.now(); 283 | qn.client.uploadImage(baseInfo.avatar, newFileKey, function (err, result) { 284 | if (err) { 285 | return callback(err); 286 | } 287 | baseInfo.avatar = qn.client.imageView(newFileKey, { 288 | mode: 1, 289 | width: 160, 290 | height: 160, 291 | q: 50, 292 | format: 'png' 293 | }); 294 | var oldFileKey = self._getAvatarFileName(baseInfo.oldAvatar); 295 | qn.client.delete(oldFileKey, function (err) { 296 | callback(null, baseInfo); 297 | }); 298 | }); 299 | }; 300 | 301 | /** 302 | * 根据 ID 更新用户的某几个字段 303 | **/ 304 | User.updateUser = function (id, obj, callback) { 305 | var self = this; 306 | self.update({ "_id": id }, { $set: obj }, callback); 307 | }; 308 | 309 | /** 310 | * 保存用户基本信息 311 | **/ 312 | User.saveBaseInfo = function (baseInfo, callback) { 313 | var self = this; 314 | if (!baseInfo.name || baseInfo.name.length < 2) { 315 | return callback("名字最少需要两个字符"); 316 | } 317 | self.existsByField("name", baseInfo.name, function (err, existsUser) { 318 | if (err) { 319 | return callback(err); 320 | } 321 | if (existsUser && 322 | existsUser._id.toString() != baseInfo.id.toString()) { 323 | return callback('名字 "' + baseInfo.name + '" 已经被使用'); 324 | } 325 | if (!baseInfo.avatar) { 326 | return self.updateUser(baseInfo.id, { 327 | name: baseInfo.name 328 | }, function (err) { 329 | callback(err, baseInfo); 330 | }); 331 | } 332 | self._uploadAvatar(baseInfo, function (err, info) { 333 | self.updateUser(info.id, { 334 | name: info.name, 335 | avatar: info.avatar 336 | }, function (err) { 337 | callback(err, info); 338 | }); 339 | }); 340 | }); 341 | }; 342 | 343 | module.exports = User; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jser.cc", 3 | "rawName": "jser", 4 | "version": "0.0.1", 5 | "description": "一个纯粹而简的社区软件", 6 | "main": "./app.js", 7 | "keywords": [ 8 | "jser" 9 | ], 10 | "author": { 11 | "name": "jser", 12 | "email": "master@jser.cc" 13 | }, 14 | "homepage": "http://jser.cc", 15 | "license": "MIT", 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/jser-dev/jser.git" 19 | }, 20 | "dependencies": { 21 | "mditor": "^0.1.4", 22 | "mongoose": "^4.4.6", 23 | "nodemailer": "^1.11.0", 24 | "nokit-handler-less": "^1.0.1", 25 | "nokit-pjax": "^0.1.1", 26 | "nokit-session-redis": "^1.0.6", 27 | "nokitjs": "^1.22.1", 28 | "qn": "^1.1.1", 29 | "request": "^2.69.0", 30 | "timeago-words": "^0.0.3" 31 | }, 32 | "devDependencies": { 33 | "jshint": "^2.8.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/ad/google/ads.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /public/ad/qcloud/300x100.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/ad/qcloud/300x100.jpg -------------------------------------------------------------------------------- /public/bootstrap/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | .btn-default, 7 | .btn-primary, 8 | .btn-success, 9 | .btn-info, 10 | .btn-warning, 11 | .btn-danger { 12 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); 13 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 14 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); 15 | } 16 | .btn-default:active, 17 | .btn-primary:active, 18 | .btn-success:active, 19 | .btn-info:active, 20 | .btn-warning:active, 21 | .btn-danger:active, 22 | .btn-default.active, 23 | .btn-primary.active, 24 | .btn-success.active, 25 | .btn-info.active, 26 | .btn-warning.active, 27 | .btn-danger.active { 28 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 29 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); 30 | } 31 | .btn-default.disabled, 32 | .btn-primary.disabled, 33 | .btn-success.disabled, 34 | .btn-info.disabled, 35 | .btn-warning.disabled, 36 | .btn-danger.disabled, 37 | .btn-default[disabled], 38 | .btn-primary[disabled], 39 | .btn-success[disabled], 40 | .btn-info[disabled], 41 | .btn-warning[disabled], 42 | .btn-danger[disabled], 43 | fieldset[disabled] .btn-default, 44 | fieldset[disabled] .btn-primary, 45 | fieldset[disabled] .btn-success, 46 | fieldset[disabled] .btn-info, 47 | fieldset[disabled] .btn-warning, 48 | fieldset[disabled] .btn-danger { 49 | -webkit-box-shadow: none; 50 | box-shadow: none; 51 | } 52 | .btn-default .badge, 53 | .btn-primary .badge, 54 | .btn-success .badge, 55 | .btn-info .badge, 56 | .btn-warning .badge, 57 | .btn-danger .badge { 58 | text-shadow: none; 59 | } 60 | .btn:active, 61 | .btn.active { 62 | background-image: none; 63 | } 64 | .btn-default { 65 | text-shadow: 0 1px 0 #fff; 66 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); 67 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); 68 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); 69 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); 70 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 71 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 72 | background-repeat: repeat-x; 73 | border-color: #dbdbdb; 74 | border-color: #ccc; 75 | } 76 | .btn-default:hover, 77 | .btn-default:focus { 78 | background-color: #e0e0e0; 79 | background-position: 0 -15px; 80 | } 81 | .btn-default:active, 82 | .btn-default.active { 83 | background-color: #e0e0e0; 84 | border-color: #dbdbdb; 85 | } 86 | .btn-default.disabled, 87 | .btn-default[disabled], 88 | fieldset[disabled] .btn-default, 89 | .btn-default.disabled:hover, 90 | .btn-default[disabled]:hover, 91 | fieldset[disabled] .btn-default:hover, 92 | .btn-default.disabled:focus, 93 | .btn-default[disabled]:focus, 94 | fieldset[disabled] .btn-default:focus, 95 | .btn-default.disabled.focus, 96 | .btn-default[disabled].focus, 97 | fieldset[disabled] .btn-default.focus, 98 | .btn-default.disabled:active, 99 | .btn-default[disabled]:active, 100 | fieldset[disabled] .btn-default:active, 101 | .btn-default.disabled.active, 102 | .btn-default[disabled].active, 103 | fieldset[disabled] .btn-default.active { 104 | background-color: #e0e0e0; 105 | background-image: none; 106 | } 107 | .btn-primary { 108 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); 109 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); 110 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); 111 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); 112 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); 113 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 114 | background-repeat: repeat-x; 115 | border-color: #245580; 116 | } 117 | .btn-primary:hover, 118 | .btn-primary:focus { 119 | background-color: #265a88; 120 | background-position: 0 -15px; 121 | } 122 | .btn-primary:active, 123 | .btn-primary.active { 124 | background-color: #265a88; 125 | border-color: #245580; 126 | } 127 | .btn-primary.disabled, 128 | .btn-primary[disabled], 129 | fieldset[disabled] .btn-primary, 130 | .btn-primary.disabled:hover, 131 | .btn-primary[disabled]:hover, 132 | fieldset[disabled] .btn-primary:hover, 133 | .btn-primary.disabled:focus, 134 | .btn-primary[disabled]:focus, 135 | fieldset[disabled] .btn-primary:focus, 136 | .btn-primary.disabled.focus, 137 | .btn-primary[disabled].focus, 138 | fieldset[disabled] .btn-primary.focus, 139 | .btn-primary.disabled:active, 140 | .btn-primary[disabled]:active, 141 | fieldset[disabled] .btn-primary:active, 142 | .btn-primary.disabled.active, 143 | .btn-primary[disabled].active, 144 | fieldset[disabled] .btn-primary.active { 145 | background-color: #265a88; 146 | background-image: none; 147 | } 148 | .btn-success { 149 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 150 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); 151 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); 152 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 153 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 154 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 155 | background-repeat: repeat-x; 156 | border-color: #3e8f3e; 157 | } 158 | .btn-success:hover, 159 | .btn-success:focus { 160 | background-color: #419641; 161 | background-position: 0 -15px; 162 | } 163 | .btn-success:active, 164 | .btn-success.active { 165 | background-color: #419641; 166 | border-color: #3e8f3e; 167 | } 168 | .btn-success.disabled, 169 | .btn-success[disabled], 170 | fieldset[disabled] .btn-success, 171 | .btn-success.disabled:hover, 172 | .btn-success[disabled]:hover, 173 | fieldset[disabled] .btn-success:hover, 174 | .btn-success.disabled:focus, 175 | .btn-success[disabled]:focus, 176 | fieldset[disabled] .btn-success:focus, 177 | .btn-success.disabled.focus, 178 | .btn-success[disabled].focus, 179 | fieldset[disabled] .btn-success.focus, 180 | .btn-success.disabled:active, 181 | .btn-success[disabled]:active, 182 | fieldset[disabled] .btn-success:active, 183 | .btn-success.disabled.active, 184 | .btn-success[disabled].active, 185 | fieldset[disabled] .btn-success.active { 186 | background-color: #419641; 187 | background-image: none; 188 | } 189 | .btn-info { 190 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 191 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 192 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); 193 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 194 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 195 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 196 | background-repeat: repeat-x; 197 | border-color: #28a4c9; 198 | } 199 | .btn-info:hover, 200 | .btn-info:focus { 201 | background-color: #2aabd2; 202 | background-position: 0 -15px; 203 | } 204 | .btn-info:active, 205 | .btn-info.active { 206 | background-color: #2aabd2; 207 | border-color: #28a4c9; 208 | } 209 | .btn-info.disabled, 210 | .btn-info[disabled], 211 | fieldset[disabled] .btn-info, 212 | .btn-info.disabled:hover, 213 | .btn-info[disabled]:hover, 214 | fieldset[disabled] .btn-info:hover, 215 | .btn-info.disabled:focus, 216 | .btn-info[disabled]:focus, 217 | fieldset[disabled] .btn-info:focus, 218 | .btn-info.disabled.focus, 219 | .btn-info[disabled].focus, 220 | fieldset[disabled] .btn-info.focus, 221 | .btn-info.disabled:active, 222 | .btn-info[disabled]:active, 223 | fieldset[disabled] .btn-info:active, 224 | .btn-info.disabled.active, 225 | .btn-info[disabled].active, 226 | fieldset[disabled] .btn-info.active { 227 | background-color: #2aabd2; 228 | background-image: none; 229 | } 230 | .btn-warning { 231 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 232 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 233 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); 234 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 235 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 236 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 237 | background-repeat: repeat-x; 238 | border-color: #e38d13; 239 | } 240 | .btn-warning:hover, 241 | .btn-warning:focus { 242 | background-color: #eb9316; 243 | background-position: 0 -15px; 244 | } 245 | .btn-warning:active, 246 | .btn-warning.active { 247 | background-color: #eb9316; 248 | border-color: #e38d13; 249 | } 250 | .btn-warning.disabled, 251 | .btn-warning[disabled], 252 | fieldset[disabled] .btn-warning, 253 | .btn-warning.disabled:hover, 254 | .btn-warning[disabled]:hover, 255 | fieldset[disabled] .btn-warning:hover, 256 | .btn-warning.disabled:focus, 257 | .btn-warning[disabled]:focus, 258 | fieldset[disabled] .btn-warning:focus, 259 | .btn-warning.disabled.focus, 260 | .btn-warning[disabled].focus, 261 | fieldset[disabled] .btn-warning.focus, 262 | .btn-warning.disabled:active, 263 | .btn-warning[disabled]:active, 264 | fieldset[disabled] .btn-warning:active, 265 | .btn-warning.disabled.active, 266 | .btn-warning[disabled].active, 267 | fieldset[disabled] .btn-warning.active { 268 | background-color: #eb9316; 269 | background-image: none; 270 | } 271 | .btn-danger { 272 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 273 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 274 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); 275 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 276 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 277 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 278 | background-repeat: repeat-x; 279 | border-color: #b92c28; 280 | } 281 | .btn-danger:hover, 282 | .btn-danger:focus { 283 | background-color: #c12e2a; 284 | background-position: 0 -15px; 285 | } 286 | .btn-danger:active, 287 | .btn-danger.active { 288 | background-color: #c12e2a; 289 | border-color: #b92c28; 290 | } 291 | .btn-danger.disabled, 292 | .btn-danger[disabled], 293 | fieldset[disabled] .btn-danger, 294 | .btn-danger.disabled:hover, 295 | .btn-danger[disabled]:hover, 296 | fieldset[disabled] .btn-danger:hover, 297 | .btn-danger.disabled:focus, 298 | .btn-danger[disabled]:focus, 299 | fieldset[disabled] .btn-danger:focus, 300 | .btn-danger.disabled.focus, 301 | .btn-danger[disabled].focus, 302 | fieldset[disabled] .btn-danger.focus, 303 | .btn-danger.disabled:active, 304 | .btn-danger[disabled]:active, 305 | fieldset[disabled] .btn-danger:active, 306 | .btn-danger.disabled.active, 307 | .btn-danger[disabled].active, 308 | fieldset[disabled] .btn-danger.active { 309 | background-color: #c12e2a; 310 | background-image: none; 311 | } 312 | .thumbnail, 313 | .img-thumbnail { 314 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 315 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 316 | } 317 | .dropdown-menu > li > a:hover, 318 | .dropdown-menu > li > a:focus { 319 | background-color: #e8e8e8; 320 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 321 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 322 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 323 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 324 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 325 | background-repeat: repeat-x; 326 | } 327 | .dropdown-menu > .active > a, 328 | .dropdown-menu > .active > a:hover, 329 | .dropdown-menu > .active > a:focus { 330 | background-color: #2e6da4; 331 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 332 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 333 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 334 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 336 | background-repeat: repeat-x; 337 | } 338 | .navbar-default { 339 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); 340 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); 341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); 342 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); 343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 344 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 345 | background-repeat: repeat-x; 346 | border-radius: 4px; 347 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 348 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); 349 | } 350 | .navbar-default .navbar-nav > .open > a, 351 | .navbar-default .navbar-nav > .active > a { 352 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 353 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); 354 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); 355 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); 356 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); 357 | background-repeat: repeat-x; 358 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 359 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); 360 | } 361 | .navbar-brand, 362 | .navbar-nav > li > a { 363 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25); 364 | } 365 | .navbar-inverse { 366 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); 367 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); 368 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); 369 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); 370 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 371 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 372 | background-repeat: repeat-x; 373 | border-radius: 4px; 374 | } 375 | .navbar-inverse .navbar-nav > .open > a, 376 | .navbar-inverse .navbar-nav > .active > a { 377 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); 378 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); 379 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); 380 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); 381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); 382 | background-repeat: repeat-x; 383 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 384 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); 385 | } 386 | .navbar-inverse .navbar-brand, 387 | .navbar-inverse .navbar-nav > li > a { 388 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); 389 | } 390 | .navbar-static-top, 391 | .navbar-fixed-top, 392 | .navbar-fixed-bottom { 393 | border-radius: 0; 394 | } 395 | @media (max-width: 767px) { 396 | .navbar .navbar-nav .open .dropdown-menu > .active > a, 397 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, 398 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { 399 | color: #fff; 400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 405 | background-repeat: repeat-x; 406 | } 407 | } 408 | .alert { 409 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2); 410 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 411 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); 412 | } 413 | .alert-success { 414 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 415 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 416 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); 417 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 418 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 419 | background-repeat: repeat-x; 420 | border-color: #b2dba1; 421 | } 422 | .alert-info { 423 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 424 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 425 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); 426 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 427 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 428 | background-repeat: repeat-x; 429 | border-color: #9acfea; 430 | } 431 | .alert-warning { 432 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 433 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 434 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); 435 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 437 | background-repeat: repeat-x; 438 | border-color: #f5e79e; 439 | } 440 | .alert-danger { 441 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 442 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 443 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); 444 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 445 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 446 | background-repeat: repeat-x; 447 | border-color: #dca7a7; 448 | } 449 | .progress { 450 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 451 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); 453 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 455 | background-repeat: repeat-x; 456 | } 457 | .progress-bar { 458 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); 459 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); 460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); 461 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); 462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); 463 | background-repeat: repeat-x; 464 | } 465 | .progress-bar-success { 466 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 467 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); 468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); 469 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 471 | background-repeat: repeat-x; 472 | } 473 | .progress-bar-info { 474 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 475 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 476 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); 477 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 478 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 479 | background-repeat: repeat-x; 480 | } 481 | .progress-bar-warning { 482 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 483 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 484 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); 485 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 486 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 487 | background-repeat: repeat-x; 488 | } 489 | .progress-bar-danger { 490 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 491 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); 492 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); 493 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 494 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 495 | background-repeat: repeat-x; 496 | } 497 | .progress-bar-striped { 498 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 499 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 500 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); 501 | } 502 | .list-group { 503 | border-radius: 4px; 504 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 505 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075); 506 | } 507 | .list-group-item.active, 508 | .list-group-item.active:hover, 509 | .list-group-item.active:focus { 510 | text-shadow: 0 -1px 0 #286090; 511 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); 512 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); 513 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); 514 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); 515 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); 516 | background-repeat: repeat-x; 517 | border-color: #2b669a; 518 | } 519 | .list-group-item.active .badge, 520 | .list-group-item.active:hover .badge, 521 | .list-group-item.active:focus .badge { 522 | text-shadow: none; 523 | } 524 | .panel { 525 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 526 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05); 527 | } 528 | .panel-default > .panel-heading { 529 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 530 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 531 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); 532 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 533 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 534 | background-repeat: repeat-x; 535 | } 536 | .panel-primary > .panel-heading { 537 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 538 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); 539 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); 540 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); 541 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); 542 | background-repeat: repeat-x; 543 | } 544 | .panel-success > .panel-heading { 545 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 546 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 547 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); 548 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 549 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 550 | background-repeat: repeat-x; 551 | } 552 | .panel-info > .panel-heading { 553 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 554 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 555 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); 556 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 557 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 558 | background-repeat: repeat-x; 559 | } 560 | .panel-warning > .panel-heading { 561 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 562 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 563 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); 564 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 565 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 566 | background-repeat: repeat-x; 567 | } 568 | .panel-danger > .panel-heading { 569 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 570 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 571 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); 572 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 573 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 574 | background-repeat: repeat-x; 575 | } 576 | .well { 577 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 578 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 579 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); 580 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 581 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 582 | background-repeat: repeat-x; 583 | border-color: #dcdcdc; 584 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 585 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); 586 | } 587 | /*# sourceMappingURL=bootstrap-theme.css.map */ 588 | -------------------------------------------------------------------------------- /public/bootstrap/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.5 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /public/bootstrap/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /public/css/common.less: -------------------------------------------------------------------------------- 1 | /* common */ 2 | body { 3 | margin: 0px; 4 | padding: 0px; 5 | background-color: #e1e1e1; 6 | font-family: "STHeitiTC-Light","STHeiti", "Helvetica Neue","Microsoft Yahei"; 7 | } 8 | 9 | *{ 10 | -webkit-font-smoothing: antialiased; 11 | } 12 | 13 | .editor{ 14 | border: none; 15 | background-color: transparent; 16 | } 17 | 18 | /* 页头 */ 19 | .navbar-default{ 20 | background-image: none; 21 | background-color: #055db5; 22 | color: #eee; 23 | border-radius: 0px; 24 | border: none; 25 | font-size: 16px; 26 | } 27 | 28 | .navbar-default .navbar-nav>li>a, 29 | .navbar-default .navbar-brand { 30 | color: #EEE; 31 | } 32 | 33 | .navbar-default .navbar-nav>li>a:focus, 34 | .navbar-default .navbar-nav>li>a:hover{ 35 | color: #fff; 36 | background-color: #0767c8; 37 | background-image: none; 38 | } 39 | 40 | .navbar-default .navbar-nav>.active>a, 41 | .navbar-default .navbar-nav>.open>a{ 42 | background-color: #0767c8; 43 | background-image: none; 44 | color: #fff; 45 | } 46 | 47 | .navbar-default .navbar-brand:focus, 48 | .navbar-default .navbar-brand:hover, 49 | .navbar-default .navbar-brand{ 50 | display: inline-block; 51 | width: 110px; 52 | height: 50px; 53 | /* background-color: #6e4e9e;*/ 54 | background-image: url('/images/logo.png'); 55 | background-repeat: no-repeat; 56 | background-position: center center; 57 | background-size: 100px 50px; 58 | } 59 | 60 | .navbar-default .navbar-collapse, .navbar-default .navbar-form{ 61 | border: none; 62 | } 63 | 64 | .navbar-avatar{ 65 | width:18px; 66 | height:18px; 67 | margin-right: 5px; 68 | border-radius: 3px; 69 | } 70 | 71 | .navbar-form{ 72 | box-shadow: none; 73 | -webkit-box-shadow:none; 74 | } 75 | 76 | .form-control{ 77 | background-color: #E1EAF2; 78 | background-image: none; 79 | border: 1px solid #045BB3; 80 | box-shadow: inset 0 1px 3px rgba(0,0,0,.2),0 1px 0 rgba(255,255,255,.1); 81 | } 82 | .form-control:focus, 83 | .form-control:active{ 84 | background-color: #fff; 85 | outline: none !important; 86 | -webkit-appearance: none; 87 | } 88 | 89 | .navbar-default .navbar-nav>.open>a:focus, 90 | .navbar-default .navbar-nav>.open>a:hover{ 91 | color: #fff; 92 | background-color: rgb(7, 103, 200); 93 | } 94 | 95 | .navbar-default .navbar-toggle, 96 | .navbar-default .navbar-toggle:hover, 97 | .navbar-default .navbar-toggle:focus, 98 | .navbar-default .navbar-toggle:active{ 99 | border: none; 100 | background-color: transparent; 101 | padding: 7px 8px; 102 | margin-top: 8px; 103 | margin-right: 8px; 104 | } 105 | .navbar-toggle .icon-bar+.icon-bar { 106 | margin-top: 7px; 107 | } 108 | .navbar-default .navbar-toggle .icon-bar{ 109 | height: 1px; 110 | width: 24px; 111 | background-color: #ddd; 112 | } 113 | .navbar-default [aria-expanded="true"] .icon-bar{ 114 | background-color: #fff; 115 | } 116 | 117 | .user-menu-btn, 118 | .user-menu-ul{ 119 | min-width: 160px; 120 | cursor: pointer; 121 | } 122 | 123 | .user-menu-btn{ 124 | text-align: center; 125 | } 126 | 127 | @media (max-width: 767px){ 128 | .navbar-default .navbar-nav .open .dropdown-menu>li>a { 129 | color: #fff; 130 | } 131 | .navbar-default .navbar-nav .open .dropdown-menu>li>a:focus, .navbar-default .navbar-nav .open .dropdown-menu>li>a:hover { 132 | color: #fff; 133 | background-color: transparent; 134 | } 135 | .user-menu-btn{ 136 | text-align: left; 137 | } 138 | } 139 | 140 | /*主容器*/ 141 | .container{ 142 | max-width: 1100px; 143 | } 144 | 145 | .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-xs-1, .col-xs-10, .col-xs-11, .col-xs-12, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9{ 146 | padding-left: 10px; 147 | padding-right: 10px; 148 | } 149 | 150 | .container-main{ 151 | min-height:300px; 152 | } 153 | 154 | .panel{ 155 | font-size: 16px; 156 | } 157 | 158 | .panel-heading, 159 | .panel-footer{ 160 | padding: 10px 15px; 161 | margin: 0px; 162 | } 163 | 164 | .panel-body{ 165 | padding: 15px; 166 | font-size: 16px !important; 167 | margin: 0px; 168 | } 169 | 170 | .panel-body .media-heading{ 171 | font-size: 16px !important; 172 | margin-top: 2px; 173 | } 174 | 175 | .panel-heading .tab{ 176 | cursor: pointer; 177 | color: #0767c8; 178 | padding:3px 9px; 179 | border-radius: 3px; 180 | display: inline-block; 181 | font-size: 16px; 182 | } 183 | 184 | .panel-heading .tab.active{ 185 | background-color: #0767c8; 186 | color: #fff; 187 | } 188 | 189 | .topic-list{ 190 | margin: 0px; 191 | padding: 0px; 192 | } 193 | 194 | .topic-list li .avatar{ 195 | width: 45px; 196 | height: 45px; 197 | border-radius: 3px; 198 | } 199 | 200 | .topic-list li .topic-info, 201 | .topic-list li .topic-info a{ 202 | font-size: 13px; 203 | color:#888; 204 | } 205 | 206 | .topic-list li, 207 | .comment-list li{ 208 | margin: 0px; 209 | padding: 10px 0px; 210 | border-bottom: 1px solid #F0F0F0; 211 | } 212 | 213 | .topic-list li:last-child, 214 | .comment-list li:last-child{ 215 | padding-bottom: 0px; 216 | border-bottom: none; 217 | } 218 | 219 | .topic-list li:first-child, 220 | .comment-list li:first-child{ 221 | padding-top: 0px; 222 | } 223 | 224 | .topic-list li .media-right .badge{ 225 | height: 20px; 226 | line-height: 20px; 227 | font-size: 12px; 228 | padding: 1px 7px 0px 7px; 229 | text-align: center; 230 | background-color: #337ab7; 231 | margin-top: 12px; 232 | } 233 | 234 | .topic-empty, 235 | .comment-empty{ 236 | text-align: center; 237 | color:#888; 238 | margin: 0px; 239 | padding: 5px; 240 | } 241 | 242 | .align-left{ 243 | text-align: left; 244 | } 245 | 246 | .topic-view .panel-heading .avatar{ 247 | width: 45px; 248 | height: 45px; 249 | border-radius: 3px; 250 | } 251 | .topic-view .panel-heading .media-heading{ 252 | font-weight: bold; 253 | } 254 | .topic-view .panel-heading .topic-info{ 255 | font-size: 14px; 256 | color:#888; 257 | } 258 | 259 | .comment-list li .avatar{ 260 | width: 28px; 261 | height: 28px; 262 | border-radius: 3px; 263 | } 264 | .comment-list li .media-heading{ 265 | font-size: 14px !important; 266 | color:#888; 267 | } 268 | .comment-form{ 269 | padding: 15px; 270 | } 271 | 272 | .pagination{ 273 | margin:0px; 274 | } 275 | .pagination .active a, 276 | .pagination .active a:hover 277 | .pagination .active a:active{ 278 | background-color: transparent !important; 279 | color:rgb(7, 103, 200) !important; 280 | border: 1px solid #ddd !important; 281 | font-weight: bold !important; 282 | cursor: pointer !important; 283 | } 284 | .pagination>li>a:focus, 285 | .pagination>li>a:hover, 286 | .pagination>li>span:focus, 287 | .pagination>li>span:hover{ 288 | z-index: 0; 289 | } 290 | 291 | .integral-list{ 292 | margin: 0px; 293 | padding: 0px; 294 | list-style-type: none; 295 | } 296 | 297 | .integral-list li{ 298 | padding: 5px 0px; 299 | border-bottom: 1px solid #F0F0F0; 300 | } 301 | .integral-list li:last-child{ 302 | padding-bottom: 0px; 303 | border-bottom: none; 304 | } 305 | .integral-list li:first-child{ 306 | padding-top: 0px; 307 | } 308 | 309 | /*页脚*/ 310 | .footbar{ 311 | background-color: #fafafa; 312 | padding: 30px 0px; 313 | margin-top: 15px; 314 | } 315 | .heading-nav, 316 | .footbar-nav{ 317 | padding: 0px; 318 | margin: 0px; 319 | } 320 | .heading-nav li, 321 | .footbar-nav li{ 322 | font-size: 15px; 323 | line-height: 15px; 324 | margin:0px; 325 | padding: 0px 9px 0px 0px; 326 | margin: 0px 9px 9px 0px; 327 | border-right: solid 1px #ddd; 328 | list-style-type: none; 329 | display: inline-block; 330 | } 331 | .heading-nav li:last-child, 332 | .footbar-nav li:last-child{ 333 | margin-right: 0px; 334 | border-right: none; 335 | padding-right: 0px; 336 | } 337 | .footbar .footbar-info{ 338 | color: #888; 339 | } 340 | 341 | /*form*/ 342 | input, textarea{ 343 | padding: 8px 10px; 344 | } 345 | input[type="button"], 346 | input[type="submit"], 347 | button{ 348 | border: solid 1px #0767c8; 349 | border-radius: 3px; 350 | background-color: #0767c8; 351 | color: #eee; 352 | padding: 8px 24px; 353 | outline: none; 354 | } 355 | 356 | input[type="button"]:hover, 357 | input[type="submit"]:hover, 358 | button:hover{ 359 | color: #fff; 360 | } 361 | 362 | .form-row label{ 363 | display: inline-block; 364 | width: 60px; 365 | font-weight: normal; 366 | text-align: right; 367 | padding-right: 5px; 368 | } 369 | .form-row input[type="text"], 370 | .form-row input[type="password"], 371 | .form-row input[type="email"], 372 | .form-row input[type="file"], 373 | .form-row input[type="number"], 374 | .form-row textarea{ 375 | display: inline-block; 376 | width: 100%; 377 | max-width: 300px; 378 | border: 1px solid #ddd; 379 | resize: none; 380 | } 381 | .mditor .editor{ 382 | max-width: 100% !important; 383 | } 384 | 385 | @media (max-width: 767px){ 386 | .form-row input[type="text"], 387 | .form-row input[type="password"], 388 | .form-row input[type="email"], 389 | .form-row input[type="file"], 390 | .form-row input[type="number"]{ 391 | display: block; 392 | max-width: 100%; 393 | } 394 | .form-row label{ 395 | text-align: left; 396 | } 397 | } 398 | 399 | input[type="file"]{ 400 | border: 1px #ddd solid; 401 | } 402 | 403 | .align-right{ 404 | text-align: right; 405 | } 406 | 407 | .alert-text{ 408 | color: brown; 409 | } 410 | 411 | .checkbox-list{ 412 | margin: 0px; 413 | padding: 0px; 414 | } 415 | .checkbox-list li{ 416 | list-style-type: none; 417 | display: inline-block; 418 | font-weight: normal; 419 | margin-right: 10px; 420 | } 421 | .checkbox-list li input, 422 | .checkbox-list li label{ 423 | font-weight: normal; 424 | vertical-align: middle; 425 | margin:2px 0px; 426 | } 427 | 428 | .input-block{ 429 | display: block; 430 | width: 100% !important; 431 | max-width: 100%; 432 | min-width: 100%; 433 | } 434 | 435 | /*user-info*/ 436 | .profile .avatar, 437 | .user-info .avatar{ 438 | width: 80px; 439 | height: 80px; 440 | border-radius: 3px; 441 | } 442 | 443 | .user-info{ 444 | padding-left: 120px; 445 | position: relative; 446 | } 447 | .user-info .avatar{ 448 | position: absolute; 449 | left:20px; 450 | top:18px; 451 | } 452 | 453 | .markdown-body pre{ 454 | border: 1px solid #eee; 455 | } 456 | 457 | .user-search-list .avatar{ 458 | width: 22px; 459 | height: 22px; 460 | margin-right: 5px; 461 | border-radius: 3px; 462 | vertical-align: middle; 463 | } 464 | 465 | .panel-heading .fa, 466 | .panel-footer .fa{ 467 | border: 1px solid #ddd; 468 | min-width: 30px; 469 | height: 30px; 470 | line-height: 30px; 471 | text-align: center; 472 | border-radius: 3px; 473 | margin-right: 2px; 474 | color:#888; 475 | cursor: pointer; 476 | font-size: 15px; 477 | } 478 | 479 | .panel-footer{ 480 | overflow: hidden; 481 | } 482 | 483 | .panel-heading .fa.active, 484 | .panel-footer .fa.active, 485 | .panel-heading .fa:hover, 486 | .panel-footer .fa:hover{ 487 | border: 1px solid #aaa; 488 | color:#555; 489 | } 490 | 491 | .thumbs, 492 | .topic-label, 493 | .topic-type{ 494 | white-space: nowrap; 495 | } 496 | .thumbs .fa{ 497 | border: none; 498 | margin-left: 8px; 499 | } 500 | .thumbs .fa:hover{ 501 | border: none; 502 | } 503 | .linkbtn, 504 | .linkbtn:hover{ 505 | text-decoration: none !important; 506 | } 507 | .topic-label, 508 | .topic-type{ 509 | background-color: #ddd; 510 | color: #0767c8; 511 | margin: 0px 0px 1px 0px; 512 | display: inline-block; 513 | font-weight: normal; 514 | font-size: 12px; 515 | padding: 3px 5px; 516 | border-radius: 3px; 517 | vertical-align: middle; 518 | } 519 | .topic-type{ 520 | color: #888; 521 | } 522 | 523 | .inline{ 524 | display: inline; 525 | } 526 | 527 | .badge{ 528 | background-color: #f63; 529 | font-size: 12px; 530 | } 531 | 532 | .panel-ad a{ 533 | border:none; 534 | } 535 | .panel-ad img{ 536 | width: 100%; 537 | } 538 | 539 | blockquote { 540 | font-size: 16px; 541 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicon.ico -------------------------------------------------------------------------------- /public/favicons/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/android-chrome-144x144.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/android-chrome-36x36.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/android-chrome-48x48.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/android-chrome-72x72.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/android-chrome-96x96.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #da532c 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/favicon.ico -------------------------------------------------------------------------------- /public/favicons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Jser\u793e\u533a", 3 | "icons": [ 4 | { 5 | "src": "\/android-chrome-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": 0.75 9 | }, 10 | { 11 | "src": "\/android-chrome-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": 1 15 | }, 16 | { 17 | "src": "\/android-chrome-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": 1.5 21 | }, 22 | { 23 | "src": "\/android-chrome-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": 2 27 | }, 28 | { 29 | "src": "\/android-chrome-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": 3 33 | }, 34 | { 35 | "src": "\/android-chrome-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": 4 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /public/favicons/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/mstile-144x144.png -------------------------------------------------------------------------------- /public/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /public/favicons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/mstile-310x150.png -------------------------------------------------------------------------------- /public/favicons/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/mstile-310x310.png -------------------------------------------------------------------------------- /public/favicons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/favicons/mstile-70x70.png -------------------------------------------------------------------------------- /public/favicons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/images/avatar/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/1.png -------------------------------------------------------------------------------- /public/images/avatar/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/10.png -------------------------------------------------------------------------------- /public/images/avatar/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/11.png -------------------------------------------------------------------------------- /public/images/avatar/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/12.png -------------------------------------------------------------------------------- /public/images/avatar/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/13.png -------------------------------------------------------------------------------- /public/images/avatar/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/14.png -------------------------------------------------------------------------------- /public/images/avatar/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/15.png -------------------------------------------------------------------------------- /public/images/avatar/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/16.png -------------------------------------------------------------------------------- /public/images/avatar/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/2.png -------------------------------------------------------------------------------- /public/images/avatar/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/3.png -------------------------------------------------------------------------------- /public/images/avatar/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/4.png -------------------------------------------------------------------------------- /public/images/avatar/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/5.png -------------------------------------------------------------------------------- /public/images/avatar/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/6.png -------------------------------------------------------------------------------- /public/images/avatar/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/7.png -------------------------------------------------------------------------------- /public/images/avatar/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/8.png -------------------------------------------------------------------------------- /public/images/avatar/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/avatar/9.png -------------------------------------------------------------------------------- /public/images/favicon-300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/favicon-300.png -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/favicon.png -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jser-dev/jser/95c94369fcd1583352f5a1f38777044a646dbb15/public/images/logo.png -------------------------------------------------------------------------------- /robot/infoq.js: -------------------------------------------------------------------------------- 1 | var request = require("request"); 2 | -------------------------------------------------------------------------------- /test/at-test.js: -------------------------------------------------------------------------------- 1 | var atRegExp = new RegExp("\\@\\w+\\s+", "igm"); 2 | 3 | var rs = "@houfeng @li ".replace(atRegExp, function (str) { 4 | console.log("item: " + str); 5 | return "[" + str + "]"; 6 | }); 7 | console.log("rs: " + rs); 8 | -------------------------------------------------------------------------------- /test/mail-test.js: -------------------------------------------------------------------------------- 1 | var mail = require('../common/mail'); 2 | 3 | 4 | mail.send({}); 5 | -------------------------------------------------------------------------------- /test/time-test.js: -------------------------------------------------------------------------------- 1 | var timeago = require('timeago-words'); 2 | // module.exports.settings = { 3 | // allowFuture: false, 4 | // prefixAgo: null, 5 | // prefixFromNow: null, 6 | // numbers: [], 7 | // suffixAgo: "ago", 8 | // suffixFromNow: "from now", 9 | // seconds: "less than a minute", 10 | // minute: "about a minute", 11 | // minutes: "%d minutes", 12 | // hour: "about an hour", 13 | // hours: "about %d hours", 14 | // day: "a day", 15 | // days: "%d days", 16 | // month: "about a month", 17 | // months: "%d months", 18 | // year: "about a year", 19 | // years: "%d years" 20 | // }; 21 | timeago.settings.suffixAgo = ''; 22 | timeago.settings.suffixFromNow = '距现在'; 23 | timeago.settings.seconds = "刚刚"; 24 | timeago.settings.minute = "1分钟前"; 25 | timeago.settings.minutes = "%d分钟前"; 26 | timeago.settings.hour = "1小时前"; 27 | timeago.settings.hours = "%d小时前"; 28 | timeago.settings.day = "1天前"; 29 | timeago.settings.days = "%d天前"; 30 | timeago.settings.month = "1月前"; 31 | timeago.settings.months = "%d月前"; 32 | timeago.settings.year = "1年前"; 33 | timeago.settings.years = "%d年前"; 34 | 35 | console.log(timeago(new Date())); -------------------------------------------------------------------------------- /test/utils-test.js: -------------------------------------------------------------------------------- 1 | var utils = require('../common/utils'); 2 | 3 | for (var i = 1; i <= 12; i++) { 4 | console.log(utils.random(1, 12)); 5 | } -------------------------------------------------------------------------------- /tmpls/message.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= this.user.name %>, 您好 4 |
5 |
6 | 欢迎您注册 "<%= this.configs.name %>",点击如下链接立即激活您的账号:
7 | <% var link='http://'+this.configs.domain+'/verify/'+this.user.verifyCode %> 8 | <%= link %> 9 |
10 |
11 | <%= this.configs.author %> 敬上 12 |
13 |
-------------------------------------------------------------------------------- /tmpls/reset-password.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= this.user.name %>, 您好 4 |
5 |
6 | 欢迎您注册 "<%= this.configs.name %>",点击如下链接立即激活您的账号:
7 | <% var link='http://'+this.configs.domain+'/verify/'+this.user.verifyCode %> 8 | <%= link %> 9 |
10 |
11 | <%= this.configs.author %> 敬上 12 |
13 |
-------------------------------------------------------------------------------- /tmpls/verify-mail.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= this.user.name %>, 您好 4 |
5 |
6 | 欢迎您注册 "<%= this.configs.name %>",点击如下链接立即激活您的账号:
7 | <% var link='http://'+this.configs.domain+'/verify/'+this.user.verifyCode %> 8 | <%= link %> 9 |
10 |
11 | <%= this.configs.author %> 敬上 12 |
13 |
-------------------------------------------------------------------------------- /views/about.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 关于 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() %> 9 | 10 | <% $.placeBegin("content") %> 11 | 12 |
13 |
关于 JSER
14 |
15 |

社区

16 |

JSER 是一个 “纯粹而简单” 的 “JS程序猿” 社区,目前由 <%= $.configs.guardian.name %> 负责维护。

17 |

软件

18 |

同时,JSER 还是一个开源的社区软件,与 JSER 社区同名, 19 | 开源地址: 20 | 21 | https://github.com/jser-dev/jser 22 | 23 |

24 |
25 |
26 | 27 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/api.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | API - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() %> 9 | 10 | <% $.placeBegin("content") %> 11 | 12 |
13 |
API 说明
14 |
15 | ... 16 |
17 |
18 | 19 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/client.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 客户端 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() %> 9 | 10 | <% $.placeBegin("content") %> 11 | 12 |
13 |
客户端
14 |
15 | ... 16 |
17 |
18 | 19 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/control-panel.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 控制面板 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() %> 9 | 10 | <% $.placeBegin("content") %> 11 | 12 |
13 |
权限管理
14 |
15 |
16 | <% $.each($.configs.roles,function(roleName,role){ %> 17 |
18 |
<%= role.text %>
19 | 20 |
21 | <% }.bind(this)) %> 22 |

23 | 24 |

25 |
26 |
27 |
28 | 29 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/forget-password.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 忘记密码 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() > 9 | 10 | <% $.placeBegin("content") %> 11 | 12 |
13 |
忘记密码
14 |
15 | 16 | <% this.data=this.data||{} %> 17 |
18 |

19 | 20 | <%= this.message %> 21 |

22 |

23 | 24 | 25 |

26 |

27 | 28 | 29 |

30 |

31 | 32 |

33 |
34 | 35 |
36 |
37 | 38 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/layout/master.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | <% $.placeHolder('head') %> 34 | 35 | 36 | 37 | <% if($.context.isWeChat){ %> 38 |
39 | favicon 40 |
41 | <% } %> 42 | 97 |
98 |
99 | 100 |
101 | <% $.placeHolder("content") %> 102 |
103 |
104 |
105 |
106 | 107 |
108 |
109 |
110 |
111 | 积分榜 112 | 更多 113 |
114 |
115 | <% var userList = $.controller.context.topUserList %> 116 |
    117 | <% $.each(userList,function(i,item){ %> 118 |
  • 119 | <%= item.name %> 120 | <%= item.score %> 121 |
  • 122 | <% }) %> 123 |
124 |
125 |
126 |
127 |
128 | 推广链接 129 |
130 |
131 | 132 | 腾讯云服务器安全可靠高性能,多种配置供您选择 133 | 134 |
135 |
136 | 147 |
148 |
149 |
150 |
151 | 友情社区 152 |
153 |
154 |
    155 | <% $.each($.configs.links,function(i,item){ %> 156 |
  • <%= item.name %>
  • 157 | <% }) %> 158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | 173 |

174 | <%= $.configs.footbar.info %> , 175 | <%= $.configs.buildYear %>-<%= (new Date()).getFullYear() %> , 176 | <%= $.configs.domain.toUpperCase() %> 177 |

178 | 181 |
182 |
183 | 184 | 185 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /views/message.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 我的消息 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() %> 9 | 10 | <% $.placeBegin("content") %> 11 | 12 | <% var user = this.user %> 13 |
14 |
15 | 我的消息 16 |
    17 | 22 |
  • 23 |
    24 | 清空 25 |
    26 |
  • 27 |
28 |
29 |
30 | <% if(this.msgList && this.msgList.length>0){ %> 31 |
    32 | <% $.each(this.msgList,function(i,item){ %> 33 |
  • 34 | <%= item.content %> 35 | 36 | [<%= $.timeago(item.sendAt) %>] 37 | 38 |
    39 | 40 | 删除 41 |
    42 |
  • 43 | <% }.bind(this)); %> 44 |
45 | <% }else{ %> 46 |

没有消息

47 | <% } %> 48 |
49 |
50 | 51 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/profile.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 个人中心 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() > 9 | 10 | <% $.placeBegin("content") %> 11 | 12 | 13 |
14 |
15 | 基本信息 16 |
17 |
18 |
19 |

20 | 21 | <%= this.saveBaseInfoMessage %> 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 | 修改密码 54 |
55 |
56 |
57 |

58 | 59 | <%= this.changePasswordMessage %> 60 |

61 |

62 | 63 | 64 |

65 |

66 | 67 | 68 |

69 |

70 | 71 | 72 |

73 |

74 | 75 |

76 |
77 |
78 |
79 | 80 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/search.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 搜索 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() %> 9 | 10 | <% $.placeBegin("content") %> 11 | 12 |
13 |
话题
14 |
15 | <% if(this.topicList && this.topicList.length>0){ %> 16 | 57 | <% }else{ %> 58 |

没有匹配的话题

59 | <% } %> 60 |
61 |
62 | 63 |
64 |
用户
65 |
66 | <% if(this.userList && this.userList.length>0){ %> 67 |
    68 | <% $.each(this.userList,function(i,item){ %> 69 |
  • 70 | 71 | <%= item.name %> 72 | <%= item.name %> 73 | 74 | 75 | <%= item.integral %> 76 | 77 |
  • 78 | <% }.bind(this)); %> 79 |
80 | <% }else{ %> 81 |

没有匹配的用户

82 | <% } %> 83 |
84 |
85 | 86 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/signin.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 登录 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() > 9 | 10 | <% $.placeBegin("content") %> 11 | 12 |
13 |
登录
14 |
15 | 16 | <% this.user=this.user||{} %> 17 |
18 |

19 | 20 | <%= this.message %> 21 |

22 |

23 | 24 | 25 |

26 |

27 | 28 | 29 |

30 |

31 | 32 | 33 | 找回密码 34 | | 35 | GitHub 登录 36 |

37 |

38 | 39 |

40 |
41 | 42 |
43 |
44 | 45 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/signup.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 注册 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() > 9 | 10 | <% $.placeBegin("content") %> 11 | 12 |
13 |
注册
14 |
15 | <% if(this.status){ %> 16 |

<%= this.message %>, 请查收邮件验证您的邮箱后 登录

17 | <% }else{ %> 18 | <% this.user=this.user||{} %> 19 |
20 |

21 | 22 | <%= this.message %> 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 | 54 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/topic-edit.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 编辑话题 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() > 9 | 10 | <% $.placeBegin("content") %> 11 | 12 |
13 | <% var user = $.context.user %> 14 |
15 | 编辑话题 16 | <% if(user && (user.isAdmin||user._id.toString()==this.topic.author._id.toString())){ %> 17 |
18 | 删除 19 |
20 | <% } %> 21 |
22 |
23 |

24 | <%= this.saveMessage %> 25 |

26 |

27 | 28 |

29 |

30 | 31 |

32 |

33 |

    34 | <% $.each($.configs.topicTypes,function(name,item){ %> 35 | <% if(item.admin)return; %> 36 |
  • 37 | > 38 | 39 |
  • 40 | <% }.bind(this)) %> 41 |
42 |

43 |

44 | > 45 | 46 | 47 |

48 |
49 | 68 |
69 | -------------------------------------------------------------------------------- /views/topic-list.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | <% var currentList = $.configs.topicTypes[this.currentType] %> 5 | <%= currentList?currentList.text:'所有话题' %> - <%= $.configs.name %> 6 | 7 | 8 | 9 | <% $.placeEnd() > 10 | 11 | <% $.placeBegin("content") %> 12 | 13 |
14 |
15 | 所有 16 | <% $.each($.configs.topicTypes,function(name,item){ %> 17 | 18 | <%= item.text %> 19 | 20 | <% }.bind(this)) %> 21 |
22 |
23 | <% if(this.list && this.list.length>0){ %> 24 | 68 | <% }else{ %> 69 |

没有话题

70 | <% } %> 71 |
72 | 91 |
92 | 93 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/topic-view.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | <%= this.topic.title %> - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() > 9 | 10 | <% $.placeBegin("content") %> 11 | 12 |
13 |
14 |
15 | 16 | <%= this.topic.author.name %> 17 | 18 |
19 |

20 | <%= this.topic.title %> 21 |

22 | 23 | <%= this.topic.author.name %> 24 | 创建于 <%= $.timeago(this.topic.createAt,"yyyy-MM-dd hh:mm") %> , 25 | 编辑于 <%= $.timeago(this.topic.updateAt,"yyyy-MM-dd hh:mm") %> 26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 | <%= this.topic.html %> 34 | 35 |
36 | 59 |
60 | 61 | 62 |
63 |
64 | 回复(<%= this.topic.comments.length %>) 65 |
66 |
67 | <% if(this.topic.comments && this.topic.comments.length>0){ %> 68 |
    69 | <% $.each(this.topic.comments,function(i,item){ %> 70 |
  • 71 | 72 | 73 | <%= item.author.name %> 74 | 75 |
    76 |

    77 | 78 | <%= item.author.name %> 79 | 80 | - <%= i+1 %>楼 - 81 | 82 | <%= $.timeago(item.createAt) %> 83 | 84 | <% if(user && (user.isAdmin||user._id.toString()==item.author._id.toString())){ %> 85 |
    86 | 87 | 删除 88 |
    89 | <% } %> 90 |

    91 | 92 | <%= item.html %> 93 | 94 |
    95 |
  • 96 | <% }.bind(this)); %> 97 |
98 | <% }else{ %> 99 |

暂没有回复

100 | <% } %> 101 |
102 | <% if(this.user){ %> 103 | 104 | 115 | 118 | <% }else{ %> 119 | 122 | <% } %> 123 |
-------------------------------------------------------------------------------- /views/user-info.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | <%= this.user.name %> - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() %> 9 | 10 | <% $.placeBegin("content") %> 11 | 12 | <% var user = this.user %> 13 |
14 |
15 | 基本信息 16 | <% var loginUser = $.controller.context.user %> 17 | <% if(loginUser && loginUser._id.toString()==this.user._id.toString()){ %> 18 | 19 | 修改 20 | 21 | <% } %> 22 |
23 | 31 |
32 | 33 | <% if($.context.user && $.context.user._id==this.userId){ %> 34 |
35 |
草稿话题
36 |
37 | <% if(this.draftList && this.draftList.length>0){ %> 38 |
    39 | <% $.each(this.draftList,function(i,item){ %> 40 |
  • 41 | <%= item.title || "无标题" %> 42 | 43 | <%= $.timeago(item.createAt) %> 44 | 45 |
  • 46 | <% }.bind(this)); %> 47 |
48 | <% }else{ %> 49 |

没有最近话题

50 | <% } %> 51 |
52 |
53 | <% } %> 54 | 55 |
56 |
最近话题
57 |
58 | <% if(this.topicList && this.topicList.length>0){ %> 59 |
    60 | <% $.each(this.topicList,function(i,item){ %> 61 |
  • 62 | <%= item.title %> 63 | 64 | <%= $.timeago(item.createAt) %> 65 | 66 |
  • 67 | <% }.bind(this)); %> 68 |
69 | <% }else{ %> 70 |

没有最近话题

71 | <% } %> 72 |
73 |
74 | 75 |
76 |
最近参与评论
77 |
78 | <% if(this.commentList && this.commentList.length>0){ %> 79 |
    80 | <% $.each(this.commentList,function(i,item){ %> 81 |
  • 82 | <%= item.topic.title %> 83 | 84 | <%= $.timeago(item.createAt) %> 85 | 86 |
  • 87 | <% }.bind(this)); %> 88 |
89 | <% }else{ %> 90 |

没有最近评论

91 | <% } %> 92 |
93 |
94 | 95 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/user-list.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 积分榜 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() %> 9 | 10 | <% $.placeBegin("content") %> 11 |
12 | <% var userList = this.topUserList||[] %> 13 |
积分榜 (前 <%= userList.length %> 位)
14 |
15 | 26 |
27 |
28 | <% $.placeEnd() %> -------------------------------------------------------------------------------- /views/verify-mail.html: -------------------------------------------------------------------------------- 1 | <% $.master("./layout/master.html") %> 2 | 3 | <% $.placeBegin("head") %> 4 | 注册 - <%= $.configs.name %> 5 | 6 | 7 | 8 | <% $.placeEnd() > 9 | 10 | <% $.placeBegin("content") %> 11 | 12 |
13 |
注册
14 |
15 | <% if(this.user){ %> 16 | <%= this.user.email %> 验证成功,现在登录

17 | <% }else{ %> 18 | 验证失败,请 注册新用户

19 | <% } %> 20 |
21 |
22 | 23 | <% $.placeEnd() %> 24 | --------------------------------------------------------------------------------