Просмотр исходного кода

Merge pull request #13 from crowi/wip-v1.1.1

v1.1.1
Sotaro KARASAWA 11 лет назад
Родитель
Сommit
466ebfd3ca

+ 6 - 0
CHANGES.md

@@ -1,6 +1,12 @@
 CHANGES
 ========
 
+## 1.1.1
+
+* Fix: Error on accessing restricted page.
+* Fix: JS Error when section title includes colon.
+* Fix: Typo on Facebook setting page (Facebook setting is now available).
+* Fix and Feature: Add creator property to Page Object and show creator info in sidebar instead of last update user.
 
 ## 1.1.0
 

+ 38 - 22
Gruntfile.js

@@ -32,11 +32,12 @@ module.exports = function(grunt) {
           outputStyle: 'nested',
           includePaths: [
             'bower_components/bootstrap-sass-official/assets/stylesheets',
-            'bower_components/fontawesome/scss'
+            'bower_components/fontawesome/scss',
+            'bower_components/reveal.js/css'
           ]
         },
         files: {
-          '<%= dirs.cssDest %>/<%= pkg.name %>.css': '<%= dirs.css %>/<%= pkg.name %>.scss',
+          '<%= dirs.cssDest %>/<%= pkg.name %>-main.css': '<%= dirs.css %>/<%= pkg.name %>.scss',
           '<%= dirs.cssDest %>/<%= pkg.name %>-reveal.css': '<%= dirs.css %>/<%= pkg.name %>-reveal.scss'
         }
       },
