├── .gitignore ├── CHANGELOG ├── LICENSE ├── README.md ├── bin └── looseleaf ├── lib ├── controllers │ ├── Auth.js │ ├── Comments.js │ ├── Files.js │ ├── Index.js │ ├── Posts.js │ └── Users.js ├── filters.js ├── helpers.js ├── looseleaf.js ├── models │ ├── Comment.js │ ├── File.js │ ├── Post.js │ └── User.js ├── routes.js ├── utils.js └── wrappers │ ├── config.js │ └── package.js ├── package.json └── skeleton ├── app.js ├── config.json ├── data ├── comments │ └── .gitkeep ├── posts │ └── .gitkeep └── users │ └── 1.json ├── public ├── bootstrap │ ├── .gitignore │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── bootstrap.css │ ├── bootstrap.min.css │ ├── docs │ │ ├── assets │ │ │ ├── css │ │ │ │ └── docs.css │ │ │ ├── ico │ │ │ │ ├── bootstrap-apple-114x114.png │ │ │ │ ├── bootstrap-apple-57x57.png │ │ │ │ ├── bootstrap-apple-72x72.png │ │ │ │ └── favicon.ico │ │ │ ├── img │ │ │ │ ├── bird.png │ │ │ │ ├── browsers.png │ │ │ │ ├── example-diagram-01.png │ │ │ │ ├── example-diagram-02.png │ │ │ │ ├── example-diagram-03.png │ │ │ │ ├── grid-18px.png │ │ │ │ └── twitter-logo-no-bird.png │ │ │ └── js │ │ │ │ ├── application.js │ │ │ │ └── google-code-prettify │ │ │ │ ├── prettify.css │ │ │ │ └── prettify.js │ │ ├── index.html │ │ └── javascript.html │ ├── examples │ │ ├── container-app.html │ │ ├── fluid.html │ │ └── hero.html │ ├── js │ │ ├── bootstrap-alerts.js │ │ ├── bootstrap-dropdown.js │ │ ├── bootstrap-modal.js │ │ ├── bootstrap-popover.js │ │ ├── bootstrap-scrollspy.js │ │ ├── bootstrap-tabs.js │ │ ├── bootstrap-twipsy.js │ │ └── tests │ │ │ ├── index.html │ │ │ ├── unit │ │ │ ├── bootstrap-alerts.js │ │ │ ├── bootstrap-dropdown.js │ │ │ ├── bootstrap-modal.js │ │ │ ├── bootstrap-popover.js │ │ │ ├── bootstrap-scrollspy.js │ │ │ ├── bootstrap-tabs.js │ │ │ └── bootstrap-twipsy.js │ │ │ └── vendor │ │ │ ├── qunit.css │ │ │ └── qunit.js │ └── lib │ │ ├── bootstrap.less │ │ ├── forms.less │ │ ├── mixins.less │ │ ├── patterns.less │ │ ├── reset.less │ │ ├── scaffolding.less │ │ ├── tables.less │ │ ├── type.less │ │ └── variables.less ├── files │ └── .gitkeep ├── images │ ├── icons │ │ ├── comment.png │ │ ├── delete.png │ │ ├── edit.png │ │ ├── logout.png │ │ ├── new.png │ │ ├── newpost.png │ │ ├── newuser.png │ │ ├── private.png │ │ └── public.png │ └── users │ │ ├── back.png │ │ ├── default.png │ │ └── top.png ├── javascripts │ ├── common.js │ ├── files.js │ ├── lib │ │ ├── activity-indicator.js │ │ ├── cleditor │ │ │ ├── images │ │ │ │ ├── buttons.gif │ │ │ │ ├── icons │ │ │ │ │ ├── 1.gif │ │ │ │ │ ├── 10.gif │ │ │ │ │ ├── 11.gif │ │ │ │ │ ├── 12.gif │ │ │ │ │ ├── 2.gif │ │ │ │ │ ├── 3.gif │ │ │ │ │ ├── 4.gif │ │ │ │ │ ├── 5.gif │ │ │ │ │ ├── 6.gif │ │ │ │ │ ├── 7.gif │ │ │ │ │ ├── 8.gif │ │ │ │ │ ├── 9.gif │ │ │ │ │ └── icons.gif │ │ │ │ ├── table.gif │ │ │ │ └── toolbar.gif │ │ │ ├── jquery.cleditor.css │ │ │ ├── jquery.cleditor.js │ │ │ ├── jquery.cleditor.min.js │ │ │ └── plugins │ │ │ │ ├── CLEditor.AdvancedTable1_0_0.zip │ │ │ │ ├── jquery.cleditor.advancedtable.js │ │ │ │ ├── jquery.cleditor.advancedtable.min.js │ │ │ │ ├── jquery.cleditor.icon.js │ │ │ │ ├── jquery.cleditor.icon.min.js │ │ │ │ ├── jquery.cleditor.table.js │ │ │ │ ├── jquery.cleditor.table.min.js │ │ │ │ ├── jquery.cleditor.xhtml.js │ │ │ │ └── jquery.cleditor.xhtml.min.js │ │ ├── ejs.min.js │ │ ├── google-code-prettify │ │ │ ├── lang-apollo.js │ │ │ ├── lang-clj.js │ │ │ ├── lang-css.js │ │ │ ├── lang-go.js │ │ │ ├── lang-hs.js │ │ │ ├── lang-lisp.js │ │ │ ├── lang-lua.js │ │ │ ├── lang-ml.js │ │ │ ├── lang-n.js │ │ │ ├── lang-proto.js │ │ │ ├── lang-scala.js │ │ │ ├── lang-sql.js │ │ │ ├── lang-tex.js │ │ │ ├── lang-vb.js │ │ │ ├── lang-vhdl.js │ │ │ ├── lang-wiki.js │ │ │ ├── lang-xq.js │ │ │ ├── lang-yaml.js │ │ │ ├── prettify.css │ │ │ └── prettify.js │ │ ├── jquery-1.6.4.min.js │ │ ├── jquery.tablesorter.min.js │ │ └── jquery.timeago.js │ ├── posts.js │ └── users.js └── stylesheets │ ├── container-app.css │ └── style.css └── views ├── index.ejs ├── layout.ejs ├── posts └── show.ejs ├── shared ├── _files.ejs ├── _footer.ejs └── _header.ejs └── users ├── index.ejs └── show.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X automatic create files 2 | .DS_Store 3 | 4 | # Vim automatic create files 5 | *~ 6 | .*.sw* 7 | 8 | # npm modules 9 | node_modules 10 | 11 | # Static, Temporary, Content files 12 | skeleton/data/posts/*.json 13 | skeleton/data/comments/*.json 14 | 15 | skeleton/data/users/*.json 16 | !skeleton/data/users/1.json 17 | 18 | skeleton/public/files/* 19 | !skeleton/public/files/.gitkeep 20 | 21 | TODO 22 | 23 | test/* 24 | 25 | skeleton/public/images/icons/mono/ 26 | 27 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v0.4.0 2 | * source code is all replaced. 3 | * new design single column. 4 | * remove comments/trackbacks, use DISQUS. 5 | * remove admin view, dynamic edit only. 6 | 7 | if you loved prev design, plese use v0.3.x 8 | ex) npm install -g looseleaf@0.3.6 9 | I'll continue supporting v0.3.x. 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) <2011> Tatsuya Tobioka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LooseLeaf 2 | 3 | Lightweight blog engine running on [node.js][] and [express][]. 4 | 5 | $ npm install -g looseleaf 6 | $ looseleaf yourblog 7 | $ node ./yourblog/app.js 8 | "yourblog" server listening on port 3000 9 | 10 | [node.js]: http://nodejs.org/ 11 | [express]: http://expressjs.com/ 12 | 13 | ## Demo 14 | 15 | * [http://blog.looseleafjs.org/][] (v0.3, Developer's blog written in Japanese) 16 | * [http://blog2.looseleafjs.org/][] (v0.4, Sandbox for everyone) 17 | 18 | [http://blog.looseleafjs.org/]: http://blog.looseleafjs.org/ 19 | [http://blog2.looseleafjs.org/]: http://blog2.looseleafjs.org/ 20 | 21 | ## License 22 | 23 | The MIT License 24 | 25 | -------------------------------------------------------------------------------- /bin/looseleaf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* Load modules */ 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var pkg = require('../lib/wrappers/package'); 7 | 8 | /* Define constants */ 9 | var BASE_DIR = path.join(__dirname, '..'); 10 | var VERSION = pkg.version; 11 | var CODE_NAME = pkg.codeName; 12 | var DEFAULT_PATH = 'blog'; 13 | var USAGE = '' 14 | + 'Usage: looseleaf [options] [target="' + DEFAULT_PATH + '"]\n' 15 | + '\n' 16 | + 'Options:\n' 17 | + ' -v, --version\tShow version number\n' 18 | + ' -h, --help\tShow this help message\n' 19 | + ' -d, --dir\tShow install directory' 20 | ; 21 | 22 | /* Define functions */ 23 | 24 | // Successful end 25 | function success(msg) { 26 | console.log(msg); 27 | process.exit(0); 28 | } 29 | 30 | // Abnormal end 31 | function error(msg) { 32 | console.error(msg); 33 | process.exit(1); 34 | } 35 | 36 | // Show message when create 37 | function create(target) { 38 | console.log('\033[32mcreate\033[0m: ' + target); 39 | } 40 | 41 | // Warning messsage 42 | function warning(msg) { 43 | console.error('\033[31mwarning\033[0m: ' + msg); 44 | } 45 | 46 | // Copy directory 47 | function copyDir(srcPath, dstPath) { 48 | 49 | if (!/\/$/.test(srcPath)) srcPath += '/'; 50 | if (!/\/$/.test(dstPath)) dstPath += '/'; 51 | 52 | // Make directory if NOT exist 53 | try { 54 | fs.mkdirSync(dstPath, fs.statSync(srcPath).mode); 55 | create(dstPath) 56 | } catch (e) { 57 | // File exists 58 | if (e.errno == 17 || e.errno == 45 || e.errno == 47) { 59 | warning(e.message); 60 | } else { 61 | throw e; 62 | } 63 | } 64 | var files = fs.readdirSync(srcPath); 65 | 66 | for(var i = 0; i < files.length; i++) { 67 | 68 | // Ignore ".*" 69 | if (/^\./.test(files[i])) { 70 | continue; 71 | } 72 | 73 | var srcFile = srcPath + files[i]; 74 | var dstFile = dstPath + files[i]; 75 | 76 | var srcStat = fs.statSync(srcFile); 77 | 78 | // Recursive call If direcotory 79 | if (srcStat.isDirectory()) { 80 | copyDir(srcFile, dstFile); 81 | } 82 | // Copy to dstPath if file 83 | else if (srcStat.isFile()) { 84 | // NOT overwrite file 85 | try { 86 | var dstStat = fs.statSync(dstFile); 87 | // File exists 88 | warning("EEXIST, File exists '" + dstFile + "'"); 89 | } catch (e) { 90 | // File NOT exists 91 | if (e.errno == 2 || e.errno == 32 || e.errno == 34) { 92 | var data = fs.readFileSync(srcFile); 93 | fs.writeFileSync(dstFile, data); 94 | create(dstFile) 95 | } else { 96 | throw e; 97 | } 98 | } 99 | } 100 | } 101 | } 102 | 103 | /* Parse arguments */ 104 | var args = process.argv.slice(2); 105 | var target = DEFAULT_PATH; 106 | while (args.length) { 107 | var arg = args.shift(); 108 | switch (arg) { 109 | case '-h': 110 | case '--help': 111 | success(USAGE); 112 | break; 113 | case '-v': 114 | case '--version': 115 | success(VERSION + ' "' + CODE_NAME + '"'); 116 | break; 117 | case '-d': 118 | case '--dir': 119 | success(BASE_DIR); 120 | break; 121 | default: 122 | target = arg; 123 | } 124 | } 125 | 126 | if (target) { 127 | copyDir(path.join(BASE_DIR, 'skeleton'), target); 128 | console.log('"' + target + '" created successfully'); 129 | require('child_process').exec('npm link looseleaf', { cwd: target }, function(err, stdout, stderr) { 130 | if (err) { 131 | throw err; 132 | } 133 | if (stderr) { 134 | console.log(stderr); 135 | } 136 | console.log('looseleaf ok'); 137 | console.log('please customize "' + path.join(target, '/config.json') + '"'); 138 | }); 139 | } else { 140 | error(USAGE); 141 | } 142 | -------------------------------------------------------------------------------- /lib/controllers/Auth.js: -------------------------------------------------------------------------------- 1 | module.exports = function(models) { 2 | 3 | this.login = function(req, res, next) { 4 | models.User.findByUsernameAndPassword(req.body.user.username, req.body.user.password, function(err, user) { 5 | if (err) next(err); 6 | req.session.user = user; 7 | // TODO notify 8 | // req.flash(); 9 | //res.redirect('home'); 10 | res.redirect(req.header('Referer')); 11 | }); 12 | }; 13 | 14 | this.logout = function(req, res, next) { 15 | req.session.destroy(function() { 16 | // res.redirect('home'); 17 | res.redirect(req.header('Referer')); 18 | }); 19 | }; 20 | 21 | return this; 22 | 23 | }.bind({}); 24 | 25 | -------------------------------------------------------------------------------- /lib/controllers/Comments.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var async = require('async'); 4 | 5 | module.exports = function(models) { 6 | 7 | this.index = function(req, res, next) { 8 | models.Comment.findAll(function(err, comments) { 9 | if (err) return next(err); 10 | res.send(JSON.stringify(comments)); 11 | }); 12 | }; 13 | 14 | this.create = function(req, res, next) { 15 | models.Comment.save(req.body.comment, function(err, comment) { 16 | if (err) return next(err); 17 | res.send(JSON.stringify(comment)); 18 | }); 19 | }; 20 | 21 | this.destroy = function(req, res, next) { 22 | if (req.post.user.id != req.session.user.id && !req.session.user.isAdmin) return res.send(401); 23 | models.Comment.remove(req.params.commentId, function(err, file) { 24 | if (err) return next(err); 25 | res.send(200); 26 | }); 27 | }; 28 | return this; 29 | 30 | }.bind({}); 31 | 32 | -------------------------------------------------------------------------------- /lib/controllers/Files.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var async = require('async'); 4 | 5 | module.exports = function(models) { 6 | 7 | this.index = function(req, res, next) { 8 | models.File.findAll(function(err, files) { 9 | if (err) return next(err); 10 | res.send(JSON.stringify(files)); 11 | }); 12 | }; 13 | 14 | this.create = function(req, res, next) { 15 | req.form.complete(function(err, fields, files) { 16 | if (err) return next(err); 17 | models.File.save(files.files.path, files.files.filename, req.session.user, function(err, file) { 18 | if (err) return next(err); 19 | models.File.findAll(function(err, files) { 20 | if (err) return next(err); 21 | res.send(JSON.stringify(files)); 22 | }); 23 | }); 24 | }); 25 | }; 26 | 27 | this.destroy = function(req, res, next) { 28 | models.File.remove(req.params.filename, req.session.user, function(err, file) { 29 | if (err) return next(err); 30 | res.send(200); 31 | }); 32 | }; 33 | return this; 34 | 35 | }.bind({}); 36 | 37 | -------------------------------------------------------------------------------- /lib/controllers/Index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(models) { 2 | 3 | this.index = function(req, res, next) { 4 | var flag = req.session.user ? null : false; 5 | models.Post.findByIsPrivate(flag, 5, 0, function(err, posts) { 6 | if (err) next(err); 7 | res.render('index', { 8 | title: '', 9 | navUsers: models.User.findAllSync(), 10 | posts: posts 11 | }); 12 | }); 13 | } 14 | 15 | this.next = function(req, res, next) { 16 | var offset = parseInt(req.params.offset); 17 | var flag = req.session.user ? null : false; 18 | models.Post.findByIsPrivate(flag, 1, offset, function(err, posts) { 19 | if (err) next(err); 20 | res.send(JSON.stringify(posts[0] || false)); 21 | }); 22 | } 23 | 24 | this.search = function(req, res, next) { 25 | var keyword = req.params.keyword; 26 | var flag = req.session.user ? null : false; 27 | models.Post.searchByIsPrivate(keyword, flag, null, null, function(err, posts) { 28 | if (err) return next(err); 29 | res.render('index', { 30 | title: keyword, 31 | navUsers: models.User.findAllSync(), 32 | posts: posts 33 | }); 34 | }); 35 | }; 36 | 37 | this.tag = function(req, res, next) { 38 | var tag = req.params.tag; 39 | var flag = req.session.user ? null : false; 40 | models.Post.searchByTagAndIsPrivate(tag, flag, null, null, function(err, posts) { 41 | if (err) return next(err); 42 | res.render('index', { 43 | title: tag, 44 | navUsers: models.User.findAllSync(), 45 | posts: posts 46 | }); 47 | }); 48 | }; 49 | 50 | this.user = function(req, res, next) { 51 | var username = req.params.username; 52 | var flag = req.session.user ? null : false; 53 | models.User.findByUsername(username, function(err, user) { 54 | if (err) return next(err); 55 | models.Post.findByUserIdAndIsPrivate(user.id, flag, null, null, function(err, posts) { 56 | if (err) return next(err); 57 | 58 | if (req.params.format == 'json') { 59 | //res.contentType('application/json'); 60 | res.send(JSON.stringify(posts)); 61 | } else { 62 | res.render('index', { 63 | title: username + '\'s posts', 64 | navUsers: models.User.findAllSync(), 65 | posts: posts 66 | }); 67 | } 68 | 69 | }); 70 | }); 71 | }; 72 | 73 | return this; 74 | 75 | }.bind({}); 76 | 77 | -------------------------------------------------------------------------------- /lib/controllers/Posts.js: -------------------------------------------------------------------------------- 1 | module.exports = function(models) { 2 | 3 | this.index = function(req, res, next) { 4 | } 5 | 6 | this.create = function(req, res, next) { 7 | models.Post.save(req.body.post, req.session.user, function(err, post) { 8 | if (err) return next(err); 9 | res.send(JSON.stringify(post)); 10 | }); 11 | }; 12 | 13 | this.loadPost = function(req, res, next, id) { 14 | models.Post.findById(id, function(err, post) { 15 | if (!post) return res.send(404); 16 | if (!req.session.user && post.isPrivate) return res.send(404); 17 | req.post = post; 18 | next(); 19 | }); 20 | }; 21 | 22 | this.show = function(req, res, next) { 23 | if (req.params.format == 'json') { 24 | //res.contentType('application/json'); 25 | res.send(JSON.stringify([req.post])); 26 | } else { 27 | res.render('posts/show', { 28 | title: req.post.title, 29 | navUsers: models.User.findAllSync(), 30 | posts: [req.post] 31 | }); 32 | } 33 | }; 34 | 35 | this.edit = function(req, res, next) { 36 | if (req.post.user.id != req.session.user.id && !req.session.user.isAdmin) return res.send(401); 37 | for (var i in req.body.post) { 38 | if (req.body.post[i] == 'true') req.body.post[i] = true; 39 | if (req.body.post[i] == 'false') req.body.post[i] = false; 40 | req.post[i] = req.body.post[i]; 41 | } 42 | models.Post.save(req.post, null, function(err, post) { 43 | if (err) return next(err); 44 | res.send(JSON.stringify(post)); 45 | }); 46 | }; 47 | 48 | this.destroy = function(req, res, next) { 49 | if (req.post.user.id != req.session.user.id && !req.session.user.isAdmin) return res.send(401); 50 | models.Post.remove(req.post, function(err) { 51 | if (err) return next(err); 52 | res.send(200); 53 | }); 54 | }; 55 | 56 | return this; 57 | 58 | }.bind({}); 59 | 60 | -------------------------------------------------------------------------------- /lib/controllers/Users.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | 3 | module.exports = function(models) { 4 | 5 | this.index = function(req, res, next) { 6 | var flag = req.session.user ? null : false; 7 | models.User.findByIsPrivate(flag, function(err, users) { 8 | if (!users) return res.send(404); 9 | res.render('users/index', { 10 | title: 'Users', 11 | navUsers: models.User.findAllSync(), 12 | users: users 13 | }); 14 | }); 15 | } 16 | 17 | this.loadUser = function(req, res, next, id) { 18 | var flag = req.session.user ? null : false; 19 | models.User.findByIdAndIsPrivate(id, flag, function(err, user) { 20 | if (!user) return res.send(404); 21 | req.user = user; 22 | next(); 23 | }); 24 | }; 25 | 26 | this.loadUserWithPassword = function(req, res, next, id) { 27 | //var flag = req.session.user ? null : false; 28 | //models.User.findByIdWithPassword(id, flag, function(err, user) { 29 | models.User.findByIdWithPassword(id, function(err, user) { 30 | if (!user) return res.send(404); 31 | req.user = user; 32 | next(); 33 | }); 34 | }; 35 | 36 | this.show = function(req, res, next) { 37 | res.render('users/show', { 38 | title: req.user.username, 39 | navUsers: models.User.findAllSync(), 40 | users: [req.user] 41 | }); 42 | }; 43 | 44 | this.edit = function(req, res, next) { 45 | if (req.user.id != req.session.user.id && !req.session.user.isAdmin) return res.send(401); 46 | for (var i in req.body.user) { 47 | if (req.body.user[i]) { 48 | if (req.body.user[i] == 'true') req.body.user[i] = true; 49 | if (req.body.user[i] == 'false') req.body.user[i] = false; 50 | req.user[i] = req.body.user[i]; 51 | } else { 52 | } 53 | } 54 | models.User.save(req.user, function(err, user) { 55 | if (err) return next(err); 56 | res.send(JSON.stringify(user)); 57 | }); 58 | }; 59 | 60 | this.create = function(req, res, next) { 61 | if (!req.session.user.isAdmin) return res.send(401); 62 | models.User.save(req.body.user, function(err, user) { 63 | if (err) return next(err); 64 | res.send(JSON.stringify(user)); 65 | }); 66 | }; 67 | 68 | return this; 69 | 70 | }.bind({}); 71 | 72 | -------------------------------------------------------------------------------- /lib/filters.js: -------------------------------------------------------------------------------- 1 | exports.login = function(req, res, next) { 2 | if (!req.session || !req.session.user) { 3 | // redirect('home'); 4 | res.send(401); 5 | } else { 6 | next(); 7 | } 8 | }; 9 | 10 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | module.exports = function(app, config) { 4 | 5 | app.helpers({ 6 | }); 7 | 8 | app.dynamicHelpers({ 9 | // As dynamic to call without "()" 10 | config: function(req, res) { 11 | return config; 12 | }, 13 | req: function(req, res) { 14 | return req; 15 | } 16 | }); 17 | 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /lib/looseleaf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * LooseLeaf: Lightweight blogging engine for node.js 3 | * http://looseleafjs.org/ 4 | * (c) 2011- tnantoka 5 | */ 6 | 7 | // Load modules 8 | var express = require('express'); 9 | var fs = require('fs'); 10 | var path = require('path'); 11 | var connectForm = require('connect-form'); 12 | //var FSStore = require('connect-fs')(express); 13 | 14 | // Create looseleaf server 15 | exports.init = function(dir) { 16 | 17 | var app = express.createServer(); 18 | 19 | var config = require('./wrappers/config')(dir); 20 | 21 | // var sessionStore = new FSStore({ dir: path.join(dir, 'sessions') }); 22 | 23 | // Configuration 24 | app.configure(function(){ 25 | if (config.process.logging) { 26 | app.use(express.logger({ 27 | format: ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :response-time ":referrer" ":user-agent"' // Combined Log Format 28 | })); 29 | } 30 | // file uploading 31 | app.use(connectForm({ 32 | uploadDir: path.join(dir, 'public/files'), 33 | keepExtensions: true 34 | })); 35 | app.set('views', path.join(dir, 'views')); // Set view directory 36 | app.set('view engine', 'ejs'); // Set templete engine 37 | app.use(express.bodyParser()); 38 | app.use(express.methodOverride()); 39 | app.use(express.cookieParser()); 40 | app.use(express.session({ 41 | secret: config.session.secret, 42 | // store: sessionStore, 43 | cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 } // 1 week 44 | //cookie: { maxAge: 1 * 24 * 60 * 60 * 1000 } // 1 day 45 | })); 46 | app.use(app.router); 47 | app.use(express.static(path.join(dir, 'public'))); // Set static directory 48 | }); 49 | 50 | // Show stack trace in development 51 | app.configure('development', function(){ 52 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 53 | }); 54 | 55 | // Only error message in production 56 | app.configure('production', function(){ 57 | app.use(express.errorHandler()); 58 | }); 59 | 60 | /* Init application */ 61 | 62 | require('./helpers')(app, config); 63 | 64 | // Models 65 | var models = {}; 66 | loadModules(path.join(__dirname, 'models'), models, dir); 67 | for (var model in models) { 68 | if (models[model].init) { 69 | models[model].init(); 70 | } 71 | } 72 | 73 | // Controllers 74 | var controllers = {}; 75 | loadModules(path.join(__dirname, 'controllers'), controllers, models); 76 | 77 | // Routes 78 | require('./routes')(app, controllers, models); 79 | 80 | // Return to app.js 81 | return { 82 | app: app, 83 | start: function() { 84 | app.listen(config.process.port); 85 | console.log('"' + config.site.title + '" server listening on port %d', app.address().port); 86 | } 87 | }; 88 | 89 | }; 90 | 91 | // Load and require js files to container obj from dir 92 | function loadModules(dir, container, args) { 93 | var files = fs.readdirSync(dir); 94 | for (var i = 0; i < files.length; i++) { 95 | var file = files[i]; 96 | var filePath = path.join(dir, file); 97 | if (fs.statSync(filePath).isDirectory()) { 98 | container[file] = {}; 99 | loadModules(filePath, container[file], args); 100 | continue; 101 | } 102 | if (/.+\.js$/.test(file)) { 103 | var name = file.replace(/\.js$/, '') 104 | container[name] = require(filePath)(args, container); 105 | } 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /lib/models/Comment.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var async = require('async'); 4 | 5 | module.exports = function(dir, models) { 6 | 7 | var self = this; 8 | 9 | var commentsDir = path.join(dir, '/data/comments/'); 10 | 11 | var comments = []; 12 | 13 | // Load all files 14 | var files = fs.readdirSync(commentsDir); 15 | var jsonFiles = []; 16 | 17 | // Select only .json files 18 | for (var i = 0; i < files.length; i++) { 19 | if (/\.json$/.test(files[i])) { 20 | jsonFiles.push(files[i]); 21 | } 22 | } 23 | 24 | // Load comments 25 | for (var i = 0; i < jsonFiles.length; i++) { 26 | var comment = require(commentsDir + jsonFiles[i]); 27 | comments.push(comment); 28 | } 29 | 30 | /* 31 | // Sort by createdAt 32 | comments.sort(function(a, b) { 33 | var aDate = new Date(a.createdAt); 34 | var bDate = new Date(b.createdAt); 35 | return bDate - aDate; 36 | }); 37 | */ 38 | 39 | // Sort by id 40 | comments.sort(function(a, b) { 41 | var aId = a.id; 42 | var bId = b.id; 43 | return aId - bId; 44 | }); 45 | 46 | this.gen = function(comment) { 47 | comment = { 48 | id: comment.id || nextId(), 49 | username: comment.username || '', 50 | body: comment.body || '', 51 | isPrivate: typeof comment.isPrivate == 'undefined' ? false : comment.isPrivate, 52 | createdAt: comment.createdAt || new Date(), 53 | updatedAt: comment.createdAt ? '' : new Date(), 54 | postId: comment.postId 55 | }; 56 | return comment; 57 | }; 58 | 59 | this.save = function(comment, cb) { 60 | comment = self.gen(comment); 61 | fs.writeFile(path.join(commentsDir, comment.id + '.json'), JSON.stringify(comment, null, ' '), 'UTF-8', function(err) { 62 | if (err) return cb(err); 63 | var index = idToIndex(comment.id); 64 | if (index < 0) { 65 | comments.push(comment); 66 | } else { 67 | comments[index] = comment; 68 | } 69 | self.findById(comment.id, function(err, comment) { 70 | cb(null, comment); 71 | }); 72 | }); 73 | }; 74 | 75 | this.remove = function(id, cb) { 76 | var filePath = path.join(path.join(commentsDir, id + '.json')); 77 | fs.unlink(filePath, function(err) { 78 | if (err) return cb(err); 79 | for (var i = 0; i < comments.length; i++) { 80 | if (comments[i].id == id) { 81 | comments.splice(i, 1); 82 | break; 83 | } 84 | } 85 | cb(null); 86 | }); 87 | 88 | }; 89 | 90 | this.findAll = function(cb) { 91 | cb(null, comments.slice()); 92 | }; 93 | 94 | this.findAllSync = function(cb) { 95 | return comments.slice(); 96 | }; 97 | 98 | this.findById = function(id, cb) { 99 | var comment = comments[idToIndex(id)]; 100 | cb(null, setRelation([comment])[0]); 101 | }; 102 | 103 | this.findByPostId = function(postId, cb) { 104 | var results = []; 105 | for (var i = 0; i < comments.length; i++) { 106 | var comment = comments[i]; 107 | if (comment.postId == postId) { 108 | results.push(comment); 109 | } 110 | } 111 | cb(null, setRelation(results)); 112 | }; 113 | 114 | /* 115 | this.findByUsernameAndIsPrivate = function(username, isPrivate, cb) { 116 | for (var i = 0; i < users.length; i++) { 117 | var user = users[i]; 118 | if (user.username == username) { 119 | models.Post.countByUserIdAndIsPrivate(user.id, isPrivate, function(err, count) { 120 | user.posts = count; 121 | cb(null, setRelation([user])[0]); 122 | }); 123 | break; 124 | } 125 | } 126 | }; 127 | 128 | this.findByUsername = function(username, cb) { 129 | for (var i = 0; i < users.length; i++) { 130 | var user = users[i]; 131 | if (user.username == username) { 132 | models.Post.countByUserId(user.id, function(err, count) { 133 | user.posts = count; 134 | cb(null, setRelation([user])[0]); 135 | }); 136 | break; 137 | } 138 | } 139 | }; 140 | */ 141 | 142 | function nextId() { 143 | var maxId = 0; 144 | for (var i = 0; i < comments.length; i++) { 145 | var comment = comments[i]; 146 | if (maxId < comment.id) { 147 | maxId = comment.id; 148 | } 149 | } 150 | return maxId + 1; 151 | } 152 | 153 | function idToIndex(id) { 154 | var index = -1; 155 | for (var i = 0; i < comments.length; i++) { 156 | var comment = comments[i]; 157 | if (id == comment.id) { 158 | index = i; 159 | break; 160 | } 161 | } 162 | return index; 163 | } 164 | 165 | function setRelation(results) { 166 | /* 167 | for (var i = 0; i < results.length; i++) { 168 | var user = copyObj(results[i]); 169 | delete user.password; 170 | results[i] = user; 171 | } 172 | */ 173 | return results; 174 | } 175 | 176 | return this; 177 | 178 | }.bind({}); 179 | 180 | 181 | -------------------------------------------------------------------------------- /lib/models/File.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var async = require('async'); 4 | 5 | module.exports = function(dir, models) { 6 | 7 | var self = this; 8 | 9 | var filesDir = path.join(dir, '/public/files/'); 10 | 11 | var files = []; 12 | 13 | var users = []; 14 | 15 | this.init = function() { 16 | users = models.User.findAllSync(); 17 | 18 | for (var i = 0; i < users.length; i++) { 19 | var userId = users[i].id; 20 | var userDir = path.join(filesDir, '/' + userId); 21 | try { 22 | fs.mkdirSync(userDir, 0755); 23 | } catch (e) { 24 | //console.log(e); 25 | } 26 | var userFiles = fs.readdirSync(userDir); 27 | for (var j = 0; j < userFiles.length; j++) { 28 | var filePath = path.join(userDir, userFiles[j]); 29 | if (/^\.+/.test(filePath)) { 30 | continue; 31 | } 32 | var stat = fs.statSync(filePath); 33 | stat.filename = userFiles[j]; 34 | stat.user = users[i]; 35 | files.push(stat); 36 | } 37 | } 38 | // Sort by created 39 | users.sort(function(a, b) { 40 | var aDate = new Date(a.cTime); 41 | var bDate = new Date(b.cTime); 42 | return bDate - aDate; 43 | }); 44 | }; 45 | 46 | this.save = function(tempfile, filename, user, cb) { 47 | var userDir = path.join(filesDir, '/' + user.id); 48 | var filePath = path.join(userDir, filename); 49 | fs.rename(tempfile, filePath, function(err) { 50 | if (err) return cb(err); 51 | var isUpdate; 52 | fs.stat(filePath, function(err, stats) { 53 | if (err) return cb(err); 54 | stats.filename = filename; 55 | stats.user = user; 56 | for (var i = 0; i < files.length; i++) { 57 | if (files[i].user.id == user.id && files[i].filename == filename) { 58 | isUpdate = true; 59 | files[i] = stats; 60 | break; 61 | } 62 | } 63 | if (!isUpdate) { 64 | files.push(stats); 65 | } 66 | cb(null, stats); 67 | }); 68 | }); 69 | }; 70 | 71 | this.remove = function(filename, user, cb) { 72 | var userDir = path.join(filesDir, '/' + user.id); 73 | var filePath = path.join(userDir, filename); 74 | fs.unlink(filePath, function(err) { 75 | if (err) return cb(err); 76 | for (var i = 0; i < files.length; i++) { 77 | if (files[i].user.id == user.id && files[i].filename == filename) { 78 | files.splice(i, 1); 79 | break; 80 | } 81 | } 82 | cb(null); 83 | }); 84 | }; 85 | 86 | this.findAll = function(cb) { 87 | var results = files.slice(); 88 | cb(null, results); 89 | }; 90 | 91 | return this; 92 | 93 | }.bind({}); 94 | 95 | 96 | -------------------------------------------------------------------------------- /lib/models/User.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var async = require('async'); 4 | var copyObj = require(path.join(__dirname, '/../utils')).copyObj; 5 | var crypto = require('crypto'); 6 | 7 | module.exports = function(dir, models) { 8 | 9 | var self = this; 10 | 11 | var usersDir = path.join(dir, '/data/users/'); 12 | 13 | var users = []; 14 | 15 | // Load all files 16 | var files = fs.readdirSync(usersDir); 17 | var jsonFiles = []; 18 | 19 | // Select only .json files 20 | for (var i = 0; i < files.length; i++) { 21 | if (/\.json$/.test(files[i])) { 22 | jsonFiles.push(files[i]); 23 | } 24 | } 25 | 26 | // Load users 27 | for (var i = 0; i < jsonFiles.length; i++) { 28 | var user = require(usersDir + jsonFiles[i]); 29 | users.push(user); 30 | } 31 | 32 | // Sort by created 33 | users.sort(function(a, b) { 34 | var aDate = new Date(a.createdAt); 35 | var bDate = new Date(b.createdAt); 36 | return bDate - aDate; 37 | }); 38 | 39 | /* 40 | // Sort by id 41 | users.sort(function(a, b) { 42 | var aId = a.id; 43 | var bId = b.id; 44 | return aId - bId; 45 | }); 46 | */ 47 | 48 | this.gen = function(user) { 49 | user = { 50 | id: user.id || nextId(), 51 | username: user.username || '', 52 | password: user.password || '', 53 | fullname: user.fullname || '', 54 | intro: user.intro || '', 55 | icon: user.icon || '', 56 | color: user.color || '', 57 | isAdmin: user.isAdmin || '', 58 | createdAt: user.createdAt || new Date(), 59 | updatedAt: user.createdAt ? '' : new Date() 60 | }; 61 | if (user.password.length != 128) { 62 | user.password = hash(user.password); 63 | } 64 | return user; 65 | }; 66 | 67 | this.save = function(user, cb) { 68 | user = self.gen(user); 69 | fs.writeFile(path.join(usersDir, user.id + '.json'), JSON.stringify(user, null, ' '), 'UTF-8', function(err) { 70 | if (err) return cb(err); 71 | var index = idToIndex(user.id); 72 | if (index < 0) { 73 | users.push(user); 74 | } else { 75 | users[index] = user; 76 | } 77 | self.findById(user.id, function(err, user) { 78 | cb(null, user); 79 | }); 80 | }); 81 | }; 82 | 83 | this.remove = function(user) { 84 | 85 | }; 86 | 87 | this.findAll = function() { 88 | cb(null, users.slice()); 89 | }; 90 | 91 | this.findAllSync = function(cb) { 92 | return users.slice(); 93 | }; 94 | 95 | this.findById = function(id, cb) { 96 | var user = users[idToIndex(id)]; 97 | models.Post.countByUserId(user.id, function(err, count) { 98 | user.posts = count; 99 | cb(null, setResults([user])[0]); 100 | }); 101 | }; 102 | 103 | this.findByIdWithPassword = function(id, cb) { 104 | var user = users[idToIndex(id)]; 105 | models.Post.countByUserId(user.id, function(err, count) { 106 | user.posts = count; 107 | cb(null, user); 108 | }); 109 | }; 110 | 111 | this.findByIdAndIsPrivate = function(id, isPrivate, cb) { 112 | var user = users[idToIndex(id)]; 113 | models.Post.countByUserIdAndIsPrivate(user.id, isPrivate, function(err, count) { 114 | user.posts = count; 115 | cb(null, setResults([user])[0]); 116 | }); 117 | }; 118 | 119 | this.findByIsPrivate = function(isPrivate, cb) { 120 | var results = users.slice(); 121 | async.forEach(results, function(user, callback) { 122 | models.Post.countByUserIdAndIsPrivate(user.id, isPrivate, function(err, count) { 123 | user.posts = count; 124 | callback(null); 125 | }); 126 | }, function(err) { 127 | if (err) return cb(err); 128 | cb(null, setResults(results)); 129 | }); 130 | }; 131 | 132 | this.findByUsernameAndPassword = function(username, password, cb) { 133 | var results = []; 134 | for (var i = 0; i < users.length; i++) { 135 | var user = users[i]; 136 | if (user.password.length == 128) { 137 | if (user.username == username && user.password == hash(password)) { 138 | results.push(user); 139 | } 140 | } else { 141 | if (user.username == username && user.password == password) { 142 | results.push(user); 143 | } 144 | } 145 | } 146 | cb(null, setResults(results)[0]); 147 | }; 148 | 149 | this.findByUsernameAndIsPrivate = function(username, isPrivate, cb) { 150 | for (var i = 0; i < users.length; i++) { 151 | var user = users[i]; 152 | if (user.username == username) { 153 | models.Post.countByUserIdAndIsPrivate(user.id, isPrivate, function(err, count) { 154 | user.posts = count; 155 | cb(null, setResults([user])[0]); 156 | }); 157 | break; 158 | } 159 | } 160 | }; 161 | 162 | this.findByUsername = function(username, cb) { 163 | for (var i = 0; i < users.length; i++) { 164 | var user = users[i]; 165 | if (user.username == username) { 166 | models.Post.countByUserId(user.id, function(err, count) { 167 | user.posts = count; 168 | cb(null, setResults([user])[0]); 169 | }); 170 | break; 171 | } 172 | } 173 | }; 174 | 175 | function nextId() { 176 | var maxId = 0; 177 | for (var i = 0; i < users.length; i++) { 178 | var user = users[i]; 179 | if (maxId < user.id) { 180 | maxId = user.id; 181 | } 182 | } 183 | return maxId + 1; 184 | } 185 | 186 | function idToIndex(id) { 187 | var index = -1; 188 | for (var i = 0; i < users.length; i++) { 189 | var user = users[i]; 190 | if (id == user.id) { 191 | index = i; 192 | break; 193 | } 194 | } 195 | return index; 196 | } 197 | 198 | function hash(password) { 199 | return crypto.createHash('sha512').update(password).digest('hex'); 200 | } 201 | 202 | function setResults(results) { 203 | for (var i = 0; i < results.length; i++) { 204 | var user = copyObj(results[i]); 205 | delete user.password; 206 | results[i] = user; 207 | } 208 | return results; 209 | } 210 | 211 | return this; 212 | 213 | }.bind({}); 214 | 215 | 216 | -------------------------------------------------------------------------------- /lib/routes.js: -------------------------------------------------------------------------------- 1 | /* Mapping uri to method of contorllers */ 2 | var filters = require('./filters'); 3 | 4 | module.exports = function(app, controllers, models) { 5 | 6 | app.get('/', controllers.Index.index); 7 | app.get('/next/:offset', controllers.Index.next); 8 | 9 | app.get('/search/:keyword', controllers.Index.search); 10 | app.get('/tags/:tag', controllers.Index.tag); 11 | app.get('/users/:username/posts.:format?', controllers.Index.user); 12 | 13 | app.param('userId', controllers.Users.loadUser); 14 | app.param('userIdWithPassword', controllers.Users.loadUserWithPassword); 15 | app.get('/users', controllers.Users.index); 16 | app.get('/users/:userId', controllers.Users.show); 17 | app.post('/users', filters.login, controllers.Users.create); 18 | app.put('/users/:userIdWithPassword', filters.login, controllers.Users.edit); 19 | 20 | app.param('postId', controllers.Posts.loadPost); 21 | app.get('/posts/:postId.:format?', controllers.Posts.show); 22 | app.post('/posts', filters.login, controllers.Posts.create); 23 | app.put('/posts/:postId', filters.login, controllers.Posts.edit); 24 | app.del('/posts/:postId', filters.login, controllers.Posts.destroy); 25 | 26 | app.get('/posts/:postId/comments', controllers.Comments.index); 27 | app.post('/posts/:postId/comments', controllers.Comments.create); 28 | app.del('/posts/:postId/comments/:commentId', filters.login, controllers.Comments.destroy); 29 | 30 | app.get('/files', filters.login, controllers.Files.index); 31 | app.post('/files', filters.login, controllers.Files.create); 32 | app.del('/files/:filename', filters.login, controllers.Files.destroy); 33 | 34 | app.post('/auth/login', controllers.Auth.login); 35 | app.get('/auth/logout', filters.login, controllers.Auth.logout); 36 | 37 | }; 38 | 39 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | exports.copyObj = function(obj) { 2 | var newObj = {}; 3 | for (var i in obj) { 4 | newObj[i] = obj[i]; 5 | } 6 | return newObj; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /lib/wrappers/config.js: -------------------------------------------------------------------------------- 1 | /* Wrapper of config.json */ 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | module.exports = function(dir) { 7 | 8 | var filePath = path.join(dir, 'config.json'); 9 | var config = JSON.parse(fs.readFileSync(filePath)); 10 | 11 | this.get = function(key) { 12 | if (typeof key != 'undefined') { 13 | return config[key]; 14 | } 15 | return config; 16 | }; 17 | 18 | this.update = function(key, value, fn) { 19 | config[key] = value; 20 | this.save(fn); 21 | }; 22 | 23 | this.save = function(newConfig, fn) { 24 | if (typeof newConfig == 'object') { 25 | config = newConfig; 26 | } 27 | fs.writeFile(filePath, JSON.stringify(config, null, ' '), 'UTF-8', function(err) { 28 | if (err) return fn(err); 29 | fn(null); 30 | }); 31 | }; 32 | 33 | // Shortcut to properties 34 | Object.keys(config).forEach(function(key) { 35 | this[key] = config[key]; 36 | }.bind(this)); 37 | 38 | return this; 39 | 40 | }.bind({}); 41 | 42 | -------------------------------------------------------------------------------- /lib/wrappers/package.js: -------------------------------------------------------------------------------- 1 | /* Wrapper of package.json */ 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | var pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '/../../package.json'))); 7 | 8 | module.exports.version = pkg.version; 9 | module.exports.codeName = 'Dog-ear'; 10 | 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "tnantoka (http://blog.bornneet.com/)", 3 | "name": "looseleaf", 4 | "description": "Lightweight blog engine on express", 5 | "keywords": [ 6 | "CMS", 7 | "Blog", 8 | "JSON", 9 | "express", 10 | "Single Column", 11 | "Bootstrap" 12 | ], 13 | "version": "0.4.4", 14 | "homepage": "http://looseleafjs.org/", 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/tnantoka/LooseLeaf.git" 18 | }, 19 | "main": "lib/looseleaf", 20 | "bin": { 21 | "looseleaf": "bin/looseleaf" 22 | }, 23 | "engines": { 24 | "node": ">=0.5.0" 25 | }, 26 | "dependencies": { 27 | "ejs": "0.4.3", 28 | "express": "2.5.0", 29 | "connect-form": "0.2.1", 30 | "async": "0.1.15" 31 | }, 32 | "devDependencies": {} 33 | } 34 | 35 | -------------------------------------------------------------------------------- /skeleton/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Start looseleaf server 3 | */ 4 | 5 | // Create express server and exports for spark like modules 6 | var ll = require('looseleaf').init(__dirname); 7 | 8 | // For other controll modules 9 | exports = ll.app; 10 | 11 | // Only listen when run on $ node app.js 12 | if (!module.parent) { 13 | ll.start(); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /skeleton/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "site" : { 3 | "title" : "My LooseLeaf" 4 | }, 5 | "copyright" : { 6 | "title" : "Your site", 7 | "uri" : "http://example.com/" 8 | }, 9 | "session" : { 10 | "secret" : "Unique string" 11 | }, 12 | "process" : { 13 | "port" : "3000", 14 | "logging" : true 15 | }, 16 | "analytics_id" : "", 17 | "disqus_shortname" : "", 18 | "usersNav": { 19 | "enable" : false, 20 | "lead" : [] 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /skeleton/data/comments/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/data/comments/.gitkeep -------------------------------------------------------------------------------- /skeleton/data/posts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/data/posts/.gitkeep -------------------------------------------------------------------------------- /skeleton/data/users/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "username": "admin", 4 | "password": "5b722b307fce6c944905d132691d5e4a2214b7fe92b738920eb3fce3a90420a19511c3010a0e7712b054daef5b57bad59ecbd93b3280f210578f547f4aed4d25", 5 | "fullname": "Administrator", 6 | "intro": "", 7 | "icon": "/images/users/default.png", 8 | "color": "", 9 | "isAdmin": true, 10 | "createdAt": "Sun Oct 30 2011 23:49:23 GMT+0900 (JST)", 11 | "updatedAt": "" 12 | } -------------------------------------------------------------------------------- /skeleton/public/bootstrap/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | thumbs.db 4 | -------------------------------------------------------------------------------- /skeleton/public/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Twitter, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /skeleton/public/bootstrap/Makefile: -------------------------------------------------------------------------------- 1 | VERSION=1.3.0 2 | DATE=$(shell DATE) 3 | BOOTSTRAP = ./bootstrap.css 4 | BOOTSTRAP_MIN = ./bootstrap.min.css 5 | BOOTSTRAP_LESS = ./lib/bootstrap.less 6 | LESS_COMPESSOR ?= `which lessc` 7 | WATCHR ?= `which watchr` 8 | 9 | build: 10 | @@if test ! -z ${LESS_COMPESSOR}; then \ 11 | sed -e 's/@VERSION/'"v${VERSION}"'/' -e 's/@DATE/'"${DATE}"'/' <${BOOTSTRAP_LESS} >${BOOTSTRAP_LESS}.tmp; \ 12 | lessc ${BOOTSTRAP_LESS}.tmp > ${BOOTSTRAP}; \ 13 | lessc ${BOOTSTRAP_LESS}.tmp > ${BOOTSTRAP_MIN} --compress; \ 14 | rm -f ${BOOTSTRAP_LESS}.tmp; \ 15 | echo "Bootstrap successfully built! - `date`"; \ 16 | else \ 17 | echo "You must have the LESS compiler installed in order to build Bootstrap."; \ 18 | echo "You can install it by running: npm install less -g"; \ 19 | fi 20 | 21 | watch: 22 | @@if test ! -z ${WATCHR}; then \ 23 | echo "Watching less files..."; \ 24 | watchr -e "watch('lib/.*\.less') { system 'make' }"; \ 25 | else \ 26 | echo "You must have the watchr installed in order to watch Bootstrap less files."; \ 27 | echo "You can install it by running: gem install watchr"; \ 28 | fi 29 | 30 | .PHONY: build watch -------------------------------------------------------------------------------- /skeleton/public/bootstrap/README.md: -------------------------------------------------------------------------------- 1 | TWITTER BOOTSTRAP 2 | ================= 3 | 4 | Bootstrap is Twitter's toolkit for kickstarting CSS for websites, apps, and more. It includes base CSS styles for typography, forms, buttons, tables, grids, navigation, alerts, and more. 5 | 6 | To get started -- checkout http://twitter.github.com/bootstrap! 7 | 8 | 9 | Usage 10 | ----- 11 | 12 | You can use Twitter Bootstrap in one of two ways: just drop the compiled CSS into any new project and start cranking, or run LESS on your site and compile on the fly like a boss. 13 | 14 | Here's what the LESS version looks like: 15 | 16 | ``` html 17 | 18 | 19 | ``` 20 | 21 | Or if you prefer, the standard css way: 22 | 23 | ``` html 24 | 25 | ``` 26 | 27 | For more info, refer to the docs! 28 | 29 | 30 | Versioning 31 | ---------- 32 | 33 | For transparency and insight into our release cycle, and for striving to maintain backwards compatibility, Bootstrap will be maintained under the Semantic Versioning guidelines as much as possible. 34 | 35 | Releases will be numbered with the follow format: 36 | 37 | `..` 38 | 39 | And constructed with the following guidelines: 40 | 41 | * Breaking backwards compatibility bumps the major 42 | * New additions without breaking backwards compatibility bumps the minor 43 | * Bug fixes and misc changes bump the patch 44 | 45 | For more information on SemVer, please visit http://semver.org/. 46 | 47 | 48 | Bug tracker 49 | ----------- 50 | 51 | Have a bug? Please create an issue here on GitHub! 52 | 53 | https://github.com/twitter/bootstrap/issues 54 | 55 | 56 | Twitter account 57 | --------------- 58 | 59 | Keep up to date on announcements and more by following Bootstrap on Twitter, @TwBootstrap. 60 | 61 | 62 | Mailing list 63 | ------------ 64 | 65 | Have a question? Ask on our mailing list! 66 | 67 | twitter-bootstrap@googlegroups.com 68 | 69 | http://groups.google.com/group/twitter-bootstrap 70 | 71 | 72 | Developers 73 | ---------- 74 | 75 | We have included a makefile with convenience methods for working with the bootstrap library. 76 | 77 | + **build** - `make build` 78 | This will run the less compiler on the bootstrap lib and generate a bootstrap.css and bootstrap.min.css file. 79 | The lessc compiler is required for this command to run. 80 | 81 | + **watch** - `make watch` 82 | This is a convenience method for watching your less files and automatically building them whenever you save. 83 | Watchr is required for this command to run. 84 | 85 | 86 | Authors 87 | ------- 88 | 89 | **Mark Otto** 90 | 91 | + http://twitter.com/mdo 92 | + http://github.com/markdotto 93 | 94 | **Jacob Thornton** 95 | 96 | + http://twitter.com/fat 97 | + http://github.com/fat 98 | 99 | 100 | Copyright and license 101 | --------------------- 102 | 103 | Copyright 2011 Twitter, Inc. 104 | 105 | Licensed under the Apache License, Version 2.0 (the "License"); 106 | you may not use this work except in compliance with the License. 107 | You may obtain a copy of the License in the LICENSE file, or at: 108 | 109 | http://www.apache.org/licenses/LICENSE-2.0 110 | 111 | Unless required by applicable law or agreed to in writing, software 112 | distributed under the License is distributed on an "AS IS" BASIS, 113 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 114 | See the License for the specific language governing permissions and 115 | limitations under the License. 116 | -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/ico/bootstrap-apple-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/public/bootstrap/docs/assets/ico/bootstrap-apple-114x114.png -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/ico/bootstrap-apple-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/public/bootstrap/docs/assets/ico/bootstrap-apple-57x57.png -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/ico/bootstrap-apple-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/public/bootstrap/docs/assets/ico/bootstrap-apple-72x72.png -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/ico/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/public/bootstrap/docs/assets/ico/favicon.ico -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/img/bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/public/bootstrap/docs/assets/img/bird.png -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/img/browsers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/public/bootstrap/docs/assets/img/browsers.png -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/img/example-diagram-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/public/bootstrap/docs/assets/img/example-diagram-01.png -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/img/example-diagram-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/public/bootstrap/docs/assets/img/example-diagram-02.png -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/img/example-diagram-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/public/bootstrap/docs/assets/img/example-diagram-03.png -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/img/grid-18px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/public/bootstrap/docs/assets/img/grid-18px.png -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/img/twitter-logo-no-bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tnantoka/LooseLeaf/0c6333977224d8f4ef7ad40415aa69e0ff76f7b5/skeleton/public/bootstrap/docs/assets/img/twitter-logo-no-bird.png -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/js/application.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | 3 | // table sort example 4 | // ================== 5 | 6 | $("#sortTableExample").tablesorter( { sortList: [[ 1, 0 ]] } ) 7 | 8 | 9 | // add on logic 10 | // ============ 11 | 12 | $('.add-on :checkbox').click(function () { 13 | if ($(this).attr('checked')) { 14 | $(this).parents('.add-on').addClass('active') 15 | } else { 16 | $(this).parents('.add-on').removeClass('active') 17 | } 18 | }) 19 | 20 | 21 | // Disable certain links in docs 22 | // ============================= 23 | // Please do not carry these styles over to your projects, it's merely here to prevent button clicks form taking you away from your spot on page 24 | 25 | $('ul.tabs a, ul.pills a, .pagination a, .well .btn, .actions .btn, .alert-message .btn, a.close').click(function (e) { 26 | e.preventDefault() 27 | }) 28 | 29 | // Copy code blocks in docs 30 | $(".copy-code").focus(function () { 31 | var el = this; 32 | // push select to event loop for chrome :{o 33 | setTimeout(function () { $(el).select(); }, 0); 34 | }); 35 | 36 | 37 | // POSITION STATIC TWIPSIES 38 | // ======================== 39 | 40 | $(window).bind( 'load resize', function () { 41 | $(".twipsies a").each(function () { 42 | $(this) 43 | .twipsy({ 44 | live: false 45 | , placement: $(this).attr('title') 46 | , trigger: 'manual' 47 | , offset: 2 48 | }) 49 | .twipsy('show') 50 | }) 51 | }) 52 | }); 53 | -------------------------------------------------------------------------------- /skeleton/public/bootstrap/docs/assets/js/google-code-prettify/prettify.css: -------------------------------------------------------------------------------- 1 | .com { color: #93a1a1; } 2 | .lit { color: #195f91; } 3 | .pun, .opn, .clo { color: #93a1a1; } 4 | .fun { color: #dc322f; } 5 | .str, .atv { color: #268bd2; } 6 | .kwd, .tag { color: #195f91; } 7 | .typ, .atn, .dec, .var { color: #CB4B16; } 8 | .pln { color: #93a1a1; } 9 | pre.prettyprint { 10 | background: #fefbf3; 11 | padding: 9px; 12 | border: 1px solid rgba(0,0,0,.2); 13 | -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.1); 14 | -moz-box-shadow: 0 1px 2px rgba(0,0,0,.1); 15 | box-shadow: 0 1px 2px rgba(0,0,0,.1); 16 | } 17 | 18 | /* Specify class=linenums on a pre to get line numbering */ 19 | ol.linenums { margin: 0 0 0 40px; } /* IE indents via margin-left */ 20 | ol.linenums li { color: rgba(0,0,0,.15); line-height: 20px; } 21 | /* Alternate shading for lines */ 22 | li.L1, li.L3, li.L5, li.L7, li.L9 { } 23 | 24 | /* 25 | $base03: #002b36; 26 | $base02: #073642; 27 | $base01: #586e75; 28 | $base00: #657b83; 29 | $base0: #839496; 30 | $base1: #93a1a1; 31 | $base2: #eee8d5; 32 | $base3: #fdf6e3; 33 | $yellow: #b58900; 34 | $orange: #cb4b16; 35 | $red: #dc322f; 36 | $magenta: #d33682; 37 | $violet: #6c71c4; 38 | $blue: #268bd2; 39 | $cyan: #2aa198; 40 | $green: #859900; 41 | */ -------------------------------------------------------------------------------- /skeleton/public/bootstrap/examples/container-app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bootstrap, from Twitter 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
79 |
80 |
81 | Project name 82 | 87 |
88 | 89 | 90 | 91 |
92 |
93 |
94 |
95 | 96 |
97 | 98 |
99 | 102 |
103 |
104 |

