├── README.md ├── app.js ├── models ├── db.js ├── post.js └── user.js ├── package.json ├── public ├── img │ ├── glyphicons-halflings-white.png │ └── glyphicons-halflings.png ├── javascripts │ ├── bootstrap.js │ └── jquery.js └── stylesheets │ ├── bootstrap-responsive.css │ └── bootstrap.css ├── routes └── index.js ├── settings.js └── views ├── index.ejs ├── layout.ejs ├── login.ejs ├── posts.ejs ├── reg.ejs ├── say.ejs └── user.ejs /README.md: -------------------------------------------------------------------------------- 1 | # Microblog 2 | 3 | A tiny mircoblog system using Node.js for learning purposes. 4 | 5 | https://www.byvoid.com/project/node 6 | 7 | Node.js開發指南 8 | 9 | ![](https://www.byvoid.com/upload/wp/2012/07/%E5%B0%81%E9%9D%A2-232x300.jpg) 10 | ![](https://www.byvoid.com/upload/wp/2012/07/%E5%B0%81%E5%BA%95-222x300.jpg) 11 | 12 | 臺灣版 13 | 14 | ![](https://www.byvoid.com/upload/projects/node/node-tw-s.jpg) 15 | 16 | ## 注意 17 | 18 | 此項目以Express2.x爲基礎,請注意使用`package.json`中的版本。 19 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | var express = require('express'); 6 | var routes = require('./routes'); 7 | var settings = require('./settings'); 8 | var MongoStore = require('connect-mongo'); 9 | 10 | var app = module.exports = express.createServer(); 11 | 12 | // Configuration 13 | 14 | app.configure(function(){ 15 | app.set('views', __dirname + '/views'); 16 | app.set('view engine', 'ejs'); 17 | app.use(express.bodyParser()); 18 | app.use(express.methodOverride()); 19 | app.use(express.cookieParser()); 20 | app.use(express.session({ 21 | secret: settings.cookieSecret, 22 | store: new MongoStore({ 23 | db: settings.db 24 | }) 25 | })); 26 | app.use(express.router(routes)); 27 | app.use(express.static(__dirname + '/public')); 28 | }); 29 | 30 | app.configure('development', function(){ 31 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 32 | }); 33 | 34 | app.configure('production', function(){ 35 | app.use(express.errorHandler()); 36 | }); 37 | 38 | app.dynamicHelpers({ 39 | user: function(req, res) { 40 | return req.session.user; 41 | }, 42 | error: function(req, res) { 43 | var err = req.flash('error'); 44 | if (err.length) 45 | return err; 46 | else 47 | return null; 48 | }, 49 | success: function(req, res) { 50 | var succ = req.flash('success'); 51 | if (succ.length) 52 | return succ; 53 | else 54 | return null; 55 | }, 56 | }); 57 | 58 | app.listen(3000); 59 | console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env); 60 | -------------------------------------------------------------------------------- /models/db.js: -------------------------------------------------------------------------------- 1 | var settings = require('../settings'); 2 | var Db = require('mongodb').Db; 3 | var Connection = require('mongodb').Connection; 4 | var Server = require('mongodb').Server; 5 | 6 | module.exports = new Db(settings.db, new Server(settings.host, Connection.DEFAULT_PORT, {})); 7 | -------------------------------------------------------------------------------- /models/post.js: -------------------------------------------------------------------------------- 1 | var mongodb = require('./db'); 2 | 3 | function Post(username, post, time) { 4 | this.user = username; 5 | this.post = post; 6 | if (time) { 7 | this.time = time; 8 | } else { 9 | this.time = new Date(); 10 | } 11 | }; 12 | module.exports = Post; 13 | 14 | Post.prototype.save = function save(callback) { 15 | // 存入 Mongodb 的文檔 16 | var post = { 17 | user: this.user, 18 | post: this.post, 19 | time: this.time, 20 | }; 21 | mongodb.open(function(err, db) { 22 | if (err) { 23 | return callback(err); 24 | } 25 | // 讀取 posts 集合 26 | db.collection('posts', function(err, collection) { 27 | if (err) { 28 | mongodb.close(); 29 | return callback(err); 30 | } 31 | // 爲 user 屬性添加索引 32 | collection.ensureIndex('user'); 33 | // 寫入 post 文檔 34 | collection.insert(post, {safe: true}, function(err, post) { 35 | mongodb.close(); 36 | callback(err, post); 37 | }); 38 | }); 39 | }); 40 | }; 41 | 42 | Post.get = function get(username, callback) { 43 | mongodb.open(function(err, db) { 44 | if (err) { 45 | return callback(err); 46 | } 47 | // 讀取 posts 集合 48 | db.collection('posts', function(err, collection) { 49 | if (err) { 50 | mongodb.close(); 51 | return callback(err); 52 | } 53 | // 查找 user 屬性爲 username 的文檔,如果 username 是 null 則匹配全部 54 | var query = {}; 55 | if (username) { 56 | query.user = username; 57 | } 58 | collection.find(query).sort({time: -1}).toArray(function(err, docs) { 59 | mongodb.close(); 60 | if (err) { 61 | callback(err, null); 62 | } 63 | // 封裝 posts 爲 Post 對象 64 | var posts = []; 65 | docs.forEach(function(doc, index) { 66 | var post = new Post(doc.user, doc.post, doc.time); 67 | posts.push(post); 68 | }); 69 | callback(null, posts); 70 | }); 71 | }); 72 | }); 73 | }; 74 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | var mongodb = require('./db'); 2 | 3 | function User(user) { 4 | this.name = user.name; 5 | this.password = user.password; 6 | }; 7 | module.exports = User; 8 | 9 | User.prototype.save = function save(callback) { 10 | // 存入 Mongodb 的文檔 11 | var user = { 12 | name: this.name, 13 | password: this.password, 14 | }; 15 | mongodb.open(function(err, db) { 16 | if (err) { 17 | return callback(err); 18 | } 19 | // 讀取 users 集合 20 | db.collection('users', function(err, collection) { 21 | if (err) { 22 | mongodb.close(); 23 | return callback(err); 24 | } 25 | // 爲 name 屬性添加索引 26 | collection.ensureIndex('name', {unique: true}); 27 | // 寫入 user 文檔 28 | collection.insert(user, {safe: true}, function(err, user) { 29 | mongodb.close(); 30 | callback(err, user); 31 | }); 32 | }); 33 | }); 34 | }; 35 | 36 | User.get = function get(username, callback) { 37 | mongodb.open(function(err, db) { 38 | if (err) { 39 | return callback(err); 40 | } 41 | // 讀取 users 集合 42 | db.collection('users', function(err, collection) { 43 | if (err) { 44 | mongodb.close(); 45 | return callback(err); 46 | } 47 | // 查找 name 屬性爲 username 的文檔 48 | collection.findOne({name: username}, function(err, doc) { 49 | mongodb.close(); 50 | if (doc) { 51 | // 封裝文檔爲 User 對象 52 | var user = new User(doc); 53 | callback(err, user); 54 | } else { 55 | callback(err, null); 56 | } 57 | }); 58 | }); 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "microblog" 3 | , "version": "0.0.1" 4 | , "private": true 5 | , "dependencies": { 6 | "express": "2.5.8" 7 | , "ejs": "0.0.1" 8 | , "connect-mongo": "0.1.7" 9 | , "mongodb": "0.9.9" 10 | } 11 | } -------------------------------------------------------------------------------- /public/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BYVoid/microblog/189c22f31248ebece319c1892b798fad09e074c3/public/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /public/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BYVoid/microblog/189c22f31248ebece319c1892b798fad09e074c3/public/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /public/javascripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | /* =================================================== 2 | * bootstrap-transition.js v2.0.1 3 | * http://twitter.github.com/bootstrap/javascript.html#transitions 4 | * =================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | !function( $ ) { 21 | 22 | $(function () { 23 | 24 | "use strict" 25 | 26 | /* CSS TRANSITION SUPPORT (https://gist.github.com/373874) 27 | * ======================================================= */ 28 | 29 | $.support.transition = (function () { 30 | var thisBody = document.body || document.documentElement 31 | , thisStyle = thisBody.style 32 | , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined 33 | 34 | return support && { 35 | end: (function () { 36 | var transitionEnd = "TransitionEnd" 37 | if ( $.browser.webkit ) { 38 | transitionEnd = "webkitTransitionEnd" 39 | } else if ( $.browser.mozilla ) { 40 | transitionEnd = "transitionend" 41 | } else if ( $.browser.opera ) { 42 | transitionEnd = "oTransitionEnd" 43 | } 44 | return transitionEnd 45 | }()) 46 | } 47 | })() 48 | 49 | }) 50 | 51 | }( window.jQuery );/* ========================================================== 52 | * bootstrap-alert.js v2.0.1 53 | * http://twitter.github.com/bootstrap/javascript.html#alerts 54 | * ========================================================== 55 | * Copyright 2012 Twitter, Inc. 56 | * 57 | * Licensed under the Apache License, Version 2.0 (the "License"); 58 | * you may not use this file except in compliance with the License. 59 | * You may obtain a copy of the License at 60 | * 61 | * http://www.apache.org/licenses/LICENSE-2.0 62 | * 63 | * Unless required by applicable law or agreed to in writing, software 64 | * distributed under the License is distributed on an "AS IS" BASIS, 65 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 66 | * See the License for the specific language governing permissions and 67 | * limitations under the License. 68 | * ========================================================== */ 69 | 70 | 71 | !function( $ ){ 72 | 73 | "use strict" 74 | 75 | /* ALERT CLASS DEFINITION 76 | * ====================== */ 77 | 78 | var dismiss = '[data-dismiss="alert"]' 79 | , Alert = function ( el ) { 80 | $(el).on('click', dismiss, this.close) 81 | } 82 | 83 | Alert.prototype = { 84 | 85 | constructor: Alert 86 | 87 | , close: function ( e ) { 88 | var $this = $(this) 89 | , selector = $this.attr('data-target') 90 | , $parent 91 | 92 | if (!selector) { 93 | selector = $this.attr('href') 94 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 95 | } 96 | 97 | $parent = $(selector) 98 | $parent.trigger('close') 99 | 100 | e && e.preventDefault() 101 | 102 | $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) 103 | 104 | $parent 105 | .trigger('close') 106 | .removeClass('in') 107 | 108 | function removeElement() { 109 | $parent 110 | .trigger('closed') 111 | .remove() 112 | } 113 | 114 | $.support.transition && $parent.hasClass('fade') ? 115 | $parent.on($.support.transition.end, removeElement) : 116 | removeElement() 117 | } 118 | 119 | } 120 | 121 | 122 | /* ALERT PLUGIN DEFINITION 123 | * ======================= */ 124 | 125 | $.fn.alert = function ( option ) { 126 | return this.each(function () { 127 | var $this = $(this) 128 | , data = $this.data('alert') 129 | if (!data) $this.data('alert', (data = new Alert(this))) 130 | if (typeof option == 'string') data[option].call($this) 131 | }) 132 | } 133 | 134 | $.fn.alert.Constructor = Alert 135 | 136 | 137 | /* ALERT DATA-API 138 | * ============== */ 139 | 140 | $(function () { 141 | $('body').on('click.alert.data-api', dismiss, Alert.prototype.close) 142 | }) 143 | 144 | }( window.jQuery );/* ============================================================ 145 | * bootstrap-button.js v2.0.1 146 | * http://twitter.github.com/bootstrap/javascript.html#buttons 147 | * ============================================================ 148 | * Copyright 2012 Twitter, Inc. 149 | * 150 | * Licensed under the Apache License, Version 2.0 (the "License"); 151 | * you may not use this file except in compliance with the License. 152 | * You may obtain a copy of the License at 153 | * 154 | * http://www.apache.org/licenses/LICENSE-2.0 155 | * 156 | * Unless required by applicable law or agreed to in writing, software 157 | * distributed under the License is distributed on an "AS IS" BASIS, 158 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 159 | * See the License for the specific language governing permissions and 160 | * limitations under the License. 161 | * ============================================================ */ 162 | 163 | !function( $ ){ 164 | 165 | "use strict" 166 | 167 | /* BUTTON PUBLIC CLASS DEFINITION 168 | * ============================== */ 169 | 170 | var Button = function ( element, options ) { 171 | this.$element = $(element) 172 | this.options = $.extend({}, $.fn.button.defaults, options) 173 | } 174 | 175 | Button.prototype = { 176 | 177 | constructor: Button 178 | 179 | , setState: function ( state ) { 180 | var d = 'disabled' 181 | , $el = this.$element 182 | , data = $el.data() 183 | , val = $el.is('input') ? 'val' : 'html' 184 | 185 | state = state + 'Text' 186 | data.resetText || $el.data('resetText', $el[val]()) 187 | 188 | $el[val](data[state] || this.options[state]) 189 | 190 | // push to event loop to allow forms to submit 191 | setTimeout(function () { 192 | state == 'loadingText' ? 193 | $el.addClass(d).attr(d, d) : 194 | $el.removeClass(d).removeAttr(d) 195 | }, 0) 196 | } 197 | 198 | , toggle: function () { 199 | var $parent = this.$element.parent('[data-toggle="buttons-radio"]') 200 | 201 | $parent && $parent 202 | .find('.active') 203 | .removeClass('active') 204 | 205 | this.$element.toggleClass('active') 206 | } 207 | 208 | } 209 | 210 | 211 | /* BUTTON PLUGIN DEFINITION 212 | * ======================== */ 213 | 214 | $.fn.button = function ( option ) { 215 | return this.each(function () { 216 | var $this = $(this) 217 | , data = $this.data('button') 218 | , options = typeof option == 'object' && option 219 | if (!data) $this.data('button', (data = new Button(this, options))) 220 | if (option == 'toggle') data.toggle() 221 | else if (option) data.setState(option) 222 | }) 223 | } 224 | 225 | $.fn.button.defaults = { 226 | loadingText: 'loading...' 227 | } 228 | 229 | $.fn.button.Constructor = Button 230 | 231 | 232 | /* BUTTON DATA-API 233 | * =============== */ 234 | 235 | $(function () { 236 | $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) { 237 | var $btn = $(e.target) 238 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') 239 | $btn.button('toggle') 240 | }) 241 | }) 242 | 243 | }( window.jQuery );/* ========================================================== 244 | * bootstrap-carousel.js v2.0.1 245 | * http://twitter.github.com/bootstrap/javascript.html#carousel 246 | * ========================================================== 247 | * Copyright 2012 Twitter, Inc. 248 | * 249 | * Licensed under the Apache License, Version 2.0 (the "License"); 250 | * you may not use this file except in compliance with the License. 251 | * You may obtain a copy of the License at 252 | * 253 | * http://www.apache.org/licenses/LICENSE-2.0 254 | * 255 | * Unless required by applicable law or agreed to in writing, software 256 | * distributed under the License is distributed on an "AS IS" BASIS, 257 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 258 | * See the License for the specific language governing permissions and 259 | * limitations under the License. 260 | * ========================================================== */ 261 | 262 | 263 | !function( $ ){ 264 | 265 | "use strict" 266 | 267 | /* CAROUSEL CLASS DEFINITION 268 | * ========================= */ 269 | 270 | var Carousel = function (element, options) { 271 | this.$element = $(element) 272 | this.options = $.extend({}, $.fn.carousel.defaults, options) 273 | this.options.slide && this.slide(this.options.slide) 274 | this.options.pause == 'hover' && this.$element 275 | .on('mouseenter', $.proxy(this.pause, this)) 276 | .on('mouseleave', $.proxy(this.cycle, this)) 277 | } 278 | 279 | Carousel.prototype = { 280 | 281 | cycle: function () { 282 | this.interval = setInterval($.proxy(this.next, this), this.options.interval) 283 | return this 284 | } 285 | 286 | , to: function (pos) { 287 | var $active = this.$element.find('.active') 288 | , children = $active.parent().children() 289 | , activePos = children.index($active) 290 | , that = this 291 | 292 | if (pos > (children.length - 1) || pos < 0) return 293 | 294 | if (this.sliding) { 295 | return this.$element.one('slid', function () { 296 | that.to(pos) 297 | }) 298 | } 299 | 300 | if (activePos == pos) { 301 | return this.pause().cycle() 302 | } 303 | 304 | return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) 305 | } 306 | 307 | , pause: function () { 308 | clearInterval(this.interval) 309 | this.interval = null 310 | return this 311 | } 312 | 313 | , next: function () { 314 | if (this.sliding) return 315 | return this.slide('next') 316 | } 317 | 318 | , prev: function () { 319 | if (this.sliding) return 320 | return this.slide('prev') 321 | } 322 | 323 | , slide: function (type, next) { 324 | var $active = this.$element.find('.active') 325 | , $next = next || $active[type]() 326 | , isCycling = this.interval 327 | , direction = type == 'next' ? 'left' : 'right' 328 | , fallback = type == 'next' ? 'first' : 'last' 329 | , that = this 330 | 331 | this.sliding = true 332 | 333 | isCycling && this.pause() 334 | 335 | $next = $next.length ? $next : this.$element.find('.item')[fallback]() 336 | 337 | if ($next.hasClass('active')) return 338 | 339 | if (!$.support.transition && this.$element.hasClass('slide')) { 340 | this.$element.trigger('slide') 341 | $active.removeClass('active') 342 | $next.addClass('active') 343 | this.sliding = false 344 | this.$element.trigger('slid') 345 | } else { 346 | $next.addClass(type) 347 | $next[0].offsetWidth // force reflow 348 | $active.addClass(direction) 349 | $next.addClass(direction) 350 | this.$element.trigger('slide') 351 | this.$element.one($.support.transition.end, function () { 352 | $next.removeClass([type, direction].join(' ')).addClass('active') 353 | $active.removeClass(['active', direction].join(' ')) 354 | that.sliding = false 355 | setTimeout(function () { that.$element.trigger('slid') }, 0) 356 | }) 357 | } 358 | 359 | isCycling && this.cycle() 360 | 361 | return this 362 | } 363 | 364 | } 365 | 366 | 367 | /* CAROUSEL PLUGIN DEFINITION 368 | * ========================== */ 369 | 370 | $.fn.carousel = function ( option ) { 371 | return this.each(function () { 372 | var $this = $(this) 373 | , data = $this.data('carousel') 374 | , options = typeof option == 'object' && option 375 | if (!data) $this.data('carousel', (data = new Carousel(this, options))) 376 | if (typeof option == 'number') data.to(option) 377 | else if (typeof option == 'string' || (option = options.slide)) data[option]() 378 | else data.cycle() 379 | }) 380 | } 381 | 382 | $.fn.carousel.defaults = { 383 | interval: 5000 384 | , pause: 'hover' 385 | } 386 | 387 | $.fn.carousel.Constructor = Carousel 388 | 389 | 390 | /* CAROUSEL DATA-API 391 | * ================= */ 392 | 393 | $(function () { 394 | $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) { 395 | var $this = $(this), href 396 | , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 397 | , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data()) 398 | $target.carousel(options) 399 | e.preventDefault() 400 | }) 401 | }) 402 | 403 | }( window.jQuery );/* ============================================================= 404 | * bootstrap-collapse.js v2.0.1 405 | * http://twitter.github.com/bootstrap/javascript.html#collapse 406 | * ============================================================= 407 | * Copyright 2012 Twitter, Inc. 408 | * 409 | * Licensed under the Apache License, Version 2.0 (the "License"); 410 | * you may not use this file except in compliance with the License. 411 | * You may obtain a copy of the License at 412 | * 413 | * http://www.apache.org/licenses/LICENSE-2.0 414 | * 415 | * Unless required by applicable law or agreed to in writing, software 416 | * distributed under the License is distributed on an "AS IS" BASIS, 417 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 418 | * See the License for the specific language governing permissions and 419 | * limitations under the License. 420 | * ============================================================ */ 421 | 422 | !function( $ ){ 423 | 424 | "use strict" 425 | 426 | var Collapse = function ( element, options ) { 427 | this.$element = $(element) 428 | this.options = $.extend({}, $.fn.collapse.defaults, options) 429 | 430 | if (this.options["parent"]) { 431 | this.$parent = $(this.options["parent"]) 432 | } 433 | 434 | this.options.toggle && this.toggle() 435 | } 436 | 437 | Collapse.prototype = { 438 | 439 | constructor: Collapse 440 | 441 | , dimension: function () { 442 | var hasWidth = this.$element.hasClass('width') 443 | return hasWidth ? 'width' : 'height' 444 | } 445 | 446 | , show: function () { 447 | var dimension = this.dimension() 448 | , scroll = $.camelCase(['scroll', dimension].join('-')) 449 | , actives = this.$parent && this.$parent.find('.in') 450 | , hasData 451 | 452 | if (actives && actives.length) { 453 | hasData = actives.data('collapse') 454 | actives.collapse('hide') 455 | hasData || actives.data('collapse', null) 456 | } 457 | 458 | this.$element[dimension](0) 459 | this.transition('addClass', 'show', 'shown') 460 | this.$element[dimension](this.$element[0][scroll]) 461 | 462 | } 463 | 464 | , hide: function () { 465 | var dimension = this.dimension() 466 | this.reset(this.$element[dimension]()) 467 | this.transition('removeClass', 'hide', 'hidden') 468 | this.$element[dimension](0) 469 | } 470 | 471 | , reset: function ( size ) { 472 | var dimension = this.dimension() 473 | 474 | this.$element 475 | .removeClass('collapse') 476 | [dimension](size || 'auto') 477 | [0].offsetWidth 478 | 479 | this.$element[size ? 'addClass' : 'removeClass']('collapse') 480 | 481 | return this 482 | } 483 | 484 | , transition: function ( method, startEvent, completeEvent ) { 485 | var that = this 486 | , complete = function () { 487 | if (startEvent == 'show') that.reset() 488 | that.$element.trigger(completeEvent) 489 | } 490 | 491 | this.$element 492 | .trigger(startEvent) 493 | [method]('in') 494 | 495 | $.support.transition && this.$element.hasClass('collapse') ? 496 | this.$element.one($.support.transition.end, complete) : 497 | complete() 498 | } 499 | 500 | , toggle: function () { 501 | this[this.$element.hasClass('in') ? 'hide' : 'show']() 502 | } 503 | 504 | } 505 | 506 | /* COLLAPSIBLE PLUGIN DEFINITION 507 | * ============================== */ 508 | 509 | $.fn.collapse = function ( option ) { 510 | return this.each(function () { 511 | var $this = $(this) 512 | , data = $this.data('collapse') 513 | , options = typeof option == 'object' && option 514 | if (!data) $this.data('collapse', (data = new Collapse(this, options))) 515 | if (typeof option == 'string') data[option]() 516 | }) 517 | } 518 | 519 | $.fn.collapse.defaults = { 520 | toggle: true 521 | } 522 | 523 | $.fn.collapse.Constructor = Collapse 524 | 525 | 526 | /* COLLAPSIBLE DATA-API 527 | * ==================== */ 528 | 529 | $(function () { 530 | $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) { 531 | var $this = $(this), href 532 | , target = $this.attr('data-target') 533 | || e.preventDefault() 534 | || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 535 | , option = $(target).data('collapse') ? 'toggle' : $this.data() 536 | $(target).collapse(option) 537 | }) 538 | }) 539 | 540 | }( window.jQuery );/* ============================================================ 541 | * bootstrap-dropdown.js v2.0.1 542 | * http://twitter.github.com/bootstrap/javascript.html#dropdowns 543 | * ============================================================ 544 | * Copyright 2012 Twitter, Inc. 545 | * 546 | * Licensed under the Apache License, Version 2.0 (the "License"); 547 | * you may not use this file except in compliance with the License. 548 | * You may obtain a copy of the License at 549 | * 550 | * http://www.apache.org/licenses/LICENSE-2.0 551 | * 552 | * Unless required by applicable law or agreed to in writing, software 553 | * distributed under the License is distributed on an "AS IS" BASIS, 554 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 555 | * See the License for the specific language governing permissions and 556 | * limitations under the License. 557 | * ============================================================ */ 558 | 559 | 560 | !function( $ ){ 561 | 562 | "use strict" 563 | 564 | /* DROPDOWN CLASS DEFINITION 565 | * ========================= */ 566 | 567 | var toggle = '[data-toggle="dropdown"]' 568 | , Dropdown = function ( element ) { 569 | var $el = $(element).on('click.dropdown.data-api', this.toggle) 570 | $('html').on('click.dropdown.data-api', function () { 571 | $el.parent().removeClass('open') 572 | }) 573 | } 574 | 575 | Dropdown.prototype = { 576 | 577 | constructor: Dropdown 578 | 579 | , toggle: function ( e ) { 580 | var $this = $(this) 581 | , selector = $this.attr('data-target') 582 | , $parent 583 | , isActive 584 | 585 | if (!selector) { 586 | selector = $this.attr('href') 587 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 588 | } 589 | 590 | $parent = $(selector) 591 | $parent.length || ($parent = $this.parent()) 592 | 593 | isActive = $parent.hasClass('open') 594 | 595 | clearMenus() 596 | !isActive && $parent.toggleClass('open') 597 | 598 | return false 599 | } 600 | 601 | } 602 | 603 | function clearMenus() { 604 | $(toggle).parent().removeClass('open') 605 | } 606 | 607 | 608 | /* DROPDOWN PLUGIN DEFINITION 609 | * ========================== */ 610 | 611 | $.fn.dropdown = function ( option ) { 612 | return this.each(function () { 613 | var $this = $(this) 614 | , data = $this.data('dropdown') 615 | if (!data) $this.data('dropdown', (data = new Dropdown(this))) 616 | if (typeof option == 'string') data[option].call($this) 617 | }) 618 | } 619 | 620 | $.fn.dropdown.Constructor = Dropdown 621 | 622 | 623 | /* APPLY TO STANDARD DROPDOWN ELEMENTS 624 | * =================================== */ 625 | 626 | $(function () { 627 | $('html').on('click.dropdown.data-api', clearMenus) 628 | $('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) 629 | }) 630 | 631 | }( window.jQuery );/* ========================================================= 632 | * bootstrap-modal.js v2.0.1 633 | * http://twitter.github.com/bootstrap/javascript.html#modals 634 | * ========================================================= 635 | * Copyright 2012 Twitter, Inc. 636 | * 637 | * Licensed under the Apache License, Version 2.0 (the "License"); 638 | * you may not use this file except in compliance with the License. 639 | * You may obtain a copy of the License at 640 | * 641 | * http://www.apache.org/licenses/LICENSE-2.0 642 | * 643 | * Unless required by applicable law or agreed to in writing, software 644 | * distributed under the License is distributed on an "AS IS" BASIS, 645 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 646 | * See the License for the specific language governing permissions and 647 | * limitations under the License. 648 | * ========================================================= */ 649 | 650 | 651 | !function( $ ){ 652 | 653 | "use strict" 654 | 655 | /* MODAL CLASS DEFINITION 656 | * ====================== */ 657 | 658 | var Modal = function ( content, options ) { 659 | this.options = options 660 | this.$element = $(content) 661 | .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) 662 | } 663 | 664 | Modal.prototype = { 665 | 666 | constructor: Modal 667 | 668 | , toggle: function () { 669 | return this[!this.isShown ? 'show' : 'hide']() 670 | } 671 | 672 | , show: function () { 673 | var that = this 674 | 675 | if (this.isShown) return 676 | 677 | $('body').addClass('modal-open') 678 | 679 | this.isShown = true 680 | this.$element.trigger('show') 681 | 682 | escape.call(this) 683 | backdrop.call(this, function () { 684 | var transition = $.support.transition && that.$element.hasClass('fade') 685 | 686 | !that.$element.parent().length && that.$element.appendTo(document.body) //don't move modals dom position 687 | 688 | that.$element 689 | .show() 690 | 691 | if (transition) { 692 | that.$element[0].offsetWidth // force reflow 693 | } 694 | 695 | that.$element.addClass('in') 696 | 697 | transition ? 698 | that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : 699 | that.$element.trigger('shown') 700 | 701 | }) 702 | } 703 | 704 | , hide: function ( e ) { 705 | e && e.preventDefault() 706 | 707 | if (!this.isShown) return 708 | 709 | var that = this 710 | this.isShown = false 711 | 712 | $('body').removeClass('modal-open') 713 | 714 | escape.call(this) 715 | 716 | this.$element 717 | .trigger('hide') 718 | .removeClass('in') 719 | 720 | $.support.transition && this.$element.hasClass('fade') ? 721 | hideWithTransition.call(this) : 722 | hideModal.call(this) 723 | } 724 | 725 | } 726 | 727 | 728 | /* MODAL PRIVATE METHODS 729 | * ===================== */ 730 | 731 | function hideWithTransition() { 732 | var that = this 733 | , timeout = setTimeout(function () { 734 | that.$element.off($.support.transition.end) 735 | hideModal.call(that) 736 | }, 500) 737 | 738 | this.$element.one($.support.transition.end, function () { 739 | clearTimeout(timeout) 740 | hideModal.call(that) 741 | }) 742 | } 743 | 744 | function hideModal( that ) { 745 | this.$element 746 | .hide() 747 | .trigger('hidden') 748 | 749 | backdrop.call(this) 750 | } 751 | 752 | function backdrop( callback ) { 753 | var that = this 754 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 755 | 756 | if (this.isShown && this.options.backdrop) { 757 | var doAnimate = $.support.transition && animate 758 | 759 | this.$backdrop = $(' 16 | <%} %> 17 | -------------------------------------------------------------------------------- /views/reg.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 用戶註冊 4 |
5 | 6 |
7 | 8 |

你的賬戶的名稱,用於登錄和顯示。

9 |
10 |
11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /views/say.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 | -------------------------------------------------------------------------------- /views/user.ejs: -------------------------------------------------------------------------------- 1 | <% if (user) { %> 2 | <%- partial('say') %> 3 | <% } %> 4 | <%- partial('posts') %> 5 | --------------------------------------------------------------------------------