mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #453 from sharelatex/ja-cut-and-paste-comments
Move comments when cutting and pasting
This commit is contained in:
commit
131d710a83
2 changed files with 88 additions and 11 deletions
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
@ -91,6 +101,18 @@ load = () ->
|
||||||
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()
|
||||||
# Apply an op that has been applied to the document to our changes to keep them up to date
|
# Apply an op that has been applied to the document to our changes to keep them up to 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
|
||||||
|
|
Loading…
Reference in a new issue