/* eslint-disable no-proto, no-unused-vars, */ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS101: Remove unnecessary use of Array.from * DS102: Remove unnecessary code created because of implicit returns * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ import logger from '@overleaf/logger' import OError from '@overleaf/o-error' export class ConsistencyError extends OError {} /** * Container for functions that need to be mocked in tests * * TODO: Rewrite tests in terms of exported functions only */ export const _mocks = {} export function buildDiff(initialContent, updates) { let diff = [{ u: initialContent }] for (const update of Array.from(updates)) { diff = applyUpdateToDiff(diff, update) } diff = compressDiff(diff) return diff } _mocks.compressDiff = diff => { const newDiff = [] for (const part of Array.from(diff)) { const lastPart = newDiff[newDiff.length - 1] if ( lastPart != null && (lastPart.meta != null ? lastPart.meta.user : undefined) != null && (part.meta != null ? part.meta.user : undefined) != null ) { if ( lastPart.i != null && part.i != null && lastPart.meta.user.id === part.meta.user.id ) { lastPart.i += part.i lastPart.meta.start_ts = Math.min( lastPart.meta.start_ts, part.meta.start_ts ) lastPart.meta.end_ts = Math.max(lastPart.meta.end_ts, part.meta.end_ts) } else if ( lastPart.d != null && part.d != null && lastPart.meta.user.id === part.meta.user.id ) { lastPart.d += part.d lastPart.meta.start_ts = Math.min( lastPart.meta.start_ts, part.meta.start_ts ) lastPart.meta.end_ts = Math.max(lastPart.meta.end_ts, part.meta.end_ts) } else { newDiff.push(part) } } else { newDiff.push(part) } } return newDiff } export function compressDiff(...args) { return _mocks.compressDiff(...args) } export function applyOpToDiff(diff, op, meta) { let consumedDiff const position = 0 let remainingDiff = diff.slice() ;({ consumedDiff, remainingDiff } = _consumeToOffset(remainingDiff, op.p)) const newDiff = consumedDiff if (op.i != null) { newDiff.push({ i: op.i, meta, }) } else if (op.d != null) { ;({ consumedDiff, remainingDiff } = _consumeDiffAffectedByDeleteOp( remainingDiff, op, meta )) newDiff.push(...Array.from(consumedDiff || [])) } newDiff.push(...Array.from(remainingDiff || [])) return newDiff } _mocks.applyUpdateToDiff = (diff, update) => { for (const op of Array.from(update.op)) { if (op.broken !== true) { diff = applyOpToDiff(diff, op, update.meta) } } return diff } export function applyUpdateToDiff(...args) { return _mocks.applyUpdateToDiff(...args) } function _consumeToOffset(remainingDiff, totalOffset) { let part const consumedDiff = [] let position = 0 while ((part = remainingDiff.shift())) { const length = _getLengthOfDiffPart(part) if (part.d != null) { consumedDiff.push(part) } else if (position + length >= totalOffset) { const partOffset = totalOffset - position if (partOffset > 0) { consumedDiff.push(_slicePart(part, 0, partOffset)) } if (partOffset < length) { remainingDiff.unshift(_slicePart(part, partOffset)) } break } else { position += length consumedDiff.push(part) } } return { consumedDiff, remainingDiff, } } function _consumeDiffAffectedByDeleteOp(remainingDiff, deleteOp, meta) { const consumedDiff = [] let remainingOp = deleteOp while (remainingOp && remainingDiff.length > 0) { let newPart ;({ newPart, remainingDiff, remainingOp } = _consumeDeletedPart( remainingDiff, remainingOp, meta )) if (newPart != null) { consumedDiff.push(newPart) } } return { consumedDiff, remainingDiff, } } function _consumeDeletedPart(remainingDiff, op, meta) { let deletedContent, newPart, remainingOp const part = remainingDiff.shift() const partLength = _getLengthOfDiffPart(part) if (part.d != null) { // Skip existing deletes remainingOp = op newPart = part } else if (partLength > op.d.length) { // Only the first bit of the part has been deleted const remainingPart = _slicePart(part, op.d.length) remainingDiff.unshift(remainingPart) deletedContent = _getContentOfPart(part).slice(0, op.d.length) if (deletedContent !== op.d) { throw new ConsistencyError( `deleted content, '${deletedContent}', does not match delete op, '${op.d}'` ) } if (part.u != null) { newPart = { d: op.d, meta, } } else if (part.i != null) { newPart = null } remainingOp = null } else if (partLength === op.d.length) { // The entire part has been deleted, but it is the last part deletedContent = _getContentOfPart(part) if (deletedContent !== op.d) { throw new ConsistencyError( `deleted content, '${deletedContent}', does not match delete op, '${op.d}'` ) } if (part.u != null) { newPart = { d: op.d, meta, } } else if (part.i != null) { newPart = null } remainingOp = null } else if (partLength < op.d.length) { // The entire part has been deleted and there is more deletedContent = _getContentOfPart(part) const opContent = op.d.slice(0, deletedContent.length) if (deletedContent !== opContent) { throw new ConsistencyError( `deleted content, '${deletedContent}', does not match delete op, '${opContent}'` ) } if (part.u) { newPart = { d: part.u, meta, } } else if (part.i != null) { newPart = null } remainingOp = { p: op.p, d: op.d.slice(_getLengthOfDiffPart(part)), } } return { newPart, remainingDiff, remainingOp, } } function _slicePart(basePart, from, to) { let part if (basePart.u != null) { part = { u: basePart.u.slice(from, to) } } else if (basePart.i != null) { part = { i: basePart.i.slice(from, to) } } if (basePart.meta != null) { part.meta = basePart.meta } return part } function _getLengthOfDiffPart(part) { return (part.u || part.d || part.i || '').length } function _getContentOfPart(part) { return part.u || part.d || part.i || '' }