Compare commits

..

42 Commits

Author SHA1 Message Date
Kevin Jahns
29760cfe53 v13.0.0-55 -- distribution files 2018-03-14 18:52:59 -07:00
Kevin Jahns
941a22b257 13.0.0-55 2018-03-14 18:52:49 -07:00
Kevin Jahns
4aa41b98a9 fix dom filtering bug 2018-03-14 18:51:48 -07:00
Kevin Jahns
079de07eff 13.0.0-54 2018-03-01 16:45:25 +01:00
Kevin Jahns
54453e87fa fix consecutive undo,redo,undo,redo.. (abc test) 2018-03-01 16:44:26 +01:00
Kevin Jahns
1b0e3659c3 undo fixes for consecutive undo-redo 2018-03-01 13:50:01 +01:00
Kevin Jahns
641f426339 13.0.0-53 2018-02-25 02:31:59 +01:00
Kevin Jahns
fcbca65d8f fromBinary is a transaction 2018-02-25 02:31:20 +01:00
Kevin Jahns
5f8ae0dd43 13.0.0-52 2018-02-18 19:20:00 +01:00
Kevin Jahns
de14fe0f3e fix getAttribute vs attributes.value fixes y-js/y-xml#8 2018-02-18 18:58:49 +01:00
Kevin Jahns
5e4b071693 actually use clock in undo-manager 2018-02-15 18:58:43 +01:00
Kevin Jahns
937de2c59f fix fast undo-redo bug 2018-02-15 18:28:53 +01:00
Kevin Jahns
f1f1bff901 preliminary undo-redo fixes 2018-02-15 17:58:14 +01:00
Kevin Jahns
4855b2d590 13.0.0-51 2018-02-07 14:08:43 +01:00
Kevin Jahns
908ce31e2f Merge branch 'master' of github.com:y-js/yjs 2018-02-07 14:08:07 +01:00
Kevin Jahns
e4d4c23f0b bugfix - persist deletes when syncing 2018-02-07 14:07:57 +01:00
Kevin Jahns
fc500a8247 Merge pull request #94 from LukasDrgon/patch-3
Add CDN usage
2018-01-31 20:36:26 -08:00
Kevin Jahns
4b84541d76 13.0.0-50 2018-01-30 20:12:58 -08:00
Kevin Jahns
a3ab42c157 implemnt mutual exclude pattern directly in Persistence.js 2018-01-30 20:11:59 -08:00
Kevin Jahns
bbd3317d62 13.0.0-49 2018-01-30 15:53:33 -08:00
Kevin Jahns
5d3922cb64 fix undo-redo 2018-01-30 15:52:36 -08:00
Kevin Jahns
a81a2cd553 13.0.0-48 2018-01-29 16:41:52 -08:00
Kevin Jahns
c0d24bdba4 lint 2018-01-29 16:41:27 -08:00
Kevin Jahns
40e913e9c5 add toBinary and fromBinary to Y.utils 2018-01-29 16:39:09 -08:00
Kevin Jahns
94f6a0fd9c implement Y.*Binding approach 2018-01-29 11:55:28 -08:00
Kevin Jahns
41a88dbc43 fix examples for Yjs@13 2018-01-25 17:28:33 -07:00
Kevin Jahns
1d4f283955 13.0.0-47 2018-01-18 18:44:56 +01:00
Kevin Jahns
fc3a4c376c implement when-handler 2018-01-18 18:44:20 +01:00
Lukas Drgon
acb0affa33 Add CDN usage 2018-01-17 22:03:49 +01:00
Kevin Jahns
0b510b64a3 persistence updates + make Persistence.init async 2018-01-16 16:13:47 +01:00
Kevin Jahns
c8f0cf5556 13.0.0-46 2018-01-10 00:20:03 +01:00
Kevin Jahns
11a4271fd1 13.0.0-45 2018-01-10 00:18:50 +01:00
Kevin Jahns
c7670915c7 Merge branch 'master' of github.com:y-js/yjs 2018-01-10 00:17:34 +01:00
Kevin Jahns
eb2d596538 implement mutualExclude factory 2018-01-10 00:17:26 +01:00
Kevin Jahns
48e17ea1a7 13.0.0-44 2018-01-10 00:16:33 +01:00
Kevin Jahns
1a22fdd45e persistence improvements 2018-01-10 00:11:25 +01:00
Kevin Jahns
07cf0b3436 export AbstractPersistence 2018-01-08 17:30:30 +01:00
Kevin Jahns
5a68b9f4ad loaded event when loaded from persistence adapter 2018-01-08 02:28:46 +01:00
Kevin Jahns
445dd3e0da fix several y-xml bugs 2018-01-03 03:50:27 +01:00
Kevin Jahns
0ba97d78f8 better relative cursor positions for text editing - decrease number of generated messages for cursor 2017-12-31 16:14:02 +01:00
Kevin Jahns
fc5be5c7cc fix empty string insertion bug 2017-12-31 14:49:20 +01:00
Kevin Jahns
f2debc150c reimplement persistence approach 2017-12-24 03:18:00 +01:00
72 changed files with 1935 additions and 21499 deletions

View File

@@ -64,6 +64,19 @@ missing modules.
<script src="./bower_components/yjs/y.js"></script> <script src="./bower_components/yjs/y.js"></script>
``` ```
### CDN
```
<script src="https://cdn.jsdelivr.net/npm/yjs@12/src/y.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-array@10/dist/y-array.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-websockets-client@8/dist/y-websockets-client.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-memory@8/dist/y-memory.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-array@10/dist/y-array.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-map@10/dist/y-map.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-text@9/dist/y-text.js"></script>
// ..
// do the same for all modules you want to use
```
### Npm ### Npm
``` ```
npm install --save yjs % add all y-* modules you want to use npm install --save yjs % add all y-* modules you want to use

View File

@@ -24,7 +24,8 @@
<body> <body>
<div id="aceContainer"></div> <div id="aceContainer"></div>
<script src="../bower_components/yjs/y.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="../bower_components/ace-builds/src/ace.js"></script> <script src="../bower_components/ace-builds/src/ace.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>

View File

@@ -1,24 +1,17 @@
/* global Y, ace */ /* global Y, ace */
Y({ let y = new Y('ace-example', {
db: {
name: 'memory'
},
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
room: 'ace-example' url: 'http://127.0.0.1:1234'
},
sourceDir: '/bower_components',
share: {
ace: 'Text' // y.share.textarea is of type Y.Text
} }
}).then(function (y) {
window.yAce = y
// bind the textarea to a shared text element
var editor = ace.edit('aceContainer')
editor.setTheme('ace/theme/chrome')
editor.getSession().setMode('ace/mode/javascript')
y.share.ace.bindAce(editor)
}) })
window.yAce = y
// bind the textarea to a shared text element
var editor = ace.edit('aceContainer')
editor.setTheme('ace/theme/chrome')
editor.getSession().setMode('ace/mode/javascript')
y.define('ace', Y.Text).bindAce(editor)

View File

