From 4d244763164eb88638aec85d85529ea49feb0f94 Mon Sep 17 00:00:00 2001
From: "Cheng-Han, Wu" <jackymaxj@gmail.com>
Date: Fri, 17 Jun 2016 16:15:53 +0800
Subject: [PATCH] Add revision modal with UIs and support to mark patch diff
 texts

---
 public/js/index.js              | 146 +++++++++++++++++++++++++++++++-
 public/views/body.ejs           |   3 +-
 public/views/header.ejs         |   4 +
 public/views/revision-modal.ejs |  27 ++++++
 4 files changed, 178 insertions(+), 2 deletions(-)
 create mode 100644 public/views/revision-modal.ejs

diff --git a/public/js/index.js b/public/js/index.js
index 85bd2e1..27a3398 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -564,7 +564,8 @@ var ui = {
     },
     modal: {
         snippetImportProjects: $("#snippetImportModalProjects"),
-        snippetImportSnippets: $("#snippetImportModalSnippets")
+        snippetImportSnippets: $("#snippetImportModalSnippets"),
+        revision: $("#revisionModal")
     }
 };
 
@@ -1382,6 +1383,149 @@ ui.toolbar.beta.pdf.attr("download", "").attr("href", noteurl + "/pdf");
 ui.toolbar.beta.slide.attr("href", noteurl + "/slide");
 
 //modal actions
