diff --git a/README.v13.md b/README.v13.md index 4dc5aa53..109fa107 100644 --- a/README.v13.md +++ b/README.v13.md @@ -1,7 +1,7 @@ # ![Yjs](https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png) > The shared editing library -Yjs is a library for automatic conflict resolution on shared state. It implements an operation-based CRDT and exposes its internal CRDT model as shared types. Shared types are common data types like `Map` or `Array` with superpowers! - changes are automatically distributed to other peers and merged without merge conflicts. +Yjs is a library for automatic conflict resolution on shared state. It implements an operation-based CRDT and exposes its internal CRDT model as shared types. Shared types are common data types like `Map` or `Array` with superpowers - changes are automatically distributed to other peers and merged without merge conflicts. Yjs is **network agnostic** (p2p!), supports many existing **rich text editors**, **offline editing**, **version snapshots**, **shared cursors**, and encodes update messages using **binary protocol encoding**. diff --git a/src/index.js b/src/index.js index d32657eb..c90449b5 100644 --- a/src/index.js +++ b/src/index.js @@ -9,28 +9,18 @@ export { YXmlHook as XmlHook, YXmlElement as XmlElement, YXmlFragment as XmlFragment, - createRelativePosition, - createRelativePositionByOffset, - createAbsolutePosition, - compareRelativePositions, - writeRelativePosition, - readRelativePosition, - createRelativePositionFromJSON, - toAbsolutePosition, - AbsolutePosition, - RelativePosition, + createCursorFromTypeOffset, + createCursorFromJSON, + createAbsolutePositionFromCursor, + writeCursor, + readCursor, ID, createID, compareIDs, - writeStructsFromTransaction, - readStructs, getState, getStates, readStatesAsMap, writeStates, - readDeleteSet, - writeDeleteSet, - createDeleteSetFromStructStore, writeModel, readModel } from './internals.js' diff --git a/src/internals.js b/src/internals.js index 6dd6b771..cf40cb6a 100644 --- a/src/internals.js +++ b/src/internals.js @@ -2,7 +2,7 @@ export * from './utils/DeleteSet.js' export * from './utils/EventHandler.js' export * from './utils/ID.js' export * from './utils/isParentOf.js' -export * from './utils/relativePosition.js' +export * from './utils/cursor.js' export * from './utils/Snapshot.js' export * from './utils/StructStore.js' export * from './utils/Transaction.js' diff --git a/src/utils/StructStore.js b/src/utils/StructStore.js index 7f9b8e1c..20a38078 100644 --- a/src/utils/StructStore.js +++ b/src/utils/StructStore.js @@ -263,16 +263,6 @@ export const replaceStruct = (store, struct, newStruct) => { structs[findIndexSS(structs, struct.id.clock)] = newStruct } -/** - * @param {StructStore} store - * @param {ID} id - * @return {boolean} - * - * @private - * @function - */ -export const exists = (store, id) => id.clock < getState(store, id.client) - /** * Read StateMap from Decoder and return as Map * diff --git a/src/utils/relativePosition.js b/src/utils/cursor.js similarity index 62% rename from src/utils/relativePosition.js rename to src/utils/cursor.js index 1e4be658..533a457c 100644 --- a/src/utils/relativePosition.js +++ b/src/utils/cursor.js @@ -1,15 +1,15 @@ /** - * @module RelativePosition + * @module Cursors */ import { - find, - exists, + getItem, getItemType, createID, writeID, readID, compareIDs, + getState, findRootTypeKey, AbstractItem, ID, StructStore, Y, AbstractType // eslint-disable-line @@ -20,31 +20,29 @@ import * as decoding from 'lib0/decoding.js' import * as error from 'lib0/error.js' /** - * A relative position that is based on the Yjs model. In contrast to an - * absolute position (position by index), the relative position can be + * A Cursor is a relative position that is based on the Yjs model. In contrast to an + * absolute position (position by index), the Cursor can be * recomputed when remote changes are received. For example: * * ```Insert(0, 'x')('a|bc') = 'xa|bc'``` Where | is the cursor position. * * A relative cursor position can be obtained with the function - * {@link getRelativePosition} and it can be transformed to an absolute position - * with {@link fromRelativePosition}. * * One of the properties must be defined. * * @example - * // Current cursor position is at position 10 - * let relativePosition = getRelativePosition(yText, 10) - * // modify yText - * yText.insert(0, 'abc') - * yText.delete(3, 10) - * // Compute the cursor position - * let absolutePosition = fromRelativePosition(y, relativePosition) - * absolutePosition.type // => yText - * console.log('cursor location is ' + absolutePosition.offset) // => cursor location is 3 + * // Current cursor position is at position 10 + * const relativePosition = createCursorFromOffset(yText, 10) + * // modify yText + * yText.insert(0, 'abc') + * yText.delete(3, 10) + * // Compute the cursor position + * const absolutePosition = toAbsolutePosition(y, relativePosition) + * absolutePosition.type === yText // => true + * console.log('cursor location is ' + absolutePosition.offset) // => cursor location is 3 * */ -export class RelativePosition { +export class Cursor { /** * @param {ID|null} type * @param {string|null} tname @@ -81,11 +79,11 @@ export class RelativePosition { /** * @param {Object} json - * @return {RelativePosition} + * @return {Cursor} * * @function */ -export const createRelativePositionFromJSON = json => new RelativePosition(json.type == null ? null : createID(json.type.client, json.type.clock), json.tname || null, json.item == null ? null : createID(json.item.client, json.item.clock)) +export const createCursorFromJSON = json => new Cursor(json.type == null ? null : createID(json.type.client, json.type.clock), json.tname || null, json.item == null ? null : createID(json.item.client, json.item.clock)) export class AbsolutePosition { /** @@ -118,7 +116,7 @@ export const createAbsolutePosition = (type, offset) => new AbsolutePosition(typ * * @function */ -export const createRelativePosition = (type, item) => { +export const createCursor = (type, item) => { let typeid = null let tname = null if (type._item === null) { @@ -126,7 +124,7 @@ export const createRelativePosition = (type, item) => { } else { typeid = type._item.id } - return new RelativePosition(typeid, tname, item) + return new Cursor(typeid, tname, item) } /** @@ -134,32 +132,32 @@ export const createRelativePosition = (type, item) => { * * @param {AbstractType} type The base type (e.g. YText or YArray). * @param {number} offset The absolute position. - * @return {RelativePosition} + * @return {Cursor} * * @function */ -export const createRelativePositionByOffset = (type, offset) => { +export const createCursorFromTypeOffset = (type, offset) => { let t = type._start while (t !== null) { if (!t.deleted && t.countable) { if (t.length > offset) { // case 1: found position somewhere in the linked list - return createRelativePosition(type, createID(t.id.client, t.id.clock + offset)) + return createCursor(type, createID(t.id.client, t.id.clock + offset)) } offset -= t.length } t = t.right } - return createRelativePosition(type, null) + return createCursor(type, null) } /** * @param {encoding.Encoder} encoder - * @param {RelativePosition} rpos + * @param {Cursor} rpos * * @function */ -export const writeRelativePosition = (encoder, rpos) => { +export const writeCursor = (encoder, rpos) => { const { type, tname, item } = rpos if (item !== null) { encoding.writeVarUint(encoder, 0) @@ -182,11 +180,11 @@ export const writeRelativePosition = (encoder, rpos) => { * @param {decoding.Decoder} decoder * @param {Y} y * @param {StructStore} store - * @return {RelativePosition|null} + * @return {Cursor|null} * * @function */ -export const readRelativePosition = (decoder, y, store) => { +export const readCursor = (decoder, y, store) => { let type = null let tname = null let itemID = null @@ -204,28 +202,28 @@ export const readRelativePosition = (decoder, y, store) => { type = readID(decoder) } } - return new RelativePosition(type, tname, itemID) + return new Cursor(type, tname, itemID) } /** - * @param {RelativePosition} rpos + * @param {Cursor} cursor * @param {Y} y * @return {AbsolutePosition|null} * * @function */ -export const toAbsolutePosition = (rpos, y) => { +export const createAbsolutePositionFromCursor = (cursor, y) => { const store = y.store - const rightID = rpos.item - const typeID = rpos.type - const tname = rpos.tname + const rightID = cursor.item + const typeID = cursor.type + const tname = cursor.tname let type = null let offset = 0 if (rightID !== null) { - if (!exists(store, rightID)) { + if (getState(store, rightID.client) <= rightID.clock) { return null } - const right = find(store, rightID) + const right = getItem(store, rightID) if (!(right instanceof AbstractItem)) { return null } @@ -255,42 +253,12 @@ export const toAbsolutePosition = (rpos, y) => { } /** - * Transforms an absolute to a relative position. - * - * @param {AbsolutePosition} apos The absolute position. - * @param {Y} y The Yjs instance in which to query for the absolute position. - * @return {RelativePosition} The absolute position in the Yjs model - * (type + offset). + * @param {Cursor|null} a + * @param {Cursor|null} b * * @function */ -export const toRelativePosition = (apos, y) => { - const type = apos.type - if (type._length === apos.offset) { - return createRelativePosition(type, null) - } else { - let offset = apos.offset - let n = type._start - while (n !== null) { - if (!n.deleted && n.countable) { - if (n.length > offset) { - return createRelativePosition(type, createID(n.id.client, n.id.clock + offset)) - } - offset -= n.length - } - n = n.right - } - } - throw error.unexpectedCase() -} - -/** - * @param {RelativePosition|null} a - * @param {RelativePosition|null} b - * - * @function - */ -export const compareRelativePositions = (a, b) => a === b || ( +export const compareCursors = (a, b) => a === b || ( a !== null && b !== null && a.tname === b.tname && ( (a.item !== null && b.item !== null && compareIDs(a.item, b.item)) || (a.type !== null && b.type !== null && compareIDs(a.type, b.type)) diff --git a/src/utils/encoding.js b/src/utils/encoding.js index 73658685..1eb39ed9 100644 --- a/src/utils/encoding.js +++ b/src/utils/encoding.js @@ -5,7 +5,6 @@ import { findIndexSS, - exists, GCRef, ItemBinaryRef, ItemDeletedRef, @@ -204,7 +203,7 @@ const resumeStructIntegration = (transaction, store) => { } while (m.length > 0) { const missing = m[m.length - 1] - if (!exists(store, missing)) { + if (getState(store, missing.client) <= missing.clock) { const client = missing.client // get the struct reader that has the missing struct const structRefs = clientsStructRefs.get(client)