@@ -13,7 +13,7 @@
<input type="submit" value="Send"> <input type="submit" value="Send">
</form> </form>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src="../../../y-websockets-client/dist/y-websockets-client.js"></script> <script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,10 +1,9 @@
/* global Y */ /* global Y */
// initialize a shared object. This function call returns a promise! let y = new Y('chat-example', {
var y = new Y({
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
room: 'chat-example' url: 'http://127.0.0.1:1234'
} }
}) })
@@ -23,6 +22,7 @@ function appendMessage (message, position) {
p.appendChild(document.createTextNode(message.message)) p.appendChild(document.createTextNode(message.message))
chatcontainer.insertBefore(p, chatcontainer.children[position] || null) chatcontainer.insertBefore(p, chatcontainer.children[position] || null)
} }
// This function makes sure that only 7 messages exist in the chat history. // This function makes sure that only 7 messages exist in the chat history.
// The rest is deleted // The rest is deleted
function cleanupChat () { function cleanupChat () {
@@ -30,23 +30,17 @@ function cleanupChat () {
chatprotocol.delete(0, chatprotocol.length - 7) chatprotocol.delete(0, chatprotocol.length - 7)
} }
} }
cleanupChat()
// Insert the initial content // Insert the initial content
chatprotocol.toArray().forEach(appendMessage) chatprotocol.toArray().forEach(appendMessage)
cleanupChat()
// whenever content changes, make sure to reflect the changes in the DOM // whenever content changes, make sure to reflect the changes in the DOM
chatprotocol.observe(function (event) { chatprotocol.observe(function (event) {
if (event.type === 'insert') {
for (let i = 0; i < event.length; i++) {
appendMessage(event.values[i], event.index + i)
}
} else if (event.type === 'delete') {
for (let i = 0; i < event.length; i++) {
chatcontainer.children[event.index].remove()
}
}
// concurrent insertions may result in a history > 7, so cleanup here // concurrent insertions may result in a history > 7, so cleanup here
cleanupChat() cleanupChat()
chatcontainer.innerHTML = ''
chatprotocol.toArray().forEach(appendMessage)
}) })
document.querySelector('#chatform').onsubmit = function (event) { document.querySelector('#chatform').onsubmit = function (event) {
// the form is submitted // the form is submitted

View File

@@ -5,7 +5,8 @@
<body> <body>
<div id="codeMirrorContainer"></div> <div id="codeMirrorContainer"></div>
<script src="../bower_components/yjs/y.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="../bower_components/codemirror/lib/codemirror.js"></script> <script src="../bower_components/codemirror/lib/codemirror.js"></script>
<script src="../bower_components/codemirror/mode/javascript/javascript.js"></script> <script src="../bower_components/codemirror/mode/javascript/javascript.js"></script>
<link rel="stylesheet" href="../bower_components/codemirror/lib/codemirror.css"> <link rel="stylesheet" href="../bower_components/codemirror/lib/codemirror.css">

View File

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

View File

@@ -12,7 +12,8 @@
</style> </style>
<button type="button" id="clearDrawingCanvas">Clear Drawing</button> <button type="button" id="clearDrawingCanvas">Clear Drawing</button>
<svg id="drawingCanvas" viewbox="0 0 100 100" width="100%"></svg> <svg id="drawingCanvas" viewbox="0 0 100 100" width="100%"></svg>
<script src="../yjs-dist.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="../bower_components/d3/d3.min.js"></script> <script src="../bower_components/d3/d3.min.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>

View File

@@ -1,11 +1,9 @@
/* globals Y, d3 */ /* globals Y, d3 */
let y = new Y({ let y = new Y('drawing-example', {
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
url: 'http://127.0.0.1:1234', url: 'http://127.0.0.1:1234'
room: 'drawing-example'
// maxBufferLength: 100
} }
}) })

View File

@@ -1,7 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
</head> </head>
<script src="../yjs-dist.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="../bower_components/d3/d3.min.js"></script> <script src="../bower_components/d3/d3.min.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
<style> <style>

View File

