Compare commits

..

1 Commits

Author SHA1 Message Date
Kevin Jahns
16e01ad3d5 v13.0.0-41 -- distribution files 2017-12-14 14:30:17 +01:00
45 changed files with 3361 additions and 2285 deletions

View File

@@ -1,7 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
</head> </head>
<!-- jquery is not required for y-xml. It is just here for convenience, and to test batch operations. -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="../yjs-dist.js"></script> <script src="../yjs-dist.js"></script>
<script src="./canvasjs.min.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</head> </head>
<body contenteditable="true"> <body contenteditable="true">

View File

@@ -1,20 +1,77 @@
/* global Y */ /* global Y, HTMLElement, customElements, CanvasJS */
window.onload = function () { window.onload = function () {
window.yXmlType.bindToDom(document.body) window.yXmlType.bindToDom(document.body)
let mt = document.createElement('magic-table')
mt.innerHTML = '<table><tr><th>Amount</th></tr><tr><td>1</td></tr><tr><td>1</td></tr></table>'
document.body.append(mt)
} }
const persistence = new Y.IndexedDB() class MagicTable extends HTMLElement {
constructor () {
super()
this.createShadowRoot()
}
get _yjsHook () {
return 'magic-table'
}
showTable () {
this.shadowRoot.innerHTML = ''
this.shadowRoot.append(document.createElement('content'))
}
showDiagram () {
let dataPoints = []
this.querySelectorAll('td').forEach(td => {
let number = Number(td.textContent)
dataPoints.push({
x: (dataPoints.length + 1) * 10,
y: number,
label: '<magic-table> content'
})
})
this.shadowRoot.innerHTML = ''
var chart = new CanvasJS.Chart(this.shadowRoot,
{
title: {
text: 'Bar chart'
},
data: [
{
type: 'bar',
dataPoints: dataPoints
}
]
})
chart.render()
// this.shadowRoot.innerHTML = '<p>dtrn</p>'
}
}
customElements.define('magic-table', MagicTable)
Y.XmlHook.addHook('magic-table', {
fillType: function (dom, type) {
type.set('table', new Y.XmlElement(dom.querySelector('table')))
},
createDom: function (type) {
const table = type.get('table').getDom()
const dom = document.createElement('magic-table')
dom.insertBefore(table, null)
return dom
}
})
// initialize a shared object. This function call returns a promise! // initialize a shared object. This function call returns a promise!
let y = new Y('htmleditor', { let y = new Y({
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' room: 'html-editor-example6'
// maxBufferLength: 100 // maxBufferLength: 100
} }
}, persistence) })
window.yXml = y window.yXml = y
window.yXmlType = y.define('xml', Y.XmlFragment) window.yXmlType = y.define('xml', Y.XmlFragment)
window.undoManager = new Y.utils.UndoManager(window.yXmlType, { window.undoManager = new Y.utils.UndoManager(window.yXmlType, {

View File

@@ -1,18 +1,24 @@
/* global Y, CodeMirror */ /* global Y, CodeMirror */
const persistence = new Y.IndexedDB() // initialize a shared object. This function call returns a promise!
const connector = { Y({
db: {
name: 'memory'
},
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
room: 'codemirror-example' room: 'codemirror-example'
},
sourceDir: '/bower_components',
share: {
codemirror: 'Text' // y.share.codemirror is of type Y.Text
} }
} }).then(function (y) {
// initialize a shared object. This function call returns a promise! window.yCodeMirror = y
const y = Y('codemirror-example', connector, persistence)
window.yCodeMirror = y
var editor = CodeMirror(document.querySelector('#codeMirrorContainer'), { var editor = CodeMirror(document.querySelector('#codeMirrorContainer'), {
mode: 'javascript', mode: 'javascript',
lineNumbers: true lineNumbers: true
})
y.share.codemirror.bindCodeMirror(editor)
}) })
y.share.codemirror.bindCodeMirror(editor)

File diff suppressed because it is too large Load Diff

View File

@@ -9,15 +9,12 @@
"author": "Kevin Jahns", "author": "Kevin Jahns",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"monaco-editor": "^0.8.3", "monaco-editor": "^0.8.3"
"rollup": "^0.52.3"
}, },
"devDependencies": { "devDependencies": {
"standard": "^10.0.2" "standard": "^10.0.2"
}, },
"standard": { "standard": {
"ignore": [ "ignore": ["bower_components"]
"bower_components"
]
} }
} }

View File

