mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Move comments when cutting and pasting
This commit is contained in:
parent
8f434cdd8b
commit
6c1a15a698
2 changed files with 89 additions and 11 deletions
|
@ -73,16 +73,24 @@ define [
|
|||
_scrollTimeout = null
|
||||
, 200
|
||||
|
||||
@_resetCutState()
|
||||
onCut = () => @onCut()
|
||||
onPaste = () => @onPaste()
|
||||
|
||||
bindToAce = () =>
|
||||
@editor.on "changeSelection", onChangeSelection
|
||||
@editor.on "change", onChangeSelection # Selection also moves with updates elsewhere in the document
|
||||
@editor.on "changeSession", onChangeSession
|
||||
@editor.on "cut", onCut
|
||||
@editor.on "paste", onPaste
|
||||
@editor.renderer.on "resize", onResize
|
||||
|
||||
unbindFromAce = () =>
|
||||
@editor.off "changeSelection", onChangeSelection
|
||||
@editor.off "change", onChangeSelection
|
||||
@editor.off "changeSession", onChangeSession
|
||||
@editor.off "cut", onCut
|
||||
@editor.off "paste", onPaste
|
||||
@editor.renderer.off "resize", onResize
|
||||
|
||||
@$scope.$watch "trackChangesEnabled", (enabled) =>
|
||||
|
@ -244,6 +252,50 @@ define [
|
|||
@_onCommentAdded(comment)
|
||||
@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)
|
||||
console.log "PASTE", pasted_text, paste_offset
|
||||
# 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: () ->
|
||||
# TODO: reintroduce this check
|
||||
session = @editor.getSession()
|
||||
|
|
|
@ -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 = () ->
|
||||
class RangesTracker
|
||||
# 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
|
||||
@_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) ->
|
||||
change = null
|
||||
for c in @changes
|
||||
|
@ -90,6 +100,18 @@ load = () ->
|
|||
change = @getChange(change_id)
|
||||
return if !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 = {}) ->
|
||||
metadata.ts ?= new Date()
|
||||
|
@ -110,17 +132,21 @@ load = () ->
|
|||
@applyOp(op, metadata)
|
||||
|
||||
addComment: (op, metadata) ->
|
||||
# TODO: Don't allow overlapping comments?
|
||||
@comments.push comment = {
|
||||
id: op.t or @newId()
|
||||
op: # Copy because we'll modify in place
|
||||
c: op.c
|
||||
p: op.p
|
||||
t: op.t
|
||||
metadata
|
||||
}
|
||||
@_markAsDirty comment, "comment", "added"
|
||||
return comment
|
||||
existing = @getComment(op.t)
|
||||
if existing?
|
||||
@moveCommentId(op.t, op.p, op.c)
|
||||
return existing
|
||||
else
|
||||
@comments.push comment = {
|
||||
id: op.t or @newId()
|
||||
op: # Copy because we'll modify in place
|
||||
c: op.c
|
||||
p: op.p
|
||||
t: op.t
|
||||
metadata
|
||||
}
|
||||
@_markAsDirty comment, "comment", "added"
|
||||
return comment
|
||||
|
||||
applyInsertToComments: (op) ->
|
||||
for comment in @comments
|
||||
|
|
Loading…
Reference in a new issue