@@ -107,15 +107,13 @@ Y.XmlHook.addHook('magic-drawing', {
} }
}) })
// initialize a shared object. This function call returns a promise! let y = new Y('html-editor-drawing-hook-example', {
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-example6'
// maxBufferLength: 100
} }
}) })
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,10 +1,8 @@
<!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="../../y.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src='../../../y-websockets-client/y-websockets-client.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,85 +1,24 @@
/* global Y, HTMLElement, customElements, CanvasJS */ /* global Y */
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)
} }
class MagicTable extends HTMLElement { let y = new Y('htmleditor', {
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!
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-example6'
// maxBufferLength: 100
} }
}) })
window.yXml = y
window.y = 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, {
captureTimeout: 500 captureTimeout: 500
}) })
document.onkeydown = function interceptUndoRedo (e) { document.onkeydown = function interceptUndoRedo (e) {
if (e.keyCode === 90 && e.metaKey) { if (e.keyCode === 90 && (e.metaKey || e.ctrlKey)) {
if (!e.shiftKey) { if (!e.shiftKey) {
window.undoManager.undo() window.undoManager.undo()
} else { } else {

View File

@@ -16,6 +16,9 @@
width: 100%; width: 100%;
} }
</style> </style>
<script type="module" src="./index.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src='../../../y-indexeddb/y-indexeddb.js'></script>
<script src="./index.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,24 +1,19 @@
/* global Y, CodeMirror */ /* global Y, CodeMirror */
// initialize a shared object. This function call returns a promise! const persistence = new Y.IndexedDB()
Y({ const connector = {
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) { }
window.yCodeMirror = y
var editor = CodeMirror(document.querySelector('#codeMirrorContainer'), { const y = new Y('codemirror-example', connector, persistence)
mode: 'javascript', window.yCodeMirror = y
lineNumbers: true
}) var editor = CodeMirror(document.querySelector('#codeMirrorContainer'), {
y.share.codemirror.bindCodeMirror(editor) mode: 'javascript',
lineNumbers: true
}) })
y.define('codemirror', Y.Text).bindCodeMirror(editor)

View File

@@ -10,7 +10,7 @@
.one { .one {
grid-column: 1 ; grid-column: 1 ;
} }
.two { .two {
grid-column: 2; grid-column: 2;
} }
.three { .three {
@@ -49,10 +49,7 @@
</div> </div>
</div> </div>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src="../../../y-array/y-array.js"></script> <script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="../../../y-text/dist/y-text.js"></script>
<script src="../../../y-memory/y-memory.js"></script>
<script src="../../../y-websockets-client/y-websockets-client.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,64 +1,38 @@
/* global Y */ /* global Y */
Y({ function bindYjsInstance (y, suffix) {
db: { y.define('textarea', Y.Text).bind(document.getElementById('textarea' + suffix))
name: 'memory'
},
connector: {
name: 'websockets-client',
room: 'Textarea-example',
url: 'https://yjs-v13.herokuapp.com/'
},
share: {
textarea: 'Text'
}
}).then(function (y) {
window.y1 = y
y.share.textarea.bind(document.getElementById('textarea1'))
})
Y({
db: {
name: 'memory'
},
connector: {
name: 'websockets-client',
room: 'Textarea-example',
url: 'https://yjs-v13-second.herokuapp.com/'
},
share: {
textarea: 'Text'
}
}).then(function (y) {
window.y2 = y
y.share.textarea.bind(document.getElementById('textarea2'))
y.connector.socket.on('connection', function () { y.connector.socket.on('connection', function () {
document.getElementById('container2').removeAttribute('disconnected') document.getElementById('container' + suffix).removeAttribute('disconnected')
}) })
y.connector.socket.on('disconnect', function () { y.connector.socket.on('disconnect', function () {
document.getElementById('container2').setAttribute('disconnected', true) document.getElementById('container' + suffix).setAttribute('disconnected', true)
}) })
}) }
Y({ let y1 = new Y('infinite-example', {
db: {
name: 'memory'
},
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
room: 'Textarea-example', url: 'http://127.0.0.1:1234'
url: 'https://yjs-v13-third.herokuapp.com/'
},
share: {
textarea: 'Text'
} }
}).then(function (y) {
window.y3 = y
y.share.textarea.bind(document.getElementById('textarea3'))
y.connector.socket.on('connection', function () {
document.getElementById('container3').removeAttribute('disconnected')
})
y.connector.socket.on('disconnect', function () {
document.getElementById('container3').setAttribute('disconnected', true)
})
}) })
window.y1 = y1
bindYjsInstance(y1, '1')
let y2 = new Y('infinite-example', {
connector: {
name: 'websockets-client',
url: 'http://127.0.0.1:1234'
}
})
window.y2 = y2
bindYjsInstance(y2, '2')
let y3 = new Y('infinite-example', {
connector: {
name: 'websockets-client',
url: 'http://127.0.0.1:1234'
}
})
window.y3 = y3
bindYjsInstance(y1, '3')

View File

@@ -17,9 +17,7 @@
</g> </g>
</svg> </svg>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src="../../../y-map/dist/y-map.js"></script> <script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="../../../y-memory/y-memory.js"></script>
<script src="../../../y-websockets-client/y-websockets-client.js"></script>
<script src="../bower_components/d3/d3.js"></script> <script src="../bower_components/d3/d3.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>

View File

@@ -1,74 +1,67 @@
/* @flow */
/* global Y, d3 */ /* global Y, d3 */
// initialize a shared object. This function call returns a promise! let y = new Y('jigsaw-example', {
Y({
db: {
name: 'memory'
},
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
room: 'Puzzle-example', url: 'http://127.0.0.1:1234'
url: 'http://localhost:1234'
},
share: {
piece1: 'Map',
piece2: 'Map',
piece3: 'Map',
piece4: 'Map'
} }
}).then(function (y) { })
window.yJigsaw = y
var origin // mouse start position - translation of piece
var drag = d3.behavior.drag()
.on('dragstart', function (params) {
// get the translation of the element
var translation = d3
.select(this)
.attr('transform')
.slice(10, -1)
.split(',')
.map(Number)
// mouse coordinates
var mouse = d3.mouse(this.parentNode)
origin = {
x: mouse[0] - translation[0],
y: mouse[1] - translation[1]
}
})
.on('drag', function () {
var mouse = d3.mouse(this.parentNode)
var x = mouse[0] - origin.x // =^= mouse - mouse at dragstart + translation at dragstart
var y = mouse[1] - origin.y
d3.select(this).attr('transform', 'translate(' + x + ',' + y + ')')
})
.on('dragend', function (piece, i) {
// save the current translation of the puzzle piece
var mouse = d3.mouse(this.parentNode)
var x = mouse[0] - origin.x
var y = mouse[1] - origin.y
piece.set('translation', {x: x, y: y})
})
var data = [y.share.piece1, y.share.piece2, y.share.piece3, y.share.piece4] let jigsaw = y.define('jigsaw', Y.Map)
var pieces = d3.select(document.querySelector('#puzzle-example')).selectAll('path').data(data) window.yJigsaw = y
pieces var origin // mouse start position - translation of piece
.classed('draggable', true) var drag = d3.behavior.drag()
.attr('transform', function (piece) { .on('dragstart', function (params) {
var translation = piece.get('translation') || {x: 0, y: 0} // get the translation of the element
return 'translate(' + translation.x + ',' + translation.y + ')' var translation = d3
}).call(drag) .select(this)
.attr('transform')
.slice(10, -1)
.split(',')
.map(Number)
// mouse coordinates
var mouse = d3.mouse(this.parentNode)
origin = {
x: mouse[0] - translation[0],
y: mouse[1] - translation[1]
}
})
.on('drag', function () {
var mouse = d3.mouse(this.parentNode)
var x = mouse[0] - origin.x // =^= mouse - mouse at dragstart + translation at dragstart
var y = mouse[1] - origin.y
d3.select(this).attr('transform', 'translate(' + x + ',' + y + ')')
})
.on('dragend', function (piece, i) {
// save the current translation of the puzzle piece
var mouse = d3.mouse(this.parentNode)
var x = mouse[0] - origin.x
var y = mouse[1] - origin.y
jigsaw.set(piece, {x: x, y: y})
})
data.forEach(function (piece) { var data = ['piece1', 'piece2', 'piece3', 'piece4']
piece.observe(function () { var pieces = d3.select(document.querySelector('#puzzle-example')).selectAll('path').data(data)
// whenever a property of a piece changes, update the translation of the pieces
pieces pieces
.transition() .classed('draggable', true)
.attr('transform', function (piece) { .attr('transform', function (piece) {
var translation = piece.get('translation') || {x: 0, y: 0} var translation = piece.get('translation') || {x: 0, y: 0}
return 'translate(' + translation.x + ',' + translation.y + ')' return 'translate(' + translation.x + ',' + translation.y + ')'
}) }).call(drag)
})
data.forEach(function (piece) {
jigsaw.observe(function () {
// whenever a property of a piece changes, update the translation of the pieces
pieces
.transition()
.attr('transform', function (piece) {
var translation = piece.get(piece)
if (translation == null || typeof translation.x !== 'number' || typeof translation.y !== 'number') {
translation = { x: 0, y: 0 }
}
return 'translate(' + translation.x + ',' + translation.y + ')'
})
}) })
}) })

View File

@@ -13,8 +13,8 @@
width: 100%; width: 100%;
} }
</style> </style>
<script src="../bower_components/yjs/y.js"></script> <script src="../../y.js"></script>
<script src="../bower_components/y-websockets-client/y-websockets-client.js"></script> <script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script> <script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>

View File

@@ -2,29 +2,21 @@
require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' } }) require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' } })
require(['vs/editor/editor.main'], function () { let y = new Y('monaco-example', {
// Initialize a shared object. This function call returns a promise! connector: {
Y({ name: 'websockets-client',
db: { url: 'http://127.0.0.1:1234'
name: 'memory' }
}, })
connector: {
name: 'websockets-client', require(['vs/editor/editor.main'], function () {
room: 'monaco-example' window.yMonaco = y
},
sourceDir: '/bower_components', // Create Monaco editor
share: { var editor = monaco.editor.create(document.getElementById('monacoContainer'), {
monaco: 'Text' // y.share.monaco is of type Y.Text language: 'javascript'
} })
}).then(function (y) {
window.yMonaco = y // Bind to y.share.monaco
y.define('monaco', Y.Text).bindMonaco(editor)
// Create Monaco editor
var editor = monaco.editor.create(document.getElementById('monacoContainer'), {
language: 'javascript'
})
// Bind to y.share.monaco
y.share.monaco.bindMonaco(editor)
})
}) })