@@ -4,7 +4,7 @@ import commonjs from 'rollup-plugin-commonjs'
var pkg = require('./package.json') var pkg = require('./package.json')
export default { export default {
input: 'yjs-dist.mjs', input: 'yjs-dist.esm',
name: 'Y', name: 'Y',
output: { output: {
file: 'yjs-dist.js', file: 'yjs-dist.js',

View File

@@ -1,9 +1,7 @@
import Y from '../src/Y.js' import Y from '../src/Y.js'
import yWebsocketsClient from '../../y-websockets-client/src/y-websockets-client.js' import yWebsocketsClient from '../../y-websockets-client/src/y-websockets-client.js'
import extendYIndexedDBPersistence from '../../y-indexeddb/src/y-indexeddb.js'
Y.extend(yWebsocketsClient) Y.extend(yWebsocketsClient)
extendYIndexedDBPersistence(Y)
export default Y export default Y

1150
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.0-47", "version": "13.0.0-41",
"description": "A framework for real-time p2p shared editing on any data", "description": "A framework for real-time p2p shared editing on any data",
"main": "./y.node.js", "main": "./y.node.js",
"browser": "./y.js", "browser": "./y.js",
@@ -65,6 +65,9 @@
"tag-dist-files": "^0.1.6" "tag-dist-files": "^0.1.6"
}, },
"dependencies": { "dependencies": {
"debug": "^2.6.8" "debug": "^2.6.8",
"fast-diff": "^1.1.2",
"utf-8": "^1.0.0",
"utf8": "^2.1.2"
} }
} }

View File

@@ -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 {
input: 'test/index.js', input: 'test/*.tests.js',
name: 'y-tests', name: 'y-tests',
sourcemap: true, sourcemap: true,
output: { output: {
@@ -11,12 +11,12 @@ export default {
format: 'umd' format: 'umd'
}, },
plugins: [ plugins: [
multiEntry(),
nodeResolve({ nodeResolve({
main: true, main: true,
module: true, module: true,
browser: true browser: true
}), }),
commonjs() commonjs(),
multiEntry()
] ]
} }

View File

