diff --git a/services/web/app/views/project/editor/editor.jade b/services/web/app/views/project/editor/editor.jade
index 47bf67b4ee..4fd79ab6d0 100644
--- a/services/web/app/views/project/editor/editor.jade
+++ b/services/web/app/views/project/editor/editor.jade
@@ -1,5 +1,5 @@
div.full-size(ng-show="ui.view == 'editor'")
- .loading(ng-show="!editor.sharejs_doc || editor.opening")
+ .loading-panel(ng-show="!editor.sharejs_doc || editor.opening")
i.fa.fa-spin.fa-refresh
| Loading...
diff --git a/services/web/app/views/project/editor/track-changes.jade b/services/web/app/views/project/editor/track-changes.jade
index 60eaf78755..682c8a4e45 100644
--- a/services/web/app/views/project/editor/track-changes.jade
+++ b/services/web/app/views/project/editor/track-changes.jade
@@ -52,4 +52,20 @@ div#trackChanges(ng-show="ui.view == 'track-changes'")
span.doc {{ doc.entity.name }}
div.users
span.user(ng-repeat="user in update.meta.users")
- | {{user.first_name}} {{user.last_name}}
\ No newline at end of file
+ | {{user.first_name}} {{user.last_name}}
+
+ .diff.full-size
+ .diff-editor(
+ ace-editor,
+ ng-show="!!trackChanges.diff && !trackChanges.diff.loading",
+ theme="settings.theme",
+ font-size="settings.fontSize",
+ text="trackChanges.diff.text",
+ annotations="trackChanges.diff.annotations",
+ read-only="true"
+ )
+ .loading-panel(ng-show="trackChanges.diff.loading")
+ i.fa.fa-spin.fa-refresh
+ | Loading...
+ .error-panel(ng-show="trackChanges.diff.error")
+ .alert.alert-danger Sorry, something went wrong :(
\ No newline at end of file
diff --git a/services/web/public/coffee/app/ide/editor/EditorManager.coffee b/services/web/public/coffee/app/ide/editor/EditorManager.coffee
index b20d9e718b..91ab8a7212 100644
--- a/services/web/public/coffee/app/ide/editor/EditorManager.coffee
+++ b/services/web/public/coffee/app/ide/editor/EditorManager.coffee
@@ -13,7 +13,7 @@ define [
}
@$scope.$on "entity:selected", (event, entity) =>
- if (entity.type == "doc")
+ if (@$scope.ui.view == "editor" and entity.type == "doc")
@openDoc(entity)
initialized = false
diff --git a/services/web/public/coffee/app/ide/editor/annotations/AnnotationsManager.coffee b/services/web/public/coffee/app/ide/editor/annotations/AnnotationsManager.coffee
index 2d10114edb..bbfb9ded2c 100644
--- a/services/web/public/coffee/app/ide/editor/annotations/AnnotationsManager.coffee
+++ b/services/web/public/coffee/app/ide/editor/annotations/AnnotationsManager.coffee
@@ -47,6 +47,26 @@ define [
snapToStartOfRange: true
}
@_drawCursor(annotation, colorScheme)
+ else if annotation.highlight?
+ @labels.push {
+ text: annotation.label
+ range: new Range(
+ annotation.highlight.start.row, annotation.highlight.start.column,
+ annotation.highlight.end.row, annotation.highlight.end.column
+ )
+ colorScheme: colorScheme
+ }
+ @_drawHighlight(annotation, colorScheme)
+ else if annotation.strikeThrough?
+ @labels.push {
+ text: annotation.label
+ range: new Range(
+ annotation.strikeThrough.start.row, annotation.strikeThrough.start.column,
+ annotation.strikeThrough.end.row, annotation.strikeThrough.end.column
+ )
+ colorScheme: colorScheme
+ }
+ @_drawStrikeThrough(annotation, colorScheme)
showAnnotationLabels: (position) ->
labelToShow = null
@@ -104,7 +124,7 @@ define [
@markerIds.push @editor.getSession().addMarker new Range(
annotation.cursor.row, annotation.cursor.column,
annotation.cursor.row, annotation.cursor.column + 1
- ), "remote-cursor", (html, range, left, top, config) ->
+ ), "annotation remote-cursor", (html, range, left, top, config) ->
div = """
+ @_addMarkerWithCustomStyle(
+ new Range(
+ annotation.highlight.start.row, annotation.highlight.start.column,
+ annotation.highlight.end.row, annotation.highlight.end.column + 1
+ ),
+ "annotation highlight",
+ false,
+ "background-color: #{colorScheme.highlightBackgroundColor}"
+ )
+
+ _drawStrikeThrough: (annotation, colorScheme) ->
+ lineHeight = @editor.renderer.lineHeight
+ @_addMarkerWithCustomStyle(
+ new Range(
+ annotation.strikeThrough.start.row, annotation.strikeThrough.start.column,
+ annotation.strikeThrough.end.row, annotation.strikeThrough.end.column + 1
+ ),
+ "annotation strike-through-background",
+ false,
+ "background-color: #{colorScheme.strikeThroughBackgroundColor}"
+ )
+ @_addMarkerWithCustomStyle(
+ new Range(
+ annotation.strikeThrough.start.row, annotation.strikeThrough.start.column,
+ annotation.strikeThrough.end.row, annotation.strikeThrough.end.column + 1
+ ),
+ "annotation strike-through-foreground",
+ true,
+ """
+ height: #{Math.round(lineHeight/2) + 2}px;
+ border-bottom: 2px solid #{colorScheme.strikeThroughForegroundColor};
+ """
+ )
+
+ _addMarkerWithCustomStyle: (range, klass, foreground, style) ->
+ if foreground?
+ markerLayer = @editor.renderer.$markerBack
+ else
+ markerLayer = @editor.renderer.$markerFront
+
+ @markerIds.push @editor.getSession().addMarker range, klass, (html, range, left, top, config) ->
+ if range.isMultiLine()
+ markerLayer.drawTextMarker(html, range, klass, config, style)
+ else
+ markerLayer.drawSingleLineMarker(html, range, "#{klass} ace_start", config, 0, style)
+ , foreground
+
_getColorScheme: (hue) ->
if @_isDarkTheme()
return {
cursor: "hsl(#{hue}, 100%, 50%)"
labelBackgroundColor: "hsl(#{hue}, 100%, 50%)"
+ highlightBackgroundColor: "hsl(#{hue}, 100%, 28%);"
+ strikeThroughBackgroundColor: "hsl(#{hue}, 100%, 20%);"
+ strikeThroughForegroundColor: "hsl(#{hue}, 100%, 60%);"
}
else
return {
cursor: "hsl(#{hue}, 100%, 50%)"
labelBackgroundColor: "hsl(#{hue}, 100%, 50%)"
+ highlightBackgroundColor: "hsl(#{hue}, 70%, 85%);"
+ strikeThroughBackgroundColor: "hsl(#{hue}, 70%, 95%);"
+ strikeThroughForegroundColor: "hsl(#{hue}, 70%, 40%);"
}
_isDarkTheme: () ->
diff --git a/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee
index 3c3c601d91..246a6574f6 100644
--- a/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee
+++ b/services/web/public/coffee/app/ide/editor/directives/aceEditor.coffee
@@ -27,6 +27,8 @@ define [
spellCheckLanguage: "="
cursorPosition: "="
annotations: "="
+ text: "="
+ readOnly: "="
}
link: (scope, element, attrs) ->
# Don't freak out if we're already in an apply callback
@@ -59,7 +61,8 @@ define [
editor.on "changeSelection", () ->
cursor = editor.getCursorPosition()
scope.$apply () ->
- scope.cursorPosition = cursor
+ if scope.cursorPosition?
+ scope.cursorPosition = cursor
scope.$watch "theme", (value) ->
editor.setTheme("ace/theme/#{value}")
@@ -85,6 +88,13 @@ define [
if sharejs_doc?
attachToAce(sharejs_doc)
+ scope.$watch "text", (text) ->
+ if text?
+ editor.setValue(text, -1)
+
+ scope.$watch "readOnly", (value) ->
+ editor.setReadOnly !!value
+
attachToAce = (sharejs_doc) ->
lines = sharejs_doc.getSnapshot().split("\n")
editor.setSession(new EditSession(lines))
diff --git a/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee b/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee
index 4bb3a4cafa..1ff4b0b3dd 100644
--- a/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee
+++ b/services/web/public/coffee/app/ide/online-users/OnlineUsersManager.coffee
@@ -29,7 +29,7 @@ define [
continue if !doc_id?
@$scope.onlineUserCursorAnnotations[doc_id] ||= []
@$scope.onlineUserCursorAnnotations[doc_id].push {
- text: client.name
+ label: client.name
cursor:
row: client.row
column: client.column
diff --git a/services/web/public/coffee/app/ide/track-changes/TrackChangesListController.coffee b/services/web/public/coffee/app/ide/track-changes/TrackChangesListController.coffee
index e5a849a919..5823553624 100644
--- a/services/web/public/coffee/app/ide/track-changes/TrackChangesListController.coffee
+++ b/services/web/public/coffee/app/ide/track-changes/TrackChangesListController.coffee
@@ -7,7 +7,7 @@ define [
$scope.recalculateSelectedUpdates = () ->
beforeSelection = true
afterSelection = false
- inSelection = false
+ $scope.trackChanges.selection.updates = []
for update in $scope.trackChanges.updates
if update.selectedTo
inSelection = true
@@ -17,6 +17,9 @@ define [
update.inSelection = inSelection
update.afterSelection = afterSelection
+ if inSelection
+ $scope.trackChanges.selection.updates.push update
+
if update.selectedFrom
inSelection = false
afterSelection = true
@@ -31,8 +34,6 @@ define [
if update.hoverSelectedTo
hoverSelectedTo = true
- console.log "RECALCULATING HOVER", hoverSelectedFrom, hoverSelectedTo
-
if hoverSelectedFrom
# We want to 'hover select' everything between hoverSelectedFrom and selectedTo
inHoverSelection = false
@@ -62,19 +63,19 @@ define [
]
App.controller "TrackChangesListItemController", ["$scope", ($scope) ->
- $scope.$watch "update.selectedFrom", (selectedFrom) ->
- if selectedFrom?
- if selectedFrom
- for update in $scope.trackChanges.updates
- update.selectedFrom = false unless update == $scope.update
- $scope.recalculateSelectedUpdates()
+ $scope.$watch "update.selectedFrom", (selectedFrom, oldSelectedFrom) ->
+ if selectedFrom
+ for update in $scope.trackChanges.updates
+ update.selectedFrom = false unless update == $scope.update
+ if selectedFrom != oldSelectedFrom
+ $scope.recalculateSelectedUpdates()
- $scope.$watch "update.selectedTo", (selectedTo) ->
- if selectedTo?
- if selectedTo
- for update in $scope.trackChanges.updates
- update.selectedTo = false unless update == $scope.update
- $scope.recalculateSelectedUpdates()
+ $scope.$watch "update.selectedTo", (selectedTo, oldSelectedTo) ->
+ if selectedTo
+ for update in $scope.trackChanges.updates
+ update.selectedTo = false unless update == $scope.update
+ if selectedTo != oldSelectedTo
+ $scope.recalculateSelectedUpdates()
$scope.select = () ->
$scope.update.selectedTo = true
diff --git a/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee b/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee
index 218e243132..1bf5962587 100644
--- a/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee
+++ b/services/web/public/coffee/app/ide/track-changes/TrackChangesManager.coffee
@@ -7,6 +7,17 @@ define [
updates: []
nextBeforeTimestamp: null
atEnd: false
+ selection: {
+ updates: []
+ doc: null
+ range: {
+ fromV: null
+ toV: null
+ start_ts: null
+ end_ts: null
+ }
+ }
+ diff: null
}
@$scope.toggleTrackChanges = () =>
@@ -18,6 +29,16 @@ define [
@$scope.$on "file-tree:initialized", () =>
@fetchNextBatchOfChanges()
+ @$scope.$watch "trackChanges.selection.updates", () =>
+ @$scope.trackChanges.selection.range = @_calculateRangeFromSelection()
+ @reloadDiff()
+
+ @$scope.$on "entity:selected", (event, entity) =>
+ if (@$scope.ui.view == "track-changes") and (entity.type == "doc")
+ @$scope.trackChanges.selection.doc = entity
+ @$scope.trackChanges.selection.range = @_calculateRangeFromSelection()
+ @reloadDiff()
+
BATCH_SIZE: 4
fetchNextBatchOfChanges: () ->
url = "/project/#{@ide.project_id}/updates?min_count=#{@BATCH_SIZE}"
@@ -31,6 +52,90 @@ define [
if !data.nextBeforeTimestamp?
@$scope.trackChanges.atEnd = true
+ reloadDiff: () ->
+ console.log "Checking if diff has changed"
+
+ diff = @$scope.trackChanges.diff
+ {updates, doc} = @$scope.trackChanges.selection
+ {fromV, toV} = @$scope.trackChanges.selection.range
+
+ return if !doc?
+
+ return if diff? and
+ diff.doc == doc and
+ diff.fromV == fromV and
+ diff.toV == toV
+
+ console.log "Loading diff", fromV, toV, doc?.id
+
+ @$scope.trackChanges.diff = diff = {
+ fromV: fromV
+ toV: toV
+ doc: doc
+ loading: true
+ error: false
+ }
+
+ url = "/project/#{@$scope.project_id}/doc/#{diff.doc.id}/diff"
+ if diff.fromV? and diff.toV?
+ url += "?from=#{diff.fromV}&to=#{diff.toV}"
+
+ @ide.$http
+ .get(url)
+ .success (data) =>
+ diff.loading = false
+ {text, annotations} = @_parseDiff(data)
+ diff.text = text
+ diff.annotations = annotations
+ .error () ->
+ diff.loading = false
+ diff.error = true
+
+ _parseDiff: (diff) ->
+ row = 0
+ column = 0
+ annotations = []
+ text = ""
+ for entry, i in diff.diff or []
+ content = entry.u or entry.i or entry.d
+ content ||= ""
+ text += content
+ lines = content.split("\n")
+ startRow = row
+ startColumn = column
+ if lines.length > 1
+ endRow = startRow + lines.length - 1
+ endColumn = lines[lines.length - 1].length
+ else
+ endRow = startRow
+ endColumn = startColumn + lines[0].length
+ row = endRow
+ column = endColumn
+
+ range = {
+ start:
+ row: startRow
+ column: startColumn
+ end:
+ row: endRow
+ column: endColumn
+ }
+
+ if entry.i?
+ annotations.push {
+ label: entry.meta.user.first_name
+ highlight: range
+ hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user.id)
+ }
+ else if entry.d?
+ annotations.push {
+ label: entry.meta.user.first_name
+ strikeThrough: range
+ hue: @ide.onlineUsersManager.getHueForUserId(entry.meta.user.id)
+ }
+
+ return {text, annotations}
+
_loadUpdates: (updates = []) ->
previousUpdate = @$scope.trackChanges.updates[@$scope.trackChanges.updates.length - 1]
@@ -52,3 +157,28 @@ define [
@$scope.trackChanges.updates =
@$scope.trackChanges.updates.concat(updates)
+
+ _calculateRangeFromSelection: () ->
+ fromV = toV = start_ts = end_ts = null
+
+ selected_doc_id = @$scope.trackChanges.selection.doc?.id
+
+ for update in @$scope.trackChanges.selection.updates or []
+ console.log "Checking update", update
+ for doc_id, doc of update.docs
+ console.log "Checking doc", doc_id, selected_doc_id, doc.fromV, doc.toV
+ if doc_id == selected_doc_id
+ console.log "Doc matches"
+ if fromV? and toV?
+ fromV = Math.min(fromV, doc.fromV)
+ toV = Math.max(toV, doc.toV)
+ start_ts = Math.min(start_ts, update.meta.start_ts)
+ end_ts = Math.max(end_ts, update.meta.end_ts)
+ else
+ fromV = doc.fromV
+ toV = doc.toV
+ start_ts = update.meta.start_ts
+ end_ts = update.meta.end_ts
+ break
+
+ return {fromV, toV, start_ts, end_ts}
diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less
index 20827b8724..aeaefac6da 100644
--- a/services/web/public/stylesheets/app/editor.less
+++ b/services/web/public/stylesheets/app/editor.less
@@ -29,7 +29,7 @@
.full-size;
}
-.loading {
+.loading-panel {
.full-size;
padding-top: 10rem;
font-family: @font-family-serif;
@@ -37,6 +37,16 @@
background-color: #fafafa;
}
+.error-panel {
+ .full-size;
+ padding: @line-height-computed;
+ background-color: #fafafa;
+ .alert {
+ max-width: 400px;
+ margin: auto;
+ }
+}
+
// The internal components of the aceEditor directive
.ace-editor-wrapper {
.full-size;
@@ -58,8 +68,6 @@
background-position: bottom left;
}
.remote-cursor {
- position: absolute;
- z-index: 2;
border-left: 2px solid transparent;
.nubbin {
height: 5px;
@@ -76,6 +84,10 @@
color: white;
font-weight: 700;
}
+ .annotation {
+ position: absolute;
+ z-index: 2;
+ }
}
.ui-layout-resizer {
diff --git a/services/web/public/stylesheets/app/editor/track-changes.less b/services/web/public/stylesheets/app/editor/track-changes.less
index c917170c3e..b0f7616540 100644
--- a/services/web/public/stylesheets/app/editor/track-changes.less
+++ b/services/web/public/stylesheets/app/editor/track-changes.less
@@ -121,6 +121,9 @@
// .border-radius(3px);
// }
+ .diff {
+ margin-right: @changesListWidth;
+ }
aside.change-list {
border-left: 1px solid @toolbar-border-color;