Compare commits
51 Commits
v13.0.0-40
...
v13.0.0-55
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29760cfe53 | ||
|
|
941a22b257 | ||
|
|
4aa41b98a9 | ||
|
|
079de07eff | ||
|
|
54453e87fa | ||
|
|
1b0e3659c3 | ||
|
|
641f426339 | ||
|
|
fcbca65d8f | ||
|
|
5f8ae0dd43 | ||
|
|
de14fe0f3e | ||
|
|
5e4b071693 | ||
|
|
937de2c59f | ||
|
|
f1f1bff901 | ||
|
|
4855b2d590 | ||
|
|
908ce31e2f | ||
|
|
e4d4c23f0b | ||
|
|
fc500a8247 | ||
|
|
4b84541d76 | ||
|
|
a3ab42c157 | ||
|
|
bbd3317d62 | ||
|
|
5d3922cb64 | ||
|
|
a81a2cd553 | ||
|
|
c0d24bdba4 | ||
|
|
40e913e9c5 | ||
|
|
94f6a0fd9c | ||
|
|
41a88dbc43 | ||
|
|
1d4f283955 | ||
|
|
fc3a4c376c | ||
|
|
acb0affa33 | ||
|
|
0b510b64a3 | ||
|
|
c8f0cf5556 | ||
|
|
11a4271fd1 | ||
|
|
c7670915c7 | ||
|
|
eb2d596538 | ||
|
|
48e17ea1a7 | ||
|
|
1a22fdd45e | ||
|
|
07cf0b3436 | ||
|
|
5a68b9f4ad | ||
|
|
445dd3e0da | ||
|
|
0ba97d78f8 | ||
|
|
fc5be5c7cc | ||
|
|
f2debc150c | ||
|
|
08f37a86e3 | ||
|
|
f5d17e6236 | ||
|
|
8f3bd7170a | ||
|
|
5586334549 | ||
|
|
24c1e4dcc8 | ||
|
|
d61bbecf4e | ||
|
|
85492ad2e0 | ||
|
|
02253f9a8d | ||
|
|
8105bef1af |
13
README.md
13
README.md
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -13,11 +13,8 @@
|
|||||||
<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="../../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-map/dist/y-map.js"></script>
|
<script src="../bower_components/d3/d3.min.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="./index.js"></script>
|
<script src="./index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,84 +1,74 @@
|
|||||||
/* globals Y, d3 */
|
/* globals Y, d3 */
|
||||||
'strict mode'
|
|
||||||
|
|
||||||
Y({
|
let y = new Y('drawing-example', {
|
||||||
db: {
|
|
||||||
name: 'memory'
|
|
||||||
},
|
|
||||||
connector: {
|
connector: {
|
||||||
name: 'websockets-client',
|
name: 'websockets-client',
|
||||||
room: 'drawing-example',
|
url: 'http://127.0.0.1:1234'
|
||||||
url: 'localhost:1234'
|
|
||||||
},
|
|
||||||
sourceDir: '/bower_components',
|
|
||||||
share: {
|
|
||||||
drawing: 'Array'
|
|
||||||
}
|
|
||||||
}).then(function (y) {
|
|
||||||
window.yDrawing = y
|
|
||||||
var drawing = y.share.drawing
|
|
||||||
var renderPath = d3.svg.line()
|
|
||||||
.x(function (d) { return d[0] })
|
|
||||||
.y(function (d) { return d[1] })
|
|
||||||
.interpolate('basis')
|
|
||||||
|
|
||||||
var svg = d3.select('#drawingCanvas')
|
|
||||||
.call(d3.behavior.drag()
|
|
||||||
.on('dragstart', dragstart)
|
|
||||||
.on('drag', drag)
|
|
||||||
.on('dragend', dragend))
|
|
||||||
|
|
||||||
// create line from a shared array object and update the line when the array changes
|
|
||||||
function drawLine (yarray) {
|
|
||||||
var line = svg.append('path').datum(yarray.toArray())
|
|
||||||
line.attr('d', renderPath)
|
|
||||||
yarray.observe(function (event) {
|
|
||||||
// we only implement insert events that are appended to the end of the array
|
|
||||||
event.values.forEach(function (value) {
|
|
||||||
line.datum().push(value)
|
|
||||||
})
|
|
||||||
line.attr('d', renderPath)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// call drawLine every time an array is appended
|
|
||||||
y.share.drawing.observe(function (event) {
|
|
||||||
if (event.type === 'insert') {
|
|
||||||
event.values.forEach(drawLine)
|
|
||||||
} else {
|
|
||||||
// just remove all elements (thats what we do anyway)
|
|
||||||
svg.selectAll('path').remove()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// draw all existing content
|
|
||||||
for (var i = 0; i < drawing.length; i++) {
|
|
||||||
drawLine(drawing.get(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear canvas on request
|
|
||||||
document.querySelector('#clearDrawingCanvas').onclick = function () {
|
|
||||||
drawing.delete(0, drawing.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sharedLine = null
|
|
||||||
function dragstart () {
|
|
||||||
drawing.insert(drawing.length, [Y.Array])
|
|
||||||
sharedLine = drawing.get(drawing.length - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// After one dragged event is recognized, we ignore them for 33ms.
|
|
||||||
var ignoreDrag = null
|
|
||||||
function drag () {
|
|
||||||
if (sharedLine != null && ignoreDrag == null) {
|
|
||||||
ignoreDrag = window.setTimeout(function () {
|
|
||||||
ignoreDrag = null
|
|
||||||
}, 33)
|
|
||||||
sharedLine.push([d3.mouse(this)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function dragend () {
|
|
||||||
sharedLine = null
|
|
||||||
window.clearTimeout(ignoreDrag)
|
|
||||||
ignoreDrag = null
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
window.yDrawing = y
|
||||||
|
var drawing = y.define('drawing', Y.Array)
|
||||||
|
var renderPath = d3.svg.line()
|
||||||
|
.x(function (d) { return d[0] })
|
||||||
|
.y(function (d) { return d[1] })
|
||||||
|
.interpolate('basic')
|
||||||
|
|
||||||
|
var svg = d3.select('#drawingCanvas')
|
||||||
|
.call(d3.behavior.drag()
|
||||||
|
.on('dragstart', dragstart)
|
||||||
|
.on('drag', drag)
|
||||||
|
.on('dragend', dragend))
|
||||||
|
|
||||||
|
// create line from a shared array object and update the line when the array changes
|
||||||
|
function drawLine (yarray) {
|
||||||
|
var line = svg.append('path').datum(yarray.toArray())
|
||||||
|
line.attr('d', renderPath)
|
||||||
|
yarray.observe(function (event) {
|
||||||
|
line.remove()
|
||||||
|
line = svg.append('path').datum(yarray.toArray())
|
||||||
|
line.attr('d', renderPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// call drawLine every time an array is appended
|
||||||
|
drawing.observe(function (event) {
|
||||||
|
event.removedElements.forEach(function () {
|
||||||
|
// if one is deleted, all will be deleted!!
|
||||||
|
svg.selectAll('path').remove()
|
||||||
|
})
|
||||||
|
event.addedElements.forEach(function (path) {
|
||||||
|
drawLine(path)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// draw all existing content
|
||||||
|
for (var i = 0; i < drawing.length; i++) {
|
||||||
|
drawLine(drawing.get(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear canvas on request
|
||||||
|
document.querySelector('#clearDrawingCanvas').onclick = function () {
|
||||||
|
drawing.delete(0, drawing.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sharedLine = null
|
||||||
|
function dragstart () {
|
||||||
|
drawing.insert(drawing.length, [Y.Array])
|
||||||
|
sharedLine = drawing.get(drawing.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// After one dragged event is recognized, we ignore them for 33ms.
|
||||||
|
var ignoreDrag = null
|
||||||
|
function drag () {
|
||||||
|
if (sharedLine != null && ignoreDrag == null) {
|
||||||
|
ignoreDrag = window.setTimeout(function () {
|
||||||
|
ignoreDrag = null
|
||||||
|
}, 10)
|
||||||
|
sharedLine.push([d3.mouse(this)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragend () {
|
||||||
|
sharedLine = null
|
||||||
|
window.clearTimeout(ignoreDrag)
|
||||||
|
ignoreDrag = null
|
||||||
|
}
|
||||||
|
|||||||
36
examples/html-editor-drawing-hook/index.html
Normal file
36
examples/html-editor-drawing-hook/index.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
</head>
|
||||||
|
<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="./index.js"></script>
|
||||||
|
<style>
|
||||||
|
magic-drawing .drawingCanvas path {
|
||||||
|
fill: none;
|
||||||
|
stroke: blue;
|
||||||
|
stroke-width: 2px;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-linecap: round;
|
||||||
|
}
|
||||||
|
magic-drawing .drawingCanvas {
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
cursor: default;
|
||||||
|
padding:1px;
|
||||||
|
border:1px solid #021a40;
|
||||||
|
}
|
||||||
|
magic-drawing .clearDrawingButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
magic-drawing {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body contenteditable="true">
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
132
examples/html-editor-drawing-hook/index.js
Normal file
132
examples/html-editor-drawing-hook/index.js
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/* global Y, d3 */
|
||||||
|
|
||||||
|
window.onload = function () {
|
||||||
|
window.yXmlType.bindToDom(document.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addMagicDrawing = function addMagicDrawing () {
|
||||||
|
let mt = document.createElement('magic-drawing')
|
||||||
|
mt.dataset.yjsHook = 'magic-drawing'
|
||||||
|
document.body.append(mt)
|
||||||
|
}
|
||||||
|
|
||||||
|
var renderPath = d3.svg.line()
|
||||||
|
.x(function (d) { return d[0] })
|
||||||
|
.y(function (d) { return d[1] })
|
||||||
|
.interpolate('basic')
|
||||||
|
|
||||||
|
function initDrawingBindings (type, dom) {
|
||||||
|
dom.contentEditable = 'false'
|
||||||
|
dom.dataset.yjsHook = 'magic-drawing'
|
||||||
|
var drawing = type.get('drawing')
|
||||||
|
if (drawing === undefined) {
|
||||||
|
drawing = type.set('drawing', new Y.Array())
|
||||||
|
}
|
||||||
|
var canvas = dom.querySelector('.drawingCanvas')
|
||||||
|
if (canvas == null) {
|
||||||
|
canvas = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
|
||||||
|
canvas.setAttribute('class', 'drawingCanvas')
|
||||||
|
canvas.setAttribute('viewbox', '0 0 100 100')
|
||||||
|
dom.insertBefore(canvas, null)
|
||||||
|
}
|
||||||
|
var clearDrawingButton = dom.querySelector('.clearDrawingButton')
|
||||||
|
if (clearDrawingButton == null) {
|
||||||
|
clearDrawingButton = document.createElement('button')
|
||||||
|
clearDrawingButton.setAttribute('type', 'button')
|
||||||
|
clearDrawingButton.setAttribute('class', 'clearDrawingButton')
|
||||||
|
clearDrawingButton.innerText = 'Clear Drawing'
|
||||||
|
dom.insertBefore(clearDrawingButton, null)
|
||||||
|
}
|
||||||
|
var svg = d3.select(canvas)
|
||||||
|
.call(d3.behavior.drag()
|
||||||
|
.on('dragstart', dragstart)
|
||||||
|
.on('drag', drag)
|
||||||
|
.on('dragend', dragend))
|
||||||
|
// create line from a shared array object and update the line when the array changes
|
||||||
|
function drawLine (yarray, svg) {
|
||||||
|
var line = svg.append('path').datum(yarray.toArray())
|
||||||
|
line.attr('d', renderPath)
|
||||||
|
yarray.observe(function (event) {
|
||||||
|
line.remove()
|
||||||
|
line = svg.append('path').datum(yarray.toArray())
|
||||||
|
line.attr('d', renderPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// call drawLine every time an array is appended
|
||||||
|
drawing.observe(function (event) {
|
||||||
|
event.removedElements.forEach(function () {
|
||||||
|
// if one is deleted, all will be deleted!!
|
||||||
|
svg.selectAll('path').remove()
|
||||||
|
})
|
||||||
|
event.addedElements.forEach(function (path) {
|
||||||
|
drawLine(path, svg)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// draw all existing content
|
||||||
|
for (var i = 0; i < drawing.length; i++) {
|
||||||
|
drawLine(drawing.get(i), svg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear canvas on request
|
||||||
|
clearDrawingButton.onclick = function () {
|
||||||
|
drawing.delete(0, drawing.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sharedLine = null
|
||||||
|
function dragstart () {
|
||||||
|
drawing.insert(drawing.length, [Y.Array])
|
||||||
|
sharedLine = drawing.get(drawing.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// After one dragged event is recognized, we ignore them for 33ms.
|
||||||
|
var ignoreDrag = null
|
||||||
|
function drag () {
|
||||||
|
if (sharedLine != null && ignoreDrag == null) {
|
||||||
|
ignoreDrag = window.setTimeout(function () {
|
||||||
|
ignoreDrag = null
|
||||||
|
}, 10)
|
||||||
|
sharedLine.push([d3.mouse(this)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragend () {
|
||||||
|
sharedLine = null
|
||||||
|
window.clearTimeout(ignoreDrag)
|
||||||
|
ignoreDrag = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Y.XmlHook.addHook('magic-drawing', {
|
||||||
|
fillType: function (dom, type) {
|
||||||
|
initDrawingBindings(type, dom)
|
||||||
|
},
|
||||||
|
createDom: function (type) {
|
||||||
|
const dom = document.createElement('magic-drawing')
|
||||||
|
initDrawingBindings(type, dom)
|
||||||
|
return dom
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let y = new Y('html-editor-drawing-hook-example', {
|
||||||
|
connector: {
|
||||||
|
name: 'websockets-client',
|
||||||
|
url: 'http://127.0.0.1:1234'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
window.yXml = y
|
||||||
|
window.yXmlType = y.define('xml', Y.XmlFragment)
|
||||||
|
window.undoManager = new Y.utils.UndoManager(window.yXmlType, {
|
||||||
|
captureTimeout: 500
|
||||||
|
})
|
||||||
|
|
||||||
|
document.onkeydown = function interceptUndoRedo (e) {
|
||||||
|
if (e.keyCode === 90 && e.metaKey) {
|
||||||
|
if (!e.shiftKey) {
|
||||||
|
window.undoManager.undo()
|
||||||
|
} else {
|
||||||
|
window.undoManager.redo()
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 + ')'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
663
examples/package-lock.json
generated
663
examples/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
274
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.0.0-40",
|
"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",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.0.0-40",
|
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: {
|
||||||
@@ -11,12 +11,12 @@ export default {
|
|||||||
format: 'umd'
|
format: 'umd'
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
multiEntry(),
|
||||||
nodeResolve({
|
nodeResolve({
|
||||||
main: true,
|
main: true,
|
||||||
module: true,
|
module: true,
|
||||||
browser: true
|
browser: true
|
||||||
}),
|
}),
|
||||||
commonjs(),
|
commonjs()
|
||||||
multiEntry()
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
14
src/Binding/Binding.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/Binding/DomBinding.js
Normal file
0
src/Binding/DomBinding.js
Normal file
45
src/Binding/TextareaBinding.js
Normal file
45
src/Binding/TextareaBinding.js
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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')
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/MessageHandler/binaryEncode.js
Normal file
18
src/MessageHandler/binaryEncode.js
Normal 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
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 () {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -5,15 +5,42 @@ import { logID } from '../MessageHandler/messageToString.js'
|
|||||||
import YEvent from '../Util/YEvent.js'
|
import YEvent from '../Util/YEvent.js'
|
||||||
|
|
||||||
class YArrayEvent extends YEvent {
|
class YArrayEvent extends YEvent {
|
||||||
constructor (yarray, remote) {
|
constructor (yarray, remote, transaction) {
|
||||||
super(yarray)
|
super(yarray)
|
||||||
this.remote = remote
|
this.remote = remote
|
||||||
|
this._transaction = transaction
|
||||||
|
this._addedElements = null
|
||||||
|
}
|
||||||
|
get addedElements () {
|
||||||
|
if (this._addedElements === null) {
|
||||||
|
const target = this.target
|
||||||
|
const transaction = this._transaction
|
||||||
|
const addedElements = new Set()
|
||||||
|
transaction.newTypes.forEach(function (type) {
|
||||||
|
if (type._parent === target && !transaction.deletedStructs.has(type)) {
|
||||||
|
addedElements.add(type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this._addedElements = addedElements
|
||||||
|
}
|
||||||
|
return this._addedElements
|
||||||
|
}
|
||||||
|
get removedElements () {
|
||||||
|
const target = this.target
|
||||||
|
const transaction = this._transaction
|
||||||
|
const removedElements = new Set()
|
||||||
|
transaction.deletedStructs.forEach(function (struct) {
|
||||||
|
if (struct._parent === target && !transaction.newTypes.has(struct)) {
|
||||||
|
removedElements.add(struct)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return removedElements
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class YArray extends Type {
|
export default class YArray extends Type {
|
||||||
_callObserver (transaction, parentSubs, remote) {
|
_callObserver (transaction, parentSubs, remote) {
|
||||||
this._callEventHandler(transaction, new YArrayEvent(this, remote))
|
this._callEventHandler(transaction, new YArrayEvent(this, remote, transaction))
|
||||||
}
|
}
|
||||||
get (pos) {
|
get (pos) {
|
||||||
let n = this._start
|
let n = this._start
|
||||||
@@ -184,8 +211,12 @@ export default class YArray extends Type {
|
|||||||
prevJsonIns._content.push(c)
|
prevJsonIns._content.push(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (prevJsonIns !== null && y !== null) {
|
if (prevJsonIns !== null) {
|
||||||
prevJsonIns._integrate(y)
|
if (y !== null) {
|
||||||
|
prevJsonIns._integrate(y)
|
||||||
|
} else if (prevJsonIns._left === null) {
|
||||||
|
this._start = prevJsonIns
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -214,6 +245,17 @@ export default class YArray extends Type {
|
|||||||
}
|
}
|
||||||
this.insertAfter(left, content)
|
this.insertAfter(left, content)
|
||||||
}
|
}
|
||||||
|
push (content) {
|
||||||
|
let n = this._start
|
||||||
|
let lastUndeleted = null
|
||||||
|
while (n !== null) {
|
||||||
|
if (!n._deleted) {
|
||||||
|
lastUndeleted = n
|
||||||
|
}
|
||||||
|
n = n._right
|
||||||
|
}
|
||||||
|
this.insertAfter(lastUndeleted, content)
|
||||||
|
}
|
||||||
_logString () {
|
_logString () {
|
||||||
const left = this._left !== null ? this._left._lastId : null
|
const left = this._left !== null ? this._left._lastId : null
|
||||||
const origin = this._origin !== null ? this._origin._lastId : null
|
const origin = this._origin !== null ? this._origin._lastId : null
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 = []
|
||||||
@@ -55,7 +55,7 @@ class YXmlTreeWalker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
if (!n._deleted && n.constructor === YXmlFragment._YXmlElement && n._start !== null) {
|
if (!n._deleted && (n.constructor === YXmlFragment._YXmlElement || n.constructor === YXmlFragment) && n._start !== null) {
|
||||||
// walk down in the tree
|
// walk down in the tree
|
||||||
n = n._start
|
n = n._start
|
||||||
} else {
|
} else {
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,8 +145,27 @@ export default class YXmlFragment extends YArray {
|
|||||||
}
|
}
|
||||||
setDomFilter (f) {
|
setDomFilter (f) {
|
||||||
this._domFilter = f
|
this._domFilter = f
|
||||||
this.forEach(xml => {
|
let attributes = new Map()
|
||||||
xml.setDomFilter(f)
|
if (this.getAttributes !== undefined) {
|
||||||
|
let attrs = this.getAttributes()
|
||||||
|
for (let key in attrs) {
|
||||||
|
attributes.set(key, attrs[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._y.transact(() => {
|
||||||
|
let result = this._domFilter(this.nodeName, new Map(attributes))
|
||||||
|
if (result === null) {
|
||||||
|
this._delete(this._y)
|
||||||
|
} else {
|
||||||
|
attributes.forEach((value, key) => {
|
||||||
|
if (!result.has(key)) {
|
||||||
|
this.removeAttribute(key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.forEach(xml => {
|
||||||
|
xml.setDomFilter(f)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_callObserver (transaction, parentSubs, remote) {
|
_callObserver (transaction, parentSubs, remote) {
|
||||||
@@ -166,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)
|
||||||
@@ -199,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)
|
||||||
@@ -258,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(() => {
|
||||||
@@ -274,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) {
|
||||||
@@ -313,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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -24,6 +29,11 @@ export default class YXmlHook extends YMap {
|
|||||||
}
|
}
|
||||||
return this._dom
|
return this._dom
|
||||||
}
|
}
|
||||||
|
_unbindFromDom () {
|
||||||
|
this._dom._yxml = null
|
||||||
|
this._yxml = null
|
||||||
|
// TODO: cleanup hook?
|
||||||
|
}
|
||||||
_fromBinary (y, decoder) {
|
_fromBinary (y, decoder) {
|
||||||
const missing = super._fromBinary(y, decoder)
|
const missing = super._fromBinary(y, decoder)
|
||||||
this.hookName = decoder.readVarString()
|
this.hookName = decoder.readVarString()
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export function reflectChangesOnDom (events, _document) {
|
|||||||
// let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement)
|
// let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement)
|
||||||
if (yxml.constructor === YXmlText) {
|
if (yxml.constructor === YXmlText) {
|
||||||
yxml._dom.nodeValue = yxml.toString()
|
yxml._dom.nodeValue = yxml.toString()
|
||||||
} else {
|
} else if (event.attributesChanged !== undefined) {
|
||||||
// update attributes
|
// update attributes
|
||||||
event.attributesChanged.forEach(attributeName => {
|
event.attributesChanged.forEach(attributeName => {
|
||||||
const value = yxml.getAttribute(attributeName)
|
const value = yxml.getAttribute(attributeName)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
15
src/Util/mutualExclude.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
19
src/Util/simpleDiff.js
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/Y.js
80
src/Y.js
@@ -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
29
test/diff.tests.js
Normal 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')
|
||||||
|
})
|
||||||
@@ -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) {
|
||||||
@@ -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
6
test/index.js
Normal 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'
|
||||||
@@ -280,7 +280,6 @@ test('deep element insert', async function xml16 (t) {
|
|||||||
deepElement.append(boldElement)
|
deepElement.append(boldElement)
|
||||||
deepElement.append(attrElement)
|
deepElement.append(attrElement)
|
||||||
dom0.append(deepElement)
|
dom0.append(deepElement)
|
||||||
console.log(dom0.outerHTML)
|
|
||||||
let str0 = dom0.outerHTML
|
let str0 = dom0.outerHTML
|
||||||
await flushAll(t, users)
|
await flushAll(t, users)
|
||||||
let str1 = dom1.outerHTML
|
let str1 = dom1.outerHTML
|
||||||
@@ -340,7 +339,6 @@ test('Filtering remote changes', async function xmlFilteringRemote (t) {
|
|||||||
// check dom
|
// check dom
|
||||||
paragraph.getDom().setAttribute('malicious', 'true')
|
paragraph.getDom().setAttribute('malicious', 'true')
|
||||||
span.getDom().setAttribute('malicious', 'true')
|
span.getDom().setAttribute('malicious', 'true')
|
||||||
console.log(xml0.toString())
|
|
||||||
// check incoming attributes
|
// check incoming attributes
|
||||||
xml1.get(0).get(0).setAttribute('malicious', 'true')
|
xml1.get(0).get(0).setAttribute('malicious', 'true')
|
||||||
xml1.insert(0, [new Y.XmlElement('hideMe')])
|
xml1.insert(0, [new Y.XmlElement('hideMe')])
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
|
|||||||
1
y.node.js.map
Normal file
1
y.node.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user