James Allen 12 лет назад
Родитель
Сommit
b7adaf9f87

+ 1 - 1
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...
 

+ 17 - 1
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}}
+							| {{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 :(

+ 1 - 1
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

+ 75 - 1
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 = """
 					<div
 						class='remote-cursor custom ace_start'
@@ -116,16 +136,70 @@ define [
 				html.push div
 			, true
 
+		_drawHighlight: (annotation, colorScheme) ->
+			@_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: () ->

+ 11 - 1
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))

+ 1 - 1
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

+ 16 - 15
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

+ 130 - 0
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}

+ 15 - 3
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 {

+ 3 - 0
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;