+var revisions = [];
+var revisionViewer = null;
+var revisionList = ui.modal.revision.find('.ui-revision-list');
+var revision = null;
+var revisionTime = null;
+ui.modal.revision.on('show.bs.modal', function (e) {
+    $.get(noteurl + '/revision')
+        .success(function(data) {
+            parseRevisions(JSON.parse(data).revision);
+            initRevisionViewer();
+        })
+        .error(function(err) {
+
+        })
+        .complete(function() {
+            //na
+        });
+});
+function checkRevisionViewer() {
+    if (revisionViewer) {
+        var container = $(revisionViewer.display.wrapper).parent();
+        $(revisionViewer.display.scroller).css('height', container.height() + 'px');
+        revisionViewer.refresh();
+    }
+}
+ui.modal.revision.on('shown.bs.modal', checkRevisionViewer);
+$(window).resize(checkRevisionViewer);
+function parseRevisions(_revisions) {
+    if (_revisions.length != revisions) {
+        revisions = _revisions;
+        var lastRevision = null;
+        if (revisionList.children().length > 0) {
+            lastRevision = revisionList.find('.active').attr('data-revision-time');
+        }
+        revisionList.html('');
+        for (var i = 0; i < revisions.length; i++) {
+            var revision = revisions[i];
+            var item = $('<a href="#" class="list-group-item"></a>');
+            item.attr('data-revision-time', revision.time);
+            if (lastRevision == revision.time) item.addClass('active');
+            var itemHeading = $('<h5 class="list-group-item-heading"></h5>');
+            itemHeading.html('<i class="fa fa-clock-o"></i> ' + moment(revision.time).format('llll'));
+            var itemText = $('<p class="list-group-item-text"></p>');
+            itemText.html('<i class="fa fa-file-text"></i> Length: ' + revision.length);
+            item.append(itemHeading).append(itemText);
+            item.click(function (e) {
+                var time = $(this).attr('data-revision-time');
+                selectRevision(time);
+            });
+            revisionList.append(item);
+        }
+        if (!lastRevision) {
+            selectRevision(revisions[0].time);
+        }
+    }
+}
+function selectRevision(time) {
+    if (time == revisionTime) return; 
+    $.get(noteurl + '/revision/' + time)
+        .success(function(data) {
+            revision = JSON.parse(data);
+            revisionTime = time;
+            var lastScrollInfo = revisionViewer.getScrollInfo();
+            revisionList.children().removeClass('active');
+            revisionList.find('[data-revision-time="' + time + '"]').addClass('active');
+            var content = revision.content;
+            revisionViewer.setValue(content);
+            revisionViewer.scrollTo(null, lastScrollInfo.top);
+            // mark the text which have been insert or delete
+            if (revision.patch.length > 0) {
+                var bias = 0;
+                for (j = 0; j < revision.patch.length; j++) {
+                    var patch = revision.patch[j];
+                    var currIndex = patch.start1 + bias;
+                    for (var i = 0; i < patch.diffs.length; i++) {
+                        var diff = patch.diffs[i];
+                        // ignore if diff only contains line breaks
+                        if ((diff[1].match(new RegExp("\n", "g")) || []).length == diff[1].length) continue;
+                        switch(diff[0]) {
+                            case 0: // retain
+                                currIndex += diff[1].length;
+                            break;
+                            case 1: // insert
+                                var prePos = revisionViewer.posFromIndex(currIndex);
+                                var postPos = revisionViewer.posFromIndex(currIndex + diff[1].length);
+                                revisionViewer.markText(prePos, postPos, {
+                                    css: 'background-color: rgba(230,255,230,0.7); text-decoration: underline;'
+                                });
+                                currIndex += diff[1].length;
+                            break;
+                            case -1: // delete
+                                var prePos = revisionViewer.posFromIndex(currIndex);
+                                revisionViewer.replaceRange(diff[1], prePos);
+                                var postPos = revisionViewer.posFromIndex(currIndex + diff[1].length);
+                                revisionViewer.markText(prePos, postPos, {
+                                    css: 'background-color: rgba(255,230,230,0.7); text-decoration: line-through;'
+                                });
+                                bias += diff[1].length;
+                                currIndex += diff[1].length;
+                            break;
+                        }
+                    }
+                }
+            }
+        })
+        .error(function(err) {
+
+        })
+        .complete(function() {
+            //na
+        });
+}
+function initRevisionViewer() {
+    if (revisionViewer) return;
+    var revisionViewerTextArea = document.getElementById("revisionViewer");
+    revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, {
+        mode: 'gfm',
+        viewportMargin: viewportMargin,
+        lineNumbers: true,
+        lineWrapping: true,
+        showCursorWhenSelecting: true,
+        inputStyle: "textarea",
+        gutters: ["CodeMirror-linenumbers"],
+        flattenSpans: true,
+        addModeClass: true,
+        readOnly: true,
+        autoRefresh: true,
+        scrollbarStyle: 'overlay'
+    });
+}
+$('#revisionModalDownload').click(function () {
+    if (!revision) return;
+    var filename = renderFilename(ui.area.markdown) + '_' + revisionTime + '.md';
+    var blob = new Blob([revision.content], {
+        type: "text/markdown;charset=utf-8"
+    });
+    saveAs(blob, filename);
+});
+$('#revisionModalRevert').click(function () {
+    if (!revision) return;
+    editor.setValue(revision.content);
+    ui.modal.revision.modal('hide');
+});
 //snippet projects
 ui.modal.snippetImportProjects.change(function() {
     var accesstoken = $("#snippetImportModalAccessToken").val(),
diff --git a/public/views/body.ejs b/public/views/body.ejs
index 62493e4..9fe1ca7 100644
--- a/public/views/body.ejs
+++ b/public/views/body.ejs
@@ -235,4 +235,5 @@
     </div>
 </div>
 <%- include signin-modal %>
-<%- include help-modal %>
\ No newline at end of file
+<%- include help-modal %>
+<%- include revision-modal %>
\ No newline at end of file
diff --git a/public/views/header.ejs b/public/views/header.ejs
index bc54056..b00c2db 100644
--- a/public/views/header.ejs
+++ b/public/views/header.ejs
@@ -28,6 +28,8 @@
                 </li>
                 <li class="divider"></li>
                 <li class="dropdown-header">Beta</li>
+                <li role="presentation"><a role="menuitem" class="ui-beta-revision" tabindex="-1" data-toggle="modal" data-target="#revisionModal"><i class="fa fa-history fa-fw"></i> Revision</a>
+                </li>
                 <li role="presentation"><a role="menuitem" class="ui-beta-pdf" tabindex="-1" href="#" target="_self"><i class="fa fa-file-pdf-o fa-fw"></i> Export PDF</a>
                 </li>
                 <li role="presentation"><a role="menuitem" class="ui-beta-slide" tabindex="-1" href="#" target="_blank"><i class="fa fa-tv fa-fw"></i> Slide Mode</a>
@@ -121,6 +123,8 @@
                 </a>
                 <ul class="dropdown-menu" role="menu" aria-labelledby="menu">
                     <li class="dropdown-header">Beta</li>
+                    <li role="presentation"><a role="menuitem" class="ui-beta-revision" tabindex="-1" data-toggle="modal" data-target="#revisionModal"><i class="fa fa-history fa-fw"></i> Revision</a>
+                    </li>
                     <li role="presentation"><a role="menuitem" class="ui-beta-pdf" tabindex="-1" href="#" target="_self"><i class="fa fa-file-pdf-o fa-fw"></i> Export PDF</a>
                     </li>
                     <li role="presentation"><a role="menuitem" class="ui-beta-slide" tabindex="-1" href="#" target="_blank"><i class="fa fa-tv fa-fw"></i> Slide Mode</a>
diff --git a/public/views/revision-modal.ejs b/public/views/revision-modal.ejs
new file mode 100644
index 0000000..008e62a
--- /dev/null
+++ b/public/views/revision-modal.ejs
@@ -0,0 +1,27 @@
+<!-- revision modal -->
+<div class="modal fade" id="revisionModal" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
+    <div class="modal-dialog modal-lg">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
+                </button>
+                <h4 class="modal-title" id="mySmallModalLabel"><i class="fa fa-history"></i> Revision</h4>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-lg-4" style="max-height: calc(100vh - 215px); overflow: auto;">
+                        <div class="list-group ui-revision-list"></div>
+                    </div>
+                    <div class="col-lg-8" style="height: calc(100vh - 215px); overflow: hidden;">
+                        <textarea id="revisionViewer" style="display:none;"></textarea>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
+                <button type="button" class="btn btn-primary" id="revisionModalDownload">Download</button>
+                <button type="button" class="btn btn-danger" id="revisionModalRevert">Revert</button>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file