From ab5061cd4716cceeaf6144881cd283625eca0fc8 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Mon, 4 Jul 2022 16:05:44 +0200 Subject: [PATCH] normalize ranges --- src/structs/ContentMove.js | 2 +- src/utils/ListIterator.js | 47 ++++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/structs/ContentMove.js b/src/structs/ContentMove.js index f062c998..745c075b 100644 --- a/src/structs/ContentMove.js +++ b/src/structs/ContentMove.js @@ -9,7 +9,7 @@ import { import { decodeRelativePosition, encodeRelativePosition } from 'yjs' /** - * @param {ContentMove} moved + * @param {ContentMove | { start: RelativePosition, end: RelativePosition }} moved * @param {Transaction} tr * @return {{ start: Item, end: Item }} $start (inclusive) is the beginning and $end (inclusive) is the end of the moved area */ diff --git a/src/utils/ListIterator.js b/src/utils/ListIterator.js index 39f8fa69..8d82d0f3 100644 --- a/src/utils/ListIterator.js +++ b/src/utils/ListIterator.js @@ -616,7 +616,7 @@ export const getMinimalListViewRanges = (tr, walker, len) => { walker.reduceMoveDepth(tr) /** - * @type {Array<{ start: RelativePosition, end: RelativePosition }>} + * @type {Array<{ start: RelativePosition, end: RelativePosition, move: Item | null }>} */ const ranges = [] // store relevant information for the beginning, before we iterate forward @@ -677,6 +677,7 @@ export const getMinimalListViewRanges = (tr, walker, len) => { preStack.shift() afterStack.shift() } + const topLevelMove = preStack.length > 0 ? preStack[0].moved : (afterStack.length > 0 ? afterStack[0].moved : null) // remove stack-items that are useless for our computation (that wouldn't produce meaningful ranges) // @todo @@ -685,19 +686,21 @@ export const getMinimalListViewRanges = (tr, walker, len) => { const move = /** @type {Item} */ (preStack.pop()) ranges.push({ start, - end: /** @type {ContentMove} */ (move.content).end + end: /** @type {ContentMove} */ (move.content).end, + move }) start = createRelativePosition(walker.type, createID(move.id.client, move.id.clock), -1) } - const middleMove = { start, end } + const middleMove = { start, end, move: topLevelMove } ranges.push(middleMove) while (afterStack.length > 0) { const move = /** @type {Item} */ (afterStack.pop()) ranges.push({ start: /** @type {ContentMove} */ (move.content).start, - end + end, + move }) end = createRelativePosition(walker.type, createID(move.id.client, move.id.clock), 0) } @@ -706,6 +709,40 @@ export const getMinimalListViewRanges = (tr, walker, len) => { // Move ranges must be applied in order middleMove.end = end + const normalizedRanges = ranges.map(range => { + // A subset of a range could be moved by another move with a higher priority. + // If that is the case, we need to ignore those moved items. + const { start, end } = getMovedCoords(range, tr) + const move = range.move + const ranges = [] + /** + * @type {RelativePosition | null} + */ + let rangeStart = range.start + /** + * @type {Item} + */ + let item = start + while (item !== end) { + if (item.moved !== move && rangeStart != null) { + ranges.push({ start: rangeStart, end: createRelativePosition(walker.type, createID(item.id.client, item.id.clock), 0) }) + rangeStart = null + } + if (item.moved === move && rangeStart === null) { + // @todo It might be better to set this to item.left, with assoc -1 + rangeStart = createRelativePosition(walker.type, createID(item.id.client, item.id.clock), 0) + } + item = /** @type {Item} */ (item.right) + } + if (rangeStart != null) { + ranges.push({ + start: rangeStart, + end: range.end + }) + } + return ranges + }).flat() + // filter out unnecessary ranges - return ranges.filter(range => !compareRelativePositions(range.start, range.end)) + return normalizedRanges.filter(range => !compareRelativePositions(range.start, range.end)) }