@@ -45,43 +46,58 @@ module.exports = function(grunt) {
           outputStyle: 'compressed',
           includePaths: [
             'bower_components/bootstrap-sass-official/assets/stylesheets',
-            'bower_components/fontawesome/scss'
+            'bower_components/fontawesome/scss',
+            'bower_components/reveal.js/css'
           ]
         },
         files: {
-          '<%= dirs.cssDest %>/<%= pkg.name %>.min.css': '<%= dirs.css %>/<%= pkg.name %>.scss',
+          '<%= dirs.cssDest %>/<%= pkg.name %>-main.min.css': '<%= dirs.css %>/<%= pkg.name %>.scss',
           '<%= dirs.cssDest %>/<%= pkg.name %>-reveal.min.css': '<%= dirs.css %>/<%= pkg.name %>-reveal.scss'
         }
       }
     },
     concat: {
       dist: {
-        src: [
-          // Bootstrap
-          'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap.js',
-          // socket.io
-          'node_modules/socket.io-client/dist/socket.io.js',
-          // markd
-          'node_modules/marked/lib/marked.js',
-          // jquery.cookie
-          'node_modules/jquery.cookie/jquery.cookie.js',
-          // crowi
-          'resource/js/crowi.js'
-        ],
-        dest: '<%= dirs.jsDest %>/<%= pkg.name %>.js'
-      }
+        files: {
+          '<%= dirs.cssDest %>/<%= pkg.name %>.css': [
+            'bower_components/highlightjs/styles/default.css',
+            '<%= dirs.cssDest %>/<%= pkg.name %>-main.css',
+          ],
+          '<%= dirs.cssDest %>/<%= pkg.name %>.min.css': [
+            'bower_components/highlightjs/styles/default.css', // TODO minimize
+            '<%= dirs.cssDest %>/<%= pkg.name %>-main.min.css',
+          ],
+          '<%= dirs.jsDest %>/<%= pkg.name %>.js': [
+            'bower_components/jquery/dist/jquery.js',
+            'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap.js',
+            'node_modules/socket.io-client/dist/socket.io.js',
+            'bower_components/marked/lib/marked.js',
+            'bower_components/jquery.cookie/jquery.cookie.js',
+            'bower_components/highlightjs/highlight.pack.js',
+            'resource/js/crowi.js'
+          ],
+          '<%= dirs.jsDest %>/<%= pkg.name %>-reveal.js': [
+            'bower_components/jquery/dist/jquery.js',
+            'bower_components/reveal.js/lib/js/head.min.js',
+            'bower_components/reveal.js/lib/js/html5shiv.js',
+            'bower_components/reveal.js/js/reveal.js'
+          ],
+        }
+      },
     },
     uglify: {
       build: {
-        src: '<%= concat.dist.dest %>',
-        dest: '<%= dirs.jsDest %>/<%= pkg.name %>.min.js'
+        files: {
+          '<%= dirs.jsDest %>/<%= pkg.name %>.min.js': '<%= dirs.jsDest %>/<%= pkg.name %>.js',
+          '<%= dirs.jsDest %>/<%= pkg.name %>-reveal.min.js': '<%= dirs.jsDest %>/<%= pkg.name %>-reveal.js'
+        }
       }
     },
     jshint: {
       options: {
         jshintrc: true
       },
-      all: ['Gruntfile.js', 'lib/**/*.js', 'models/**/*.js', 'routes/**/*.js', 'form/**/*.js', 'resource/js/**/*.js']
+      all: paths.scripts
     },
     watch: {
       css: {
@@ -109,6 +125,6 @@ module.exports = function(grunt) {
 
   // grunt watch dev
   grunt.registerTask('default', ['sass', 'concat', 'uglify']);
-  grunt.registerTask('dev', ['jshint', 'sass:dev', 'concat']);
+  grunt.registerTask('dev', ['sass:dev', 'concat', 'jshint']);
 
 };

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Sotaro KARASAWA <sotaro.k@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.

+ 3 - 22
README.md

@@ -40,7 +40,7 @@ Start Up on Local
 Crowi is designed setting up to Heroku or some PaaS, but you can start up Crowi with ENV parameter on your local.
 
 ```
-$ PASSWORD_SEED=somesecretstring MONGOHQ_URL=mongodb://username:password@localhost/crowi node app.js
+$ PASSWORD_SEED=somesecretstring MONGO_URI=mongodb://username:password@localhost/crowi node app.js
 ```
 
 ### Environment
@@ -57,24 +57,5 @@ $ PASSWORD_SEED=somesecretstring MONGOHQ_URL=mongodb://username:password@localho
 License
 ---------
 
-> The MIT License (MIT)
->
-> Copyright (c) 2013 Sotaro KARASAWA <sotaro.k@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.
+* The MIT License (MIT)
+* See LICENSE file.

+ 0 - 22
TODO

@@ -1,22 +0,0 @@
-TODO
-=====
-
-* ~~User create~~
-* ~~Page edit~~
-* ~~List view~~
-* ~~Page title as a separated link~~
-* ~~Page fromatter~~
-    * ~~Text (auto link, auto image expand)~~
-* ~~Timeline list view~~
-* Page move
-* Page copy
-* Page fromatter
-    * ~~Hatena~~
-    * ~~Markdown~~
-* Search
-* Pagination
-* Realtime preview on page edit
-* Page revert (from history)
-* History diff view
-* Ajax edit
-* Live edit

+ 19 - 14
app.js

@@ -19,6 +19,7 @@ var express  = require('express')
   , session  = require('express-session')
   , models
   , config
+  , configModel
   , server
   , sessionConfig
   , RedisStore
@@ -31,10 +32,11 @@ var env = app.get('env');
 var days = (1000*3600*24*30);
 
 // mongoUri = mongodb://user:password@host/dbname
-var mongoUri = process.env.MONGOLAB_URI
-  || process.env.MONGOHQ_URL
-  || process.env.MONGO_URI
-  || 'mongodb://localhost/crowi';
+var mongoUri = process.env.MONGOLAB_URI ||
+  process.env.MONGOHQ_URL ||
+  process.env.MONGO_URI ||
+  'mongodb://localhost/crowi'
+  ;
 
 mongo.connect(mongoUri);
 
@@ -47,16 +49,16 @@ sessionConfig = {
     maxAge: days,
   },
 };
-var redisUrl = process.env.REDISTOGO_URL
-  || process.env.REDIS_URL
-  || null;
+var redisUrl = process.env.REDISTOGO_URL ||
+  process.env.REDIS_URL ||
+  null;
 
 if (redisUrl) {
-  var ru   = require("url").parse(redisUrl);
-  var redis = require("redis");
+  var ru   = require('url').parse(redisUrl);
+  var redis = require('redis');
   var redisClient = redis.createClient(ru.port, ru.hostname);
   if (ru.auth) {
-    redisClient.auth(ru.auth.split(":")[1]);
+    redisClient.auth(ru.auth.split(':')[1]);
   }
 
   RedisStore = require('connect-redis')(session);
@@ -89,7 +91,10 @@ async.series([
       return next();
     });
   }, function (next) {
-    var config = app.set('config');
+    var config = app.set('config')
+      , tzoffset
+      ;
+
 
     app.set('mailer', require('./lib/mailer')(app));
 
@@ -107,7 +112,7 @@ async.series([
 
       req.config = config;
 
-      config.crowi['app:url'] = req.baseUrl = (req.headers['x-forwarded-proto'] == 'https' ? 'https' : req.protocol) + "://" + req.get('host');
+      config.crowi['app:url'] = req.baseUrl = (req.headers['x-forwarded-proto'] == 'https' ? 'https' : req.protocol) + '://' + req.get('host');
       res.locals({
         req: req,
         baseUrl: req.baseUrl,
@@ -164,7 +169,7 @@ async.series([
       app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
 
       server = http.createServer(app).listen(app.get('port'), function(){
-        console.log("[" + app.get('env') + "] Express server listening on port " + app.get('port'));
+        console.log('[' + app.get('env') + '] Express server listening on port ' + app.get('port'));
       });
     }
 
@@ -176,7 +181,7 @@ async.series([
       });
 
       server = http.createServer(app).listen(app.get('port'), function(){
-        console.log("[" + app.get('env') + "] Express server listening on port " + app.get('port'));
+        console.log('[' + app.get('env') + '] Express server listening on port ' + app.get('port'));
       });
     }
 

+ 7 - 2
bower.json

@@ -1,6 +1,6 @@
 {
   "name": "crowi",
-  "version": "1.1.0",
+  "version": "1.1.1",
   "description": "Crocos' Wiki implementation in node.js",
   "authors": [
     "Sotaro KARASAWA <sotarok@crocos.co.jp>",
@@ -20,6 +20,11 @@
   ],
   "dependencies": {
     "bootstrap-sass-official": "~3.3.1",
-    "fontawesome": "~4.2.0"
+    "fontawesome": "~4.2.0",
+    "jquery.cookie": "~1.4.1",
+    "marked": "~0.3.3",
+    "reveal.js": "~3.0.0",
+    "jquery": "~2.1.3",
+    "highlightjs": "~8.4.0"
   }
 }

+ 2 - 2
form/admin/fb.js

@@ -4,7 +4,7 @@ var form = require('express-form')
   , field = form.field;
 
 module.exports = form(
-  field('settingForm[fb:appId]').trim().is(/^\d+$/),
-  field('settingForm[fb:seret]').trim().is(/^[\da-z]+$/)
+  field('settingForm[facebook:appId]').trim().is(/^\d+$/),
+  field('settingForm[facebook:secret]').trim().is(/^[\da-z]+$/)
 );
 

+ 3 - 7
lib/mailer.js

@@ -52,7 +52,7 @@ module.exports = function(app) {
     }
 
     var ses = require('nodemailer-ses-transport');
-    var client = nodemailer.createTransport(ses(option));
+    client = nodemailer.createTransport(ses(option));
 
     debug('mailer setted up for SES', client);
     return client;
@@ -64,16 +64,12 @@ module.exports = function(app) {
       return;
     }
 
-    if (config.crowi['mail:smtpUser']
-        && config.crowi['mail:smtpPassword']
-        && config.crowi['mail:smtpHost']
-        && config.crowi['mail:smtpPort']
+    if (config.crowi['mail:smtpUser'] && config.crowi['mail:smtpPassword'] && config.crowi['mail:smtpHost'] && config.crowi['mail:smtpPort']
       ) {
       // SMTP 設定がある場合はそれを優先
       mailer = createSMTPClient();
 
-    } else if (config.crowi['aws:accessKeyId']
-      && config.crowi['aws:secretAccessKey']) {
+    } else if (config.crowi['aws:accessKeyId'] && config.crowi['aws:secretAccessKey']) {
       // AWS 設定がある場合はSESを設定
       mailer = createSESClient();
     } else {

+ 7 - 10
lib/middlewares.js

@@ -1,5 +1,4 @@
-var debug = require('debug')('crowi:lib:middlewares')
-  ;
+var debug = require('debug')('crowi:lib:middlewares');
 
 exports.loginChecker = function(app, models) {
   return function(req, res, next) {
@@ -7,7 +6,7 @@ exports.loginChecker = function(app, models) {
     if (req.session.user && '_id' in req.session.user) {
       models.User.findById(req.session.user._id, function(err, userData) {
         if (err) {
-          next()
+          next();
         } else {
           req.user = req.session.user = userData;
           res.locals({user: req.user});
@@ -38,13 +37,15 @@ exports.swigFilters = function(app, swig) {
 
     swig.setFilter('datetz', function(input, format) {
       // timezone
-      var swigFilters = require('swig/lib/filters')
+      var swigFilters = require('swig/lib/filters');
       return swigFilters.date(input, format, app.get('tzoffset'));
     });
 
     swig.setFilter('presentation', function(string) {
       // 手抜き
-      return string.replace(/[\n]+#/g, '\n\n\n#');
+      return string
+        .replace(/[\n]+#/g, '\n\n\n#')
+        .replace(/\s(https?.+(jpe?g|png|gif))\s/, '\n\n\n![]($1)\n\n\n');
     });
 
     swig.setFilter('picture', function(user) {
@@ -130,11 +131,7 @@ exports.applicationInstalled = function() {
 exports.awsEnabled = function() {
   return function (req, res, next) {
     var config = req.config;
-    if (config.crowi['aws:region'] != ''
-        && config.crowi['aws:bucket'] != ''
-        && config.crowi['aws:accessKeyId'] != ''
-        && config.crowi['aws:secretAccessKey'] != ''
-       ) {
+    if (config.crowi['aws:region'] !== '' && config.crowi['aws:bucket'] !== '' && config.crowi['aws:accessKeyId'] !== '' && config.crowi['aws:secretAccessKey'] !== '') {
       req.flash('globalError', 'AWS settings required to use this function. Please ask the administrator.');
       return res.redirect('/');
     }

+ 1 - 0
models/bookmark.js

@@ -4,6 +4,7 @@ module.exports = function(app, models) {
     , ObjectId = mongoose.Schema.Types.ObjectId
     , bookmarkSchema;
 
+
   bookmarkSchema = new mongoose.Schema({
     page: { type: ObjectId, ref: 'Page', index: true },
     user: { type: ObjectId, ref: 'User', index: true },

+ 23 - 8
models/page.js

@@ -10,20 +10,21 @@ module.exports = function(app, models) {
     , pageSchema;
 
   function populatePageData(pageData, revisionId, callback) {
-    debug('pageData', pageData.revision);
     if (revisionId) {
       pageData.revision = revisionId;
     }
 
     pageData.latestRevision = pageData.revision;
+    pageData.likerCount = pageData.liker.length || 0;
+    pageData.seenUsersCount = pageData.seenUsers.length || 0;
+
     pageData.populate([
+      {path: 'creator', model: 'User'},
       {path: 'revision', model: 'Revision'},
       {path: 'liker', options: { limit: 11 }},
       {path: 'seenUsers', options: { limit: 11 }},
     ], function (err, pageData) {
-      models.Page.populate(pageData, {path: 'revision.author', model: 'User'}, function(err, pageData) {
-        return callback(err, pageData);
-      });
+      models.Page.populate(pageData, {path: 'revision.author', model: 'User'}, callback);
     });
   }
 
@@ -33,6 +34,7 @@ module.exports = function(app, models) {
     redirectTo: { type: String, index: true },
     grant: { type: Number, default: GRANT_PUBLIC, index: true },
     grantedUsers: [{ type: ObjectId, ref: 'User' }],
+    creator: { type: ObjectId, ref: 'User', index: true },
     liker: [{ type: ObjectId, ref: 'User', index: true }],
     seenUsers: [{ type: ObjectId, ref: 'User', index: true }],
     createdAt: { type: Date, default: Date.now },
@@ -221,19 +223,31 @@ module.exports = function(app, models) {
       });
   };
 
-  pageSchema.statics.findPageById = function(id, userData, cb) {
+  pageSchema.statics.findPageById = function(id, cb) {
     var Page = this;
 
-    this.findOne({_id: id}, function(err, pageData) {
+    Page.findOne({_id: id}, function(err, pageData) {
       if (pageData === null) {
         return cb(new Error('Page Not Found'), null);
       }
 
-      if (!pageData.isGrantedFor(userData)) {
+      return populatePageData(pageData, null, cb);
+    });
+  };
+
+  pageSchema.statics.findPageByIdAndGrantedUser = function(id, userData, cb) {
+    var Page = this;
+
+    Page.findPageById(id, function(err, pageData) {
+      if (pageData === null) {
+        return cb(new Error('Page Not Found'), null);
+      }
+
+      if (userData && !pageData.isGrantedFor(userData)) {
         return cb(PAGE_GRANT_ERROR, null);
       }
 
-      return populatePageData(pageData, null, cb);
+      return cb(null,pageData);
     });
   };
 
@@ -353,6 +367,7 @@ module.exports = function(app, models) {
 
       var newPage = new Page();
       newPage.path = path;
+      newPage.creator = user;
       newPage.createdAt = Date.now();
       newPage.updatedAt = Date.now();
       newPage.redirectTo = redirectTo;

+ 12 - 12
package.json

@@ -1,9 +1,12 @@
 {
   "name": "crowi",
-  "version": "1.1.0",
+  "version": "1.1.1",
   "description": "The simple & powerful Wiki",
   "tags": [
-    "wiki", "communication", "documentation", "collaboration"
+    "wiki",
+    "communication",
+    "documentation",
+    "collaboration"
   ],
   "author": "Sotaro KARASAWA <sotaro.k@gmail.com>",
   "contributors": [
@@ -15,12 +18,12 @@
   },
   "engines": {
     "node": "0.10.x",
-    "npm": "1.3.x"
+    "npm": "2.4.x"
   },
   "dependencies": {
     "async": "~0.9.0",
     "aws-sdk": "~2.0.0-rc.19",
-    "bower": "~1.3.9",
+    "bower": "~1.3.12",
     "cli": "~0.6.4",
     "connect-flash": "~0.1.1",
     "connect-redis": "^2.1.0",
@@ -28,28 +31,25 @@
     "debug": "^1.0.3",
     "express": "=3.4.4",
     "express-form": "~0.10.1",
+    "express-session": "~1.9.3",
     "facebook-node-sdk": "=0.1.10",
     "googleapis": "=0.4.7",
     "grunt": "~0.4.1",
     "grunt-cli": "~0.1.13",
-    "grunt-contrib-concat": "~0.3.0",
+    "grunt-contrib-concat": "~0.5.0",
     "grunt-contrib-jshint": "^0.10.0",
     "grunt-contrib-uglify": "~0.2.2",
     "grunt-contrib-watch": "~0.5.3",
     "grunt-sass": "~0.14.1",
-    "jquery.cookie": "~1.4.1",
-    "marked": "=0.2.9",
     "mongoose": "=3.8.14",
     "mongoose-paginate": "~3.1.0",
     "nodemailer": "~1.2.2",
     "nodemailer-ses-transport": "~1.1.0",
-    "reveal.js": "~2.6.2",
+    "redis": "~0.12.1",
     "socket.io": "~0.9.16",
     "socket.io-client": "~0.9.16",
     "swig": "=1.3.2",
-    "time": "=0.10.0",
-    "redis": "~0.12.1",
-    "express-session": "~1.9.3"
+    "time": "=0.10.0"
   },
   "devDependencies": {},
   "license": [
@@ -60,7 +60,7 @@
   ],
   "scripts": {
     "start": "node app.js",
-    "postinstall": "./node_modules/bower/bin/bower cache clean && ./node_modules/bower/bin/bower install && ./node_modules/grunt-cli/bin/grunt"
+    "postinstall": "./node_modules/.bin/bower cache clean && ./node_modules/.bin/bower install && ./node_modules/.bin/grunt"
   },
   "env": {
     "NODE_ENV": "production"

+ 1 - 0
public/bower_components

@@ -0,0 +1 @@
+../bower_components

+ 0 - 3
public/hoge.css

@@ -1,3 +0,0 @@
-body {
-  font-family: Helvetica;
-}

+ 18 - 0
resource/css/_layout.scss

@@ -361,6 +361,24 @@
       margin-top: 30px;
     }
   }
+
+  .content-main .timeline-body { // {{{ timeline
+     .revision-path {
+       margin-top: 1.6em;
+       margin-bottom: 0;
+       border: solid 2px #ddd;
+       border-bottom: none;
+       padding: 16px;
+       background: #ddd;
+     }
+     .revision-body {
+       font-size: 14px;
+       border: solid 2px #ddd;
+       padding: 16px;
+       background: #fdfdfd;
+     }
+  } // }}}
+
   // on-edit
   .content-main.on-edit {
     position: fixed;

+ 1 - 0
resource/css/_wiki.scss

@@ -111,6 +111,7 @@ div.body {
     margin: 5px;
     box-shadow: 0 0 12px 0px #999;
     border: solid 1px #999;
+    max-width: 100%;
   }
 
   ul, ol {

+ 26 - 18
resource/css/crowi-reveal.scss

@@ -1,4 +1,5 @@
-//@import "../../node_modules/reveal.js/css/theme/template/theme";
+@import 'reveal';
+@import 'theme/source/black';
 
 .reveal {
   font-size: 32px;
@@ -6,38 +7,44 @@
     font-family: "Lucida Grande", "Hiragino Kaku Gothic Pro W3", Meiryo, san-serif;
   }
 
-  section {
-    text-align: left;
+  .slides > section {
+    //text-align: left;
+    padding: 0;
 
     &.only.present {
-      top: -25%;
       h1, h2, h3, h4, h5, h6 {
         font-size: 2.5em;
       }
     }
 
-    h1, h2, h3, h4, h5, h6 {
-      margin-bottom: 1em;
-      font-weight: bold;
-      line-height: 1.2em;
-      text-transform: none;
-      text-align: left;
-      text-shadow: none;
-    }
+    p {
+      line-height: 1.6;
 
-    p, ul li, ol li {
-      line-height: 1.3em;
+      &:first-child {
+        margin-top: 0;
+      }
     }
 
-    p {
-      margin-top: .5em;
+    pre {
+      code {
+        padding: 20px 40px;
+      }
+    }
+    blockquote {
+      width: 80%;
+      padding: 20px 60px;
     }
 
     ul {
       margin-top: .2em;
       margin-bottom: .1em;
-      li {
-        margin-bottom: .2em;
+      > li {
+        line-height: 1.6;
+        margin-bottom: .5em;
+
+        > ul > li {
+          font-size: .85em;
+        }
       }
     }
 
@@ -116,6 +123,7 @@
         }
       }
     }
+    // }}}
 
   }
 }

+ 2 - 0
resource/css/crowi.scss

@@ -6,6 +6,8 @@
 
 @import 'font-awesome';
 
+@import 'mixins';
+
 // crowi component
 @import 'mixins';
 @import 'layout';

+ 30 - 50
resource/js/crowi.js

@@ -40,7 +40,7 @@ Crowi.correctHeaders = function(contentId) {
   var $content = $(contentId || '#revision-body-content');
   var i = 0;
   $('h1,h2,h3,h4,h5,h6', $content).each(function(idx, elm) {
-    var id = 'head' + i++ + '-' + $(this).text().replace(/\/|\(|\)|\s|\?|\!|\.|\+|\*|\-|\=|\#|\~|\&|\^/g, '');
+    var id = 'head' + i++;
     $(this).attr('id', id);
     $(this).addClass('revision-head');
     $(this).append('<span class="revision-head-link"><a href="#' + id +'"><i class="fa fa-link"></i></a></span>');
@@ -95,60 +95,41 @@ Crowi.revisionToc = function(contentId, tocId) {
 
 
 Crowi.escape = function(s) {
-  s = s.replace(/&/g, '&amp;');
-  s = s.replace(/</g, '&lt;');
-  s = s.replace(/>/g, '&gt;');
-  s = s.replace(/"/g, '&quot;');
+  s = s.replace(/&/g, '&amp;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/"/g, '&quot;')
+    ;
   return s;
 };
 Crowi.unescape = function(s) {
-  s = s.replace(/&nbsp;/g, ' ');
-  s = s.replace(/&amp;/g, '&');
-  s = s.replace(/&lt;(?!\?)/g, '<');
-  s = s.replace(/([^\?])&gt;/g, '$1>');
-  s = s.replace(/&quot;/g, '"');
+  s = s.replace(/&nbsp;/g, ' ')
+    .replace(/&amp;/g, '&')
+    .replace(/&lt;/g, '<')
+    .replace(/&gt;/g, '>')
+    .replace(/&quot;/g, '"')
+    ;
   return s;
 };
 
-Crowi.getRendererType = function(format) {
-  if (!Crowi.rendererType[format]) {
-    throw new Error('no such renderer');
-  }
-
-  return new Crowi.rendererType[format]();
+Crowi.getRendererType = function() {
+  return new Crowi.rendererType.markdown();
 };
 
 Crowi.rendererType = {};
-Crowi.rendererType.text = function(){};
 Crowi.rendererType.markdown = function(){};
-Crowi.rendererType.text.prototype = {
-  render: function($content) {
-    var $revisionHtml = this.$revisionBody.children('pre');
-    this.$content = $content;
-    $revisionHtml.html(this.$content.html());
-    this.expandImage();
-    this.link();
-  },
-  link: function () {
-    this.$revisionBody.html(this.$revisionBody.html().replace(/\s(https?:\/\/[\S]+)/g, ' <a href="$1">$1</a>'));
-  },
-  expandImage: function () {
-    this.$revisionBody.html(this.$revisionBody.html().replace(/\s(https?:\/\/[\S]+\.(jpg|jpeg|gif|png))/g, ' <img src="$1" class="auto-expanded-image" />'));
-  }
-};
 Crowi.rendererType.markdown.prototype = {
-  render: function($content) {
+  render: function(contentText) {
     marked.setOptions({
       gfm: true,
       highlight: function (code, lang, callback) {
-        callback(null, code);
-        // あとで
-        //highlight: function (code, lang, callback) {
-        //  pygmentize({ lang: lang, format: 'html' }, code, function (err, result) {
-        //    if (err) return callback(err);
-        //    callback(null, result.toString());
-        //  });
-        //},
+        var result;
+        if (lang) {
+          result = hljs.highlight(lang, code);
+        } else {
+          result = hljs.highlightAuto(code);
+        }
+        callback(null, result.value);
       },
       tables: true,
       breaks: true,
@@ -159,7 +140,7 @@ Crowi.rendererType.markdown.prototype = {
       langPrefix: 'lang-'
     });
 
-    var contentHtml = Crowi.unescape(Crowi.escape($content.val()) || $content.html());
+    var contentHtml = Crowi.unescape(contentText);
     contentHtml = this.expandImage(contentHtml);
     contentHtml = this.link(contentHtml);
 
@@ -170,7 +151,6 @@ Crowi.rendererType.markdown.prototype = {
         throw err;
       }
       $body.html(content);
-      //console.log(content);
     });
   },
   link: function (content) {
@@ -184,18 +164,18 @@ Crowi.rendererType.markdown.prototype = {
   }
 };
 
-Crowi.renderer = function (contentId, format, revisionBody) {
-  var $revisionBody = revisionBody || '#revision-body-content';
+Crowi.renderer = function (contentText, revisionBody) {
+  var $revisionBody = revisionBody || $('#revision-body-content');
 
-  this.$content = $(contentId);
-  this.$revisionBody = $($revisionBody);
-  this.format = format;
-  this.renderer = Crowi.getRendererType(format);
+  this.contentText = contentText;
+  this.$revisionBody = $revisionBody;
+  this.format = 'markdown'; // とりあえず
+  this.renderer = Crowi.getRendererType();
   this.renderer.$revisionBody = this.$revisionBody;
 };
 Crowi.renderer.prototype = {
   render: function() {
-    this.renderer.render(this.$content);
+    this.renderer.render(this.contentText);
   }
 };
 

+ 2 - 2
routes/admin.js

@@ -83,7 +83,7 @@ module.exports = function(app) {
 
   actions.user = {};
   actions.user.index = function(req, res) {
-    var page = parseInt(req.query.page) || 0;
+    var page = parseInt(req.query.page) || 1;
 
     User.findUsersWithPagination({page: page}, function(err, users, pageCount, itemCount) {
       var pager = createPager(page, pageCount, itemCount, MAX_PAGE_LIST);
@@ -221,7 +221,7 @@ module.exports = function(app) {
   function saveSetting(req, res, form)
   {
     Config.updateNamespaceByArray('crowi', form, function(err, config) {
-      Config.updateConfigCache('crowi', config)
+      Config.updateConfigCache('crowi', config);
       return res.json({status: true});
     });
   }

+ 2 - 1
routes/index.js

@@ -56,7 +56,8 @@ module.exports = function(app) {
   app.post('/me/auth/google'         , middleware.loginRequired(app) , me.authGoogle);
   app.get('/me/auth/google/callback' , middleware.loginRequired(app) , me.authGoogleCallback);
 
-  app.get('/_r/:id'                  , middleware.loginRequired(app) , page.api.redirector);
+  app.get('/:id([0-9a-z]{24})'       , middleware.loginRequired(app) , page.api.redirector);
+  app.get('/_r/:id([0-9a-z]{24})'    , middleware.loginRequired(app) , page.api.redirector); // alias
   app.get('/_api/check_username'     , user.api.checkUsername);
   app.post('/_api/me/picture/upload' , middleware.loginRequired(app) , me.api.uploadPicture);
   app.get('/_api/user/bookmarks'     , middleware.loginRequired(app) , user.api.bookmarks);

+ 4 - 4
routes/page.js

@@ -188,7 +188,7 @@ module.exports = function(app) {
       return res.redirect(d.path);
     };
 
-    Page.findPageById(id, req.user, function(err, pageData) {
+    Page.findPageById(id, function(err, pageData) {
       if (pageData) {
         if (pageData.grant == Page.GRANT_RESTRICTED && !pageData.isGrantedFor(req.user)) {
           return Page.pushToGrantedUsers(pageData, req.user, cb);
@@ -219,7 +219,7 @@ module.exports = function(app) {
 
   api.bookmark = function(req, res){
     var id = req.params.id;
-    Page.findPageById(id, req.user, function(err, pageData) {
+    Page.findPageByIdAndGrantedUser(id, req.user, function(err, pageData) {
       if (pageData) {
         Bookmark.add(pageData, req.user, function(err, data) {
           return res.json({status: true});
@@ -235,7 +235,7 @@ module.exports = function(app) {
    */
   api.like = function(req, res){
     var id = req.params.id;
-    Page.findPageById(id, req.user, function(err, pageData) {
+    Page.findPageByIdAndGrantedUser(id, req.user, function(err, pageData) {
       if (pageData) {
         pageData.like(req.user, function(err, data) {
           return res.json({status: true});
@@ -252,7 +252,7 @@ module.exports = function(app) {
   api.unlike = function(req, res){
     var id = req.params.id;
 
-    Page.findPageById(id, req.user, function(err, pageData) {
+    Page.findPageByIdAndGrantedUser(id, req.user, function(err, pageData) {
       if (pageData) {
         pageData.unlike(req.user, function(err, data) {
           return res.json({status: true});

+ 2 - 2
views/_form.html

@@ -37,12 +37,12 @@
     var watchTimer = setInterval(function() {
       var content = $('#form-body').val();
       if (prevContent != content) {
-        var renderer = new Crowi.renderer('#form-body', $('#form-format').val(), '#preview-body');
+        var renderer = new Crowi.renderer($('#form-body').val(), $('#preview-body'));
         renderer.render();
 
         prevContent = content;
       }
-    }, 1000);
+    }, 500);
 
     // tabs handle
     $('textarea#form-body').on('keydown', function(event){

+ 1 - 3
views/layout/2column.html

@@ -60,7 +60,7 @@
       #}
       <li id="login-user">
         <a href="/user/{{ user.username }}" id="link-mypage">
-          <img src="{{ user|picture }}" class="picture picture-rounded" width="25" /> マイページ
+          <img src="{{ user|picture }}" class="picture picture-rounded" width="25" /> {{ user.name }}
         </a>
       </li>
       <li class="dropdown">
@@ -132,9 +132,7 @@
 
 <script>
   $(function() {
-    console.log($.cookie('aside-hidden'));
     if ($.cookie('aside-hidden') == 1) {
-      console.log("add aside-hidden");
       $('.main-container').addClass('aside-hidden');
     }
   });

+ 2 - 12
views/layout/layout.html

@@ -11,19 +11,9 @@
 
   <meta name="viewport" content="width=device-width,initial-scale=1">
 
-  {% if env  == 'development' %}
-  <link rel="stylesheet" href="/css/crowi.css">
-  {% else %}
-  <link rel="stylesheet" href="/css/crowi.min.css">
-  {% endif %}
-
-  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+  <link rel="stylesheet" href="/css/crowi{% if env  == 'production' %}.min{% endif %}.css">
+  <script src="/js/crowi{% if env  == 'production' %}.min{% endif %}.js"></script>
   <link href='//fonts.googleapis.com/css?family=Maven+Pro:400,700' rel='stylesheet' type='text/css'>
-  {% if env  == 'development' %}
-  <script src="/js/crowi.js"></script>
-  {% else %}
-  <script src="/js/crowi.min.js"></script>
-  {% endif %}
 </head>
 {% endblock %}
 

+ 6 - 9
views/page.html

@@ -42,7 +42,6 @@
       </a>
     </li>
 
-    <li><a href="#raw-text" data-toggle="tab"><i class="fa fa-font"></i> テキスト表示</a></li>
     <li {% if req.body.pageForm %}class="active"{% endif %}><a href="#edit-form" data-toggle="tab"><i class="fa fa-pencil-square-o"></i> 編集</a></li>
 
     <li class="dropdown pull-right">
@@ -79,6 +78,7 @@
     </div>
   </div>
 #}
+    <script type="text/template" id="raw-text-original">{{ revision.body }}</script>
 
     {# formatted text #}
     <div class="tab-pane {% if not req.body.pageForm %}active{% endif %}" id="revision-body">
@@ -86,16 +86,12 @@
         <a data-toggle="collapse" data-parent="#revision-toc" href="#revision-toc-content" class="revision-toc-head collapsed">目次</a>
 
       </div>
-    {% if revision.format == 'text' %}
-      <div class="wiki {{ revision.format }}" id="revision-body-content"><pre class="" id=""></pre></div>
-    {% else  %}
       <div class="wiki {{ revision.format }}" id="revision-body-content"></div>
-    {% endif  %}
     </div>
 
     {# raw text #}
     <div class="tab-pane" id="raw-text">
-      <pre id="raw-text-original">{{ revision.body }}</pre>
+      <pre id="">{{ revision.body }}</pre>
     </div>
 
     {# edit form #}
@@ -105,7 +101,7 @@
   </div>
   <script type="text/javascript">
     $(function(){
-        var renderer = new Crowi.renderer('#raw-text-original', '{{ revision.format }}');
+        var renderer = new Crowi.renderer($('#raw-text-original').html());
         renderer.render();
         Crowi.correctHeaders('#revision-body-content');
         Crowi.revisionToc('#revision-body-content', '#revision-toc');
@@ -169,12 +165,13 @@
 {% if page %} {# {{{ if page #}
 <div class="page-meta">
   <div class="row">
+    {# default(author) としているのは、v1.1.1 以前に page.creator データが入ってないから。暫定として最新更新ユーザーを表示しちゃう。 #}
     <div class="col-md-3 creator-picture">
-      <img src="{{ author|picture }}" class="picture picture-lg picture-rounded"><br>
+      <img src="{{ page.creator|default(author)|picture }}" class="picture picture-lg picture-rounded"><br>
     </div>
     <div class="col-md-9">
       <p class="creator">
-        {{ author.name }}
+        {{ page.creator.name|default(author.name) }}
       </p>
       <p class="created-at">
         作成日: {{ page.createdAt|datetz('Y/m/d H:i:s') }}<br>

+ 5 - 6
views/page_list.html

@@ -43,9 +43,10 @@
       {% for page in pages %}
       <div class="timeline-body" id="id-{{ page.id }}">
         <h3 class="revision-path"><a href="{{ page.path }}">{{ page.path }}</a></h3>
-        <div class="revision-body" data-format="{{ page.revision.format }}"><pre></pre></div>
-        <pre class="hide raw-text-original">{{ page.revision.body }}</pre>
+        <div class="revision-body wiki"></div>
+        <script type="text/template">{{ page.revision.body }}</script>
       </div>
+      <hr>
       {% endfor %}
     </div>
   </div>
@@ -55,12 +56,10 @@
         $('#view-timeline .timeline-body').each(function()
         {
           var id = $(this).attr('id');
-          //var format = $(this).children('.body').data('format');
-          var format = 'text';
-          var contentId = '#' + id + ' .raw-text-original';
+          var contentId = '#' + id + ' > script';
           var revisionBody = '#' + id + ' .revision-body';
           var revisionPath = '#' + id + ' .revision-path';
-          var renderer = new Crowi.renderer(contentId, format, revisionBody);
+          var renderer = new Crowi.renderer($(contentId).html(), $(revisionBody));
           renderer.render();
         });
         //$('.tooltip .tabs').tabs();

+ 13 - 25
views/page_presentation.html

@@ -6,10 +6,10 @@
     <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
 
-    <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/reveal.js/2.5/css/reveal.min.css">
-    <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/reveal.js/2.5/css/theme/solarized.css">
+
     <link rel="stylesheet" type="text/css" href="/css/crowi-reveal.css">
-    <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/reveal.js/2.5/lib/css/zenburn.css">
+    <link rel="stylesheet" type="text/css" href="/bower_components/reveal.js/lib/css/zenburn.css">
+
     <title>{{ path|path2name }} | {{ path }}</title>
   </head>
   <body>
@@ -17,7 +17,7 @@
       <div class="slides">
         <section data-markdown data-separator="^\n\n\n">
           <script type="text/template">
-{{ revision.body|presentation }}
+{{ revision.body|presentation|safe }}
 
 
 
@@ -27,38 +27,26 @@
       </div>
     </div>
 
-    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
-    <script src="//cdnjs.cloudflare.com/ajax/libs/reveal.js/2.5/lib/js/head.min.js"></script>
-    <script src="//cdnjs.cloudflare.com/ajax/libs/reveal.js/2.5/js/reveal.min.js"></script>
+    <script src="/js/crowi-reveal.js"></script>
     <script>
-
-      // Full list of configuration options available here:
-      // https://github.com/hakimel/reveal.js#configuration
       Reveal.initialize({
         controls: true,
         progress: true,
         history: true,
-        center: false,
-
-        theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
-        transition: Reveal.getQueryHash().transition || 'default', // default/cube/page/concave/zoom/linear/fade/none
-
-        // Parallax scrolling
-        // parallaxBackgroundImage: 'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg',
-        // parallaxBackgroundSize: '2100px 900px',
+        center: true,
+        transition: 'slide',
 
         // Optional libraries used to extend on reveal.js
         dependencies: [
-          { src: '//cdnjs.cloudflare.com/ajax/libs/reveal.js/2.5/lib/js/classList.js', condition: function() { return !document.body.classList; } },
-          { src: '//cdnjs.cloudflare.com/ajax/libs/reveal.js/2.5/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
-          { src: '//cdnjs.cloudflare.com/ajax/libs/reveal.js/2.5/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
-          { src: '//cdnjs.cloudflare.com/ajax/libs/reveal.js/2.5/plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
-          { src: '//cdnjs.cloudflare.com/ajax/libs/reveal.js/2.5/plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
-          { src: '//cdnjs.cloudflare.com/ajax/libs/reveal.js/2.5/plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }
+          { src: '/bower_components/reveal.js/lib/js/classList.js', condition: function() { return !document.body.classList; } },
+          { src: '/bower_components/reveal.js/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
+          { src: '/bower_components/reveal.js/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
+          { src: '/bower_components/reveal.js/plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
+          { src: '/bower_components/reveal.js/plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
+          { src: '/bower_components/reveal.js/plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }
         ]
       });
 
-      //
       Reveal.addEventListener('ready', function(event) {
         // event.currentSlide, event.indexh, event.indexv
         $('.reveal section').each(function(e) {