@@ -1,3 +1,4 @@
import utf8 from 'utf-8'
import ID from '../Util/ID.js' import ID from '../Util/ID.js'
import { default as RootID, RootFakeUserID } from '../Util/RootID.js' import { default as RootID, RootFakeUserID } from '../Util/RootID.js'
@@ -90,8 +91,7 @@ export default class BinaryDecoder {
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
bytes[i] = this.uint8arr[this.pos++] bytes[i] = this.uint8arr[this.pos++]
} }
let encodedString = String.fromCodePoint(...bytes) return utf8.getStringFromBytes(bytes)
return decodeURIComponent(escape(encodedString))
} }
/** /**
* Look ahead and read varString without incrementing position * Look ahead and read varString without incrementing position

View File

@@ -1,3 +1,4 @@
import utf8 from 'utf-8'
import { RootFakeUserID } from '../Util/RootID.js' import { RootFakeUserID } from '../Util/RootID.js'
const bits7 = 0b1111111 const bits7 = 0b1111111
@@ -61,8 +62,7 @@ export default class BinaryEncoder {
} }
writeVarString (str) { writeVarString (str) {
let encodedString = unescape(encodeURIComponent(str)) let bytes = utf8.setBytesFromString(str)
let bytes = encodedString.split('').map(c => c.codePointAt())
let len = bytes.length let len = bytes.length
this.writeVarUint(len) this.writeVarUint(len)
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {

View File

@@ -132,7 +132,6 @@ export default class AbstractConnector {
f() f()
} }
this.whenSyncedListeners = [] this.whenSyncedListeners = []
this.y._setContentReady()
this.y.emit('synced') this.y.emit('synced')
} }
} }
@@ -269,7 +268,7 @@ export default class AbstractConnector {
if (messageType === 'sync step 2' && senderConn.auth === 'write') { if (messageType === 'sync step 2' && senderConn.auth === 'write') {
readSyncStep2(decoder, encoder, y, senderConn, sender) readSyncStep2(decoder, encoder, y, senderConn, sender)
} else if (messageType === 'update' && (skipAuth || senderConn.auth === 'write')) { } else if (messageType === 'update' && (skipAuth || senderConn.auth === 'write')) {
integrateRemoteStructs(y, decoder) integrateRemoteStructs(decoder, encoder, y, senderConn, sender)
} else { } else {
throw new Error('Unable to receive message') throw new Error('Unable to receive message')
} }

View File

@@ -1,16 +0,0 @@
import { writeStructs } from './syncStep1.js'
import { integrateRemoteStructs } from './integrateRemoteStructs.js'
import { readDeleteSet, writeDeleteSet } from './deleteSet.js'
import BinaryEncoder from '../Binary/Encoder.js'
export function fromBinary (y, decoder) {
integrateRemoteStructs(y, decoder)
readDeleteSet(y, decoder)
}
export function toBinary (y) {
let encoder = new BinaryEncoder()
writeStructs(y, encoder, new Map())
writeDeleteSet(y, encoder)
return encoder
}

View File

@@ -65,7 +65,7 @@ export function stringifyStructs (y, decoder, strBuilder) {
} }
} }
export function integrateRemoteStructs (y, decoder) { export function integrateRemoteStructs (decoder, encoder, y) {
const len = decoder.readUint32() const len = decoder.readUint32()
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
let reference = decoder.readVarUint() let reference = decoder.readVarUint()

View File

@@ -30,7 +30,7 @@ export function sendSyncStep1 (connector, syncUser) {
connector.send(syncUser, encoder.createBuffer()) connector.send(syncUser, encoder.createBuffer())
} }
export function writeStructs (y, encoder, ss) { export default function writeStructs (encoder, decoder, y, ss) {
const lenPos = encoder.pos const lenPos = encoder.pos
encoder.writeUint32(0) encoder.writeUint32(0)
let len = 0 let len = 0
@@ -60,7 +60,7 @@ export function readSyncStep1 (decoder, encoder, y, senderConn, sender) {
encoder.writeVarString('sync step 2') encoder.writeVarString('sync step 2')
encoder.writeVarString(y.connector.authInfo || '') encoder.writeVarString(y.connector.authInfo || '')
const ss = readStateSet(decoder) const ss = readStateSet(decoder)
writeStructs(y, encoder, ss) writeStructs(encoder, decoder, y, ss)
writeDeleteSet(y, encoder) writeDeleteSet(y, encoder)
y.connector.send(senderConn.uid, encoder.createBuffer()) y.connector.send(senderConn.uid, encoder.createBuffer())
senderConn.receivedSyncStep2 = true senderConn.receivedSyncStep2 = true

View File

@@ -22,7 +22,7 @@ export function stringifySyncStep2 (y, decoder, strBuilder) {
} }
export function readSyncStep2 (decoder, encoder, y, senderConn, sender) { export function readSyncStep2 (decoder, encoder, y, senderConn, sender) {
integrateRemoteStructs(y, decoder) integrateRemoteStructs(decoder, encoder, y)
readDeleteSet(y, decoder) readDeleteSet(y, decoder)
y.connector._setSyncedWith(sender) y.connector._setSyncedWith(sender)
} }

View File

@@ -1,116 +1,47 @@
import BinaryEncoder from './Binary/Encoder.js' // import BinaryEncoder from './Binary/Encoder.js'
import BinaryDecoder from './Binary/Decoder.js'
import { toBinary, fromBinary } from './MessageHandler/binaryEncode.js'
import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.js'
import { createMutualExclude } from './Util/mutualExclude.js'
function getFreshCnf () { export default function extendPersistence (Y) {
let buffer = new BinaryEncoder() class AbstractPersistence {
buffer.writeUint32(0) constructor (y, opts) {
return { this.y = y
len: 0, this.opts = opts
buffer this.saveOperationsBuffer = []
} this.log = Y.debug('y:persistence')
}
export default class AbstractPersistence {
constructor (opts) {
this.opts = opts
this.ys = new Map()
this.mutualExclude = createMutualExclude()
}
_init (y) {
let cnf = this.ys.get(y)
if (cnf === undefined) {
cnf = getFreshCnf()
this.ys.set(y, cnf)
return this.init(y).then(() => {
y.on('afterTransaction', (y, transaction) => {
let cnf = this.ys.get(y)
if (cnf.len > 0) {
cnf.buffer.setUint32(0, cnf.len)
this.saveUpdate(y, cnf.buffer.createBuffer(), transaction)
let _cnf = getFreshCnf()
for (let key in _cnf) {
cnf[key] = _cnf[key]
}
}
})
return this.retrieve(y)
}).then(function () {
return Promise.resolve(cnf)
})
} else {
return Promise.resolve(cnf)
} }
}
deinit (y) {
this.ys.delete(y)
y.persistence = null
}
destroy () { saveToMessageQueue (binary) {
this.ys = null this.log('Room %s: Save message to message queue', this.y.options.connector.room)
} }
/** saveOperations (ops) {
* Remove all persisted data that belongs to a room. ops = ops.map(function (op) {
* Automatically destroys all Yjs all Yjs instances that persist to return Y.Struct[op.struct].encode(op)
* the room. If `destroyYjsInstances = false` the persistence functionality })
* will be removed from the Yjs instances. /*
* const saveOperations = () => {
* ** Must be overwritten! ** if (this.saveOperationsBuffer.length > 0) {
*/ let encoder = new BinaryEncoder()
removePersistedData (room, destroyYjsInstances = true) { encoder.writeVarString(this.opts.room)
this.ys.forEach((cnf, y) => { encoder.writeVarString('update')
if (y.room === room) { let ops = this.saveOperationsBuffer
if (destroyYjsInstances) { this.saveOperationsBuffer = []
y.destroy() let length = ops.length
} else { encoder.writeUint32(length)
this.deinit(y) for (var i = 0; i < length; i++) {
let op = ops[i]
Y.Struct[op.struct].binaryEncode(encoder, op)
}
this.saveToMessageQueue(encoder.createBuffer())
} }
} }
}) */
} if (this.saveOperationsBuffer.length === 0) {
this.saveOperationsBuffer = ops
/* overwrite */ } else {
saveUpdate (buffer) { this.saveOperationsBuffer = this.saveOperationsBuffer.concat(ops)
} }
/**
* Save struct to update buffer.
* saveUpdate is called when transaction ends
*/
saveStruct (y, struct) {
let cnf = this.ys.get(y)
if (cnf !== undefined) {
this.mutualExclude(function () {
struct._toBinary(cnf.buffer)
cnf.len++
})
} }
} }
/* overwrite */ Y.AbstractPersistence = AbstractPersistence
retrieve (y, model, updates) {
this.mutualExclude(function () {
y.transact(function () {
if (model != null) {
fromBinary(y, new BinaryDecoder(new Uint8Array(model)))
}
if (updates != null) {
for (let i = 0; i < updates.length; i++) {
integrateRemoteStructs(y, new BinaryDecoder(new Uint8Array(updates[i])))
}
}
})
y.emit('persistenceReady')
})
}
/* overwrite */
persist (y) {
return toBinary(y).createBuffer()
}
} }