File diff suppressed because it is too large Load Diff

View File

@@ -9,12 +9,15 @@
"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": ["bower_components"] "ignore": [
"bower_components"
]
} }
} }

View File

@@ -26,10 +26,7 @@
<script src="../bower_components/quill/dist/quill.js"></script> <script src="../bower_components/quill/dist/quill.js"></script>
--> -->
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src="../../../y-array/y-array.js"></script> <script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="../../../y-richtext/dist/y-richtext.js"></script>
<script src="../../../y-memory/y-memory.js"></script>
<script src="../../../y-websockets-client/y-websockets-client.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>
</html> </html>

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.esm', input: 'yjs-dist.mjs',
name: 'Y', name: 'Y',
output: { output: {
file: 'yjs-dist.js', file: 'yjs-dist.js',

View File

@@ -1,11 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<body> <body>
<textarea style="width:80%;" rows=40 id="textfield" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea> <textarea style="width:80%;" rows=40 autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src="../../../y-array/y-array.js"></script> <script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="../../../y-text/y-text.js"></script>
<script src="../../../y-websockets-client/y-websockets-client.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,23 +1,15 @@
/* global Y */ /* global Y */
// initialize a shared object. This function call returns a promise! let y = new Y('textarea-example', {
Y({
db: {
name: 'memory'
},
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
room: 'Textarea-example2', url: 'http://127.0.0.1:1234'
// url: '//localhost:1234', }
url: 'https://yjs-v13.herokuapp.com/'
},
share: {
textarea: 'Text'
},
timeout: 5000 // reject if no connection was established within 5 seconds
}).then(function (y) {
window.yTextarea = y
// bind the textarea to a shared text element
y.share.textarea.bind(document.getElementById('textfield'))
}) })
window.yTextarea = y
// bind the textarea to a shared text element
let type = y.define('textarea', Y.Text)
let textarea = document.querySelector('textarea')
window.binding = new Y.TextareaBinding(type, textarea)

View File

@@ -3,7 +3,8 @@
</head> </head>
<!-- jquery is not required for y-xml. It is just here for convenience, and to test batch operations. --> <!-- 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="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="../yjs-dist.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</head> </head>
<body> <body>

View File

@@ -1,23 +1,13 @@
/* global Y */ /* global Y */
// initialize a shared object. This function call returns a promise! let y = new Y('xml-example', {
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'
url: 'http://192.168.178.81:1234',
room: 'Xml-example'
},
sourceDir: '/bower_components',
share: {
xml: 'Xml("p")' // y.share.xml is of type Y.Xml with tagname "p"
} }
}).then(function (y) {
window.yXml = y
// bind xml type to a dom, and put it in body
window.sharedDom = y.share.xml.getDom()
document.body.appendChild(window.sharedDom)
}) })
window.yXml = y
// bind xml type to a dom, and put it in body
window.sharedDom = y.define('xml', Y.XmlElement).getDom()
document.body.appendChild(window.sharedDom)

View File

@@ -1,7 +1,9 @@
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

