mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
[overleaf-core-editor] Make comment properties immutable (#17022)
* make range class immutable * rename variable * use newRanges * range readonly props * skrinkBy test * Fix range shrinking bug * [overleaf-core-editor] Make comment properties immutable * remove added line * format fix * make readonly array * [overleaf-editor-core] AddCommentOperation and DeleteCommentOperation (#16871) * [overleaf-editor-core] AddCommentOperation and DeleteCommentOperation * added add comment op test * delete comment op test * import core to escape circle deps * desctructure in tests * require directly in builder * invert of add comment is always delete comment * no merging on compose * NoOp if comment is not found * use comment.clone() * update test * change CommentRawData type * jsdoc assert type * fix formating * EditNoOperation * return other in compose * use ReturnType * Revert "use ReturnType" This reverts commit 2c7e04f1541310e9fc08963170a783a437ed1992. * transorm add comment operation * transform delete comment operation * moved comment.js * format fix * fix transform addComment and textoperation * fix merge * test more complex test operations * change to else if * move range.js * fix types * fix AddComment and TextOperation transform * fixed AddComment-TextOperation trasform, added test * deletecommentoperation should win * should not delete comment * remove unused function, fix type * fix format * add resolved for existing comment * transform EditNoOperation * fix test description * change the order of EditNoOperation * fix DeleteCommentOperation-DeleteCommentOperation transform * fix types after merging main * refactor operation types * fix errors after merging * fix bad merge * format fix * removed comment.clone() * return old comment * remove unused var GitOrigin-RevId: e31d723075cb04b0b7177e7cae0014c295f92a68
This commit is contained in:
parent
1019142457
commit
c9b9ae4180
5 changed files with 78 additions and 95 deletions
|
@ -9,34 +9,24 @@ const Range = require('./range')
|
||||||
|
|
||||||
class Comment {
|
class Comment {
|
||||||
/**
|
/**
|
||||||
* @type {Range[]}
|
* @readonly
|
||||||
|
* @type {ReadonlyArray<Range>}
|
||||||
*/
|
*/
|
||||||
ranges = []
|
ranges = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @readonly
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
resolved = false
|
resolved = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Range[]} ranges
|
* @param {ReadonlyArray<Range>} ranges
|
||||||
* @param {boolean} [resolved]
|
* @param {boolean} [resolved]
|
||||||
*/
|
*/
|
||||||
constructor(ranges, resolved = false) {
|
constructor(ranges, resolved = false) {
|
||||||
this.resolved = resolved
|
this.resolved = resolved
|
||||||
for (const range of ranges) {
|
this.ranges = this.mergeRanges(ranges)
|
||||||
this.addRange(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Range} range
|
|
||||||
*/
|
|
||||||
addRange(range) {
|
|
||||||
this.ranges.push(range)
|
|
||||||
this.ranges.sort((a, b) => a.start - b.start)
|
|
||||||
this.mergeRanges()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,6 +34,7 @@ class Comment {
|
||||||
* @param {number} cursor
|
* @param {number} cursor
|
||||||
* @param {number} length
|
* @param {number} length
|
||||||
* @param {boolean} [extendComment]
|
* @param {boolean} [extendComment]
|
||||||
|
* @returns {Comment}
|
||||||
*/
|
*/
|
||||||
applyInsert(cursor, length, extendComment = false) {
|
applyInsert(cursor, length, extendComment = false) {
|
||||||
let existingRangeExtended = false
|
let existingRangeExtended = false
|
||||||
|
@ -91,17 +82,18 @@ class Comment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ranges = newRanges
|
|
||||||
|
|
||||||
// if the insert is not inside any range, add a new range
|
// if the insert is not inside any range, add a new range
|
||||||
if (extendComment && !existingRangeExtended) {
|
if (extendComment && !existingRangeExtended) {
|
||||||
this.addRange(new Range(cursor, length))
|
newRanges.push(new Range(cursor, length))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new Comment(newRanges, this.resolved)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {Range} deletedRange
|
* @param {Range} deletedRange
|
||||||
|
* @returns {Comment}
|
||||||
*/
|
*/
|
||||||
applyDelete(deletedRange) {
|
applyDelete(deletedRange) {
|
||||||
const newRanges = []
|
const newRanges = []
|
||||||
|
@ -116,26 +108,29 @@ class Comment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ranges = newRanges
|
return new Comment(newRanges, this.resolved)
|
||||||
this.mergeRanges()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {TextOperation} operation
|
* @param {TextOperation} operation
|
||||||
|
* @returns {Comment}
|
||||||
*/
|
*/
|
||||||
applyTextOperation(operation) {
|
applyTextOperation(operation) {
|
||||||
|
/** @type {Comment} */
|
||||||
|
let comment = this
|
||||||
let cursor = 0
|
let cursor = 0
|
||||||
for (const op of operation.ops) {
|
for (const op of operation.ops) {
|
||||||
if (op instanceof RetainOp) {
|
if (op instanceof RetainOp) {
|
||||||
cursor += op.length
|
cursor += op.length
|
||||||
} else if (op instanceof InsertOp) {
|
} else if (op instanceof InsertOp) {
|
||||||
this.applyInsert(cursor, op.insertion.length)
|
comment = comment.applyInsert(cursor, op.insertion.length)
|
||||||
cursor += op.insertion.length
|
cursor += op.insertion.length
|
||||||
} else if (op instanceof RemoveOp) {
|
} else if (op instanceof RemoveOp) {
|
||||||
this.applyDelete(new Range(cursor, op.length))
|
comment = comment.applyDelete(new Range(cursor, op.length))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return comment
|
||||||
}
|
}
|
||||||
|
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
|
@ -153,11 +148,16 @@ class Comment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeRanges() {
|
/**
|
||||||
|
* @param {ReadonlyArray<Range>} ranges
|
||||||
|
* @returns {ReadonlyArray<Range>}
|
||||||
|
*/
|
||||||
|
mergeRanges(ranges) {
|
||||||
/** @type {Range[]} */
|
/** @type {Range[]} */
|
||||||
const mergedRanges = []
|
const mergedRanges = []
|
||||||
|
|
||||||
for (const range of this.ranges) {
|
const sortedRanges = [...ranges].sort((a, b) => a.start - b.start)
|
||||||
|
for (const range of sortedRanges) {
|
||||||
if (range.isEmpty()) {
|
if (range.isEmpty()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -170,14 +170,7 @@ class Comment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ranges = mergedRanges
|
return mergedRanges
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Comment}
|
|
||||||
*/
|
|
||||||
clone() {
|
|
||||||
return Comment.fromRaw(this.toRaw())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -41,10 +41,9 @@ class CommentList {
|
||||||
add(id, newComment) {
|
add(id, newComment) {
|
||||||
const existingComment = this.getComment(id)
|
const existingComment = this.getComment(id)
|
||||||
if (existingComment) {
|
if (existingComment) {
|
||||||
existingComment.resolved = existingComment.resolved && newComment.resolved
|
const resolved = existingComment.resolved && newComment.resolved
|
||||||
for (const range of newComment.ranges) {
|
const mergedRanges = [...existingComment.ranges, ...newComment.ranges]
|
||||||
existingComment.addRange(range)
|
this.comments.set(id, new Comment(mergedRanges, resolved))
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.comments.set(id, newComment)
|
this.comments.set(id, newComment)
|
||||||
}
|
}
|
||||||
|
@ -77,11 +76,12 @@ class CommentList {
|
||||||
opts.commentIds = []
|
opts.commentIds = []
|
||||||
}
|
}
|
||||||
for (const [commentId, comment] of this.comments) {
|
for (const [commentId, comment] of this.comments) {
|
||||||
comment.applyInsert(
|
const commentAfterInsert = comment.applyInsert(
|
||||||
range.pos,
|
range.pos,
|
||||||
range.length,
|
range.length,
|
||||||
opts.commentIds.includes(commentId)
|
opts.commentIds.includes(commentId)
|
||||||
)
|
)
|
||||||
|
this.comments.set(commentId, commentAfterInsert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,8 +89,9 @@ class CommentList {
|
||||||
* @param {Range} range
|
* @param {Range} range
|
||||||
*/
|
*/
|
||||||
applyDelete(range) {
|
applyDelete(range) {
|
||||||
for (const [, comment] of this.comments) {
|
for (const [commentId, comment] of this.comments) {
|
||||||
comment.applyDelete(range)
|
const commentAfterDelete = comment.applyDelete(range)
|
||||||
|
this.comments.set(commentId, commentAfterDelete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ class DeleteCommentOperation extends EditOperation {
|
||||||
return new EditNoOperation()
|
return new EditNoOperation()
|
||||||
}
|
}
|
||||||
|
|
||||||
return new core.AddCommentOperation(this.commentId, comment.clone())
|
return new core.AddCommentOperation(this.commentId, comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -31,14 +31,12 @@ class EditOperationTransformer {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a instanceof AddCommentOperation && b instanceof TextOperation) {
|
if (a instanceof AddCommentOperation && b instanceof TextOperation) {
|
||||||
const comment = a.comment.clone()
|
const comment = a.comment.applyTextOperation(b)
|
||||||
comment.applyTextOperation(b)
|
|
||||||
return [new AddCommentOperation(a.commentId, comment), b]
|
return [new AddCommentOperation(a.commentId, comment), b]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a instanceof TextOperation && b instanceof AddCommentOperation) {
|
if (a instanceof TextOperation && b instanceof AddCommentOperation) {
|
||||||
const comment = b.comment.clone()
|
const comment = b.comment.applyTextOperation(a)
|
||||||
comment.applyTextOperation(a)
|
|
||||||
return [a, new AddCommentOperation(b.commentId, comment)]
|
return [a, new AddCommentOperation(b.commentId, comment)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,77 +8,72 @@ const Range = require('../lib/range')
|
||||||
describe('Comment', function () {
|
describe('Comment', function () {
|
||||||
it('should move ranges to the right of insert', function () {
|
it('should move ranges to the right of insert', function () {
|
||||||
const comment = new Comment([new Range(5, 10)])
|
const comment = new Comment([new Range(5, 10)])
|
||||||
comment.applyInsert(3, 5, false)
|
const resComment = comment.applyInsert(3, 5, false)
|
||||||
expect(comment.ranges).to.eql([new Range(10, 10)])
|
expect(resComment.ranges).to.eql([new Range(10, 10)])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should expand the range after insert inside it', function () {
|
describe('applyInsert', function () {
|
||||||
const comment = new Comment([new Range(5, 10)])
|
it('should insert 1 char before the range', function () {
|
||||||
comment.applyInsert(4, 1) // inserting 1 char before the range
|
const comment = new Comment([new Range(5, 10)])
|
||||||
expect(comment.ranges).to.eql([new Range(6, 10)])
|
expect(comment.applyInsert(4, 1).ranges).to.eql([new Range(6, 10)])
|
||||||
comment.applyInsert(6, 1) // inserting 1 char at the edge (without expandCommand = false)
|
})
|
||||||
expect(comment.ranges).to.eql([new Range(7, 10)])
|
|
||||||
comment.applyInsert(7, 1, true) // inserting 1 char at the edge (with expandCommand = true)
|
it('should insert 1 char at the edge, without expandCommand', function () {
|
||||||
expect(comment.ranges).to.eql([new Range(7, 11)])
|
const comment = new Comment([new Range(5, 10)])
|
||||||
comment.applyInsert(8, 1, true) // inserting 1 char inside the range
|
expect(comment.applyInsert(5, 1).ranges).to.eql([new Range(6, 10)])
|
||||||
expect(comment.ranges).to.eql([new Range(7, 12)])
|
})
|
||||||
|
|
||||||
|
it('should insert 1 char at the edge, with expandCommand', function () {
|
||||||
|
const comment = new Comment([new Range(5, 10)])
|
||||||
|
expect(comment.applyInsert(5, 1, true).ranges).to.eql([new Range(5, 11)])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should expand the range after insert inside it', function () {
|
||||||
|
const comment = new Comment([new Range(5, 10)])
|
||||||
|
expect(comment.applyInsert(6, 1, true).ranges).to.eql([new Range(5, 11)])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should split the range if inside another and expandComment is false', function () {
|
it('should split the range if inside another and expandComment is false', function () {
|
||||||
const comment1 = new Comment([new Range(5, 10)])
|
const comment = new Comment([new Range(5, 10)])
|
||||||
comment1.applyInsert(6, 10, false)
|
const commentRes = comment.applyInsert(6, 10, false)
|
||||||
expect(comment1.ranges).to.eql([new Range(5, 1), new Range(16, 9)])
|
expect(commentRes.ranges).to.eql([new Range(5, 1), new Range(16, 9)])
|
||||||
|
})
|
||||||
|
|
||||||
// insert at the end of the range
|
it('should insert the range if expandComment is false', function () {
|
||||||
const comment2 = new Comment([new Range(5, 10)])
|
const comment = new Comment([new Range(5, 10)])
|
||||||
comment2.applyInsert(14, 10, false)
|
const commentRes = comment.applyInsert(14, 10, false)
|
||||||
expect(comment2.ranges).to.eql([new Range(5, 9), new Range(24, 1)])
|
expect(commentRes.ranges).to.eql([new Range(5, 9), new Range(24, 1)])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should move the range if insert is at range start and expandComment is false', function () {
|
it('should move the range if insert is at range start and expandComment is false', function () {
|
||||||
const comment = new Comment([new Range(5, 10)])
|
const comment = new Comment([new Range(5, 10)])
|
||||||
comment.applyInsert(5, 10, false)
|
const commentRes = comment.applyInsert(5, 10, false)
|
||||||
expect(comment.ranges).to.eql([new Range(15, 10)])
|
expect(commentRes.ranges).to.eql([new Range(15, 10)])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should ignore the range if insert is at range end and expandComment is false', function () {
|
it('should ignore the range if insert is at range end and expandComment is false', function () {
|
||||||
const comment = new Comment([new Range(5, 10)])
|
const comment = new Comment([new Range(5, 10)])
|
||||||
comment.applyInsert(15, 10, false)
|
const commentRes = comment.applyInsert(15, 10, false)
|
||||||
expect(comment.ranges).to.eql([new Range(5, 10)])
|
expect(commentRes.ranges).to.eql([new Range(5, 10)])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should expand the range after inserting on the edge of it if expandComment is true', function () {
|
it('should expand the range after inserting on the edge of it if expandComment is true', function () {
|
||||||
const comment = new Comment([new Range(5, 10)])
|
const comment = new Comment([new Range(5, 10)])
|
||||||
comment.applyInsert(15, 10, true)
|
const commentRes = comment.applyInsert(15, 10, true)
|
||||||
expect(comment.ranges).to.eql([new Range(5, 20)])
|
expect(commentRes.ranges).to.eql([new Range(5, 20)])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should add a new range if expandComment is true and not inside any range', function () {
|
it('should move comment ranges if delete is before it', function () {
|
||||||
const commentNoRanges = new Comment([])
|
const comment = new Comment([new Range(5, 10)])
|
||||||
commentNoRanges.applyInsert(5, 10, true)
|
const commentRes = comment.applyDelete(new Range(3, 5))
|
||||||
expect(commentNoRanges.ranges).to.eql([new Range(5, 10)])
|
expect(commentRes.ranges).to.eql([new Range(3, 7)])
|
||||||
|
|
||||||
const commentWithRanges = new Comment([new Range(5, 10)])
|
|
||||||
commentWithRanges.applyInsert(50, 10, true)
|
|
||||||
expect(commentWithRanges.ranges).to.eql([
|
|
||||||
new Range(5, 10),
|
|
||||||
new Range(50, 10),
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should move ranges if delete is before it', function () {
|
|
||||||
const from5to14 = new Comment([new Range(5, 10)])
|
|
||||||
const from3to7 = new Range(3, 5)
|
|
||||||
|
|
||||||
from5to14.applyDelete(from3to7)
|
|
||||||
const from3to9 = new Range(3, 7)
|
|
||||||
expect(from5to14.ranges).to.eql([from3to9])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should merge ranges after delete', function () {
|
it('should merge ranges after delete', function () {
|
||||||
const comment = new Comment([new Range(5, 10), new Range(20, 10)])
|
const comment = new Comment([new Range(5, 10), new Range(20, 10)])
|
||||||
comment.applyDelete(new Range(7, 18))
|
const commentRes = comment.applyDelete(new Range(7, 18))
|
||||||
expect(comment.ranges).to.eql([new Range(5, 7)])
|
expect(commentRes.ranges).to.eql([new Range(5, 7)])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should merge overlapping ranges', function () {
|
it('should merge overlapping ranges', function () {
|
||||||
|
@ -87,7 +82,6 @@ describe('Comment', function () {
|
||||||
new Range(15, 20),
|
new Range(15, 20),
|
||||||
new Range(50, 10),
|
new Range(50, 10),
|
||||||
])
|
])
|
||||||
comment.mergeRanges()
|
|
||||||
expect(comment.ranges).to.eql([new Range(5, 30), new Range(50, 10)])
|
expect(comment.ranges).to.eql([new Range(5, 30), new Range(50, 10)])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -97,7 +91,6 @@ describe('Comment', function () {
|
||||||
new Range(50, 10),
|
new Range(50, 10),
|
||||||
new Range(5, 10),
|
new Range(5, 10),
|
||||||
])
|
])
|
||||||
comment.mergeRanges()
|
|
||||||
expect(comment.ranges).to.eql([new Range(5, 30), new Range(50, 10)])
|
expect(comment.ranges).to.eql([new Range(5, 30), new Range(50, 10)])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -107,7 +100,6 @@ describe('Comment', function () {
|
||||||
new Range(10, 5),
|
new Range(10, 5),
|
||||||
new Range(50, 10),
|
new Range(50, 10),
|
||||||
])
|
])
|
||||||
comment.mergeRanges()
|
|
||||||
expect(comment.ranges).to.eql([new Range(5, 10), new Range(50, 10)])
|
expect(comment.ranges).to.eql([new Range(5, 10), new Range(50, 10)])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -117,7 +109,6 @@ describe('Comment', function () {
|
||||||
new Range(15, 5),
|
new Range(15, 5),
|
||||||
new Range(50, 10),
|
new Range(50, 10),
|
||||||
])
|
])
|
||||||
comment.mergeRanges()
|
|
||||||
expect(comment.ranges).to.eql([new Range(5, 15), new Range(50, 10)])
|
expect(comment.ranges).to.eql([new Range(5, 15), new Range(50, 10)])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue