mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Display diffs
This commit is contained in:
parent
b696d93c72
commit
b7adaf9f87
10 changed files with 270 additions and 24 deletions
|
@ -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...
|
||||
|
||||
|
|
|
@ -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 :(
|
|
@ -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
|
||||
|
|
|
@ -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: () ->
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -121,6 +121,9 @@
|
|||
// .border-radius(3px);
|
||||
// }
|
||||
|
||||
.diff {
|
||||
margin-right: @changesListWidth;
|
||||
}
|
||||
|
||||
aside.change-list {
|
||||
border-left: 1px solid @toolbar-border-color;
|
||||
|
|
Loading…
Reference in a new issue