diff --git a/app/components/SimpleEditor/elements/track_change/TrackChangesProvider.js b/app/components/SimpleEditor/elements/track_change/TrackChangesProvider.js index 2666404a7eed6273a5fee7a0d9bb8552c444209c..f9f0aa2b4989f0c1f58e16fb54e304d1af1e55ef 100644 --- a/app/components/SimpleEditor/elements/track_change/TrackChangesProvider.js +++ b/app/components/SimpleEditor/elements/track_change/TrackChangesProvider.js @@ -1,16 +1,18 @@ import { + clone, each, filter, find, keys, - last - // max, - // minBy + last, + maxBy, + minBy } from 'lodash' import { createAnnotation, deleteCharacter as deleteChar, + deleteNode, deleteSelection as deleteSel, expandAnnotation, truncateAnnotation @@ -60,7 +62,7 @@ class TrackChangesProvider { const isOnLeftEdge = this.isOnLeftEdge(annotation) this.insertText(event) - if (isOnLeftEdge) this.expandAnnotationToTheLeft(annotation) + if (isOnLeftEdge) this.expandAnnotationToDirection(annotation) return } @@ -94,13 +96,12 @@ class TrackChangesProvider { let { selection } = options const notOnTrack = this.isNotOnTrackAnnotation() - // const isOnAdd = this.isOnAnnotation('add') const isOnDelete = this.isOnAnnotation('delete') if (notOnTrack) return this.deleteSelectedAndCreateAddition(options) - // delete all additions of the same user and shorten selection by the - // number of deleted characters + // delete all additions of the same user and + // shorten selection by the number of deleted characters const shortenBy = this.deleteAllOwnAdditions(selection) const startOffset = selection.startOffset const endOffset = selection.endOffset - shortenBy @@ -132,46 +133,126 @@ class TrackChangesProvider { return this.handleAddCollapsed(options) } - deleteSelectedAndCreateAddition (options) { - let { selection } = options + handleDelete (options) { + const { key, move } = options + const isSelectionCollapsed = this.isSelectionCollapsed() - this.createDeleteAnnotation(selection) - this.moveCursorTo('end', selection) + options.direction = { + cursorTo: (move === 'left') ? 'start' : 'end', + key: key, + move: move + } - // selection is now collapsed, so handle it as collapsed - this.handleAddCollapsed(options) + if (isSelectionCollapsed) return this.handleDeleteCollapsed(options) + if (!isSelectionCollapsed) return this.handleDeleteNonCollapsed(options) } - insertCharacterWithAddAnnotation (options) { - const { event } = options - this.insertText(event) + handleDeleteCollapsed (options) { + const { direction } = options - // TODO -- watch it with additions by other users - this.createAdditionAnnotationOnLastChar() - } + const notOnTrack = this.isNotOnTrackAnnotation() + const isOnAdd = this.isOnAnnotation('add') + const isOnDelete = this.isOnAnnotation('delete') - createAdditionAnnotationOnLastChar () { - const selection = this.setSelectionPlusOne('left') - this.createAddAnnotation(selection) - this.moveCursorTo('end') + if (notOnTrack) return this.selectCharacterAndMarkDeleted(options) + + if (isOnAdd) { + console.log('delete collapsed on add') + const annotation = this.getAnnotationByStatus('add') + const isOnLeftEdge = this.isOnLeftEdge(annotation) + const isOnRightEdge = this.isOnRightEdge(annotation) + const key = direction.key + + // when on own additions, simply delete the character + // unless it is on the edge: then make a deletion annotation + + // TODO -- watch it for different users + if ( + (isOnLeftEdge && key === 'BACKSPACE') || + (isOnRightEdge && key === 'DELETE') + ) { + return this.selectCharacterAndMarkDeleted(options) + } + + return this.deleteCharacter(direction.move) + } + + if (isOnDelete) { + const annotation = this.getAnnotationByStatus('delete') + const isOnLeftEdge = this.isOnLeftEdge(annotation) + const isOnRightEdge = this.isOnRightEdge(annotation) + const key = direction.key + + let moveOnly, point + + if (!isOnLeftEdge && !isOnRightEdge) { + point = annotation[direction.cursorTo + 'Offset'] + moveOnly = true + } else if (isOnLeftEdge && key === 'DELETE') { + point = annotation.endOffset + moveOnly = true + } else if (isOnRightEdge && key === 'BACKSPACE') { + point = annotation.startOffset + moveOnly = true + } + + if (moveOnly) return this.moveCursorTo(point) + return this.expandAnnotationToDirection(annotation, direction) + } } - expandAnnotationToTheLeft (annotation) { - const selection = this.setSelectionPlusOne('left') - this.expandTrackAnnotation(selection, annotation) - this.moveCursorTo('end') + handleDeleteNonCollapsed (options) { + const { direction } = options + let { selection } = options + + const notOnTrack = this.isNotOnTrackAnnotation() + const isOnDelete = this.isOnAnnotation('delete') + + if (notOnTrack) return this.markSelectionAsDeleted(options) + + const shortenBy = this.deleteAllOwnAdditions(selection) + const startOffset = selection.startOffset + const endOffset = selection.endOffset - shortenBy + this.updateSelection(selection, startOffset, endOffset) + + console.log('collapsed', selection.isCollapsed()) + if (selection.isCollapsed()) return this.handleDeleteCollapsed(options) + + if (isOnDelete) { + // console.log + const annotation = this.getAnnotationByStatus('delete') + const containedWithin = this.isSelectionContainedWithin(annotation) + + if (containedWithin) { + const point = annotation[direction.cursorTo + 'Offset'] + return this.moveCursorTo(point) + } + + // const selection = this.getSelection() + // this.expandTrackAnnotation(selection, annotation) + // + // const key = direction.key + // + // let point + // if (key === 'BACKSPACE') point = selection.startOffset + // if (key === 'DELETE') point = selection.endOffset + // this.moveCursorTo(point) + } + + options.selection = this.deleteOrMergeAllOwnDeletions(selection) + // console.log(this.getSelection()) + // options.selection = this.getSelection() + this.markSelectionAsDeleted(options) } - insertCharacterWithoutExpandingAnnotation (annotation, options) { - const { event } = options - let selection = options + /* + HANDLER COMMON FUNCTIONS + */ - this.insertText(event) - selection = this.setSelectionPlusOne('left') - this.truncateTrackAnnotation(selection, annotation) + createAdditionAnnotationOnLastChar () { + const selection = this.setSelectionPlusOne('left') + this.createAddAnnotation(selection) this.moveCursorTo('end') - - options.event = null } deleteAllOwnAdditions (selection) { @@ -199,137 +280,85 @@ class TrackChangesProvider { return shortenBy // return how much shorter the selection should now be } - handleDelete (options) { - const { key, move, status } = options - if (status !== 'delete') return - // console.log('deleting') - - // console.log(this.getSelection()) - - const isSelectionCollapsed = this.isSelectionCollapsed() - let selection + deleteOrMergeAllOwnDeletions (selection) { + const deletions = clone(this.getAllAnnotationsByStatus('delete')) + // filter by own deletions + console.log(selection, deletions) - const direction = { - cursorTo: (move === 'left') ? 'start' : 'end', - key: key, - move: move - } + // const originalSelection = clone(selection) + const selectionArray = [selection] - if (isSelectionCollapsed) { - const notOnTrack = this.isNotOnTrackAnnotation() - if (notOnTrack) { - selection = this.setSelectionPlusOne(direction.move) - this.createDeleteAnnotation(selection) - this.moveCursorTo(direction.cursorTo) - return - } else { - const isOnAdd = this.isOnAnnotation('add') - if (isOnAdd) { - const annotation = this.getAnnotationByStatus('add') - const isOnLeftEdge = this.isOnLeftEdge(annotation) - const isOnRightEdge = this.isOnRightEdge(annotation) - const key = direction.key - - if ( - (isOnLeftEdge && key === 'BACKSPACE') || - (isOnRightEdge && key === 'DELETE') - ) { - selection = this.setSelectionPlusOne(direction.move) - this.createDeleteAnnotation(selection) - this.moveCursorTo(direction.cursorTo) - return - } + each(deletions, (annotation) => { + const annotationSelection = annotation.getSelection() + const contained = selection.contains(annotationSelection) - this.deleteCharacter(direction.move) - return - } + if (!contained) { + selectionArray.push(annotationSelection) + } + console.log('fkdjsjf') + this.removeTrackAnnotation(annotation) + }) - const isOnDelete = this.isOnAnnotation('delete') - if (isOnDelete) { - const annotation = this.getAnnotationByStatus('delete') - const isOnLeftEdge = this.isOnLeftEdge(annotation) - const isOnRightEdge = this.isOnRightEdge(annotation) - const key = direction.key - - let moveOnly, point - - if (!isOnLeftEdge && !isOnRightEdge) { - point = annotation[direction.cursorTo + 'Offset'] - moveOnly = true - } else if (isOnLeftEdge && key === 'DELETE') { - point = annotation.endOffset - moveOnly = true - } else if (isOnRightEdge && key === 'BACKSPACE') { - point = annotation.startOffset - moveOnly = true - } + selection.startOffset = minBy(selectionArray, 'startOffset').startOffset + selection.endOffset = maxBy(selectionArray, 'endOffset').endOffset - if (moveOnly) return this.moveCursorTo(point) + return selection + // TODO + // this.updateSelection(selection, startOffset, endOffset) + } - selection = this.setSelectionPlusOne(direction.move) - this.expandTrackAnnotation(selection, annotation) - this.moveCursorTo(direction.cursorTo) - return - } - } - } else { - const notOnTrack = this.isNotOnTrackAnnotation() + deleteSelectedAndCreateAddition (options) { + let { selection } = options - if (notOnTrack) { - selection = this.getSelection() - this.createDeleteAnnotation(selection) - this.moveCursorTo(direction.cursorTo) - return - } + this.createDeleteAnnotation(selection) + this.moveCursorTo('end', selection) - const onAdd = this.isOnAnnotation('add') + // selection is now collapsed, so handle it as collapsed + this.handleAddCollapsed(options) + } - if (onAdd) { - // console.log('on add') - const annotation = this.getAnnotationByStatus('add') - const withinAnnotation = this.isSelectionContainedWithin(annotation) + expandAnnotationToDirection (annotation, options) { + if (!options) options = {} + const move = options.move || 'left' + const cursorTo = options.cursorTo || 'end' - if (withinAnnotation) { - const point = annotation[direction.cursorTo + 'Offset'] - this.moveCursorTo(point) - return - } + const selection = this.setSelectionPlusOne(move) + this.expandTrackAnnotation(selection, annotation) + this.moveCursorTo(cursorTo) + } - const annotationContainedInSelection = this.isAnnotationContainedWithinSelection(annotation) - if (annotationContainedInSelection) { - selection = this.getSelection() - this.createDeleteAnnotation(selection) - return - } + insertCharacterWithAddAnnotation (options) { + const { event } = options + this.insertText(event) - selection = this.getSelection() - this.createDeleteAnnotation(selection) - this.separateAnnotations() - return - } + // TODO -- watch it with additions by other users + this.createAdditionAnnotationOnLastChar() + } - const isOnDelete = this.isOnAnnotation('delete') + insertCharacterWithoutExpandingAnnotation (annotation, options) { + const { event } = options + let selection = options - if (isOnDelete) { - const annotation = this.getAnnotationByStatus('delete') - const containedWithin = this.isSelectionContainedWithin(annotation) + this.insertText(event) + selection = this.setSelectionPlusOne('left') + this.truncateTrackAnnotation(selection, annotation) + this.moveCursorTo('end') - if (containedWithin) { - const point = annotation[direction.cursorTo + 'Offset'] - return this.moveCursorTo(point) - } + options.event = null + } - const selection = this.getSelection() - this.expandTrackAnnotation(selection, annotation) + markSelectionAsDeleted (options) { + const { direction, selection } = options + this.createDeleteAnnotation(selection) + this.moveCursorTo(direction.cursorTo) + } - const key = direction.key + selectCharacterAndMarkDeleted (options) { + const { direction } = options + const selection = this.setSelectionPlusOne(direction.move) - let point - if (key === 'BACKSPACE') point = selection.startOffset - if (key === 'DELETE') point = selection.endOffset - this.moveCursorTo(point) - } - } + this.createDeleteAnnotation(selection) + this.moveCursorTo(direction.cursorTo) } /* @@ -374,50 +403,39 @@ class TrackChangesProvider { surface.transaction(transformation, info) } - expandTrackAnnotation (selection, annotation) { + deleteCharacter (direction) { const surface = this.getSurface() - const info = this.getInfo() + const info = { action: 'delete' } const transformation = (tx, args) => { - args.selection = selection - args.anno = annotation - - expandAnnotation(tx, args) + args.direction = direction + return deleteChar(tx, args) } surface.transaction(transformation, info) } - truncateTrackAnnotation (selection, annotation) { + deleteSelection (selection) { const surface = this.getSurface() - const info = this.getInfo() + const info = { action: 'delete' } const transformation = (tx, args) => { - args.anno = annotation args.selection = selection - truncateAnnotation(tx, args) + return deleteSel(tx, args) } surface.transaction(transformation, info) } - separateAnnotations () { + expandTrackAnnotation (selection, annotation) { const surface = this.getSurface() const info = this.getInfo() - // TODO -- separate all annotations - // to see this fail: - // make two add annotations with free text inbetween - // make a selection from the middle of the first to the middle of the second - // press backspace - // only the first two spans are separated correctly - const addAnnotation = this.getAnnotationByStatus('add') - const deleteAnnotation = this.getAnnotationByStatus('delete') - const transformation = (tx, args) => { - args.anno = deleteAnnotation - args.selection = addAnnotation.getSelection() - truncateAnnotation(tx, args) + args.selection = selection + args.anno = annotation + + expandAnnotation(tx, args) } surface.transaction(transformation, info) @@ -434,25 +452,25 @@ class TrackChangesProvider { }, { action: 'type' }) } - deleteCharacter (direction) { + removeTrackAnnotation (annotation) { const surface = this.getSurface() - const info = { action: 'delete' } const transformation = (tx, args) => { - args.direction = direction - return deleteChar(tx, args) + args.nodeId = annotation.id + deleteNode(tx, args) } - surface.transaction(transformation, info) + surface.transaction(transformation) } - deleteSelection (selection) { + truncateTrackAnnotation (selection, annotation) { const surface = this.getSurface() - const info = { action: 'delete' } + const info = this.getInfo() const transformation = (tx, args) => { + args.anno = annotation args.selection = selection - return deleteSel(tx, args) + truncateAnnotation(tx, args) } surface.transaction(transformation, info) @@ -480,12 +498,12 @@ class TrackChangesProvider { return annotationsForStatus[0] } - getExistingAnnotation () { - const documentSession = this.getDocumentSession() - const selectionState = documentSession.getSelectionState() - const annotations = selectionState.getAnnotationsForType('track-change') - return annotations[0] - } + // getExistingAnnotation () { + // const documentSession = this.getDocumentSession() + // const selectionState = documentSession.getSelectionState() + // const annotations = selectionState.getAnnotationsForType('track-change') + // return annotations[0] + // } getAllExistingTrackAnnotations () { const documentSession = this.getDocumentSession() @@ -513,6 +531,11 @@ class TrackChangesProvider { return { skipSelection: true } } + isNotOnTrackAnnotation () { + const annotations = this.getAllExistingTrackAnnotations() + return (annotations.length === 0) + } + // returns whether the selection is on an add / delete tracked change isOnAnnotation (status) { const annotations = this.getAllExistingTrackAnnotations() @@ -534,11 +557,6 @@ class TrackChangesProvider { return (selection.endOffset === annotation.endOffset) } - isNotOnTrackAnnotation () { - const annotations = this.getAllExistingTrackAnnotations() - return (annotations.length === 0) - } - /** HISTORY HANDLERS @@ -588,23 +606,32 @@ class TrackChangesProvider { */ - // get delete direction - // is part of the selection outside the annotation - // move cursor to + isAnnotationContainedWithinSelection (annotation, strict) { + const selection = this.getSelection() + const annotationSelection = annotation.getSelection() - updateSelection (selection, startOffset, endOffset) { - selection.startOffset = startOffset - selection.endOffset = endOffset - return selection + return selection.contains(annotationSelection, strict) } - setSelectionPlusOne (direction) { + isSelectionCollapsed () { const selection = this.getSelection() + const isCollapsed = selection.isCollapsed() + return isCollapsed + } - if (direction === 'left') selection.startOffset -= 1 - if (direction === 'right') selection.endOffset += 1 + // TODO -- refactor this and isAnnotationContainedWithinSelection into one + isSelectionContainedWithin (annotation, strict) { + const selection = this.getSelection() + // console.trace() + const annotationSelection = annotation.getSelection() - return selection + return annotationSelection.contains(selection, strict) + + // const leftSide = (selection.startOffset < annotation.startOffset) + // const rightSide = (selection.endOffset > annotation.endOffset) + // + // if (leftSide || rightSide) return false + // return true } moveCursorTo (point, sel) { @@ -624,50 +651,23 @@ class TrackChangesProvider { surface.setSelection(selection) } - clearSelection (args, status) { - const selection = args.selection - if ( - status === 'add' || - (status === 'delete' && args.deleteCollapsed) - ) { - selection.startOffset = selection.endOffset - } else if (status === 'delete') { - selection.endOffset = selection.startOffset - } - return selection - } - - createSelection (args) { - const selection = args.selection - selection.startOffset -= 1 - return selection - } - - isAnnotationContainedWithinSelection (annotation, strict) { + setSelectionPlusOne (direction) { const selection = this.getSelection() - const annotationSelection = annotation.getSelection() - return selection.contains(annotationSelection, strict) - } + if (direction === 'left') selection.startOffset -= 1 + if (direction === 'right') selection.endOffset += 1 - isSelectionCollapsed () { - const selection = this.getSelection() - const isCollapsed = selection.isCollapsed() - return isCollapsed + return selection } - // TODO -- refactor this and isAnnotationContainedWithinSelection into one - isSelectionContainedWithin (annotation, strict) { - const selection = this.getSelection() - const annotationSelection = annotation.getSelection() + updateSelection (selection, startOffset, endOffset) { + const surface = this.getSurface() - return annotationSelection.contains(selection, strict) + selection.startOffset = startOffset + selection.endOffset = endOffset - // const leftSide = (selection.startOffset < annotation.startOffset) - // const rightSide = (selection.endOffset > annotation.endOffset) - // - // if (leftSide || rightSide) return false - // return true + surface.setSelection(selection) + return selection } /* @@ -676,24 +676,10 @@ class TrackChangesProvider { */ - getCommandManager () { - return this.config.commandManager - } - - getCommandStates () { - const commandManager = this.getCommandManager() - return commandManager.getCommandStates() - } - getDocumentSession () { return this.config.documentSession } - getMode () { - const state = this.getTrackState() - return state.mode - } - getSelection () { const surface = this.getSurface() return surface.getSelection() @@ -705,11 +691,6 @@ class TrackChangesProvider { return surfaceManager.getSurface(id) } - - getTrackState () { - const commandStates = this.getCommandStates() - return commandStates['track-change'] - } } export default TrackChangesProvider