Main content

105 |
106 |
107 |

Secondary content

108 |
109 |
110 |
111 | 112 |
113 |

© Company 2011

114 |
115 | 116 |
117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /skeleton/public/bootstrap/examples/fluid.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bootstrap, from Twitter 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 | Project name 35 | 40 |

Logged in as username

41 |
42 |
43 |
44 | 45 |
46 | 71 |
72 | 73 |
74 |

Hello, world!

75 |

Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.

76 |

Learn more »

77 |
78 | 79 | 80 |
81 |
82 |

Heading

83 |

Etiam porta sem malesuada magna mollis euismod. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.

84 |

View details »

85 |
86 |
87 |

Heading

88 |

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.

89 |

View details »

90 |
91 |
92 |

Heading

93 |

Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

94 |

View details »

95 |
96 |
97 | 98 |
99 | 100 | 101 |
102 |
103 |

Heading

104 |

Etiam porta sem malesuada magna mollis euismod. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.

105 |

View details »

106 |
107 |
108 |

Heading

109 |

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.

110 |

View details »

111 |
112 |
113 |

Heading

114 |

Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

115 |

View details »

116 |
117 |
118 | 119 |
120 |

© Company 2011

121 |
122 |
123 |
124 | 125 | 126 | -------------------------------------------------------------------------------- /skeleton/public/bootstrap/examples/hero.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Bootstrap, from Twitter 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 | Project name 35 | 40 |
41 |
42 |
43 | 44 |
45 | 46 | 47 |
48 |

