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

Merge pull request #65 from crowi/add-diff-viewer

Added diff view in History tab.
Sotaro KARASAWA 10 лет назад
Родитель
Сommit
535f61fbd3
7 измененных файлов с 173 добавлено и 9 удалено
  1. 1 0
      gulpfile.js
  2. 38 8
      lib/models/revision.js
  3. 4 0
      lib/routes/index.js
  4. 52 0
      lib/routes/revision.js
  5. 4 0
      lib/views/page.html
  6. 1 0
      package.json
  7. 73 1
      resource/js/crowi.js

+ 1 - 0
gulpfile.js

@@ -47,6 +47,7 @@ var js = {
     'node_modules/inline-attachment/src/inline-attachment.js',
     'node_modules/socket.io-client/socket.io.js',
     'node_modules/jquery.cookie/jquery.cookie.js',
+    'node_modules/diff/dist/diff.js',
     'resource/thirdparty-js/jquery.selection.js',
     dirs.jsDist + '/crowi-bundled.js',
   ],

+ 38 - 8
lib/models/revision.js

@@ -21,6 +21,44 @@ module.exports = function(crowi) {
       });
   };
 
+  revisionSchema.statics.findRevision = function(id) {
+    var Revision = this;
+
+    return new Promise(function(resolve, reject) {
+      Revision.findById(id)
+        .populate('author')
+        .exec(function(err, data) {
+          if (err) {
+            return reject(err);
+          }
+
+          return resolve(data);
+        });
+      });
+  };
+
+  revisionSchema.statics.findRevisions = function(ids) {
+    var Revision = this;
+
+    if (!Array.isArray(ids)) {
+      return Promise.reject('The argument was not Array.');
+    }
+
+    return new Promise(function(resolve, reject) {
+      Revision
+        .find({ _id: { $in: ids }})
+        .sort({createdAt: -1})
+        .populate('author')
+        .exec(function(err, revisions) {
+          if (err) {
+            return reject(err);
+          }
+
+          return resolve(revisions);
+        });
+    });
+  };
+
   revisionSchema.statics.findRevisionList = function(path, options) {
     var Revision = this;
 
@@ -52,14 +90,6 @@ module.exports = function(crowi) {
     });
   };
 
