Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First import

  • Loading branch information...
commit b398897438d41dbf7d3f0bf3327fdc0474d6f464 0 parents
@claudioc authored
Showing with 12,628 additions and 0 deletions.
  1. +3 −0  .gitignore
  2. +2 −0  AUTHORS
  3. +9 −0 LICENSE
  4. +60 −0 README.md
  5. +1 −0  data
  6. +275 −0 jiky
  7. +186 −0 lib/gitmech.js
  8. +49 −0 lib/locker.js
  9. +20 −0 lib/namer.js
  10. +34 −0 lib/tools.js
  11. +36 −0 package.json
  12. +345 −0 public/css/shCoreDefault.css
  13. +150 −0 public/css/style.css
  14. +82 −0 public/js/app.js
  15. +1,092 −0 public/vendor/bootstrap/css/bootstrap-responsive.css
  16. +9 −0 public/vendor/bootstrap/css/bootstrap-responsive.min.css
  17. +6,039 −0 public/vendor/bootstrap/css/bootstrap.css
  18. +9 −0 public/vendor/bootstrap/css/bootstrap.min.css
  19. BIN  public/vendor/bootstrap/img/glyphicons-halflings-white.png
  20. BIN  public/vendor/bootstrap/img/glyphicons-halflings.png
  21. +2,159 −0 public/vendor/bootstrap/js/bootstrap.js
  22. +6 −0 public/vendor/bootstrap/js/bootstrap.min.js
  23. +2 −0  public/vendor/jquery.min.js
  24. +634 −0 public/vendor/markitup/jquery.markitup.js
  25. BIN  public/vendor/markitup/sets/default/images/bold.png
  26. BIN  public/vendor/markitup/sets/default/images/clean.png
  27. BIN  public/vendor/markitup/sets/default/images/image.png
  28. BIN  public/vendor/markitup/sets/default/images/italic.png
  29. BIN  public/vendor/markitup/sets/default/images/link.png
  30. BIN  public/vendor/markitup/sets/default/images/list-bullet.png
  31. BIN  public/vendor/markitup/sets/default/images/list-numeric.png
  32. BIN  public/vendor/markitup/sets/default/images/picture.png
  33. BIN  public/vendor/markitup/sets/default/images/preview.png
  34. BIN  public/vendor/markitup/sets/default/images/stroke.png
  35. +30 −0 public/vendor/markitup/sets/default/set.js
  36. +34 −0 public/vendor/markitup/sets/default/style.css
  37. BIN  public/vendor/markitup/sets/markdown/images/bold_16.png
  38. BIN  public/vendor/markitup/sets/markdown/images/code_16.png
  39. BIN  public/vendor/markitup/sets/markdown/images/h1.png
  40. BIN  public/vendor/markitup/sets/markdown/images/h2.png
  41. BIN  public/vendor/markitup/sets/markdown/images/h3.png
  42. BIN  public/vendor/markitup/sets/markdown/images/h4.png
  43. BIN  public/vendor/markitup/sets/markdown/images/h5.png
  44. BIN  public/vendor/markitup/sets/markdown/images/h6.png
  45. BIN  public/vendor/markitup/sets/markdown/images/info_16.png
  46. BIN  public/vendor/markitup/sets/markdown/images/italic_16.png
  47. BIN  public/vendor/markitup/sets/markdown/images/link_16.png
  48. BIN  public/vendor/markitup/sets/markdown/images/list-bullet_16.png
  49. BIN  public/vendor/markitup/sets/markdown/images/list-numeric_16.png
  50. BIN  public/vendor/markitup/sets/markdown/images/picture_16.png
  51. BIN  public/vendor/markitup/sets/markdown/images/preview_16.png
  52. BIN  public/vendor/markitup/sets/markdown/images/quotes_16.png
  53. +56 −0 public/vendor/markitup/sets/markdown/set.js
  54. +44 −0 public/vendor/markitup/sets/markdown/style.css
  55. BIN  public/vendor/markitup/skins/markitup/images/bg-container.png
  56. BIN  public/vendor/markitup/skins/markitup/images/bg-editor-bbcode.png
  57. BIN  public/vendor/markitup/skins/markitup/images/bg-editor-dotclear.png
  58. BIN  public/vendor/markitup/skins/markitup/images/bg-editor-html.png
  59. BIN  public/vendor/markitup/skins/markitup/images/bg-editor-json.png
  60. BIN  public/vendor/markitup/skins/markitup/images/bg-editor-markdown.png
  61. BIN  public/vendor/markitup/skins/markitup/images/bg-editor-textile.png
  62. BIN  public/vendor/markitup/skins/markitup/images/bg-editor-wiki.png
  63. BIN  public/vendor/markitup/skins/markitup/images/bg-editor-xml.png
  64. BIN  public/vendor/markitup/skins/markitup/images/bg-editor.png
  65. BIN  public/vendor/markitup/skins/markitup/images/handle.png
  66. BIN  public/vendor/markitup/skins/markitup/images/menu.png
  67. BIN  public/vendor/markitup/skins/markitup/images/submenu.png
  68. +147 −0 public/vendor/markitup/skins/markitup/style.css
  69. BIN  public/vendor/markitup/skins/simple/images/handle.png
  70. BIN  public/vendor/markitup/skins/simple/images/menu.png
  71. BIN  public/vendor/markitup/skins/simple/images/submenu.png
  72. +118 −0 public/vendor/markitup/skins/simple/style.css
  73. +5 −0 public/vendor/markitup/templates/preview.css
  74. +11 −0 public/vendor/markitup/templates/preview.html
  75. +499 −0 routes/index.js
  76. +5 −0 scripts/supervisor_run.sh
  77. +4 −0 test/README.md
  78. +39 −0 test/spec/lockerSpec.js
  79. +21 −0 test/spec/namerSpec.js
  80. +46 −0 test/spec/toolsSpec.js
  81. +4 −0 views/404.jade
  82. +11 −0 views/compare.jade
  83. +33 −0 views/create.jade
  84. +41 −0 views/edit.jade
  85. +21 −0 views/history.jade
  86. +4 −0 views/index.jade
  87. +52 −0 views/layout.jade
  88. +22 −0 views/list.jade
  89. +6 −0 views/login.jade
  90. +42 −0 views/mixins/form.jade
  91. +3 −0  views/preview.jade
  92. +16 −0 views/search.jade
  93. +26 −0 views/show.jade
  94. +72 −0 views/syntax.jade
  95. +14 −0 views/welcome.jade