Hello, world!

49 |

Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.

50 |

Learn more »

51 |
52 | 53 | 54 |
55 |
56 |

Heading

57 |

Etiam porta sem malesuada magna mollis euismod. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit.

58 |

View details »

59 |
60 |
61 |

Heading

62 |

Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui.

63 |

View details »

64 |
65 |
66 |

Heading

67 |

Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

68 |

View details »

69 |
70 |
71 | 72 |
73 |

© Company 2011

74 |
75 | 76 |
77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /skeleton/public/bootstrap/js/bootstrap-alerts.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-alerts.js v1.3.0 3 | * http://twitter.github.com/bootstrap/javascript.html#alerts 4 | * ========================================================== 5 | * Copyright 2011 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 | 21 | !function( $ ){ 22 | 23 | /* CSS TRANSITION SUPPORT (https://gist.github.com/373874) 24 | * ======================================================= */ 25 | 26 | var transitionEnd 27 | 28 | $(document).ready(function () { 29 | 30 | $.support.transition = (function () { 31 | var thisBody = document.body || document.documentElement 32 | , thisStyle = thisBody.style 33 | , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined 34 | return support 35 | })() 36 | 37 | // set CSS transition event type 38 | if ( $.support.transition ) { 39 | transitionEnd = "TransitionEnd" 40 | if ( $.browser.webkit ) { 41 | transitionEnd = "webkitTransitionEnd" 42 | } else if ( $.browser.mozilla ) { 43 | transitionEnd = "transitionend" 44 | } else if ( $.browser.opera ) { 45 | transitionEnd = "oTransitionEnd" 46 | } 47 | } 48 | 49 | }) 50 | 51 | /* ALERT CLASS DEFINITION 52 | * ====================== */ 53 | 54 | var Alert = function ( content, options ) { 55 | this.settings = $.extend({}, $.fn.alert.defaults, options) 56 | this.$element = $(content) 57 | .delegate(this.settings.selector, 'click', this.close) 58 | } 59 | 60 | Alert.prototype = { 61 | 62 | close: function (e) { 63 | var $element = $(this).parent('.alert-message') 64 | 65 | e && e.preventDefault() 66 | $element.removeClass('in') 67 | 68 | function removeElement () { 69 | $element.remove() 70 | } 71 | 72 | $.support.transition && $element.hasClass('fade') ? 73 | $element.bind(transitionEnd, removeElement) : 74 | removeElement() 75 | } 76 | 77 | } 78 | 79 | 80 | /* ALERT PLUGIN DEFINITION 81 | * ======================= */ 82 | 83 | $.fn.alert = function ( options ) { 84 | 85 | if ( options === true ) { 86 | return this.data('alert') 87 | } 88 | 89 | return this.each(function () { 90 | var $this = $(this) 91 | 92 | if ( typeof options == 'string' ) { 93 | return $this.data('alert')[options]() 94 | } 95 | 96 | $(this).data('alert', new Alert( this, options )) 97 | 98 | }) 99 | } 100 | 101 | $.fn.alert.defaults = { 102 | selector: '.close' 103 | } 104 | 105 | $(document).ready(function () { 106 | new Alert($('body'), { 107 | selector: '.alert-message[data-alert] .close' 108 | }) 109 | }) 110 | 111 | }( window.jQuery || window.ender ); -------------------------------------------------------------------------------- /skeleton/public/bootstrap/js/bootstrap-dropdown.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-dropdown.js v1.3.0 3 | * http://twitter.github.com/bootstrap/javascript.html#dropdown 4 | * ============================================================ 5 | * Copyright 2011 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 | 21 | !function( $ ){ 22 | 23 | /* DROPDOWN PLUGIN DEFINITION 24 | * ========================== */ 25 | 26 | $.fn.dropdown = function ( selector ) { 27 | return this.each(function () { 28 | $(this).delegate(selector || d, 'click', function (e) { 29 | var li = $(this).parent('li') 30 | , isActive = li.hasClass('open') 31 | 32 | clearMenus() 33 | !isActive && li.toggleClass('open') 34 | return false 35 | }) 36 | }) 37 | } 38 | 39 | /* APPLY TO STANDARD DROPDOWN ELEMENTS 40 | * =================================== */ 41 | 42 | var d = 'a.menu, .dropdown-toggle' 43 | 44 | function clearMenus() { 45 | $(d).parent('li').removeClass('open') 46 | } 47 | 48 | $(function () { 49 | $('html').bind("click", clearMenus) 50 | $('body').dropdown( '[data-dropdown] a.menu, [data-dropdown] .dropdown-toggle' ) 51 | }) 52 | 53 | }( window.jQuery || window.ender ); 54 | -------------------------------------------------------------------------------- /skeleton/public/bootstrap/js/bootstrap-modal.js: -------------------------------------------------------------------------------- 1 | /* ========================================================= 2 | * bootstrap-modal.js v1.3.0 3 | * http://twitter.github.com/bootstrap/javascript.html#modal 4 | * ========================================================= 5 | * Copyright 2011 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 | 21 | !function( $ ){ 22 | 23 | /* CSS TRANSITION SUPPORT (https://gist.github.com/373874) 24 | * ======================================================= */ 25 | 26 | var transitionEnd 27 | 28 | $(document).ready(function () { 29 | 30 | $.support.transition = (function () { 31 | var thisBody = document.body || document.documentElement 32 | , thisStyle = thisBody.style 33 | , support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined 34 | return support 35 | })() 36 | 37 | // set CSS transition event type 38 | if ( $.support.transition ) { 39 | transitionEnd = "TransitionEnd" 40 | if ( $.browser.webkit ) { 41 | transitionEnd = "webkitTransitionEnd" 42 | } else if ( $.browser.mozilla ) { 43 | transitionEnd = "transitionend" 44 | } else if ( $.browser.opera ) { 45 | transitionEnd = "oTransitionEnd" 46 | } 47 | } 48 | 49 | }) 50 | 51 | 52 | /* MODAL PUBLIC CLASS DEFINITION 53 | * ============================= */ 54 | 55 | var Modal = function ( content, options ) { 56 | this.settings = $.extend({}, $.fn.modal.defaults, options) 57 | this.$element = $(content) 58 | .delegate('.close', 'click.modal', $.proxy(this.hide, this)) 59 | 60 | if ( this.settings.show ) { 61 | this.show() 62 | } 63 | 64 | return this 65 | } 66 | 67 | Modal.prototype = { 68 | 69 | toggle: function () { 70 | return this[!this.isShown ? 'show' : 'hide']() 71 | } 72 | 73 | , show: function () { 74 | var that = this 75 | this.isShown = true 76 | this.$element.trigger('show') 77 | 78 | escape.call(this) 79 | backdrop.call(this, function () { 80 | var transition = $.support.transition && that.$element.hasClass('fade') 81 | 82 | that.$element 83 | .appendTo(document.body) 84 | .show() 85 | 86 | if (transition) { 87 | that.$element[0].offsetWidth // force reflow 88 | } 89 | 90 | that.$element 91 | .addClass('in') 92 | 93 | transition ? 94 | that.$element.one(transitionEnd, function () { that.$element.trigger('shown') }) : 95 | that.$element.trigger('shown') 96 | 97 | }) 98 | 99 | return this 100 | } 101 | 102 | , hide: function (e) { 103 | e && e.preventDefault() 104 | 105 | if ( !this.isShown ) { 106 | return this 107 | } 108 | 109 | var that = this 110 | this.isShown = false 111 | 112 | escape.call(this) 113 | 114 | this.$element 115 | .trigger('hide') 116 | .removeClass('in') 117 | 118 | function removeElement () { 119 | that.$element 120 | .hide() 121 | .trigger('hidden') 122 | 123 | backdrop.call(that) 124 | } 125 | 126 | $.support.transition && this.$element.hasClass('fade') ? 127 | this.$element.one(transitionEnd, removeElement) : 128 | removeElement() 129 | 130 | return this 131 | } 132 | 133 | } 134 | 135 | 136 | /* MODAL PRIVATE METHODS 137 | * ===================== */ 138 | 139 | function backdrop ( callback ) { 140 | var that = this 141 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 142 | if ( this.isShown && this.settings.backdrop ) { 143 | var doAnimate = $.support.transition && animate 144 | 145 | this.$backdrop = $('