diff --git a/Examples/TextBind/index.html b/Examples/TextBind/index.html index e97b932e..9bfd4525 100644 --- a/Examples/TextBind/index.html +++ b/Examples/TextBind/index.html @@ -1,20 +1,12 @@ -
- -Collaborative Json editing with yjs -and XMPP Connector.
- - - -yjs is a Framework for Real-Time collaboration on arbitrary data types. -
diff --git a/Examples/TextBind/index.js b/Examples/TextBind/index.js index 90d5657f..d489a02a 100644 --- a/Examples/TextBind/index.js +++ b/Examples/TextBind/index.js @@ -1,25 +1,47 @@ /* global Y */ +// create a shared object. This function call will return a promise! Y({ db: { name: 'Memory' }, connector: { name: 'WebRTC', - room: 'mineeeeeee', + room: 'TextBindDemo', debug: true } }).then(function (yconfig) { - window.y = yconfig.root + // yconfig holds all the information about the shared object window.yconfig = yconfig + // yconfig.root holds the shared element + window.y = yconfig.root + + // now we bind the textarea and the contenteditable h1 element + // to a shared element var textarea = document.getElementById('textfield') var contenteditable = document.getElementById('contenteditable') yconfig.root.observePath(['text'], function (text) { + // every time the 'text' property of the yconfig.root changes, + // this function is called. Then we bind it to the html elements if (text != null) { + // when the text property is deleted, text may be undefined! + // This is why we have to check if text exists.. text.bind(textarea) text.bind(contenteditable) - window.ytext = text } }) + // create a shared TextBind yconfig.root.set('text', Y.TextBind) + + // We also provide a button for disconnecting/reconnecting the shared element + var button = document.querySelector('#button') + button.onclick = function () { + if (button.innerText === 'Disconnect') { + yconfig.disconnect() + button.innerText = 'Reconnect' + } else { + yconfig.reconnect() + button.innerText = 'Disconnect' + } + } }) diff --git a/gulpfile.js b/gulpfile.js index 5f947329..26a03eb4 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -97,7 +97,7 @@ if (options.regenerator) { files.test = polyfills.concat(files.test) } -gulp.task('build:deploy', function () { +gulp.task('deploy', function () { gulp.src(files.src) .pipe(sourcemaps.init()) .pipe(concat('y.js')) @@ -120,6 +120,13 @@ gulp.task('build:test', function () { if (!options.regenerator) { babelOptions.blacklist = 'regenerator' } + gulp.src(files.src) + .pipe(sourcemaps.init()) + .pipe(concat('y.js')) + .pipe(babel(babelOptions)) + .pipe(sourcemaps.write()) + .pipe(gulp.dest('.')) + return gulp.src('src/**/*.js') .pipe(sourcemaps.init()) .pipe(babel(babelOptions)) @@ -140,10 +147,6 @@ gulp.task('dev:browser', ['build:test'], function () { .pipe(jasmineBrowser.server({port: options.testport})) }) -gulp.task('dev:deploy', ['build:deploy'], function () { - gulp.watch('src/**/*.js', ['build:deploy']) -}) - gulp.task('dev', ['build:test'], function () { gulp.start('dev:browser') gulp.start('dev:node') diff --git a/src/Connectors/WebRTC.js b/src/Connectors/WebRTC.js index 954aa0d0..91e5ca26 100644 --- a/src/Connectors/WebRTC.js +++ b/src/Connectors/WebRTC.js @@ -1,4 +1,4 @@ -/* global Y */ +/* global Y, SimpleWebRTC */ 'use strict' class WebRTC extends Y.AbstractConnector { @@ -11,21 +11,16 @@ class WebRTC extends Y.AbstractConnector { } options.role = 'slave' super(y, options) - - var room = options.room - - var webrtcOptions = { + this.webrtcOptions = { url: options.url || 'https://yatta.ninja:8888', room: options.room } - - var swr = new SimpleWebRTC(webrtcOptions) // eslint-disable-line no-undef + var swr = new SimpleWebRTC(this.webrtcOptions) this.swr = swr var self = this - swr.once('connectionReady', function (userId) { // SimpleWebRTC (swr) is initialized - swr.joinRoom(room) + swr.joinRoom(self.webrtcOptions.room) swr.once('joinedRoom', function () { self.setUserId(userId) @@ -61,6 +56,14 @@ class WebRTC extends Y.AbstractConnector { }) }) } + disconnect () { + this.swr.leaveRoom() + super.disconnect() + } + reconnect () { + this.swr.joinRoom(this.webrtcOptions.room) + super.reconnect() + } send (uid, message) { var self = this // we have to make sure that the message is sent under all circumstances diff --git a/src/OperationStore.js b/src/OperationStore.js index 1127b934..954d9234 100644 --- a/src/OperationStore.js +++ b/src/OperationStore.js @@ -115,10 +115,26 @@ class AbstractTransaction { }) } } + + * deleteList (start) { + if (this.store.y.connector.isSynced) { + while (start != null && this.store.y.connector.isSynced) { + start = (yield* this.getOperation(start)) + start.gc = true + yield* this.setOperation(start) + // TODO: will always reset the parent.. + this.store.gc1.push(start.id) + start = start.right + } + } else { + // TODO: when not possible??? do later in (gcWhenSynced) + } + } + /* Mark an operation as deleted, and add it to the GC, if possible. */ - * deleteOperation (targetId) { + * deleteOperation (targetId, preventCallType) { var target = yield* this.getOperation(targetId) if (target == null || !target.deleted) { @@ -129,12 +145,31 @@ class AbstractTransaction { if (!target.deleted) { // set deleted & notify type target.deleted = true - var type = this.store.initializedTypes[JSON.stringify(target.parent)] - if (type != null) { - yield* type._changed(this, { - struct: 'Delete', - target: targetId - }) + if (!preventCallType) { + var type = this.store.initializedTypes[JSON.stringify(target.parent)] + if (type != null) { + yield* type._changed(this, { + struct: 'Delete', + target: targetId + }) + } + } + // delete containing lists + if (target.start != null) { + // TODO: don't do it like this .. -.- + yield* this.deleteList(target.start) + yield* this.deleteList(target.id) + } + if (target.map != null) { + for (var name in target.map) { + yield* this.deleteList(target.map[name]) + } + // TODO: here to.. (see above) + yield* this.deleteList(target.id) + } + if (target.opContent != null) { + yield* this.deleteOperation(target.opContent) + target.opContent = null } } var left = target.left != null ? yield* this.getOperation(target.left) : null @@ -182,10 +217,12 @@ class AbstractTransaction { // if op exists, then clean that mess up.. var o = yield* this.getOperation(id) if (o != null) { + /* if (!o.deleted) { yield* this.deleteOperation(id) o = yield* this.getOperation(id) } + */ // remove gc'd op from the left op, if it exists if (o.left != null) { @@ -233,23 +270,26 @@ class AbstractTransaction { yield* this.setOperation(right) } - // remove gc'd op from parent, if it exists - var parent = yield* this.getOperation(o.parent) - var setParent = false // whether to save parent to the os - if (Y.utils.compareIds(parent.start, o.id)) { - // gc'd op is the start - setParent = true - parent.start = o.right + if (o.parent != null) { + // remove gc'd op from parent, if it exists + var parent = yield* this.getOperation(o.parent) + var setParent = false // whether to save parent to the os + if (Y.utils.compareIds(parent.start, o.id)) { + // gc'd op is the start + setParent = true + parent.start = o.right + } + if (Y.utils.compareIds(parent.end, o.id)) { + // gc'd op is the end + setParent = true + parent.end = o.left + } + if (setParent) { + yield* this.setOperation(parent) + } } - if (Y.utils.compareIds(parent.end, o.id)) { - // gc'd op is the end - setParent = true - parent.end = o.left - } - if (setParent) { - yield* this.setOperation(parent) - } - yield* this.removeOperation(o.id) // actually remove it from the os + // finally remove it from the os + yield* this.removeOperation(o.id) } } } diff --git a/src/OperationStores/Memory.js b/src/OperationStores/Memory.js index 743c59d4..a660a313 100644 --- a/src/OperationStores/Memory.js +++ b/src/OperationStores/Memory.js @@ -203,12 +203,11 @@ Y.Memory = (function () { for (var i in deletions) { var del = deletions[i] var id = [del[0], del[1]] + // always try to delete.. + yield* this.deleteOperation(id) if (del[2]) { // gc yield* this.garbageCollectOperation(id) - } else { - // delete - yield* this.deleteOperation(id) } } } diff --git a/src/Struct.js b/src/Struct.js index 704a48a5..03910719 100644 --- a/src/Struct.js +++ b/src/Struct.js @@ -194,12 +194,20 @@ var Struct = { yield* this.setOperation(right) } - // notify parent + // update parents .map/start/end properties if (op.parentSub != null) { if (left == null) { parent.map[op.parentSub] = op.id yield* this.setOperation(parent) } + // is a child of a map struct. + // Then also make sure that only the most left element is not deleted + if (op.right != null) { + yield* this.deleteOperation(op.right, true) + } + if (op.left != null) { + yield* this.deleteOperation(op.id, true) + } } else { if (right == null || left == null) { if (right == null) {