mirror of
https://github.com/overleaf/overleaf.git
synced 2024-10-31 21:21:03 -04:00
150 lines
4.6 KiB
CoffeeScript
150 lines
4.6 KiB
CoffeeScript
strInject = (s1, pos, s2) -> s1[...pos] + s2 + s1[pos..]
|
|
strRemove = (s1, pos, length) -> s1[...pos] + s1[(pos + length)..]
|
|
|
|
module.exports = HistoryBuilder =
|
|
normalizeUpdate: (update) ->
|
|
if update.meta.start_ts?
|
|
return [update] # already normalized
|
|
|
|
updates = []
|
|
for op in update.op
|
|
updates.push
|
|
op: [op]
|
|
meta:
|
|
start_ts: update.meta.ts
|
|
end_ts: update.meta.ts
|
|
user_id: update.meta.user_id
|
|
return updates
|
|
|
|
compressUpdates: (rawUpdates) ->
|
|
return [] if rawUpdates.length == 0
|
|
normalizedUpdates = []
|
|
for rawUpdate in rawUpdates
|
|
normalizedUpdates = normalizedUpdates.concat HistoryBuilder.normalizeUpdate(rawUpdate)
|
|
|
|
return [] if normalizedUpdates.length == 0
|
|
firstPass = [normalizedUpdates.shift()]
|
|
for update in normalizedUpdates
|
|
lastCompressedUpdate = firstPass.pop()
|
|
if lastCompressedUpdate?
|
|
firstPass = firstPass.concat HistoryBuilder._concatTwoUpdates lastCompressedUpdate, update, false
|
|
else
|
|
firstPass.push rawUpdate
|
|
|
|
return [] if firstPass.length == 0
|
|
secondPass = [firstPass.shift()]
|
|
for update in firstPass
|
|
lastCompressedUpdate = secondPass.pop()
|
|
if lastCompressedUpdate?
|
|
secondPass = secondPass.concat HistoryBuilder._concatTwoUpdates lastCompressedUpdate, update, true
|
|
else
|
|
secondPass.push update
|
|
|
|
return secondPass
|
|
|
|
MAX_TIME_BETWEEN_UPDATES: oneMinute = 60 * 1000
|
|
|
|
_concatTwoUpdates: (firstUpdate, secondUpdate, mergeInsertsAndDeletes) ->
|
|
firstUpdate =
|
|
op: firstUpdate.op
|
|
meta:
|
|
user_id: firstUpdate.meta.user_id or null
|
|
start_ts: firstUpdate.meta.start_ts or firstUpdate.meta.ts
|
|
end_ts: firstUpdate.meta.end_ts or firstUpdate.meta.ts
|
|
secondUpdate =
|
|
op: secondUpdate.op
|
|
meta:
|
|
user_id: secondUpdate.meta.user_id or null
|
|
start_ts: secondUpdate.meta.start_ts or secondUpdate.meta.ts
|
|
end_ts: secondUpdate.meta.end_ts or secondUpdate.meta.ts
|
|
|
|
if firstUpdate.meta.user_id != secondUpdate.meta.user_id
|
|
return [firstUpdate, secondUpdate]
|
|
|
|
if secondUpdate.meta.start_ts - firstUpdate.meta.end_ts > HistoryBuilder.MAX_TIME_BETWEEN_UPDATES
|
|
return [firstUpdate, secondUpdate]
|
|
|
|
firstOp = firstUpdate.op[0]
|
|
secondOp = secondUpdate.op[0]
|
|
# Two inserts
|
|
if firstOp.i? and secondOp.i? and firstOp.p <= secondOp.p <= (firstOp.p + firstOp.i.length)
|
|
return [
|
|
meta:
|
|
start_ts: firstUpdate.meta.start_ts
|
|
end_ts: secondUpdate.meta.end_ts
|
|
user_id: firstUpdate.meta.user_id
|
|
op: [
|
|
p: firstOp.p
|
|
i: strInject(firstOp.i, secondOp.p - firstOp.p, secondOp.i)
|
|
]
|
|
]
|
|
# Two deletes
|
|
else if firstOp.d? and secondOp.d? and secondOp.p <= firstOp.p <= (secondOp.p + secondOp.d.length)
|
|
return [
|
|
meta:
|
|
start_ts: firstUpdate.meta.start_ts
|
|
end_ts: secondUpdate.meta.end_ts
|
|
user_id: firstUpdate.meta.user_id
|
|
op: [
|
|
p: secondOp.p
|
|
d: strInject(secondOp.d, firstOp.p - secondOp.p, firstOp.d)
|
|
]
|
|
]
|
|
# An insert and then a delete
|
|
if mergeInsertsAndDeletes and firstOp.i? and secondOp.d? and firstOp.p <= secondOp.p <= (firstOp.p + firstOp.i.length)
|
|
offset = secondOp.p - firstOp.p
|
|
insertedText = firstOp.i.slice(offset, offset + secondOp.d.length)
|
|
if insertedText == secondOp.d
|
|
insert = strRemove(firstOp.i, offset, secondOp.d.length)
|
|
return [] if insert == ""
|
|
return [
|
|
meta:
|
|
start_ts: firstUpdate.meta.start_ts
|
|
end_ts: secondUpdate.meta.end_ts
|
|
user_id: firstUpdate.meta.user_id
|
|
op: [
|
|
p: firstOp.p
|
|
i: insert
|
|
]
|
|
]
|
|
else
|
|
# This shouldn't be possible!
|
|
return [firstUpdate, secondUpdate]
|
|
else if mergeInsertsAndDeletes and firstOp.d? and secondOp.i? and firstOp.p == secondOp.p
|
|
offset = firstOp.d.indexOf(secondOp.i)
|
|
if offset == -1
|
|
return [firstUpdate, secondUpdate]
|
|
headD = firstOp.d.slice(0, offset)
|
|
inserted = firstOp.d.slice(offset, secondOp.i.length)
|
|
tailD = firstOp.d.slice(offset + secondOp.i.length)
|
|
headP = firstOp.p
|
|
tailP = firstOp.p + secondOp.i.length
|
|
updates = []
|
|
if headD != ""
|
|
updates.push
|
|
meta:
|
|
start_ts: firstUpdate.meta.start_ts
|
|
end_ts: secondUpdate.meta.end_ts
|
|
user_id: firstUpdate.meta.user_id
|
|
op: [
|
|
p: headP
|
|
d: headD
|
|
]
|
|
if tailD != ""
|
|
updates.push
|
|
meta:
|
|
start_ts: firstUpdate.meta.start_ts
|
|
end_ts: secondUpdate.meta.end_ts
|
|
user_id: firstUpdate.meta.user_id
|
|
op: [
|
|
p: tailP
|
|
d: tailD
|
|
]
|
|
if updates.length == 2
|
|
updates[0].meta.start_ts = updates[0].meta.end_ts = firstUpdate.meta.start_ts
|
|
updates[1].meta.start_ts = updates[1].meta.end_ts = secondUpdate.meta.end_ts
|
|
return updates
|
|
|
|
else
|
|
return [firstUpdate, secondUpdate]
|
|
|