274
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.0-43", "version": "13.0.0-55",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -1039,7 +1039,7 @@
"requires": { "requires": {
"anymatch": "1.3.0", "anymatch": "1.3.0",
"async-each": "1.0.1", "async-each": "1.0.1",
"fsevents": "1.1.2", "fsevents": "1.1.3",
"glob-parent": "2.0.0", "glob-parent": "2.0.0",
"inherits": "2.0.3", "inherits": "2.0.3",
"is-binary-path": "1.0.1", "is-binary-path": "1.0.1",
@@ -1092,12 +1092,6 @@
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true "dev": true
}, },
"colors": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
"dev": true
},
"commander": { "commander": {
"version": "2.10.0", "version": "2.10.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.10.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-2.10.0.tgz",
@@ -1261,16 +1255,6 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true "dev": true
}, },
"cors": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.3.tgz",
"integrity": "sha1-TPeOHSMymnSWsvwiJbd8pbteuAI=",
"dev": true,
"requires": {
"object-assign": "4.1.1",
"vary": "1.1.1"
}
},
"currently-unhandled": { "currently-unhandled": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
@@ -1783,12 +1767,6 @@
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true "dev": true
}, },
"etag": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz",
"integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=",
"dev": true
},
"event-emitter": { "event-emitter": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
@@ -1799,21 +1777,6 @@
"es5-ext": "0.10.23" "es5-ext": "0.10.23"
} }
}, },
"event-stream": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
"integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
"dev": true,
"requires": {
"duplexer": "0.1.1",
"from": "0.1.7",
"map-stream": "0.1.0",
"pause-stream": "0.0.11",
"split": "0.3.3",
"stream-combiner": "0.0.4",
"through": "2.3.8"
}
},
"exit-hook": { "exit-hook": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
@@ -1871,11 +1834,6 @@
"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",
@@ -2009,12 +1967,6 @@
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=",
"dev": true "dev": true
}, },
"fresh": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz",
"integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=",
"dev": true
},
"from": { "from": {
"version": "0.1.7", "version": "0.1.7",
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
@@ -2040,14 +1992,14 @@
"dev": true "dev": true
}, },
"fsevents": { "fsevents": {
"version": "1.1.2", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz",
"integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==", "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==",
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
"nan": "2.7.0", "nan": "2.8.0",
"node-pre-gyp": "0.6.36" "node-pre-gyp": "0.6.39"
}, },
"dependencies": { "dependencies": {
"abbrev": { "abbrev": {
@@ -2205,7 +2157,6 @@
"version": "2.0.5", "version": "2.0.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"boom": "2.10.1" "boom": "2.10.1"
} }
@@ -2253,6 +2204,12 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"detect-libc": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
},
"ecc-jsbn": { "ecc-jsbn": {
"version": "0.1.1", "version": "0.1.1",
"bundled": true, "bundled": true,
@@ -2394,7 +2351,6 @@
"version": "3.1.3", "version": "3.1.3",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"boom": "2.10.1", "boom": "2.10.1",
"cryptiles": "2.0.5", "cryptiles": "2.0.5",
@@ -2566,11 +2522,13 @@
"optional": true "optional": true
}, },
"node-pre-gyp": { "node-pre-gyp": {
"version": "0.6.36", "version": "0.6.39",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
"detect-libc": "1.0.2",
"hawk": "3.1.3",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"nopt": "4.0.1", "nopt": "4.0.1",
"npmlog": "4.1.0", "npmlog": "4.1.0",
@@ -2778,7 +2736,6 @@
"version": "1.0.9", "version": "1.0.9",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"hoek": "2.16.3" "hoek": "2.16.3"
} }
@@ -3539,7 +3496,7 @@
"chokidar": "1.7.0", "chokidar": "1.7.0",
"colors": "1.1.2", "colors": "1.1.2",
"connect": "3.5.1", "connect": "3.5.1",
"cors": "2.8.3", "cors": "2.8.4",
"event-stream": "3.3.4", "event-stream": "3.3.4",
"faye-websocket": "0.11.1", "faye-websocket": "0.11.1",
"http-auth": "3.1.3", "http-auth": "3.1.3",
@@ -3547,15 +3504,127 @@
"object-assign": "4.1.1", "object-assign": "4.1.1",
"opn": "5.1.0", "opn": "5.1.0",
"proxy-middleware": "0.15.0", "proxy-middleware": "0.15.0",
"send": "0.15.3", "send": "0.16.1",
"serve-index": "1.9.0" "serve-index": "1.9.0"
}, },
"dependencies": { "dependencies": {
"colors": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
"dev": true
},
"cors": {
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz",
"integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=",
"dev": true,
"requires": {
"object-assign": "4.1.1",
"vary": "1.1.1"
}
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
"integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=",
"dev": true
},
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"dev": true
},
"event-stream": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
"integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
"dev": true,
"requires": {
"duplexer": "0.1.1",
"from": "0.1.7",
"map-stream": "0.1.0",
"pause-stream": "0.0.11",
"split": "0.3.3",
"stream-combiner": "0.0.4",
"through": "2.3.8"
}
},
"fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
"dev": true
},
"http-errors": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
"integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
"dev": true,
"requires": {
"depd": "1.1.1",
"inherits": "2.0.3",
"setprototypeof": "1.0.3",
"statuses": "1.3.1"
}
},
"mime": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==",
"dev": true
},
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true "dev": true
},
"opn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz",
"integrity": "sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==",
"dev": true,
"requires": {
"is-wsl": "1.1.0"
}
},
"proxy-middleware": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz",
"integrity": "sha1-o/3xvvtzD5UZZYcqwvYHTGFHelY=",
"dev": true
},
"send": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz",
"integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==",
"dev": true,
"requires": {
"debug": "2.6.9",
"depd": "1.1.1",
"destroy": "1.0.4",
"encodeurl": "1.0.1",
"escape-html": "1.0.3",
"etag": "1.8.1",
"fresh": "0.5.2",
"http-errors": "1.6.2",
"mime": "1.4.1",
"ms": "2.0.0",
"on-finished": "2.3.0",
"range-parser": "1.2.0",
"statuses": "1.3.1"
}
} }
} }
}, },
@@ -3711,12 +3780,6 @@
"regex-cache": "0.4.3" "regex-cache": "0.4.3"
} }
}, },
"mime": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz",
"integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=",
"dev": true
},
"mime-db": { "mime-db": {
"version": "1.27.0", "version": "1.27.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
@@ -3781,9 +3844,9 @@
"dev": true "dev": true
}, },
"nan": { "nan": {
"version": "2.7.0", "version": "2.8.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz",
"integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=", "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
@@ -3889,15 +3952,6 @@
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
"dev": true "dev": true
}, },
"opn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz",
"integrity": "sha512-iPNl7SyM8L30Rm1sjGdLLheyHVw5YXVfi3SKWJzBI7efxRwHojfRFjwE/OLM6qp9xJYMgab8WicTU1cPoY+Hpg==",
"dev": true,
"requires": {
"is-wsl": "1.1.0"
}
},
"optionator": { "optionator": {
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
@@ -4145,12 +4199,6 @@
"integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
"dev": true "dev": true
}, },
"proxy-middleware": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/proxy-middleware/-/proxy-middleware-0.15.0.tgz",
"integrity": "sha1-o/3xvvtzD5UZZYcqwvYHTGFHelY=",
"dev": true
},
"randomatic": { "randomatic": {
"version": "1.1.7", "version": "1.1.7",
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
@@ -4626,38 +4674,6 @@
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true "dev": true
}, },
"send": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz",
"integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=",
"dev": true,
"requires": {
"debug": "2.6.7",
"depd": "1.1.0",
"destroy": "1.0.4",
"encodeurl": "1.0.1",
"escape-html": "1.0.3",
"etag": "1.8.0",
"fresh": "0.5.0",
"http-errors": "1.6.1",
"mime": "1.3.4",
"ms": "2.0.0",
"on-finished": "2.3.0",
"range-parser": "1.2.0",
"statuses": "1.3.1"
},
"dependencies": {
"debug": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz",
"integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=",
"dev": true,
"requires": {
"ms": "2.0.0"
}
}
}
},
"serve-index": { "serve-index": {
"version": "1.9.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.0.tgz", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.0.tgz",
@@ -4879,16 +4895,6 @@
"strip-ansi": "3.0.1" "strip-ansi": "3.0.1"
} }
}, },
"string.fromcodepoint": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz",
"integrity": "sha1-jZeDM8C8klOPUPOD5IiPPlYZ1lM="
},
"string.prototype.codepointat": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.0.tgz",
"integrity": "sha1-aybpvTr8qnvjtCabUm3huCAArHg="
},
"string_decoder": { "string_decoder": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
@@ -5109,20 +5115,6 @@
"integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=",
"dev": true "dev": true
}, },
"utf-8": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/utf-8/-/utf-8-1.0.0.tgz",
"integrity": "sha1-QpwJ+xrDLOuvVllh7aSMs/RSIZc=",
"requires": {
"string.fromcodepoint": "0.2.1",
"string.prototype.codepointat": "0.2.0"
}
},
"utf8": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz",
"integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY="
},
"util-deprecate": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.0-43", "version": "13.0.0-55",
"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,9 +65,6 @@
"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/y-xml.tests.js', input: 'test/index.js',
name: 'y-tests', name: 'y-tests',
sourcemap: true, sourcemap: true,
output: { output: {

View File

@@ -1,4 +1,3 @@
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'
@@ -91,7 +90,8 @@ 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++]
} }
return utf8.getStringFromBytes(bytes) let encodedString = String.fromCodePoint(...bytes)
return decodeURIComponent(escape(encodedString))
} }
/** /**
* Look ahead and read varString without incrementing position * Look ahead and read varString without incrementing position

View File

@@ -1,4 +1,3 @@
import utf8 from 'utf-8'
import { RootFakeUserID } from '../Util/RootID.js' import { RootFakeUserID } from '../Util/RootID.js'
const bits7 = 0b1111111 const bits7 = 0b1111111
@@ -62,7 +61,8 @@ export default class BinaryEncoder {
} }
writeVarString (str) { writeVarString (str) {
let bytes = utf8.setBytesFromString(str) let encodedString = unescape(encodeURIComponent(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++) {

14
src/Binding/Binding.js Normal file
View File

@@ -0,0 +1,14 @@
import { createMutualExclude } from '../Util/mutualExclude.js'
export default class Binding {
constructor (type, target) {
this.type = type
this.target = target
this._mutualExclude = createMutualExclude()
}
destroy () {
this.type = null
this.target = null
}
}

View File

View File

@@ -0,0 +1,45 @@
import Binding from './Binding.js'
import simpleDiff from '../Util/simpleDiff.js'
import { getRelativePosition, fromRelativePosition } from '../Util/relativePosition.js'
function typeObserver () {
this._mutualExclude(() => {
const textarea = this.target
const textType = this.type
const relativeStart = getRelativePosition(textType, textarea.selectionStart)
const relativeEnd = getRelativePosition(textType, textarea.selectionEnd)
textarea.value = textType.toString()
const start = fromRelativePosition(textType._y, relativeStart)
const end = fromRelativePosition(textType._y, relativeEnd)
textarea.setSelectionRange(start, end)
})
}
function domObserver () {
this._mutualExclude(() => {
let diff = simpleDiff(this.type.toString(), this.target.value)
this.type.delete(diff.pos, diff.remove)
this.type.insert(diff.pos, diff.insert)
})
}
export default class TextareaBinding extends Binding {
constructor (textType, domTextarea) {
// Binding handles textType as this.type and domTextarea as this.target
super(textType, domTextarea)
// set initial value
domTextarea.value = textType.toString()
// Observers are handled by this class
this._typeObserver = typeObserver.bind(this)
this._domObserver = domObserver.bind(this)
textType.observe(this._typeObserver)
domTextarea.addEventListener('input', this._domObserver)
}
destroy () {
// Remove everything that is handled by this class
this.type.unobserve(this._typeObserver)
this.target.unobserve(this._domObserver)
super.destroy()
}
}

View File

@@ -132,6 +132,7 @@ export default class AbstractConnector {
f() f()
} }
this.whenSyncedListeners = [] this.whenSyncedListeners = []
this.y._setContentReady()
this.y.emit('synced') this.y.emit('synced')
} }
} }
@@ -268,7 +269,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(decoder, encoder, y, senderConn, sender) integrateRemoteStructs(y, decoder)
} else { } else {
throw new Error('Unable to receive message') throw new Error('Unable to receive message')
} }

View File

@@ -0,0 +1,18 @@
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) {
y.transact(function () {
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 (decoder, encoder, y) { export function integrateRemoteStructs (y, decoder) {
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 default function writeStructs (encoder, decoder, y, ss) { export function writeStructs (y, encoder, 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(encoder, decoder, y, ss) writeStructs(y, encoder, 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(decoder, encoder, y) integrateRemoteStructs(y, decoder)
readDeleteSet(y, decoder) readDeleteSet(y, decoder)
y.connector._setSyncedWith(sender) y.connector._setSyncedWith(sender)
} }

View File

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

View File

@@ -51,20 +51,57 @@ export default class Item {
this._parent = null this._parent = null
this._parentSub = null this._parentSub = null
this._deleted = false this._deleted = false
this._redone = null
} }
/** /**
* Copy the effect of struct * Create a operation with the same effect (without position effect)
*/ */
_copy () { _copy () {
let struct = new this.constructor() return new this.constructor()
struct._origin = this._left }
struct._left = this._left /**
struct._right = this * Redo the effect of this operation.
struct._right_origin = this */
struct._parent = this._parent _redo (y) {
if (this._redone !== null) {
return this._redone
}
let struct = this._copy()
let left = this._left
let right = this
let parent = this._parent
// make sure that parent is redone
if (parent._deleted === true && parent._redone === null) {
parent._redo(y)
}
if (parent._redone !== null) {
parent = parent._redone
// find next cloned items
while (left !== null) {
if (left._redone !== null && left._redone._parent === parent) {
left = left._redone
break
}
left = left._left
}
while (right !== null) {
if (right._redone !== null && right._redone._parent === parent) {
right = right._redone
}
right = right._right
}
}
struct._origin = left
struct._left = left
struct._right = right
struct._right_origin = right
struct._parent = parent
struct._parentSub = this._parentSub struct._parentSub = this._parentSub
struct._integrate(y)
this._redone = struct
return struct return struct
} }
get _lastId () { get _lastId () {
return new ID(this._id.user, this._id.clock + this._length - 1) return new ID(this._id.user, this._id.clock + this._length - 1)
} }
@@ -87,16 +124,22 @@ export default class Item {
return this._right return this._right
} }
_delete (y, createDelete = true) { _delete (y, createDelete = true) {
this._deleted = true if (!this._deleted) {
y.ds.markDeleted(this._id, this._length) this._deleted = true
if (createDelete) { y.ds.markDeleted(this._id, this._length)
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) if (createDelete) {
// broadcast and persists Delete
del._integrate(y, true)
} else if (y.persistence !== null) {
// only persist Delete
y.persistence.saveStruct(y, del)
}
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.
@@ -111,9 +154,11 @@ export default class Item {
* - Check if this is struct deleted * - Check if this is struct deleted
*/ */
_integrate (y) { _integrate (y) {
y._transaction.newTypes.add(this)
const parent = this._parent const parent = this._parent
const selfID = this._id const selfID = this._id
const userState = selfID === null ? 0 : y.ss.getState(selfID.user) const user = selfID === null ? y.userID : selfID.user
const userState = y.ss.getState(user)
if (selfID === null) { if (selfID === null) {
this._id = y.ss.getNextID(this._length) this._id = y.ss.getNextID(this._length)
} else if (selfID.user === RootFakeUserID) { } else if (selfID.user === RootFakeUserID) {
@@ -216,11 +261,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._forwardAppliedStructs || this._id.user === y.userID) { if (y.connector !== null && (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.saveOperations(this) y.persistence.saveStruct(y, this)
} }
} }
} }

View File

@@ -79,39 +79,6 @@ export default class Type extends Item {
type = type._parent type = type._parent
} }
} }
_copy (undeleteChildren) {
let copy = super._copy()
let map = new Map()
copy._map = map
for (let [key, value] of this._map) {
if (undeleteChildren.has(value) || !value.deleted) {
let _item = value._copy(undeleteChildren)
_item._parent = copy
map.set(key, value._copy(undeleteChildren))
}
}
let prevUndeleted = null
copy._start = null
let item = this._start
while (item !== null) {
if (undeleteChildren.has(item) || !item.deleted) {
let _item = item._copy(undeleteChildren)
_item._left = prevUndeleted
_item._origin = prevUndeleted
_item._right = null
_item._right_origin = null
_item._parent = copy
if (prevUndeleted === null) {
copy._start = _item
} else {
prevUndeleted._right = _item
}
prevUndeleted = _item
}
item = item._right
}
return copy
}
_transact (f) { _transact (f) {
const y = this._y const y = this._y
if (y !== null) { if (y !== null) {
@@ -133,7 +100,6 @@ export default class Type extends Item {
this._deepEventHandler.removeEventListener(f) this._deepEventHandler.removeEventListener(f)
} }
_integrate (y) { _integrate (y) {
y._transaction.newTypes.add(this)
super._integrate(y) super._integrate(y)
this._y = y this._y = y
// when integrating children we must make sure to // when integrating children we must make sure to

View File

@@ -9,17 +9,21 @@ class YArrayEvent extends YEvent {
super(yarray) super(yarray)
this.remote = remote this.remote = remote
this._transaction = transaction this._transaction = transaction
this._addedElements = null
} }
get addedElements () { get addedElements () {
const target = this.target if (this._addedElements === null) {
const transaction = this._transaction const target = this.target
const addedElements = new Set() const transaction = this._transaction
transaction.newTypes.forEach(function (type) { const addedElements = new Set()
if (type._parent === target && !transaction.deletedStructs.has(type)) { transaction.newTypes.forEach(function (type) {
addedElements.add(type) if (type._parent === target && !transaction.deletedStructs.has(type)) {
} addedElements.add(type)
}) }
return addedElements })
this._addedElements = addedElements
}
return this._addedElements
} }
get removedElements () { get removedElements () {
const target = this.target const target = this.target

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 instanceof ItemJSON && old._content[0] === value) { if (old.constructor === ItemJSON && !old._deleted && 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,6 +24,9 @@ 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,4 +1,3 @@
// 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'
@@ -9,6 +8,9 @@ export default class YXmlElement extends YXmlFragment {
super() super()
this.nodeName = null this.nodeName = null
this._scrollElement = null this._scrollElement = null
if (typeof arg2 === 'function') {
this._domFilter = arg2
}
if (typeof arg1 === 'string') { if (typeof arg1 === 'string') {
this.nodeName = arg1.toUpperCase() this.nodeName = arg1.toUpperCase()
} else if (arg1 != null && arg1.nodeType != null && arg1.nodeType === arg1.ELEMENT_NODE) { } else if (arg1 != null && arg1.nodeType != null && arg1.nodeType === arg1.ELEMENT_NODE) {
@@ -17,12 +19,9 @@ export default class YXmlElement extends YXmlFragment {
} else { } else {
this.nodeName = 'UNDEFINED' this.nodeName = 'UNDEFINED'
} }
if (typeof arg2 === 'function') {
this._domFilter = arg2
}
} }
_copy (undeleteChildren) { _copy () {
let struct = super._copy(undeleteChildren) let struct = super._copy()
struct.nodeName = this.nodeName struct.nodeName = this.nodeName
return struct return struct
} }
@@ -37,9 +36,10 @@ export default class YXmlElement extends YXmlFragment {
let attributes = new Map() let attributes = new Map()
for (let i = 0; i < dom.attributes.length; i++) { for (let i = 0; i < dom.attributes.length; i++) {
let attr = dom.attributes[i] let attr = dom.attributes[i]
attributes.set(attr.name, attr.value) // get attribute via getAttribute for custom element support (some write something different in attr.value)
attributes.set(attr.name, dom.getAttribute(attr.name))
} }
attributes = this._domFilter(dom, attributes) attributes = this._domFilter(dom.nodeName, attributes)
attributes.forEach((value, name) => { attributes.forEach((value, name) => {
this.setAttribute(name, value) this.setAttribute(name, value)
}) })
@@ -48,6 +48,11 @@ 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 'fast-diff' import diff from '../../Util/simpleDiff.js'
function domToYXml (parent, doms, _document) { function domToYXml (parent, doms, _document) {
const types = [] const types = []
@@ -101,9 +101,11 @@ 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
} }
} }
@@ -150,18 +152,20 @@ export default class YXmlFragment extends YArray {
attributes.set(key, attrs[key]) attributes.set(key, attrs[key])
} }
} }
let result = this._domFilter(this.nodeName, new Map(attributes)) this._y.transact(() => {
if (result === null) { let result = this._domFilter(this.nodeName, new Map(attributes))
this._delete(this._y) if (result === null) {
} else { this._delete(this._y)
attributes.forEach((value, key) => { } else {
if (!result.has(key)) { attributes.forEach((value, key) => {
this.removeAttribute(key) if (!result.has(key)) {
} this.removeAttribute(key)
}
})
}
this.forEach(xml => {
xml.setDomFilter(f)
}) })
}
this.forEach(xml => {
xml.setDomFilter(f)
}) })
} }
_callObserver (transaction, parentSubs, remote) { _callObserver (transaction, parentSubs, remote) {
@@ -183,6 +187,9 @@ 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)
@@ -216,9 +223,7 @@ export default class YXmlFragment extends YArray {
_document = _document || document _document = _document || document
this._dom = dom this._dom = dom
dom._yxml = this dom._yxml = this
// TODO: refine this.. if (this._parent === null) {
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)
@@ -275,9 +280,10 @@ 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._y.on('beforeTransaction', () => { this._beforeTransactionHandler = () => {
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(() => {
@@ -291,19 +297,9 @@ export default class YXmlFragment extends YArray {
} }
switch (mutation.type) { switch (mutation.type) {
case 'characterData': case 'characterData':
var diffs = diff(yxml.toString(), dom.nodeValue) var change = diff(yxml.toString(), dom.nodeValue)
var pos = 0 yxml.delete(change.pos, change.remove)
for (var i = 0; i < diffs.length; i++) { yxml.insert(change.pos, change.insert)
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) {
@@ -330,6 +326,9 @@ 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

@@ -14,6 +14,11 @@ export default class YXmlHook extends YMap {
getHook(hookName).fillType(dom, this) getHook(hookName).fillType(dom, this)
} }
} }
_copy () {
const struct = super._copy()
struct.hookName = this.hookName
return struct
}
getDom (_document) { getDom (_document) {
_document = _document || document _document = _document || document
if (this._dom === null) { if (this._dom === null) {

View File

@@ -11,6 +11,10 @@ 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) {
return this.user < id.user || (this.user === id.user && this.clock < id.clock) if (id.constructor === ID) {
return this.user < id.user || (this.user === id.user && this.clock < id.clock)
} else {
return false
}
} }
} }

View File

@@ -1,6 +1,7 @@
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)
@@ -21,16 +22,32 @@ 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.remove(f) listener.on.delete(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,6 +12,10 @@ 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) {
return this.user < id.user || (this.user === id.user && (this.name < id.name || (this.name === id.name && this.type < id.type))) 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)))
} else {
return true
}
} }
} }