-  revisionSchema.statics.findRevision = function(id, cb) {
-    this.findById(id)
-      .populate('author')
-      .exec(function(err, data) {
-        cb(err, data);
-      });
-  };
-
   revisionSchema.statics.prepareRevision = function(pageData, body, user, options) {
     var Revision = this;
 

+ 4 - 0
lib/routes/index.js

@@ -11,6 +11,7 @@ module.exports = function(crowi, app) {
     , attachment= require('./attachment')(crowi, app)
     , comment   = require('./comment')(crowi, app)
     , bookmark  = require('./bookmark')(crowi, app)
+    , revision  = require('./revision')(crowi, app)
     , loginRequired = middleware.loginRequired
     , accessTokenParser = middleware.accessTokenParser
     ;
@@ -88,6 +89,9 @@ module.exports = function(crowi, app) {
   app.post('/_api/likes.add'          , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.like);
   app.post('/_api/likes.remove'       , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.unlike);
 
+  app.get( '/_api/revisions.get'      , accessTokenParser(crowi, app) , loginRequired(crowi, app) , revision.api.get);
+  app.get( '/_api/revisions.list'     , accessTokenParser(crowi, app) , loginRequired(crowi, app) ,revision.api.list);
+
   //app.get('/_api/revision/:id'     , user.useUserData()         , revision.api.get);
   //app.get('/_api/r/:revisionId'    , user.useUserData()         , page.api.get);
 

+ 52 - 0
lib/routes/revision.js

@@ -0,0 +1,52 @@
+module.exports = function(crowi, app) {
+  'use strict';
+
+  var debug = require('debug')('crowi:routes:revision')
+    , Revision = crowi.model('Revision')
+    , ApiResponse = require('../util/apiResponse')
+    , actions = {}
+  ;
+  actions.api = {};
+
+  /**
+   * @api {get} /revisions.get Get revision
+   * @apiName GetRevision
+   * @apiGroup Revision
+   *
+   * @apiParam {String} revision_id Revision Id.
+   */
+  actions.api.get = function(req, res) {
+    var revisionId = req.query.revision_id;
+
+    Revision
+      .findRevision(revisionId)
+      .then(function(revisionData) {
+        return res.json(ApiResponse.success(revisionData));
+      })
+      .catch(function(err) {
+        return res.json(ApiResponse.error(err));
+      });
+  };
+
+  /**
+   * @api {get} /revisions.list Get revisions
+   * @apiName ListRevision
+   * @apiGroup Revision
+   *
+   * @apiParam {String} revision_ids Revision Ids.
+   */
+  actions.api.list = function(req, res) {
+    var revisionIds = req.query.revision_ids.split(',');
+
+    Revision
+      .findRevisions(revisionIds)
+      .then(function(revisions) {
+        return res.json(ApiResponse.success(revisions));
+      })
+      .catch(function(err) {
+        return res.json(ApiResponse.error(err));
+      });
+  };
+
+  return actions;
+};

+ 4 - 0
lib/views/page.html

@@ -140,6 +140,10 @@
               {{ t.createdAt|datetz('Y-m-d H:i:s') }}
               <br>
               <a href="?revision={{ t._id.toString() }}"><i class="fa fa-history"></i> このバージョンを見る</a>
+              <a class="diff-view" data-revision-id="{{ t._id.toString() }}">
+                <i id="diff-icon-{{ t._id.toString() }}" class="fa fa-arrow-circle-right"></i> 差分を見る
+              </a>
+              <pre class="fk-hide" id="diff-display-{{ t._id.toString()}}"></pre>
             </div>
           </div>
         </div>

+ 1 - 0
package.json

@@ -41,6 +41,7 @@
     "consolidate": "~0.11.0",
     "cookie-parser": "~1.3.4",
     "debug": "~2.2.0",
+    "diff": "^2.2.2",
     "errorhandler": "~1.3.4",
     "express": "~4.13.3",
     "express-form": "~0.12.0",

+ 73 - 1
resource/js/crowi.js

@@ -4,6 +4,7 @@
 */
 
 var hljs = require('highlight.js');
+var jsdiff = require('diff');
 var marked = require('marked');
 var Crowi = {};
 
@@ -608,6 +609,77 @@ $(function() {
         $seenUserList.append(CreateUserLinkWithPicture(user));
       });
     }
+
+    // History Diff
+    var allRevisionIds = [];
+    $.each($('.diff-view'), function() {
+      allRevisionIds.push($(this).data('revisionId'));
+    });
+
+    $('.diff-view').on('click', function(e) {
+      e.preventDefault();
+
+      var getBeforeRevisionId = function(revisionId) {
+        var currentPos = $.inArray(revisionId, allRevisionIds);
+        if (currentPos < 0) {
+          return false;
+        }
+
+        var beforeRevisionId = allRevisionIds[currentPos + 1];
+        if (typeof beforeRevisionId === 'undefined') {
+          return false;
+        }
+
+        return beforeRevisionId;
+      };
+
+      var revisionId = $(this).data('revisionId');
+      var beforeRevisionId = getBeforeRevisionId(revisionId);
+      var $diffDisplay = $('#diff-display-' + revisionId);
+      var $diffIcon = $('#diff-icon-' + revisionId);
+
+      if ($diffIcon.hasClass('fa-arrow-circle-right')) {
+        $diffIcon.removeClass('fa-arrow-circle-right');
+        $diffIcon.addClass('fa-arrow-circle-down');
+      } else {
+        $diffIcon.removeClass('fa-arrow-circle-down');
+        $diffIcon.addClass('fa-arrow-circle-right');
+      }
+
+      if (beforeRevisionId === false) {
+        $diffDisplay.text('差分はありません');
+        $diffDisplay.slideToggle();
+      } else {
+        var revisionIds = revisionId + ',' + beforeRevisionId;
+
+        $.ajax({
+          type: 'GET',
+          url: '/_api/revisions.list?revision_ids=' + revisionIds,
+          dataType: 'json'
+        }).done(function(res) {
+          var currentText = res[0].body;
+          var previousText = res[1].body;
+
+          $diffDisplay.text('');
+
+          var diff = jsdiff.diffLines(previousText, currentText);
+          diff.forEach(function(part) {
+            var color = part.added ? 'green' : part.removed ? 'red' : 'grey';
+            var $span = $('<span>');
+            $span.css('color', color);
+            $span.text(part.value);
+            $diffDisplay.append($span);
+          });
+
+          $diffDisplay.slideToggle();
+        });
+      }
+    });
+
+    // default open
+    var diffViews = $('.diff-view');
+    if (diffViews.length > 0) for (var i = 0; i < 2; i++) {
+      diffViews[i].click();
+    }
   }
 });
-