Merge pull request #453 from sharelatex/ja-cut-and-paste-comments

Move comments when cutting and pasting
This commit is contained in:
James Allen 2017-03-20 13:49:51 +00:00 committed by GitHub
commit 131d710a83
2 changed files with 88 additions and 11 deletions

View file

@ -73,16 +73,24 @@ define [
_scrollTimeout = null _scrollTimeout = null
, 200 , 200
@_resetCutState()
onCut = () => @onCut()
onPaste = () => @onPaste()
bindToAce = () => bindToAce = () =>
@editor.on "changeSelection", onChangeSelection @editor.on "changeSelection", onChangeSelection
@editor.on "change", onChangeSelection # Selection also moves with updates elsewhere in the document @editor.on "change", onChangeSelection # Selection also moves with updates elsewhere in the document
@editor.on "changeSession", onChangeSession @editor.on "changeSession", onChangeSession
@editor.on "cut", onCut
@editor.on "paste", onPaste
@editor.renderer.on "resize", onResize @editor.renderer.on "resize", onResize
unbindFromAce = () => unbindFromAce = () =>
@editor.off "changeSelection", onChangeSelection @editor.off "changeSelection", onChangeSelection
@editor.off "change", onChangeSelection @editor.off "change", onChangeSelection
@editor.off "changeSession", onChangeSession @editor.off "changeSession", onChangeSession
@editor.off "cut", onCut
@editor.off "paste", onPaste
@editor.renderer.off "resize", onResize @editor.renderer.off "resize", onResize
@$scope.$watch "trackChangesEnabled", (enabled) => @$scope.$watch "trackChangesEnabled", (enabled) =>
@ -244,6 +252,49 @@ define [
@_onCommentAdded(comment) @_onCommentAdded(comment)
@broadcastChange() @broadcastChange()
_resetCutState: () ->
@_cutState = {
text: null
comments: []
docId: null
}
onCut: () ->
@_resetCutState()
selection = @editor.getSelectionRange()
selection_start = @_aceRangeToShareJs(selection.start)
selection_end = @_aceRangeToShareJs(selection.end)
@_cutState.text = @editor.getSelectedText()
@_cutState.docId = @$scope.docId
for comment in @rangesTracker.comments
comment_start = comment.op.p
comment_end = comment_start + comment.op.c.length
if selection_start <= comment_start and comment_end <= selection_end
@_cutState.comments.push {
offset: comment.op.p - selection_start
text: comment.op.c
comment: comment
}
onPaste: () =>
@editor.once "change", (change) =>
return if change.action != "insert"
pasted_text = change.lines.join("\n")
paste_offset = @_aceRangeToShareJs(change.start)
# We have to wait until the change has been processed by the range tracker,
# since if we move the ops into place beforehand, they will be moved again
# when the changes are processed by the range tracker. This ranges:dirty
# event is fired after the doc has applied the changes to the range tracker.
@$scope.sharejsDoc.on "ranges:dirty.paste", () =>
@$scope.sharejsDoc.off "ranges:dirty.paste" # Doc event emitter uses namespaced events
if pasted_text == @_cutState.text and @$scope.docId == @_cutState.docId
for {comment, offset, text} in @_cutState.comments
op = { c: text, p: paste_offset + offset, t: comment.id }
@$scope.sharejsDoc.submitOp op # Resubmitting an existing comment op (by thread id) will move it
@_resetCutState()
# Check that comments still match text. Will throw error if not.
@rangesTracker.validate(@editor.getValue())
checkMapping: () -> checkMapping: () ->
# TODO: reintroduce this check # TODO: reintroduce this check
session = @editor.getSession() session = @editor.getSession()

View file

@ -1,3 +1,6 @@
# This file is shared between document-updater and web, so that the server and client share
# an identical track changes implementation. Do not edit it directly in web or document-updater,
# instead edit it at https://github.com/sharelatex/ranges-tracker, where it has a suite of tests
load = () -> load = () ->
class RangesTracker class RangesTracker
# The purpose of this class is to track a set of inserts and deletes to a document, like # The purpose of this class is to track a set of inserts and deletes to a document, like
@ -78,6 +81,13 @@ load = () ->
@comments = @comments.filter (c) -> c.id != comment_id @comments = @comments.filter (c) -> c.id != comment_id
@_markAsDirty comment, "comment", "removed" @_markAsDirty comment, "comment", "removed"
moveCommentId: (comment_id, position, text) ->
for comment in @comments
if comment.id == comment_id
comment.op.p = position
comment.op.c = text
@_markAsDirty comment, "comment", "moved"
getChange: (change_id) -> getChange: (change_id) ->
change = null change = null
for c in @changes for c in @changes
@ -90,6 +100,18 @@ load = () ->
change = @getChange(change_id) change = @getChange(change_id)
return if !change? return if !change?
@_removeChange(change) @_removeChange(change)
validate: (text) ->
for change in @changes
if change.op.i?
content = text.slice(change.op.p, change.op.p + change.op.i.length)
if content != change.op.i
throw new Error("Change (#{JSON.stringify(change)}) doesn't match text (#{JSON.stringify(content)})")
for comment in @comments
content = text.slice(comment.op.p, comment.op.p + comment.op.c.length)
if content != comment.op.c
throw new Error("Comment (#{JSON.stringify(comment)}) doesn't match text (#{JSON.stringify(content)})")
return true
applyOp: (op, metadata = {}) -> applyOp: (op, metadata = {}) ->
metadata.ts ?= new Date() metadata.ts ?= new Date()
@ -110,17 +132,21 @@ load = () ->
@applyOp(op, metadata) @applyOp(op, metadata)
addComment: (op, metadata) -> addComment: (op, metadata) ->
# TODO: Don't allow overlapping comments? existing = @getComment(op.t)
@comments.push comment = { if existing?
id: op.t or @newId() @moveCommentId(op.t, op.p, op.c)
op: # Copy because we'll modify in place return existing
c: op.c else
p: op.p @comments.push comment = {
t: op.t id: op.t or @newId()
metadata op: # Copy because we'll modify in place
} c: op.c
@_markAsDirty comment, "comment", "added" p: op.p
return comment t: op.t
metadata
}
@_markAsDirty comment, "comment", "added"
return comment
applyInsertToComments: (op) -> applyInsertToComments: (op) ->
for comment in @comments for comment in @comments