View File

@@ -4,11 +4,12 @@ class ReverseOperation {
constructor (y, transaction) { constructor (y, transaction) {
this.created = new Date() this.created = new Date()
const beforeState = transaction.beforeState const beforeState = transaction.beforeState
this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1)
if (beforeState.has(y.userID)) { if (beforeState.has(y.userID)) {
this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1)
this.fromState = new ID(y.userID, beforeState.get(y.userID)) this.fromState = new ID(y.userID, beforeState.get(y.userID))
} else { } else {
this.fromState = this.toState this.toState = null
this.fromState = null
} }
this.deletedStructs = transaction.deletedStructs this.deletedStructs = transaction.deletedStructs
} }
@@ -30,28 +31,32 @@ function applyReverseOperation (y, scope, reverseBuffer) {
while (!performedUndo && reverseBuffer.length > 0) { while (!performedUndo && reverseBuffer.length > 0) {
let undoOp = reverseBuffer.pop() let undoOp = reverseBuffer.pop()
// make sure that it is possible to iterate {from}-{to} // make sure that it is possible to iterate {from}-{to}
y.os.getItemCleanStart(undoOp.fromState) if (undoOp.fromState !== null) {
y.os.getItemCleanEnd(undoOp.toState) y.os.getItemCleanStart(undoOp.fromState)
y.os.iterate(undoOp.fromState, undoOp.toState, op => { y.os.getItemCleanEnd(undoOp.toState)
if (!op._deleted && isStructInScope(y, op, scope)) { y.os.iterate(undoOp.fromState, undoOp.toState, op => {
performedUndo = true while (op._deleted && op._redone !== null) {
op._delete(y) op = op._redone
} }
}) if (op._deleted === false && isStructInScope(y, op, scope)) {
performedUndo = true
op._delete(y)
}
})
}
for (let op of undoOp.deletedStructs) { for (let op of undoOp.deletedStructs) {
if ( if (
isStructInScope(y, op, scope) && isStructInScope(y, op, scope) &&
op._parent !== y && op._parent !== y &&
!op._parent._deleted &&
( (
op._parent._id.user !== y.userID || op._id.user !== y.userID ||
op._parent._id.clock < undoOp.fromState.clock || undoOp.fromState === null ||
op._parent._id.clock > undoOp.fromState.clock op._id.clock < undoOp.fromState.clock ||
op._id.clock > undoOp.toState.clock
) )
) { ) {
performedUndo = true performedUndo = true
op = op._copy(undoOp.deletedStructs) op._redo(y)
op._integrate(y)
} }
} }
} }
@@ -68,6 +73,7 @@ export default class UndoManager {
this._scope = scope this._scope = scope
this._undoing = false this._undoing = false
this._redoing = false this._redoing = false
this._lastTransactionWasUndo = false
const y = scope._y const y = scope._y
this.y = y this.y = y
y.on('afterTransaction', (y, transaction, remote) => { y.on('afterTransaction', (y, transaction, remote) => {
@@ -75,17 +81,29 @@ export default class UndoManager {
let reverseOperation = new ReverseOperation(y, transaction) let reverseOperation = new ReverseOperation(y, transaction)
if (!this._undoing) { if (!this._undoing) {
let lastUndoOp = this._undoBuffer.length > 0 ? this._undoBuffer[this._undoBuffer.length - 1] : null let lastUndoOp = this._undoBuffer.length > 0 ? this._undoBuffer[this._undoBuffer.length - 1] : null
if (lastUndoOp !== null && reverseOperation.created - lastUndoOp.created <= options.captureTimeout) { if (
this._redoing === false &&
this._lastTransactionWasUndo === false &&
lastUndoOp !== null &&
reverseOperation.created - lastUndoOp.created <= options.captureTimeout
) {
lastUndoOp.created = reverseOperation.created lastUndoOp.created = reverseOperation.created
lastUndoOp.toState = reverseOperation.toState if (reverseOperation.toState !== null) {
lastUndoOp.toState = reverseOperation.toState
if (lastUndoOp.fromState === null) {
lastUndoOp.fromState = reverseOperation.fromState
}
}
reverseOperation.deletedStructs.forEach(lastUndoOp.deletedStructs.add, lastUndoOp.deletedStructs) reverseOperation.deletedStructs.forEach(lastUndoOp.deletedStructs.add, lastUndoOp.deletedStructs)
} else { } else {
this._lastTransactionWasUndo = false
this._undoBuffer.push(reverseOperation) this._undoBuffer.push(reverseOperation)
} }
if (!this._redoing) { if (!this._redoing) {
this._redoBuffer = [] this._redoBuffer = []
} }
} else { } else {
this._lastTransactionWasUndo = true
this._redoBuffer.push(reverseOperation) this._redoBuffer.push(reverseOperation)
} }
} }

15
src/Util/mutualExclude.js Normal file
View File

@@ -0,0 +1,15 @@
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,37 +2,31 @@ 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) {
if (offset === 0) { let t = type._start
return ['startof', type._id.user, type._id.clock || null, type._id.name || null, type._id.type || null] while (t !== null) {
} else { if (t._deleted === false) {
let t = type._start if (t._length > offset) {
while (t !== null) { return [t._id.user, t._id.clock + offset]
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
} }
t = t._right offset -= t._length
} }
return null t = t._right
} }
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] === 'startof') { if (rpos[0] === 'endof') {
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: y.os.get(id), type,
offset: 0 offset: type.length
} }
} else { } else {
let offset = 0 let offset = 0
@@ -42,7 +36,7 @@ export function fromRelativePosition (y, rpos) {
return null return null
} }
if (!struct._deleted) { if (!struct._deleted) {
offset = rpos[1] - struct._id.clock + 1 offset = rpos[1] - struct._id.clock
} }
struct = struct._left struct = struct._left
while (struct !== null) { while (struct !== null) {

19
src/Util/simpleDiff.js Normal file
View File

@@ -0,0 +1,19 @@
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,6 +5,7 @@ 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'
@@ -21,26 +22,58 @@ import { addStruct as addType } from './Util/structReferences.js'
import debug from 'debug' import debug from 'debug'
import Transaction from './Transaction.js' import Transaction from './Transaction.js'
import TextareaBinding from './Binding/TextareaBinding.js'
import { toBinary, fromBinary } from './MessageHandler/binaryEncode.js'
export default class Y extends NamedEventHandler { export default class Y extends NamedEventHandler {
constructor (opts) { constructor (room, opts, persistence) {
super() super()
this.room = room
if (opts != null) {
opts.connector.room = room
}
this._contentReady = false
this._opts = opts this._opts = opts
this.userID = opts._userID != null ? opts._userID : generateUserID() this.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) {
@@ -90,9 +123,6 @@ 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)
@@ -123,11 +153,18 @@ export default class Y extends NamedEventHandler {
} }
} }
destroy () { destroy () {
super.destroy()
this.share = null this.share = null
if (this.connector.destroy != null) { if (this.connector != null) {
this.connector.destroy() if (this.connector.destroy != null) {
} else { this.connector.destroy()
this.connector.disconnect() } else {
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
@@ -155,7 +192,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.Persisence = Persistence Y.AbstractPersistence = Persistence
Y.Array = YArray Y.Array = YArray
Y.Map = YMap Y.Map = YMap
Y.Text = YText Y.Text = YText
@@ -164,12 +201,17 @@ Y.XmlFragment = YXmlFragment
Y.XmlText = YXmlText Y.XmlText = YXmlText
Y.XmlHook = YXmlHook Y.XmlHook = YXmlHook
Y.TextareaBinding = TextareaBinding
Y.utils = { Y.utils = {
BinaryDecoder, BinaryDecoder,
UndoManager, UndoManager,
getRelativePosition, getRelativePosition,
fromRelativePosition, fromRelativePosition,
addType addType,
integrateRemoteStructs,
toBinary,
fromBinary
} }
Y.debug = debug Y.debug = debug

29
test/diff.tests.js Normal file
View File

@@ -0,0 +1,29 @@
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,6 +54,9 @@ 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="./encode-decode.js"></script> <script type="module" src="./index.js"></script>
</body> </body>
</html> </html>

6
test/index.js Normal file
View File

@@ -0,0 +1,6 @@
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({ let y = new Y(connOpts.room, {
_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

1682
y.node.js

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

19485
y.test.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long