├── README.md
├── app.js
├── bin
└── www
├── common
├── email.js
├── helps.js
└── tools.js
├── config.js
├── controllers
├── topic.js
├── upload.js
└── users.js
├── middlewares
└── auth.js
├── models
├── base.js
├── comment.js
├── index.js
├── tag.js
├── topic.js
└── users.js
├── npm-debug.log
├── package.json
├── proxy
├── comment.js
├── index.js
├── tag.js
├── topic.js
└── users.js
├── public
├── images
│ ├── default-avatar.png
│ └── loading.gif
├── javascripts
│ ├── ajax-upload.js
│ ├── bootstrap.min.js
│ ├── common.js
│ ├── editor.js
│ ├── jquery-ui.min.js
│ ├── jquery.min.js
│ ├── marked.js
│ └── tag-it.js
└── stylesheets
│ ├── bootstrap.min.css
│ ├── editor-self.css
│ ├── editor.css
│ ├── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ ├── glyphicons-halflings-regular.woff2
│ └── icomoon.woff
│ ├── images
│ ├── loading.gif
│ ├── ui-bg_diagonals-thick_18_b81900_40x40.png
│ ├── ui-bg_diagonals-thick_20_666666_40x40.png
│ ├── ui-bg_flat_10_000000_40x100.png
│ ├── ui-bg_glass_100_f6f6f6_1x400.png
│ ├── ui-bg_glass_100_fdf5ce_1x400.png
│ ├── ui-bg_glass_65_ffffff_1x400.png
│ ├── ui-bg_gloss-wave_35_f6a828_500x100.png
│ ├── ui-bg_highlight-soft_100_eeeeee_1x100.png
│ ├── ui-bg_highlight-soft_75_ffe45c_1x100.png
│ ├── ui-icons_222222_256x240.png
│ ├── ui-icons_228ef1_256x240.png
│ ├── ui-icons_ef8c08_256x240.png
│ ├── ui-icons_ffd27a_256x240.png
│ └── ui-icons_ffffff_256x240.png
│ ├── jquery-ui.css
│ ├── jquery.tagit.css
│ ├── main.css
│ └── styles.css
├── routes
├── index.js
├── topic.js
├── upload.js
└── users.js
└── views
├── error.jade
├── header.jade
├── index.jade
├── layout.jade
├── sidebar-tag.jade
├── sidebar-user.jade
├── topic
├── create.jade
├── home.jade
├── list.jade
├── page.jade
├── show.jade
├── tag.jade
└── topic.jade
└── users
├── active.jade
├── home.jade
├── login.jade
├── register.jade
└── setting.jade
/README.md:
--------------------------------------------------------------------------------
1 | # NodeJS-forum
2 | 最近学习NodeJS,利用ExpressJS框架和MongoDB数据库搭建了一个简单的Forum!
3 |
4 | 演示地址:[Demo](http://nodejs.luckybird.me/)
5 |
6 | **功能简介**
7 |
8 | * 用户注册,发送激活邮件,用户登录,上传头像
9 | * 发起话题,创建话题标签,用户评论话题
10 | * Markdown编辑器,在线预览,支持图片
11 |
12 | **安装方法**
13 |
14 | ```
15 | $ git clone https://github.com/luckybirdme/NodeJS-forum.git
16 | $ npm install
17 | $ DEBUG=myExpressApp:* npm start
18 |
19 | ```
20 | 注意事项:
21 |
22 | 1. 请确保安装了NodeJS,npm,MongoDB
23 | 2. 请根据环境修改根目录的config.js配置文件
24 |
25 | **备注**
26 |
27 | 使用过程中如有疑问,可查看本人的学习笔记,博客地址:[LuckyBird](http://www.luckybird.me/)
28 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | var config = require("./config");
2 | var helps = require("./common/helps");
3 |
4 |
5 | var express = require('express');
6 | var path = require('path');
7 | var favicon = require('serve-favicon');
8 | var logger = require('morgan');
9 |
10 |
11 |
12 | var cookieParser = require('cookie-parser');
13 | var bodyParser = require('body-parser');
14 |
15 | var csrf = require('csurf');
16 |
17 | var routes = require('./routes/index');
18 | var users = require('./routes/users');
19 | var topic = require('./routes/topic');
20 | var upload = require('./routes/upload');
21 |
22 |
23 | var app = express();
24 |
25 | // view engine setup
26 | app.set('views', path.join(__dirname, 'views'));
27 | app.set('view engine', 'jade');
28 |
29 | // uncomment after placing your favicon in /public
30 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
31 | app.use(logger('dev'));
32 | app.use(bodyParser.json());
33 | app.use(bodyParser.urlencoded({ extended: false }));
34 | app.use(cookieParser());
35 | app.use(express.static(path.join(__dirname, 'public')));
36 |
37 | app.use('/upload',express.static(path.join(__dirname, 'upload')));
38 | global.appRoot = path.resolve(__dirname);
39 |
40 | const session = require('express-session');
41 | const MongoStore = require('connect-mongo')(session);
42 | app.use(session({
43 | secret: config.session.cookie.secret,
44 | name: config.session.cookie.name,
45 | cookie: {
46 | maxAge:config.session.cookie.maxAge
47 | },
48 | resave: false,
49 | saveUninitialized: true,
50 | store: new MongoStore({ url:config.session.database.address })
51 | }));
52 |
53 | var csrfProtection = csrf();
54 | app.use(csrfProtection);
55 |
56 | app.use(function (err, req, res, next) {
57 | if (err.code !== 'EBADCSRFTOKEN') return next(err)
58 |
59 | // handle CSRF token errors here
60 | var name = 'submit';
61 | var notice = 'Invalid csrf token , please refresh';
62 | helps.resJsonError(req,res,name,notice);
63 | })
64 |
65 |
66 | app.use(function(req,res,next){
67 | res.locals.session = req.session;
68 | res.locals.webName = config.web.name;
69 | next();
70 | });
71 |
72 |
73 | app.use('/', routes);
74 | app.use('/users', users);
75 | app.use('/topic', topic);
76 | app.use('/upload', upload);
77 |
78 | // catch 404 and forward to error handler
79 | app.use(function(req, res, next) {
80 | var err = new Error('Not Found');
81 | err.status = 404;
82 | next(err);
83 | });
84 |
85 | // error handlers
86 |
87 | // development error handler
88 | // will print stacktrace
89 | if (app.get('env') === 'development') {
90 | app.use(function(err, req, res, next) {
91 | res.status(err.status || 500);
92 | res.render('error', {
93 | message: err.message,
94 | error: err
95 | });
96 | });
97 | }
98 |
99 | // production error handler
100 | // no stacktraces leaked to user
101 | app.use(function(err, req, res, next) {
102 | res.status(err.status || 500);
103 | res.render('error', {
104 | message: err.message,
105 | error: {}
106 | });
107 | });
108 |
109 |
110 | module.exports = app;
111 |
--------------------------------------------------------------------------------
/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | var app = require('../app');
8 | var debug = require('debug')('myExpressApp:server');
9 | var http = require('http');
10 |
11 | /**
12 | * Get port from environment and store in Express.
13 | */
14 |
15 | var port = normalizePort(process.env.PORT || '3000');
16 | app.set('port', port);
17 |
18 | /**
19 | * Create HTTP server.
20 | */
21 |
22 | var server = http.createServer(app);
23 |
24 | /**
25 | * Listen on provided port, on all network interfaces.
26 | */
27 |
28 | server.listen(port);
29 | server.on('error', onError);
30 | server.on('listening', onListening);
31 |
32 | /**
33 | * Normalize a port into a number, string, or false.
34 | */
35 |
36 | function normalizePort(val) {
37 | var port = parseInt(val, 10);
38 |
39 | if (isNaN(port)) {
40 | // named pipe
41 | return val;
42 | }
43 |
44 | if (port >= 0) {
45 | // port number
46 | return port;
47 | }
48 |
49 | return false;
50 | }
51 |
52 | /**
53 | * Event listener for HTTP server "error" event.
54 | */
55 |
56 | function onError(error) {
57 | if (error.syscall !== 'listen') {
58 | throw error;
59 | }
60 |
61 | var bind = typeof port === 'string'
62 | ? 'Pipe ' + port
63 | : 'Port ' + port;
64 |
65 | // handle specific listen errors with friendly messages
66 | switch (error.code) {
67 | case 'EACCES':
68 | console.error(bind + ' requires elevated privileges');
69 | process.exit(1);
70 | break;
71 | case 'EADDRINUSE':
72 | console.error(bind + ' is already in use');
73 | process.exit(1);
74 | break;
75 | default:
76 | throw error;
77 | }
78 | }
79 |
80 | /**
81 | * Event listener for HTTP server "listening" event.
82 | */
83 |
84 | function onListening() {
85 | var addr = server.address();
86 | var bind = typeof addr === 'string'
87 | ? 'pipe ' + addr
88 | : 'port ' + addr.port;
89 | debug('Listening on ' + bind);
90 | }
91 |
--------------------------------------------------------------------------------
/common/email.js:
--------------------------------------------------------------------------------
1 | var nodemailer = require('nodemailer');
2 | var config = require("../config");
3 |
4 | var transporter = nodemailer.createTransport({
5 | host: config.email.host,
6 | port: config.email.port,
7 | auth: {
8 | user: config.email.user,
9 | pass: config.email.pass
10 | }
11 | });
12 |
13 | exports.sendMail = sendMail;
14 |
15 | function sendMail(from,email,subject,html){
16 |
17 | var mailOptions = {
18 | from:from,
19 | to: email,
20 | subject: subject,
21 | html: html
22 | };
23 | // send mail with defined transport object
24 | transporter.sendMail(mailOptions, function(error, info){
25 | if(error){
26 | return console.log("sendMail error: "+error);
27 | }
28 | console.log('sendMail success: ' + info.response);
29 |
30 | });
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/common/helps.js:
--------------------------------------------------------------------------------
1 | exports.jsonRedirect = jsonRedirect;
2 | exports.resJsonSuccess = resJsonSuccess;
3 | exports.resJsonError = resJsonError;
4 |
5 | function jsonRedirect(res,url){
6 | var data = {
7 | redirect : url
8 | };
9 | res.json(data);
10 | }
11 |
12 | function resJsonSuccess(req,res,name,notice,url){
13 | var data = {
14 | success : {
15 | name : name,
16 | notice : notice,
17 | url:url
18 | }
19 | };
20 | resJsonOut(req,res,data);
21 | }
22 |
23 | function resJsonError(req,res,name,notice){
24 | var data = {
25 | error : {
26 | name : name,
27 | notice : notice
28 | }
29 | };
30 | resJsonOut(req,res,data);
31 | }
32 |
33 | function resJsonOut(req,res,data){
34 | res.json(data);
35 | }
36 |
37 |
38 |
--------------------------------------------------------------------------------
/common/tools.js:
--------------------------------------------------------------------------------
1 | var config = require("../config")
2 | var moment = require("moment");
3 | var bcrypt = require('bcrypt');
4 | var utility = require('utility');
5 | var mkdirp = require('mkdirp');
6 |
7 | exports.mkDirpath = mkDirpath;
8 | exports.formatDate = formatDate;
9 | exports.bcryptGenSalt = bcryptGenSalt;
10 | exports.bcryptCompare = bcryptCompare;
11 | exports.getActiveKey = getActiveKey;
12 |
13 | function getActiveKey(user){
14 | var activeKey = utility.md5(user.userEmail+user.password+config.auth.activeKey+user.update_at);
15 | return activeKey;
16 | }
17 |
18 |
19 |
20 | function bcryptCompare(password,hash,callback){
21 | bcrypt.compare(password, hash, callback);
22 | }
23 |
24 | function bcryptGenSalt(password,callback){
25 | bcrypt.genSalt(10, function(err, salt) {
26 | bcrypt.hash(password, salt,callback);
27 | });
28 | }
29 |
30 |
31 | function formatDate (date, friendly) {
32 | date = moment(date);
33 | if (friendly) {
34 | return date.fromNow();
35 | } else {
36 | return date.format('YYYY-MM-DD HH:mm');
37 | }
38 |
39 | }
40 |
41 | function mkDirpath(dir,callback){
42 | mkdirp(dir,callback);
43 | }
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var appRoot = path.resolve(__dirname);
3 | var config = {
4 | web:{
5 | name:"LuckyBird",
6 | appRoot:appRoot
7 | },
8 | url:{
9 | //host:"http://10.118.27.156:3000"
10 | host:"http://172.20.10.2:3000"
11 | },
12 | upload:{
13 | uploadPath:"",
14 | imageFloder:"/upload/images/",
15 | defaultAvatar:"/images/default-avatar.png"
16 | },
17 | auth:{
18 | activeKey : "activeKey",
19 | checkActive: false
20 | },
21 | database :{
22 | address : "mongodb://localhost/myExpressApp"
23 | },
24 | session :{
25 | cookie : {
26 | secret: 'sessionSecret',
27 | name: 'sessionName',
28 | maxAge: 1000 * 60 * 60 * 24 * 30
29 | },
30 | database:{
31 | address: 'mongodb://localhost/myExpressAppSession'
32 | }
33 |
34 |
35 | },
36 | email:{
37 | host: 'smtp.qq.com',
38 | port: 25,
39 | user: '123456789@qq.com',
40 | pass: '123456789',
41 | from: 'LuckyBird<123456789@qq.com>'
42 | },
43 | topic:{
44 | page:{
45 | limit:10
46 | }
47 | }
48 | };
49 |
50 | module.exports = config;
51 |
--------------------------------------------------------------------------------
/controllers/topic.js:
--------------------------------------------------------------------------------
1 | var config = require("../config")
2 | var validator = require('validator');
3 | var eventproxy = require('eventproxy');
4 | var helps = require("../common/helps");
5 | var marked = require('marked');
6 | var proxy = require('../proxy');
7 | var Tag = proxy.Tag;
8 | var Topic = proxy.Topic;
9 | var Comment = proxy.Comment;
10 | var auth = require("../middlewares/auth");
11 | var mongoose = require('mongoose');
12 |
13 | exports.create = create;
14 | exports.home = home;
15 | exports.show = show;
16 | exports.add = add;
17 | exports.comment = comment;
18 | exports.getTags = getTags;
19 | exports.getComments = getComments;
20 |
21 | function getComments(req,res,next){
22 |
23 | var topicId = req.query.topicId;
24 | var sort = '-update_at';
25 |
26 | Comment.getTopicComments(topicId,sort,function(error,comments){
27 | if(error){
28 | return next(error);
29 | }
30 | var showComments = [];
31 | if(comments){
32 | comments.forEach(function(comment){
33 | showComments.push({
34 | userName:comment.userName,
35 | content:comment.content,
36 | update_at_ago:comment.update_at_ago()
37 | });
38 | });
39 | }
40 | var data = {
41 | showComments : showComments
42 | };
43 | res.json(data);
44 | })
45 | }
46 |
47 | function comment(req,res,next){
48 | var content = validator.trim(req.body.content);
49 | var topicId = req.body.topicId;
50 |
51 | var ep = new eventproxy();
52 | ep.fail(next);
53 | ep.once('comment_error', function (name,notice) {
54 | helps.resJsonError(req,res,name,notice);
55 | });
56 |
57 | if(validator.isNull(content)){
58 | ep.emit("comment_error","content","Content can't be empty");
59 | return;
60 | }
61 |
62 | var comment = {
63 | content:content,
64 | topicId:topicId
65 | }
66 |
67 | var checkEvent = new eventproxy();
68 | checkEvent.all('checkComment', function (comment) {
69 | Topic.getById(comment.topicId,function(error,topic){
70 | if(error){
71 | return next(error);
72 | }
73 | if(topic){
74 | var update = {
75 | update_at:new Date(),
76 | commentCount:topic.commentCount+1
77 | }
78 | Topic.update(topic._id,update,function(error,raw){
79 | if(error){
80 | return next(error);
81 | }
82 | var url = config.url.host+"/topic/show?_id="+topic._id;
83 | helps.jsonRedirect(res,url);
84 | })
85 | }else{
86 | ep.emit("comment_error","submit","Error occurred when get topic");
87 | }
88 |
89 | })
90 | });
91 |
92 | auth.getUserBySession(req,function(user){
93 | if(user){
94 | comment.userName = user.userName;
95 | Comment.insert(comment,function(error,comment){
96 | if(error){
97 | return next(error);
98 | }
99 | if(comment){
100 | checkEvent.emit('checkComment',comment);
101 | }else{
102 | ep.emit("comment_error","submit","Error occurred when save comment");
103 | }
104 |
105 | });
106 |
107 | }else{
108 | ep.emit("comment_error","submit","Session is timeout, please login");
109 | }
110 | });
111 | }
112 |
113 | function getTags(req,res,next){
114 | Tag.getAllTags(function(error,tags){
115 | if(error){
116 | return next(error);
117 | }
118 | var showTags = [];
119 | if(tags){
120 | tags.forEach(function(tag){
121 | showTags.push({
122 | _id:tag._id,
123 | showName:tag.showName
124 | });
125 | });
126 | }
127 | var data = {
128 | showTags : showTags
129 | };
130 | res.json(data);
131 |
132 | });
133 | }
134 |
135 |
136 | function add(req,res,next){
137 | var _id = req.query._id;
138 |
139 | var checkEvent = new eventproxy();
140 | checkEvent.all('checkTopic','checkTag', function (topicTags,hasTags) {
141 | var resData = {
142 | title : "Add",
143 | hasTags : hasTags
144 | };
145 | if(topicTags){
146 | resData.topic = topicTags.topic;
147 | resData.tagsName = topicTags.tagsName;
148 | }
149 | res.render('topic/create', resData);
150 | });
151 |
152 |
153 |
154 |
155 | Topic.getById(_id,function(error,topic){
156 | if(error){
157 | return next(error);
158 | }
159 | if(topic){
160 | var tagsId = topic.tagsId;
161 | var ep = new eventproxy();
162 | ep.after('getTags',tagsId.length,function (tagsName) {
163 | var topicTags = {
164 | topic : topic,
165 | tagsName : tagsName
166 | }
167 | checkEvent.emit('checkTopic',topicTags);
168 | });
169 |
170 |
171 | tagsId.forEach(function(tagId){
172 | Tag.getById(tagId,function(error,tag){
173 | if(error){
174 | return next(error);
175 | }
176 | if(tag){
177 | ep.emit("getTags",tag.showName);
178 | }else{
179 | ep.emit("getTags");
180 | }
181 |
182 | })
183 | });
184 | }else{
185 | checkEvent.emit('checkTopic');
186 | }
187 |
188 | })
189 |
190 | Tag.getAllTags(function(error,tags){
191 | if(error){
192 | return next(error);
193 | }
194 | var hasTags=[];
195 | if(tags){
196 | tags.forEach(function(tag){
197 | hasTags.push(tag.showName);
198 | });
199 | }
200 | checkEvent.emit('checkTag',hasTags);
201 | });
202 | }
203 |
204 | function show(req,res,next){
205 | var _id = req.query._id;
206 | Topic.getById(_id,function(error,topic){
207 | if (error) {
208 | return next(error);
209 | }
210 | if(topic){
211 |
212 | topic.openCount = topic.openCount + 1;
213 | var update = {openCount:topic.openCount};
214 |
215 | Topic.update(topic._id,update,function(error,raw){
216 | if (error) {
217 | return next(error);
218 | }
219 |
220 | var tagsId = topic.tagsId;
221 | var ep = new eventproxy();
222 | ep.after('getTags',tagsId.length,function (tags) {
223 |
224 | var resData = {
225 | title : topic.title,
226 | topic : topic,
227 | tags : tags
228 | };
229 |
230 | res.render('topic/show', resData);
231 |
232 | });
233 |
234 | tagsId.forEach(function(tagId){
235 | Tag.getById(tagId,function(error,tag){
236 | if(error){
237 | return next(error);
238 | }
239 | if(tag){
240 | ep.emit("getTags",tag);
241 | }else{
242 | ep.emit("getTags");
243 | }
244 |
245 | })
246 | });
247 | })
248 |
249 |
250 | }else{
251 | var url = config.url.host;
252 | res.redirect(url);
253 | }
254 |
255 | })
256 | }
257 |
258 | function home(req,res,next){
259 | var query = {};
260 | var fields={};
261 | var limit = config.topic.page.limit;
262 | var skip = 0;
263 | var sort = '-update_at';
264 |
265 | pageUrl = "/topic/home?page=true";
266 | var tagId = req.query.tagId;
267 | if(!validator.isNull(tagId)){
268 | query.tagsId = mongoose.Types.ObjectId(tagId);
269 | pageUrl+=+pageUrl+"&tagId="+tagId;
270 | }
271 |
272 |
273 | var pageNum = req.query.pageNum;
274 | if(!validator.isNull(pageNum) && pageNum > 1){
275 | var prevNum = parseInt(pageNum)-1;
276 | skip = limit*prevNum;
277 | }else{
278 | pageNum = 1;
279 | }
280 |
281 |
282 |
283 |
284 | Topic.getByPage(query,fields,skip,limit,sort,function(error,topics){
285 | if (error) {
286 | return next(error);
287 | }
288 | var pageInfo={
289 | pageNum : pageNum
290 | };
291 | if(pageNum > 1){
292 | pageInfo.prevUrl = pageUrl+"&pageNum="+(parseInt(pageNum)-1);
293 | }
294 |
295 | if(topics && topics.length == limit){
296 | pageInfo.nextUrl = pageUrl+"&pageNum="+(parseInt(pageNum)+1);
297 | }
298 |
299 | Tag.getByPage({},{},0,10,'-useCount',function(error,tags){
300 | if (error) {
301 | return next(error);
302 | }
303 | var resData = {
304 | title : 'Home',
305 | topics : topics,
306 | pageInfo : pageInfo,
307 | tags:tags,
308 | tagId:tagId
309 | };
310 | res.render('topic/home', resData);
311 | })
312 |
313 |
314 | });
315 |
316 |
317 | }
318 |
319 |
320 |
321 | function create(req,res,next){
322 | var _id = req.body._id;
323 | var title = validator.trim(req.body.title);
324 | var tags = validator.trim(req.body.tags);
325 | var Markdown = req.body.markdown;
326 |
327 | var ep = new eventproxy();
328 | ep.fail(next);
329 | ep.once('create_error', function (name,notice) {
330 | helps.resJsonError(req,res,name,notice);
331 | });
332 |
333 | if(validator.isNull(title)){
334 | ep.emit("create_error","title","Title can't be empty");
335 | return;
336 | }
337 |
338 | if(validator.isNull(tags)){
339 | ep.emit("create_error","tags","Tags can't be empty");
340 | return;
341 | }
342 |
343 | if(validator.isNull(Markdown)){
344 | ep.emit("create_error","content","Content can't be empty");
345 | return;
346 | }
347 |
348 | var content = marked(Markdown);
349 |
350 | var showNameArray = tags.split(",");
351 | var tagNameArray = [];
352 | var tagArray = [];
353 | showNameArray.forEach(function (showName) {
354 | tagName = showName.toUpperCase();
355 | if(!validator.isNull(tagName) && tagNameArray.indexOf(tagName) == '-1' ){
356 | tagNameArray.push(tagName);
357 | tagArray.push({
358 | tagName:tagName,
359 | showName:showName
360 | });
361 | }
362 | });
363 |
364 | var checkEvent = new eventproxy();
365 | checkEvent.after('checkTag',tagArray.length, function (tagsId) {
366 | var newTopic = {
367 | title : title,
368 | tagsId : tagsId,
369 | Markdown : Markdown,
370 | content : content,
371 | update_at : new Date()
372 | };
373 |
374 | auth.getUserBySession(req,function(user){
375 | if(user){
376 | newTopic.userName = user.userName;
377 | saveTopic(_id,newTopic,req,function(error,topic){
378 | if (error) {
379 | return next(error);
380 | }
381 | if(topic){
382 | var url = config.url.host+"/topic/home";
383 | helps.jsonRedirect(res,url);
384 | }else{
385 | ep.emit("create_error","submit","Error occurred when save topic");
386 | }
387 | });
388 | }else{
389 | ep.emit("create_error","submit","Session is timeout, please login");
390 | }
391 | })
392 |
393 |
394 | });
395 |
396 | tagArray.forEach(function (tag) {
397 |
398 | saveTag(tag,function(error,tag){
399 | if (error) {
400 | return next(error);
401 | }
402 | if(tag){
403 | checkEvent.emit('checkTag', tag._id);
404 | }else{
405 | ep.emit("create_error","tags","Error occurred when save tag");
406 | }
407 | });
408 |
409 | });
410 |
411 |
412 |
413 | }
414 |
415 |
416 | function saveTag(newTag,callback){
417 | Tag.getByName(newTag.tagName,function(error,tag){
418 | if (error) {
419 | return next(error);
420 | }
421 | if(tag){
422 | tag.useCount = tag.useCount+1;
423 | Tag.save(tag,callback);
424 | }else{
425 | Tag.insert(newTag,callback);
426 | }
427 |
428 | });
429 | }
430 |
431 | function saveTopic(_id,newTopic,req,callback){
432 |
433 | Topic.getById(_id,function(error,topic){
434 | if (error) {
435 | return next(error);
436 | }
437 |
438 | if(topic){
439 | if(topic.userName == newTopic.userName){
440 | Topic.update(_id,newTopic,callback)
441 | }else{
442 | var url = config.url.host;
443 | helps.jsonRedirect(res,url);
444 | }
445 |
446 | }else{
447 | Topic.insert(newTopic,callback);
448 | }
449 | });
450 |
451 | }
452 |
--------------------------------------------------------------------------------
/controllers/upload.js:
--------------------------------------------------------------------------------
1 | var auth = require("../middlewares/auth");
2 | var helps = require("../common/helps");
3 | var tools = require("../common/tools");
4 | var config = require("../config");
5 | var multer = require('multer')
6 |
7 |
8 | exports.image = image;
9 |
10 | function image(req,res,next){
11 | auth.getUserBySession(req,function(user){
12 | if (user) {
13 | var d = new Date();
14 | var fileFloder = config.upload.imageFloder+d.getFullYear()+"/"+(d.getMonth()+1)+"/";
15 | var fileName = user.userName+ '-' + d.valueOf()+'.png';
16 | var uploadPath = config.upload.uploadPath;
17 | if(uploadPath == ""||uploadPath==null||undefined){
18 | uploadPath = config.web.appRoot;
19 | }
20 | if(uploadPath == ""||uploadPath==null||undefined){
21 | uploadPath = appRoot;;
22 | }
23 | var filePath = uploadPath+fileFloder;
24 | var fileUrl = fileFloder+fileName;
25 |
26 | tools.mkDirpath(filePath,function(error){
27 | if (error) {
28 | return next(error);
29 | }
30 | var storage = multer.diskStorage({
31 | destination: function (req, file, cb) {
32 | cb(null, filePath)
33 | },
34 | filename: function (req, file, cb) {
35 | cb(null, fileName)
36 | }
37 | })
38 |
39 | var limits = {
40 | fileSize:10*1024*1024
41 | };
42 | var options = {
43 | storage: storage,
44 | limits:limits
45 | };
46 |
47 | var upload = multer(options).single("imageInput");
48 |
49 | upload(req, res, function (error) {
50 | if (error) {
51 | // An error occurred when uploading
52 | console.log("upload error:"+error);
53 | helps.resJsonError(req,res,"upload",error.code);
54 | return;
55 | }
56 | // Everything went fine
57 | helps.resJsonSuccess(req,res,'upload','Successfully',fileUrl);
58 | });
59 | });
60 |
61 |
62 | }else{
63 | helps.resJsonError(req,res,"upload","User is not exists");
64 | }
65 | });
66 |
67 |
68 |
69 | }
70 |
71 |
72 |
--------------------------------------------------------------------------------
/controllers/users.js:
--------------------------------------------------------------------------------
1 | var config = require("../config")
2 | var validator = require('validator');
3 | var eventproxy = require('eventproxy');
4 |
5 | var auth = require("../middlewares/auth");
6 | var tools = require("../common/tools");
7 | var email = require("../common/email");
8 | var helps = require("../common/helps");
9 | var proxy = require('../proxy');
10 | var Users = proxy.Users;
11 | var Topic = proxy.Topic;
12 |
13 | exports.register = register;
14 | exports.login = login;
15 | exports.logout = logout;
16 | exports.active = active;
17 | exports.setting = setting;
18 | exports.home = home;
19 |
20 | function home(req,res,next){
21 | var userName = req.query.userName;
22 |
23 | Users.getByName(userName,function(error,user){
24 | if (error) {
25 | return next(error);
26 | }
27 | if(user){
28 | Topic.getUserTopics(userName,function(error,topics){
29 | if (error) {
30 | return next(error);
31 | }
32 |
33 | var resData = {
34 | title : 'Home',
35 | topics : topics,
36 | user:user
37 | };
38 | res.render('users/home', resData);
39 |
40 | })
41 | }else{
42 | var url = config.url.host;
43 | res.redirect(url);
44 | }
45 | })
46 |
47 | }
48 |
49 | function setting(req,res,next){
50 | var userName = validator.trim(req.body.userName);
51 | var userEmail = validator.trim(req.body.userEmail);
52 | var userAvatar = validator.trim(req.body.userAvatar);
53 |
54 | var ep = new eventproxy();
55 | ep.fail(next);
56 | ep.once('setting_error', function (name,notice) {
57 | helps.resJsonError(req,res,name,notice);
58 | });
59 |
60 |
61 | var checkEvent = new eventproxy();
62 | checkEvent.all('checkName', function (user) {
63 | user.update_at = new Date();
64 | Users.save(user,function(error,user){
65 | if (error) {
66 | return next(error);
67 | }
68 | auth.saveUserSession(req,user);
69 | var url = config.url.host+"/users/setting";
70 | helps.jsonRedirect(res,url);
71 |
72 | });
73 |
74 |
75 | });
76 |
77 |
78 |
79 | Users.getByEmail(userEmail,function(error,originalUser){
80 | if (error) {
81 | return next(error);
82 | }
83 | if (originalUser) {
84 | if(!validator.isNull(userAvatar) && userAvatar != originalUser.userAvatar){
85 | originalUser.userAvatar = userAvatar;
86 | }
87 | if(!validator.isNull(userName) && userName != originalUser.userName){
88 | Users.getByName(userName,function(error,user){
89 | if (error) {
90 | return next(error);
91 | }
92 | if (user) {
93 | ep.emit("setting_error","userName","Name had been registered");
94 | }else{
95 | originalUser.userName = userName;
96 | checkEvent.emit("checkName",originalUser);
97 | }
98 |
99 | });
100 | }else{
101 | checkEvent.emit("checkName",originalUser);
102 | }
103 |
104 | }else{
105 | ep.emit("setting_error","submit","User is not exists");
106 | }
107 | });
108 |
109 |
110 | }
111 |
112 | function active(req,res,next){
113 | var activeKey = validator.trim(req.body.activeKey);
114 | var userEmail = validator.trim(req.body.userEmail);
115 |
116 | var ep = new eventproxy();
117 | ep.fail(next);
118 | ep.once('active_error', function (name,notice) {
119 | helps.resJsonError(req,res,name,notice);
120 | });
121 |
122 | Users.getByEmail(userEmail,function(error,user){
123 | if (error) {
124 | return next(error);
125 | }
126 | if (user) {
127 | var getActiveKey = tools.getActiveKey(user);
128 | if(getActiveKey == activeKey){
129 | var update = {activeState : true,update_at : new Date()};
130 | Users.update(user._id,update,function(error){
131 | if (error) {
132 | return next(error);
133 | }
134 |
135 | helps.resJsonSuccess(req,res,"userEmail","Active successfully , please login !");
136 |
137 | })
138 |
139 | }else{
140 | ep.emit("active_error","userEmail","ActiveKey is not right");
141 | }
142 |
143 | }else{
144 | ep.emit("active_error","userEmail","Email is not exists");
145 | }
146 |
147 | return;
148 |
149 | });
150 | }
151 |
152 |
153 |
154 | function logout(req,res,next){
155 | auth.removeUserSession(req,function(error){
156 | if (error) {
157 | return next(error);
158 | }
159 | var url = config.url.host;
160 | res.redirect(url);
161 | });
162 |
163 | }
164 |
165 | function login(req, res, next){
166 | var userEmail = validator.trim(req.body.userEmail);
167 | var passWord = validator.trim(req.body.passWord);
168 | var ep = new eventproxy();
169 | ep.fail(next);
170 | ep.once('login_error', function (name,notice) {
171 | helps.resJsonError(req,res,name,notice);
172 | });
173 |
174 | if(validator.isNull(userEmail)){
175 | ep.emit("login_error","userEmail","Email can't be empty");
176 | return;
177 | }
178 |
179 | if(validator.isNull(passWord)){
180 | ep.emit("login_error","passWord","Password can't be empty");
181 | return;
182 | }
183 |
184 | Users.getByEmail(userEmail,function(error,user){
185 | if (error) {
186 | return next(error);
187 | }
188 | if (user) {
189 | if(config.auth.checkActive && !user.activeState){
190 | ep.emit("login_error","userEmail","Email is not active , please check your register email");
191 | return;
192 | }
193 |
194 | tools.bcryptCompare(passWord,user.passWord,function(error,state){
195 | if (error) {
196 | return next(error);
197 | }
198 | if(state){
199 | auth.saveUserSession(req,user);
200 | var url = config.url.host;
201 | helps.jsonRedirect(res,url);
202 | }else{
203 | ep.emit("login_error","passWord","Password is not right");
204 | }
205 |
206 | });
207 |
208 | }else{
209 | ep.emit("login_error","userEmail","Email is not exists");
210 | }
211 |
212 | return;
213 |
214 | });
215 |
216 | }
217 |
218 |
219 | function register(req, res, next){
220 | var userName = validator.trim(req.body.userName);
221 | var userEmail = validator.trim(req.body.userEmail);
222 | var passWord = validator.trim(req.body.passWord);
223 | var confirmPassword = validator.trim(req.body.confirmPassword);
224 |
225 | var ep = new eventproxy();
226 | ep.fail(next);
227 | ep.once('register_error', function (name,notice) {
228 | helps.resJsonError(req,res,name,notice);
229 | });
230 |
231 | if(userName.length < 6){
232 | ep.emit("register_error","userName","Name's length is too short");
233 | return;
234 | }
235 |
236 | if(!validator.isEmail(userEmail)){
237 | ep.emit("register_error","userEmail","Email is not right");
238 | return;
239 | }
240 |
241 | if(validator.isNull(passWord) || passWord.length < 6){
242 | ep.emit("register_error","passWord","Password is too shorts");
243 | return;
244 | }
245 |
246 | if(validator.isNull(passWord) || passWord != confirmPassword){
247 | ep.emit("register_error","confirmPassword","ConfirmPassword is not the same as passWord");
248 | return;
249 | }
250 |
251 |
252 | var checkEvent = new eventproxy();
253 | checkEvent.all('checkName','checkEmail', function () {
254 |
255 |
256 | tools.bcryptGenSalt(passWord,function(error, hash){
257 | if (error) {
258 | return next(error);
259 | }
260 |
261 | var newUser = {
262 | userName : userName,
263 | userEmail : userEmail,
264 | passWord : hash,
265 | userAvatar : config.upload.defaultAvatar
266 | };
267 |
268 | Users.insert(newUser,function(error,user){
269 | if (error) {
270 | return next(error);
271 | }
272 |
273 | if (user) {
274 | sendActiveEmail(user);
275 | var url = config.url.host+"/users/login"
276 | helps.jsonRedirect(res,url);
277 | }else{
278 | ep.emit("register_error","userEmail","Error occurred when save user");
279 | }
280 | });
281 |
282 | });
283 |
284 |
285 |
286 | });
287 |
288 | Users.getByEmail(userEmail,function(error,user){
289 | if (error) {
290 | return next(error);
291 | }
292 | if (user) {
293 | ep.emit("register_error","userEmail","Email had been registered");
294 | }else{
295 | checkEvent.emit("checkEmail");
296 | }
297 |
298 | return;
299 |
300 | });
301 |
302 | Users.getByName(userName,function(error,user){
303 |
304 | if (error) {
305 | return next(error);
306 | }
307 | if (user) {
308 | ep.emit("register_error","userName","Name had been registered");
309 | }else{
310 | checkEvent.emit("checkName");
311 | }
312 |
313 | return;
314 |
315 | });
316 | }
317 |
318 |
319 | function sendActiveEmail(user){
320 | var activeKey = tools.getActiveKey(user);
321 |
322 | var activeUrl = config.url.host+"/users/active?activeKey="+activeKey
323 | var from = config.email.from;
324 | var subject = "Active Email from LuckyBird";
325 | var html = "
Wellcome to LuckyBird
This is your active url : "+activeUrl+"
";
326 | email.sendMail(from,user.userEmail,subject,html);
327 | }
328 |
329 |
330 |
331 |
--------------------------------------------------------------------------------
/middlewares/auth.js:
--------------------------------------------------------------------------------
1 | var config = require("../config");
2 | var helps = require("../common/helps");
3 |
4 | exports.saveUserSession = saveUserSession;
5 | exports.removeUserSession = removeUserSession;
6 | exports.getUserBySession = getUserBySession;
7 | exports.requiredLogin = requiredLogin;
8 | exports.getCsrfToken = getCsrfToken;
9 |
10 | function getUserBySession(req,callback){
11 | if(req.session && req.session.user){
12 | return callback(req.session.user);
13 | }else{
14 | return callback(false);;
15 | }
16 | }
17 |
18 | function requiredLogin(req,res,next){
19 | if(req.session && req.session.user){
20 | return next();
21 | }else{
22 | var url = config.url.host;
23 | if(req.xhr){
24 | helps.jsonRedirect(res,url);
25 | }else{
26 | res.redirect(url);
27 | }
28 |
29 | }
30 | }
31 |
32 | function saveUserSession(req,user){
33 | var session = req.session;
34 | session.user = user;
35 | }
36 |
37 | function removeUserSession(req,callback){
38 | req.session.destroy(callback);
39 | }
40 |
41 | function getCsrfToken(req,res,next){
42 | req.session.csrfSecret = undefined;
43 | res.locals.csrfToken = req.csrfToken();
44 | return next();
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/models/base.js:
--------------------------------------------------------------------------------
1 | var tools = require('../common/tools');
2 |
3 | module.exports = function (schema) {
4 | schema.methods.create_at_ago = function () {
5 | return tools.formatDate(this.create_at, true);
6 | };
7 |
8 | schema.methods.update_at_ago = function () {
9 | return tools.formatDate(this.update_at, true);
10 | };
11 | };
--------------------------------------------------------------------------------
/models/comment.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var ObjectId = Schema.ObjectId;
4 | var Base = require("./base");
5 | var CommentSchema = new Schema({
6 | content: { type: String},
7 | userName:{type:String},
8 | topicId:{type:ObjectId},
9 | create_at: { type: Date, default: Date.now },
10 | update_at: { type: Date, default: Date.now }
11 |
12 | });
13 | CommentSchema.plugin(Base);
14 | mongoose.model('Comment', CommentSchema);
--------------------------------------------------------------------------------
/models/index.js:
--------------------------------------------------------------------------------
1 | require('./users');
2 | require('./tag');
3 | require('./topic');
4 | require('./comment');
5 |
6 | var mongoose = require('mongoose');
7 | var config = require('../config');
8 | var database = config.database;
9 |
10 | mongoose.connect(database.address, {
11 | server: {poolSize: 20}
12 | }, function (error) {
13 | if (error) {
14 | console.log('connect to '+database.address+' error: '+error.message);
15 | process.exit(1);
16 | }
17 | });
18 |
19 | exports.Users = mongoose.model('Users');
20 | exports.Tag = mongoose.model('Tag');
21 | exports.Topic = mongoose.model('Topic');
22 | exports.Comment = mongoose.model('Comment');
23 |
24 |
25 |
--------------------------------------------------------------------------------
/models/tag.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 |
4 | var TagSchema = new Schema({
5 | tagName: { type: String},
6 | showName:{type:String},
7 | useCount:{type:Number,default:0},
8 | create_at: { type: Date, default: Date.now },
9 | update_at: { type: Date, default: Date.now }
10 |
11 | });
12 |
13 | TagSchema.index({tagName: 1}, {unique: true});
14 |
15 | mongoose.model('Tag', TagSchema);
--------------------------------------------------------------------------------
/models/topic.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var Base = require("./base");
4 | var TopicSchema = new Schema({
5 | title: { type: String},
6 | content:{type:String},
7 | tagsId:{type:Array},
8 | userName:{type:String},
9 | Markdown:{type:String},
10 | openCount:{type:Number,default:0},
11 | commentCount:{type:Number,default:0},
12 | create_at: { type: Date, default: Date.now },
13 | update_at: { type: Date, default: Date.now }
14 |
15 | });
16 |
17 | TopicSchema.plugin(Base);
18 |
19 | mongoose.model('Topic', TopicSchema);
--------------------------------------------------------------------------------
/models/users.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var Base = require("./base");
4 | var UserSchema = new Schema({
5 | userName: { type: String},
6 | userEmail: { type: String},
7 | passWord: { type: String },
8 | userAvatar:{type:String},
9 | activeState : {type: Boolean, default: false},
10 | create_at: { type: Date, default: Date.now },
11 | update_at: { type: Date, default: Date.now }
12 |
13 | });
14 |
15 | UserSchema.index({userName: 1}, {unique: true});
16 | UserSchema.index({userEmail: 1}, {unique: true});
17 | UserSchema.plugin(Base);
18 |
19 | mongoose.model('Users', UserSchema);
20 |
21 |
--------------------------------------------------------------------------------
/npm-debug.log:
--------------------------------------------------------------------------------
1 | 0 info it worked if it ends with ok
2 | 1 verbose cli [ '/usr/bin/nodejs', '/usr/bin/npm', 'start' ]
3 | 2 info using npm@2.14.12
4 | 3 info using node@v4.2.4
5 | 4 verbose run-script [ 'prestart', 'start', 'poststart' ]
6 | 5 info prestart myExpressApp@0.0.0
7 | 6 info start myExpressApp@0.0.0
8 | 7 verbose unsafe-perm in lifecycle true
9 | 8 info myExpressApp@0.0.0 Failed to exec start script
10 | 9 verbose stack Error: myExpressApp@0.0.0 start: `node ./bin/www`
11 | 9 verbose stack Exit status 1
12 | 9 verbose stack at EventEmitter. (/usr/lib/node_modules/npm/lib/utils/lifecycle.js:214:16)
13 | 9 verbose stack at emitTwo (events.js:87:13)
14 | 9 verbose stack at EventEmitter.emit (events.js:172:7)
15 | 9 verbose stack at ChildProcess. (/usr/lib/node_modules/npm/lib/utils/spawn.js:24:14)
16 | 9 verbose stack at emitTwo (events.js:87:13)
17 | 9 verbose stack at ChildProcess.emit (events.js:172:7)
18 | 9 verbose stack at maybeClose (internal/child_process.js:818:16)
19 | 9 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:211:5)
20 | 10 verbose pkgid myExpressApp@0.0.0
21 | 11 verbose cwd /mnt/hgfs/ShareFolder/web/NodeJS-learn-forum
22 | 12 error Linux 3.19.0-25-generic
23 | 13 error argv "/usr/bin/nodejs" "/usr/bin/npm" "start"
24 | 14 error node v4.2.4
25 | 15 error npm v2.14.12
26 | 16 error code ELIFECYCLE
27 | 17 error myExpressApp@0.0.0 start: `node ./bin/www`
28 | 17 error Exit status 1
29 | 18 error Failed at the myExpressApp@0.0.0 start script 'node ./bin/www'.
30 | 18 error This is most likely a problem with the myExpressApp package,
31 | 18 error not with npm itself.
32 | 18 error Tell the author that this fails on your system:
33 | 18 error node ./bin/www
34 | 18 error You can get their info via:
35 | 18 error npm owner ls myExpressApp
36 | 18 error There is likely additional logging output above.
37 | 19 verbose exit [ 1, true ]
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "myExpressApp",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "node ./bin/www"
7 | },
8 | "dependencies": {
9 | "bcrypt": "^0.8.5",
10 | "body-parser": "~1.13.2",
11 | "connect-mongo": "~1.1.0",
12 | "cookie-parser": "~1.3.5",
13 | "csurf": "^1.8.3",
14 | "debug": "~2.2.0",
15 | "eventproxy": "~0.3.4",
16 | "express": "~4.13.1",
17 | "express-session": "~1.12.1",
18 | "marked": "^0.3.5",
19 | "mkdirp": "^0.5.1",
20 | "moment": "~2.10.6",
21 | "mongoose": "~4.3.3",
22 | "morgan": "~1.6.1",
23 | "multer": "^1.1.0",
24 | "nodemailer": "^1.10.0",
25 | "serve-favicon": "~2.3.0",
26 | "utility": "^1.6.0",
27 | "validator": "~4.4.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/proxy/comment.js:
--------------------------------------------------------------------------------
1 | var models = require('../models');
2 | var Comment = models.Comment;
3 |
4 |
5 |
6 | exports.insert = function(comment,callback){
7 | var newComment = new Comment(comment);
8 | newComment.save(callback);
9 | }
10 |
11 | exports.save = function(comment,callback){
12 | comment.save(callback);
13 | }
14 |
15 | exports.getById = function(_id,callback){
16 | Comment.findById(_id, callback);
17 | }
18 |
19 | exports.getTopicComments = function(topicId,sort,callback){
20 | Comment.find({topicId:topicId},'',{sort:sort}, callback);
21 | }
22 |
23 |
24 |
--------------------------------------------------------------------------------
/proxy/index.js:
--------------------------------------------------------------------------------
1 | exports.Users = require('./users');
2 | exports.Tag = require('./tag');
3 | exports.Topic = require('./topic');
4 | exports.Comment = require('./comment');
5 |
--------------------------------------------------------------------------------
/proxy/tag.js:
--------------------------------------------------------------------------------
1 | var models = require('../models');
2 | var Tag = models.Tag;
3 |
4 |
5 | exports.getByName = function (tagName,callback){
6 | Tag.findOne({tagName: tagName}, callback);
7 | };
8 |
9 | exports.insert = function(tag,callback){
10 | var newTag = new Tag(tag);
11 | newTag.save(callback);
12 | }
13 |
14 | exports.save = function(tag,callback){
15 | tag.save(callback);
16 | }
17 |
18 | exports.update = function(_id,update,callback){
19 | Tag.update({_id:_id},{$set:update},callback);
20 | }
21 |
22 | exports.getAllTags = function(callback){
23 | Tag.find({},callback);
24 | }
25 |
26 | exports.getById = function(_id,callback){
27 | Tag.findById(_id, callback);
28 | }
29 |
30 | exports.getByPage = function(query,fields,skip,limit,sort,callback){
31 | Tag.find(query, fields, { skip: skip, limit: limit,sort:sort }, callback);
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/proxy/topic.js:
--------------------------------------------------------------------------------
1 | var models = require('../models');
2 | var Topic = models.Topic;
3 |
4 |
5 | exports.insert = function(topic,callback){
6 | var newTopic = new Topic(topic);
7 | newTopic.save(callback);
8 | }
9 |
10 | exports.save = function(topic,callback){
11 | Topic.save(callback);
12 | }
13 |
14 | exports.update = function(_id,update,callback){
15 | Topic.update({_id:_id},{$set:update},callback);
16 | }
17 |
18 | exports.getByPage = function(query,fields,skip,limit,sort,callback){
19 | Topic.find(query, fields, { skip: skip, limit: limit,sort:sort}, callback);
20 | }
21 |
22 | exports.getById = function(_id,callback){
23 | Topic.findById(_id, callback);
24 | }
25 |
26 | exports.getUserTopics = function(userName,callback){
27 | Topic.find({userName:userName}, callback);
28 | }
29 |
--------------------------------------------------------------------------------
/proxy/users.js:
--------------------------------------------------------------------------------
1 | var models = require('../models');
2 | var Users = models.Users;
3 |
4 |
5 |
6 | exports.insert = function (user,callback) {
7 | var newUser = new Users(user);
8 | newUser.save(callback);
9 | };
10 |
11 | exports.save = function(user,callback){
12 | user.save(callback);
13 | }
14 |
15 | exports.update = function(_id,update,callback){
16 | Users.update({_id:_id},{$set:update},callback);
17 | }
18 |
19 | exports.getByEmail = function (userEmail,callback){
20 | Users.findOne({userEmail: userEmail}, callback);
21 | };
22 |
23 | exports.getByName = function (userName,callback){
24 | Users.findOne({userName: userName}, callback);
25 | };
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/public/images/default-avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/images/default-avatar.png
--------------------------------------------------------------------------------
/public/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/images/loading.gif
--------------------------------------------------------------------------------
/public/javascripts/ajax-upload.js:
--------------------------------------------------------------------------------
1 | jQuery.extend({
2 | createUploadIframe: function(id, uri)
3 | {
4 | //create frame
5 | var frameId = 'jUploadFrame' + id;
6 | var iframeHtml = '';
19 | jQuery(iframeHtml).appendTo(document.body);
20 |
21 | return jQuery('#' + frameId).get(0);
22 | },
23 | createUploadForm: function(id,fileElementId,data,fileElement)
24 | {
25 | //create form
26 | var formId = 'jUploadForm' + id;
27 | var fileId = 'jUploadFile' + id;
28 | var form = jQuery('');
29 | if(data)
30 | {
31 | for(var i in data)
32 | {
33 | jQuery(' ').appendTo(form);
34 | }
35 | }
36 |
37 | // 上传多个文件
38 | if(typeof(fileElementId) == 'string'){
39 | fileElementId = [fileElementId];
40 | }
41 | for(var i in fileElementId){
42 | var oldElement = jQuery('#' + fileElementId[i]);
43 | var newElement = jQuery(oldElement).clone();
44 | jQuery(oldElement).attr('id', fileId);
45 | jQuery(oldElement).before(newElement);
46 | jQuery(oldElement).appendTo(form);
47 | }
48 |
49 |
50 | /*
51 | var oldElement;
52 | if(fileElement == null)
53 | oldElement = jQuery('#' + fileElementId);
54 | else
55 | oldElement = fileElement;
56 |
57 | var newElement = jQuery(oldElement).clone();
58 | jQuery(oldElement).attr('id', fileId);
59 | jQuery(oldElement).before(newElement);
60 | jQuery(oldElement).appendTo(form);
61 | */
62 |
63 |
64 | //set attributes
65 | jQuery(form).css('position', 'absolute');
66 | jQuery(form).css('top', '-1200px');
67 | jQuery(form).css('left', '-1200px');
68 | jQuery(form).appendTo('body');
69 | return form;
70 | },
71 |
72 | ajaxFileUpload: function(s) {
73 | // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout
74 | s = jQuery.extend({}, jQuery.ajaxSettings, s);
75 | var id = new Date().getTime()
76 | var form = jQuery.createUploadForm(id, s.fileElementId, (typeof(s.data)=='undefined'?false:s.data),s.fileElement);
77 | var io = jQuery.createUploadIframe(id, s.secureuri);
78 | var frameId = 'jUploadFrame' + id;
79 | var formId = 'jUploadForm' + id;
80 | // Watch for a new set of requests
81 | if ( s.global && ! jQuery.active++ )
82 | {
83 | jQuery.event.trigger( "ajaxStart" );
84 | }
85 | var requestDone = false;
86 | // Create the request object
87 | var xml = {}
88 | if ( s.global )
89 | jQuery.event.trigger("ajaxSend", [xml, s]);
90 | // Wait for a response to come back
91 | var uploadCallback = function(isTimeout)
92 | {
93 | var io = document.getElementById(frameId);
94 | try
95 | {
96 | if(io.contentWindow)
97 | {
98 | xml.responseText = io.contentWindow.document.body?io.contentWindow.document.body.innerHTML:null;
99 | xml.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document;
100 |
101 | }else if(io.contentDocument)
102 | {
103 | xml.responseText = io.contentDocument.document.body?io.contentDocument.document.body.innerHTML:null;
104 | xml.responseXML = io.contentDocument.document.XMLDocument?io.contentDocument.document.XMLDocument:io.contentDocument.document;
105 | }
106 | }catch(e)
107 | {
108 | jQuery.handleError(s, xml, null, e,"1");
109 | }
110 | if ( xml || isTimeout == "timeout")
111 | {
112 | requestDone = true;
113 | var status;
114 | try {
115 | status = isTimeout != "timeout" ? "success" : "error";
116 | // Make sure that the request was successful or notmodified
117 | if ( status != "error" )
118 | {
119 | // process the data (runs the xml through httpData regardless of callback)
120 | var data = jQuery.uploadHttpData( xml, s.dataType );
121 | // If a local callback was specified, fire it and pass it the data
122 | if ( s.success )
123 | s.success( data, status );
124 |
125 | // Fire the global callback
126 | if( s.global )
127 | jQuery.event.trigger( "ajaxSuccess", [xml, s] );
128 | } else
129 | jQuery.handleError(s, xml, status,"2");
130 | } catch(e)
131 | {
132 | status = "error";
133 | jQuery.handleError(s, xml, status, e,"3");
134 | }
135 |
136 | // The request was completed
137 | if( s.global )
138 | jQuery.event.trigger( "ajaxComplete", [xml, s] );
139 |
140 | // Handle the global AJAX counter
141 | if ( s.global && ! --jQuery.active )
142 | jQuery.event.trigger( "ajaxStop" );
143 |
144 | // Process result
145 | if ( s.complete )
146 | s.complete(xml, status);
147 |
148 | jQuery(io).unbind()
149 |
150 | setTimeout(function()
151 | { try
152 | {
153 | jQuery(io).remove();
154 | jQuery(form).remove();
155 |
156 | } catch(e)
157 | {
158 | jQuery.handleError(s, xml, null, e,"4");
159 | }
160 |
161 | }, 100)
162 |
163 | xml = null
164 |
165 | }
166 | }
167 | // Timeout checker
168 | if ( s.timeout > 0 )
169 | {
170 | setTimeout(function(){
171 | // Check to see if the request is still happening
172 | if( !requestDone ) uploadCallback( "timeout" );
173 | }, s.timeout);
174 | }
175 | try
176 | {
177 |
178 | var form = jQuery('#' + formId);
179 | jQuery(form).attr('action', s.url);
180 | jQuery(form).attr('method', 'POST');
181 | jQuery(form).attr('target', frameId);
182 | if(form.encoding)
183 | {
184 | jQuery(form).attr('encoding', 'multipart/form-data');
185 | }
186 | else
187 | {
188 | jQuery(form).attr('enctype', 'multipart/form-data');
189 | }
190 | jQuery(form).submit();
191 |
192 | } catch(e)
193 | {
194 | jQuery.handleError(s, xml, null, e,"5");
195 | }
196 |
197 | jQuery('#' + frameId).load(uploadCallback);
198 | return {abort: function(){
199 | try
200 | {
201 | jQuery('#' + frameId).remove();
202 | jQuery(form).remove();
203 | }
204 | catch(e){}
205 | }};
206 | },
207 |
208 | uploadHttpData: function( r, type ) {
209 | var data = !type;
210 |
211 | data = type == "xml" || data ? r.responseXML : r.responseText;
212 |
213 | //json error
214 | var reg = /(.+)<\/pre>/g;
215 | var result = data.match(reg);
216 | data = RegExp.$1;
217 |
218 |
219 | // If the type is "script", eval it in global context
220 | if ( type == "script" )
221 | jQuery.globalEval( data );
222 |
223 | // Get the JavaScript object, if JSON is used.
224 | if ( type == "json" )
225 | eval( "data = " + data );
226 |
227 | // evaluate scripts within html
228 | if ( type == "html" )
229 | jQuery("").html(data).evalScripts();
230 |
231 | return data;
232 | },
233 |
234 | handleError: function( s, xml, status, e ,w) {
235 | // If a local callback was specified, fire it
236 | if ( s.error )
237 | s.error( xml, status, e ,w);
238 |
239 | // Fire the global callback
240 | if ( s.global )
241 | jQuery.event.trigger( "ajaxError", [xml, s, e] );
242 | }
243 | });
--------------------------------------------------------------------------------
/public/javascripts/common.js:
--------------------------------------------------------------------------------
1 | $(function(){
2 | init();
3 | })
4 |
5 | function init(){
6 |
7 | var registerObj = $("#registerForm");
8 | if(registerObj.length > 0 ){
9 | registerObj.submit(function(e) {
10 | ajaxPost(e,registerObj,"/users/register");
11 | });
12 | }
13 |
14 | var loginObj = $("#loginForm");
15 | if(loginObj.length > 0 ){
16 | loginObj.submit(function(e) {
17 | ajaxPost(e,loginObj,"/users/login");
18 | });
19 | }
20 |
21 |
22 | var activeObj = $("#activeForm");
23 | if(activeObj.length > 0 ){
24 | activeObj.submit(function(e) {
25 | ajaxPost(e,activeObj,"/users/active");
26 | });
27 | }
28 |
29 | var settingObj = $("#settingForm");
30 | if(settingObj.length > 0 ){
31 | settingObj.submit(function(e) {
32 | ajaxPost(e,settingObj,"/users/setting");
33 | });
34 | }
35 |
36 | var createObj = $("#createForm");
37 | if(createObj.length > 0 ){
38 | createObj.submit(function(e) {
39 | ajaxPost(e,createObj,"/topic/create");
40 | });
41 | }
42 |
43 | var commentObj = $("#commentForm");
44 | if(commentObj.length > 0 ){
45 | commentObj.submit(function(e) {
46 | ajaxPost(e,commentObj,"/topic/comment");
47 | });
48 | }
49 |
50 | var uploadUserAvatar = $("#uploadUserAvatar");
51 | if(uploadUserAvatar.length > 0 ){
52 |
53 | uploadUserAvatar.click(function(){
54 | $("#imageInput").click();
55 | });
56 |
57 | changeUploadImage()
58 | }
59 |
60 |
61 | // init editor
62 | var editorObj = $("#editor");
63 |
64 | if(editorObj.length > 0){
65 |
66 | var editor = new Editor({
67 | element: editorObj.get(0),
68 | status:false
69 | });
70 | editor.render();
71 |
72 | $("#imageLocalButton").click(function(){
73 | return $("#imageInput").click();
74 | });
75 |
76 | changeUploadImage();
77 |
78 | }
79 |
80 |
81 | var tagObj = $("#tags-container")
82 |
83 | if( tagObj.length > 0 ){
84 | var hasTags = $("#hasTags").text();
85 | var option = {
86 | singleField: true,
87 | singleFieldNode: $('#tags')
88 | };
89 | if(hasTags.length > 0){
90 | var availableTags = hasTags.split(",");
91 | option.availableTags = availableTags;
92 | }
93 |
94 | tagObj.tagit(option);
95 | }
96 |
97 | var showTags = $("#showTags");
98 | if(showTags.length > 0 ){
99 | var url = "/topic/getTags";
100 | getSidebarTags(showTags,url);
101 | }
102 |
103 | var showComments = $("#showComment");
104 | var topicId = $("#topicId").val();
105 | if(showComments.length > 0 ){
106 | var url = "/topic/getComments?topicId="+topicId;
107 | getTopicComments(showComments,url);
108 | }
109 |
110 | var csrfToken = $("meta[name='csrfToken']").attr('content');
111 | var setCSRFToken = function (csrfToken) {
112 | $(document).ajaxSend(function (event,xhr,options) {
113 | var type = options.type.toUpperCase();
114 | if (type == 'POST') {
115 | if(xhr.setRequestHeader){
116 | xhr.setRequestHeader('csrf-token', csrfToken);
117 | }else{
118 | options.data['_csrf'] = csrfToken;
119 |
120 | if(options.url.indexOf("?") == -1){
121 | options.url = options.url+"?_csrf="+csrfToken;
122 | }else{
123 | options.url = options.url+"&_csrf="+csrfToken;
124 | }
125 | }
126 | }
127 | });
128 | };
129 |
130 | setCSRFToken(csrfToken);
131 |
132 | }
133 |
134 | function getTopicComments(obj,url){
135 | $.ajax({
136 | type: "get",
137 | url: url,
138 | timeout : 10000,
139 | dataType: "json",
140 | success: function(data){
141 | var showComments = data.showComments;
142 | var str="";
143 | showComments.forEach(function(comment){
144 | str+="";
151 |
152 | });
153 | obj.html(str);
154 | },
155 | error:function(error) {
156 |
157 | },
158 | complete : function(XMLHttpRequest,status){
159 | }
160 | });
161 | }
162 |
163 | function getSidebarTags(obj,url){
164 | $.ajax({
165 | type: "get",
166 | url: url,
167 | timeout : 10000,
168 | dataType: "json",
169 | success: function(data){
170 | var showTags = data.showTags;
171 | var str="";
172 | var tagStyle = ["btn-info","btn-success","btn-warning","btn-disabled"];
173 | var num = 0;
174 | showTags.forEach(function(tag){
175 | num = parseInt(3*Math.random());
176 | str+="
"
177 | str+=""+tag.showName+" ";
178 | str+=" "
179 | });
180 | obj.html(str);
181 | },
182 | error:function(error) {
183 |
184 | },
185 | complete : function(XMLHttpRequest,status){
186 | }
187 | });
188 | }
189 |
190 | function ajaxPost(e,obj,url){
191 |
192 | hideAlertNotice();
193 |
194 | showBtnLoading();
195 |
196 | var data = obj.serialize();
197 | $.ajax({
198 | type: "POST",
199 | url: url,
200 | timeout : 10000,
201 | dataType: "json",
202 | data: data, // serializes the form's elements.
203 |
204 | success: function(data){
205 | if(data.error){
206 | showAlertNotice(data.error);
207 | }else if(data.success){
208 | showSuccessNotice(data.success);
209 | }else if(data.redirect){
210 | location.href = data.redirect;
211 | };
212 |
213 | },
214 | error:function(error) {
215 |
216 | },
217 | complete : function(XMLHttpRequest,status){
218 | hideBtnLoading();
219 | }
220 | });
221 | e.preventDefault(); // avoid to execute the actual submit of the form.
222 |
223 | }
224 |
225 | function showBtnLoading(){
226 | var btn = $(":submit");
227 | btn.attr("data-loading-text","Going...");
228 | btn.button("loading");
229 | }
230 |
231 | function hideBtnLoading(){
232 | var btn = $(":submit");
233 | btn.button("reset");
234 | }
235 |
236 |
237 | function hideAlertNotice(){
238 | $(".alert-required").each(function(){
239 | var value = $(this).hide();
240 | });
241 | }
242 |
243 | function showAlertNotice(data){
244 | $("#"+data.name+"Alert").html(data.notice).show();
245 | }
246 |
247 | function showSuccessNotice(data){
248 | $("#"+data.name+"Success").html(data.notice).show();
249 | }
250 |
251 |
252 | function changeUploadImage(){
253 | $("#imageInput").off("change");
254 | $("#imageInput").on("change", function () {
255 | ajaxUploadImage();
256 | });
257 | }
258 |
259 |
260 | function ajaxUploadImage(){
261 | hideAlertNotice();
262 | showBtnLoading();
263 | var url = "/upload/image";
264 |
265 | $.ajaxFileUpload({
266 | url:url,
267 | timeout:60000,
268 | secureuri:false,
269 | fileElementId:["imageInput"],
270 | dataType:"json",
271 | type : "post",
272 | data : {
273 | test:'test'
274 | },
275 | success:function(data, status ){
276 | if(data.error){
277 | showAlertNotice(data.error);
278 | }else if(data.success){
279 | if($("#avatar").length > 0 ){
280 | $("#avatar").attr("src",data.success.url);
281 | $("#userAvatar").val(data.success.url);
282 | };
283 | if($("#imageUrl").length > 0)
284 | {
285 | $("#imageUrl").val(data.success.url);
286 | }
287 |
288 | }
289 | },
290 | error:function(data, status, e ,w){
291 |
292 | },
293 |
294 | complete:function(data, status){
295 | hideBtnLoading();
296 | changeUploadImage();
297 | }
298 |
299 |
300 |
301 | });
302 | }
303 |
--------------------------------------------------------------------------------
/public/javascripts/marked.js:
--------------------------------------------------------------------------------
1 | /**
2 | * marked - a markdown parser
3 | * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
4 | * https://github.com/chjj/marked
5 | */
6 | (function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/
])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i.5){ch="x"+ch.toString(16)}out+=""+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return""+(escaped?code:escape(code,true))+"\n
"}return''+(escaped?code:escape(code,true))+"\n
\n"};Renderer.prototype.blockquote=function(quote){return"\n"+quote+" \n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"\n"};Renderer.prototype.hr=function(){return this.options.xhtml?" \n":" \n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+""+type+">\n"};Renderer.prototype.listitem=function(text){return""+text+" \n"};Renderer.prototype.paragraph=function(text){return""+text+"
\n"};Renderer.prototype.table=function(header,body){return"\n"+"\n"+header+" \n"+"\n"+body+" \n"+"
\n"};Renderer.prototype.tablerow=function(content){return"\n"+content+" \n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+""+type+">\n"};Renderer.prototype.strong=function(text){return""+text+" "};Renderer.prototype.em=function(text){return""+text+" "};Renderer.prototype.codespan=function(text){return""+text+"
"};Renderer.prototype.br=function(){return this.options.xhtml?" ":" "};Renderer.prototype.del=function(text){return""+text+""};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0||prot.indexOf("vbscript:")===0){return""}}var out='"+text+" ";return out};Renderer.prototype.image=function(href,title,text){var out=' ":">";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i /g,">").replace(/"/g,""").replace(/'/g,"'")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;iAn error occured:"+escape(e.message+"",true)+" "}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}());
--------------------------------------------------------------------------------
/public/javascripts/tag-it.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery UI Tag-it!
3 | *
4 | * @version v2.0 (06/2011)
5 | *
6 | * Copyright 2011, Levy Carneiro Jr.
7 | * Released under the MIT license.
8 | * http://aehlke.github.com/tag-it/LICENSE
9 | *
10 | * Homepage:
11 | * http://aehlke.github.com/tag-it/
12 | *
13 | * Authors:
14 | * Levy Carneiro Jr.
15 | * Martin Rehfeld
16 | * Tobias Schmidt
17 | * Skylar Challand
18 | * Alex Ehlke
19 | *
20 | * Maintainer:
21 | * Alex Ehlke - Twitter: @aehlke
22 | *
23 | * Dependencies:
24 | * jQuery v1.4+
25 | * jQuery UI v1.8+
26 | */
27 | (function($) {
28 |
29 | $.widget('ui.tagit', {
30 | options: {
31 | allowDuplicates : false,
32 | caseSensitive : true,
33 | fieldName : 'tags',
34 | placeholderText : null, // Sets `placeholder` attr on input field.
35 | readOnly : false, // Disables editing.
36 | removeConfirmation: false, // Require confirmation to remove tags.
37 | tagLimit : null, // Max number of tags allowed (null for unlimited).
38 |
39 | // Used for autocomplete, unless you override `autocomplete.source`.
40 | availableTags : [],
41 |
42 | // Use to override or add any options to the autocomplete widget.
43 | //
44 | // By default, autocomplete.source will map to availableTags,
45 | // unless overridden.
46 | autocomplete: {},
47 |
48 | // Shows autocomplete before the user even types anything.
49 | showAutocompleteOnFocus: false,
50 |
51 | // When enabled, quotes are unneccesary for inputting multi-word tags.
52 | allowSpaces: false,
53 |
54 | // The below options are for using a single field instead of several
55 | // for our form values.
56 | //
57 | // When enabled, will use a single hidden field for the form,
58 | // rather than one per tag. It will delimit tags in the field
59 | // with singleFieldDelimiter.
60 | //
61 | // The easiest way to use singleField is to just instantiate tag-it
62 | // on an INPUT element, in which case singleField is automatically
63 | // set to true, and singleFieldNode is set to that element. This
64 | // way, you don't need to fiddle with these options.
65 | singleField: false,
66 |
67 | // This is just used when preloading data from the field, and for
68 | // populating the field with delimited tags as the user adds them.
69 | singleFieldDelimiter: ',',
70 |
71 | // Set this to an input DOM node to use an existing form field.
72 | // Any text in it will be erased on init. But it will be
73 | // populated with the text of tags as they are created,
74 | // delimited by singleFieldDelimiter.
75 | //
76 | // If this is not set, we create an input node for it,
77 | // with the name given in settings.fieldName.
78 | singleFieldNode: null,
79 |
80 | // Whether to animate tag removals or not.
81 | animate: true,
82 |
83 | // Optionally set a tabindex attribute on the input that gets
84 | // created for tag-it.
85 | tabIndex: null,
86 |
87 | // Event callbacks.
88 | beforeTagAdded : null,
89 | afterTagAdded : null,
90 |
91 | beforeTagRemoved : null,
92 | afterTagRemoved : null,
93 |
94 | onTagClicked : null,
95 | onTagLimitExceeded : null,
96 |
97 |
98 | // DEPRECATED:
99 | //
100 | // /!\ These event callbacks are deprecated and WILL BE REMOVED at some
101 | // point in the future. They're here for backwards-compatibility.
102 | // Use the above before/after event callbacks instead.
103 | onTagAdded : null,
104 | onTagRemoved: null,
105 | // `autocomplete.source` is the replacement for tagSource.
106 | tagSource: null
107 | // Do not use the above deprecated options.
108 | },
109 |
110 | _create: function() {
111 | // for handling static scoping inside callbacks
112 | var that = this;
113 |
114 | // There are 2 kinds of DOM nodes this widget can be instantiated on:
115 | // 1. UL, OL, or some element containing either of these.
116 | // 2. INPUT, in which case 'singleField' is overridden to true,
117 | // a UL is created and the INPUT is hidden.
118 | if (this.element.is('input')) {
119 | this.tagList = $('').insertAfter(this.element);
120 | this.options.singleField = true;
121 | this.options.singleFieldNode = this.element;
122 | this.element.addClass('tagit-hidden-field');
123 | } else {
124 | this.tagList = this.element.find('ul, ol').andSelf().last();
125 | }
126 |
127 | this.tagInput = $(' ').addClass('ui-widget-content');
128 |
129 | if (this.options.readOnly) this.tagInput.attr('disabled', 'disabled');
130 |
131 | if (this.options.tabIndex) {
132 | this.tagInput.attr('tabindex', this.options.tabIndex);
133 | }
134 |
135 | if (this.options.placeholderText) {
136 | this.tagInput.attr('placeholder', this.options.placeholderText);
137 | }
138 |
139 | if (!this.options.autocomplete.source) {
140 | this.options.autocomplete.source = function(search, showChoices) {
141 | var filter = search.term.toLowerCase();
142 | var choices = $.grep(this.options.availableTags, function(element) {
143 | // Only match autocomplete options that begin with the search term.
144 | // (Case insensitive.)
145 | return (element.toLowerCase().indexOf(filter) === 0);
146 | });
147 | if (!this.options.allowDuplicates) {
148 | choices = this._subtractArray(choices, this.assignedTags());
149 | }
150 | showChoices(choices);
151 | };
152 | }
153 |
154 | if (this.options.showAutocompleteOnFocus) {
155 | this.tagInput.focus(function(event, ui) {
156 | that._showAutocomplete();
157 | });
158 |
159 | if (typeof this.options.autocomplete.minLength === 'undefined') {
160 | this.options.autocomplete.minLength = 0;
161 | }
162 | }
163 |
164 | // Bind autocomplete.source callback functions to this context.
165 | if ($.isFunction(this.options.autocomplete.source)) {
166 | this.options.autocomplete.source = $.proxy(this.options.autocomplete.source, this);
167 | }
168 |
169 | // DEPRECATED.
170 | if ($.isFunction(this.options.tagSource)) {
171 | this.options.tagSource = $.proxy(this.options.tagSource, this);
172 | }
173 |
174 | this.tagList
175 | .addClass('tagit')
176 | .addClass('ui-widget ui-widget-content ui-corner-all')
177 | // Create the input field.
178 | .append($(' ').append(this.tagInput))
179 | .click(function(e) {
180 | var target = $(e.target);
181 | if (target.hasClass('tagit-label')) {
182 | var tag = target.closest('.tagit-choice');
183 | if (!tag.hasClass('removed')) {
184 | that._trigger('onTagClicked', e, {tag: tag, tagLabel: that.tagLabel(tag)});
185 | }
186 | } else {
187 | // Sets the focus() to the input field, if the user
188 | // clicks anywhere inside the UL. This is needed
189 | // because the input field needs to be of a small size.
190 | that.tagInput.focus();
191 | }
192 | });
193 |
194 | // Single field support.
195 | var addedExistingFromSingleFieldNode = false;
196 | if (this.options.singleField) {
197 | if (this.options.singleFieldNode) {
198 | // Add existing tags from the input field.
199 | var node = $(this.options.singleFieldNode);
200 | var tags = node.val().split(this.options.singleFieldDelimiter);
201 | node.val('');
202 | $.each(tags, function(index, tag) {
203 | that.createTag(tag, null, true);
204 | addedExistingFromSingleFieldNode = true;
205 | });
206 | } else {
207 | // Create our single field input after our list.
208 | this.options.singleFieldNode = $(' ');
209 | this.tagList.after(this.options.singleFieldNode);
210 | }
211 | }
212 |
213 | // Add existing tags from the list, if any.
214 | if (!addedExistingFromSingleFieldNode) {
215 | this.tagList.children('li').each(function() {
216 | if (!$(this).hasClass('tagit-new')) {
217 | that.createTag($(this).text(), $(this).attr('class'), true);
218 | $(this).remove();
219 | }
220 | });
221 | }
222 |
223 | // Events.
224 | this.tagInput
225 | .keydown(function(event) {
226 | // Backspace is not detected within a keypress, so it must use keydown.
227 | if (event.which == $.ui.keyCode.BACKSPACE && that.tagInput.val() === '') {
228 | var tag = that._lastTag();
229 | if (!that.options.removeConfirmation || tag.hasClass('remove')) {
230 | // When backspace is pressed, the last tag is deleted.
231 | that.removeTag(tag);
232 | } else if (that.options.removeConfirmation) {
233 | tag.addClass('remove ui-state-highlight');
234 | }
235 | } else if (that.options.removeConfirmation) {
236 | that._lastTag().removeClass('remove ui-state-highlight');
237 | }
238 |
239 | // Comma/Space/Enter are all valid delimiters for new tags,
240 | // except when there is an open quote or if setting allowSpaces = true.
241 | // Tab will also create a tag, unless the tag input is empty,
242 | // in which case it isn't caught.
243 | if (
244 | (event.which === $.ui.keyCode.COMMA && event.shiftKey === false) ||
245 | event.which === $.ui.keyCode.ENTER ||
246 | (
247 | event.which == $.ui.keyCode.TAB &&
248 | that.tagInput.val() !== ''
249 | ) ||
250 | (
251 | event.which == $.ui.keyCode.SPACE &&
252 | that.options.allowSpaces !== true &&
253 | (
254 | $.trim(that.tagInput.val()).replace( /^s*/, '' ).charAt(0) != '"' ||
255 | (
256 | $.trim(that.tagInput.val()).charAt(0) == '"' &&
257 | $.trim(that.tagInput.val()).charAt($.trim(that.tagInput.val()).length - 1) == '"' &&
258 | $.trim(that.tagInput.val()).length - 1 !== 0
259 | )
260 | )
261 | )
262 | ) {
263 | // Enter submits the form if there's no text in the input.
264 | if (!(event.which === $.ui.keyCode.ENTER && that.tagInput.val() === '')) {
265 | event.preventDefault();
266 | }
267 |
268 | // Autocomplete will create its own tag from a selection and close automatically.
269 | if (!(that.options.autocomplete.autoFocus && that.tagInput.data('autocomplete-open'))) {
270 | that.tagInput.autocomplete('close');
271 | that.createTag(that._cleanedInput());
272 | }
273 | }
274 | }).blur(function(e){
275 | // Create a tag when the element loses focus.
276 | // If autocomplete is enabled and suggestion was clicked, don't add it.
277 | if (!that.tagInput.data('autocomplete-open')) {
278 | that.createTag(that._cleanedInput());
279 | }
280 | });
281 |
282 | // Autocomplete.
283 | if (this.options.availableTags || this.options.tagSource || this.options.autocomplete.source) {
284 | var autocompleteOptions = {
285 | select: function(event, ui) {
286 | that.createTag(ui.item.value);
287 | // Preventing the tag input to be updated with the chosen value.
288 | return false;
289 | }
290 | };
291 | $.extend(autocompleteOptions, this.options.autocomplete);
292 |
293 | // tagSource is deprecated, but takes precedence here since autocomplete.source is set by default,
294 | // while tagSource is left null by default.
295 | autocompleteOptions.source = this.options.tagSource || autocompleteOptions.source;
296 |
297 | this.tagInput.autocomplete(autocompleteOptions).bind('autocompleteopen.tagit', function(event, ui) {
298 | that.tagInput.data('autocomplete-open', true);
299 | }).bind('autocompleteclose.tagit', function(event, ui) {
300 | that.tagInput.data('autocomplete-open', false);
301 | });
302 |
303 | this.tagInput.autocomplete('widget').addClass('tagit-autocomplete');
304 | }
305 | },
306 |
307 | destroy: function() {
308 | $.Widget.prototype.destroy.call(this);
309 |
310 | this.element.unbind('.tagit');
311 | this.tagList.unbind('.tagit');
312 |
313 | this.tagInput.removeData('autocomplete-open');
314 |
315 | this.tagList.removeClass([
316 | 'tagit',
317 | 'ui-widget',
318 | 'ui-widget-content',
319 | 'ui-corner-all',
320 | 'tagit-hidden-field'
321 | ].join(' '));
322 |
323 | if (this.element.is('input')) {
324 | this.element.removeClass('tagit-hidden-field');
325 | this.tagList.remove();
326 | } else {
327 | this.element.children('li').each(function() {
328 | if ($(this).hasClass('tagit-new')) {
329 | $(this).remove();
330 | } else {
331 | $(this).removeClass([
332 | 'tagit-choice',
333 | 'ui-widget-content',
334 | 'ui-state-default',
335 | 'ui-state-highlight',
336 | 'ui-corner-all',
337 | 'remove',
338 | 'tagit-choice-editable',
339 | 'tagit-choice-read-only'
340 | ].join(' '));
341 |
342 | $(this).text($(this).children('.tagit-label').text());
343 | }
344 | });
345 |
346 | if (this.singleFieldNode) {
347 | this.singleFieldNode.remove();
348 | }
349 | }
350 |
351 | return this;
352 | },
353 |
354 | _cleanedInput: function() {
355 | // Returns the contents of the tag input, cleaned and ready to be passed to createTag
356 | return $.trim(this.tagInput.val().replace(/^"(.*)"$/, '$1'));
357 | },
358 |
359 | _lastTag: function() {
360 | return this.tagList.find('.tagit-choice:last:not(.removed)');
361 | },
362 |
363 | _tags: function() {
364 | return this.tagList.find('.tagit-choice:not(.removed)');
365 | },
366 |
367 | assignedTags: function() {
368 | // Returns an array of tag string values
369 | var that = this;
370 | var tags = [];
371 | if (this.options.singleField) {
372 | tags = $(this.options.singleFieldNode).val().split(this.options.singleFieldDelimiter);
373 | if (tags[0] === '') {
374 | tags = [];
375 | }
376 | } else {
377 | this._tags().each(function() {
378 | tags.push(that.tagLabel(this));
379 | });
380 | }
381 | return tags;
382 | },
383 |
384 | _updateSingleTagsField: function(tags) {
385 | // Takes a list of tag string values, updates this.options.singleFieldNode.val to the tags delimited by this.options.singleFieldDelimiter
386 | $(this.options.singleFieldNode).val(tags.join(this.options.singleFieldDelimiter)).trigger('change');
387 | },
388 |
389 | _subtractArray: function(a1, a2) {
390 | var result = [];
391 | for (var i = 0; i < a1.length; i++) {
392 | if ($.inArray(a1[i], a2) == -1) {
393 | result.push(a1[i]);
394 | }
395 | }
396 | return result;
397 | },
398 |
399 | tagLabel: function(tag) {
400 | // Returns the tag's string label.
401 | if (this.options.singleField) {
402 | return $(tag).find('.tagit-label:first').text();
403 | } else {
404 | return $(tag).find('input:first').val();
405 | }
406 | },
407 |
408 | _showAutocomplete: function() {
409 | this.tagInput.autocomplete('search', '');
410 | },
411 |
412 | _findTagByLabel: function(name) {
413 | var that = this;
414 | var tag = null;
415 | this._tags().each(function(i) {
416 | if (that._formatStr(name) == that._formatStr(that.tagLabel(this))) {
417 | tag = $(this);
418 | return false;
419 | }
420 | });
421 | return tag;
422 | },
423 |
424 | _isNew: function(name) {
425 | return !this._findTagByLabel(name);
426 | },
427 |
428 | _formatStr: function(str) {
429 | if (this.options.caseSensitive) {
430 | return str;
431 | }
432 | return $.trim(str.toLowerCase());
433 | },
434 |
435 | _effectExists: function(name) {
436 | return Boolean($.effects && ($.effects[name] || ($.effects.effect && $.effects.effect[name])));
437 | },
438 |
439 | createTag: function(value, additionalClass, duringInitialization) {
440 | var that = this;
441 |
442 | value = $.trim(value);
443 |
444 | if(this.options.preprocessTag) {
445 | value = this.options.preprocessTag(value);
446 | }
447 |
448 | if (value === '') {
449 | return false;
450 | }
451 |
452 | if (!this.options.allowDuplicates && !this._isNew(value)) {
453 | var existingTag = this._findTagByLabel(value);
454 | if (this._trigger('onTagExists', null, {
455 | existingTag: existingTag,
456 | duringInitialization: duringInitialization
457 | }) !== false) {
458 | if (this._effectExists('highlight')) {
459 | existingTag.effect('highlight');
460 | }
461 | }
462 | return false;
463 | }
464 |
465 | if (this.options.tagLimit && this._tags().length >= this.options.tagLimit) {
466 | this._trigger('onTagLimitExceeded', null, {duringInitialization: duringInitialization});
467 | return false;
468 | }
469 |
470 | var label = $(this.options.onTagClicked ? ' ' : ' ').text(value);
471 |
472 | // Create tag.
473 | var tag = $(' ')
474 | .addClass('tagit-choice ui-widget-content ui-state-default ui-corner-all')
475 | .addClass(additionalClass)
476 | .append(label);
477 |
478 | if (this.options.readOnly){
479 | tag.addClass('tagit-choice-read-only');
480 | } else {
481 | tag.addClass('tagit-choice-editable');
482 | // Button for removing the tag.
483 | var removeTagIcon = $(' ')
484 | .addClass('ui-icon ui-icon-close');
485 | var removeTag = $('\xd7 ') // \xd7 is an X
486 | .addClass('tagit-close')
487 | .append(removeTagIcon)
488 | .click(function(e) {
489 | // Removes a tag when the little 'x' is clicked.
490 | that.removeTag(tag);
491 | });
492 | tag.append(removeTag);
493 | }
494 |
495 | // Unless options.singleField is set, each tag has a hidden input field inline.
496 | if (!this.options.singleField) {
497 | var escapedValue = label.html();
498 | tag.append(' ');
499 | }
500 |
501 | if (this._trigger('beforeTagAdded', null, {
502 | tag: tag,
503 | tagLabel: this.tagLabel(tag),
504 | duringInitialization: duringInitialization
505 | }) === false) {
506 | return;
507 | }
508 |
509 | if (this.options.singleField) {
510 | var tags = this.assignedTags();
511 | tags.push(value);
512 | this._updateSingleTagsField(tags);
513 | }
514 |
515 | // DEPRECATED.
516 | this._trigger('onTagAdded', null, tag);
517 |
518 | this.tagInput.val('');
519 |
520 | // Insert tag.
521 | this.tagInput.parent().before(tag);
522 |
523 | this._trigger('afterTagAdded', null, {
524 | tag: tag,
525 | tagLabel: this.tagLabel(tag),
526 | duringInitialization: duringInitialization
527 | });
528 |
529 | if (this.options.showAutocompleteOnFocus && !duringInitialization) {
530 | setTimeout(function () { that._showAutocomplete(); }, 0);
531 | }
532 | },
533 |
534 | removeTag: function(tag, animate) {
535 | animate = typeof animate === 'undefined' ? this.options.animate : animate;
536 |
537 | tag = $(tag);
538 |
539 | // DEPRECATED.
540 | this._trigger('onTagRemoved', null, tag);
541 |
542 | if (this._trigger('beforeTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)}) === false) {
543 | return;
544 | }
545 |
546 | if (this.options.singleField) {
547 | var tags = this.assignedTags();
548 | var removedTagLabel = this.tagLabel(tag);
549 | tags = $.grep(tags, function(el){
550 | return el != removedTagLabel;
551 | });
552 | this._updateSingleTagsField(tags);
553 | }
554 |
555 | if (animate) {
556 | tag.addClass('removed'); // Excludes this tag from _tags.
557 | var hide_args = this._effectExists('blind') ? ['blind', {direction: 'horizontal'}, 'fast'] : ['fast'];
558 |
559 | var thisTag = this;
560 | hide_args.push(function() {
561 | tag.remove();
562 | thisTag._trigger('afterTagRemoved', null, {tag: tag, tagLabel: thisTag.tagLabel(tag)});
563 | });
564 | tag.fadeOut('fast').hide.apply(tag, hide_args).dequeue();
565 | } else {
566 | tag.remove();
567 | this._trigger('afterTagRemoved', null, {tag: tag, tagLabel: this.tagLabel(tag)});
568 | }
569 |
570 | },
571 |
572 | removeTagByLabel: function(tagLabel, animate) {
573 | var toRemove = this._findTagByLabel(tagLabel);
574 | if (!toRemove) {
575 | throw "No such tag exists with the name '" + tagLabel + "'";
576 | }
577 | this.removeTag(toRemove, animate);
578 | },
579 |
580 | removeAll: function() {
581 | // Removes all tags.
582 | var that = this;
583 | this._tags().each(function(index, tag) {
584 | that.removeTag(tag, false);
585 | });
586 | }
587 |
588 | });
589 | })(jQuery);
590 |
591 |
--------------------------------------------------------------------------------
/public/stylesheets/editor-self.css:
--------------------------------------------------------------------------------
1 | .ui-state-default .ui-icon {
2 | background-image: url("images/ui-icons_228ef1_256x240.png");
3 | }
4 |
5 | .ui-state-focus,
6 | .ui-widget-content .ui-state-focus,
7 | .ui-widget-header .ui-state-focus {
8 | border: 1px solid #ccc;
9 | background: #f6f6f6;
10 | font-weight: bold;
11 | color: #1c94c4;
12 | }
13 |
14 | ul.tagit {
15 | padding: 1px 12px;
16 | }
17 | .ui-widget-content{
18 | background: #FFFFFF;
19 | }
20 |
21 | .CodeMirror {
22 | max-height: 312px;
23 | }
24 |
25 | .CodeMirror-scroll,.CodeMirror-vscrollbar{
26 | max-height:312px;
27 | }
28 | .editor-wrapper {
29 | max-height:360px;
30 | border: 1px solid #ccc;
31 | border-radius: 4px;
32 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
33 | box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
34 | -webkit-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
35 | transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
36 | }
37 |
38 | .editor-preview{
39 | position:static;
40 | padding: 10px 10px 10px 10px;
41 | border: 1px solid #ccc;
42 | border-radius: 4px;
43 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
44 | box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
45 | -webkit-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
46 | transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
47 | }
48 | .editor-preview img {
49 | width: 100%;
50 | }
51 | .CodeMirror-scroll {
52 | margin-left: 10px;
53 | margin-top: 5px;
54 |
55 | }
56 | .editor-toolbar{
57 | padding: 0 10px;
58 | }
59 |
60 |
61 | .image-upload-modal-body{
62 | padding-top: 10px;
63 | padding-bottom: 10px;
64 | }
65 |
66 | .image-upload-modal-body .form-group{
67 | margin-bottom: 50px;
68 | }
69 | .image-upload-modal-footer{
70 | text-align: center;
71 | margin-top: 0px;
72 | }
73 | .image-local-loading img{
74 | padding: 0 8px;
75 | width: 75%;
76 | }
77 |
--------------------------------------------------------------------------------
/public/stylesheets/editor.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'icomoon';
3 | src:url('fonts/icomoon.eot');
4 | src:url('fonts/icomoon.eot?#iefix') format('embedded-opentype'),
5 | url('fonts/icomoon.woff') format('woff'),
6 | url('fonts/icomoon.ttf') format('truetype'),
7 | url('fonts/icomoon.svg#icomoon') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | /* Use the following CSS code if you want to use data attributes for inserting your icons */
13 | [data-icon]:before {
14 | font-family: 'icomoon';
15 | content: attr(data-icon);
16 | speak: none;
17 | font-weight: normal;
18 | font-variant: normal;
19 | text-transform: none;
20 | line-height: 1;
21 | -webkit-font-smoothing: antialiased;
22 | }
23 |
24 | /* Use the following CSS code if you want to have a class per icon */
25 | /*
26 | Instead of a list of all class selectors,
27 | you can use the generic selector below, but it's slower:
28 | [class*="icon-"] {
29 | */
30 | .icon-bold, .icon-italic, .icon-quote, .icon-unordered-list, .icon-ordered-list, .icon-link, .icon-image, .icon-play, .icon-music, .icon-contract, .icon-fullscreen, .icon-question, .icon-info, .icon-undo, .icon-redo, .icon-code, .icon-preview {
31 | font-family: 'icomoon';
32 | speak: none;
33 | font-style: normal;
34 | font-weight: normal;
35 | font-variant: normal;
36 | text-transform: none;
37 | line-height: 1;
38 | -webkit-font-smoothing: antialiased;
39 | }
40 | .icon-bold:before {
41 | content: "\e000";
42 | }
43 | .icon-italic:before {
44 | content: "\e001";
45 | }
46 | .icon-quote:before {
47 | content: "\e003";
48 | }
49 | .icon-unordered-list:before {
50 | content: "\e004";
51 | }
52 | .icon-ordered-list:before {
53 | content: "\e005";
54 | }
55 | .icon-link:before {
56 | content: "\e006";
57 | }
58 | .icon-image:before {
59 | content: "\e007";
60 | }
61 | .icon-play:before {
62 | content: "\e008";
63 | }
64 | .icon-music:before {
65 | content: "\e009";
66 | }
67 | .icon-contract:before {
68 | content: "\e00a";
69 | }
70 | .icon-fullscreen:before {
71 | content: "\e00b";
72 | }
73 | .icon-question:before {
74 | content: "\e00c";
75 | }
76 | .icon-info:before {
77 | content: "\e00d";
78 | }
79 | .icon-undo:before {
80 | content: "\e00e";
81 | }
82 | .icon-redo:before {
83 | content: "\e00f";
84 | }
85 | .icon-code:before {
86 | content: "\e011";
87 | }
88 | .icon-preview:before {
89 | content: "\e002";
90 | }
91 | /* BASICS */
92 |
93 | .CodeMirror {
94 | height: 300px;
95 | }
96 | .CodeMirror-scroll {
97 | /* Set scrolling behaviour here */
98 | overflow: auto;
99 | }
100 |
101 | /* PADDING */
102 |
103 | .CodeMirror-lines {
104 | padding: 4px 0; /* Vertical padding around content */
105 | }
106 | .CodeMirror pre {
107 | padding: 0 4px; /* Horizontal padding of content */
108 | }
109 |
110 | .CodeMirror-scrollbar-filler {
111 | background-color: white; /* The little square between H and V scrollbars */
112 | }
113 |
114 | /* CURSOR */
115 | .CodeMirror div.CodeMirror-cursor {
116 | border-left: 1px solid black;
117 | z-index: 3;
118 | }
119 | /* Shown when moving in bi-directional text */
120 | .CodeMirror div.CodeMirror-secondarycursor {
121 | border-left: 1px solid silver;
122 | }
123 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
124 | width: auto;
125 | border: 0;
126 | background: #7e7;
127 | z-index: 1;
128 | }
129 | /* Can style cursor different in overwrite (non-insert) mode */
130 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
131 |
132 | /* DEFAULT THEME */
133 |
134 | .cm-s-paper .cm-keyword {color: #555;}
135 | .cm-s-paper .cm-atom {color: #7f8c8d;}
136 | .cm-s-paper .cm-number {color: #7f8c8d;}
137 | .cm-s-paper .cm-def {color: #00f;}
138 | .cm-s-paper .cm-variable {color: black;}
139 | .cm-s-paper .cm-variable-2 {color: #555;}
140 | .cm-s-paper .cm-variable-3 {color: #085;}
141 | .cm-s-paper .cm-property {color: black;}
142 | .cm-s-paper .cm-operator {color: black;}
143 | .cm-s-paper .cm-comment {color: #959595;}
144 | .cm-s-paper .cm-string {color: #7f8c8d;}
145 | .cm-s-paper .cm-string-2 {color: #f50;}
146 | .cm-s-paper .cm-meta {color: #555;}
147 | .cm-s-paper .cm-error {color: #f00;}
148 | .cm-s-paper .cm-qualifier {color: #555;}
149 | .cm-s-paper .cm-builtin {color: #555;}
150 | .cm-s-paper .cm-bracket {color: #997;}
151 | .cm-s-paper .cm-tag {color: #7f8c8d;}
152 | .cm-s-paper .cm-attribute {color: #7f8c8d;}
153 | .cm-s-paper .cm-header {color: #000;}
154 | .cm-s-paper .cm-quote {color: #888;}
155 | .cm-s-paper .cm-hr {color: #999;}
156 | .cm-s-paper .cm-link {color: #7f8c8d;}
157 |
158 | .cm-negative {color: #d44;}
159 | .cm-positive {color: #292;}
160 | .cm-header, .cm-strong {font-weight: bold;}
161 | .cm-em {font-style: italic;}
162 | .cm-link {text-decoration: underline;}
163 |
164 | .cm-invalidchar {color: #f00;}
165 |
166 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
167 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
168 |
169 |
170 | /* STOP */
171 |
172 | /* The rest of this file contains styles related to the mechanics of
173 | the editor. You probably shouldn't touch them. */
174 |
175 | .CodeMirror {
176 | position: relative;
177 | overflow: hidden;
178 | }
179 |
180 | .CodeMirror-scroll {
181 | /* 30px is the magic margin used to hide the element's real scrollbars */
182 | /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */
183 | margin-bottom: -30px; margin-right: -30px;
184 | padding-bottom: 30px; padding-right: 30px;
185 | height: 100%;
186 | outline: none; /* Prevent dragging from highlighting the element */
187 | position: relative;
188 | }
189 | .CodeMirror-sizer {
190 | position: relative;
191 | }
192 |
193 | /* The fake, visible scrollbars. Used to force redraw during scrolling
194 | before actuall scrolling happens, thus preventing shaking and
195 | flickering artifacts. */
196 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler {
197 | position: absolute;
198 | z-index: 6;
199 | display: none;
200 | }
201 | .CodeMirror-vscrollbar {
202 | right: 0; top: 0;
203 | overflow-x: hidden;
204 | overflow-y: scroll;
205 | }
206 | .CodeMirror-hscrollbar {
207 | bottom: 0; left: 0;
208 | overflow-y: hidden;
209 | overflow-x: scroll;
210 | }
211 | .CodeMirror-scrollbar-filler {
212 | right: 0; bottom: 0;
213 | z-index: 6;
214 | }
215 |
216 | .CodeMirror-lines {
217 | cursor: text;
218 | }
219 | .CodeMirror pre {
220 | /* Reset some styles that the rest of the page might have set */
221 | -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0;
222 | border-width: 0;
223 | background: transparent;
224 | font-family: inherit;
225 | font-size: inherit;
226 | margin: 0;
227 | white-space: pre-wrap;
228 | word-wrap: normal;
229 | line-height: inherit;
230 | color: inherit;
231 | z-index: 2;
232 | position: relative;
233 | overflow: visible;
234 | }
235 | .CodeMirror-wrap pre {
236 | word-wrap: break-word;
237 | white-space: pre-wrap;
238 | word-break: normal;
239 | }
240 | .CodeMirror-linebackground {
241 | position: absolute;
242 | left: 0; right: 0; top: 0; bottom: 0;
243 | z-index: 0;
244 | }
245 |
246 | .CodeMirror-linewidget {
247 | position: relative;
248 | z-index: 2;
249 | overflow: auto;
250 | }
251 |
252 | .CodeMirror-widget {
253 | display: inline-block;
254 | }
255 |
256 | .CodeMirror-wrap .CodeMirror-scroll {
257 | overflow-x: hidden;
258 | }
259 |
260 | .CodeMirror-measure {
261 | position: absolute;
262 | width: 100%; height: 0px;
263 | overflow: hidden;
264 | visibility: hidden;
265 | }
266 | .CodeMirror-measure pre { position: static; }
267 |
268 | .CodeMirror div.CodeMirror-cursor {
269 | position: absolute;
270 | visibility: hidden;
271 | border-right: none;
272 | width: 0;
273 | }
274 | .CodeMirror-focused div.CodeMirror-cursor {
275 | visibility: visible;
276 | }
277 |
278 | .CodeMirror-selected { background: #d9d9d9; }
279 | .CodeMirror-focused .CodeMirror-selected { background: #BDC3C7; }
280 |
281 | .cm-searching {
282 | background: #ffa;
283 | background: rgba(255, 255, 0, .4);
284 | }
285 |
286 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */
287 | .CodeMirror span { *vertical-align: text-bottom; }
288 |
289 | @media print {
290 | /* Hide the cursor when printing */
291 | .CodeMirror div.CodeMirror-cursor {
292 | visibility: hidden;
293 | }
294 | }
295 | .CodeMirror {
296 | height: 450px;
297 | }
298 | :-webkit-full-screen {
299 | background: #f9f9f5;
300 | padding: 0.5em 1em;
301 | width: 100%;
302 | height: 100%;
303 | }
304 | :-moz-full-screen {
305 | padding: 0.5em 1em;
306 | background: #f9f9f5;
307 | width: 100%;
308 | height: 100%;
309 | }
310 | .editor-wrapper {
311 | font: 16px/1.62 "Helvetica Neue", "Xin Gothic", "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif;
312 | color: #2c3e50;
313 | }
314 | /* this is the title */
315 | .editor-wrapper input.title {
316 | font: 18px "Helvetica Neue", "Xin Gothic", "Hiragino Sans GB", "WenQuanYi Micro Hei", "Microsoft YaHei", sans-serif;
317 | background: transparent;
318 | padding: 4px;
319 | width: 100%;
320 | border: none;
321 | outline: none;
322 | opacity: 0.6;
323 | }
324 | .editor-toolbar {
325 | position: relative;
326 | opacity: 0.6;
327 | -webkit-user-select: none;
328 | -moz-user-select: none;
329 | -ms-user-select: none;
330 | -o-user-select: none;
331 | user-select: none;
332 | }
333 | .editor-toolbar:before, .editor-toolbar:after {
334 | display: block;
335 | content: ' ';
336 | height: 1px;
337 | background-color: #bdc3c7;
338 | background: -moz-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9);
339 | background: -webkit-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9);
340 | background: -ms-linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9);
341 | background: linear-gradient(45deg, #f9f9f9, #bdc3c7, #f9f9f9);
342 | }
343 | .editor-toolbar:before {
344 | margin-bottom: 8px;
345 | }
346 | .editor-toolbar:after {
347 | margin-top: 8px;
348 | }
349 | .editor-wrapper input.title:hover, .editor-wrapper input.title:focus, .editor-toolbar:hover {
350 | opacity: 0.8;
351 | }
352 | .editor-toolbar a {
353 | display: inline-block;
354 | text-align: center;
355 | text-decoration: none !important;
356 | color: #2c3e50 !important;
357 | width: 24px;
358 | height: 24px;
359 | margin: 2px;
360 | border: 1px solid transparent;
361 | border-radius: 3px;
362 | cursor: pointer;
363 | }
364 | .editor-toolbar a:hover, .editor-toolbar a.active {
365 | background: #fcfcfc;
366 | border-color: #95a5a6;
367 | }
368 | .editor-toolbar a:before {
369 | line-height: 24px;
370 | }
371 | .editor-toolbar i.separator {
372 | display: inline-block;
373 | width: 0;
374 | border-left: 1px solid #d9d9d9;
375 | border-right: 1px solid white;
376 | color: transparent;
377 | text-indent: -10px;
378 | margin: 0 6px;
379 | }
380 | .editor-toolbar a.icon-fullscreen {
381 | position: absolute;
382 | right: 0;
383 | }
384 | .editor-statusbar {
385 | border-top: 1px solid #ece9e9;
386 | padding: 8px 10px;
387 | font-size: 12px;
388 | color: #959694;
389 | text-align: right;
390 | }
391 | .editor-statusbar span {
392 | display: inline-block;
393 | min-width: 4em;
394 | margin-left: 1em;
395 | }
396 | .editor-statusbar .lines:before {
397 | content: 'lines: ';
398 | }
399 | .editor-statusbar .words:before {
400 | content: 'words: ';
401 | }
402 | .editor-preview {
403 | position: absolute;
404 | width: 100%;
405 | height: 100%;
406 | top: 0;
407 | left: 100%;
408 | background: #f9f9f5;
409 | z-index: 9999;
410 | overflow: auto;
411 | -webkit-transition: left 0.2s ease;
412 | -moz-transition: left 0.2s ease;
413 | -ms-transition: left 0.2s ease;
414 | transition: left 0.2s ease;
415 | }
416 | .editor-preview-active {
417 | left: 0;
418 | }
419 | .editor-preview > p {
420 | margin-top: 0;
421 | }
422 |
--------------------------------------------------------------------------------
/public/stylesheets/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/public/stylesheets/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/public/stylesheets/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/public/stylesheets/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/public/stylesheets/fonts/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/fonts/icomoon.woff
--------------------------------------------------------------------------------
/public/stylesheets/images/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/loading.gif
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-bg_diagonals-thick_18_b81900_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-bg_diagonals-thick_18_b81900_40x40.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-bg_diagonals-thick_20_666666_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-bg_diagonals-thick_20_666666_40x40.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-bg_flat_10_000000_40x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-bg_flat_10_000000_40x100.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-bg_glass_100_f6f6f6_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-bg_glass_100_f6f6f6_1x400.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-bg_glass_100_fdf5ce_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-bg_glass_100_fdf5ce_1x400.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-bg_glass_65_ffffff_1x400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-bg_glass_65_ffffff_1x400.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-bg_gloss-wave_35_f6a828_500x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-bg_gloss-wave_35_f6a828_500x100.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-bg_highlight-soft_100_eeeeee_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-bg_highlight-soft_75_ffe45c_1x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-bg_highlight-soft_75_ffe45c_1x100.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-icons_222222_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-icons_222222_256x240.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-icons_228ef1_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-icons_228ef1_256x240.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-icons_ef8c08_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-icons_ef8c08_256x240.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-icons_ffd27a_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-icons_ffd27a_256x240.png
--------------------------------------------------------------------------------
/public/stylesheets/images/ui-icons_ffffff_256x240.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckybirdme/NodeJS-forum/9b01a7a9502c657c776681057fe53123940916b7/public/stylesheets/images/ui-icons_ffffff_256x240.png
--------------------------------------------------------------------------------
/public/stylesheets/jquery-ui.css:
--------------------------------------------------------------------------------
1 | /*! jQuery UI - v1.11.4 - 2015-03-11
2 | * http://jqueryui.com
3 | * Includes: core.css, accordion.css, autocomplete.css, button.css, datepicker.css, dialog.css, draggable.css, menu.css, progressbar.css, resizable.css, selectable.css, selectmenu.css, slider.css, sortable.css, spinner.css, tabs.css, tooltip.css, theme.css
4 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Trebuchet%20MS%2CTahoma%2CVerdana%2CArial%2Csans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=f6a828&bgTextureHeader=gloss_wave&bgImgOpacityHeader=35&borderColorHeader=e78f08&fcHeader=ffffff&iconColorHeader=ffffff&bgColorContent=eeeeee&bgTextureContent=highlight_soft&bgImgOpacityContent=100&borderColorContent=dddddd&fcContent=333333&iconColorContent=222222&bgColorDefault=f6f6f6&bgTextureDefault=glass&bgImgOpacityDefault=100&borderColorDefault=cccccc&fcDefault=1c94c4&iconColorDefault=ef8c08&bgColorHover=fdf5ce&bgTextureHover=glass&bgImgOpacityHover=100&borderColorHover=fbcb09&fcHover=c77405&iconColorHover=ef8c08&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=fbd850&fcActive=eb8f00&iconColorActive=ef8c08&bgColorHighlight=ffe45c&bgTextureHighlight=highlight_soft&bgImgOpacityHighlight=75&borderColorHighlight=fed22f&fcHighlight=363636&iconColorHighlight=228ef1&bgColorError=b81900&bgTextureError=diagonals_thick&bgImgOpacityError=18&borderColorError=cd0a0a&fcError=ffffff&iconColorError=ffd27a&bgColorOverlay=666666&bgTextureOverlay=diagonals_thick&bgImgOpacityOverlay=20&opacityOverlay=50&bgColorShadow=000000&bgTextureShadow=flat&bgImgOpacityShadow=10&opacityShadow=20&thicknessShadow=5px&offsetTopShadow=-5px&offsetLeftShadow=-5px&cornerRadiusShadow=5px
5 | * Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
6 |
7 | /* Layout helpers
8 | ----------------------------------*/
9 | .ui-helper-hidden {
10 | display: none;
11 | }
12 | .ui-helper-hidden-accessible {
13 | border: 0;
14 | clip: rect(0 0 0 0);
15 | height: 1px;
16 | margin: -1px;
17 | overflow: hidden;
18 | padding: 0;
19 | position: absolute;
20 | width: 1px;
21 | }
22 | .ui-helper-reset {
23 | margin: 0;
24 | padding: 0;
25 | border: 0;
26 | outline: 0;
27 | line-height: 1.3;
28 | text-decoration: none;
29 | font-size: 100%;
30 | list-style: none;
31 | }
32 | .ui-helper-clearfix:before,
33 | .ui-helper-clearfix:after {
34 | content: "";
35 | display: table;
36 | border-collapse: collapse;
37 | }
38 | .ui-helper-clearfix:after {
39 | clear: both;
40 | }
41 | .ui-helper-clearfix {
42 | min-height: 0; /* support: IE7 */
43 | }
44 | .ui-helper-zfix {
45 | width: 100%;
46 | height: 100%;
47 | top: 0;
48 | left: 0;
49 | position: absolute;
50 | opacity: 0;
51 | filter:Alpha(Opacity=0); /* support: IE8 */
52 | }
53 |
54 | .ui-front {
55 | z-index: 100;
56 | }
57 |
58 |
59 | /* Interaction Cues
60 | ----------------------------------*/
61 | .ui-state-disabled {
62 | cursor: default !important;
63 | }
64 |
65 |
66 | /* Icons
67 | ----------------------------------*/
68 |
69 | /* states and images */
70 | .ui-icon {
71 | display: block;
72 | text-indent: -99999px;
73 | overflow: hidden;
74 | background-repeat: no-repeat;
75 | }
76 |
77 |
78 | /* Misc visuals
79 | ----------------------------------*/
80 |
81 | /* Overlays */
82 | .ui-widget-overlay {
83 | position: fixed;
84 | top: 0;
85 | left: 0;
86 | width: 100%;
87 | height: 100%;
88 | }
89 | .ui-accordion .ui-accordion-header {
90 | display: block;
91 | cursor: pointer;
92 | position: relative;
93 | margin: 2px 0 0 0;
94 | padding: .5em .5em .5em .7em;
95 | min-height: 0; /* support: IE7 */
96 | font-size: 100%;
97 | }
98 | .ui-accordion .ui-accordion-icons {
99 | padding-left: 2.2em;
100 | }
101 | .ui-accordion .ui-accordion-icons .ui-accordion-icons {
102 | padding-left: 2.2em;
103 | }
104 | .ui-accordion .ui-accordion-header .ui-accordion-header-icon {
105 | position: absolute;
106 | left: .5em;
107 | top: 50%;
108 | margin-top: -8px;
109 | }
110 | .ui-accordion .ui-accordion-content {
111 | padding: 1em 2.2em;
112 | border-top: 0;
113 | overflow: auto;
114 | }
115 | .ui-autocomplete {
116 | position: absolute;
117 | top: 0;
118 | left: 0;
119 | cursor: default;
120 | }
121 | .ui-button {
122 | display: inline-block;
123 | position: relative;
124 | padding: 0;
125 | line-height: normal;
126 | margin-right: .1em;
127 | cursor: pointer;
128 | vertical-align: middle;
129 | text-align: center;
130 | overflow: visible; /* removes extra width in IE */
131 | }
132 | .ui-button,
133 | .ui-button:link,
134 | .ui-button:visited,
135 | .ui-button:hover,
136 | .ui-button:active {
137 | text-decoration: none;
138 | }
139 | /* to make room for the icon, a width needs to be set here */
140 | .ui-button-icon-only {
141 | width: 2.2em;
142 | }
143 | /* button elements seem to need a little more width */
144 | button.ui-button-icon-only {
145 | width: 2.4em;
146 | }
147 | .ui-button-icons-only {
148 | width: 3.4em;
149 | }
150 | button.ui-button-icons-only {
151 | width: 3.7em;
152 | }
153 |
154 | /* button text element */
155 | .ui-button .ui-button-text {
156 | display: block;
157 | line-height: normal;
158 | }
159 | .ui-button-text-only .ui-button-text {
160 | padding: .4em 1em;
161 | }
162 | .ui-button-icon-only .ui-button-text,
163 | .ui-button-icons-only .ui-button-text {
164 | padding: .4em;
165 | text-indent: -9999999px;
166 | }
167 | .ui-button-text-icon-primary .ui-button-text,
168 | .ui-button-text-icons .ui-button-text {
169 | padding: .4em 1em .4em 2.1em;
170 | }
171 | .ui-button-text-icon-secondary .ui-button-text,
172 | .ui-button-text-icons .ui-button-text {
173 | padding: .4em 2.1em .4em 1em;
174 | }
175 | .ui-button-text-icons .ui-button-text {
176 | padding-left: 2.1em;
177 | padding-right: 2.1em;
178 | }
179 | /* no icon support for input elements, provide padding by default */
180 | input.ui-button {
181 | padding: .4em 1em;
182 | }
183 |
184 | /* button icon element(s) */
185 | .ui-button-icon-only .ui-icon,
186 | .ui-button-text-icon-primary .ui-icon,
187 | .ui-button-text-icon-secondary .ui-icon,
188 | .ui-button-text-icons .ui-icon,
189 | .ui-button-icons-only .ui-icon {
190 | position: absolute;
191 | top: 50%;
192 | margin-top: -8px;
193 | }
194 | .ui-button-icon-only .ui-icon {
195 | left: 50%;
196 | margin-left: -8px;
197 | }
198 | .ui-button-text-icon-primary .ui-button-icon-primary,
199 | .ui-button-text-icons .ui-button-icon-primary,
200 | .ui-button-icons-only .ui-button-icon-primary {
201 | left: .5em;
202 | }
203 | .ui-button-text-icon-secondary .ui-button-icon-secondary,
204 | .ui-button-text-icons .ui-button-icon-secondary,
205 | .ui-button-icons-only .ui-button-icon-secondary {
206 | right: .5em;
207 | }
208 |
209 | /* button sets */
210 | .ui-buttonset {
211 | margin-right: 7px;
212 | }
213 | .ui-buttonset .ui-button {
214 | margin-left: 0;
215 | margin-right: -.3em;
216 | }
217 |
218 | /* workarounds */
219 | /* reset extra padding in Firefox, see h5bp.com/l */
220 | input.ui-button::-moz-focus-inner,
221 | button.ui-button::-moz-focus-inner {
222 | border: 0;
223 | padding: 0;
224 | }
225 | .ui-datepicker {
226 | width: 17em;
227 | padding: .2em .2em 0;
228 | display: none;
229 | }
230 | .ui-datepicker .ui-datepicker-header {
231 | position: relative;
232 | padding: .2em 0;
233 | }
234 | .ui-datepicker .ui-datepicker-prev,
235 | .ui-datepicker .ui-datepicker-next {
236 | position: absolute;
237 | top: 2px;
238 | width: 1.8em;
239 | height: 1.8em;
240 | }
241 | .ui-datepicker .ui-datepicker-prev-hover,
242 | .ui-datepicker .ui-datepicker-next-hover {
243 | top: 1px;
244 | }
245 | .ui-datepicker .ui-datepicker-prev {
246 | left: 2px;
247 | }
248 | .ui-datepicker .ui-datepicker-next {
249 | right: 2px;
250 | }
251 | .ui-datepicker .ui-datepicker-prev-hover {
252 | left: 1px;
253 | }
254 | .ui-datepicker .ui-datepicker-next-hover {
255 | right: 1px;
256 | }
257 | .ui-datepicker .ui-datepicker-prev span,
258 | .ui-datepicker .ui-datepicker-next span {
259 | display: block;
260 | position: absolute;
261 | left: 50%;
262 | margin-left: -8px;
263 | top: 50%;
264 | margin-top: -8px;
265 | }
266 | .ui-datepicker .ui-datepicker-title {
267 | margin: 0 2.3em;
268 | line-height: 1.8em;
269 | text-align: center;
270 | }
271 | .ui-datepicker .ui-datepicker-title select {
272 | font-size: 1em;
273 | margin: 1px 0;
274 | }
275 | .ui-datepicker select.ui-datepicker-month,
276 | .ui-datepicker select.ui-datepicker-year {
277 | width: 45%;
278 | }
279 | .ui-datepicker table {
280 | width: 100%;
281 | font-size: .9em;
282 | border-collapse: collapse;
283 | margin: 0 0 .4em;
284 | }
285 | .ui-datepicker th {
286 | padding: .7em .3em;
287 | text-align: center;
288 | font-weight: bold;
289 | border: 0;
290 | }
291 | .ui-datepicker td {
292 | border: 0;
293 | padding: 1px;
294 | }
295 | .ui-datepicker td span,
296 | .ui-datepicker td a {
297 | display: block;
298 | padding: .2em;
299 | text-align: right;
300 | text-decoration: none;
301 | }
302 | .ui-datepicker .ui-datepicker-buttonpane {
303 | background-image: none;
304 | margin: .7em 0 0 0;
305 | padding: 0 .2em;
306 | border-left: 0;
307 | border-right: 0;
308 | border-bottom: 0;
309 | }
310 | .ui-datepicker .ui-datepicker-buttonpane button {
311 | float: right;
312 | margin: .5em .2em .4em;
313 | cursor: pointer;
314 | padding: .2em .6em .3em .6em;
315 | width: auto;
316 | overflow: visible;
317 | }
318 | .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
319 | float: left;
320 | }
321 |
322 | /* with multiple calendars */
323 | .ui-datepicker.ui-datepicker-multi {
324 | width: auto;
325 | }
326 | .ui-datepicker-multi .ui-datepicker-group {
327 | float: left;
328 | }
329 | .ui-datepicker-multi .ui-datepicker-group table {
330 | width: 95%;
331 | margin: 0 auto .4em;
332 | }
333 | .ui-datepicker-multi-2 .ui-datepicker-group {
334 | width: 50%;
335 | }
336 | .ui-datepicker-multi-3 .ui-datepicker-group {
337 | width: 33.3%;
338 | }
339 | .ui-datepicker-multi-4 .ui-datepicker-group {
340 | width: 25%;
341 | }
342 | .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
343 | .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
344 | border-left-width: 0;
345 | }
346 | .ui-datepicker-multi .ui-datepicker-buttonpane {
347 | clear: left;
348 | }
349 | .ui-datepicker-row-break {
350 | clear: both;
351 | width: 100%;
352 | font-size: 0;
353 | }
354 |
355 | /* RTL support */
356 | .ui-datepicker-rtl {
357 | direction: rtl;
358 | }
359 | .ui-datepicker-rtl .ui-datepicker-prev {
360 | right: 2px;
361 | left: auto;
362 | }
363 | .ui-datepicker-rtl .ui-datepicker-next {
364 | left: 2px;
365 | right: auto;
366 | }
367 | .ui-datepicker-rtl .ui-datepicker-prev:hover {
368 | right: 1px;
369 | left: auto;
370 | }
371 | .ui-datepicker-rtl .ui-datepicker-next:hover {
372 | left: 1px;
373 | right: auto;
374 | }
375 | .ui-datepicker-rtl .ui-datepicker-buttonpane {
376 | clear: right;
377 | }
378 | .ui-datepicker-rtl .ui-datepicker-buttonpane button {
379 | float: left;
380 | }
381 | .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
382 | .ui-datepicker-rtl .ui-datepicker-group {
383 | float: right;
384 | }
385 | .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
386 | .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
387 | border-right-width: 0;
388 | border-left-width: 1px;
389 | }
390 | .ui-dialog {
391 | overflow: hidden;
392 | position: absolute;
393 | top: 0;
394 | left: 0;
395 | padding: .2em;
396 | outline: 0;
397 | }
398 | .ui-dialog .ui-dialog-titlebar {
399 | padding: .4em 1em;
400 | position: relative;
401 | }
402 | .ui-dialog .ui-dialog-title {
403 | float: left;
404 | margin: .1em 0;
405 | white-space: nowrap;
406 | width: 90%;
407 | overflow: hidden;
408 | text-overflow: ellipsis;
409 | }
410 | .ui-dialog .ui-dialog-titlebar-close {
411 | position: absolute;
412 | right: .3em;
413 | top: 50%;
414 | width: 20px;
415 | margin: -10px 0 0 0;
416 | padding: 1px;
417 | height: 20px;
418 | }
419 | .ui-dialog .ui-dialog-content {
420 | position: relative;
421 | border: 0;
422 | padding: .5em 1em;
423 | background: none;
424 | overflow: auto;
425 | }
426 | .ui-dialog .ui-dialog-buttonpane {
427 | text-align: left;
428 | border-width: 1px 0 0 0;
429 | background-image: none;
430 | margin-top: .5em;
431 | padding: .3em 1em .5em .4em;
432 | }
433 | .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
434 | float: right;
435 | }
436 | .ui-dialog .ui-dialog-buttonpane button {
437 | margin: .5em .4em .5em 0;
438 | cursor: pointer;
439 | }
440 | .ui-dialog .ui-resizable-se {
441 | width: 12px;
442 | height: 12px;
443 | right: -5px;
444 | bottom: -5px;
445 | background-position: 16px 16px;
446 | }
447 | .ui-draggable .ui-dialog-titlebar {
448 | cursor: move;
449 | }
450 | .ui-draggable-handle {
451 | -ms-touch-action: none;
452 | touch-action: none;
453 | }
454 | .ui-menu {
455 | list-style: none;
456 | padding: 0;
457 | margin: 0;
458 | display: block;
459 | outline: none;
460 | }
461 | .ui-menu .ui-menu {
462 | position: absolute;
463 | }
464 | .ui-menu .ui-menu-item {
465 | position: relative;
466 | margin: 0;
467 | padding: 3px 1em 3px .4em;
468 | cursor: pointer;
469 | min-height: 0; /* support: IE7 */
470 | /* support: IE10, see #8844 */
471 | list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
472 | }
473 | .ui-menu .ui-menu-divider {
474 | margin: 5px 0;
475 | height: 0;
476 | font-size: 0;
477 | line-height: 0;
478 | border-width: 1px 0 0 0;
479 | }
480 | .ui-menu .ui-state-focus,
481 | .ui-menu .ui-state-active {
482 | margin: -1px;
483 | }
484 |
485 | /* icon support */
486 | .ui-menu-icons {
487 | position: relative;
488 | }
489 | .ui-menu-icons .ui-menu-item {
490 | padding-left: 2em;
491 | }
492 |
493 | /* left-aligned */
494 | .ui-menu .ui-icon {
495 | position: absolute;
496 | top: 0;
497 | bottom: 0;
498 | left: .2em;
499 | margin: auto 0;
500 | }
501 |
502 | /* right-aligned */
503 | .ui-menu .ui-menu-icon {
504 | left: auto;
505 | right: 0;
506 | }
507 | .ui-progressbar {
508 | height: 2em;
509 | text-align: left;
510 | overflow: hidden;
511 | }
512 | .ui-progressbar .ui-progressbar-value {
513 | margin: -1px;
514 | height: 100%;
515 | }
516 | .ui-progressbar .ui-progressbar-overlay {
517 | background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");
518 | height: 100%;
519 | filter: alpha(opacity=25); /* support: IE8 */
520 | opacity: 0.25;
521 | }
522 | .ui-progressbar-indeterminate .ui-progressbar-value {
523 | background-image: none;
524 | }
525 | .ui-resizable {
526 | position: relative;
527 | }
528 | .ui-resizable-handle {
529 | position: absolute;
530 | font-size: 0.1px;
531 | display: block;
532 | -ms-touch-action: none;
533 | touch-action: none;
534 | }
535 | .ui-resizable-disabled .ui-resizable-handle,
536 | .ui-resizable-autohide .ui-resizable-handle {
537 | display: none;
538 | }
539 | .ui-resizable-n {
540 | cursor: n-resize;
541 | height: 7px;
542 | width: 100%;
543 | top: -5px;
544 | left: 0;
545 | }
546 | .ui-resizable-s {
547 | cursor: s-resize;
548 | height: 7px;
549 | width: 100%;
550 | bottom: -5px;
551 | left: 0;
552 | }
553 | .ui-resizable-e {
554 | cursor: e-resize;
555 | width: 7px;
556 | right: -5px;
557 | top: 0;
558 | height: 100%;
559 | }
560 | .ui-resizable-w {
561 | cursor: w-resize;
562 | width: 7px;
563 | left: -5px;
564 | top: 0;
565 | height: 100%;
566 | }
567 | .ui-resizable-se {
568 | cursor: se-resize;
569 | width: 12px;
570 | height: 12px;
571 | right: 1px;
572 | bottom: 1px;
573 | }
574 | .ui-resizable-sw {
575 | cursor: sw-resize;
576 | width: 9px;
577 | height: 9px;
578 | left: -5px;
579 | bottom: -5px;
580 | }
581 | .ui-resizable-nw {
582 | cursor: nw-resize;
583 | width: 9px;
584 | height: 9px;
585 | left: -5px;
586 | top: -5px;
587 | }
588 | .ui-resizable-ne {
589 | cursor: ne-resize;
590 | width: 9px;
591 | height: 9px;
592 | right: -5px;
593 | top: -5px;
594 | }
595 | .ui-selectable {
596 | -ms-touch-action: none;
597 | touch-action: none;
598 | }
599 | .ui-selectable-helper {
600 | position: absolute;
601 | z-index: 100;
602 | border: 1px dotted black;
603 | }
604 | .ui-selectmenu-menu {
605 | padding: 0;
606 | margin: 0;
607 | position: absolute;
608 | top: 0;
609 | left: 0;
610 | display: none;
611 | }
612 | .ui-selectmenu-menu .ui-menu {
613 | overflow: auto;
614 | /* Support: IE7 */
615 | overflow-x: hidden;
616 | padding-bottom: 1px;
617 | }
618 | .ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup {
619 | font-size: 1em;
620 | font-weight: bold;
621 | line-height: 1.5;
622 | padding: 2px 0.4em;
623 | margin: 0.5em 0 0 0;
624 | height: auto;
625 | border: 0;
626 | }
627 | .ui-selectmenu-open {
628 | display: block;
629 | }
630 | .ui-selectmenu-button {
631 | display: inline-block;
632 | overflow: hidden;
633 | position: relative;
634 | text-decoration: none;
635 | cursor: pointer;
636 | }
637 | .ui-selectmenu-button span.ui-icon {
638 | right: 0.5em;
639 | left: auto;
640 | margin-top: -8px;
641 | position: absolute;
642 | top: 50%;
643 | }
644 | .ui-selectmenu-button span.ui-selectmenu-text {
645 | text-align: left;
646 | padding: 0.4em 2.1em 0.4em 1em;
647 | display: block;
648 | line-height: 1.4;
649 | overflow: hidden;
650 | text-overflow: ellipsis;
651 | white-space: nowrap;
652 | }
653 | .ui-slider {
654 | position: relative;
655 | text-align: left;
656 | }
657 | .ui-slider .ui-slider-handle {
658 | position: absolute;
659 | z-index: 2;
660 | width: 1.2em;
661 | height: 1.2em;
662 | cursor: default;
663 | -ms-touch-action: none;
664 | touch-action: none;
665 | }
666 | .ui-slider .ui-slider-range {
667 | position: absolute;
668 | z-index: 1;
669 | font-size: .7em;
670 | display: block;
671 | border: 0;
672 | background-position: 0 0;
673 | }
674 |
675 | /* support: IE8 - See #6727 */
676 | .ui-slider.ui-state-disabled .ui-slider-handle,
677 | .ui-slider.ui-state-disabled .ui-slider-range {
678 | filter: inherit;
679 | }
680 |
681 | .ui-slider-horizontal {
682 | height: .8em;
683 | }
684 | .ui-slider-horizontal .ui-slider-handle {
685 | top: -.3em;
686 | margin-left: -.6em;
687 | }
688 | .ui-slider-horizontal .ui-slider-range {
689 | top: 0;
690 | height: 100%;
691 | }
692 | .ui-slider-horizontal .ui-slider-range-min {
693 | left: 0;
694 | }
695 | .ui-slider-horizontal .ui-slider-range-max {
696 | right: 0;
697 | }
698 |
699 | .ui-slider-vertical {
700 | width: .8em;
701 | height: 100px;
702 | }
703 | .ui-slider-vertical .ui-slider-handle {
704 | left: -.3em;
705 | margin-left: 0;
706 | margin-bottom: -.6em;
707 | }
708 | .ui-slider-vertical .ui-slider-range {
709 | left: 0;
710 | width: 100%;
711 | }
712 | .ui-slider-vertical .ui-slider-range-min {
713 | bottom: 0;
714 | }
715 | .ui-slider-vertical .ui-slider-range-max {
716 | top: 0;
717 | }
718 | .ui-sortable-handle {
719 | -ms-touch-action: none;
720 | touch-action: none;
721 | }
722 | .ui-spinner {
723 | position: relative;
724 | display: inline-block;
725 | overflow: hidden;
726 | padding: 0;
727 | vertical-align: middle;
728 | }
729 | .ui-spinner-input {
730 | border: none;
731 | background: none;
732 | color: inherit;
733 | padding: 0;
734 | margin: .2em 0;
735 | vertical-align: middle;
736 | margin-left: .4em;
737 | margin-right: 22px;
738 | }
739 | .ui-spinner-button {
740 | width: 16px;
741 | height: 50%;
742 | font-size: .5em;
743 | padding: 0;
744 | margin: 0;
745 | text-align: center;
746 | position: absolute;
747 | cursor: default;
748 | display: block;
749 | overflow: hidden;
750 | right: 0;
751 | }
752 | /* more specificity required here to override default borders */
753 | .ui-spinner a.ui-spinner-button {
754 | border-top: none;
755 | border-bottom: none;
756 | border-right: none;
757 | }
758 | /* vertically center icon */
759 | .ui-spinner .ui-icon {
760 | position: absolute;
761 | margin-top: -8px;
762 | top: 50%;
763 | left: 0;
764 | }
765 | .ui-spinner-up {
766 | top: 0;
767 | }
768 | .ui-spinner-down {
769 | bottom: 0;
770 | }
771 |
772 | /* TR overrides */
773 | .ui-spinner .ui-icon-triangle-1-s {
774 | /* need to fix icons sprite */
775 | background-position: -65px -16px;
776 | }
777 | .ui-tabs {
778 | position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
779 | padding: .2em;
780 | }
781 | .ui-tabs .ui-tabs-nav {
782 | margin: 0;
783 | padding: .2em .2em 0;
784 | }
785 | .ui-tabs .ui-tabs-nav li {
786 | list-style: none;
787 | float: left;
788 | position: relative;
789 | top: 0;
790 | margin: 1px .2em 0 0;
791 | border-bottom-width: 0;
792 | padding: 0;
793 | white-space: nowrap;
794 | }
795 | .ui-tabs .ui-tabs-nav .ui-tabs-anchor {
796 | float: left;
797 | padding: .5em 1em;
798 | text-decoration: none;
799 | }
800 | .ui-tabs .ui-tabs-nav li.ui-tabs-active {
801 | margin-bottom: -1px;
802 | padding-bottom: 1px;
803 | }
804 | .ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,
805 | .ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,
806 | .ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor {
807 | cursor: text;
808 | }
809 | .ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor {
810 | cursor: pointer;
811 | }
812 | .ui-tabs .ui-tabs-panel {
813 | display: block;
814 | border-width: 0;
815 | padding: 1em 1.4em;
816 | background: none;
817 | }
818 | .ui-tooltip {
819 | padding: 8px;
820 | position: absolute;
821 | z-index: 9999;
822 | max-width: 300px;
823 | -webkit-box-shadow: 0 0 5px #aaa;
824 | box-shadow: 0 0 5px #aaa;
825 | }
826 | body .ui-tooltip {
827 | border-width: 2px;
828 | }
829 |
830 | /* Component containers
831 | ----------------------------------*/
832 | .ui-widget {
833 | font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
834 | font-size: 1.1em;
835 | }
836 | .ui-widget .ui-widget {
837 | font-size: 1em;
838 | }
839 | .ui-widget input,
840 | .ui-widget select,
841 | .ui-widget textarea,
842 | .ui-widget button {
843 | font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
844 | font-size: 1em;
845 | }
846 | .ui-widget-content {
847 | border: 1px solid #dddddd;
848 | background: #eeeeee url("images/ui-bg_highlight-soft_100_eeeeee_1x100.png") 50% top repeat-x;
849 | color: #333333;
850 | }
851 | .ui-widget-content a {
852 | color: #333333;
853 | }
854 | .ui-widget-header {
855 | border: 1px solid #e78f08;
856 | background: #f6a828 url("images/ui-bg_gloss-wave_35_f6a828_500x100.png") 50% 50% repeat-x;
857 | color: #ffffff;
858 | font-weight: bold;
859 | }
860 | .ui-widget-header a {
861 | color: #ffffff;
862 | }
863 |
864 | /* Interaction states
865 | ----------------------------------*/
866 | .ui-state-default,
867 | .ui-widget-content .ui-state-default,
868 | .ui-widget-header .ui-state-default {
869 | border: 1px solid #cccccc;
870 | background: #f6f6f6 url("images/ui-bg_glass_100_f6f6f6_1x400.png") 50% 50% repeat-x;
871 | font-weight: bold;
872 | color: #1c94c4;
873 | }
874 | .ui-state-default a,
875 | .ui-state-default a:link,
876 | .ui-state-default a:visited {
877 | color: #1c94c4;
878 | text-decoration: none;
879 | }
880 | .ui-state-hover,
881 | .ui-widget-content .ui-state-hover,
882 | .ui-widget-header .ui-state-hover,
883 | .ui-state-focus,
884 | .ui-widget-content .ui-state-focus,
885 | .ui-widget-header .ui-state-focus {
886 | border: 1px solid #fbcb09;
887 | background: #fdf5ce url("images/ui-bg_glass_100_fdf5ce_1x400.png") 50% 50% repeat-x;
888 | font-weight: bold;
889 | color: #c77405;
890 | }
891 | .ui-state-hover a,
892 | .ui-state-hover a:hover,
893 | .ui-state-hover a:link,
894 | .ui-state-hover a:visited,
895 | .ui-state-focus a,
896 | .ui-state-focus a:hover,
897 | .ui-state-focus a:link,
898 | .ui-state-focus a:visited {
899 | color: #c77405;
900 | text-decoration: none;
901 | }
902 | .ui-state-active,
903 | .ui-widget-content .ui-state-active,
904 | .ui-widget-header .ui-state-active {
905 | border: 1px solid #fbd850;
906 | background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;
907 | font-weight: bold;
908 | color: #eb8f00;
909 | }
910 | .ui-state-active a,
911 | .ui-state-active a:link,
912 | .ui-state-active a:visited {
913 | color: #eb8f00;
914 | text-decoration: none;
915 | }
916 |
917 | /* Interaction Cues
918 | ----------------------------------*/
919 | .ui-state-highlight,
920 | .ui-widget-content .ui-state-highlight,
921 | .ui-widget-header .ui-state-highlight {
922 | border: 1px solid #fed22f;
923 | background: #ffe45c url("images/ui-bg_highlight-soft_75_ffe45c_1x100.png") 50% top repeat-x;
924 | color: #363636;
925 | }
926 | .ui-state-highlight a,
927 | .ui-widget-content .ui-state-highlight a,
928 | .ui-widget-header .ui-state-highlight a {
929 | color: #363636;
930 | }
931 | .ui-state-error,
932 | .ui-widget-content .ui-state-error,
933 | .ui-widget-header .ui-state-error {
934 | border: 1px solid #cd0a0a;
935 | background: #b81900 url("images/ui-bg_diagonals-thick_18_b81900_40x40.png") 50% 50% repeat;
936 | color: #ffffff;
937 | }
938 | .ui-state-error a,
939 | .ui-widget-content .ui-state-error a,
940 | .ui-widget-header .ui-state-error a {
941 | color: #ffffff;
942 | }
943 | .ui-state-error-text,
944 | .ui-widget-content .ui-state-error-text,
945 | .ui-widget-header .ui-state-error-text {
946 | color: #ffffff;
947 | }
948 | .ui-priority-primary,
949 | .ui-widget-content .ui-priority-primary,
950 | .ui-widget-header .ui-priority-primary {
951 | font-weight: bold;
952 | }
953 | .ui-priority-secondary,
954 | .ui-widget-content .ui-priority-secondary,
955 | .ui-widget-header .ui-priority-secondary {
956 | opacity: .7;
957 | filter:Alpha(Opacity=70); /* support: IE8 */
958 | font-weight: normal;
959 | }
960 | .ui-state-disabled,
961 | .ui-widget-content .ui-state-disabled,
962 | .ui-widget-header .ui-state-disabled {
963 | opacity: .35;
964 | filter:Alpha(Opacity=35); /* support: IE8 */
965 | background-image: none;
966 | }
967 | .ui-state-disabled .ui-icon {
968 | filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
969 | }
970 |
971 | /* Icons
972 | ----------------------------------*/
973 |
974 | /* states and images */
975 | .ui-icon {
976 | width: 16px;
977 | height: 16px;
978 | }
979 | .ui-icon,
980 | .ui-widget-content .ui-icon {
981 | background-image: url("images/ui-icons_222222_256x240.png");
982 | }
983 | .ui-widget-header .ui-icon {
984 | background-image: url("images/ui-icons_ffffff_256x240.png");
985 | }
986 | .ui-state-default .ui-icon {
987 | background-image: url("images/ui-icons_ef8c08_256x240.png");
988 | }
989 | .ui-state-hover .ui-icon,
990 | .ui-state-focus .ui-icon {
991 | background-image: url("images/ui-icons_ef8c08_256x240.png");
992 | }
993 | .ui-state-active .ui-icon {
994 | background-image: url("images/ui-icons_ef8c08_256x240.png");
995 | }
996 | .ui-state-highlight .ui-icon {
997 | background-image: url("images/ui-icons_228ef1_256x240.png");
998 | }
999 | .ui-state-error .ui-icon,
1000 | .ui-state-error-text .ui-icon {
1001 | background-image: url("images/ui-icons_ffd27a_256x240.png");
1002 | }
1003 |
1004 | /* positioning */
1005 | .ui-icon-blank { background-position: 16px 16px; }
1006 | .ui-icon-carat-1-n { background-position: 0 0; }
1007 | .ui-icon-carat-1-ne { background-position: -16px 0; }
1008 | .ui-icon-carat-1-e { background-position: -32px 0; }
1009 | .ui-icon-carat-1-se { background-position: -48px 0; }
1010 | .ui-icon-carat-1-s { background-position: -64px 0; }
1011 | .ui-icon-carat-1-sw { background-position: -80px 0; }
1012 | .ui-icon-carat-1-w { background-position: -96px 0; }
1013 | .ui-icon-carat-1-nw { background-position: -112px 0; }
1014 | .ui-icon-carat-2-n-s { background-position: -128px 0; }
1015 | .ui-icon-carat-2-e-w { background-position: -144px 0; }
1016 | .ui-icon-triangle-1-n { background-position: 0 -16px; }
1017 | .ui-icon-triangle-1-ne { background-position: -16px -16px; }
1018 | .ui-icon-triangle-1-e { background-position: -32px -16px; }
1019 | .ui-icon-triangle-1-se { background-position: -48px -16px; }
1020 | .ui-icon-triangle-1-s { background-position: -64px -16px; }
1021 | .ui-icon-triangle-1-sw { background-position: -80px -16px; }
1022 | .ui-icon-triangle-1-w { background-position: -96px -16px; }
1023 | .ui-icon-triangle-1-nw { background-position: -112px -16px; }
1024 | .ui-icon-triangle-2-n-s { background-position: -128px -16px; }
1025 | .ui-icon-triangle-2-e-w { background-position: -144px -16px; }
1026 | .ui-icon-arrow-1-n { background-position: 0 -32px; }
1027 | .ui-icon-arrow-1-ne { background-position: -16px -32px; }
1028 | .ui-icon-arrow-1-e { background-position: -32px -32px; }
1029 | .ui-icon-arrow-1-se { background-position: -48px -32px; }
1030 | .ui-icon-arrow-1-s { background-position: -64px -32px; }
1031 | .ui-icon-arrow-1-sw { background-position: -80px -32px; }
1032 | .ui-icon-arrow-1-w { background-position: -96px -32px; }
1033 | .ui-icon-arrow-1-nw { background-position: -112px -32px; }
1034 | .ui-icon-arrow-2-n-s { background-position: -128px -32px; }
1035 | .ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
1036 | .ui-icon-arrow-2-e-w { background-position: -160px -32px; }
1037 | .ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
1038 | .ui-icon-arrowstop-1-n { background-position: -192px -32px; }
1039 | .ui-icon-arrowstop-1-e { background-position: -208px -32px; }
1040 | .ui-icon-arrowstop-1-s { background-position: -224px -32px; }
1041 | .ui-icon-arrowstop-1-w { background-position: -240px -32px; }
1042 | .ui-icon-arrowthick-1-n { background-position: 0 -48px; }
1043 | .ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
1044 | .ui-icon-arrowthick-1-e { background-position: -32px -48px; }
1045 | .ui-icon-arrowthick-1-se { background-position: -48px -48px; }
1046 | .ui-icon-arrowthick-1-s { background-position: -64px -48px; }
1047 | .ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
1048 | .ui-icon-arrowthick-1-w { background-position: -96px -48px; }
1049 | .ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
1050 | .ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
1051 | .ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
1052 | .ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
1053 | .ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
1054 | .ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
1055 | .ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
1056 | .ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
1057 | .ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
1058 | .ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
1059 | .ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
1060 | .ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
1061 | .ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
1062 | .ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
1063 | .ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
1064 | .ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
1065 | .ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
1066 | .ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
1067 | .ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
1068 | .ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
1069 | .ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
1070 | .ui-icon-arrow-4 { background-position: 0 -80px; }
1071 | .ui-icon-arrow-4-diag { background-position: -16px -80px; }
1072 | .ui-icon-extlink { background-position: -32px -80px; }
1073 | .ui-icon-newwin { background-position: -48px -80px; }
1074 | .ui-icon-refresh { background-position: -64px -80px; }
1075 | .ui-icon-shuffle { background-position: -80px -80px; }
1076 | .ui-icon-transfer-e-w { background-position: -96px -80px; }
1077 | .ui-icon-transferthick-e-w { background-position: -112px -80px; }
1078 | .ui-icon-folder-collapsed { background-position: 0 -96px; }
1079 | .ui-icon-folder-open { background-position: -16px -96px; }
1080 | .ui-icon-document { background-position: -32px -96px; }
1081 | .ui-icon-document-b { background-position: -48px -96px; }
1082 | .ui-icon-note { background-position: -64px -96px; }
1083 | .ui-icon-mail-closed { background-position: -80px -96px; }
1084 | .ui-icon-mail-open { background-position: -96px -96px; }
1085 | .ui-icon-suitcase { background-position: -112px -96px; }
1086 | .ui-icon-comment { background-position: -128px -96px; }
1087 | .ui-icon-person { background-position: -144px -96px; }
1088 | .ui-icon-print { background-position: -160px -96px; }
1089 | .ui-icon-trash { background-position: -176px -96px; }
1090 | .ui-icon-locked { background-position: -192px -96px; }
1091 | .ui-icon-unlocked { background-position: -208px -96px; }
1092 | .ui-icon-bookmark { background-position: -224px -96px; }
1093 | .ui-icon-tag { background-position: -240px -96px; }
1094 | .ui-icon-home { background-position: 0 -112px; }
1095 | .ui-icon-flag { background-position: -16px -112px; }
1096 | .ui-icon-calendar { background-position: -32px -112px; }
1097 | .ui-icon-cart { background-position: -48px -112px; }
1098 | .ui-icon-pencil { background-position: -64px -112px; }
1099 | .ui-icon-clock { background-position: -80px -112px; }
1100 | .ui-icon-disk { background-position: -96px -112px; }
1101 | .ui-icon-calculator { background-position: -112px -112px; }
1102 | .ui-icon-zoomin { background-position: -128px -112px; }
1103 | .ui-icon-zoomout { background-position: -144px -112px; }
1104 | .ui-icon-search { background-position: -160px -112px; }
1105 | .ui-icon-wrench { background-position: -176px -112px; }
1106 | .ui-icon-gear { background-position: -192px -112px; }
1107 | .ui-icon-heart { background-position: -208px -112px; }
1108 | .ui-icon-star { background-position: -224px -112px; }
1109 | .ui-icon-link { background-position: -240px -112px; }
1110 | .ui-icon-cancel { background-position: 0 -128px; }
1111 | .ui-icon-plus { background-position: -16px -128px; }
1112 | .ui-icon-plusthick { background-position: -32px -128px; }
1113 | .ui-icon-minus { background-position: -48px -128px; }
1114 | .ui-icon-minusthick { background-position: -64px -128px; }
1115 | .ui-icon-close { background-position: -80px -128px; }
1116 | .ui-icon-closethick { background-position: -96px -128px; }
1117 | .ui-icon-key { background-position: -112px -128px; }
1118 | .ui-icon-lightbulb { background-position: -128px -128px; }
1119 | .ui-icon-scissors { background-position: -144px -128px; }
1120 | .ui-icon-clipboard { background-position: -160px -128px; }
1121 | .ui-icon-copy { background-position: -176px -128px; }
1122 | .ui-icon-contact { background-position: -192px -128px; }
1123 | .ui-icon-image { background-position: -208px -128px; }
1124 | .ui-icon-video { background-position: -224px -128px; }
1125 | .ui-icon-script { background-position: -240px -128px; }
1126 | .ui-icon-alert { background-position: 0 -144px; }
1127 | .ui-icon-info { background-position: -16px -144px; }
1128 | .ui-icon-notice { background-position: -32px -144px; }
1129 | .ui-icon-help { background-position: -48px -144px; }
1130 | .ui-icon-check { background-position: -64px -144px; }
1131 | .ui-icon-bullet { background-position: -80px -144px; }
1132 | .ui-icon-radio-on { background-position: -96px -144px; }
1133 | .ui-icon-radio-off { background-position: -112px -144px; }
1134 | .ui-icon-pin-w { background-position: -128px -144px; }
1135 | .ui-icon-pin-s { background-position: -144px -144px; }
1136 | .ui-icon-play { background-position: 0 -160px; }
1137 | .ui-icon-pause { background-position: -16px -160px; }
1138 | .ui-icon-seek-next { background-position: -32px -160px; }
1139 | .ui-icon-seek-prev { background-position: -48px -160px; }
1140 | .ui-icon-seek-end { background-position: -64px -160px; }
1141 | .ui-icon-seek-start { background-position: -80px -160px; }
1142 | /* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
1143 | .ui-icon-seek-first { background-position: -80px -160px; }
1144 | .ui-icon-stop { background-position: -96px -160px; }
1145 | .ui-icon-eject { background-position: -112px -160px; }
1146 | .ui-icon-volume-off { background-position: -128px -160px; }
1147 | .ui-icon-volume-on { background-position: -144px -160px; }
1148 | .ui-icon-power { background-position: 0 -176px; }
1149 | .ui-icon-signal-diag { background-position: -16px -176px; }
1150 | .ui-icon-signal { background-position: -32px -176px; }
1151 | .ui-icon-battery-0 { background-position: -48px -176px; }
1152 | .ui-icon-battery-1 { background-position: -64px -176px; }
1153 | .ui-icon-battery-2 { background-position: -80px -176px; }
1154 | .ui-icon-battery-3 { background-position: -96px -176px; }
1155 | .ui-icon-circle-plus { background-position: 0 -192px; }
1156 | .ui-icon-circle-minus { background-position: -16px -192px; }
1157 | .ui-icon-circle-close { background-position: -32px -192px; }
1158 | .ui-icon-circle-triangle-e { background-position: -48px -192px; }
1159 | .ui-icon-circle-triangle-s { background-position: -64px -192px; }
1160 | .ui-icon-circle-triangle-w { background-position: -80px -192px; }
1161 | .ui-icon-circle-triangle-n { background-position: -96px -192px; }
1162 | .ui-icon-circle-arrow-e { background-position: -112px -192px; }
1163 | .ui-icon-circle-arrow-s { background-position: -128px -192px; }
1164 | .ui-icon-circle-arrow-w { background-position: -144px -192px; }
1165 | .ui-icon-circle-arrow-n { background-position: -160px -192px; }
1166 | .ui-icon-circle-zoomin { background-position: -176px -192px; }
1167 | .ui-icon-circle-zoomout { background-position: -192px -192px; }
1168 | .ui-icon-circle-check { background-position: -208px -192px; }
1169 | .ui-icon-circlesmall-plus { background-position: 0 -208px; }
1170 | .ui-icon-circlesmall-minus { background-position: -16px -208px; }
1171 | .ui-icon-circlesmall-close { background-position: -32px -208px; }
1172 | .ui-icon-squaresmall-plus { background-position: -48px -208px; }
1173 | .ui-icon-squaresmall-minus { background-position: -64px -208px; }
1174 | .ui-icon-squaresmall-close { background-position: -80px -208px; }
1175 | .ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
1176 | .ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
1177 | .ui-icon-grip-solid-vertical { background-position: -32px -224px; }
1178 | .ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
1179 | .ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
1180 | .ui-icon-grip-diagonal-se { background-position: -80px -224px; }
1181 |
1182 |
1183 | /* Misc visuals
1184 | ----------------------------------*/
1185 |
1186 | /* Corner radius */
1187 | .ui-corner-all,
1188 | .ui-corner-top,
1189 | .ui-corner-left,
1190 | .ui-corner-tl {
1191 | border-top-left-radius: 4px;
1192 | }
1193 | .ui-corner-all,
1194 | .ui-corner-top,
1195 | .ui-corner-right,
1196 | .ui-corner-tr {
1197 | border-top-right-radius: 4px;
1198 | }
1199 | .ui-corner-all,
1200 | .ui-corner-bottom,
1201 | .ui-corner-left,
1202 | .ui-corner-bl {
1203 | border-bottom-left-radius: 4px;
1204 | }
1205 | .ui-corner-all,
1206 | .ui-corner-bottom,
1207 | .ui-corner-right,
1208 | .ui-corner-br {
1209 | border-bottom-right-radius: 4px;
1210 | }
1211 |
1212 | /* Overlays */
1213 | .ui-widget-overlay {
1214 | background: #666666 url("images/ui-bg_diagonals-thick_20_666666_40x40.png") 50% 50% repeat;
1215 | opacity: .5;
1216 | filter: Alpha(Opacity=50); /* support: IE8 */
1217 | }
1218 | .ui-widget-shadow {
1219 | margin: -5px 0 0 -5px;
1220 | padding: 5px;
1221 | background: #000000 url("images/ui-bg_flat_10_000000_40x100.png") 50% 50% repeat-x;
1222 | opacity: .2;
1223 | filter: Alpha(Opacity=20); /* support: IE8 */
1224 | border-radius: 5px;
1225 | }
1226 |
--------------------------------------------------------------------------------
/public/stylesheets/jquery.tagit.css:
--------------------------------------------------------------------------------
1 | ul.tagit {
2 | padding: 1px 5px;
3 | overflow: auto;
4 | margin-left: inherit; /* usually we don't want the regular ul margins. */
5 | margin-right: inherit;
6 | }
7 | ul.tagit li {
8 | display: block;
9 | float: left;
10 | margin: 2px 5px 2px 0;
11 | }
12 | ul.tagit li.tagit-choice {
13 | position: relative;
14 | line-height: inherit;
15 | }
16 | input.tagit-hidden-field {
17 | display: none;
18 | }
19 | ul.tagit li.tagit-choice-read-only {
20 | padding: .2em .5em .2em .5em;
21 | }
22 |
23 | ul.tagit li.tagit-choice-editable {
24 | padding: .2em 18px .2em .5em;
25 | }
26 |
27 | ul.tagit li.tagit-new {
28 | padding: .25em 4px .25em 0;
29 | }
30 |
31 | ul.tagit li.tagit-choice a.tagit-label {
32 | cursor: pointer;
33 | text-decoration: none;
34 | }
35 | ul.tagit li.tagit-choice .tagit-close {
36 | cursor: pointer;
37 | position: absolute;
38 | right: .1em;
39 | top: 50%;
40 | margin-top: -8px;
41 | line-height: 17px;
42 | }
43 |
44 | /* used for some custom themes that don't need image icons */
45 | ul.tagit li.tagit-choice .tagit-close .text-icon {
46 | display: none;
47 | }
48 |
49 | ul.tagit li.tagit-choice input {
50 | display: block;
51 | float: left;
52 | margin: 2px 5px 2px 0;
53 | }
54 | ul.tagit input[type="text"] {
55 | -moz-box-sizing: border-box;
56 | -webkit-box-sizing: border-box;
57 | box-sizing: border-box;
58 |
59 | -moz-box-shadow: none;
60 | -webkit-box-shadow: none;
61 | box-shadow: none;
62 |
63 | border: none;
64 | margin: 0;
65 | padding: 0;
66 | width: inherit;
67 | background-color: inherit;
68 | outline: none;
69 | }
70 |
--------------------------------------------------------------------------------
/public/stylesheets/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #f4f6f8;
3 | }
4 | a:hover{
5 | text-decoration: none;
6 | }
7 | nav.navbar-white{
8 | background-color: #fff;
9 | -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.05);
10 | box-shadow: 0 1px 1px rgba(0,0,0,0.05);
11 | border:none;
12 | }
13 | .navbar-white .navbar-toggle {
14 | border-color: #ddd;
15 | padding: 5px 10px;
16 | margin-top: 9px;
17 | margin-bottom: 9px;
18 | }
19 | .nav-menu-name{
20 | color: #3e82c1;
21 | }
22 |
23 | .alert-required{
24 | color:#d58512;
25 | display: none;
26 | }
27 | .success-notice{
28 | color:#5cb85c;
29 | display: none;
30 | }
31 |
32 | .header-container {
33 | max-width: 1150px;
34 | zoom: 1;
35 | margin-right: auto;
36 | margin-left: auto;
37 | }
38 |
39 | .container {
40 | padding-right: 0;
41 | padding-left: 0;
42 | margin-right: auto;
43 | margin-left: auto;
44 | }
45 |
46 |
47 | .sidebar {
48 | font-size: 14px;
49 | }
50 | .panel .inner {
51 | line-height: 2em;
52 | background-color: #fff;
53 | border-radius: 0 0 3px 3px;
54 | }
55 |
56 | .user-avatar {
57 | vertical-align: middle;
58 | margin-right: 5px;
59 | }
60 |
61 | .user-avatar img {
62 | width: 48px;
63 | height: 48px;
64 | }
65 | .user-name {
66 | max-width: 120px;
67 | overflow: hidden;
68 | text-overflow: ellipsis;
69 | white-space: nowrap;
70 | display: inline-block;
71 | vertical-align: middle;
72 | }
73 |
74 | .add-topic-btn{
75 | margin-top:10px;
76 | padding-top:10px;
77 | border-top: 1px solid #e2e2e2;
78 | }
79 |
80 |
81 | .post-list-group>.post-list-item{
82 | margin-bottom: 0px;
83 | border: none;
84 | border-bottom: 1px dashed #ccc;
85 | padding:0;
86 | margin-bottom: 10px;
87 | position:static;
88 | }
89 | .meta{
90 | color: #ABABAB;
91 |
92 | }
93 | .meta a {
94 | text-decoration: none;
95 | }
96 | .meta a:hover{
97 | color:#428bca
98 | }
99 |
100 | .meta i{
101 | margin-left: 5px;
102 | margin-right: 5px;
103 | }
104 | .meta i:first-child{
105 | margin-left: 0;
106 | }
107 | .meta span{
108 | margin-right: 5px;
109 | }
110 | .last-comment-at{
111 | font-size: 15px;
112 | margin: 6px auto;
113 | }
114 | .header-tag{
115 | padding-bottom: 7px;
116 | padding-top: 7px;
117 | min-height:41px;
118 | }
119 | .header-tag span{
120 | margin-right: 10px;
121 | }
122 | .header-tag span.btn{
123 | padding-bottom: 2px;
124 | padding-top: 2px;
125 | padding-left: 5px;
126 | padding-right: 5px;
127 | }
128 | .header-tag span.btn-info a{
129 | color: white;
130 | }
131 | .pagination-container{
132 | text-align: center;
133 | height: auto;
134 | }
135 | .pagination>li>span.page-info{
136 | border: none;
137 | color: #ABABAB;
138 | }
139 | .first-tag{
140 | margin-left: -5px;
141 | }
142 |
143 | .more-tag{
144 | margin-bottom: 5px;
145 | margin-top: 5px;
146 | margin-right: 10px;
147 | }
148 | .sidebar-tag-container{
149 | }
150 | .btn-tags{
151 | margin-right: 5px;
152 | margin-bottom: 5px;
153 | padding-bottom: 2px;
154 | padding-top: 2px;
155 | padding-left: 5px;
156 | padding-right: 5px;
157 | }
158 | .comment-body{
159 | padding: 15px;
160 | }
161 | .comment-header{
162 | border-bottom: 1px dashed #ccc;
163 | }
164 | .comment-list{
165 | margin-bottom: 20px;
166 | }
167 | .comment-list-item{
168 | border-bottom: 1px dashed #ccc;
169 | padding: 10px 0;
170 | }
171 | .post-body img {
172 | width: 100%;
173 | }
174 |
--------------------------------------------------------------------------------
/public/stylesheets/styles.css:
--------------------------------------------------------------------------------
1 | @media screen and (min-width: 992px) {
2 | .the-icons li {
3 | width: 12.5%;
4 | }
5 | }
6 |
7 | .the-buttons > li {
8 | float: left;
9 | height: 80px;
10 | padding: 10px;
11 | border: 1px solid #ddd;
12 | font-size: 12px;
13 | line-height: 1.25;
14 | text-align: center;
15 | -webkit-box-sizing: border-box;
16 | -moz-box-sizing: border-box;
17 | box-sizing: border-box;
18 | margin: 0 -1px -1px 0;
19 | }
20 |
21 | .the-icons .glyphicon {
22 | display: block;
23 | margin: 5px auto;
24 | vertical-align: middle;
25 | margin-right: 3px;
26 | font-size: 24px;
27 | }
28 |
29 | .the-icons li {
30 | float: left;
31 | width: 100px;
32 | height: 110px;
33 | padding: 10px;
34 | border: 1px solid #ddd;
35 | font-size: 12px;
36 | line-height: 1.25;
37 | text-align: center;
38 | -webkit-box-sizing: border-box;
39 | -moz-box-sizing: border-box;
40 | box-sizing: border-box;
41 | margin: 0 auto;
42 | }
43 |
44 | .the-icons, .the-buttons {
45 | list-style:none;
46 | }
47 |
48 | .the-icons .glyphicon {
49 | display:inline;
50 | }
51 |
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 | var topic = require('../controllers/topic');
4 |
5 | /* GET home page. */
6 | router.get('/', topic.home);
7 |
8 | module.exports = router;
9 |
--------------------------------------------------------------------------------
/routes/topic.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 | var auth = require("../middlewares/auth");
4 | var topic = require('../controllers/topic');
5 |
6 |
7 |
8 | /* GET topics listing. */
9 | router.get('/home',topic.home);
10 | router.get('/getTags',topic.getTags);
11 | router.get('/getComments',topic.getComments);
12 |
13 | router.get('/show',auth.getCsrfToken,topic.show);
14 | router.post('/comment',topic.comment);
15 |
16 | router.get('/add',auth.getCsrfToken,auth.requiredLogin,topic.add);
17 | router.post('/create',auth.requiredLogin,topic.create);
18 |
19 |
20 |
21 |
22 | module.exports = router;
23 |
--------------------------------------------------------------------------------
/routes/upload.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 | var auth = require("../middlewares/auth");
4 | var upload = require('../controllers/upload');
5 |
6 |
7 |
8 | /* GET upload listing. */
9 |
10 | router.post('/image',auth.requiredLogin,upload.image);
11 |
12 |
13 | module.exports = router;
14 |
--------------------------------------------------------------------------------
/routes/users.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 | var users = require('../controllers/users');
4 | var auth = require("../middlewares/auth");
5 |
6 |
7 |
8 |
9 | /* GET users listing. */
10 | router.get('/login',auth.getCsrfToken, function(req, res, next) {
11 | res.render('users/login', { title: 'Login' });
12 | });
13 | router.post('/login',users.login);
14 |
15 | router.get('/logout',users.logout);
16 |
17 |
18 | router.get('/register',auth.getCsrfToken, function(req, res, next) {
19 | res.render('users/register', { title: 'Register' });
20 | });
21 | router.post('/register',users.register);
22 |
23 |
24 | router.get('/active', auth.getCsrfToken,function(req, res, next) {
25 | var activeKey = req.param("activeKey");
26 | res.render('users/active', { title: 'Active',activeKey:activeKey });
27 | });
28 | router.post('/active',users.active);
29 |
30 | router.get('/home',users.home);
31 |
32 | router.get('/setting',auth.getCsrfToken,auth.requiredLogin,function(req,res){
33 | res.render('users/setting', { title: "Setting"});
34 | });
35 | router.post('/setting',auth.requiredLogin,users.setting);
36 |
37 | module.exports = router;
38 |
--------------------------------------------------------------------------------
/views/error.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= message
5 | h2= error.status
6 | pre #{error.stack}
7 |
--------------------------------------------------------------------------------
/views/header.jade:
--------------------------------------------------------------------------------
1 | nav.navbar.navbar-white
2 | div.header-container
3 | div.navbar-header
4 | button(type="button" data-toggle="collapse" data-target=".navbar-ex1-collapse").navbar-toggle
5 | span.nav-menu-name Menu
6 | a(href="/").navbar-brand #{webName}
7 | div.collapse.navbar-collapse.navbar-ex1-collapse
8 | ul.nav.navbar-nav.navbar-right
9 | li
10 | a(href="/") Home
11 | if session && session.user
12 | li
13 | a(href="/users/home?userName=#{session.user.userName}") #{session.user.userName}
14 | li
15 | a(href="/users/setting") Setting
16 | li
17 | a(href="/users/logout") Logout
18 | else
19 | li
20 | a(href="/users/login") Login
21 | li
22 | a(href="/users/register") Register
--------------------------------------------------------------------------------
/views/index.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | div.panel.panel-default
5 | div.panel-heading #{title}
6 | div.panel-body This is #{title} page
7 |
8 |
--------------------------------------------------------------------------------
/views/layout.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | meta(http-equiv="content-type" content="text/html; charset=UTF-8")
5 | meta(name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1")
6 | if csrfToken
7 | meta(name="csrfToken" content="#{csrfToken}")
8 |
9 | title= title
10 | link(rel='stylesheet', href='/stylesheets/bootstrap.min.css')
11 | link(rel='stylesheet', href='/stylesheets/styles.css')
12 |
13 | link(rel='stylesheet', href='/stylesheets/jquery-ui.css')
14 | link(rel='stylesheet', href='/stylesheets/jquery.tagit.css')
15 |
16 | link(rel='stylesheet', href='/stylesheets/editor.css')
17 | link(rel='stylesheet', href='/stylesheets/editor-self.css')
18 |
19 | link(rel='stylesheet', href='/stylesheets/main.css')
20 |
21 | body
22 | include header
23 |
24 | div.container
25 | div.main.col-sm-9.clearfix
26 | block content
27 | div.sidebar.col-sm-3.clearfix
28 | include sidebar-user
29 | div.sidebar.col-sm-3.clearfix
30 | include sidebar-tag
31 |
32 | script(src='/javascripts/jquery.min.js')
33 |
34 | script(src='/javascripts/ajax-upload.js')
35 |
36 | script(src='/javascripts/marked.js')
37 | script(src='/javascripts/editor.js')
38 |
39 | script(src='/javascripts/jquery-ui.min.js')
40 | script(src='/javascripts/tag-it.js')
41 |
42 | script(src='/javascripts/bootstrap.min.js')
43 | script(src='/javascripts/common.js')
44 |
45 |
46 |
--------------------------------------------------------------------------------
/views/sidebar-tag.jade:
--------------------------------------------------------------------------------
1 | div.panel.panel-default
2 | div.panel-heading Tags
3 | div.panel-body
4 | div#showTags.sidebar-tag-container
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/views/sidebar-user.jade:
--------------------------------------------------------------------------------
1 | div.panel.panel-default
2 | if session && session.user
3 | div.panel-heading My Account
4 | div.panel-body
5 | div.inner
6 | div
7 | a(href="/users/home?userName=#{session.user.userName}").user-avatar
8 | img(src="#{session.user.userAvatar}")
9 | span.user-name
10 | a(href="/users/home?userName=#{session.user.userName}") #{session.user.userName}
11 | div.add-topic-btn
12 | a(href="/topic/add").btn.btn-primary Add Topic
13 | else
14 | div.panel-heading Wellcome to LuckyBird
15 | div.panel-body
16 | div.inner
17 | p A magic website , builed by Node.JS and Express !
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/views/topic/create.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | div.panel.panel-default
5 | div.panel-heading #{title}
6 | div.panel-body
7 | form#createForm.form-horizontal
8 | div.control-group
9 | label.control-label Title
10 | div.controls
11 | if topic
12 | input(name="_id" value="#{topic._id}").hidden
13 | input(name="title" value="#{topic.title}").form-control.input-xlarge
14 | else
15 | input(name="title").form-control.input-xlarge
16 | div#titleAlert.alert-required This field is required
17 | div.control-group
18 | label.control-label Tags
19 | div.controls
20 | ul#tags-container
21 | if tagsName
22 | input(name="tags" value="#{tagsName}")#tags.hidden
23 | else
24 | input(name="tags" value="")#tags.hidden
25 | span#hasTags.hidden #{hasTags}
26 | div#tagsAlert.alert-required This field is required
27 | div.control-group
28 | label.control-label Content
29 | div.controls
30 | div.editor-wrapper
31 | if topic
32 | textarea(name="markdown" cols="50" rows="10")#editor #{topic.Markdown}
33 | else
34 | textarea(name="markdown" cols="50" rows="10")#editor
35 |
36 | div#contentAlert.alert-required This field is required
37 | div.controls
38 | label.control-label Preview
39 | div#editor-preview-container
40 |
41 | hr
42 | div.form-action
43 | button(type="submit").btn.btn-primary Submit
44 | div#submitAlert.alert-required Email is not right
45 |
46 |
47 | button(data-toggle="modal" data-target="#imageUploadModal" style="display:none")#uploadImageButton
48 | input(type = "file" value="" name="imageInput" style="display:none;")#imageInput
49 |
50 | div#imageUploadModal.modal.fade
51 | div.modal-dialog
52 | div.modal-content
53 | div.modal-header
54 | button(type="button" data-dismiss="modal").close ×
55 | h4.modal-title Add Image
56 | div.modal-body.image-upload-modal-body
57 | div.panel-body
58 | button(type="button").btn.btn-primary.pull-right#imageLocalButton Upload
59 |
60 | div.form-group
61 | label.col-md-4.control-label Image Address
62 | div.col-md-6
63 | input.form-control(name="imageUrl" type="text")#imageUrl
64 | div#uploadAlert.alert-required This field is required
65 | div.modal-footer.image-upload-modal-footer
66 | button#addToPostButton(type="button" data-dismiss="modal").btn.btn-primary Add to Topic
--------------------------------------------------------------------------------
/views/topic/home.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | div.panel.panel-default
5 | div.panel-heading.header-tag
6 | include tag
7 | div.panel-body
8 | include list
9 | include page
10 |
11 |
12 |
--------------------------------------------------------------------------------
/views/topic/list.jade:
--------------------------------------------------------------------------------
1 | div.list-group.post-list-group
2 | if topics.length > 0
3 | each topic in topics
4 | div.pull-right.last-comment-at
5 | div.meta
6 | a(href="/topic/show/?_id=#{topic._id}")
7 | span.timeago #{topic.update_at_ago()}
8 | include topic
9 | else
10 | div.text-center
11 | p There is the no more topic
12 | p
13 | a(href="/topic/add").btn.btn-sm.btn-primary Add Topic
14 |
15 |
--------------------------------------------------------------------------------
/views/topic/page.jade:
--------------------------------------------------------------------------------
1 | div.pagination-container
2 | if pageInfo.prevUrl
3 | ul.pagination.pull-left
4 | li
5 | a(href="#{pageInfo.prevUrl}")
6 | span < Previous
7 | ul.pagination
8 | li
9 | span.page-info #{pageInfo.pageNum} page
10 | if pageInfo.nextUrl
11 | ul.pagination.pull-right
12 | li
13 | a(href="#{pageInfo.nextUrl}")
14 | span Next >
--------------------------------------------------------------------------------
/views/topic/show.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | div.panel.panel-default
5 | div.panel-heading.header-tag
6 | include tag
7 | div.panel-body
8 | div.list-group.post-list-group
9 | if session.user
10 | if session.user.userName == topic.userName
11 | a(href="/topic/add?_id=#{topic._id}").btn.btn-primary.pull-right Edit
12 | include topic
13 | div.post-body !{topic.content}
14 | hr
15 | div.comment-body
16 | div.comment-header
17 | label.control-label Comments
18 | div#showComment.comment-list
19 | form#commentForm.form-horizontal
20 | input(type="hidden" name="topicId" id="topicId" value="#{topic._id}")
21 | div.control-group
22 | div.controls
23 | textarea(name="content" rows="3" cols="50").form-control.input-xlarge
24 | div#contentAlert.alert-required Email is not right
25 | hr
26 | div.control-group
27 | div.controls
28 | button(type="submit").btn.btn-primary Submit
29 | div#submitAlert.alert-required Email is not right
30 |
31 |
32 |
--------------------------------------------------------------------------------
/views/topic/tag.jade:
--------------------------------------------------------------------------------
1 | a(href="/topic/home")
2 | if tagId
3 | span.pull-left.first-tag.btn HOT
4 | else
5 | span.pull-left.first-tag.btn.btn-info HOT
6 | div
7 | each tag in tags
8 | a(href="/topic/home?tagId=#{tag._id}")
9 | if tag._id == tagId
10 | span.btn.btn-info #{tag.showName}
11 | else
12 | span.btn #{tag.showName}
13 |
--------------------------------------------------------------------------------
/views/topic/topic.jade:
--------------------------------------------------------------------------------
1 | div.list-group-item.post-list-item
2 | a(href="/topic/show/?_id=#{topic._id}")
3 | h4.list-group-item-heading #{topic.title}
4 | div.meta
5 | i.glyphicon.glyphicon-eye-open
6 | span #{topic.openCount}
7 | i.glyphicon.glyphicon-comment
8 | span #{topic.commentCount}
9 | i.glyphicon.glyphicon-user
10 | a(href="/users/home?userName=#{topic.userName}")
11 | span #{topic.userName}
12 |
--------------------------------------------------------------------------------
/views/users/active.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | div.panel.panel-default
5 | div.panel-heading #{title}
6 | div.panel-body
7 | form#activeForm.form-horizontal
8 | input(type="hidden" name="activeKey" value="#{activeKey}")
9 | div.form-group
10 | label.col-md-4.control-label Email
11 | div.col-md-3
12 | input(name="userEmail").form-control
13 | div#userEmailAlert.alert-required Email is not right
14 | div#userEmailSuccess.success-notice Email is not right
15 | div.form-group
16 | div.col-md-6.col-md-offset-4
17 | button(type="submit").btn.btn-primary Active
18 | div#submitAlert.alert-required Email is not right
19 | div.form-group
20 | div.col-md-6.col-md-offset-4
21 | a(href="/users/login") Go to Login
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/views/users/home.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | div.panel.panel-default
5 | div.panel-heading User Info
6 | div.panel-body
7 | div.inner
8 | div
9 | a(href="/users/home?userName=#{user.userName}").user-avatar
10 | img(src="#{user.userAvatar}")
11 | span.user-name
12 | a(href="/users/home?userName=#{user.userName}") #{user.userName}
13 |
14 |
15 | div.panel.panel-default
16 | div.panel-heading User Topic
17 | div.panel-body
18 | include ../topic/list
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/views/users/login.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | div.panel.panel-default
5 | div.panel-heading #{title}
6 | div.panel-body
7 | form#loginForm.form-horizontal
8 | div.form-group
9 | label.col-md-4.control-label Email
10 | div.col-md-3
11 | input(name="userEmail").form-control
12 | div#userEmailAlert.alert-required Email is not right
13 | div.form-group
14 | label.col-md-4.control-label Password
15 | div.col-md-3
16 | input(name="passWord" type="password").form-control
17 | div#passWordAlert.alert-required This field is required
18 | div.form-group
19 | div.col-md-6.col-md-offset-4
20 | button(type="submit").btn.btn-primary Login
21 | div#submitAlert.alert-required Email is not right
22 | div.form-group
23 | div.col-md-6.col-md-offset-4
24 | a(href="/users/register") Go to Register
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/views/users/register.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | div.panel.panel-default
5 | div.panel-heading #{title}
6 | div.panel-body
7 | form#registerForm.form-horizontal
8 | div.form-group
9 | label.col-md-4.control-label Name
10 | div.col-md-3
11 | input(name="userName").form-control
12 | div#userNameAlert.alert-required This field is required
13 | div.form-group
14 | label.col-md-4.control-label Email
15 | div.col-md-3
16 | input(name="userEmail").form-control
17 | div#userEmailAlert.alert-required Email is not right
18 | div.form-group
19 | label.col-md-4.control-label Password
20 | div.col-md-3
21 | input(name="passWord" type="password").form-control
22 | div#passWordAlert.alert-required This field is required
23 | div.form-group
24 | label.col-md-4.control-label Confirm Password
25 | div.col-md-3
26 | input(name="confirmPassword" type="password").form-control
27 | div#confirmPasswordAlert.alert-required confirmPassword must be same as password
28 | div.form-group
29 | div.col-md-6.col-md-offset-4
30 | button(type="submit").btn.btn-primary Register
31 | div#submitAlert.alert-required Email is not right
32 | div.form-group
33 | div.col-md-6.col-md-offset-4
34 | a(href="/users/login") Go to Login
35 |
36 |
37 |
--------------------------------------------------------------------------------
/views/users/setting.jade:
--------------------------------------------------------------------------------
1 | extends ../layout
2 |
3 | block content
4 | div.panel.panel-default
5 | div.panel-heading #{title}
6 | div.panel-body
7 | form#settingForm.form-horizontal
8 |
9 | div.form-group
10 | label.col-md-4.control-label Avatar
11 | div.col-md-3
12 | div.inner
13 | div
14 | a(href="/users/home").user-avatar
15 | img(src="#{session.user.userAvatar}")#avatar
16 | input(name="userAvatar" id="userAvatar" value="#{session.user.userAvatar}" type="hidden")
17 | span
18 | button(type="button" id="uploadUserAvatar").btn.btn-default.btn-sm Upload
19 | input(name="imageInput" id="imageInput" type="file" style="display:none")
20 |
21 | div#uploadAlert.alert-required This field is required
22 |
23 | div.form-group
24 | label.col-md-4.control-label Name
25 | div.col-md-3
26 | input(name="userName" value="#{session.user.userName}").form-control
27 | div#userNameAlert.alert-required This field is required
28 | div.form-group
29 | label.col-md-4.control-label Email
30 | div.col-md-3
31 | input(name="userEmail" value="#{session.user.userEmail}" readonly="readonly").form-control
32 | div#userEmailAlert.alert-required Email is not right
33 | div.form-group
34 | div.col-md-6.col-md-offset-4
35 | button(type="submit").btn.btn-primary Submit
36 | div#submitAlert.alert-required Email is not right
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
"+comment.content+"
"; 150 | str+="