View File

@@ -7,7 +7,7 @@ import { logID } from '../MessageHandler/messageToString.js'
* TODO: implement getItemCleanStartNode for better performance (only one lookup) * TODO: implement getItemCleanStartNode for better performance (only one lookup)
*/ */
export function deleteItemRange (y, user, clock, range) { export function deleteItemRange (y, user, clock, range) {
const createDelete = y.connector !== null && y.connector._forwardAppliedStructs const createDelete = y.connector._forwardAppliedStructs
let item = y.os.getItemCleanStart(new ID(user, clock)) let item = y.os.getItemCleanStart(new ID(user, clock))
if (item !== null) { if (item !== null) {
if (!item._deleted) { if (!item._deleted) {
@@ -70,12 +70,12 @@ export default class Delete {
// from remote // from remote
const id = this._targetID const id = this._targetID
deleteItemRange(y, id.user, id.clock, this._length) deleteItemRange(y, id.user, id.clock, this._length)
} else if (y.connector !== null) { } else {
// from local // from local
y.connector.broadcastStruct(this) y.connector.broadcastStruct(this)
} }
if (y.persistence !== null) { if (y.persistence !== null) {
y.persistence.saveStruct(y, this) y.persistence.saveOperations(this)
} }
} }
_logString () { _logString () {

View File

@@ -87,18 +87,16 @@ export default class Item {
return this._right return this._right
} }
_delete (y, createDelete = true) { _delete (y, createDelete = true) {
if (!this._deleted) { this._deleted = true
this._deleted = true y.ds.markDeleted(this._id, this._length)
y.ds.markDeleted(this._id, this._length) if (createDelete) {
if (createDelete) { let del = new Delete()
let del = new Delete() del._targetID = this._id
del._targetID = this._id del._length = this._length
del._length = this._length del._integrate(y, true)
del._integrate(y, true)
}
transactionTypeChanged(y, this._parent, this._parentSub)
y._transaction.deletedStructs.add(this)
} }
transactionTypeChanged(y, this._parent, this._parentSub)
y._transaction.deletedStructs.add(this)
} }
/** /**
* This is called right before this struct receives any children. * This is called right before this struct receives any children.
@@ -218,11 +216,11 @@ export default class Item {
y.os.put(this) y.os.put(this)
transactionTypeChanged(y, parent, parentSub) transactionTypeChanged(y, parent, parentSub)
if (this._id.user !== RootFakeUserID) { if (this._id.user !== RootFakeUserID) {
if (y.connector !== null && (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)
} }
if (y.persistence !== null) { if (y.persistence !== null) {
y.persistence.saveStruct(y, this) y.persistence.saveOperations(this)
} }
} }
} }

View File

@@ -207,12 +207,8 @@ export default class YArray extends Type {
prevJsonIns._content.push(c) prevJsonIns._content.push(c)
} }
} }
if (prevJsonIns !== null) { if (prevJsonIns !== null && y !== null) {
if (y !== null) { prevJsonIns._integrate(y)
prevJsonIns._integrate(y)
} else if (prevJsonIns._left === null) {
this._start = prevJsonIns
}
} }
}) })
} }

View File

@@ -56,7 +56,7 @@ export default class YMap extends Type {
this._transact(y => { this._transact(y => {
const old = this._map.get(key) || null const old = this._map.get(key) || null
if (old !== null) { if (old !== null) {
if (old.constructor === ItemJSON && !old._deleted && old._content[0] === value) { if (old instanceof ItemJSON && old._content[0] === value) {
// Trying to overwrite with same value // Trying to overwrite with same value
// break here // break here
return value return value

View File

@@ -24,9 +24,6 @@ export default class YText extends YArray {
return strBuilder.join('') return strBuilder.join('')
} }
insert (pos, text) { insert (pos, text) {
if (text.length <= 0) {
return
}
this._transact(y => { this._transact(y => {
let left = null let left = null
let right = this._start let right = this._start

View File

@@ -1,3 +1,4 @@
// import diff from 'fast-diff'
import { defaultDomFilter } from './utils.js' import { defaultDomFilter } from './utils.js'
import YMap from '../YMap.js' import YMap from '../YMap.js'
@@ -47,11 +48,6 @@ export default class YXmlElement extends YXmlFragment {
return dom return dom
} }
} }
_bindToDom (dom, _document) {
_document = _document || document
this._dom = dom
dom._yxml = this
}
_fromBinary (y, decoder) { _fromBinary (y, decoder) {
const missing = super._fromBinary(y, decoder) const missing = super._fromBinary(y, decoder)
this.nodeName = decoder.readVarString() this.nodeName = decoder.readVarString()

View File

@@ -7,7 +7,7 @@ import YArray from '../YArray.js'
import YXmlEvent from './YXmlEvent.js' import YXmlEvent from './YXmlEvent.js'
import { YXmlText, YXmlHook } from './y-xml' import { YXmlText, YXmlHook } from './y-xml'
import { logID } from '../../MessageHandler/messageToString.js' import { logID } from '../../MessageHandler/messageToString.js'
import diff from '../../Util/simpleDiff.js' import diff from 'fast-diff'
function domToYXml (parent, doms, _document) { function domToYXml (parent, doms, _document) {
const types = [] const types = []
@@ -101,11 +101,9 @@ export default class YXmlFragment extends YArray {
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
/*
if (this._domObserver !== null) { if (this._domObserver !== null) {
this._domObserver.takeRecords() this._domObserver.takeRecords()
} }
*/
token = true token = true
} }
} }
@@ -145,23 +143,6 @@ export default class YXmlFragment extends YArray {
} }
setDomFilter (f) { setDomFilter (f) {
this._domFilter = f this._domFilter = f
let attributes = new Map()
if (this.getAttributes !== undefined) {
let attrs = this.getAttributes()
for (let key in attrs) {
attributes.set(key, attrs[key])
}
}
let result = this._domFilter(this.nodeName, new Map(attributes))
if (result === null) {
this._delete(this._y)
} else {
attributes.forEach((value, key) => {
if (!result.has(key)) {
this.removeAttribute(key)
}
})
}
this.forEach(xml => { this.forEach(xml => {
xml.setDomFilter(f) xml.setDomFilter(f)
}) })
@@ -185,9 +166,6 @@ export default class YXmlFragment extends YArray {
this._dom._yxml = null this._dom._yxml = null
this._dom = null this._dom = null
} }
if (this._beforeTransactionHandler !== undefined) {
this._y.off('beforeTransaction', this._beforeTransactionHandler)
}
} }
insertDomElementsAfter (prev, doms, _document) { insertDomElementsAfter (prev, doms, _document) {
const types = domToYXml(this, doms, _document) const types = domToYXml(this, doms, _document)
@@ -221,7 +199,9 @@ export default class YXmlFragment extends YArray {
_document = _document || document _document = _document || document
this._dom = dom this._dom = dom
dom._yxml = this dom._yxml = this
if (this._parent === null) { // TODO: refine this..
if ((this.constructor !== YXmlFragment && this._parent !== this._y) || this._parent === null) {
// TODO: only top level YXmlFragment can bind. Also allow YXmlElements..
return return
} }
this._y.on('beforeTransaction', beforeTransactionSelectionFixer) this._y.on('beforeTransaction', beforeTransactionSelectionFixer)
@@ -278,10 +258,9 @@ export default class YXmlFragment extends YArray {
}) })
// Apply Dom changes on Y.Xml // Apply Dom changes on Y.Xml
if (typeof MutationObserver !== 'undefined') { if (typeof MutationObserver !== 'undefined') {
this._beforeTransactionHandler = () => { this._y.on('beforeTransaction', () => {
this._domObserverListener(this._domObserver.takeRecords()) this._domObserverListener(this._domObserver.takeRecords())
} })
this._y.on('beforeTransaction', this._beforeTransactionHandler)
this._domObserverListener = mutations => { this._domObserverListener = mutations => {
this._mutualExclude(() => { this._mutualExclude(() => {
this._y.transact(() => { this._y.transact(() => {
@@ -295,9 +274,19 @@ export default class YXmlFragment extends YArray {
} }
switch (mutation.type) { switch (mutation.type) {
case 'characterData': case 'characterData':
var change = diff(yxml.toString(), dom.nodeValue) var diffs = diff(yxml.toString(), dom.nodeValue)
yxml.delete(change.pos, change.remove) var pos = 0
yxml.insert(change.pos, change.insert) 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
yxml.delete(pos, d[1].length)
} else { // INSERT
yxml.insert(pos, d[1])
pos += d[1].length
}
}
break break
case 'attributes': case 'attributes':
if (yxml.constructor === YXmlFragment) { if (yxml.constructor === YXmlFragment) {
@@ -324,9 +313,6 @@ export default class YXmlFragment extends YArray {
} }
}) })
for (let dom of diffChildren) { for (let dom of diffChildren) {
if (dom.yOnChildrenChanged !== undefined) {
dom.yOnChildrenChanged()
}
if (dom._yxml != null && dom._yxml !== false) { if (dom._yxml != null && dom._yxml !== false) {
applyChangesFromDom(dom) applyChangesFromDom(dom)
} }

View File

@@ -24,11 +24,6 @@ export default class YXmlHook extends YMap {
} }
return this._dom return this._dom
} }
_unbindFromDom () {
this._dom._yxml = null
this._yxml = null
// TODO: cleanup hook?
}
_fromBinary (y, decoder) { _fromBinary (y, decoder) {
const missing = super._fromBinary(y, decoder) const missing = super._fromBinary(y, decoder)
this.hookName = decoder.readVarString() this.hookName = decoder.readVarString()

View File

@@ -174,7 +174,7 @@ export function reflectChangesOnDom (events, _document) {
// let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement) // let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement)
if (yxml.constructor === YXmlText) { if (yxml.constructor === YXmlText) {
yxml._dom.nodeValue = yxml.toString() yxml._dom.nodeValue = yxml.toString()
} else if (event.attributesChanged !== undefined) { } else {
// update attributes // update attributes
event.attributesChanged.forEach(attributeName => { event.attributesChanged.forEach(attributeName => {
const value = yxml.getAttribute(attributeName) const value = yxml.getAttribute(attributeName)

View File

@@ -11,10 +11,6 @@ export default class ID {
return id !== null && id.user === this.user && id.clock === this.clock return id !== null && id.user === this.user && id.clock === this.clock
} }
lessThan (id) { lessThan (id) {
if (id.constructor === ID) { return this.user < id.user || (this.user === id.user && this.clock < id.clock)
return this.user < id.user || (this.user === id.user && this.clock < id.clock)
} else {
return false
}
} }
} }

View File

@@ -1,7 +1,6 @@
export default class NamedEventHandler { export default class NamedEventHandler {
constructor () { constructor () {
this._eventListener = new Map() this._eventListener = new Map()
this._stateListener = new Map()
} }
_getListener (name) { _getListener (name) {
let listeners = this._eventListener.get(name) let listeners = this._eventListener.get(name)
@@ -22,32 +21,16 @@ export default class NamedEventHandler {
let listeners = this._getListener(name) let listeners = this._getListener(name)
listeners.on.add(f) listeners.on.add(f)
} }
_initStateListener (name) {
let state = this._stateListener.get(name)
if (state === undefined) {
state = {}
state.promise = new Promise(function (resolve) {
state.resolve = resolve
})
this._stateListener.set(name, state)
}
return state
}
when (name) {
return this._initStateListener(name).promise
}
off (name, f) { off (name, f) {
if (name == null || f == null) { if (name == null || f == null) {
throw new Error('You must specify event name and function!') throw new Error('You must specify event name and function!')
} }
const listener = this._eventListener.get(name) const listener = this._eventListener.get(name)
if (listener !== undefined) { if (listener !== undefined) {
listener.on.delete(f) listener.remove(f)
listener.once.delete(f)
} }
} }
emit (name, ...args) { emit (name, ...args) {
this._initStateListener(name).resolve()
const listener = this._eventListener.get(name) const listener = this._eventListener.get(name)
if (listener !== undefined) { if (listener !== undefined) {
listener.on.forEach(f => f.apply(null, args)) listener.on.forEach(f => f.apply(null, args))

View File

@@ -12,10 +12,6 @@ export default class RootID {
return id !== null && id.user === this.user && id.name === this.name && id.type === this.type return id !== null && id.user === this.user && id.name === this.name && id.type === this.type
} }
lessThan (id) { lessThan (id) {
if (id.constructor === RootID) { return this.user < id.user || (this.user === id.user && (this.name < id.name || (this.name === id.name && this.type < id.type)))
return this.user < id.user || (this.user === id.user && (this.name < id.name || (this.name === id.name && this.type < id.type)))
} else {
return true
}
} }
} }

View File

@@ -1,15 +0,0 @@
export function createMutualExclude () {
var token = true
return function mutualExclude (f) {
if (token) {
token = false
try {
f()
} catch (e) {
console.error(e)
}
token = true
}
}
}

View File

@@ -2,31 +2,37 @@ import ID from './ID.js'
import RootID from './RootID.js' import RootID from './RootID.js'
export function getRelativePosition (type, offset) { export function getRelativePosition (type, offset) {
let t = type._start if (offset === 0) {
while (t !== null) { return ['startof', type._id.user, type._id.clock || null, type._id.name || null, type._id.type || null]
if (t._deleted === false) { } else {
if (t._length > offset) { let t = type._start
return [t._id.user, t._id.clock + offset] while (t !== null) {
if (t._deleted === false) {
if (t._length >= offset) {
return [t._id.user, t._id.clock + offset - 1]
}
if (t._right === null) {
return [t._id.user, t._id.clock + t._length - 1]
}
offset -= t._length
} }
offset -= t._length t = t._right
} }
t = t._right return null
} }
return ['endof', type._id.user, type._id.clock || null, type._id.name || null, type._id.type || null]
} }
export function fromRelativePosition (y, rpos) { export function fromRelativePosition (y, rpos) {
if (rpos[0] === 'endof') { if (rpos[0] === 'startof') {
let id let id
if (rpos[3] === null) { if (rpos[3] === null) {
id = new ID(rpos[1], rpos[2]) id = new ID(rpos[1], rpos[2])
} else { } else {
id = new RootID(rpos[3], rpos[4]) id = new RootID(rpos[3], rpos[4])
} }
const type = y.os.get(id)
return { return {
type, type: y.os.get(id),
offset: type.length offset: 0
} }
} else { } else {
let offset = 0 let offset = 0
@@ -36,7 +42,7 @@ export function fromRelativePosition (y, rpos) {
return null return null
} }
if (!struct._deleted) { if (!struct._deleted) {
offset = rpos[1] - struct._id.clock offset = rpos[1] - struct._id.clock + 1
} }
struct = struct._left struct = struct._left
while (struct !== null) { while (struct !== null) {

View File

@@ -1,19 +0,0 @@
export default function simpleDiff (a, b) {
let left = 0 // number of same characters counting from left
let right = 0 // number of same characters counting from right
while (left < a.length && left < b.length && a[left] === b[left]) {
left++
}
if (left !== a.length || left !== b.length) {
// Only check right if a !== b
while (right + left < a.length && right + left < b.length && a[a.length - right - 1] === b[b.length - right - 1]) {
right++
}
}
return {
pos: left,
remove: a.length - left - right,
insert: b.slice(left, b.length - right)
}
}

View File

@@ -5,7 +5,6 @@ import { generateUserID } from './Util/generateUserID.js'
import RootID from './Util/RootID.js' import RootID from './Util/RootID.js'
import NamedEventHandler from './Util/NamedEventHandler.js' import NamedEventHandler from './Util/NamedEventHandler.js'
import UndoManager from './Util/UndoManager.js' import UndoManager from './Util/UndoManager.js'
import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.js'
import { messageToString, messageToRoomname } from './MessageHandler/messageToString.js' import { messageToString, messageToRoomname } from './MessageHandler/messageToString.js'
@@ -23,53 +22,25 @@ import debug from 'debug'
import Transaction from './Transaction.js' import Transaction from './Transaction.js'
export default class Y extends NamedEventHandler { export default class Y extends NamedEventHandler {
constructor (room, opts, persistence) { constructor (opts) {
super() super()
this.room = room
if (opts != null) {
opts.connector.room = room
}
this._contentReady = false
this._opts = opts this._opts = opts
this.userID = generateUserID() this.userID = opts._userID != null ? opts._userID : generateUserID()
this.share = {} this.share = {}
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)
this.connector = new Y[opts.connector.name](this, opts.connector)
if (opts.persistence != null) {
this.persistence = new Y[opts.persistence.name](this, opts.persistence)
this.persistence.retrieveContent()
} else {
this.persistence = null
}
this.connected = true
this._missingStructs = new Map() this._missingStructs = new Map()
this._readyToIntegrate = [] this._readyToIntegrate = []
this._transaction = null this._transaction = null
this.connector = null
this.connected = false
let initConnection = () => {
if (opts != null) {
this.connector = new Y[opts.connector.name](this, opts.connector)
this.connected = true
this.emit('connectorReady')
}
}
if (persistence != null) {
this.persistence = persistence
persistence._init(this).then(initConnection)
} else {
this.persistence = null
initConnection()
}
}
_setContentReady () {
if (!this._contentReady) {
this._contentReady = true
this.emit('content')
}
}
whenContentReady () {
if (this._contentReady) {
return Promise.resolve()
} else {
return new Promise(resolve => {
this.once('content', resolve)
})
}
} }
_beforeChange () {} _beforeChange () {}
transact (f, remote = false) { transact (f, remote = false) {
@@ -119,6 +90,9 @@ export default class Y extends NamedEventHandler {
set _start (start) { set _start (start) {
return null return null
} }
get room () {
return this._opts.connector.room
}
define (name, TypeConstructor) { define (name, TypeConstructor) {
let id = new RootID(name, TypeConstructor) let id = new RootID(name, TypeConstructor)
let type = this.os.get(id) let type = this.os.get(id)
@@ -149,18 +123,11 @@ export default class Y extends NamedEventHandler {
} }
} }
destroy () { destroy () {
super.destroy()
this.share = null this.share = null
if (this.connector != null) { if (this.connector.destroy != null) {
if (this.connector.destroy != null) { this.connector.destroy()
this.connector.destroy() } else {
} else { this.connector.disconnect()
this.connector.disconnect()
}
}
if (this.persistence !== null) {
this.persistence.deinit(this)
this.persistence = null
} }
this.os = null this.os = null
this.ds = null this.ds = null
@@ -188,7 +155,7 @@ Y.extend = function extendYjs () {
// TODO: The following assignments should be moved to yjs-dist // TODO: The following assignments should be moved to yjs-dist
Y.AbstractConnector = Connector Y.AbstractConnector = Connector
Y.AbstractPersistence = Persistence Y.Persisence = Persistence
Y.Array = YArray Y.Array = YArray
Y.Map = YMap Y.Map = YMap
Y.Text = YText Y.Text = YText
@@ -202,8 +169,7 @@ Y.utils = {
UndoManager, UndoManager,
getRelativePosition, getRelativePosition,
fromRelativePosition, fromRelativePosition,
addType, addType
integrateRemoteStructs
} }
Y.debug = debug Y.debug = debug

View File

@@ -1,29 +0,0 @@
import { test } from '../node_modules/cutest/cutest.mjs'
import simpleDiff from '../src/Util/simpleDiff.js'
import Chance from 'chance'
function runDiffTest (t, a, b, expected) {
let result = simpleDiff(a, b)
t.compare(result, expected, `Compare "${a}" with "${b}"`)
}
test('diff tests', async function diff1 (t) {
runDiffTest(t, 'abc', 'axc', { pos: 1, remove: 1, insert: 'x' })
runDiffTest(t, 'bc', 'xc', { pos: 0, remove: 1, insert: 'x' })
runDiffTest(t, 'ab', 'ax', { pos: 1, remove: 1, insert: 'x' })
runDiffTest(t, 'b', 'x', { pos: 0, remove: 1, insert: 'x' })
runDiffTest(t, '', 'abc', { pos: 0, remove: 0, insert: 'abc' })
runDiffTest(t, 'abc', 'xyz', { pos: 0, remove: 3, insert: 'xyz' })
runDiffTest(t, 'axz', 'au', { pos: 1, remove: 2, insert: 'u' })
runDiffTest(t, 'ax', 'axy', { pos: 2, remove: 0, insert: 'y' })
})
test('random diff tests', async function randomDiff (t) {
const chance = new Chance(t.getSeed() * 1000000000)
let a = chance.word()
let b = chance.word()
let change = simpleDiff(a, b)
let arr = Array.from(a)
arr.splice(change.pos, change.remove, ...Array.from(change.insert))
t.assert(arr.join('') === b, 'Applying change information is correct')
})

View File

@@ -54,9 +54,6 @@ test('varString', async function varString (t) {
testEncoding(t, writeVarString, readVarString, 'test!') testEncoding(t, writeVarString, readVarString, 'test!')
testEncoding(t, writeVarString, readVarString, '☺☺☺') testEncoding(t, writeVarString, readVarString, '☺☺☺')
testEncoding(t, writeVarString, readVarString, '1234') testEncoding(t, writeVarString, readVarString, '1234')
testEncoding(t, writeVarString, readVarString, '쾟')
testEncoding(t, writeVarString, readVarString, '龟') // surrogate length 3
testEncoding(t, writeVarString, readVarString, '😝') // surrogate length 4
}) })
test('varString random', async function varStringRandom (t) { test('varString random', async function varStringRandom (t) {

View File

@@ -3,6 +3,6 @@
<head> <head>
</head> </head>
<body> <body>
<script type="module" src="./index.js"></script> <script type="module" src="./encode-decode.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,6 +0,0 @@
import './red-black-tree.js'
import './y-array.tests.js'
import './y-map.tests.js'
import './y-xml.tests.js'
import './encode-decode.tests.js'
import './diff.tests.js'

View File

@@ -145,7 +145,7 @@ export async function initArrays (t, opts) {
} else { } else {
connOpts = Object.assign({ role: 'slave' }, conn) connOpts = Object.assign({ role: 'slave' }, conn)
} }
let y = new Y(connOpts.room, { let y = new Y({
_userID: i, // evil hackery, don't try this at home _userID: i, // evil hackery, don't try this at home
connector: connOpts connector: connOpts
}) })

8
y.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1445
y.node.js

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

1757
y.test.js

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long