mirror of
https://github.com/apache/zeppelin
synced 2026-05-24 09:38:26 +00:00
[ZEPPELIN-2813] revisions comparator for note
This commit is contained in:
parent
2e59008089
commit
a192b95dbb
12 changed files with 406 additions and 11 deletions
|
|
@ -29,8 +29,6 @@ import java.util.regex.Pattern;
|
|||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.vfs2.FileSystemException;
|
||||
import org.apache.zeppelin.conf.ZeppelinConfiguration;
|
||||
|
|
@ -50,8 +48,8 @@ import org.apache.zeppelin.interpreter.InterpreterSetting;
|
|||
import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry;
|
||||
import org.apache.zeppelin.interpreter.remote.RemoteInterpreterProcessListener;
|
||||
import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
|
||||
import org.apache.zeppelin.notebook.JobListenerFactory;
|
||||
import org.apache.zeppelin.notebook.Folder;
|
||||
import org.apache.zeppelin.notebook.JobListenerFactory;
|
||||
import org.apache.zeppelin.notebook.Note;
|
||||
import org.apache.zeppelin.notebook.Notebook;
|
||||
import org.apache.zeppelin.notebook.NotebookAuthorization;
|
||||
|
|
@ -59,7 +57,6 @@ import org.apache.zeppelin.notebook.NotebookEventListener;
|
|||
import org.apache.zeppelin.notebook.NotebookImportDeserializer;
|
||||
import org.apache.zeppelin.notebook.Paragraph;
|
||||
import org.apache.zeppelin.notebook.ParagraphJobListener;
|
||||
import org.apache.zeppelin.notebook.ParagraphRuntimeInfo;
|
||||
import org.apache.zeppelin.notebook.repo.NotebookRepo.Revision;
|
||||
import org.apache.zeppelin.notebook.socket.Message;
|
||||
import org.apache.zeppelin.notebook.socket.Message.OP;
|
||||
|
|
@ -83,11 +80,11 @@ import org.quartz.SchedulerException;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Queues;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
|
|
@ -340,6 +337,9 @@ public class NotebookServer extends WebSocketServlet
|
|||
case NOTE_REVISION:
|
||||
getNoteByRevision(conn, notebook, messagereceived);
|
||||
break;
|
||||
case NOTE_REVISION_FOR_COMPARE:
|
||||
getNoteByRevisionForCompare(conn, notebook, messagereceived);
|
||||
break;
|
||||
case LIST_NOTE_JOBS:
|
||||
unicastNoteJobInfo(conn, messagereceived);
|
||||
break;
|
||||
|
|
@ -1920,6 +1920,25 @@ public class NotebookServer extends WebSocketServlet
|
|||
.put("note", revisionNote)));
|
||||
}
|
||||
|
||||
private void getNoteByRevisionForCompare(NotebookSocket conn, Notebook notebook,
|
||||
Message fromMessage) throws IOException {
|
||||
String noteId = (String) fromMessage.get("noteId");
|
||||
String revisionId = (String) fromMessage.get("revisionId");
|
||||
|
||||
String position = (String) fromMessage.get("position");
|
||||
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
|
||||
Note revisionNote;
|
||||
if (revisionId.equals("Head")) {
|
||||
revisionNote = notebook.loadNoteFromRepo(noteId, subject);
|
||||
} else {
|
||||
revisionNote = notebook.getNoteByRevision(noteId, revisionId, subject);
|
||||
}
|
||||
|
||||
conn.send(serializeMessage(
|
||||
new Message(OP.NOTE_REVISION_FOR_COMPARE).put("noteId", noteId)
|
||||
.put("revisionId", revisionId).put("position", position).put("note", revisionNote)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This callback is for the paragraph that runs on ZeppelinServer
|
||||
*
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@
|
|||
"bootstrap3-dialog": "bootstrap-dialog#~1.34.7",
|
||||
"select2": "^4.0.3",
|
||||
"MathJax": "2.7.0",
|
||||
"ngclipboard": "^1.1.1"
|
||||
"ngclipboard": "^1.1.1",
|
||||
"jsdiff": "3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "1.5.7"
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ module.exports = function(config) {
|
|||
'bower_components/MathJax/MathJax.js',
|
||||
'bower_components/clipboard/dist/clipboard.js',
|
||||
'bower_components/ngclipboard/dist/ngclipboard.js',
|
||||
'bower_components/jsdiff/diff.js',
|
||||
'bower_components/angular-mocks/angular-mocks.js',
|
||||
// endbower
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,15 @@ limitations under the License.
|
|||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="button"
|
||||
ng-if="noteRevisions.length > 1"
|
||||
class="btn btn-default btn-xs"
|
||||
id="compareRevisions"
|
||||
data-toggle="modal"
|
||||
data-target="#revisionsComparatorModal"
|
||||
tooltip-placement="bottom" uib-tooltip="Compare revisions">
|
||||
<i class="fa fa-exchange"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-default btn-xs revisionName" title="{{currentRevision}}">
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
}
|
||||
|
||||
$scope.noteRevisions = []
|
||||
$scope.firstNoteRevisionForCompare = null
|
||||
$scope.secondNoteRevisionForCompare = null
|
||||
$scope.mergeNoteRevisionsForCompare = null
|
||||
$scope.currentFirstRevisionForCompare = 'Choose...'
|
||||
$scope.currentSecondRevisionForCompare = 'Choose...'
|
||||
$scope.currentRevision = 'Head'
|
||||
$scope.revisionView = isRevisionPath($location.path())
|
||||
|
||||
|
|
@ -231,13 +236,24 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
})
|
||||
}
|
||||
|
||||
$scope.preVisibleRevisionsComparator = function() {
|
||||
$scope.mergeNoteRevisionsForCompare = null
|
||||
$scope.firstNoteRevisionForCompare = null
|
||||
$scope.secondNoteRevisionForCompare = null
|
||||
$scope.currentFirstRevisionForCompare = 'Choose...'
|
||||
$scope.currentSecondRevisionForCompare = 'Choose...'
|
||||
$scope.$apply()
|
||||
}
|
||||
|
||||
$scope.$on('listRevisionHistory', function (event, data) {
|
||||
console.debug('received list of revisions %o', data)
|
||||
$scope.noteRevisions = data.revisionList
|
||||
$scope.noteRevisions.splice(0, 0, {
|
||||
id: 'Head',
|
||||
message: 'Head'
|
||||
})
|
||||
if ($scope.noteRevisions.length === 0 || $scope.noteRevisions[0].id !== 'Head') {
|
||||
$scope.noteRevisions.splice(0, 0, {
|
||||
id: 'Head',
|
||||
message: 'Head'
|
||||
})
|
||||
}
|
||||
if ($routeParams.revisionId) {
|
||||
let index = _.findIndex($scope.noteRevisions, {'id': $routeParams.revisionId})
|
||||
if (index > -1) {
|
||||
|
|
@ -278,6 +294,99 @@ function NotebookCtrl ($scope, $route, $routeParams, $location, $rootScope,
|
|||
}
|
||||
}
|
||||
|
||||
// compare revisions
|
||||
$scope.compareRevisions = function () {
|
||||
if ($scope.firstNoteRevisionForCompare && $scope.secondNoteRevisionForCompare) {
|
||||
let paragraphs1 = $scope.firstNoteRevisionForCompare.note.paragraphs
|
||||
let paragraphs2 = $scope.secondNoteRevisionForCompare.note.paragraphs
|
||||
let merge = {
|
||||
added: [],
|
||||
deleted: [],
|
||||
compared: []
|
||||
}
|
||||
for (let p1 of paragraphs1) {
|
||||
let p2 = null
|
||||
for (let p of paragraphs2) {
|
||||
if (p1.id === p.id) {
|
||||
p2 = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if (p2 === null) {
|
||||
merge.deleted.push({paragraph: p1, firstString: (p1.text || '').split('\n')[0]})
|
||||
} else {
|
||||
let colorClass = ''
|
||||
let span = null
|
||||
let text1 = p1.text || ''
|
||||
let text2 = p2.text || ''
|
||||
|
||||
let diff = window.JsDiff.diffLines(text1, text2)
|
||||
let diffHtml = document.createDocumentFragment()
|
||||
let identical = true
|
||||
let identicalClass = 'color-black'
|
||||
|
||||
diff.forEach(function(part) {
|
||||
colorClass = part.added ? 'color-green' : part.removed ? 'color-red' : identicalClass
|
||||
span = document.createElement('span')
|
||||
span.className = colorClass
|
||||
if (identical && colorClass !== identicalClass) {
|
||||
identical = false
|
||||
}
|
||||
span.appendChild(document.createTextNode(part.value))
|
||||
diffHtml.appendChild(span)
|
||||
})
|
||||
|
||||
let pre = document.createElement('pre')
|
||||
pre.appendChild(diffHtml)
|
||||
|
||||
merge.compared.push(
|
||||
{paragraph: p1, diff: pre.innerHTML, identical: identical, firstString: (p1.text || '').split('\n')[0]})
|
||||
}
|
||||
}
|
||||
|
||||
for (let p2 of paragraphs2) {
|
||||
let p1 = null
|
||||
for (let p of paragraphs1) {
|
||||
if (p2.id === p.id) {
|
||||
p1 = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if (p1 === null) {
|
||||
merge.added.push({paragraph: p2, firstString: (p2.text || '').split('\n')[0]})
|
||||
}
|
||||
}
|
||||
$scope.mergeNoteRevisionsForCompare = merge
|
||||
}
|
||||
}
|
||||
|
||||
$scope.getNoteRevisionForReview = function (revision, position) {
|
||||
if (position) {
|
||||
if (position === 'first') {
|
||||
$scope.currentFirstRevisionForCompare = revision.message
|
||||
} else {
|
||||
$scope.currentSecondRevisionForCompare = revision.message
|
||||
}
|
||||
websocketMsgSrv.getNoteByRevisionForCompare($routeParams.noteId, revision.id, position)
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('noteRevisionForCompare', function (event, data) {
|
||||
console.debug('received note revision for compare %o', data)
|
||||
if (data.note && data.position) {
|
||||
if (data.position === 'first') {
|
||||
$scope.firstNoteRevisionForCompare = data
|
||||
} else {
|
||||
$scope.secondNoteRevisionForCompare = data
|
||||
}
|
||||
|
||||
if ($scope.firstNoteRevisionForCompare !== null && $scope.secondNoteRevisionForCompare !== null &&
|
||||
$scope.firstNoteRevisionForCompare.revisionId !== $scope.secondNoteRevisionForCompare.revisionId) {
|
||||
$scope.compareRevisions()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
$scope.runAllParagraphs = function (noteId) {
|
||||
BootstrapDialog.confirm({
|
||||
closable: true,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ limitations under the License.
|
|||
-->
|
||||
<!-- Here the controller <NotebookCtrl> is not needed because explicitly set in the app.js (route) -->
|
||||
<div id="actionbar" ng-include src="'app/notebook/notebook-actionBar.html'"></div>
|
||||
<div id="note-revisions-comparator-modal-container" ng-include src="'app/notebook/revisions-comparator.html'"></div>
|
||||
<div id="content" class="notebookContent">
|
||||
<!-- settings -->
|
||||
<div ng-if="showSetting" class="setting">
|
||||
|
|
|
|||
|
|
@ -270,6 +270,117 @@ table.table-shortcut {
|
|||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.revisions-comparator-modal-dialog {
|
||||
width: 80%;
|
||||
margin: 30px auto;
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
.revisions-comparator-modal-dialog .modal-content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.revisions-comparator-modal-header {
|
||||
min-height: 16.428571429px;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #9cb4c5;
|
||||
background-color: #3071a9;
|
||||
border: 2px solid #3071a9;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
.revisions-comparator-modal-header .close {
|
||||
color: #cfcfcf;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.revisions-comparator-modal-title {
|
||||
color: white;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.revisions-comparator-code-panel {
|
||||
display: inline-block;
|
||||
width: 50%;
|
||||
float: left;
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
max-height: 300px
|
||||
}
|
||||
|
||||
.revisions-comparator-code-panel-title {
|
||||
width: 50%;
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
padding: 5px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.revisions-comparator-bar {
|
||||
margin: 10px auto;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.revisions-comparator-status {
|
||||
font-size: 12px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#diffPanel {
|
||||
height: calc(100% - 60px);
|
||||
}
|
||||
|
||||
#diffPanel .panel-group {
|
||||
height: inherit;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.revision-name-for-compare {
|
||||
cursor: default;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
max-width: 100px;
|
||||
width: 100px;
|
||||
padding: 1px 10px;
|
||||
}
|
||||
|
||||
.revisions-comparator-caret {
|
||||
margin: 0 0 10px 6px;
|
||||
}
|
||||
|
||||
.revisions-comparator-link, .revisions-comparator-link:hover,
|
||||
.revisions-comparator-link:visited, .revisions-comparator-link:focus {
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.revisions-comparator-forst-string {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 12px;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.color-green {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.color-red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.color-black {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.color-orange {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
/*
|
||||
Paragraph Title
|
||||
*/
|
||||
|
|
|
|||
126
zeppelin-web/src/app/notebook/revisions-comparator.html
Normal file
126
zeppelin-web/src/app/notebook/revisions-comparator.html
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
<!--
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<div class="modal fade" id="revisionsComparatorModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"
|
||||
modalvisible previsiblecallback="preVisibleRevisionsComparator">
|
||||
<div class="revisions-comparator-modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="revisions-comparator-modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
|
||||
<h4 class="revisions-comparator-modal-title" id="myModalLabel">Revisions comparator</h4>
|
||||
</div>
|
||||
<div class="revisions-comparator-bar">
|
||||
<div class="btn-group">
|
||||
<button type="button" ng-if="noteRevisions.length > 0"
|
||||
class="btn btn-sm btn-default dropdown-toggle"
|
||||
data-toggle="dropdown" id="firstRevisionDropdown" title="{{currentFirstRevisionForCompare}}">
|
||||
<div class="revision-name-for-compare">{{currentFirstRevisionForCompare}}</div>
|
||||
<span class="caret revisions-comparator-caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu pull-right" aria-labelledby="firstRevisionDropdown">
|
||||
<li></li>
|
||||
<li ng-repeat="revision in noteRevisions | orderBy:'time':true" class="revision">
|
||||
<a style="cursor:pointer" ng-click="getNoteRevisionForReview(revision, 'first')">
|
||||
<span style="display: block;">
|
||||
<strong>{{revision.message}}</strong>
|
||||
</span>
|
||||
<span class="revisionDate">
|
||||
<em>{{formatRevisionDate(revision.time)}}</em>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span>compare with</span>
|
||||
<div class="btn-group">
|
||||
<button type="button" ng-if="noteRevisions.length > 0"
|
||||
class="btn btn-sm btn-default dropdown-toggle"
|
||||
ng-disabled="firstNoteRevisionForCompare === null"
|
||||
data-toggle="dropdown" id="secondRevisionDropdown" title="{{currentSecondRevisionForCompare}}">
|
||||
<div class="revision-name-for-compare">{{currentSecondRevisionForCompare}}</div>
|
||||
<span class="caret revisions-comparator-caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu pull-right" aria-labelledby="secondRevisionDropdown">
|
||||
<li ng-repeat="revision in noteRevisions | orderBy:'time':true" class="revision">
|
||||
<a style="cursor:pointer" ng-click="getNoteRevisionForReview(revision, 'second')">
|
||||
<span style="display: block;">
|
||||
<strong>{{revision.message}}</strong>
|
||||
</span>
|
||||
<span class="revisionDate">
|
||||
<em>{{formatRevisionDate(revision.time)}}</em>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="diffPanel">
|
||||
<div class="panel-group">
|
||||
<div class="panel" data-ng-repeat="p in (mergeNoteRevisionsForCompare ? mergeNoteRevisionsForCompare.added : [])">
|
||||
<div class="panel-heading">
|
||||
<a class="revisions-comparator-link" data-toggle="collapse" data-parent="#diffPanel" href="#{{p.paragraph.id}}">
|
||||
<h4 class="panel-title">
|
||||
{{p.paragraph.id}}<strong style="padding: 5px;" ng-if="p.paragraph.title">({{p.paragraph.title}})</strong>
|
||||
<i class="revisions-comparator-status color-green">added</i>
|
||||
<i class="revisions-comparator-forst-string">{{p.firstString}}</i>
|
||||
</h4>
|
||||
</a>
|
||||
</div>
|
||||
<div id="{{p.paragraph.id}}" class="panel-collapse collapse">
|
||||
<span class="revisions-comparator-code-panel-title">Revision: <strong>{{currentFirstRevisionForCompare}}</strong></span>
|
||||
<span class="revisions-comparator-code-panel-title">Revision: <strong>{{currentSecondRevisionForCompare}}</strong></span>
|
||||
<pre class="revisions-comparator-code-panel"></pre>
|
||||
<pre class="revisions-comparator-code-panel color-green">{{p.paragraph.text}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel" data-ng-repeat="p in (mergeNoteRevisionsForCompare ? mergeNoteRevisionsForCompare.deleted : [])">
|
||||
<div class="panel-heading">
|
||||
<a class="revisions-comparator-link" data-toggle="collapse" data-parent="#diffPanel" href="#{{p.paragraph.id}}">
|
||||
<h4 class="panel-title">
|
||||
{{p.paragraph.id}} <strong style="padding: 5px;" ng-if="p.paragraph.title">({{p.paragraph.title}})</strong>
|
||||
<i class="revisions-comparator-status color-red">deleted</i>
|
||||
<i class="revisions-comparator-forst-string">{{p.firstString}}</i>
|
||||
</h4>
|
||||
</a>
|
||||
</div>
|
||||
<div id="{{p.paragraph.id}}" class="panel-collapse collapse">
|
||||
<span class="revisions-comparator-code-panel-title">Revision: <strong>{{currentFirstRevisionForCompare}}</strong></span>
|
||||
<span class="revisions-comparator-code-panel-title">Revision: <strong>{{currentSecondRevisionForCompare}}</strong></span>
|
||||
<pre class="revisions-comparator-code-panel color-red">{{p.paragraph.text}}</pre>
|
||||
<pre class="revisions-comparator-code-panel"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel" data-ng-repeat="p in (mergeNoteRevisionsForCompare ? mergeNoteRevisionsForCompare.compared : [])">
|
||||
<div class="panel-heading">
|
||||
<a class="revisions-comparator-link" data-toggle="collapse" data-parent="#diffPanel" href="#{{p.paragraph.id}}">
|
||||
<h4 class="panel-title">
|
||||
{{p.paragraph.id}} <strong style="padding: 5px;" ng-if="p.paragraph.title">({{p.paragraph.title}})</strong>
|
||||
<i class="revisions-comparator-status" ng-show="p.identical">contents are identical</i>
|
||||
<i class="revisions-comparator-status color-orange" ng-show="!(p.identical)">there are differences</i>
|
||||
<i class="revisions-comparator-forst-string">{{p.firstString}}</i>
|
||||
</h4>
|
||||
</a>
|
||||
</div>
|
||||
<div id="{{p.paragraph.id}}" class="panel-collapse collapse">
|
||||
<span class="revisions-comparator-code-panel-title">Revision: <strong>{{currentFirstRevisionForCompare}}</strong></span>
|
||||
<span class="revisions-comparator-code-panel-title">Diff with revision: <strong>{{currentSecondRevisionForCompare}}</strong></span>
|
||||
<pre class="revisions-comparator-code-panel">{{p.paragraph.text}}</pre>
|
||||
<pre class="revisions-comparator-code-panel" ng-bind-html="p.diff"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -138,6 +138,8 @@ function WebsocketEventFactory ($rootScope, $websocket, $location, baseUrlSrv) {
|
|||
$rootScope.$broadcast('listRevisionHistory', data)
|
||||
} else if (op === 'NOTE_REVISION') {
|
||||
$rootScope.$broadcast('noteRevision', data)
|
||||
} else if (op === 'NOTE_REVISION_FOR_COMPARE') {
|
||||
$rootScope.$broadcast('noteRevisionForCompare', data)
|
||||
} else if (op === 'INTERPRETER_BINDINGS') {
|
||||
$rootScope.$broadcast('interpreterBindings', data)
|
||||
} else if (op === 'ERROR_INFO') {
|
||||
|
|
|
|||
|
|
@ -294,6 +294,17 @@ function WebsocketMessageService ($rootScope, websocketEvents) {
|
|||
})
|
||||
},
|
||||
|
||||
getNoteByRevisionForCompare: function (noteId, revisionId, position) {
|
||||
websocketEvents.sendNewEvent({
|
||||
op: 'NOTE_REVISION_FOR_COMPARE',
|
||||
data: {
|
||||
noteId: noteId,
|
||||
revisionId: revisionId,
|
||||
position: position
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getEditorSetting: function (paragraphId, replName) {
|
||||
websocketEvents.sendNewEvent({
|
||||
op: 'EDITOR_SETTING',
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ limitations under the License.
|
|||
<script src="bower_components/MathJax/MathJax.js"></script>
|
||||
<script src="bower_components/clipboard/dist/clipboard.js"></script>
|
||||
<script src="bower_components/ngclipboard/dist/ngclipboard.js"></script>
|
||||
<script src="bower_components/jsdiff/diff.js"></script>
|
||||
<!-- endbower -->
|
||||
<!-- endbuild -->
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -152,6 +152,10 @@ public class Message implements JsonSerializable {
|
|||
SET_NOTE_REVISION, // [c-s] set current notebook head to this revision
|
||||
// @param noteId
|
||||
// @param revisionId
|
||||
NOTE_REVISION_FOR_COMPARE, // [c-s] get certain revision of note for compare
|
||||
// @param noteId
|
||||
// @param revisionId
|
||||
// @param position
|
||||
APP_APPEND_OUTPUT, // [s-c] append output
|
||||
APP_UPDATE_OUTPUT, // [s-c] update (replace) output
|
||||
APP_LOAD, // [s-c] on app load
|
||||
|
|
|
|||
Loading…
Reference in a new issue