3  .gitignore
@@ -0,0 +1,3 @@
+node_modules/*
+config.yml
+data/*
2  AUTHORS
@@ -0,0 +1,2 @@
+Claudio Cicali <claudio.cicali@gmail.com>
+http://ccl.me
9 LICENSE
@@ -0,0 +1,9 @@
+(The MIT License)
+
+Copyright (c) 2012 Claudio Cicali <claudio.cicali@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
60 README.md
@@ -0,0 +1,60 @@
+Jiky
+====
+
+A simple git based wiki engine written for Node.js
+The aim of wiki is to provide a very easy way to create a centralized documentation area for people used to work with git and markdown. It should fit well into a development team without the need to learn or install ad-hoc servers or applications. Jiky is very much inspired by the github wiki system (gollum), but tries to be more a stand-alone system than gollum is.
+
+Features
+--------
+
+- No database: uses a git repository as the document archive
+- No user management: authentication provided (only) via with Google logins
+- Markdown for everything, [github flavored](http://github.github.com/github-flavored-markdown/)
+- Uses [Markitup](http://markitup.jaysalvat.com/home/) as the markup editor, with a nice preview
+- Inspired by the well known github [Gollum](https://github.com/github/gollum) wiki
+- Show differences between document revisions
+- Search through the content and the page names
+- Layout accepts custom sidebar and footer
+- Can also use custom css and JavaScript scripts
+- White list for authorization on page reading and writing
+
+Known limitations
+-----------------
+
+- There is only one authentication method (Google)
+- The repository is "flat" (no directory)
+- Authorization is only based on a regexp built white list on the user email address
+
+Installation
+------------
+
+`npm install jiky`
+
+Jiky needs a config file. To create a sample config file, just run `jiky -s`, redirect the output on a file and then edit it. The config file contains all the available configuration keys. Be sure to provide a valid server hostname (like wiki.mycompany.com) for Google Auth to be able to get back to you.
+
+The basic command to run the wiki will then be
+
+`jiky -c config.yaml`
+
+Before running jiky you need to initialize its git repository somewhere (`git init` is sufficient).
+
+Authentication
+--------------
+
+The _authentication_ section of the config file has two keys: anonRead and validMatches. If the anonRead is true, then anyone can read anything. If anonRead is false, then the email of the user MUST match at least one of the regular expressions provided via validMatches, which is a comma separated list. There is no "anonWrite", though. To edit a page the user must be authenticated.
+
+The authentication is mandatory to edit pages from the web interface, but Jiky works on a git repository; that means that you could skip the authentication altogheter and edit pages with your editor and push to the remote that Jiky is serving.
+
+Customization
+-------------
+
+You can customize Jiky in four different ways:
+
+- add a left sidebar to every page: just add a file named `_sidebar.md` containing the markdown you want to display to the repository. You can edit or create the sidebar from Jiky itself, visiting `/wiki/_sidebar` (note that the title of the page in this case is useless)
+- add a footer to every page: the page you need to create is "_footer.md" and the same rules for the sidebar apply
+- add a custom CSS file, included in every page after every other CSS. The name of the file must be `_style.css` and must reside in the repository. It is not possible to edit the file from jiky itself
+- add a custom JavaScript file, included in every page after every other JavaScript file. The name of the file must be `_script.js` and must reside in the repository. It is not possible to edit the file from jiky itself
+
+All those files are cached (thus, not re-read for every page load, but kept in memory). This means that for every modification in _style.css and _script.js you need to restart the server. This is not tru for the footer and the sidebar ONLY if you edit those pages from jiky (which in that case will clear the cache by itself).
+
+Jiky uses twitter Bootstrap and jQuery as its front-end components.
1  data
@@ -0,0 +1 @@
+Subproject commit 787c19abde6552b8c4a9d96d9634f07c93f6c08d
275 jiky
@@ -0,0 +1,275 @@
+#!/usr/bin/env node
+
+var express = require('express')
+ , http = require('http')
+ , path = require('path')
+ , passport = require('passport')
+ , Git = require("./lib/gitmech")
+ , expValidator = require('express-validator')
+ , gravatar = require("gravatar")
+ , Fs = require("fs")
+ , Url = require("url")
+ , Marked = require('marked')
+ , GoogleStrategy = require('passport-google').Strategy
+ , yaml = require("yaml")
+ , program = require('commander');
+
+program.version('0.1.0')
+ .option('-c, --config <path>', 'Specify the config file')
+ .option('-s, --sample-config', 'Dumps a config file template and exits')
+ .parse(process.argv);
+
+if (program.sampleConfig) {
+ console.log(sampleConfig());
+ process.exit(0);
+}
+
+if (!program.config || !Fs.existsSync(program.config)) {
+ program.help();
+ process.exit(-1);
+}
+
+var config = yaml.eval(Fs.readFileSync(program.config).toString());
+
+if (!config.application || !config.server) {
+ console.log("A problem exists on the config file. Cannot continue.");
+ process.exit(-1);
+}
+
+var repo = config.application.repository;
+
+try {
+ Git.init(repo);
+} catch(e) {
+ console.log(e.message)
+ console.log("Please specify a valid git repository");
+ program.help()
+ process.exit(-1);
+}
+
+// Global to be accessed from the routes module
+global.app = express();
+app.locals.Git = Git;
+app.locals.repo = repo;
+app.locals._sidebar;
+app.locals._footer;
+app.locals._style;
+app.locals._script;
+app.locals.appTitle = config.application.title || "Jiky";
+app.locals.port = config.server.port || process.env.PORT || 6067;
+app.locals.hostname = config.server.hostname || "localhost";
+app.locals.authorization = config.authorization || { anonRead: false, validMatches: ".+" };
+
+app.locals.coalesce = function(value, def) {
+ return typeof value === 'undefined' ? def : value;
+}
+
+var routes = require("./routes");
+
+app.configure('development', function() {
+ app.use(express.errorHandler());
+ app.locals.pretty = true; // Pretty HTML output from Jade
+});
+
+app.configure('production', function() {
+});
+
+app.configure(function() {
+ app.set('port', app.locals.port);
+ app.set('views', __dirname + '/views');
+ app.set('view engine', 'jade');
+ app.use(express.favicon());
+ app.use(express.static(path.join(__dirname, 'public')));
+ app.use(express.logger('default'));
+ app.use(express.cookieParser("mille1000"));
+ app.use(express.cookieSession({ secret: 'tobo!', cookie: { maxAge: 30 * 24 * 60 * 60 * 1000 }})); // a Month
+ app.use(express.bodyParser());
+ app.use(expValidator);
+ app.use(express.methodOverride());
+ app.use(passport.initialize());
+ app.use(passport.session());
+ app.use(function (req, res, next) {
+ res.locals({
+ get user() {
+ return req.user;
+ },
+ isAnonymous: function () {
+ return !req.user;
+ },
+ _sidebar: null,
+ _footer: null,
+ _style: null,
+ _script: null,
+ gravatar: function(email) {
+ return gravatar;
+ },
+ // FIXME: we need to handle these has* better, because we're hitting the
+ // fs for every single request even if we cache the sidebar/footer/etc
+ hasSidebar: function() {
+ return Fs.existsSync(app.locals.repo + "/" + "_sidebar.md");
+ },
+ hasFooter: function() {
+ return Fs.existsSync(app.locals.repo + "/" + "_footer.md");
+ },
+ hasCustomStyle: function() {
+ return Fs.existsSync(app.locals.repo + "/" + "_style.css");
+ },
+ hasCustomScript: function() {
+ return Fs.existsSync(app.locals.repo + "/" + "_script.js");
+ }
+ });
+
+ next();
+ });
+
+ // Logic to include custom _footer, _sidebar, _script and _style.css
+ app.use(function (req, res, next) {
+
+ if (res.locals.hasSidebar()) {
+ if (!app.locals._sidebar) {
+ Git.readFile("_sidebar.md", "HEAD", function(err, content) {
+ if (!err) {
+ res.locals._sidebar = app.locals._sidebar = Marked(content.split("\n").splice(1).join("\n"));
+ }
+ });
+ } else {
+ res.locals._sidebar = app.locals._sidebar;
+ }
+ } else {
+ res.locals._sidebar = app.locals._sidebar = null;
+ }
+
+ if (res.locals.hasFooter()) {
+ if (!app.locals._footer) {
+ Git.readFile("_footer.md", "HEAD", function(err, content) {
+ if (!err) {
+ res.locals._footer = app.locals._footer = Marked(content.split("\n").splice(1).join("\n"));
+ }
+ });
+ } else {
+ res.locals._footer = app.locals._footer;
+ }
+ } else {
+ res.locals._footer = app.locals._footer = null;
+ }
+
+ if (res.locals.hasCustomStyle()) {
+ if (!app.locals._style) {
+ // Read sync because this info is needed by the layout
+ res.locals._style = app.locals._style = Fs.readFileSync(app.locals.repo + "/" + "_style.css");
+ } else {
+ res.locals._style = app.locals._style;
+ }
+ } else {
+ res.locals._style = app.locals._style = null;
+ }
+
+ if (res.locals.hasCustomScript()) {
+ if (!app.locals._script) {
+ // Read sync because this info is needed by the layout
+ res.locals._script = app.locals._script = Fs.readFileSync(app.locals.repo + "/" + "_script.js");
+ } else {
+ res.locals._script = app.locals._script;
+ }
+ } else {
+ res.locals._script = app.locals._script = null;
+ }
+
+ next();
+ });
+
+ app.use(app.router);
+});
+
+function requireAuthentication(req, res, next) {
+ if (!res.locals.user) {
+ res.redirect("/login");
+ /*
+ res.statusCode = 403;
+ res.end('<h1>Forbidden</h1>');
+ */
+ } else {
+ next();
+ }
+}
+
+/*
+ * Passport configuration
+ */
+
+passport.use(new GoogleStrategy({
+ returnURL: 'http://' + app.locals.hostname + ':' + app.locals.port + '/auth/google/return',
+ realm: 'http://' + app.locals.hostname + ':' + app.locals.port
+ },
+
+ function(identifier, profile, done) {
+ done(undefined, profile);
+ }
+));
+
+passport.serializeUser(function(user, done) {
+ done(null, user);
+});
+
+passport.deserializeUser(function(user, done) {
+ user.asGitAuthor = user.displayName + " <" + user.emails[0].value + ">";
+ done(undefined, user);
+});
+
+app.all("/pages/*", requireAuthentication);
+
+if (!app.locals.authorization.anonRead) {
+ app.all("/wiki/*", requireAuthentication);
+ app.all("/search", requireAuthentication);
+}
+
+app.get ("/", routes.index);
+app.get ("/wiki", routes.pageList);
+app.get ("/wiki/:page", routes.pageShow);
+app.get ("/wiki/:page/history", routes.pageHistory);
+app.get ("/wiki/:page/:version", routes.pageShow);
+app.get ("/wiki/:page/compare/:revisions", routes.pageCompare);
+
+app.get ("/search", routes.pageSearch);
+
+app.get ("/pages/new", routes.pageNew);
+app.get ("/pages/new/:page", routes.pageNew);
+app.post ("/pages", routes.pageCreate);
+
+app.get ("/pages/:page/edit", routes.pageEdit);
+app.put ("/pages/:page", routes.pageUpdate);
+app.delete ("/pages/:page", routes.pageDestroy);
+
+app.post ("/misc/preview", routes.miscPreview);
+app.get ("/misc/syntax-reference", routes.miscSyntaxReference);
+app.get ("/misc/existence", routes.miscExistence);
+
+app.get ("/login", routes.login);
+app.get ("/logout", routes.logout);
+
+app.get ("/auth/google", passport.authenticate('google'));
+app.get ("/auth/google/return", passport.authenticate('google', { successRedirect: '/auth/done', failureRedirect: '/login' }));
+app.get ("/auth/done", routes.authDone);
+
+app.all('*', routes.error404);
+
+http.createServer(app).listen(app.get('port'), function(){
+ console.log((new Date()) + " - Jiky server listening on port " + app.get('port'));
+});
+
+// Dumps a sample config file
+function sampleConfig() {
+ return "\
+---\n\
+ # Configuration sample file for Jiky (YAML)\n\
+ application:\n\
+ repository: \"/home/user/test\"\n\
+ title: \"Jiky\"\n\
+ server:\n\
+ hostname: \"jiky.org\"\n\
+ port: 6067\n\
+ authorization:\n\
+ anonRead: true\n\
+ validMatches: \".+\"\
+";
+}
186 lib/gitmech.js
@@ -0,0 +1,186 @@
+var Path = require("path")
+ , ChildProcess = require('child_process')
+ , Fs = require("fs");
+
+module.exports = Gitmech = (function() {
+
+ var Repo;
+ var gitCommands, gitDir, workTree;
+ var gitENOENT = /fatal: (Path '([^']+)' does not exist in '([0-9a-f]{40})'|ambiguous argument '([^']+)': unknown revision or path not in the working tree.)/;
+
+ // Internal helper to talk to the git subprocess
+ function gitExec(commands, callback) {
+ commands = gitCommands.concat(commands);
+ //console.log("git " + commands.join(" "));
+ var child = ChildProcess.spawn("git", commands, { cwd: workTree });
+ var stdout = [], stderr = [];
+ child.stdout.addListener('data', function (text) {
+ stdout[stdout.length] = text;
+ });
+ child.stderr.addListener('data', function (text) {
+ stderr[stderr.length] = text;
+ });
+ var exitCode;
+ child.addListener('exit', function (code) {
+ exitCode = code;
+ });
+ child.addListener('close', function () {
+ if (exitCode > 0) {
+ var err = new Error("git " + commands.join(" ") + "\n" + join(stderr, 'utf8'));
+ if (gitENOENT.test(err.message)) {
+ err.errno = process.ENOENT;
+ }
+ callback(err);
+ return;
+ }
+ callback(null, join(stdout));
+ });
+ child.stdin.end();
+ }
+
+ function join(arr) {
+ var result, index = 0, length;
+ length = arr.reduce(function(l, b) {
+ return l + b.length;
+ }, 0);
+ result = new Buffer(length);
+ arr.forEach(function(b) {
+ b.copy(result, index);
+ index += b.length;
+ });
+
+ return result;
+ }
+
+ return {
+
+ init: function(repoDir) {
+
+ try {
+ Fs.statSync(repoDir);
+ } catch (e) {
+ throw new Error("Bad repository path (not exists): " + repoDir);
+ }
+
+ try {
+ var gitDir = Path.join(repoDir, ".git");
+ Fs.statSync(gitDir);
+ workTree = repoDir;
+ gitCommands = ["--git-dir=" + gitDir, "--work-tree=" + workTree];
+ } catch (e) {
+ throw new Error("Bad repository path (not initialized): " + repoDir);
+ }
+ },
+
+ readFile: function(path, version, callback) {
+ gitExec(["show", version + ":" + path], function(err, data) {
+ if (err) {
+ callback(err);
+ } else {
+ callback(null, data.toString());
+ }
+ });
+ },
+
+ log: function(path, version, howMany, callback) {
+
+ if (typeof howMany == 'function') {
+ callback = howMany;
+ howMany = 1;
+ }
+
+ gitExec(["log", "-" + howMany, "--reverse", "--no-notes", "--pretty=format:%h%n%H%n%an%n%ae%n%aD%n%ar%n%at%n%s", version, "--", path], function(err, data) {
+
+ var logdata = data.toString().split("\n")
+ , group
+ , metadata = [];
+
+ for (var i = Math.floor(logdata.length / 8); i-- > 0; ) {
+ group = logdata.slice(i * 8, (i + 1) * 8);
+ metadata.push({
+ name: path.replace(".md", ""),
+ hash: group[0],
+ hashRef: group[0],
+ fullhash: group[1],
+ author: group[2],
+ email: group[3],
+ date: group[4],
+ relDate: group[5],
+ timestamp: group[6],
+ subject: group[7]
+ });
+ }
+
+ if (metadata[0]) {
+ metadata[0].hashRef = ''; // This can be used linking this version, but needs to be empty for HEAD
+ }
+
+ if (howMany == 1) {
+ metadata = metadata[0];
+ }
+
+ callback(null, metadata);
+ });
+ },
+
+ add: function(path, message, author, callback) {
+ gitExec(["add", path], function(err) {
+ if (err) {
+ callback(err);
+ } else {
+ gitExec(["commit", "--author=\"" + author + "\"", "-m", message, path], function(err) {
+ callback(err);
+ });
+ }
+ });
+ },
+
+ rm: function(path, message, author, callback) {
+ gitExec(["rm", path], function(err) {
+ if (err) {
+ callback(err);
+ } else {
+ gitExec(["commit", "--author=\"" + author + "\"", "-m", message, path], function(err) {
+ callback(err);
+ });
+ }
+ });
+ },
+
+ grep: function(pattern, callback) {
+ // TODO decide for -w
+ gitExec([ "grep", "--no-color", "-F", "-n", "-i", "-I", pattern ], function(err, data) {
+ var result;
+ if (data) {
+ result = data.toString().split("\n");
+ } else {
+ result = [];
+ }
+
+ gitExec([ "ls-files", "*" + pattern + "*.md" ], function(err, data) {
+
+ if (data) {
+ data.toString().split("\n").forEach(function(name) {
+ result.push(name);
+ });
+ }
+
+ callback(err, result);
+ });
+ });
+ },
+
+ diff: function(path, revisions, callback) {
+ gitExec([ "diff", "--no-color", "-b", revisions, "--", path ], function(err, data) {
+ callback(err, data.toString());
+ });
+ },
+
+ ls: function(callback) {
+ gitExec([ "ls-tree", "--name-only", "-r", "HEAD" ], function(err, data) {
+ callback(null, data.toString().split("\n").filter(function(v) { return v!=""}));
+ });
+ }
+ };
+
+})();
49 lib/locker.js
@@ -0,0 +1,49 @@
+
+var locker = {
+
+ locks: {},
+
+ purgeTime: 3600 * 1000,
+
+ lock: function(page, user) {
+ var d = new Date();
+ this.locks[page] = {
+ user: user,
+ ts: d.getTime() + (d.getTimezoneOffset() * 60 * 1000)
+ }
+ },
+
+ unlock: function(page) {
+ delete this.locks[page];
+ },
+
+ getLock: function(page) {
+ return this.locks[page] ? this.locks[page] : null;
+ },
+
+ reset: function() {
+ this.locks = {};
+ },
+
+ count: function() {
+ return Object.keys(this.locks).length;
+ },
+
+ purge: function() {
+ var d = new Date()
+ , now = d.getTime() + (d.getTimezoneOffset() * 60 * 1000);
+
+ for (var page in this.locks) {
+ if ((now - this.locks[page].ts) > this.purgeTime) {
+ this.unlock(page);
+ }
+ }
+ }
+
+};
+
+setTimeout(function() {
+ locker.purge();
+}, 3600 * 1000);
+
+module.exports = locker;
20 lib/namer.js
@@ -0,0 +1,20 @@
+var Iconv = require("iconv").Iconv;
+
+var iconv = new Iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE');
+
+var normalize = function(str) {
+
+ if (!str) {
+ return "";
+ }
+
+ str = iconv.convert(str)
+ .toString()
+ .replace(/\s/g, '-')
+ .replace(/[^a-zA-Z0-9\- _]/g, "")
+ .toLowerCase();
+
+ return str;
+};
+
+exports.normalize = normalize;
34 lib/tools.js
@@ -0,0 +1,34 @@
+
+var tools = {
+
+ isAuthorized: function(email, pattern) {
+
+ if (!email || email.trim() == "") {
+ return false;
+ }
+
+ if (!pattern || pattern.trim() == "") {
+ return true;
+ }
+
+ if (!email.match(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b/i)) {
+ return false;
+ }
+
+ var tests = pattern.split(",").map(function(str) { return str.trim(); });
+ var expr;
+ for (var i=0; i < tests.length; i++) {
+ try {
+ expr = expr || !!email.match(new RegExp("^" + tests[i] + "$", "i"));
+ } catch(e) { // Invalid regular expression
+ return false;
+ }
+ }
+
+ return expr;
+
+ }
+
+};
+
+module.exports = tools;
36 package.json
@@ -0,0 +1,36 @@
+{
+ "name": "Jiky",
+ "version": "0.1.0",
+ "description": "A nodejs based wiki engine",
+ "author": "Claudio Cicali <claudio.cicali@gmail.com>",
+ "main": "jiky.js",
+ "bin": "./jiky.js",
+ "repository" :
+ { "type" : "git",
+ "url" : "https://github.com/claudioc/jiky"
+ },
+ "directories" : { "lib" : "./lib/" },
+ "dependencies": {
+ "express": "3.x",
+ "jade": "*",
+ "passport": "*",
+ "passport-google": "*",
+ "iconv": "*",
+ "marked": ">= 0.2.x",
+ "node-syntaxhighlighter": "*",
+ "gravatar": ">= 1.0.6",
+ "express-validator": ">= 0.3.0",
+ "commander": "*",
+ "yaml": "*",
+ "chai": "*"
+ },
+ "engines": {
+ "node": "0.8.x",
+ "npm": "1.1.x"
+ },
+ "licenses":
+ [ { "type" : "MIT"
+ , "url" : "http://github.com/claudioc/jiky/raw/master/LICENSE"
+ }
+ ]
+}
345 public/css/shCoreDefault.css
@@ -0,0 +1,345 @@
+.syntaxhighlighter a,
+.syntaxhighlighter div,
+.syntaxhighlighter code,
+.syntaxhighlighter table,
+.syntaxhighlighter table td,
+.syntaxhighlighter table tr,
+.syntaxhighlighter table tbody,
+.syntaxhighlighter table thead,
+.syntaxhighlighter table caption,
+.syntaxhighlighter textarea {
+ -moz-border-radius: 0 0 0 0 !important;
+ -webkit-border-radius: 0 0 0 0 !important;
+ background: none !important;
+ border: 0 !important;
+ bottom: auto !important;
+ float: none !important;
+ height: auto !important;
+ left: auto !important;
+ line-height: 1.1em !important;
+ margin: 0 !important;
+ outline: 0 !important;
+ overflow: visible !important;
+ position: static !important;
+ right: auto !important;
+ text-align: left !important;
+ top: auto !important;
+ vertical-align: baseline !important;
+ width: auto !important;
+ box-sizing: content-box !important;
+ font-family: Consolas, "Liberation Mono", Courier, monospace !important;
+
+ font-weight: normal !important;
+ font-style: normal !important;
+ font-size: 13px !important;
+ min-height: inherit !important;
+ min-height: auto !important;
+}
+
+.syntaxhighlighter {
+ width: 100% !important;
+ position: relative !important;
+ overflow: auto !important;
+ font-size: 1em !important;
+ border: 1px solid #CCC;
+ background-color: #f8f8f8;
+ border-radius: 3px;
+ font-size: 13px;
+ line-height: 19px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+/* Override Bootstrap */
+
+.syntaxhighlighter .container:after {
+ clear: inherit;
+}
+
+.syntaxhighlighter .container:before, .container:after {
+ content: "";
+ display: inherit;
+ line-height: inherit;
+}
+
+.syntaxhighlighter.source {
+ overflow: hidden !important;
+}
+.syntaxhighlighter .bold {
+ font-weight: bold !important;
+}
+.syntaxhighlighter .italic {
+ font-style: italic !important;
+}
+.syntaxhighlighter .line {
+ white-space: pre !important;
+}
+.syntaxhighlighter table {
+ width: 100% !important;
+}
+
+.syntaxhighlighter td {
+ padding: 10px;
+}
+
+.syntaxhighlighter table caption {
+ text-align: left !important;
+ padding: .5em 0 0.5em 1em !important;
+}
+.syntaxhighlighter table td.code {
+ width: 100% !important;
+}
+.syntaxhighlighter table td.code .container {
+ position: relative !important;
+}
+.syntaxhighlighter table td.code .container textarea {
+ box-sizing: border-box !important;
+ position: absolute !important;
+ left: 0 !important;
+ top: 0 !important;
+ width: 100% !important;
+ height: 100% !important;
+ border: none !important;
+ background: white !important;
+ padding-left: 1em !important;
+ overflow: hidden !important;
+ white-space: pre !important;
+}
+.syntaxhighlighter table td.gutter .line {
+ text-align: right !important;
+ padding: 0 0.5em 0 1em !important;
+}
+.syntaxhighlighter table td.gutter {
+ border-right: 1px solid #CCCCCC !important;
+ padding-right: 4px;
+}
+
+.syntaxhighlighter table td.gutter .line,
+.syntaxhighlighter table td.code .line {
+ padding: 0 .5em 5px .5em !important;
+}
+
+.syntaxhighlighter table td.gutter .line:last-child,
+.syntaxhighlighter table td.code .line:last-child {
+ padding-bottom: 0 !important;
+}
+
+.syntaxhighlighter table td.code {
+ padding-left: 5px;
+}
+
+.syntaxhighlighter.nogutter td.code .container textarea, .syntaxhighlighter.nogutter td.code .line {
+ padding-left: 0em !important;
+}
+.syntaxhighlighter.show {
+ display: block !important;
+}
+.syntaxhighlighter.collapsed table {
+ display: none !important;
+}
+.syntaxhighlighter.collapsed .toolbar {
+ padding: 0.1em 0.8em 0em 0.8em !important;
+ font-size: 1em !important;
+ position: static !important;
+ width: auto !important;
+ height: auto !important;
+}
+.syntaxhighlighter.collapsed .toolbar span {
+ display: inline !important;
+ margin-right: 1em !important;
+}
+.syntaxhighlighter.collapsed .toolbar span a {
+ padding: 0 !important;
+ display: none !important;
+}
+.syntaxhighlighter.collapsed .toolbar span a.expandSource {
+ display: inline !important;
+}
+.syntaxhighlighter .toolbar {
+ position: absolute !important;
+ right: 1px !important;
+ top: 1px !important;
+ width: 11px !important;
+ height: 11px !important;
+ font-size: 10px !important;
+ z-index: 10 !important;
+}
+.syntaxhighlighter .toolbar span.title {
+ display: inline !important;
+}
+.syntaxhighlighter .toolbar a {
+ display: block !important;
+ text-align: center !important;
+ text-decoration: none !important;
+ padding-top: 1px !important;
+}
+.syntaxhighlighter .toolbar a.expandSource {
+ display: none !important;
+}
+.syntaxhighlighter.ie {
+ font-size: .9em !important;
+ padding: 1px 0 1px 0 !important;
+}
+.syntaxhighlighter.ie .toolbar {
+ line-height: 8px !important;
+}
+.syntaxhighlighter.ie .toolbar a {
+ padding-top: 0px !important;
+}
+.syntaxhighlighter.printing .line.alt1 .content,
+.syntaxhighlighter.printing .line.alt2 .content,
+.syntaxhighlighter.printing .line.highlighted .number,
+.syntaxhighlighter.printing .line.highlighted.alt1 .content,
+.syntaxhighlighter.printing .line.highlighted.alt2 .content {
+ background: none !important;
+}
+.syntaxhighlighter.printing .line .number {
+ color: #bbbbbb !important;
+}
+.syntaxhighlighter.printing .line .content {
+ color: black !important;
+}
+.syntaxhighlighter.printing .toolbar {
+ display: none !important;
+}
+.syntaxhighlighter.printing a {
+ text-decoration: none !important;
+}
+.syntaxhighlighter.printing .plain, .syntaxhighlighter.printing .plain a {
+ color: black !important;
+}
+.syntaxhighlighter.printing .comments, .syntaxhighlighter.printing .comments a {
+ color: #008200 !important;
+}
+.syntaxhighlighter.printing .string, .syntaxhighlighter.printing .string a {
+ color: blue !important;
+}
+.syntaxhighlighter.printing .keyword {
+ color: #006699 !important;
+ font-weight: bold !important;
+}
+.syntaxhighlighter.printing .preprocessor {
+ color: gray !important;
+}
+.syntaxhighlighter.printing .variable {
+ color: #aa7700 !important;
+}
+.syntaxhighlighter.printing .value {
+ color: #009900 !important;
+}
+.syntaxhighlighter.printing .functions {
+ color: #ff1493 !important;
+}
+.syntaxhighlighter.printing .constants {
+ color: #0066cc !important;
+}
+.syntaxhighlighter.printing .script {
+ font-weight: bold !important;
+}
+.syntaxhighlighter.printing .color1, .syntaxhighlighter.printing .color1 a {
+ color: gray !important;
+}
+.syntaxhighlighter.printing .color2, .syntaxhighlighter.printing .color2 a {
+ color: #ff1493 !important;
+}
+.syntaxhighlighter.printing .color3, .syntaxhighlighter.printing .color3 a {
+ color: red !important;
+}
+.syntaxhighlighter.printing .break, .syntaxhighlighter.printing .break a {
+ color: black !important;
+}
+
+.syntaxhighlighter .line.alt1,
+.syntaxhighlighter .line.alt2 {
+ background-color: #f8f8f8 !important;
+}
+.syntaxhighlighter .line.highlighted.alt1, .syntaxhighlighter .line.highlighted.alt2 {
+ background-color: #e0e0e0 !important;
+}
+.syntaxhighlighter .line.highlighted.number {
+ color: black !important;
+}
+.syntaxhighlighter table caption {
+ color: black !important;
+}
+.syntaxhighlighter .gutter {
+ color: #bfbfbf !important;
+}
+.syntaxhighlighter .gutter .line.highlighted {
+ background-color: #6ce26c !important;
+ color: white !important;
+}
+.syntaxhighlighter.printing .line .content {
+ border: none !important;
+}
+.syntaxhighlighter.collapsed {
+ overflow: visible !important;
+}
+.syntaxhighlighter.collapsed .toolbar {
+ color: blue !important;
+ background: white !important;
+ border: 1px solid #6ce26c !important;
+}
+.syntaxhighlighter.collapsed .toolbar a {
+ color: blue !important;
+}
+.syntaxhighlighter.collapsed .toolbar a:hover {
+ color: red !important;
+}
+.syntaxhighlighter .toolbar {
+ color: white !important;
+ background: #6ce26c !important;
+ border: none !important;
+}
+.syntaxhighlighter .toolbar a {
+ color: white !important;
+}
+.syntaxhighlighter .toolbar a:hover {
+ color: black !important;
+}
+.syntaxhighlighter .plain, .syntaxhighlighter .plain a {
+ color: black !important;
+}
+.syntaxhighlighter .comments, .syntaxhighlighter .comments a {
+ color: #008200 !important;
+}
+.syntaxhighlighter .string, .syntaxhighlighter .string a {
+ color: blue !important;
+}
+.syntaxhighlighter .keyword {
+ color: #006699 !important;
+}
+.syntaxhighlighter .preprocessor {
+ color: gray !important;
+}
+.syntaxhighlighter .variable {
+ color: #aa7700 !important;
+}
+.syntaxhighlighter .value {
+ color: #009900 !important;
+}
+.syntaxhighlighter .functions {
+ color: #ff1493 !important;
+}
+.syntaxhighlighter .constants {
+ color: #0066cc !important;
+}
+.syntaxhighlighter .script {
+ font-weight: bold !important;
+ color: #006699 !important;
+ background-color: none !important;
+}
+.syntaxhighlighter .color1, .syntaxhighlighter .color1 a {
+ color: gray !important;
+}
+.syntaxhighlighter .color2, .syntaxhighlighter .color2 a {
+ color: #ff1493 !important;
+}
+.syntaxhighlighter .color3, .syntaxhighlighter .color3 a {
+ color: red !important;
+}
+
+.syntaxhighlighter .keyword {
+ font-weight: bold !important;
+}
150 public/css/style.css
@@ -0,0 +1,150 @@
+ body {
+ padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
+}
+
+p.user {
+ float: right;
+ color: white;
+ padding: 10px 0 0;
+}
+
+hr {
+ border-color: #e1e1e1;
+}
+
+dl.search-results dd {
+ margin-bottom: 15px;
+}
+
+.history td:first-child {
+ width: 10px;
+}
+
+.page-param {
+ color: #145AB0;
+}
+
+.compare {
+ font-family: Consolas, monospace;
+ font-size: 12px;
+}
+
+.compare td {
+ padding: 0 3px;
+ border-top-width: 0
+}
+
+.compare .gd {
+ background-color: #ffdddd;
+}
+
+.compare .gc {
+ color: #999999;
+ background-color: #EAF2F5;
+}
+
+.compare .gi {
+ background-color: #ddffdd;
+}
+
+.compare .ln {
+ color: #999999;
+ background-color: #EAF2F5;
+ text-align: right;
+ border-bottom-color: transparent;
+}
+
+/* Overrides Bootstrap */
+pre {
+ background-color: transparent;
+ border-width: 0;
+ padding: 0;
+}
+
+pre code {
+ white-space: nowrap;
+}
+
+code {
+ white-space: auto;
+}
+
+ul.doc-list {
+ margin: 0;
+ padding: 0;
+}
+
+ul.doc-list > li {
+ list-style-type: none;
+ padding: 10px 10px 0 10px;
+ background-color: #f8f8f8;
+ border: 1px solid #ccc;
+ margin-bottom: 10px;
+ border-radius: 3px;
+}
+
+.footer {
+ margin-top: 20px;
+ border-top: 1px dashed #d1d1d1;
+ color: #c1c1c1;
+ padding: 5px 0;
+ font-size: 90%;
+}
+
+.page-actions {
+ text-align: right;
+ margin: 0;
+ padding: 0;
+}
+
+.page-actions > li {
+ display: inline;
+ list-style-type: none;
+}
+
+body #syntax-reference,
+body #preview {
+ width: 50%; /* desired relative width */
+ left: 25%; /* (100%-width)/2 */
+ margin: auto auto auto auto; /* place center */
+}
+
+.markItUpEditor {
+ height: 500px !important;
+ font-family: Consolas, monospace !important;
+}
+
+input#pageTitle {
+ font-weight: bold;
+ font-size: 130%;
+}
+
+.cheatsheet-content .col {
+ float: left;
+ width: 30%;
+ margin-left: 10px;
+}
+
+.cheatsheet-content pre {
+ border: 1px solid #ddd;
+ background-color: #eee;
+ border-radius: 3px;
+ width: 100%;
+ padding: 5px;
+ font-size: 12px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.cheatsheet-content hr {
+ clear: both;
+}
+
+.search-results .nl {
+ color: #aaa;
+}
+
+a.unknown {
+ color: red;
+}
82 public/js/app.js
@@ -0,0 +1,82 @@
+
+!(function(window, undefined) {
+
+ var cheatsheetShown = false;
+
+ var Jiky = {
+
+ init: function() {
+
+ $('.confirm-delete-page').on("click", function(evt) {
+ return confirm("Do you really want to delete this page?");
+ });
+
+ var $hCol1 = $('.history td:first-child');
+
+ toggleCompareCheckboxes();
+ $hCol1.find('input').on('click', function() {
+ toggleCompareCheckboxes();
+ });
+
+ $("#rev-compare").on("click", function() {
+ if ($hCol1.find(":checked").length < 2) {
+ return false;
+ }
+ window.location.href = "/wiki/" + $(this).data('pagename') + "/compare/" + $hCol1.find(":checked").map(function() { return $(this).val(); }).toArray().join("..");
+ return false;
+ });
+
+ if (window.location.pathname.match(/^\/wiki\//)) {
+ var pages = $.map($("#content a[href]").filter(function(i, a) {
+ var href = $(a).attr("href");
+ return !(href[0] == '/' || href.match(/^(f|ht)tps?:/));
+ }), function(a) {
+ return a.getAttribute("href").split("#")[0];
+ });
+
+ $.getJSON("/misc/existence", {data: pages}, function(result) {
+ $.each(result.data, function(href, a) {
+ $("#content a[href=" + a + "]").addClass("unknown");
+ });
+ });
+ }
+
+ function toggleCompareCheckboxes() {
+ if ($hCol1.find(":checked").length == 2) {
+ $hCol1.find(":not(:checked)")
+ .hide();
+ $hCol1.parent("tr")
+ .css({"color": "silver"});
+ $hCol1.find(":checked")
+ .parents("tr")
+ .css({"color": "black"});
+ } else {
+ $hCol1.find('input')
+ .show()
+ .parents("tr")
+ .css({"color": "black"});
+ }
+ }
+
+ },
+
+ preview: function() {
+ $('#preview').modal("show");
+ $.post("/misc/preview", {data: $('#editor').val()}, function(data) {
+ $('#preview .modal-body').html(data).get(0).scrollTop = 0;
+ });
+ },
+
+ markdownSyntax: function() {
+ $('#syntax-reference').modal("show");
+ if (!cheatsheetShown) {
+ $('#syntax-reference .modal-body').load("/misc/syntax-reference");
+ cheatsheetShown = true;
+ }
+ }
+
+ }
+
+ window.Jiky = Jiky;
+
+})(this);
1,092 public/vendor/bootstrap/css/bootstrap-responsive.css
@@ -0,0 +1,1092 @@
+/*!
+ * Bootstrap Responsive v2.2.2
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+@-ms-viewport {
+ width: device-width;
+}
+
+.clearfix {
+ *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+.hide-text {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.hidden {
+ display: none;
+ visibility: hidden;
+}
+
+.visible-phone {
+ display: none !important;
+}
+
+.visible-tablet {
+ display: none !important;
+}
+
+.hidden-desktop {
+ display: none !important;
+}
+
+.visible-desktop {
+ display: inherit !important;
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+ .hidden-desktop {
+ display: inherit !important;
+ }
+ .visible-desktop {
+ display: none !important ;
+ }
+ .visible-tablet {
+ display: inherit !important;
+ }
+ .hidden-tablet {
+ display: none !important;
+ }
+}
+
+@media (max-width: 767px) {
+ .hidden-desktop {
+ display: inherit !important;
+ }
+ .visible-desktop {
+ display: none !important;
+ }
+ .visible-phone {
+ display: inherit !important;
+ }
+ .hidden-phone {
+ display: none !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .row {
+ margin-left: -30px;
+ *zoom: 1;
+ }
+ .row:before,
+ .row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row:after {
+ clear: both;
+ }
+ [class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 30px;
+ }
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container {
+ width: 1170px;
+ }
+ .span12 {
+ width: 1170px;
+ }
+ .span11 {
+ width: 1070px;
+ }
+ .span10 {
+ width: 970px;
+ }
+ .span9 {
+ width: 870px;
+ }
+ .span8 {
+ width: 770px;
+ }
+ .span7 {
+ width: 670px;
+ }
+ .span6 {
+ width: 570px;
+ }
+ .span5 {
+ width: 470px;
+ }
+ .span4 {
+ width: 370px;
+ }
+ .span3 {
+ width: 270px;
+ }
+ .span2 {
+ width: 170px;
+ }
+ .span1 {
+ width: 70px;
+ }
+ .offset12 {
+ margin-left: 1230px;
+ }
+ .offset11 {
+ margin-left: 1130px;
+ }
+ .offset10 {
+ margin-left: 1030px;
+ }
+ .offset9 {
+ margin-left: 930px;
+ }
+ .offset8 {
+ margin-left: 830px;
+ }
+ .offset7 {
+ margin-left: 730px;
+ }
+ .offset6 {
+ margin-left: 630px;
+ }
+ .offset5 {
+ margin-left: 530px;
+ }
+ .offset4 {
+ margin-left: 430px;
+ }
+ .offset3 {
+ margin-left: 330px;
+ }
+ .offset2 {
+ margin-left: 230px;
+ }
+ .offset1 {
+ margin-left: 130px;
+ }
+ .row-fluid {
+ width: 100%;
+ *zoom: 1;
+ }
+ .row-fluid:before,
+ .row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row-fluid:after {
+ clear: both;
+ }
+ .row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.564102564102564%;
+ *margin-left: 2.5109110747408616%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+ }
+ .row-fluid .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 2.564102564102564%;
+ }
+ .row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+ }
+ .row-fluid .span11 {
+ width: 91.45299145299145%;
+ *width: 91.39979996362975%;
+ }
+ .row-fluid .span10 {
+ width: 82.90598290598291%;
+ *width: 82.8527914166212%;
+ }
+ .row-fluid .span9 {
+ width: 74.35897435897436%;
+ *width: 74.30578286961266%;
+ }
+ .row-fluid .span8 {
+ width: 65.81196581196582%;
+ *width: 65.75877432260411%;
+ }
+ .row-fluid .span7 {
+ width: 57.26495726495726%;
+ *width: 57.21176577559556%;
+ }
+ .row-fluid .span6 {
+ width: 48.717948717948715%;
+ *width: 48.664757228587014%;
+ }
+ .row-fluid .span5 {
+ width: 40.17094017094017%;
+ *width: 40.11774868157847%;
+ }
+ .row-fluid .span4 {
+ width: 31.623931623931625%;
+ *width: 31.570740134569924%;
+ }
+ .row-fluid .span3 {
+ width: 23.076923076923077%;
+ *width: 23.023731587561375%;
+ }
+ .row-fluid .span2 {
+ width: 14.52991452991453%;
+ *width: 14.476723040552828%;
+ }
+ .row-fluid .span1 {
+ width: 5.982905982905983%;
+ *width: 5.929714493544281%;
+ }
+ .row-fluid .offset12 {
+ margin-left: 105.12820512820512%;
+ *margin-left: 105.02182214948171%;
+ }
+ .row-fluid .offset12:first-child {
+ margin-left: 102.56410256410257%;
+ *margin-left: 102.45771958537915%;
+ }
+ .row-fluid .offset11 {
+ margin-left: 96.58119658119658%;
+ *margin-left: 96.47481360247316%;
+ }
+ .row-fluid .offset11:first-child {
+ margin-left: 94.01709401709402%;
+ *margin-left: 93.91071103837061%;
+ }
+ .row-fluid .offset10 {
+ margin-left: 88.03418803418803%;
+ *margin-left: 87.92780505546462%;
+ }
+ .row-fluid .offset10:first-child {
+ margin-left: 85.47008547008548%;
+ *margin-left: 85.36370249136206%;
+ }
+ .row-fluid .offset9 {
+ margin-left: 79.48717948717949%;
+ *margin-left: 79.38079650845607%;
+ }
+ .row-fluid .offset9:first-child {
+ margin-left: 76.92307692307693%;
+ *margin-left: 76.81669394435352%;
+ }
+ .row-fluid .offset8 {
+ margin-left: 70.94017094017094%;
+ *margin-left: 70.83378796144753%;
+ }
+ .row-fluid .offset8:first-child {
+ margin-left: 68.37606837606839%;
+ *margin-left: 68.26968539734497%;
+ }
+ .row-fluid .offset7 {
+ margin-left: 62.393162393162385%;
+ *margin-left: 62.28677941443899%;
+ }
+ .row-fluid .offset7:first-child {
+ margin-left: 59.82905982905982%;
+ *margin-left: 59.72267685033642%;
+ }
+ .row-fluid .offset6 {
+ margin-left: 53.84615384615384%;
+ *margin-left: 53.739770867430444%;
+ }
+ .row-fluid .offset6:first-child {
+ margin-left: 51.28205128205128%;
+ *margin-left: 51.175668303327875%;
+ }
+ .row-fluid .offset5 {
+ margin-left: 45.299145299145295%;
+ *margin-left: 45.1927623204219%;
+ }
+ .row-fluid .offset5:first-child {
+ margin-left: 42.73504273504273%;
+ *margin-left: 42.62865975631933%;
+ }
+ .row-fluid .offset4 {
+ margin-left: 36.75213675213675%;
+ *margin-left: 36.645753773413354%;
+ }
+ .row-fluid .offset4:first-child {
+ margin-left: 34.18803418803419%;
+ *margin-left: 34.081651209310785%;
+ }
+ .row-fluid .offset3 {
+ margin-left: 28.205128205128204%;
+ *margin-left: 28.0987452264048%;
+ }
+ .row-fluid .offset3:first-child {
+ margin-left: 25.641025641025642%;
+ *margin-left: 25.53464266230224%;
+ }
+ .row-fluid .offset2 {
+ margin-left: 19.65811965811966%;
+ *margin-left: 19.551736679396257%;
+ }
+ .row-fluid .offset2:first-child {
+ margin-left: 17.094017094017094%;
+ *margin-left: 16.98763411529369%;
+ }
+ .row-fluid .offset1 {
+ margin-left: 11.11111111111111%;
+ *margin-left: 11.004728132387708%;
+ }
+ .row-fluid .offset1:first-child {
+ margin-left: 8.547008547008547%;
+ *margin-left: 8.440625568285142%;
+ }
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 30px;
+ }
+ input.span12,
+ textarea.span12,
+ .uneditable-input.span12 {
+ width: 1156px;
+ }
+ input.span11,
+ textarea.span11,
+ .uneditable-input.span11 {
+ width: 1056px;
+ }
+ input.span10,
+ textarea.span10,
+ .uneditable-input.span10 {
+ width: 956px;
+ }
+ input.span9,
+ textarea.span9,
+ .uneditable-input.span9 {
+ width: 856px;
+ }
+ input.span8,
+ textarea.span8,
+ .uneditable-input.span8 {
+ width: 756px;
+ }
+ input.span7,
+ textarea.span7,
+ .uneditable-input.span7 {
+ width: 656px;
+ }
+ input.span6,
+ textarea.span6,
+ .uneditable-input.span6 {
+ width: 556px;
+ }
+ input.span5,
+ textarea.span5,
+ .uneditable-input.span5 {
+ width: 456px;
+ }
+ input.span4,
+ textarea.span4,
+ .uneditable-input.span4 {
+ width: 356px;
+ }
+ input.span3,
+ textarea.span3,
+ .uneditable-input.span3 {
+ width: 256px;
+ }
+ input.span2,
+ textarea.span2,
+ .uneditable-input.span2 {
+ width: 156px;
+ }
+ input.span1,
+ textarea.span1,
+ .uneditable-input.span1 {
+ width: 56px;
+ }
+ .thumbnails {
+ margin-left: -30px;
+ }
+ .thumbnails > li {
+ margin-left: 30px;
+ }
+ .row-fluid .thumbnails {
+ margin-left: 0;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+ .row {
+ margin-left: -20px;
+ *zoom: 1;
+ }
+ .row:before,
+ .row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row:after {
+ clear: both;
+ }
+ [class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 20px;
+ }
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container {
+ width: 724px;
+ }
+ .span12 {
+ width: 724px;
+ }
+ .span11 {
+ width: 662px;
+ }
+ .span10 {
+ width: 600px;
+ }
+ .span9 {
+ width: 538px;
+ }
+ .span8 {
+ width: 476px;
+ }
+ .span7 {
+ width: 414px;
+ }
+ .span6 {
+ width: 352px;
+ }
+ .span5 {
+ width: 290px;
+ }
+ .span4 {
+ width: 228px;
+ }
+ .span3 {
+ width: 166px;
+ }
+ .span2 {
+ width: 104px;
+ }
+ .span1 {
+ width: 42px;
+ }
+ .offset12 {
+ margin-left: 764px;
+ }
+ .offset11 {
+ margin-left: 702px;
+ }
+ .offset10 {
+ margin-left: 640px;
+ }
+ .offset9 {
+ margin-left: 578px;
+ }
+ .offset8 {
+ margin-left: 516px;
+ }
+ .offset7 {
+ margin-left: 454px;
+ }
+ .offset6 {
+ margin-left: 392px;
+ }
+ .offset5 {
+ margin-left: 330px;
+ }
+ .offset4 {
+ margin-left: 268px;
+ }
+ .offset3 {
+ margin-left: 206px;
+ }
+ .offset2 {
+ margin-left: 144px;
+ }
+ .offset1 {
+ margin-left: 82px;
+ }
+ .row-fluid {
+ width: 100%;
+ *zoom: 1;
+ }
+ .row-fluid:before,
+ .row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row-fluid:after {
+ clear: both;
+ }
+ .row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.7624309392265194%;
+ *margin-left: 2.709239449864817%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+ }
+ .row-fluid .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 2.7624309392265194%;
+ }
+ .row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+ }
+ .row-fluid .span11 {
+ width: 91.43646408839778%;
+ *width: 91.38327259903608%;
+ }
+ .row-fluid .span10 {
+ width: 82.87292817679558%;
+ *width: 82.81973668743387%;
+ }
+ .row-fluid .span9 {
+ width: 74.30939226519337%;
+ *width: 74.25620077583166%;
+ }
+ .row-fluid .span8 {
+ width: 65.74585635359117%;
+ *width: 65.69266486422946%;
+ }
+ .row-fluid .span7 {
+ width: 57.18232044198895%;
+ *width: 57.12912895262725%;
+ }
+ .row-fluid .span6 {
+ width: 48.61878453038674%;
+ *width: 48.56559304102504%;
+ }
+ .row-fluid .span5 {
+ width: 40.05524861878453%;
+ *width: 40.00205712942283%;
+ }
+ .row-fluid .span4 {
+ width: 31.491712707182323%;
+ *width: 31.43852121782062%;
+ }
+ .row-fluid .span3 {
+ width: 22.92817679558011%;
+ *width: 22.87498530621841%;
+ }
+ .row-fluid .span2 {
+ width: 14.3646408839779%;
+ *width: 14.311449394616199%;
+ }
+ .row-fluid .span1 {
+ width: 5.801104972375691%;
+ *width: 5.747913483013988%;
+ }
+ .row-fluid .offset12 {
+ margin-left: 105.52486187845304%;
+ *margin-left: 105.41847889972962%;
+ }
+ .row-fluid .offset12:first-child {
+ margin-left: 102.76243093922652%;
+ *margin-left: 102.6560479605031%;
+ }
+ .row-fluid .offset11 {
+ margin-left: 96.96132596685082%;
+ *margin-left: 96.8549429881274%;
+ }
+ .row-fluid .offset11:first-child {
+ margin-left: 94.1988950276243%;
+ *margin-left: 94.09251204890089%;
+ }
+ .row-fluid .offset10 {
+ margin-left: 88.39779005524862%;
+ *margin-left: 88.2914070765252%;
+ }
+ .row-fluid .offset10:first-child {
+ margin-left: 85.6353591160221%;
+ *margin-left: 85.52897613729868%;
+ }
+ .row-fluid .offset9 {
+ margin-left: 79.8342541436464%;
+ *margin-left: 79.72787116492299%;
+ }
+ .row-fluid .offset9:first-child {
+ margin-left: 77.07182320441989%;
+ *margin-left: 76.96544022569647%;
+ }
+ .row-fluid .offset8 {
+ margin-left: 71.2707182320442%;
+ *margin-left: 71.16433525332079%;
+ }
+ .row-fluid .offset8:first-child {
+ margin-left: 68.50828729281768%;
+ *margin-left: 68.40190431409427%;
+ }
+ .row-fluid .offset7 {
+ margin-left: 62.70718232044199%;
+ *margin-left: 62.600799341718584%;
+ }
+ .row-fluid .offset7:first-child {
+ margin-left: 59.94475138121547%;
+ *margin-left: 59.838368402492065%;
+ }
+ .row-fluid .offset6 {
+ margin-left: 54.14364640883978%;
+ *margin-left: 54.037263430116376%;
+ }
+ .row-fluid .offset6:first-child {
+ margin-left: 51.38121546961326%;
+ *margin-left: 51.27483249088986%;
+ }
+ .row-fluid .offset5 {
+ margin-left: 45.58011049723757%;
+ *margin-left: 45.47372751851417%;
+ }
+ .row-fluid .offset5:first-child {
+ margin-left: 42.81767955801105%;
+ *margin-left: 42.71129657928765%;
+ }
+ .row-fluid .offset4 {
+ margin-left: 37.01657458563536%;
+ *margin-left: 36.91019160691196%;
+ }
+ .row-fluid .offset4:first-child {
+ margin-left: 34.25414364640884%;
+ *margin-left: 34.14776066768544%;
+ }
+ .row-fluid .offset3 {
+ margin-left: 28.45303867403315%;
+ *margin-left: 28.346655695309746%;
+ }
+ .row-fluid .offset3:first-child {
+ margin-left: 25.69060773480663%;
+ *margin-left: 25.584224756083227%;
+ }
+ .row-fluid .offset2 {
+ margin-left: 19.88950276243094%;
+ *margin-left: 19.783119783707537%;
+ }
+ .row-fluid .offset2:first-child {
+ margin-left: 17.12707182320442%;
+ *margin-left: 17.02068884448102%;
+ }
+ .row-fluid .offset1 {
+ margin-left: 11.32596685082873%;
+ *margin-left: 11.219583872105325%;
+ }
+ .row-fluid .offset1:first-child {
+ margin-left: 8.56353591160221%;
+ *margin-left: 8.457152932878806%;
+ }
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 20px;
+ }
+ input.span12,
+ textarea.span12,
+ .uneditable-input.span12 {
+ width: 710px;
+ }
+ input.span11,
+ textarea.span11,
+ .uneditable-input.span11 {
+ width: 648px;
+ }
+ input.span10,
+ textarea.span10,
+ .uneditable-input.span10 {
+ width: 586px;
+ }
+ input.span9,
+ textarea.span9,
+ .uneditable-input.span9 {
+ width: 524px;
+ }
+ input.span8,
+ textarea.span8,
+ .uneditable-input.span8 {
+ width: 462px;
+ }
+ input.span7,
+ textarea.span7,
+ .uneditable-input.span7 {
+ width: 400px;
+ }
+ input.span6,
+ textarea.span6,
+ .uneditable-input.span6 {
+ width: 338px;
+ }
+ input.span5,
+ textarea.span5,
+ .uneditable-input.span5 {
+ width: 276px;
+ }
+ input.span4,
+ textarea.span4,
+ .uneditable-input.span4 {
+ width: 214px;
+ }
+ input.span3,
+ textarea.span3,
+ .uneditable-input.span3 {
+ width: 152px;
+ }
+ input.span2,
+ textarea.span2,
+ .uneditable-input.span2 {
+ width: 90px;
+ }
+ input.span1,
+ textarea.span1,
+ .uneditable-input.span1 {
+ width: 28px;
+ }
+}
+
+@media (max-width: 767px) {
+ body {
+ padding-right: 20px;
+ padding-left: 20px;
+ }
+ .navbar-fixed-top,
+ .navbar-fixed-bottom,
+ .navbar-static-top {
+ margin-right: -20px;
+ margin-left: -20px;
+ }
+ .container-fluid {
+ padding: 0;
+ }
+ .dl-horizontal dt {
+ float: none;
+ width: auto;
+ clear: none;
+ text-align: left;
+ }
+ .dl-horizontal dd {
+ margin-left: 0;
+ }
+ .container {
+ width: auto;
+ }
+ .row-fluid {
+ width: 100%;
+ }
+ .row,
+ .thumbnails {
+ margin-left: 0;
+ }
+ .thumbnails > li {
+ float: none;
+ margin-left: 0;
+ }
+ [class*="span"],
+ .uneditable-input[class*="span"],
+ .row-fluid [class*="span"] {
+ display: block;
+ float: none;
+ width: 100%;
+ margin-left: 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .span12,
+ .row-fluid .span12 {
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="offset"]:first-child {
+ margin-left: 0;
+ }
+ .input-large,
+ .input-xlarge,
+ .input-xxlarge,
+ input[class*="span"],
+ select[class*="span"],
+ textarea[class*="span"],
+ .uneditable-input {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .input-prepend input,
+ .input-append input,
+ .input-prepend input[class*="span"],
+ .input-append input[class*="span"] {
+ display: inline-block;
+ width: auto;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 0;
+ }
+ .modal {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ left: 20px;
+ width: auto;
+ margin: 0;
+ }
+ .modal.fade {
+ top: -100px;
+ }
+ .modal.fade.in {
+ top: 20px;
+ }
+}
+
+@media (max-width: 480px) {
+ .nav-collapse {
+ -webkit-transform: translate3d(0, 0, 0);
+ }
+ .page-header h1 small {
+ display: block;
+ line-height: 20px;
+ }
+ input[type="checkbox"],
+ input[type="radio"] {
+ border: 1px solid #ccc;
+ }
+ .form-horizontal .control-label {
+ float: none;
+ width: auto;
+ padding-top: 0;
+ text-align: left;
+ }
+ .form-horizontal .controls {
+ margin-left: 0;
+ }
+ .form-horizontal .control-list {
+ padding-top: 0;
+ }
+ .form-horizontal .form-actions {
+ padding-right: 10px;
+ padding-left: 10px;
+ }
+ .media .pull-left,
+ .media .pull-right {
+ display: block;
+ float: none;
+ margin-bottom: 10px;
+ }
+ .media-object {
+ margin-right: 0;
+ margin-left: 0;
+ }
+ .modal {
+ top: 10px;
+ right: 10px;
+ left: 10px;
+ }
+ .modal-header .close {
+ padding: 10px;
+ margin: -10px;
+ }
+ .carousel-caption {
+ position: static;
+ }
+}
+
+@media (max-width: 979px) {
+ body {
+ padding-top: 0;
+ }
+ .navbar-fixed-top,
+ .navbar-fixed-bottom {
+ position: static;
+ }
+ .navbar-fixed-top {
+ margin-bottom: 20px;
+ }
+ .navbar-fixed-bottom {
+ margin-top: 20px;
+ }
+ .navbar-fixed-top .navbar-inner,
+ .navbar-fixed-bottom .navbar-inner {
+ padding: 5px;
+ }
+ .navbar .container {
+ width: auto;
+ padding: 0;
+ }
+ .navbar .brand {
+ padding-right: 10px;
+ padding-left: 10px;
+ margin: 0 0 0 -5px;
+ }
+ .nav-collapse {
+ clear: both;
+ }
+ .nav-collapse .nav {
+ float: none;
+ margin: 0 0 10px;
+ }
+ .nav-collapse .nav > li {
+ float: none;
+ }
+ .nav-collapse .nav > li > a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > .divider-vertical {
+ display: none;
+ }
+ .nav-collapse .nav .nav-header {
+ color: #777777;
+ text-shadow: none;
+ }
+ .nav-collapse .nav > li > a,
+ .nav-collapse .dropdown-menu a {
+ padding: 9px 15px;
+ font-weight: bold;
+ color: #777777;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ }
+ .nav-collapse .btn {
+ padding: 4px 10px 4px;
+ font-weight: normal;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ }
+ .nav-collapse .dropdown-menu li + li a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > li > a:hover,
+ .nav-collapse .dropdown-menu a:hover {
+ background-color: #f2f2f2;
+ }
+ .navbar-inverse .nav-collapse .nav > li > a,
+ .navbar-inverse .nav-collapse .dropdown-menu a {
+ color: #999999;
+ }
+ .navbar-inverse .nav-collapse .nav > li > a:hover,
+ .navbar-inverse .nav-collapse .dropdown-menu a:hover {
+ background-color: #111111;
+ }
+ .nav-collapse.in .btn-group {
+ padding: 0;
+ margin-top: 5px;
+ }
+ .nav-collapse .dropdown-menu {
+ position: static;
+ top: auto;
+ left: auto;
+ display: none;
+ float: none;
+ max-width: none;
+ padding: 0;