implemented xml type for new event system
This commit is contained in:
parent
1311c7a0d8
commit
755c9eb16e
@ -1,21 +1,17 @@
|
|||||||
/* global Y */
|
/* global Y */
|
||||||
|
|
||||||
// initialize a shared object. This function call returns a promise!
|
// initialize a shared object. This function call returns a promise!
|
||||||
Y({
|
let y = new Y({
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
connector: {
|
||||||
name: 'websockets-client',
|
name: 'websockets-client',
|
||||||
url: 'http://127.0.0.1:1234',
|
url: 'http://127.0.0.1:1234',
|
||||||
room: 'html-editor-example6'
|
room: 'html-editor-example6'
|
||||||
// maxBufferLength: 100
|
// maxBufferLength: 100
|
||||||
},
|
|
||||||
share: {
|
|
||||||
xml: 'XmlFragment()' // y.share.xml is of type Y.Xml with tagname "p"
|
|
||||||
}
|
}
|
||||||
}).then(function (y) {
|
|
||||||
window.yXml = y
|
|
||||||
// Bind children of XmlFragment to the document.body
|
|
||||||
window.yXml.share.xml.bindToDom(document.body)
|
|
||||||
})
|
})
|
||||||
|
window.yXml = y
|
||||||
|
window.onload = function () {
|
||||||
|
console.log('start!')
|
||||||
|
// Bind children of XmlFragment to the document.body
|
||||||
|
y.get('xml', Y.XmlFragment).bindToDom(document.body)
|
||||||
|
}
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
|
|
||||||
import Y from '../src/y.js'
|
import Y from '../src/Y.js'
|
||||||
import yArray from '../../y-array/src/y-array.js'
|
|
||||||
import yIndexedDB from '../../y-indexeddb/src/y-indexeddb.js'
|
|
||||||
import yMap from '../../y-map/src/y-map.js'
|
|
||||||
import yText from '../../y-text/src/y-text.js'
|
|
||||||
import yXml from '../../y-xml/src/y-xml.js'
|
|
||||||
import yWebsocketsClient from '../../y-websockets-client/src/y-websockets-client.js'
|
import yWebsocketsClient from '../../y-websockets-client/src/y-websockets-client.js'
|
||||||
|
|
||||||
Y.extend(yArray, yIndexedDB, yMap, yText, yXml, yWebsocketsClient)
|
Y.extend(yWebsocketsClient)
|
||||||
|
|
||||||
export default Y
|
export default Y
|
||||||
|
5
package-lock.json
generated
5
package-lock.json
generated
@ -1870,6 +1870,11 @@
|
|||||||
"integrity": "sha1-ysNCuPqJAm7+c6Jg/p9rgE9J5H8=",
|
"integrity": "sha1-ysNCuPqJAm7+c6Jg/p9rgE9J5H8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"fast-diff": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
|
||||||
|
},
|
||||||
"fast-levenshtein": {
|
"fast-levenshtein": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^2.6.8",
|
"debug": "^2.6.8",
|
||||||
|
"fast-diff": "^1.1.2",
|
||||||
"utf-8": "^1.0.0",
|
"utf-8": "^1.0.0",
|
||||||
"utf8": "^2.1.2"
|
"utf8": "^2.1.2"
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ import commonjs from 'rollup-plugin-commonjs'
|
|||||||
var pkg = require('./package.json')
|
var pkg = require('./package.json')
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
entry: 'src/Y.js',
|
entry: 'src/y-dist.cjs.js',
|
||||||
moduleName: 'Y',
|
moduleName: 'Y',
|
||||||
format: 'umd',
|
format: 'cjs',
|
||||||
plugins: [
|
plugins: [
|
||||||
nodeResolve({
|
nodeResolve({
|
||||||
main: true,
|
main: true,
|
||||||
|
@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
|
|||||||
import multiEntry from 'rollup-plugin-multi-entry'
|
import multiEntry from 'rollup-plugin-multi-entry'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
entry: 'test/y-array.tests.js',
|
entry: 'test/y-xml.tests.js',
|
||||||
moduleName: 'y-tests',
|
moduleName: 'y-tests',
|
||||||
format: 'umd',
|
format: 'umd',
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@ -5,7 +5,7 @@ import { sendSyncStep1, readSyncStep1 } from './MessageHandler/syncStep1.js'
|
|||||||
import { readSyncStep2 } from './MessageHandler/syncStep2.js'
|
import { readSyncStep2 } from './MessageHandler/syncStep2.js'
|
||||||
import { readUpdate } from './MessageHandler/update.js'
|
import { readUpdate } from './MessageHandler/update.js'
|
||||||
|
|
||||||
import { debug } from './Y.js'
|
import debug from 'debug'
|
||||||
|
|
||||||
export default class AbstractConnector {
|
export default class AbstractConnector {
|
||||||
constructor (y, opts) {
|
constructor (y, opts) {
|
||||||
@ -251,9 +251,13 @@ export default class AbstractConnector {
|
|||||||
// cannot wait for sync step 1 to finish, because we may wait for sync step 2 in sync step 1 (->lock)
|
// cannot wait for sync step 1 to finish, because we may wait for sync step 2 in sync step 1 (->lock)
|
||||||
readSyncStep1(decoder, encoder, this.y, senderConn, sender)
|
readSyncStep1(decoder, encoder, this.y, senderConn, sender)
|
||||||
} else if (messageType === 'sync step 2' && senderConn.auth === 'write') {
|
} else if (messageType === 'sync step 2' && senderConn.auth === 'write') {
|
||||||
readSyncStep2(decoder, encoder, this.y, senderConn, sender)
|
this.y.transact(() => {
|
||||||
|
readSyncStep2(decoder, encoder, this.y, senderConn, sender)
|
||||||
|
})
|
||||||
} else if (messageType === 'update' && (skipAuth || senderConn.auth === 'write')) {
|
} else if (messageType === 'update' && (skipAuth || senderConn.auth === 'write')) {
|
||||||
readUpdate(decoder, encoder, this.y, senderConn, sender)
|
this.y.transact(() => {
|
||||||
|
readUpdate(decoder, encoder, this.y, senderConn, sender)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Unable to receive message')
|
throw new Error('Unable to receive message')
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { deleteItemRange } from '../Struct/Delete.js'
|
import { deleteItemRange } from '../Struct/Delete.js'
|
||||||
|
import ID from '../Util/ID.js'
|
||||||
|
|
||||||
export function stringifyDeleteSet (y, decoder, strBuilder) {
|
export function stringifyDeleteSet (y, decoder, strBuilder) {
|
||||||
let dsLength = decoder.readUint32()
|
let dsLength = decoder.readUint32()
|
||||||
@ -18,7 +19,7 @@ export function stringifyDeleteSet (y, decoder, strBuilder) {
|
|||||||
|
|
||||||
export function writeDeleteSet (y, encoder) {
|
export function writeDeleteSet (y, encoder) {
|
||||||
let currentUser = null
|
let currentUser = null
|
||||||
let currentLength = 0
|
let currentLength
|
||||||
let lastLenPos
|
let lastLenPos
|
||||||
|
|
||||||
let numberOfUsers = 0
|
let numberOfUsers = 0
|
||||||
@ -36,14 +37,17 @@ export function writeDeleteSet (y, encoder) {
|
|||||||
if (currentUser !== null) { // happens on first iteration
|
if (currentUser !== null) { // happens on first iteration
|
||||||
encoder.setUint32(lastLenPos, currentLength)
|
encoder.setUint32(lastLenPos, currentLength)
|
||||||
}
|
}
|
||||||
|
currentUser = user
|
||||||
encoder.writeVarUint(user)
|
encoder.writeVarUint(user)
|
||||||
// pseudo-fill pos
|
// pseudo-fill pos
|
||||||
lastLenPos = encoder.pos
|
lastLenPos = encoder.pos
|
||||||
encoder.writeUint32(0)
|
encoder.writeUint32(0)
|
||||||
|
currentLength = 0
|
||||||
}
|
}
|
||||||
encoder.writeVarUint(clock)
|
encoder.writeVarUint(clock)
|
||||||
encoder.writeVarUint(len)
|
encoder.writeVarUint(len)
|
||||||
encoder.writeUint8(gc ? 1 : 0)
|
encoder.writeUint8(gc ? 1 : 0)
|
||||||
|
currentLength++
|
||||||
})
|
})
|
||||||
if (currentUser !== null) { // happens on first iteration
|
if (currentUser !== null) { // happens on first iteration
|
||||||
encoder.setUint32(lastLenPos, currentLength)
|
encoder.setUint32(lastLenPos, currentLength)
|
||||||
@ -56,62 +60,64 @@ export function readDeleteSet (y, decoder) {
|
|||||||
for (let i = 0; i < dsLength; i++) {
|
for (let i = 0; i < dsLength; i++) {
|
||||||
let user = decoder.readVarUint()
|
let user = decoder.readVarUint()
|
||||||
let dv = []
|
let dv = []
|
||||||
let dvLength = decoder.readVarUint()
|
let dvLength = decoder.readUint32()
|
||||||
for (let j = 0; j < dvLength; j++) {
|
for (let j = 0; j < dvLength; j++) {
|
||||||
let from = decoder.readVarUint()
|
let from = decoder.readVarUint()
|
||||||
let len = decoder.readVarUint()
|
let len = decoder.readVarUint()
|
||||||
let gc = decoder.readUint8() === 1
|
let gc = decoder.readUint8() === 1
|
||||||
dv.push([from, len, gc])
|
dv.push([from, len, gc])
|
||||||
}
|
}
|
||||||
var pos = 0
|
if (dvLength > 0) {
|
||||||
var d = dv[pos]
|
let pos = 0
|
||||||
y.ds.iterate([user, 0], [user, Number.MAX_VALUE], function (n) {
|
let d = dv[pos]
|
||||||
// cases:
|
y.ds.iterate(new ID(user, 0), new ID(user, Number.MAX_VALUE), function (n) {
|
||||||
// 1. d deletes something to the right of n
|
// cases:
|
||||||
// => go to next n (break)
|
// 1. d deletes something to the right of n
|
||||||
// 2. d deletes something to the left of n
|
// => go to next n (break)
|
||||||
// => create deletions
|
// 2. d deletes something to the left of n
|
||||||
// => reset d accordingly
|
// => create deletions
|
||||||
// *)=> if d doesn't delete anything anymore, go to next d (continue)
|
// => reset d accordingly
|
||||||
// 3. not 2) and d deletes something that also n deletes
|
// *)=> if d doesn't delete anything anymore, go to next d (continue)
|
||||||
// => reset d so that it doesn't contain n's deletion
|
// 3. not 2) and d deletes something that also n deletes
|
||||||
// *)=> if d does not delete anything anymore, go to next d (continue)
|
// => reset d so that it doesn't contain n's deletion
|
||||||
while (d != null) {
|
// *)=> if d does not delete anything anymore, go to next d (continue)
|
||||||
var diff = 0 // describe the diff of length in 1) and 2)
|
while (d != null) {
|
||||||
if (n.id[1] + n.len <= d[0]) {
|
var diff = 0 // describe the diff of length in 1) and 2)
|
||||||
// 1)
|
if (n._id.clock + n.len <= d[0]) {
|
||||||
break
|
// 1)
|
||||||
} else if (d[0] < n.id[1]) {
|
break
|
||||||
// 2)
|
} else if (d[0] < n._id.clock) {
|
||||||
// delete maximum the len of d
|
// 2)
|
||||||
// else delete as much as possible
|
// delete maximum the len of d
|
||||||
diff = Math.min(n.id[1] - d[0], d[1])
|
// else delete as much as possible
|
||||||
deleteItemRange(y, user, d[0], diff)
|
diff = Math.min(n._id.clock - d[0], d[1])
|
||||||
// deletions.push([user, d[0], diff, d[2]])
|
deleteItemRange(y, user, d[0], diff)
|
||||||
} else {
|
// deletions.push([user, d[0], diff, d[2]])
|
||||||
// 3)
|
} else {
|
||||||
diff = n.id[1] + n.len - d[0] // never null (see 1)
|
// 3)
|
||||||
if (d[2] && !n.gc) {
|
diff = n._id.clock + n.len - d[0] // never null (see 1)
|
||||||
// d marks as gc'd but n does not
|
if (d[2] && !n.gc) {
|
||||||
// then delete either way
|
// d marks as gc'd but n does not
|
||||||
deleteItemRange(y, user, d[0], Math.min(diff, d[1]))
|
// then delete either way
|
||||||
// deletions.push([user, d[0], Math.min(diff, d[1]), d[2]])
|
deleteItemRange(y, user, d[0], Math.min(diff, d[1]))
|
||||||
|
// deletions.push([user, d[0], Math.min(diff, d[1]), d[2]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (d[1] <= diff) {
|
||||||
|
// d doesn't delete anything anymore
|
||||||
|
d = dv[++pos]
|
||||||
|
} else {
|
||||||
|
d[0] = d[0] + diff // reset pos
|
||||||
|
d[1] = d[1] - diff // reset length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (d[1] <= diff) {
|
})
|
||||||
// d doesn't delete anything anymore
|
// for the rest.. just apply it
|
||||||
d = dv[++pos]
|
for (; pos < dv.length; pos++) {
|
||||||
} else {
|
d = dv[pos]
|
||||||
d[0] = d[0] + diff // reset pos
|
deleteItemRange(y, user, d[0], d[1])
|
||||||
d[1] = d[1] - diff // reset length
|
// deletions.push([user, d[0], d[1], d[2]])
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
// for the rest.. just apply it
|
|
||||||
for (; pos < dv.length; pos++) {
|
|
||||||
d = dv[pos]
|
|
||||||
deleteItemRange(y, user, d[0], d[1])
|
|
||||||
// deletions.push([user, d[0], d[1], d[2]])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { getStruct } from '../Util/structReferences.js'
|
import { getStruct } from '../Util/structReferences.js'
|
||||||
import BinaryDecoder from '../Binary/Decoder.js'
|
import BinaryDecoder from '../Binary/Decoder.js'
|
||||||
|
import Delete from '../Struct/Delete.js'
|
||||||
|
|
||||||
class MissingEntry {
|
class MissingEntry {
|
||||||
constructor (decoder, missing, struct) {
|
constructor (decoder, missing, struct) {
|
||||||
@ -16,24 +17,26 @@ class MissingEntry {
|
|||||||
*/
|
*/
|
||||||
function _integrateRemoteStructHelper (y, struct) {
|
function _integrateRemoteStructHelper (y, struct) {
|
||||||
struct._integrate(y)
|
struct._integrate(y)
|
||||||
let msu = y._missingStructs.get(struct._id.user)
|
if (!(struct instanceof Delete)) {
|
||||||
if (msu != null) {
|
let msu = y._missingStructs.get(struct._id.user)
|
||||||
let len = struct._length
|
if (msu != null) {
|
||||||
for (let i = 0; i < len; i++) {
|
let len = struct._length
|
||||||
if (msu.has(struct._id.clock + i)) {
|
for (let i = 0; i < len; i++) {
|
||||||
let msuc = msu.get(struct._id.clock + i)
|
if (msu.has(struct._id.clock + i)) {
|
||||||
msuc.forEach(missingDef => {
|
let msuc = msu.get(struct._id.clock + i)
|
||||||
missingDef.missing--
|
msuc.forEach(missingDef => {
|
||||||
if (missingDef.missing === 0) {
|
missingDef.missing--
|
||||||
let missing = missingDef.struct._fromBinary(y, missingDef.decoder)
|
if (missingDef.missing === 0) {
|
||||||
if (missing.length > 0) {
|
let missing = missingDef.struct._fromBinary(y, missingDef.decoder)
|
||||||
console.error('Missing should be empty!')
|
if (missing.length > 0) {
|
||||||
} else {
|
console.error('Missing should be empty!')
|
||||||
y._readyToIntegrate.push(missingDef.struct)
|
} else {
|
||||||
|
y._readyToIntegrate.push(missingDef.struct)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
msu.delete(struct._id.clock)
|
||||||
msu.delete(struct._id.clock)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,18 @@ import { stringifyUpdate } from './update.js'
|
|||||||
import { stringifySyncStep1 } from './syncStep1.js'
|
import { stringifySyncStep1 } from './syncStep1.js'
|
||||||
import { stringifySyncStep2 } from './syncStep2.js'
|
import { stringifySyncStep2 } from './syncStep2.js'
|
||||||
|
|
||||||
export function messageToString (buffer) {
|
export function messageToString (y, buffer) {
|
||||||
let decoder = new BinaryDecoder(buffer)
|
let decoder = new BinaryDecoder(buffer)
|
||||||
decoder.readVarString() // read roomname
|
decoder.readVarString() // read roomname
|
||||||
let type = decoder.readVarString()
|
let type = decoder.readVarString()
|
||||||
let strBuilder = []
|
let strBuilder = []
|
||||||
strBuilder.push('\n === ' + type + ' ===\n')
|
strBuilder.push('\n === ' + type + ' ===\n')
|
||||||
if (type === 'update') {
|
if (type === 'update') {
|
||||||
stringifyUpdate(decoder, strBuilder)
|
stringifyUpdate(y, decoder, strBuilder)
|
||||||
} else if (type === 'sync step 1') {
|
} else if (type === 'sync step 1') {
|
||||||
stringifySyncStep1(decoder, strBuilder)
|
stringifySyncStep1(y, decoder, strBuilder)
|
||||||
} else if (type === 'sync step 2') {
|
} else if (type === 'sync step 2') {
|
||||||
stringifySyncStep2(decoder, strBuilder)
|
stringifySyncStep2(y, decoder, strBuilder)
|
||||||
} else {
|
} else {
|
||||||
strBuilder.push('-- Unknown message type - probably an encoding issue!!!')
|
strBuilder.push('-- Unknown message type - probably an encoding issue!!!')
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,9 @@ import BinaryEncoder from '../Binary/Encoder.js'
|
|||||||
import { readStateSet, writeStateSet } from './stateSet.js'
|
import { readStateSet, writeStateSet } from './stateSet.js'
|
||||||
import { writeDeleteSet } from './deleteSet.js'
|
import { writeDeleteSet } from './deleteSet.js'
|
||||||
import ID from '../Util/ID.js'
|
import ID from '../Util/ID.js'
|
||||||
|
import { RootFakeUserID } from '../Util/RootID.js'
|
||||||
|
|
||||||
export function stringifySyncStep1 (decoder, strBuilder) {
|
export function stringifySyncStep1 (y, decoder, strBuilder) {
|
||||||
let auth = decoder.readVarString()
|
let auth = decoder.readVarString()
|
||||||
let protocolVersion = decoder.readVarUint()
|
let protocolVersion = decoder.readVarUint()
|
||||||
strBuilder.push(`
|
strBuilder.push(`
|
||||||
@ -31,10 +32,13 @@ export function sendSyncStep1 (connector, syncUser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function writeStructs (encoder, decoder, y, ss) {
|
export default function writeStructs (encoder, decoder, y, ss) {
|
||||||
for (let [user, clock] of ss) {
|
for (let user of y.ss.state.keys()) {
|
||||||
y.os.iterate(new ID(user, clock), null, function (struct) {
|
let clock = ss.get(user) || 0
|
||||||
struct._toBinary(encoder)
|
if (user !== RootFakeUserID) {
|
||||||
})
|
y.os.iterate(new ID(user, clock), new ID(user, Number.MAX_VALUE), function (struct) {
|
||||||
|
struct._toBinary(encoder)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,10 @@ import { integrateRemoteStructs } from './integrateRemoteStructs.js'
|
|||||||
import { stringifyUpdate } from './update.js'
|
import { stringifyUpdate } from './update.js'
|
||||||
import { readDeleteSet } from './deleteSet.js'
|
import { readDeleteSet } from './deleteSet.js'
|
||||||
|
|
||||||
export function stringifySyncStep2 (decoder, strBuilder) {
|
export function stringifySyncStep2 (y, decoder, strBuilder) {
|
||||||
strBuilder.push(' - auth: ' + decoder.readVarString() + '\n')
|
strBuilder.push(' - auth: ' + decoder.readVarString() + '\n')
|
||||||
strBuilder.push(' == OS: \n')
|
strBuilder.push(' == OS: \n')
|
||||||
stringifyUpdate(decoder, strBuilder)
|
stringifyUpdate(y, decoder, strBuilder)
|
||||||
// write DS to string
|
// write DS to string
|
||||||
strBuilder.push(' == DS: \n')
|
strBuilder.push(' == DS: \n')
|
||||||
let len = decoder.readUint32()
|
let len = decoder.readUint32()
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
|
|
||||||
import { getStruct } from '../Util/structReferences.js'
|
import { getStruct } from '../Util/structReferences.js'
|
||||||
|
|
||||||
export function stringifyUpdate (decoder, strBuilder) {
|
export function stringifyUpdate (y, decoder, strBuilder) {
|
||||||
while (decoder.length !== decoder.pos) {
|
while (decoder.length !== decoder.pos) {
|
||||||
let reference = decoder.readVarUint()
|
let reference = decoder.readVarUint()
|
||||||
let Constr = getStruct(reference)
|
let Constr = getStruct(reference)
|
||||||
let struct = new Constr()
|
let struct = new Constr()
|
||||||
let missing = struct._fromBinary(decoder)
|
let missing = struct._fromBinary(y, decoder)
|
||||||
let logMessage = struct._logString()
|
let logMessage = struct._logString()
|
||||||
if (missing.length > 0) {
|
if (missing.length > 0) {
|
||||||
logMessage += missing.map(m => m._logString()).join(', ')
|
logMessage += missing.map(m => m._logString()).join(', ')
|
||||||
|
@ -3,25 +3,26 @@ import ID from '../Util/ID.js'
|
|||||||
|
|
||||||
class DSNode {
|
class DSNode {
|
||||||
constructor (id, len, gc) {
|
constructor (id, len, gc) {
|
||||||
this.id = id
|
this._id = id
|
||||||
this.len = len
|
this.len = len
|
||||||
this.gc = gc
|
this.gc = gc
|
||||||
}
|
}
|
||||||
clone () {
|
clone () {
|
||||||
return new DSNode(this.id, this.len, this.gc)
|
return new DSNode(this._id, this.len, this.gc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DeleteStore extends Tree {
|
export default class DeleteStore extends Tree {
|
||||||
isDeleted (id) {
|
isDeleted (id) {
|
||||||
var n = this.ds.findWithUpperBound(id)
|
var n = this.findWithUpperBound(id)
|
||||||
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len
|
return n !== null && n._id.user === id.user && id.clock < n._id.clock + n.len
|
||||||
}
|
}
|
||||||
|
// TODO: put this in function (and all other methods)
|
||||||
applyMissingDeletesOnStruct (struct) {
|
applyMissingDeletesOnStruct (struct) {
|
||||||
const strID = struct._id
|
const strID = struct._id
|
||||||
// find most right delete
|
// find most right delete
|
||||||
let n = this.findWithUpperBound(new ID(strID.user, strID.clock + struct.length - 1))
|
let n = this.findWithUpperBound(new ID(strID.user, strID.clock + struct._length - 1))
|
||||||
if (n === null || n.id.user !== strID.user || n.id.clock + n.length <= strID.clock) {
|
if (n === null || n._id.user !== strID.user || n._id.clock + n.len <= strID.clock) {
|
||||||
// struct is not deleted
|
// struct is not deleted
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -37,22 +38,22 @@ export default class DeleteStore extends Tree {
|
|||||||
throw new Error('length must be defined')
|
throw new Error('length must be defined')
|
||||||
}
|
}
|
||||||
var n = this.findWithUpperBound(id)
|
var n = this.findWithUpperBound(id)
|
||||||
if (n != null && n.id.user === id.user) {
|
if (n != null && n._id.user === id.user) {
|
||||||
if (n.id.clock <= id.clock && id.clock <= n.id.clock + n.len) {
|
if (n._id.clock <= id.clock && id.clock <= n._id.clock + n.len) {
|
||||||
// id is in n's range
|
// id is in n's range
|
||||||
var diff = id.clock + length - (n.id.clock + n.len) // overlapping right
|
var diff = id.clock + length - (n._id.clock + n.len) // overlapping right
|
||||||
if (diff > 0) {
|
if (diff > 0) {
|
||||||
// id+length overlaps n
|
// id+length overlaps n
|
||||||
if (!n.gc) {
|
if (!n.gc) {
|
||||||
n.len += diff
|
n.len += diff
|
||||||
} else {
|
} else {
|
||||||
diff = n.id.clock + n.len - id.clock // overlapping left (id till n.end)
|
diff = n._id.clock + n.len - id.clock // overlapping left (id till n.end)
|
||||||
if (diff < length) {
|
if (diff < length) {
|
||||||
// a partial deletion
|
// a partial deletion
|
||||||
let nId = id.clone()
|
let nId = id.clone()
|
||||||
nId.clock += diff
|
nId.clock += diff
|
||||||
n = new DSNode(nId, length - diff, false)
|
n = new DSNode(nId, length - diff, false)
|
||||||
this.ds.put(n)
|
this.put(n)
|
||||||
} else {
|
} else {
|
||||||
// already gc'd
|
// already gc'd
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -67,21 +68,21 @@ export default class DeleteStore extends Tree {
|
|||||||
} else {
|
} else {
|
||||||
// cannot extend left (there is no left!)
|
// cannot extend left (there is no left!)
|
||||||
n = new DSNode(id, length, false)
|
n = new DSNode(id, length, false)
|
||||||
this.ds.put(n) // TODO: you double-put !!
|
this.put(n) // TODO: you double-put !!
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// cannot extend left
|
// cannot extend left
|
||||||
n = new DSNode(id, length, false)
|
n = new DSNode(id, length, false)
|
||||||
this.ds.put(n)
|
this.put(n)
|
||||||
}
|
}
|
||||||
// can extend right?
|
// can extend right?
|
||||||
var next = this.ds.findNext(n.id)
|
var next = this.findNext(n._id)
|
||||||
if (
|
if (
|
||||||
next != null &&
|
next != null &&
|
||||||
n.id.user === next.id.user &&
|
n._id.user === next._id.user &&
|
||||||
n.id.clock + n.len >= next.id.clock
|
n._id.clock + n.len >= next._id.clock
|
||||||
) {
|
) {
|
||||||
diff = n.id.clock + n.len - next.id.clock // from next.start to n.end
|
diff = n._id.clock + n.len - next._id.clock // from next.start to n.end
|
||||||
while (diff >= 0) {
|
while (diff >= 0) {
|
||||||
// n overlaps with next
|
// n overlaps with next
|
||||||
if (next.gc) {
|
if (next.gc) {
|
||||||
@ -92,7 +93,7 @@ export default class DeleteStore extends Tree {
|
|||||||
diff = diff - next.len // missing range after next
|
diff = diff - next.len // missing range after next
|
||||||
if (diff > 0) {
|
if (diff > 0) {
|
||||||
this.put(n) // unneccessary? TODO!
|
this.put(n) // unneccessary? TODO!
|
||||||
this.markDeleted(new ID(next.id.user, next.id.clock + next.len), diff)
|
this.markDeleted(new ID(next._id.user, next._id.clock + next.len), diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@ -101,19 +102,19 @@ export default class DeleteStore extends Tree {
|
|||||||
if (diff > next.len) {
|
if (diff > next.len) {
|
||||||
// n is even longer than next
|
// n is even longer than next
|
||||||
// get next.next, and try to extend it
|
// get next.next, and try to extend it
|
||||||
var _next = this.findNext(next.id)
|
var _next = this.findNext(next._id)
|
||||||
this.delete(next.id)
|
this.delete(next._id)
|
||||||
if (_next == null || n.id.user !== _next.id.user) {
|
if (_next == null || n._id.user !== _next._id.user) {
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
next = _next
|
next = _next
|
||||||
diff = n.id.clock + n.len - next.id.clock // from next.start to n.end
|
diff = n._id.clock + n.len - next._id.clock // from next.start to n.end
|
||||||
// continue!
|
// continue!
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// n just partially overlaps with next. extend n, delete next, and break this loop
|
// n just partially overlaps with next. extend n, delete next, and break this loop
|
||||||
n.len += next.len - diff
|
n.len += next.len - diff
|
||||||
this.delete(next.id)
|
this.delete(next._id)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Tree from '../Util/Tree.js'
|
import Tree from '../Util/Tree.js'
|
||||||
import RootID from '../Util/ID.js'
|
import RootID from '../Util/RootID.js'
|
||||||
import { getStruct } from '../Util/structReferences.js'
|
import { getStruct } from '../Util/structReferences.js'
|
||||||
|
|
||||||
export default class OperationStore extends Tree {
|
export default class OperationStore extends Tree {
|
||||||
@ -10,10 +10,12 @@ export default class OperationStore extends Tree {
|
|||||||
get (id) {
|
get (id) {
|
||||||
let struct = this.find(id)
|
let struct = this.find(id)
|
||||||
if (struct === null && id instanceof RootID) {
|
if (struct === null && id instanceof RootID) {
|
||||||
let Constr = getStruct(id.type)
|
const Constr = getStruct(id.type)
|
||||||
|
const y = this.y
|
||||||
struct = new Constr()
|
struct = new Constr()
|
||||||
struct._id = id
|
struct._id = id
|
||||||
struct._parent = this.y
|
struct._parent = y
|
||||||
|
struct._integrate(y)
|
||||||
this.put(struct)
|
this.put(struct)
|
||||||
}
|
}
|
||||||
return struct
|
return struct
|
||||||
|
@ -4,12 +4,12 @@ export default class StateStore {
|
|||||||
constructor (y) {
|
constructor (y) {
|
||||||
this.y = y
|
this.y = y
|
||||||
this.state = new Map()
|
this.state = new Map()
|
||||||
this.currentClock = 0
|
|
||||||
}
|
}
|
||||||
getNextID (len) {
|
getNextID (len) {
|
||||||
let id = new ID(this.y.userID, this.currentClock)
|
const user = this.y.userID
|
||||||
this.currentClock += len
|
const state = this.getState(user)
|
||||||
return id
|
this.setState(user, state + len)
|
||||||
|
return new ID(user, state)
|
||||||
}
|
}
|
||||||
updateRemoteState (struct) {
|
updateRemoteState (struct) {
|
||||||
let user = struct._id.user
|
let user = struct._id.user
|
||||||
@ -27,4 +27,8 @@ export default class StateStore {
|
|||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
setState (user, state) {
|
||||||
|
// TODO: modify missingi structs here
|
||||||
|
this.state.set(user, state)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,35 @@
|
|||||||
import { getReference } from '../Util/structReferences.js'
|
import { getReference } from '../Util/structReferences.js'
|
||||||
|
import ID from '../Util/ID.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all items in an ID-range
|
||||||
|
* TODO: implement getItemCleanStartNode for better performance (only one lookup)
|
||||||
|
*/
|
||||||
export function deleteItemRange (y, user, clock, range) {
|
export function deleteItemRange (y, user, clock, range) {
|
||||||
let items = y.os.getItems(this._target, this._length)
|
const createDelete = y.connector._forwardAppliedStructs
|
||||||
for (let i = items.length - 1; i >= 0; i--) {
|
let item = y.os.getItemCleanStart(new ID(user, clock))
|
||||||
items[i]._delete(y, false)
|
if (item !== null) {
|
||||||
|
if (!item._deleted) {
|
||||||
|
item._splitAt(y, range)
|
||||||
|
item._delete(y, createDelete)
|
||||||
|
}
|
||||||
|
let itemLen = item._length
|
||||||
|
range -= itemLen
|
||||||
|
clock += itemLen
|
||||||
|
if (range > 0) {
|
||||||
|
let node = y.os.findNode(new ID(user, clock))
|
||||||
|
while (node !== null && range > 0 && node.val._id.equals(new ID(user, clock))) {
|
||||||
|
const nodeVal = node.val
|
||||||
|
if (!nodeVal._deleted) {
|
||||||
|
nodeVal._splitAt(y, range)
|
||||||
|
nodeVal._delete(y, createDelete)
|
||||||
|
}
|
||||||
|
const nodeLen = nodeVal._length
|
||||||
|
range -= nodeLen
|
||||||
|
clock += nodeLen
|
||||||
|
node = node.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,6 +44,7 @@ export default class Delete {
|
|||||||
_fromBinary (y, decoder) {
|
_fromBinary (y, decoder) {
|
||||||
this._targetID = decoder.readID()
|
this._targetID = decoder.readID()
|
||||||
this._length = decoder.readVarUint()
|
this._length = decoder.readVarUint()
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
_toBinary (encoder) {
|
_toBinary (encoder) {
|
||||||
encoder.writeUint8(getReference(this.constructor))
|
encoder.writeUint8(getReference(this.constructor))
|
||||||
|
@ -34,6 +34,9 @@ export default class Item {
|
|||||||
this._parentSub = null
|
this._parentSub = null
|
||||||
this._deleted = false
|
this._deleted = false
|
||||||
}
|
}
|
||||||
|
get _lastId () {
|
||||||
|
return new ID(this._id.user, this._id.clock + this._length - 1)
|
||||||
|
}
|
||||||
get _length () {
|
get _length () {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@ -61,6 +64,17 @@ export default class Item {
|
|||||||
del._length = this._length
|
del._length = this._length
|
||||||
del._integrate(y, true)
|
del._integrate(y, true)
|
||||||
}
|
}
|
||||||
|
const parent = this._parent
|
||||||
|
if (parent !== y && !parent._deleted) {
|
||||||
|
y._transactionChangedTypes.set(parent, this._parentSub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* This is called right before this struct receives any children.
|
||||||
|
* It can be overwritten to apply pending changes before applying remote changes
|
||||||
|
*/
|
||||||
|
_beforeChange () {
|
||||||
|
// nop
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* - Integrate the struct so that other types/structs can see it
|
* - Integrate the struct so that other types/structs can see it
|
||||||
@ -68,12 +82,26 @@ export default class Item {
|
|||||||
* - Check if this is struct deleted
|
* - Check if this is struct deleted
|
||||||
*/
|
*/
|
||||||
_integrate (y) {
|
_integrate (y) {
|
||||||
|
const parent = this._parent
|
||||||
const selfID = this._id
|
const selfID = this._id
|
||||||
|
const userState = selfID === null ? 0 : y.ss.getState(selfID.user)
|
||||||
if (selfID === null) {
|
if (selfID === null) {
|
||||||
this._id = y.ss.getNextID(this._length)
|
this._id = y.ss.getNextID(this._length)
|
||||||
} else if (selfID.clock < y.ss.getState(selfID.user)) {
|
} else if (selfID.user === RootFakeUserID) {
|
||||||
|
// nop
|
||||||
|
} else if (selfID.clock < userState) {
|
||||||
// already applied..
|
// already applied..
|
||||||
return []
|
return []
|
||||||
|
} else if (selfID.clock === userState) {
|
||||||
|
y.ss.setState(selfID.user, userState + this._length)
|
||||||
|
} else {
|
||||||
|
// missing content from user
|
||||||
|
throw new Error('Can not apply yet!')
|
||||||
|
}
|
||||||
|
if (!parent._deleted && !y._transactionChangedTypes.has(parent) && !y._transactionNewTypes.has(parent)) {
|
||||||
|
// this is the first time parent is updated
|
||||||
|
// or this types is new
|
||||||
|
this._parent._beforeChange()
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
# $this has to find a unique position between origin and the next known character
|
# $this has to find a unique position between origin and the next known character
|
||||||
@ -96,7 +124,7 @@ export default class Item {
|
|||||||
if (this._left !== null) {
|
if (this._left !== null) {
|
||||||
o = this._left._right
|
o = this._left._right
|
||||||
} else if (this._parentSub !== null) {
|
} else if (this._parentSub !== null) {
|
||||||
o = this._parent._map.get(this._parentSub)
|
o = this._parent._map.get(this._parentSub) || null
|
||||||
} else {
|
} else {
|
||||||
o = this._parent._start
|
o = this._parent._start
|
||||||
}
|
}
|
||||||
@ -124,14 +152,36 @@ export default class Item {
|
|||||||
}
|
}
|
||||||
o = o._right
|
o = o._right
|
||||||
}
|
}
|
||||||
|
// reconnect left/right + update parent map/start if necessary
|
||||||
|
const parentSub = this._parentSub
|
||||||
if (this._left === null) {
|
if (this._left === null) {
|
||||||
if (this._parentSub !== null) {
|
let right
|
||||||
this._parent._map.set(this._parentSub, this)
|
if (parentSub !== null) {
|
||||||
|
const pmap = parent._map
|
||||||
|
right = pmap.get(parentSub) || null
|
||||||
|
pmap.set(parentSub, this)
|
||||||
} else {
|
} else {
|
||||||
this._parent._start = this
|
right = parent._start
|
||||||
|
parent._start = this
|
||||||
|
}
|
||||||
|
this._right = right
|
||||||
|
if (right !== null) {
|
||||||
|
right._left = this
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const left = this._left
|
||||||
|
const right = left._right
|
||||||
|
this._right = right
|
||||||
|
left._right = this
|
||||||
|
if (right !== null) {
|
||||||
|
right._left = this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
y.os.put(this)
|
y.os.put(this)
|
||||||
|
if (parent !== y && !parent._deleted) {
|
||||||
|
y._transactionChangedTypes.set(parent, parentSub)
|
||||||
|
}
|
||||||
|
|
||||||
if (this._id.user !== RootFakeUserID) {
|
if (this._id.user !== RootFakeUserID) {
|
||||||
if (y.connector._forwardAppliedStructs || this._id.user === y.userID) {
|
if (y.connector._forwardAppliedStructs || this._id.user === y.userID) {
|
||||||
y.connector.broadcastStruct(this)
|
y.connector.broadcastStruct(this)
|
||||||
@ -160,10 +210,10 @@ export default class Item {
|
|||||||
encoder.writeUint8(info)
|
encoder.writeUint8(info)
|
||||||
encoder.writeID(this._id)
|
encoder.writeID(this._id)
|
||||||
if (info & 0b1) {
|
if (info & 0b1) {
|
||||||
encoder.writeID(this._origin._id)
|
encoder.writeID(this._origin._lastId)
|
||||||
}
|
}
|
||||||
if (info & 0b10) {
|
if (info & 0b10) {
|
||||||
encoder.writeID(this._left._id)
|
encoder.writeID(this._left._lastId)
|
||||||
}
|
}
|
||||||
if (info & 0b100) {
|
if (info & 0b100) {
|
||||||
encoder.writeID(this._right_origin._id)
|
encoder.writeID(this._right_origin._id)
|
||||||
@ -179,7 +229,8 @@ export default class Item {
|
|||||||
_fromBinary (y, decoder) {
|
_fromBinary (y, decoder) {
|
||||||
let missing = []
|
let missing = []
|
||||||
const info = decoder.readUint8()
|
const info = decoder.readUint8()
|
||||||
this._id = decoder.readID()
|
const id = decoder.readID()
|
||||||
|
this._id = id
|
||||||
// read origin
|
// read origin
|
||||||
if (info & 0b1) {
|
if (info & 0b1) {
|
||||||
// origin != null
|
// origin != null
|
||||||
@ -214,9 +265,9 @@ export default class Item {
|
|||||||
// right != null
|
// right != null
|
||||||
const rightID = decoder.readID()
|
const rightID = decoder.readID()
|
||||||
if (this._right_origin === null) {
|
if (this._right_origin === null) {
|
||||||
const right = y.os.getCleanStart(rightID)
|
const right = y.os.getItemCleanStart(rightID)
|
||||||
if (right === null) {
|
if (right === null) {
|
||||||
missing.push(right)
|
missing.push(rightID)
|
||||||
} else {
|
} else {
|
||||||
this._right = right
|
this._right = right
|
||||||
this._right_origin = right
|
this._right_origin = right
|
||||||
@ -230,7 +281,7 @@ export default class Item {
|
|||||||
if (this._parent === null) {
|
if (this._parent === null) {
|
||||||
const parent = y.os.get(parentID)
|
const parent = y.os.get(parentID)
|
||||||
if (parent === null) {
|
if (parent === null) {
|
||||||
missing.push(parent)
|
missing.push(parentID)
|
||||||
} else {
|
} else {
|
||||||
this._parent = parent
|
this._parent = parent
|
||||||
}
|
}
|
||||||
@ -239,11 +290,15 @@ export default class Item {
|
|||||||
if (this._origin !== null) {
|
if (this._origin !== null) {
|
||||||
this._parent = this._origin._parent
|
this._parent = this._origin._parent
|
||||||
} else if (this._right_origin !== null) {
|
} else if (this._right_origin !== null) {
|
||||||
this._parent = this._origin._parent
|
this._parent = this._right_origin._parent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (info & 0b1000) {
|
if (info & 0b1000) {
|
||||||
this._parentSub = decoder.readVarString()
|
// TODO: maybe put this in read parent condition (you can also read parentsub from left/right)
|
||||||
|
this._parentSub = JSON.parse(decoder.readVarString())
|
||||||
|
}
|
||||||
|
if (y.ss.getState(id.user) < id.clock) {
|
||||||
|
missing.push(new ID(id.user, id.clock - 1))
|
||||||
}
|
}
|
||||||
return missing
|
return missing
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ export default class ItemJSON extends Item {
|
|||||||
let len = decoder.readVarUint()
|
let len = decoder.readVarUint()
|
||||||
this._content = new Array(len)
|
this._content = new Array(len)
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
this._content[i] = JSON.parse(decoder.readVarString())
|
const ctnt = decoder.readVarString()
|
||||||
|
this._content[i] = JSON.parse(ctnt)
|
||||||
}
|
}
|
||||||
return missing
|
return missing
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,18 @@
|
|||||||
import Item from './Item.js'
|
import Item from './Item.js'
|
||||||
|
import EventHandler from '../Util/EventHandler.js'
|
||||||
|
|
||||||
|
// restructure children as if they were inserted one after another
|
||||||
|
function integrateChildren (y, start) {
|
||||||
|
let right
|
||||||
|
do {
|
||||||
|
right = start._right
|
||||||
|
start._right = null
|
||||||
|
start._right_origin = null
|
||||||
|
start._origin = start._left
|
||||||
|
start._integrate(y)
|
||||||
|
start = right
|
||||||
|
} while (right !== null)
|
||||||
|
}
|
||||||
|
|
||||||
export default class Type extends Item {
|
export default class Type extends Item {
|
||||||
constructor () {
|
constructor () {
|
||||||
@ -6,24 +20,46 @@ export default class Type extends Item {
|
|||||||
this._map = new Map()
|
this._map = new Map()
|
||||||
this._start = null
|
this._start = null
|
||||||
this._y = null
|
this._y = null
|
||||||
|
this._eventHandler = new EventHandler()
|
||||||
|
}
|
||||||
|
observe (f) {
|
||||||
|
this._eventHandler.addEventListener(f)
|
||||||
|
}
|
||||||
|
unobserve (f) {
|
||||||
|
this._eventHandler.removeEventListener(f)
|
||||||
}
|
}
|
||||||
_integrate (y) {
|
_integrate (y) {
|
||||||
|
y._transactionNewTypes.add(this)
|
||||||
super._integrate(y)
|
super._integrate(y)
|
||||||
this._y = y
|
this._y = y
|
||||||
|
// when integrating children we must make sure to
|
||||||
|
// integrate start
|
||||||
|
const start = this._start
|
||||||
|
if (start !== null) {
|
||||||
|
this._start = null
|
||||||
|
integrateChildren(y, start)
|
||||||
|
}
|
||||||
|
// integrate map children
|
||||||
|
const map = this._map
|
||||||
|
for (let [key, t] of map) {
|
||||||
|
map.delete(key)
|
||||||
|
integrateChildren(y, t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_delete (y) {
|
_delete (y, createDelete) {
|
||||||
super._delete(y)
|
super._delete(y, createDelete)
|
||||||
|
y._transactionChangedTypes.delete(this)
|
||||||
// delete map types
|
// delete map types
|
||||||
for (let value of this._map.values()) {
|
for (let value of this._map.values()) {
|
||||||
if (value instanceof Item && !value._deleted) {
|
if (value instanceof Item && !value._deleted) {
|
||||||
value._delete()
|
value._delete(y, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// delete array types
|
// delete array types
|
||||||
let t = this._start
|
let t = this._start
|
||||||
while (t !== null) {
|
while (t !== null) {
|
||||||
if (!t._deleted) {
|
if (!t._deleted) {
|
||||||
t._delete()
|
t._delete(y, false)
|
||||||
}
|
}
|
||||||
t = t._right
|
t = t._right
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,16 @@ import Type from '../Struct/Type.js'
|
|||||||
import ItemJSON from '../Struct/ItemJSON.js'
|
import ItemJSON from '../Struct/ItemJSON.js'
|
||||||
|
|
||||||
export default class YArray extends Type {
|
export default class YArray extends Type {
|
||||||
|
_callObserver () {
|
||||||
|
this._eventHandler.callEventListeners({})
|
||||||
|
}
|
||||||
|
get (i) {
|
||||||
|
// TODO: This can be improved!
|
||||||
|
return this.toArray()[i]
|
||||||
|
}
|
||||||
|
toArray () {
|
||||||
|
return this.map(c => c)
|
||||||
|
}
|
||||||
toJSON () {
|
toJSON () {
|
||||||
return this.map(c => {
|
return this.map(c => {
|
||||||
if (c instanceof Type) {
|
if (c instanceof Type) {
|
||||||
@ -11,6 +21,7 @@ export default class YArray extends Type {
|
|||||||
return c.toString()
|
return c.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return c
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
map (f) {
|
map (f) {
|
||||||
@ -25,11 +36,15 @@ export default class YArray extends Type {
|
|||||||
let n = this._start
|
let n = this._start
|
||||||
while (n !== null) {
|
while (n !== null) {
|
||||||
if (!n._deleted) {
|
if (!n._deleted) {
|
||||||
const content = n._content
|
if (n instanceof Type) {
|
||||||
const contentLen = content.length
|
f(n, pos++, this)
|
||||||
for (let i = 0; i < contentLen; i++) {
|
} else {
|
||||||
pos++
|
const content = n._content
|
||||||
f(content[i], pos, this)
|
const contentLen = content.length
|
||||||
|
for (let i = 0; i < contentLen; i++) {
|
||||||
|
pos++
|
||||||
|
f(content[i], pos, this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n = n._right
|
n = n._right
|
||||||
@ -42,14 +57,14 @@ export default class YArray extends Type {
|
|||||||
if (!n._deleted) {
|
if (!n._deleted) {
|
||||||
length += n._length
|
length += n._length
|
||||||
}
|
}
|
||||||
n = n._next
|
n = n._right
|
||||||
}
|
}
|
||||||
return length
|
return length
|
||||||
}
|
}
|
||||||
[Symbol.iterator] () {
|
[Symbol.iterator] () {
|
||||||
return {
|
return {
|
||||||
next: function () {
|
next: function () {
|
||||||
while (this._item !== null && (this._item._deleted || this._item._content.length <= this._itemElement)) {
|
while (this._item !== null && (this._item._deleted || this._item._length <= this._itemElement)) {
|
||||||
// item is deleted or itemElement does not exist (is deleted)
|
// item is deleted or itemElement does not exist (is deleted)
|
||||||
this._item = this._item._right
|
this._item = this._item._right
|
||||||
this._itemElement = 0
|
this._itemElement = 0
|
||||||
@ -58,11 +73,16 @@ export default class YArray extends Type {
|
|||||||
return {
|
return {
|
||||||
done: true
|
done: true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
let content
|
||||||
|
if (this._item instanceof Type) {
|
||||||
|
content = this._item
|
||||||
} else {
|
} else {
|
||||||
return {
|
content = this._item._content[this._itemElement++]
|
||||||
value: [this._count, this._item._content[this._itemElement++]],
|
}
|
||||||
done: false
|
return {
|
||||||
}
|
value: [this._count, content],
|
||||||
|
done: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_item: this._start,
|
_item: this._start,
|
||||||
@ -71,68 +91,99 @@ export default class YArray extends Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete (pos, length = 1) {
|
delete (pos, length = 1) {
|
||||||
let item = this._start
|
this._y.transact(() => {
|
||||||
let count = 0
|
let item = this._start
|
||||||
while (item !== null && length > 0) {
|
let count = 0
|
||||||
if (count < pos && pos < count + item._length) {
|
while (item !== null && length > 0) {
|
||||||
const diffDel = pos - count
|
if (count <= pos && pos < count + item._length) {
|
||||||
item = item
|
const diffDel = pos - count
|
||||||
._splitAt(this._y, diffDel)
|
item = item._splitAt(this._y, diffDel)
|
||||||
._splitAt(this._y, length)
|
item._splitAt(this._y, length)
|
||||||
length -= item._length
|
length -= item._length
|
||||||
item._delete(this._y)
|
item._delete(this._y)
|
||||||
|
}
|
||||||
|
if (!item._deleted) {
|
||||||
|
count += item._length
|
||||||
|
}
|
||||||
|
item = item._right
|
||||||
}
|
}
|
||||||
if (!item._deleted) {
|
if (length > 0) {
|
||||||
count += item._length
|
throw new Error('Delete exceeds the range of the YArray')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
insertAfter (left, content) {
|
||||||
|
const apply = () => {
|
||||||
|
let right
|
||||||
|
if (left === null) {
|
||||||
|
right = this._start
|
||||||
|
} else {
|
||||||
|
right = left._right
|
||||||
|
}
|
||||||
|
let prevJsonIns = null
|
||||||
|
for (let i = 0; i < content.length; i++) {
|
||||||
|
let c = content[i]
|
||||||
|
if (c instanceof Type) {
|
||||||
|
if (prevJsonIns !== null) {
|
||||||
|
if (this._y !== null) {
|
||||||
|
prevJsonIns._integrate(this._y)
|
||||||
|
}
|
||||||
|
left = prevJsonIns
|
||||||
|
prevJsonIns = null
|
||||||
|
}
|
||||||
|
c._origin = left
|
||||||
|
c._left = left
|
||||||
|
c._right = right
|
||||||
|
c._right_origin = right
|
||||||
|
c._parent = this
|
||||||
|
if (this._y !== null) {
|
||||||
|
c._integrate(this._y)
|
||||||
|
} else if (left === null) {
|
||||||
|
this._start = c
|
||||||
|
}
|
||||||
|
left = c
|
||||||
|
} else {
|
||||||
|
if (prevJsonIns === null) {
|
||||||
|
prevJsonIns = new ItemJSON()
|
||||||
|
prevJsonIns._origin = left
|
||||||
|
prevJsonIns._left = left
|
||||||
|
prevJsonIns._right = right
|
||||||
|
prevJsonIns._right_origin = right
|
||||||
|
prevJsonIns._parent = this
|
||||||
|
prevJsonIns._content = []
|
||||||
|
}
|
||||||
|
prevJsonIns._content.push(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prevJsonIns !== null && this._y !== null) {
|
||||||
|
prevJsonIns._integrate(this._y)
|
||||||
}
|
}
|
||||||
item = item._right
|
|
||||||
}
|
}
|
||||||
if (length > 0) {
|
if (this._y !== null) {
|
||||||
throw new Error('Delete exceeds the range of the YArray')
|
this._y.transact(apply)
|
||||||
|
} else {
|
||||||
|
apply()
|
||||||
}
|
}
|
||||||
|
return content
|
||||||
}
|
}
|
||||||
insert (pos, content) {
|
insert (pos, content) {
|
||||||
let left = this._start
|
let left = null
|
||||||
let right = null
|
let right = this._start
|
||||||
let count = 0
|
let count = 0
|
||||||
while (left !== null) {
|
while (right !== null) {
|
||||||
if (count <= pos && pos < count + left._content.length) {
|
if (count <= pos && pos < count + right._length) {
|
||||||
right = left._splitAt(this.y, pos - count)
|
right = right._splitAt(this._y, pos - count)
|
||||||
|
left = right._left
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
count += left._length
|
count += right._length
|
||||||
left = left.right
|
left = right
|
||||||
|
right = right._right
|
||||||
}
|
}
|
||||||
if (pos > count) {
|
if (pos > count) {
|
||||||
throw new Error('Position exceeds array range!')
|
throw new Error('Position exceeds array range!')
|
||||||
}
|
}
|
||||||
let prevJsonIns = null
|
this.insertAfter(left, content)
|
||||||
for (let i = 0; i < content.length; i++) {
|
|
||||||
let c = content[i]
|
|
||||||
if (c instanceof Type) {
|
|
||||||
if (prevJsonIns === null) {
|
|
||||||
prevJsonIns._integrate(this._y)
|
|
||||||
prevJsonIns = null
|
|
||||||
}
|
|
||||||
c._left = left
|
|
||||||
c._origin = left
|
|
||||||
c._right = right
|
|
||||||
c._parent = this
|
|
||||||
} else {
|
|
||||||
if (prevJsonIns === null) {
|
|
||||||
prevJsonIns = new ItemJSON()
|
|
||||||
prevJsonIns._origin = left
|
|
||||||
prevJsonIns._left = left
|
|
||||||
prevJsonIns._right = right
|
|
||||||
prevJsonIns._parent = this
|
|
||||||
prevJsonIns._content = []
|
|
||||||
}
|
|
||||||
prevJsonIns._content.push(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (prevJsonIns !== null) {
|
|
||||||
prevJsonIns._integrate(this._y)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_logString () {
|
_logString () {
|
||||||
let s = super._logString()
|
let s = super._logString()
|
||||||
|
@ -3,6 +3,11 @@ import Item from '../Struct/Item.js'
|
|||||||
import ItemJSON from '../Struct/ItemJSON.js'
|
import ItemJSON from '../Struct/ItemJSON.js'
|
||||||
|
|
||||||
export default class YMap extends Type {
|
export default class YMap extends Type {
|
||||||
|
_callObserver (parentSub) {
|
||||||
|
this._eventHandler.callEventListeners({
|
||||||
|
name: parentSub
|
||||||
|
})
|
||||||
|
}
|
||||||
toJSON () {
|
toJSON () {
|
||||||
const map = {}
|
const map = {}
|
||||||
for (let [key, item] of this._map) {
|
for (let [key, item] of this._map) {
|
||||||
@ -22,22 +27,40 @@ export default class YMap extends Type {
|
|||||||
}
|
}
|
||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
delete (key) {
|
||||||
|
this._y.transact(() => {
|
||||||
|
let c = this._map.get(key)
|
||||||
|
if (c !== undefined) {
|
||||||
|
c._delete(this._y)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
set (key, value) {
|
set (key, value) {
|
||||||
let old = this._map.get(key)
|
this._y.transact(() => {
|
||||||
let v
|
const old = this._map.get(key) || null
|
||||||
if (value instanceof Item) {
|
if (old !== null) {
|
||||||
v = value
|
old._delete(this._y)
|
||||||
} else {
|
}
|
||||||
let v = new ItemJSON()
|
let v
|
||||||
v._content = JSON.stringify(value)
|
if (value instanceof Item) {
|
||||||
}
|
v = value
|
||||||
v._right = old
|
} else {
|
||||||
v._parent = this
|
v = new ItemJSON()
|
||||||
v._parentSub = key
|
v._content = [value]
|
||||||
v._integrate()
|
}
|
||||||
|
v._right = old
|
||||||
|
v._right_origin = old
|
||||||
|
v._parent = this
|
||||||
|
v._parentSub = key
|
||||||
|
v._integrate(this._y)
|
||||||
|
})
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
get (key) {
|
get (key) {
|
||||||
let v = this._map.get(key)
|
let v = this._map.get(key)
|
||||||
|
if (v === undefined || v._deleted) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
if (v instanceof Type) {
|
if (v instanceof Type) {
|
||||||
return v
|
return v
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,53 @@
|
|||||||
|
import ItemString from '../Struct/ItemString.js'
|
||||||
import YArray from './YArray.js'
|
import YArray from './YArray.js'
|
||||||
|
|
||||||
export default class YText extends YArray {
|
export default class YText extends YArray {
|
||||||
|
constructor (string) {
|
||||||
|
super()
|
||||||
|
if (typeof string === 'string') {
|
||||||
|
const start = new ItemString()
|
||||||
|
start._parent = this
|
||||||
|
start._content = string
|
||||||
|
this._start = start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toString () {
|
||||||
|
const strBuilder = []
|
||||||
|
let n = this._start
|
||||||
|
while (n !== null) {
|
||||||
|
if (!n._deleted) {
|
||||||
|
strBuilder.push(n._content)
|
||||||
|
}
|
||||||
|
n = n._right
|
||||||
|
}
|
||||||
|
return strBuilder.join('')
|
||||||
|
}
|
||||||
|
insert (pos, text) {
|
||||||
|
this._y.transact(() => {
|
||||||
|
let left = null
|
||||||
|
let right = this._start
|
||||||
|
let count = 0
|
||||||
|
while (right !== null) {
|
||||||
|
if (count <= pos && pos < count + right._length) {
|
||||||
|
right = right._splitAt(this._y, pos - count)
|
||||||
|
left = right._left
|
||||||
|
break
|
||||||
|
}
|
||||||
|
count += right._length
|
||||||
|
left = right
|
||||||
|
right = right._right
|
||||||
|
}
|
||||||
|
if (pos > count) {
|
||||||
|
throw new Error('Position exceeds array range!')
|
||||||
|
}
|
||||||
|
let item = new ItemString()
|
||||||
|
item._origin = left
|
||||||
|
item._left = left
|
||||||
|
item._right = right
|
||||||
|
item._right_origin = right
|
||||||
|
item._parent = this
|
||||||
|
item._content = text
|
||||||
|
item._integrate(this._y)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import YArray from './YArray.js'
|
|
||||||
|
|
||||||
export default class YXml extends YArray {
|
|
||||||
setDomFilter () {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
toString () {
|
|
||||||
return '<nodeName></nodeName>'
|
|
||||||
}
|
|
||||||
}
|
|
117
src/Type/y-xml/YXmlElement.js
Normal file
117
src/Type/y-xml/YXmlElement.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/* global MutationObserver */
|
||||||
|
|
||||||
|
// import diff from 'fast-diff'
|
||||||
|
import { defaultDomFilter } from './utils.js'
|
||||||
|
|
||||||
|
import YMap from '../YMap.js'
|
||||||
|
import YXmlFragment from './YXmlFragment.js'
|
||||||
|
|
||||||
|
export default class YXmlElement extends YXmlFragment {
|
||||||
|
constructor (arg1, arg2) {
|
||||||
|
super()
|
||||||
|
this.nodeName = null
|
||||||
|
this._scrollElement = null
|
||||||
|
if (typeof arg1 === 'string') {
|
||||||
|
this.nodeName = arg1.toUpperCase()
|
||||||
|
} else if (arg1 != null && arg1.nodeType != null && arg1.nodeType === document.ELEMENT_NODE) {
|
||||||
|
this.nodeName = arg1.nodeName
|
||||||
|
this._setDom(arg1)
|
||||||
|
} else {
|
||||||
|
this.nodeName = 'UNDEFINED'
|
||||||
|
}
|
||||||
|
if (typeof arg2 === 'function') {
|
||||||
|
this._domFilter = arg2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_setDom (dom) {
|
||||||
|
if (this._dom != null) {
|
||||||
|
throw new Error('Only call this method if you know what you are doing ;)')
|
||||||
|
} else if (dom.__yxml != null) { // TODO do i need to check this? - no.. but for dev purps..
|
||||||
|
throw new Error('Already bound to an YXml type')
|
||||||
|
} else {
|
||||||
|
dom.__yxml = this
|
||||||
|
// tag is already set in constructor
|
||||||
|
// set attributes
|
||||||
|
let attrNames = []
|
||||||
|
for (let i = 0; i < dom.attributes.length; i++) {
|
||||||
|
attrNames.push(dom.attributes[i].name)
|
||||||
|
}
|
||||||
|
attrNames = this._domFilter(dom, attrNames)
|
||||||
|
for (let i = 0; i < attrNames.length; i++) {
|
||||||
|
let attrName = attrNames[i]
|
||||||
|
let attrValue = dom.getAttribute(attrName)
|
||||||
|
this.setAttribute(attrName, attrValue)
|
||||||
|
}
|
||||||
|
this.insertDomElements(0, Array.prototype.slice.call(dom.childNodes))
|
||||||
|
if (MutationObserver != null) {
|
||||||
|
this._dom = this._bindToDom(dom)
|
||||||
|
}
|
||||||
|
return dom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_fromBinary (y, decoder) {
|
||||||
|
const missing = super._fromBinary(y, decoder)
|
||||||
|
this.nodeName = decoder.readVarString()
|
||||||
|
return missing
|
||||||
|
}
|
||||||
|
_toBinary (encoder) {
|
||||||
|
super._toBinary(encoder)
|
||||||
|
encoder.writeVarString(this.nodeName)
|
||||||
|
}
|
||||||
|
_integrate (y) {
|
||||||
|
if (this.nodeName === null) {
|
||||||
|
throw new Error('nodeName must be defined!')
|
||||||
|
}
|
||||||
|
if (this._domFilter === defaultDomFilter && this._parent instanceof YXmlFragment) {
|
||||||
|
this._domFilter = this._parent._domFilter
|
||||||
|
}
|
||||||
|
super._integrate(y)
|
||||||
|
}
|
||||||
|
toString () {
|
||||||
|
const attrs = this.getAttributes()
|
||||||
|
const stringBuilder = []
|
||||||
|
for (let key in attrs) {
|
||||||
|
stringBuilder.push(key + '="' + attrs[key] + '"')
|
||||||
|
}
|
||||||
|
const nodeName = this.nodeName.toLocaleLowerCase()
|
||||||
|
const attrsString = stringBuilder.length > 0 ? ' ' + stringBuilder.join(' ') : ''
|
||||||
|
return `<${nodeName}${attrsString}>${super.toString()}</${nodeName}>`
|
||||||
|
}
|
||||||
|
removeAttribute () {
|
||||||
|
return YMap.prototype.delete.apply(this, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
setAttribute () {
|
||||||
|
return YMap.prototype.set.apply(this, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
getAttribute () {
|
||||||
|
return YMap.prototype.get.apply(this, arguments)
|
||||||
|
}
|
||||||
|
|
||||||
|
getAttributes () {
|
||||||
|
const obj = {}
|
||||||
|
for (let [key, value] of this._map) {
|
||||||
|
obj[key] = value._content[0]
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
getDom () {
|
||||||
|
let dom = this._dom
|
||||||
|
if (dom == null) {
|
||||||
|
dom = document.createElement(this.nodeName)
|
||||||
|
dom.__yxml = this
|
||||||
|
let attrs = this.getAttributes()
|
||||||
|
for (let key in attrs) {
|
||||||
|
dom.setAttribute(key, attrs[key])
|
||||||
|
}
|
||||||
|
this.forEach(yxml => {
|
||||||
|
dom.appendChild(yxml.getDom())
|
||||||
|
})
|
||||||
|
if (MutationObserver !== null) {
|
||||||
|
this._dom = this._bindToDom(dom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dom
|
||||||
|
}
|
||||||
|
}
|
167
src/Type/y-xml/YXmlFragment.js
Normal file
167
src/Type/y-xml/YXmlFragment.js
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
/* global MutationObserver */
|
||||||
|
|
||||||
|
import { defaultDomFilter, applyChangesFromDom, reflectChangesOnDom } from './utils.js'
|
||||||
|
|
||||||
|
import YArray from '../YArray.js'
|
||||||
|
import YXmlText from './YXmlText.js'
|
||||||
|
|
||||||
|
function domToYXml (parent, doms) {
|
||||||
|
const types = []
|
||||||
|
doms.forEach(d => {
|
||||||
|
if (d.__yxml != null && d.__yxml !== false) {
|
||||||
|
d.__yxml._unbindFromDom()
|
||||||
|
}
|
||||||
|
if (parent._domFilter(d, []) !== null) {
|
||||||
|
let type
|
||||||
|
if (d.nodeType === document.TEXT_NODE) {
|
||||||
|
type = new YXmlText(d)
|
||||||
|
} else if (d.nodeType === document.ELEMENT_NODE) {
|
||||||
|
type = new YXmlFragment._YXmlElement(d, parent._domFilter)
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported node!')
|
||||||
|
}
|
||||||
|
type.enableSmartScrolling(parent._scrollElement)
|
||||||
|
types.push(type)
|
||||||
|
} else {
|
||||||
|
d.__yxml = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return types
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class YXmlFragment extends YArray {
|
||||||
|
constructor () {
|
||||||
|
super()
|
||||||
|
this._dom = null
|
||||||
|
this._domFilter = defaultDomFilter
|
||||||
|
this._domObserver = null
|
||||||
|
// this function makes sure that either the
|
||||||
|
// dom event is executed, or the yjs observer is executed
|
||||||
|
var token = true
|
||||||
|
this._mutualExclude = f => {
|
||||||
|
if (token) {
|
||||||
|
token = false
|
||||||
|
try {
|
||||||
|
f()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
this._domObserver.takeRecords()
|
||||||
|
token = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Apply Y.Xml events to dom
|
||||||
|
this.observe(reflectChangesOnDom)
|
||||||
|
}
|
||||||
|
enableSmartScrolling (scrollElement) {
|
||||||
|
this._scrollElement = scrollElement
|
||||||
|
this.forEach(xml => {
|
||||||
|
xml.enableSmartScrolling(scrollElement)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setDomFilter (f) {
|
||||||
|
this._domFilter = f
|
||||||
|
this.forEach(xml => {
|
||||||
|
xml.setDomFilter(f)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_callObserver (parentSub) {
|
||||||
|
let event
|
||||||
|
if (parentSub !== null) {
|
||||||
|
event = {
|
||||||
|
type: 'attributeChanged',
|
||||||
|
name: parentSub,
|
||||||
|
value: this.getAttribute(parentSub),
|
||||||
|
target: this
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
event = {
|
||||||
|
type: 'contentChanged',
|
||||||
|
target: this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._eventHandler.callEventListeners(event)
|
||||||
|
}
|
||||||
|
toString () {
|
||||||
|
return this.map(xml => xml.toString()).join('')
|
||||||
|
}
|
||||||
|
_unbindFromDom () {
|
||||||
|
if (this._domObserver != null) {
|
||||||
|
this._domObserver.disconnect()
|
||||||
|
this._domObserver = null
|
||||||
|
}
|
||||||
|
if (this._dom != null) {
|
||||||
|
this._dom.__yxml = null
|
||||||
|
this._dom = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insertDomElementsAfter (prev, doms) {
|
||||||
|
const types = domToYXml(this, doms)
|
||||||
|
return this.insertAfter(prev, types)
|
||||||
|
}
|
||||||
|
insertDomElements (pos, doms) {
|
||||||
|
const types = domToYXml(this, doms)
|
||||||
|
this.insert(pos, types)
|
||||||
|
return types.length
|
||||||
|
}
|
||||||
|
bindToDom (dom) {
|
||||||
|
if (this._dom != null) {
|
||||||
|
this._unbindFromDom()
|
||||||
|
}
|
||||||
|
if (dom.__yxml != null) {
|
||||||
|
dom.__yxml._unbindFromDom()
|
||||||
|
}
|
||||||
|
if (MutationObserver == null) {
|
||||||
|
throw new Error('Not able to bind to a DOM element, because MutationObserver is not available!')
|
||||||
|
}
|
||||||
|
dom.innerHTML = ''
|
||||||
|
this.forEach(t => {
|
||||||
|
dom.insertBefore(t.getDom(), null)
|
||||||
|
})
|
||||||
|
this._dom = dom
|
||||||
|
dom.__yxml = this
|
||||||
|
this._bindToDom(dom)
|
||||||
|
}
|
||||||
|
// binds to a dom element
|
||||||
|
// Only call if dom and YXml are isomorph
|
||||||
|
_bindToDom (dom) {
|
||||||
|
this._domObserverListener = mutations => {
|
||||||
|
this._mutualExclude(() => {
|
||||||
|
let diffChildren = false
|
||||||
|
mutations.forEach(mutation => {
|
||||||
|
if (mutation.type === 'attributes') {
|
||||||
|
let name = mutation.attributeName
|
||||||
|
// check if filter accepts attribute
|
||||||
|
if (this._domFilter(this._dom, [name]).length > 0) {
|
||||||
|
var val = mutation.target.getAttribute(name)
|
||||||
|
if (this.getAttribute(name) !== val) {
|
||||||
|
if (val == null) {
|
||||||
|
this.removeAttribute(name)
|
||||||
|
} else {
|
||||||
|
this.setAttribute(name, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (mutation.type === 'childList') {
|
||||||
|
diffChildren = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (diffChildren) {
|
||||||
|
applyChangesFromDom(this)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this._domObserver = new MutationObserver(this._domObserverListener)
|
||||||
|
const observeOptions = { childList: true }
|
||||||
|
if (this instanceof YXmlFragment._YXmlElement) {
|
||||||
|
observeOptions.attributes = true
|
||||||
|
}
|
||||||
|
this._domObserver.observe(dom, observeOptions)
|
||||||
|
return dom
|
||||||
|
}
|
||||||
|
_beforeChange () {
|
||||||
|
if (this._domObserver != null) {
|
||||||
|
this._domObserverListener(this._domObserver.takeRecords())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
157
src/Type/y-xml/YXmlText.js
Normal file
157
src/Type/y-xml/YXmlText.js
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
/* global getSelection, MutationObserver */
|
||||||
|
|
||||||
|
import diff from 'fast-diff'
|
||||||
|
import YText from '../YText.js'
|
||||||
|
import { getAnchorViewPosition, fixScrollPosition, getBoundingClientRect } from './utils.js'
|
||||||
|
|
||||||
|
function fixPosition (event, pos) {
|
||||||
|
if (event.index <= pos) {
|
||||||
|
if (event.type === 'delete') {
|
||||||
|
return pos - Math.min(pos - event.index, event.length)
|
||||||
|
} else {
|
||||||
|
return pos + 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class YXmlText extends YText {
|
||||||
|
constructor (arg1) {
|
||||||
|
let dom = null
|
||||||
|
let initialText = null
|
||||||
|
if (arg1 != null && arg1.nodeType === document.TEXT_NODE) {
|
||||||
|
dom = arg1
|
||||||
|
initialText = dom.nodeValue
|
||||||
|
}
|
||||||
|
super(initialText)
|
||||||
|
this._dom = null
|
||||||
|
this._domObserver = null
|
||||||
|
this._domObserverListener = null
|
||||||
|
this._scrollElement = null
|
||||||
|
if (dom !== null) {
|
||||||
|
this._setDom(arg1)
|
||||||
|
}
|
||||||
|
var token = true
|
||||||
|
this._mutualExclude = f => {
|
||||||
|
if (token) {
|
||||||
|
token = false
|
||||||
|
try {
|
||||||
|
f()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
this._domObserver.takeRecords()
|
||||||
|
token = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.observe(event => {
|
||||||
|
if (this._dom != null) {
|
||||||
|
const dom = this._dom
|
||||||
|
this._mutualExclude(() => {
|
||||||
|
let selection = null
|
||||||
|
let shouldUpdateSelection = false
|
||||||
|
let anchorNode = null
|
||||||
|
let anchorOffset = null
|
||||||
|
let focusNode = null
|
||||||
|
let focusOffset = null
|
||||||
|
if (typeof getSelection !== 'undefined') {
|
||||||
|
selection = getSelection()
|
||||||
|
if (selection.anchorNode === dom) {
|
||||||
|
anchorNode = selection.anchorNode
|
||||||
|
anchorOffset = fixPosition(event, selection.anchorOffset)
|
||||||
|
shouldUpdateSelection = true
|
||||||
|
}
|
||||||
|
if (selection.focusNode === dom) {
|
||||||
|
focusNode = selection.focusNode
|
||||||
|
focusOffset = fixPosition(event, selection.focusOffset)
|
||||||
|
shouldUpdateSelection = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let anchorViewPosition = getAnchorViewPosition(this._scrollElement)
|
||||||
|
let anchorViewFix
|
||||||
|
if (anchorViewPosition !== null && (anchorViewPosition.anchor !== null || getBoundingClientRect(this._dom).top <= 0)) {
|
||||||
|
anchorViewFix = anchorViewPosition
|
||||||
|
} else {
|
||||||
|
anchorViewFix = null
|
||||||
|
}
|
||||||
|
dom.nodeValue = this.toString()
|
||||||
|
fixScrollPosition(this._scrollElement, anchorViewFix)
|
||||||
|
|
||||||
|
if (shouldUpdateSelection) {
|
||||||
|
selection.setBaseAndExtent(
|
||||||
|
anchorNode || selection.anchorNode,
|
||||||
|
anchorOffset || selection.anchorOffset,
|
||||||
|
focusNode || selection.focusNode,
|
||||||
|
focusOffset || selection.focusOffset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setDomFilter () {}
|
||||||
|
enableSmartScrolling (scrollElement) {
|
||||||
|
this._scrollElement = scrollElement
|
||||||
|
}
|
||||||
|
_setDom (dom) {
|
||||||
|
if (this._dom != null) {
|
||||||
|
this._unbindFromDom()
|
||||||
|
}
|
||||||
|
if (dom.__yxml != null) {
|
||||||
|
dom.__yxml._unbindFromDom()
|
||||||
|
}
|
||||||
|
// set marker
|
||||||
|
this._dom = dom
|
||||||
|
dom.__yxml = this
|
||||||
|
if (typeof MutationObserver === 'undefined') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._domObserverListener = () => {
|
||||||
|
this._mutualExclude(() => {
|
||||||
|
var diffs = diff(this.toString(), dom.nodeValue)
|
||||||
|
var pos = 0
|
||||||
|
for (var i = 0; i < diffs.length; i++) {
|
||||||
|
var d = diffs[i]
|
||||||
|
if (d[0] === 0) { // EQUAL
|
||||||
|
pos += d[1].length
|
||||||
|
} else if (d[0] === -1) { // DELETE
|
||||||
|
this.delete(pos, d[1].length)
|
||||||
|
} else { // INSERT
|
||||||
|
this.insert(pos, d[1])
|
||||||
|
pos += d[1].length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this._domObserver = new MutationObserver(this._domObserverListener)
|
||||||
|
this._domObserver.observe(this._dom, { characterData: true })
|
||||||
|
}
|
||||||
|
getDom () {
|
||||||
|
if (this._dom == null) {
|
||||||
|
const dom = document.createTextNode(this.toString())
|
||||||
|
this._setDom(dom)
|
||||||
|
return dom
|
||||||
|
}
|
||||||
|
return this._dom
|
||||||
|
}
|
||||||
|
_beforeChange () {
|
||||||
|
if (this._domObserver != null && this._y !== null) { // TODO: do I need th y condition
|
||||||
|
this._domObserverListener(this._domObserver.takeRecords())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_delete (y, createDelete) {
|
||||||
|
this._unbindFromDom()
|
||||||
|
super._delete(y, createDelete)
|
||||||
|
}
|
||||||
|
_unbindFromDom () {
|
||||||
|
if (this._domObserver != null) {
|
||||||
|
this._domObserver.disconnect()
|
||||||
|
this._domObserver = null
|
||||||
|
}
|
||||||
|
if (this._dom != null) {
|
||||||
|
this._dom.__yxml = null
|
||||||
|
this._dom = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
206
src/Type/y-xml/utils.js
Normal file
206
src/Type/y-xml/utils.js
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
|
||||||
|
export function defaultDomFilter (node, attributes) {
|
||||||
|
return attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAnchorViewPosition (scrollElement) {
|
||||||
|
if (scrollElement == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
let anchor = document.getSelection().anchorNode
|
||||||
|
if (anchor != null) {
|
||||||
|
let top = getBoundingClientRect(anchor).top
|
||||||
|
if (top >= 0 && top <= document.documentElement.clientHeight) {
|
||||||
|
return {
|
||||||
|
anchor: anchor,
|
||||||
|
top: top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
anchor: null,
|
||||||
|
scrollTop: scrollElement.scrollTop,
|
||||||
|
scrollHeight: scrollElement.scrollHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get BoundingClientRect that works on text nodes
|
||||||
|
export function getBoundingClientRect (element) {
|
||||||
|
if (element.getBoundingClientRect != null) {
|
||||||
|
// is element node
|
||||||
|
return element.getBoundingClientRect()
|
||||||
|
} else {
|
||||||
|
// is text node
|
||||||
|
if (element.parentNode == null) {
|
||||||
|
// range requires that text nodes have a parent
|
||||||
|
let span = document.createElement('span')
|
||||||
|
span.appendChild(element)
|
||||||
|
}
|
||||||
|
let range = document.createRange()
|
||||||
|
range.selectNode(element)
|
||||||
|
return range.getBoundingClientRect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fixScrollPosition (scrollElement, fix) {
|
||||||
|
if (scrollElement !== null && fix !== null) {
|
||||||
|
if (fix.anchor === null) {
|
||||||
|
if (scrollElement.scrollTop === fix.scrollTop) {
|
||||||
|
scrollElement.scrollTop = scrollElement.scrollHeight - fix.scrollHeight
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scrollElement.scrollTop = getBoundingClientRect(fix.anchor).top - fix.top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function iterateUntilUndeleted (item) {
|
||||||
|
while (item !== null && item._deleted) {
|
||||||
|
item = item._right
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 1. Check if any of the nodes was deleted
|
||||||
|
* 2. Iterate over the children.
|
||||||
|
* 2.1 If a node exists without __yxml property, insert a new node
|
||||||
|
* 2.2 If _contents.length < dom.childNodes.length, fill the
|
||||||
|
* rest of _content with childNodes
|
||||||
|
* 2.3 If a node was moved, delete it and
|
||||||
|
* recreate a new yxml element that is bound to that node.
|
||||||
|
* You can detect that a node was moved because expectedId
|
||||||
|
* !== actualId in the list
|
||||||
|
*/
|
||||||
|
export function applyChangesFromDom (yxml) {
|
||||||
|
const y = yxml._y
|
||||||
|
let knownChildren =
|
||||||
|
new Set(
|
||||||
|
Array.prototype.map.call(yxml._dom.childNodes, child => child.__yxml)
|
||||||
|
.filter(id => id !== undefined)
|
||||||
|
)
|
||||||
|
// 1. Check if any of the nodes was deleted
|
||||||
|
yxml.forEach(function (childType, i) {
|
||||||
|
if (!knownChildren.has(childType)) {
|
||||||
|
childType._delete(y)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 2. iterate
|
||||||
|
let childNodes = yxml._dom.childNodes
|
||||||
|
let len = childNodes.length
|
||||||
|
let prevExpectedNode = null
|
||||||
|
let expectedNode = iterateUntilUndeleted(yxml._start)
|
||||||
|
for (let domCnt = 0; domCnt < len; domCnt++) {
|
||||||
|
const child = childNodes[domCnt]
|
||||||
|
const childYXml = child.__yxml
|
||||||
|
if (childYXml != null) {
|
||||||
|
if (childYXml === false) {
|
||||||
|
// should be ignored or is going to be deleted
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (expectedNode !== null) {
|
||||||
|
if (expectedNode !== childYXml) {
|
||||||
|
// 2.3 Not expected node
|
||||||
|
if (childYXml._parent !== this) {
|
||||||
|
// element is going to be deleted by its previous parent
|
||||||
|
child.__yxml = null
|
||||||
|
} else {
|
||||||
|
childYXml._delete(y)
|
||||||
|
}
|
||||||
|
prevExpectedNode = yxml.insertDomElementsAfter(prevExpectedNode, [child])[0]
|
||||||
|
} else {
|
||||||
|
prevExpectedNode = expectedNode
|
||||||
|
expectedNode = iterateUntilUndeleted(expectedNode._right)
|
||||||
|
}
|
||||||
|
// if this is the expected node id, just continue
|
||||||
|
} else {
|
||||||
|
// 2.2 fill _conten with child nodes
|
||||||
|
prevExpectedNode = yxml.insertDomElementsAfter(prevExpectedNode, [child])[0]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 2.1 A new node was found
|
||||||
|
prevExpectedNode = yxml.insertDomElementsAfter(prevExpectedNode, [child])[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reflectChangesOnDom (event) {
|
||||||
|
const yxml = event.target
|
||||||
|
const dom = yxml._dom
|
||||||
|
if (dom != null) {
|
||||||
|
yxml._mutualExclude(() => {
|
||||||
|
// TODO: do this once before applying stuff
|
||||||
|
// let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement)
|
||||||
|
if (event.type === 'attributeChanged') {
|
||||||
|
if (event.value === undefined) {
|
||||||
|
dom.removeAttribute(event.name)
|
||||||
|
} else {
|
||||||
|
dom.setAttribute(event.name, event.value)
|
||||||
|
}
|
||||||
|
} else if (event.type === 'contentChanged') {
|
||||||
|
// create fragment of undeleted nodes
|
||||||
|
const fragment = document.createDocumentFragment()
|
||||||
|
yxml.forEach(function (t) {
|
||||||
|
fragment.append(t.getDom())
|
||||||
|
})
|
||||||
|
// remove remainding nodes
|
||||||
|
let lastChild = dom.lastChild
|
||||||
|
while (lastChild !== null) {
|
||||||
|
dom.removeChild(lastChild)
|
||||||
|
lastChild = dom.lastChild
|
||||||
|
}
|
||||||
|
// insert fragment of undeleted nodes
|
||||||
|
dom.append(fragment)
|
||||||
|
}
|
||||||
|
/* TODO: smartscrolling
|
||||||
|
.. else if (event.type === 'childInserted' || event.type === 'insert') {
|
||||||
|
let nodes = event.values
|
||||||
|
for (let i = nodes.length - 1; i >= 0; i--) {
|
||||||
|
let node = nodes[i]
|
||||||
|
node.setDomFilter(yxml._domFilter)
|
||||||
|
node.enableSmartScrolling(yxml._scrollElement)
|
||||||
|
let dom = node.getDom()
|
||||||
|
let fixPosition = null
|
||||||
|
let nextDom = null
|
||||||
|
if (yxml._content.length > event.index + i + 1) {
|
||||||
|
nextDom = yxml.get(event.index + i + 1).getDom()
|
||||||
|
}
|
||||||
|
yxml._dom.insertBefore(dom, nextDom)
|
||||||
|
if (anchorViewPosition === null) {
|
||||||
|
// nop
|
||||||
|
} else if (anchorViewPosition.anchor !== null) {
|
||||||
|
// no scrolling when current selection
|
||||||
|
if (!dom.contains(anchorViewPosition.anchor) && !anchorViewPosition.anchor.contains(dom)) {
|
||||||
|
fixPosition = anchorViewPosition
|
||||||
|
}
|
||||||
|
} else if (getBoundingClientRect(dom).top <= 0) {
|
||||||
|
// adjust scrolling if modified element is out of view,
|
||||||
|
// there is no anchor element, and the browser did not adjust scrollTop (this is checked later)
|
||||||
|
fixPosition = anchorViewPosition
|
||||||
|
}
|
||||||
|
fixScrollPosition(yxml._scrollElement, fixPosition)
|
||||||
|
}
|
||||||
|
} else if (event.type === 'childRemoved' || event.type === 'delete') {
|
||||||
|
for (let i = event.values.length - 1; i >= 0; i--) {
|
||||||
|
let dom = event.values[i]._dom
|
||||||
|
let fixPosition = null
|
||||||
|
if (anchorViewPosition === null) {
|
||||||
|
// nop
|
||||||
|
} else if (anchorViewPosition.anchor !== null) {
|
||||||
|
// no scrolling when current selection
|
||||||
|
if (!dom.contains(anchorViewPosition.anchor) && !anchorViewPosition.anchor.contains(dom)) {
|
||||||
|
fixPosition = anchorViewPosition
|
||||||
|
}
|
||||||
|
} else if (getBoundingClientRect(dom).top <= 0) {
|
||||||
|
// adjust scrolling if modified element is out of view,
|
||||||
|
// there is no anchor element, and the browser did not adjust scrollTop (this is checked later)
|
||||||
|
fixPosition = anchorViewPosition
|
||||||
|
}
|
||||||
|
dom.remove()
|
||||||
|
fixScrollPosition(yxml._scrollElement, fixPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
9
src/Type/y-xml/y-xml.js
Normal file
9
src/Type/y-xml/y-xml.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
import YXmlFragment from './YXmlFragment.js'
|
||||||
|
import YXmlElement from './YXmlElement.js'
|
||||||
|
|
||||||
|
export { default as YXmlFragment } from './YXmlFragment.js'
|
||||||
|
export { default as YXmlElement } from './YXmlElement.js'
|
||||||
|
export { default as YXmlText } from './YXmlText.js'
|
||||||
|
|
||||||
|
YXmlFragment._YXmlElement = YXmlElement
|
@ -1,9 +0,0 @@
|
|||||||
import Delete from '../Struct/Delete'
|
|
||||||
import ID from './ID'
|
|
||||||
|
|
||||||
export function deleteItemRange (y, user, clock, length) {
|
|
||||||
let del = new Delete()
|
|
||||||
del._target = new ID(user, clock)
|
|
||||||
del._length = length
|
|
||||||
del._integrate(y)
|
|
||||||
}
|
|
@ -1,8 +1,11 @@
|
|||||||
import YArray from '../Type/YArray.js'
|
import YArray from '../Type/YArray.js'
|
||||||
import YMap from '../Type/YMap.js'
|
import YMap from '../Type/YMap.js'
|
||||||
import YText from '../Type/YText.js'
|
import YText from '../Type/YText.js'
|
||||||
import YXml from '../Type/YXml.js'
|
import YXmlFragment from '../Type/y-xml/YXmlFragment.js'
|
||||||
|
import YXmlElement from '../Type/y-xml/YXmlElement.js'
|
||||||
|
import YXmlText from '../Type/y-xml/YXmlText.js'
|
||||||
|
|
||||||
|
import Delete from '../Struct/Delete.js'
|
||||||
import ItemJSON from '../Struct/ItemJSON.js'
|
import ItemJSON from '../Struct/ItemJSON.js'
|
||||||
import ItemString from '../Struct/ItemString.js'
|
import ItemString from '../Struct/ItemString.js'
|
||||||
|
|
||||||
@ -22,9 +25,13 @@ export function getReference (typeConstructor) {
|
|||||||
return references.get(typeConstructor)
|
return references.get(typeConstructor)
|
||||||
}
|
}
|
||||||
|
|
||||||
addStruct(0, YArray)
|
addStruct(0, ItemJSON)
|
||||||
addStruct(1, YMap)
|
addStruct(1, ItemString)
|
||||||
addStruct(2, YText)
|
addStruct(2, Delete)
|
||||||
addStruct(3, YXml)
|
|
||||||
addStruct(4, ItemJSON)
|
addStruct(3, YArray)
|
||||||
addStruct(5, ItemString)
|
addStruct(4, YMap)
|
||||||
|
addStruct(5, YText)
|
||||||
|
addStruct(6, YXmlFragment)
|
||||||
|
addStruct(7, YXmlElement)
|
||||||
|
addStruct(8, YXmlText)
|
||||||
|
40
src/Y.js
40
src/Y.js
@ -12,15 +12,22 @@ import Persistence from './Persistence.js'
|
|||||||
import YArray from './Type/YArray.js'
|
import YArray from './Type/YArray.js'
|
||||||
import YMap from './Type/YMap.js'
|
import YMap from './Type/YMap.js'
|
||||||
import YText from './Type/YText.js'
|
import YText from './Type/YText.js'
|
||||||
import YXml from './Type/YXml.js'
|
import { YXmlFragment, YXmlElement, YXmlText } from './Type/y-xml/y-xml.js'
|
||||||
|
import BinaryDecoder from './Binary/Decoder.js'
|
||||||
|
|
||||||
import debug from 'debug'
|
import debug from 'debug'
|
||||||
|
|
||||||
|
function callTypesAfterTransaction (y) {
|
||||||
|
y._transactionChangedTypes.forEach(function (parentSub, type) {
|
||||||
|
type._callObserver(parentSub)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export default class Y extends NamedEventHandler {
|
export default class Y extends NamedEventHandler {
|
||||||
constructor (opts) {
|
constructor (opts) {
|
||||||
super()
|
super()
|
||||||
this._opts = opts
|
this._opts = opts
|
||||||
this.userID = generateUserID()
|
this.userID = opts._userID != null ? opts._userID : generateUserID()
|
||||||
this.ds = new DeleteStore(this)
|
this.ds = new DeleteStore(this)
|
||||||
this.os = new OperationStore(this)
|
this.os = new OperationStore(this)
|
||||||
this.ss = new StateStore(this)
|
this.ss = new StateStore(this)
|
||||||
@ -34,6 +41,27 @@ export default class Y extends NamedEventHandler {
|
|||||||
this.connected = true
|
this.connected = true
|
||||||
this._missingStructs = new Map()
|
this._missingStructs = new Map()
|
||||||
this._readyToIntegrate = []
|
this._readyToIntegrate = []
|
||||||
|
this._transactionsInProgress = 0
|
||||||
|
// types added during transaction
|
||||||
|
this._transactionNewTypes = new Set()
|
||||||
|
// changed types (does not include new types)
|
||||||
|
this._transactionChangedTypes = new Map()
|
||||||
|
this.on('afterTransaction', callTypesAfterTransaction)
|
||||||
|
}
|
||||||
|
_beforeChange () {}
|
||||||
|
transact (f) {
|
||||||
|
this._transactionsInProgress++
|
||||||
|
try {
|
||||||
|
f()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
this._transactionsInProgress--
|
||||||
|
if (this._transactionsInProgress === 0) {
|
||||||
|
this.emit('afterTransaction', this)
|
||||||
|
this._transactionChangedTypes = new Map()
|
||||||
|
this._transactionNewTypes = new Set()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// fake _start for root properties (y.set('name', type))
|
// fake _start for root properties (y.set('name', type))
|
||||||
get _start () {
|
get _start () {
|
||||||
@ -102,9 +130,13 @@ Y.Persisence = Persistence
|
|||||||
Y.Array = YArray
|
Y.Array = YArray
|
||||||
Y.Map = YMap
|
Y.Map = YMap
|
||||||
Y.Text = YText
|
Y.Text = YText
|
||||||
Y.Xml = YXml
|
Y.XmlElement = YXmlElement
|
||||||
|
Y.XmlFragment = YXmlFragment
|
||||||
|
Y.XmlText = YXmlText
|
||||||
|
|
||||||
export { default as debug } from 'debug'
|
Y.utils = {
|
||||||
|
BinaryDecoder
|
||||||
|
}
|
||||||
|
|
||||||
Y.debug = debug
|
Y.debug = debug
|
||||||
debug.formatters.Y = messageToString
|
debug.formatters.Y = messageToString
|
||||||
|
3
src/y-dist.cjs.js
Normal file
3
src/y-dist.cjs.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
import Y from './Y.js'
|
||||||
|
export default Y
|
@ -27,9 +27,9 @@ test('basic spec', async function array0 (t) {
|
|||||||
test('insert three elements, try re-get property', async function array1 (t) {
|
test('insert three elements, try re-get property', async function array1 (t) {
|
||||||
var { users, array0, array1 } = await initArrays(t, { users: 2 })
|
var { users, array0, array1 } = await initArrays(t, { users: 2 })
|
||||||
array0.insert(0, [1, 2, 3])
|
array0.insert(0, [1, 2, 3])
|
||||||
t.compare(array0.toArray(), [1, 2, 3], '.toArray() works')
|
t.compare(array0.toJSON(), [1, 2, 3], '.toJSON() works')
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
t.compare(array1.toArray(), [1, 2, 3], '.toArray() works after sync')
|
t.compare(array1.toJSON(), [1, 2, 3], '.toJSON() works after sync')
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -76,8 +76,8 @@ test('disconnect really prevents sending messages', async function array5 (t) {
|
|||||||
array0.insert(1, ['user0'])
|
array0.insert(1, ['user0'])
|
||||||
array1.insert(1, ['user1'])
|
array1.insert(1, ['user1'])
|
||||||
await wait(1000)
|
await wait(1000)
|
||||||
t.compare(array0.toArray(), ['x', 'user0', 'y'])
|
t.compare(array0.toJSON(), ['x', 'user0', 'y'])
|
||||||
t.compare(array1.toArray(), ['x', 'user1', 'y'])
|
t.compare(array1.toJSON(), ['x', 'user1', 'y'])
|
||||||
await users[1].reconnect()
|
await users[1].reconnect()
|
||||||
await users[2].reconnect()
|
await users[2].reconnect()
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
@ -225,7 +225,7 @@ test('event has correct value when setting a primitive on a YArray (same user)',
|
|||||||
array0.insert(0, ['stuff'])
|
array0.insert(0, ['stuff'])
|
||||||
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
||||||
t.assert(event.values[0] === 'stuff', 'check that value is actually present')
|
t.assert(event.values[0] === 'stuff', 'check that value is actually present')
|
||||||
t.assert(event.values[0] === array0.toArray()[0], '.toArray works as expected')
|
t.assert(event.values[0] === array0.toJSON()[0], '.toJSON works as expected')
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -240,7 +240,7 @@ test('event has correct value when setting a primitive on a YArray (received fro
|
|||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
||||||
t.assert(event.values[0] === 'stuff', 'check that value is actually present')
|
t.assert(event.values[0] === 'stuff', 'check that value is actually present')
|
||||||
t.assert(event.values[0] === array0.toArray()[0], '.toArray works as expected')
|
t.assert(event.values[0] === array0.toJSON()[0], '.toJSON works as expected')
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -254,7 +254,7 @@ test('event has correct value when setting a type on a YArray (same user)', asyn
|
|||||||
array0.insert(0, [Y.Array])
|
array0.insert(0, [Y.Array])
|
||||||
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
||||||
t.assert(event.values[0] != null, 'event.value exists')
|
t.assert(event.values[0] != null, 'event.value exists')
|
||||||
t.assert(event.values[0] === array0.toArray()[0], '.toArray works as expected')
|
t.assert(event.values[0] === array0.toJSON()[0], '.toJSON works as expected')
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
test('event has correct value when setting a type on a YArray (ops received from another user)', async function array14 (t) {
|
test('event has correct value when setting a type on a YArray (ops received from another user)', async function array14 (t) {
|
||||||
@ -268,7 +268,7 @@ test('event has correct value when setting a type on a YArray (ops received from
|
|||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
t.assert(event.values[0] === event.object.get(0), 'compare value with get method')
|
||||||
t.assert(event.values[0] != null, 'event.value exists')
|
t.assert(event.values[0] != null, 'event.value exists')
|
||||||
t.assert(event.values[0] === array0.toArray()[0], '.toArray works as expected')
|
t.assert(event.values[0] === array0.toJSON()[0], '.toJSON works as expected')
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@ test('basic map tests', async function map0 (t) {
|
|||||||
map0.set('number', 1)
|
map0.set('number', 1)
|
||||||
map0.set('string', 'hello Y')
|
map0.set('string', 'hello Y')
|
||||||
map0.set('object', { key: { key2: 'value' } })
|
map0.set('object', { key: { key2: 'value' } })
|
||||||
map0.set('y-map', Y.Map)
|
map0.set('y-map', new Y.Map())
|
||||||
let map = map0.get('y-map')
|
let map = map0.get('y-map')
|
||||||
map.set('y-array', Y.Array)
|
map.set('y-array', new Y.Array())
|
||||||
let array = map.get('y-array')
|
let array = map.get('y-array')
|
||||||
array.insert(0, [0])
|
array.insert(0, [0])
|
||||||
array.insert(0, [-1])
|
array.insert(0, [-1])
|
||||||
@ -46,7 +46,7 @@ test('Basic get&set of Map property (converge via sync)', async function map1 (t
|
|||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
|
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
var u = user.share.map
|
var u = user.get('map', Y.Map)
|
||||||
t.compare(u.get('stuff'), 'stuffy')
|
t.compare(u.get('stuff'), 'stuffy')
|
||||||
}
|
}
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
@ -54,7 +54,7 @@ test('Basic get&set of Map property (converge via sync)', async function map1 (t
|
|||||||
|
|
||||||
test('Map can set custom types (Map)', async function map2 (t) {
|
test('Map can set custom types (Map)', async function map2 (t) {
|
||||||
let { users, map0 } = await initArrays(t, { users: 2 })
|
let { users, map0 } = await initArrays(t, { users: 2 })
|
||||||
var map = map0.set('Map', Y.Map)
|
var map = map0.set('Map', new Y.Map())
|
||||||
map.set('one', 1)
|
map.set('one', 1)
|
||||||
map = map0.get('Map')
|
map = map0.get('Map')
|
||||||
t.compare(map.get('one'), 1)
|
t.compare(map.get('one'), 1)
|
||||||
@ -63,7 +63,7 @@ test('Map can set custom types (Map)', async function map2 (t) {
|
|||||||
|
|
||||||
test('Map can set custom types (Map) - get also returns the type', async function map3 (t) {
|
test('Map can set custom types (Map) - get also returns the type', async function map3 (t) {
|
||||||
let { users, map0 } = await initArrays(t, { users: 2 })
|
let { users, map0 } = await initArrays(t, { users: 2 })
|
||||||
map0.set('Map', Y.Map)
|
map0.set('Map', new Y.Map())
|
||||||
var map = map0.get('Map')
|
var map = map0.get('Map')
|
||||||
map.set('one', 1)
|
map.set('one', 1)
|
||||||
map = map0.get('Map')
|
map = map0.get('Map')
|
||||||
@ -73,7 +73,7 @@ test('Map can set custom types (Map) - get also returns the type', async functio
|
|||||||
|
|
||||||
test('Map can set custom types (Array)', async function map4 (t) {
|
test('Map can set custom types (Array)', async function map4 (t) {
|
||||||
let { users, map0 } = await initArrays(t, { users: 2 })
|
let { users, map0 } = await initArrays(t, { users: 2 })
|
||||||
var array = map0.set('Array', Y.Array)
|
var array = map0.set('Array', new Y.Array())
|
||||||
array.insert(0, [1, 2, 3])
|
array.insert(0, [1, 2, 3])
|
||||||
array = map0.get('Array')
|
array = map0.get('Array')
|
||||||
t.compare(array.toArray(), [1, 2, 3])
|
t.compare(array.toArray(), [1, 2, 3])
|
||||||
@ -88,7 +88,7 @@ test('Basic get&set of Map property (converge via update)', async function map5
|
|||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
|
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
var u = user.share.map
|
var u = user.get('map', Y.Map)
|
||||||
t.compare(u.get('stuff'), 'stuffy')
|
t.compare(u.get('stuff'), 'stuffy')
|
||||||
}
|
}
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
@ -102,7 +102,7 @@ test('Basic get&set of Map property (handle conflict)', async function map6 (t)
|
|||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
|
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
var u = user.share.map
|
var u = user.get('map', Y.Map)
|
||||||
t.compare(u.get('stuff'), 'c0')
|
t.compare(u.get('stuff'), 'c0')
|
||||||
}
|
}
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
@ -115,7 +115,7 @@ test('Basic get&set&delete of Map property (handle conflict)', async function ma
|
|||||||
map1.set('stuff', 'c1')
|
map1.set('stuff', 'c1')
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
var u = user.share.map
|
var u = user.get('map', Y.Map)
|
||||||
t.assert(u.get('stuff') === undefined)
|
t.assert(u.get('stuff') === undefined)
|
||||||
}
|
}
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
@ -129,7 +129,7 @@ test('Basic get&set of Map property (handle three conflicts)', async function ma
|
|||||||
map2.set('stuff', 'c3')
|
map2.set('stuff', 'c3')
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
var u = user.share.map
|
var u = user.get('map', Y.Map)
|
||||||
t.compare(u.get('stuff'), 'c0')
|
t.compare(u.get('stuff'), 'c0')
|
||||||
}
|
}
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
@ -149,7 +149,7 @@ test('Basic get&set&delete of Map property (handle three conflicts)', async func
|
|||||||
map3.set('stuff', 'c3')
|
map3.set('stuff', 'c3')
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
var u = user.share.map
|
var u = user.get('map', Y.Map)
|
||||||
t.assert(u.get('stuff') === undefined)
|
t.assert(u.get('stuff') === undefined)
|
||||||
}
|
}
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
@ -163,7 +163,7 @@ test('observePath properties', async function map10 (t) {
|
|||||||
map.set('yay', 4)
|
map.set('yay', 4)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
map1.set('map', Y.Map)
|
map1.set('map', new Y.Map())
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
map = map2.get('map')
|
map = map2.get('map')
|
||||||
t.compare(map.get('yay'), 4)
|
t.compare(map.get('yay'), 4)
|
||||||
@ -172,7 +172,7 @@ test('observePath properties', async function map10 (t) {
|
|||||||
|
|
||||||
test('observe deep properties', async function map11 (t) {
|
test('observe deep properties', async function map11 (t) {
|
||||||
let { users, map1, map2, map3 } = await initArrays(t, { users: 4 })
|
let { users, map1, map2, map3 } = await initArrays(t, { users: 4 })
|
||||||
var _map1 = map1.set('map', Y.Map)
|
var _map1 = map1.set('map', new Y.Map())
|
||||||
var calls = 0
|
var calls = 0
|
||||||
var dmapid
|
var dmapid
|
||||||
_map1.observe(function (event) {
|
_map1.observe(function (event) {
|
||||||
@ -182,10 +182,10 @@ test('observe deep properties', async function map11 (t) {
|
|||||||
})
|
})
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
var _map3 = map3.get('map')
|
var _map3 = map3.get('map')
|
||||||
_map3.set('deepmap', Y.Map)
|
_map3.set('deepmap', new Y.Map())
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
var _map2 = map2.get('map')
|
var _map2 = map2.get('map')
|
||||||
_map2.set('deepmap', Y.Map)
|
_map2.set('deepmap', new Y.Map())
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
var dmap1 = _map1.get('deepmap')
|
var dmap1 = _map1.get('deepmap')
|
||||||
var dmap2 = _map2.get('deepmap')
|
var dmap2 = _map2.get('deepmap')
|
||||||
@ -205,8 +205,8 @@ test('observes using observePath', async function map12 (t) {
|
|||||||
pathes.push(event.path)
|
pathes.push(event.path)
|
||||||
calls++
|
calls++
|
||||||
})
|
})
|
||||||
map0.set('map', Y.Map)
|
map0.set('map', new Y.Map())
|
||||||
map0.get('map').set('array', Y.Array)
|
map0.get('map').set('array', new Y.Array())
|
||||||
map0.get('map').get('array').insert(0, ['content'])
|
map0.get('map').get('array').insert(0, ['content'])
|
||||||
t.assert(calls === 3)
|
t.assert(calls === 3)
|
||||||
t.compare(pathes, [[], ['map'], ['map', 'array']])
|
t.compare(pathes, [[], ['map'], ['map', 'array']])
|
||||||
@ -233,7 +233,7 @@ test('throws add & update & delete events (with type and primitive content)', as
|
|||||||
name: 'stuff'
|
name: 'stuff'
|
||||||
})
|
})
|
||||||
// update, oldValue is in contents
|
// update, oldValue is in contents
|
||||||
map0.set('stuff', Y.Array)
|
map0.set('stuff', new Y.Array())
|
||||||
compareEvent(t, event, {
|
compareEvent(t, event, {
|
||||||
type: 'update',
|
type: 'update',
|
||||||
object: map0,
|
object: map0,
|
||||||
@ -288,7 +288,7 @@ test('event has correct value when setting a type on a YMap (same user)', async
|
|||||||
map0.observe(function (e) {
|
map0.observe(function (e) {
|
||||||
event = e
|
event = e
|
||||||
})
|
})
|
||||||
map0.set('stuff', Y.Map)
|
map0.set('stuff', new Y.Map())
|
||||||
t.compare(event.value._model, event.object.get(event.name)._model)
|
t.compare(event.value._model, event.object.get(event.name)._model)
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
})
|
})
|
||||||
@ -300,7 +300,7 @@ test('event has correct value when setting a type on a YMap (ops received from a
|
|||||||
map0.observe(function (e) {
|
map0.observe(function (e) {
|
||||||
event = e
|
event = e
|
||||||
})
|
})
|
||||||
map1.set('stuff', Y.Map)
|
map1.set('stuff', new Y.Map())
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
t.compare(event.value._model, event.object.get(event.name)._model)
|
t.compare(event.value._model, event.object.get(event.name)._model)
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
@ -310,13 +310,13 @@ var mapTransactions = [
|
|||||||
function set (t, user, chance) {
|
function set (t, user, chance) {
|
||||||
let key = chance.pickone(['one', 'two'])
|
let key = chance.pickone(['one', 'two'])
|
||||||
var value = chance.string()
|
var value = chance.string()
|
||||||
user.share.map.set(key, value)
|
user.get('map', Y.Map).set(key, value)
|
||||||
},
|
},
|
||||||
function setType (t, user, chance) {
|
function setType (t, user, chance) {
|
||||||
let key = chance.pickone(['one', 'two'])
|
let key = chance.pickone(['one', 'two'])
|
||||||
var value = chance.pickone([Y.Array, Y.Map])
|
var value = chance.pickone([new Y.Array(), new Y.Map()])
|
||||||
let type = user.share.map.set(key, value)
|
let type = user.get('map', Y.Map).set(key, value)
|
||||||
if (value === Y.Array) {
|
if (value === new Y.Array()) {
|
||||||
type.insert(0, [1, 2, 3, 4])
|
type.insert(0, [1, 2, 3, 4])
|
||||||
} else {
|
} else {
|
||||||
type.set('deepkey', 'deepvalue')
|
type.set('deepkey', 'deepvalue')
|
||||||
@ -324,7 +324,7 @@ var mapTransactions = [
|
|||||||
},
|
},
|
||||||
function _delete (t, user, chance) {
|
function _delete (t, user, chance) {
|
||||||
let key = chance.pickone(['one', 'two'])
|
let key = chance.pickone(['one', 'two'])
|
||||||
user.share.map.delete(key)
|
user.get('map', Y.Map).delete(key)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ test('events', async function xml1 (t) {
|
|||||||
type: 'childInserted',
|
type: 'childInserted',
|
||||||
index: 0
|
index: 0
|
||||||
}
|
}
|
||||||
xml0.insert(0, [Y.XmlText('some text')])
|
xml0.insert(0, [new Y.XmlText('some text')])
|
||||||
t.compare(event, expectedEvent, 'child inserted event')
|
t.compare(event, expectedEvent, 'child inserted event')
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
t.compare(remoteEvent, expectedEvent, 'child inserted event (remote)')
|
t.compare(remoteEvent, expectedEvent, 'child inserted event (remote)')
|
||||||
@ -110,8 +110,8 @@ test('element insert (dom -> y)', async function xml4 (t) {
|
|||||||
test('element insert (y -> dom)', async function xml5 (t) {
|
test('element insert (y -> dom)', async function xml5 (t) {
|
||||||
var { users, xml0 } = await initArrays(t, { users: 3 })
|
var { users, xml0 } = await initArrays(t, { users: 3 })
|
||||||
let dom0 = xml0.getDom()
|
let dom0 = xml0.getDom()
|
||||||
xml0.insert(0, [Y.XmlText('some text')])
|
xml0.insert(0, [new Y.XmlText('some text')])
|
||||||
xml0.insert(1, [Y.XmlElement('p')])
|
xml0.insert(1, [new Y.XmlElement('p')])
|
||||||
t.assert(dom0.childNodes[0].textContent === 'some text', 'Retrieve Text node')
|
t.assert(dom0.childNodes[0].textContent === 'some text', 'Retrieve Text node')
|
||||||
t.assert(dom0.childNodes[1].nodeName === 'P', 'Retrieve Element node')
|
t.assert(dom0.childNodes[1].nodeName === 'P', 'Retrieve Element node')
|
||||||
await compareUsers(t, users)
|
await compareUsers(t, users)
|
||||||
@ -132,7 +132,7 @@ test('y on insert, then delete (dom -> y)', async function xml6 (t) {
|
|||||||
test('y on insert, then delete (y -> dom)', async function xml7 (t) {
|
test('y on insert, then delete (y -> dom)', async function xml7 (t) {
|
||||||
var { users, xml0 } = await initArrays(t, { users: 3 })
|
var { users, xml0 } = await initArrays(t, { users: 3 })
|
||||||
let dom0 = xml0.getDom()
|
let dom0 = xml0.getDom()
|
||||||
xml0.insert(0, [Y.XmlElement('p')])
|
xml0.insert(0, [new Y.XmlElement('p')])
|
||||||
t.assert(dom0.childNodes[0].nodeName === 'P', 'Get inserted element from dom')
|
t.assert(dom0.childNodes[0].nodeName === 'P', 'Get inserted element from dom')
|
||||||
xml0.delete(0, 1)
|
xml0.delete(0, 1)
|
||||||
t.assert(dom0.childNodes.length === 0, '#childNodes is empty after delete')
|
t.assert(dom0.childNodes.length === 0, '#childNodes is empty after delete')
|
||||||
@ -142,7 +142,7 @@ test('y on insert, then delete (y -> dom)', async function xml7 (t) {
|
|||||||
test('delete consecutive (1) (Text)', async function xml8 (t) {
|
test('delete consecutive (1) (Text)', async function xml8 (t) {
|
||||||
var { users, xml0 } = await initArrays(t, { users: 3 })
|
var { users, xml0 } = await initArrays(t, { users: 3 })
|
||||||
let dom0 = xml0.getDom()
|
let dom0 = xml0.getDom()
|
||||||
xml0.insert(0, ['1', '2', '3'].map(Y.XmlText))
|
xml0.insert(0, [new Y.XmlText('1'), new Y.XmlText('2'), new Y.XmlText('3')])
|
||||||
await wait()
|
await wait()
|
||||||
xml0.delete(1, 2)
|
xml0.delete(1, 2)
|
||||||
await wait()
|
await wait()
|
||||||
@ -155,7 +155,7 @@ test('delete consecutive (1) (Text)', async function xml8 (t) {
|
|||||||
test('delete consecutive (2) (Text)', async function xml9 (t) {
|
test('delete consecutive (2) (Text)', async function xml9 (t) {
|
||||||
var { users, xml0 } = await initArrays(t, { users: 3 })
|
var { users, xml0 } = await initArrays(t, { users: 3 })
|
||||||
let dom0 = xml0.getDom()
|
let dom0 = xml0.getDom()
|
||||||
xml0.insert(0, ['1', '2', '3'].map(Y.XmlText))
|
xml0.insert(0, [new Y.XmlText('1'), new Y.XmlText('2'), new Y.XmlText('3')])
|
||||||
await wait()
|
await wait()
|
||||||
xml0.delete(0, 1)
|
xml0.delete(0, 1)
|
||||||
xml0.delete(1, 1)
|
xml0.delete(1, 1)
|
||||||
@ -169,7 +169,7 @@ test('delete consecutive (2) (Text)', async function xml9 (t) {
|
|||||||
test('delete consecutive (1) (Element)', async function xml10 (t) {
|
test('delete consecutive (1) (Element)', async function xml10 (t) {
|
||||||
var { users, xml0 } = await initArrays(t, { users: 3 })
|
var { users, xml0 } = await initArrays(t, { users: 3 })
|
||||||
let dom0 = xml0.getDom()
|
let dom0 = xml0.getDom()
|
||||||
xml0.insert(0, [Y.XmlElement('A'), Y.XmlElement('B'), Y.XmlElement('C')])
|
xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')])
|
||||||
await wait()
|
await wait()
|
||||||
xml0.delete(1, 2)
|
xml0.delete(1, 2)
|
||||||
await wait()
|
await wait()
|
||||||
@ -182,7 +182,7 @@ test('delete consecutive (1) (Element)', async function xml10 (t) {
|
|||||||
test('delete consecutive (2) (Element)', async function xml11 (t) {
|
test('delete consecutive (2) (Element)', async function xml11 (t) {
|
||||||
var { users, xml0 } = await initArrays(t, { users: 3 })
|
var { users, xml0 } = await initArrays(t, { users: 3 })
|
||||||
let dom0 = xml0.getDom()
|
let dom0 = xml0.getDom()
|
||||||
xml0.insert(0, [Y.XmlElement('A'), Y.XmlElement('B'), Y.XmlElement('C')])
|
xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')])
|
||||||
await wait()
|
await wait()
|
||||||
xml0.delete(0, 1)
|
xml0.delete(0, 1)
|
||||||
xml0.delete(1, 1)
|
xml0.delete(1, 1)
|
||||||
@ -198,8 +198,8 @@ test('Receive a bunch of elements (with disconnect)', async function xml12 (t) {
|
|||||||
let dom0 = xml0.getDom()
|
let dom0 = xml0.getDom()
|
||||||
let dom1 = xml1.getDom()
|
let dom1 = xml1.getDom()
|
||||||
users[1].disconnect()
|
users[1].disconnect()
|
||||||
xml0.insert(0, [Y.XmlElement('A'), Y.XmlElement('B'), Y.XmlElement('C')])
|
xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')])
|
||||||
xml0.insert(0, [Y.XmlElement('X'), Y.XmlElement('Y'), Y.XmlElement('Z')])
|
xml0.insert(0, [new Y.XmlElement('X'), new Y.XmlElement('Y'), new Y.XmlElement('Z')])
|
||||||
await users[1].reconnect()
|
await users[1].reconnect()
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
t.assert(xml0.length === 6, 'check length (y)')
|
t.assert(xml0.length === 6, 'check length (y)')
|
||||||
@ -267,36 +267,37 @@ test('filter attribute', async function xml15 (t) {
|
|||||||
|
|
||||||
// TODO: move elements
|
// TODO: move elements
|
||||||
var xmlTransactions = [
|
var xmlTransactions = [
|
||||||
function attributeChange (t, user, chance) {
|
/*function attributeChange (t, user, chance) {
|
||||||
user.share.xml.getDom().setAttribute(chance.word(), chance.word())
|
user.get('xml', Y.XmlElement).getDom().setAttribute(chance.word(), chance.word())
|
||||||
},
|
},
|
||||||
function attributeChangeHidden (t, user, chance) {
|
function attributeChangeHidden (t, user, chance) {
|
||||||
user.share.xml.getDom().setAttribute('hidden', chance.word())
|
user.get('xml', Y.XmlElement).getDom().setAttribute('hidden', chance.word())
|
||||||
},
|
},*/
|
||||||
function insertText (t, user, chance) {
|
function insertText (t, user, chance) {
|
||||||
let dom = user.share.xml.getDom()
|
let dom = user.get('xml', Y.XmlElement).getDom()
|
||||||
var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null
|
var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null
|
||||||
dom.insertBefore(document.createTextNode(chance.word()), succ)
|
dom.insertBefore(document.createTextNode(chance.word()), succ)
|
||||||
},
|
},/*
|
||||||
function insertHiddenDom (t, user, chance) {
|
function insertHiddenDom (t, user, chance) {
|
||||||
let dom = user.share.xml.getDom()
|
let dom = user.get('xml', Y.XmlElement).getDom()
|
||||||
var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null
|
var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null
|
||||||
dom.insertBefore(document.createElement('hidden'), succ)
|
dom.insertBefore(document.createElement('hidden'), succ)
|
||||||
},
|
},
|
||||||
|
/*
|
||||||
function insertDom (t, user, chance) {
|
function insertDom (t, user, chance) {
|
||||||
let dom = user.share.xml.getDom()
|
let dom = user.get('xml', Y.XmlElement).getDom()
|
||||||
var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null
|
var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null
|
||||||
dom.insertBefore(document.createElement(chance.word()), succ)
|
dom.insertBefore(document.createElement(chance.word()), succ)
|
||||||
},
|
},
|
||||||
function deleteChild (t, user, chance) {
|
function deleteChild (t, user, chance) {
|
||||||
let dom = user.share.xml.getDom()
|
let dom = user.get('xml', Y.XmlElement).getDom()
|
||||||
if (dom.childNodes.length > 0) {
|
if (dom.childNodes.length > 0) {
|
||||||
var d = chance.pickone(dom.childNodes)
|
var d = chance.pickone(dom.childNodes)
|
||||||
d.remove()
|
d.remove()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function insertTextSecondLayer (t, user, chance) {
|
function insertTextSecondLayer (t, user, chance) {
|
||||||
let dom = user.share.xml.getDom()
|
let dom = user.get('xml', Y.XmlElement).getDom()
|
||||||
if (dom.children.length > 0) {
|
if (dom.children.length > 0) {
|
||||||
let dom2 = chance.pickone(dom.children)
|
let dom2 = chance.pickone(dom.children)
|
||||||
let succ = dom2.childNodes.length > 0 ? chance.pickone(dom2.childNodes) : null
|
let succ = dom2.childNodes.length > 0 ? chance.pickone(dom2.childNodes) : null
|
||||||
@ -304,7 +305,7 @@ var xmlTransactions = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
function insertDomSecondLayer (t, user, chance) {
|
function insertDomSecondLayer (t, user, chance) {
|
||||||
let dom = user.share.xml.getDom()
|
let dom = user.get('xml', Y.XmlElement).getDom()
|
||||||
if (dom.children.length > 0) {
|
if (dom.children.length > 0) {
|
||||||
let dom2 = chance.pickone(dom.children)
|
let dom2 = chance.pickone(dom.children)
|
||||||
let succ = dom2.childNodes.length > 0 ? chance.pickone(dom2.childNodes) : null
|
let succ = dom2.childNodes.length > 0 ? chance.pickone(dom2.childNodes) : null
|
||||||
@ -312,7 +313,7 @@ var xmlTransactions = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
function deleteChildSecondLayer (t, user, chance) {
|
function deleteChildSecondLayer (t, user, chance) {
|
||||||
let dom = user.share.xml.getDom()
|
let dom = user.get('xml', Y.XmlElement).getDom()
|
||||||
if (dom.children.length > 0) {
|
if (dom.children.length > 0) {
|
||||||
let dom2 = chance.pickone(dom.children)
|
let dom2 = chance.pickone(dom.children)
|
||||||
if (dom2.childNodes.length > 0) {
|
if (dom2.childNodes.length > 0) {
|
||||||
@ -320,7 +321,7 @@ var xmlTransactions = [
|
|||||||
d.remove()
|
d.remove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
]
|
]
|
||||||
|
|
||||||
test('y-xml: Random tests (10)', async function xmlRandom10 (t) {
|
test('y-xml: Random tests (10)', async function xmlRandom10 (t) {
|
||||||
|
@ -3,6 +3,8 @@ import _Y from '../src/Y.js'
|
|||||||
import yTest from './test-connector.js'
|
import yTest from './test-connector.js'
|
||||||
|
|
||||||
import Chance from 'chance'
|
import Chance from 'chance'
|
||||||
|
import ItemJSON from '../src/Struct/ItemJSON.js'
|
||||||
|
import ItemString from '../src/Struct/ItemString.js'
|
||||||
|
|
||||||
export const Y = _Y
|
export const Y = _Y
|
||||||
|
|
||||||
@ -22,8 +24,8 @@ function getStateSet (y) {
|
|||||||
function getDeleteSet (y) {
|
function getDeleteSet (y) {
|
||||||
var ds = {}
|
var ds = {}
|
||||||
y.ds.iterate(null, null, function (n) {
|
y.ds.iterate(null, null, function (n) {
|
||||||
var user = n.id[0]
|
var user = n._id.user
|
||||||
var counter = n.id[1]
|
var counter = n._id.clock
|
||||||
var len = n.len
|
var len = n.len
|
||||||
var gc = n.gc
|
var gc = n.gc
|
||||||
var dv = ds[user]
|
var dv = ds[user]
|
||||||
@ -112,12 +114,17 @@ export async function compareUsers (t, users) {
|
|||||||
let ops = []
|
let ops = []
|
||||||
u.os.iterate(null, null, function (op) {
|
u.os.iterate(null, null, function (op) {
|
||||||
if (!op._deleted) {
|
if (!op._deleted) {
|
||||||
ops.push({
|
const json = {
|
||||||
id: op._id,
|
id: op._id,
|
||||||
left: op._left,
|
left: op._left === null ? null : op._left._id,
|
||||||
right: op._right,
|
right: op._right === null ? null : op._right._id,
|
||||||
|
length: op._length,
|
||||||
deleted: op._deleted
|
deleted: op._deleted
|
||||||
})
|
}
|
||||||
|
if (op instanceof ItemJSON || op instanceof ItemString) {
|
||||||
|
json.content = op._content
|
||||||
|
}
|
||||||
|
ops.push(json)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
data.os = ops
|
data.os = ops
|
||||||
@ -152,10 +159,13 @@ export async function initArrays (t, opts) {
|
|||||||
connOpts = Object.assign({ role: 'slave' }, conn)
|
connOpts = Object.assign({ role: 'slave' }, conn)
|
||||||
}
|
}
|
||||||
let y = new Y({
|
let y = new Y({
|
||||||
|
_userID: i, // evil hackery, don't try this at home
|
||||||
connector: connOpts
|
connector: connOpts
|
||||||
})
|
})
|
||||||
result.users.push(y)
|
result.users.push(y)
|
||||||
result['array' + i] = y.get('array', Y.Array)
|
result['array' + i] = y.get('array', Y.Array)
|
||||||
|
result['map' + i] = y.get('map', Y.Map)
|
||||||
|
result['xml' + i] = y.get('xml', Y.XmlElement)
|
||||||
y.get('xml', Y.Xml).setDomFilter(function (d, attrs) {
|
y.get('xml', Y.Xml).setDomFilter(function (d, attrs) {
|
||||||
if (d.nodeName === 'HIDDEN') {
|
if (d.nodeName === 'HIDDEN') {
|
||||||
return null
|
return null
|
||||||
|
@ -136,7 +136,6 @@ export default function extendTestConnector (Y) {
|
|||||||
// this one needs to sync with every other user
|
// this one needs to sync with every other user
|
||||||
flushUsers = Array.from(this.connections.keys()).map(uid => this.testRoom.users.get(uid).y)
|
flushUsers = Array.from(this.connections.keys()).map(uid => this.testRoom.users.get(uid).y)
|
||||||
}
|
}
|
||||||
var finished = []
|
|
||||||
for (let i = 0; i < flushUsers.length; i++) {
|
for (let i = 0; i < flushUsers.length; i++) {
|
||||||
let userID = flushUsers[i].connector.y.userID
|
let userID = flushUsers[i].connector.y.userID
|
||||||
if (userID !== this.y.userID && this.connections.has(userID)) {
|
if (userID !== this.y.userID && this.connections.has(userID)) {
|
||||||
@ -144,14 +143,12 @@ export default function extendTestConnector (Y) {
|
|||||||
if (buffer != null) {
|
if (buffer != null) {
|
||||||
var messages = buffer.splice(0)
|
var messages = buffer.splice(0)
|
||||||
for (let j = 0; j < messages.length; j++) {
|
for (let j = 0; j < messages.length; j++) {
|
||||||
let p = super.receiveMessage(userID, messages[j])
|
super.receiveMessage(userID, messages[j])
|
||||||
finished.push(p)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(finished)
|
return 'done'
|
||||||
return finished.length > 0 ? 'flushing' : 'done'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: this should be moved to a separate module (dont work on Y)
|
// TODO: this should be moved to a separate module (dont work on Y)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user