Compare commits
42 Commits
v13.0.0-42
...
v13.0.0-54
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55f0abcdfc | ||
|
|
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 |
13
README.md
13
README.md
@@ -64,6 +64,19 @@ missing modules.
|
||||
<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 install --save yjs % add all y-* modules you want to use
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
<body>
|
||||
|
||||
<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="./index.js"></script>
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
/* global Y, ace */
|
||||
|
||||
Y({
|
||||
db: {
|
||||
name: 'memory'
|
||||
},
|
||||
let y = new Y('ace-example', {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
room: 'ace-example'
|
||||
},
|
||||
sourceDir: '/bower_components',
|
||||
share: {
|
||||
ace: 'Text' // y.share.textarea is of type Y.Text
|
||||
url: 'http://127.0.0.1:1234'
|
||||
}
|
||||
}).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">
|
||||
</form>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/* global Y */
|
||||
|
||||
// initialize a shared object. This function call returns a promise!
|
||||
var y = new Y({
|
||||
let y = new Y('chat-example', {
|
||||
connector: {
|
||||
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))
|
||||
chatcontainer.insertBefore(p, chatcontainer.children[position] || null)
|
||||
}
|
||||
|
||||
// This function makes sure that only 7 messages exist in the chat history.
|
||||
// The rest is deleted
|
||||
function cleanupChat () {
|
||||
@@ -30,23 +30,17 @@ function cleanupChat () {
|
||||
chatprotocol.delete(0, chatprotocol.length - 7)
|
||||
}
|
||||
}
|
||||
cleanupChat()
|
||||
|
||||
// Insert the initial content
|
||||
chatprotocol.toArray().forEach(appendMessage)
|
||||
cleanupChat()
|
||||
|
||||
// whenever content changes, make sure to reflect the changes in the DOM
|
||||
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
|
||||
cleanupChat()
|
||||
chatcontainer.innerHTML = ''
|
||||
chatprotocol.toArray().forEach(appendMessage)
|
||||
})
|
||||
document.querySelector('#chatform').onsubmit = function (event) {
|
||||
// the form is submitted
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
<body>
|
||||
<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/mode/javascript/javascript.js"></script>
|
||||
<link rel="stylesheet" href="../bower_components/codemirror/lib/codemirror.css">
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
/* global Y, CodeMirror */
|
||||
|
||||
// initialize a shared object. This function call returns a promise!
|
||||
Y({
|
||||
db: {
|
||||
name: 'memory'
|
||||
},
|
||||
let y = new Y('codemirror-example', {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
room: 'codemirror-example'
|
||||
},
|
||||
sourceDir: '/bower_components',
|
||||
share: {
|
||||
codemirror: 'Text' // y.share.codemirror is of type Y.Text
|
||||
url: 'http://127.0.0.1:1234'
|
||||
}
|
||||
}).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)
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
</style>
|
||||
<button type="button" id="clearDrawingCanvas">Clear Drawing</button>
|
||||
<svg id="drawingCanvas" viewbox="0 0 100 100" width="100%"></svg>
|
||||
<script src="../yjs-dist.js"></script>
|
||||
<script src="../../y.js"></script>
|
||||
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
|
||||
<script src="../bower_components/d3/d3.min.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
/* globals Y, d3 */
|
||||
|
||||
let y = new Y({
|
||||
let y = new Y('drawing-example', {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
url: 'http://127.0.0.1:1234',
|
||||
room: 'drawing-example'
|
||||
// maxBufferLength: 100
|
||||
url: 'http://127.0.0.1:1234'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
</head>
|
||||
<script src="../yjs-dist.js"></script>
|
||||
<script src="../../y.js"></script>
|
||||
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
|
||||
<script src="../bower_components/d3/d3.min.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
<style>
|
||||
|
||||
@@ -107,15 +107,13 @@ Y.XmlHook.addHook('magic-drawing', {
|
||||
}
|
||||
})
|
||||
|
||||
// initialize a shared object. This function call returns a promise!
|
||||
let y = new Y({
|
||||
let y = new Y('html-editor-drawing-hook-example', {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
url: 'http://127.0.0.1:1234',
|
||||
room: 'html-editor-example6'
|
||||
// maxBufferLength: 100
|
||||
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, {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
</head>
|
||||
<!-- jquery is not required for y-xml. It is just here for convenience, and to test batch operations. -->
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
|
||||
<script src="../yjs-dist.js"></script>
|
||||
<script src="./canvasjs.min.js"></script>
|
||||
<script src="../../y.js"></script>
|
||||
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
|
||||
<script src="./index.js"></script>
|
||||
</head>
|
||||
<body contenteditable="true">
|
||||
|
||||
@@ -1,85 +1,24 @@
|
||||
/* global Y, HTMLElement, customElements, CanvasJS */
|
||||
/* global Y */
|
||||
|
||||
window.onload = function () {
|
||||
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 {
|
||||
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({
|
||||
let y = new Y('htmleditor', {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
url: 'http://127.0.0.1:1234',
|
||||
room: 'html-editor-example6'
|
||||
// maxBufferLength: 100
|
||||
url: 'http://127.0.0.1:1234'
|
||||
}
|
||||
})
|
||||
window.yXml = y
|
||||
|
||||
window.y = 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.keyCode === 90 && (e.metaKey || e.ctrlKey)) {
|
||||
if (!e.shiftKey) {
|
||||
window.undoManager.undo()
|
||||
} else {
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
width: 100%;
|
||||
}
|
||||
</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>
|
||||
</html>
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
/* global Y, CodeMirror */
|
||||
|
||||
// initialize a shared object. This function call returns a promise!
|
||||
Y({
|
||||
db: {
|
||||
name: 'memory'
|
||||
},
|
||||
const persistence = new Y.IndexedDB()
|
||||
const connector = {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
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'), {
|
||||
mode: 'javascript',
|
||||
lineNumbers: true
|
||||
})
|
||||
y.share.codemirror.bindCodeMirror(editor)
|
||||
const y = new Y('codemirror-example', connector, persistence)
|
||||
window.yCodeMirror = y
|
||||
|
||||
var editor = CodeMirror(document.querySelector('#codeMirrorContainer'), {
|
||||
mode: 'javascript',
|
||||
lineNumbers: true
|
||||
})
|
||||
|
||||
y.define('codemirror', Y.Text).bindCodeMirror(editor)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
.one {
|
||||
grid-column: 1 ;
|
||||
}
|
||||
.two {
|
||||
.two {
|
||||
grid-column: 2;
|
||||
}
|
||||
.three {
|
||||
@@ -49,10 +49,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<script src="../../y.js"></script>
|
||||
<script src="../../../y-array/y-array.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='../../../y-websockets-client/y-websockets-client.js'></script>
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,64 +1,38 @@
|
||||
/* global Y */
|
||||
|
||||
Y({
|
||||
db: {
|
||||
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'))
|
||||
function bindYjsInstance (y, suffix) {
|
||||
y.define('textarea', Y.Text).bind(document.getElementById('textarea' + suffix))
|
||||
y.connector.socket.on('connection', function () {
|
||||
document.getElementById('container2').removeAttribute('disconnected')
|
||||
document.getElementById('container' + suffix).removeAttribute('disconnected')
|
||||
})
|
||||
y.connector.socket.on('disconnect', function () {
|
||||
document.getElementById('container2').setAttribute('disconnected', true)
|
||||
document.getElementById('container' + suffix).setAttribute('disconnected', true)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Y({
|
||||
db: {
|
||||
name: 'memory'
|
||||
},
|
||||
let y1 = new Y('infinite-example', {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
room: 'Textarea-example',
|
||||
url: 'https://yjs-v13-third.herokuapp.com/'
|
||||
},
|
||||
share: {
|
||||
textarea: 'Text'
|
||||
url: 'http://127.0.0.1:1234'
|
||||
}
|
||||
}).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>
|
||||
</svg>
|
||||
<script src="../../y.js"></script>
|
||||
<script src="../../../y-map/dist/y-map.js"></script>
|
||||
<script src="../../../y-memory/y-memory.js"></script>
|
||||
<script src="../../../y-websockets-client/y-websockets-client.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>
|
||||
</body>
|
||||
|
||||
@@ -1,74 +1,67 @@
|
||||
/* @flow */
|
||||
/* global Y, d3 */
|
||||
|
||||
// initialize a shared object. This function call returns a promise!
|
||||
Y({
|
||||
db: {
|
||||
name: 'memory'
|
||||
},
|
||||
let y = new Y('jigsaw-example', {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
room: 'Puzzle-example',
|
||||
url: 'http://localhost:1234'
|
||||
},
|
||||
share: {
|
||||
piece1: 'Map',
|
||||
piece2: 'Map',
|
||||
piece3: 'Map',
|
||||
piece4: 'Map'
|
||||
url: 'http://127.0.0.1:1234'
|
||||
}
|
||||
}).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]
|
||||
var pieces = d3.select(document.querySelector('#puzzle-example')).selectAll('path').data(data)
|
||||
let jigsaw = y.define('jigsaw', Y.Map)
|
||||
window.yJigsaw = y
|
||||
|
||||
pieces
|
||||
.classed('draggable', true)
|
||||
.attr('transform', function (piece) {
|
||||
var translation = piece.get('translation') || {x: 0, y: 0}
|
||||
return 'translate(' + translation.x + ',' + translation.y + ')'
|
||||
}).call(drag)
|
||||
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
|
||||
jigsaw.set(piece, {x: x, y: y})
|
||||
})
|
||||
|
||||
data.forEach(function (piece) {
|
||||
piece.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('translation') || {x: 0, y: 0}
|
||||
return 'translate(' + translation.x + ',' + translation.y + ')'
|
||||
})
|
||||
})
|
||||
var data = ['piece1', 'piece2', 'piece3', 'piece4']
|
||||
var pieces = d3.select(document.querySelector('#puzzle-example')).selectAll('path').data(data)
|
||||
|
||||
pieces
|
||||
.classed('draggable', true)
|
||||
.attr('transform', function (piece) {
|
||||
var translation = piece.get('translation') || {x: 0, y: 0}
|
||||
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%;
|
||||
}
|
||||
</style>
|
||||
<script src="../bower_components/yjs/y.js"></script>
|
||||
<script src="../bower_components/y-websockets-client/y-websockets-client.js"></script>
|
||||
<script src="../../y.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="./index.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -2,29 +2,21 @@
|
||||
|
||||
require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' } })
|
||||
|
||||
require(['vs/editor/editor.main'], function () {
|
||||
// Initialize a shared object. This function call returns a promise!
|
||||
Y({
|
||||
db: {
|
||||
name: 'memory'
|
||||
},
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
room: 'monaco-example'
|
||||
},
|
||||
sourceDir: '/bower_components',
|
||||
share: {
|
||||
monaco: 'Text' // y.share.monaco is of type Y.Text
|
||||
}
|
||||
}).then(function (y) {
|
||||
window.yMonaco = y
|
||||
|
||||
// Create Monaco editor
|
||||
var editor = monaco.editor.create(document.getElementById('monacoContainer'), {
|
||||
language: 'javascript'
|
||||
})
|
||||
|
||||
// Bind to y.share.monaco
|
||||
y.share.monaco.bindMonaco(editor)
|
||||
})
|
||||
let y = new Y('monaco-example', {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
url: 'http://127.0.0.1:1234'
|
||||
}
|
||||
})
|
||||
|
||||
require(['vs/editor/editor.main'], function () {
|
||||
window.yMonaco = y
|
||||
|
||||
// Create Monaco editor
|
||||
var editor = monaco.editor.create(document.getElementById('monacoContainer'), {
|
||||
language: 'javascript'
|
||||
})
|
||||
|
||||
// Bind to y.share.monaco
|
||||
y.define('monaco', Y.Text).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",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"monaco-editor": "^0.8.3"
|
||||
"monaco-editor": "^0.8.3",
|
||||
"rollup": "^0.52.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"standard": "^10.0.2"
|
||||
},
|
||||
"standard": {
|
||||
"ignore": ["bower_components"]
|
||||
"ignore": [
|
||||
"bower_components"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,7 @@
|
||||
<script src="../bower_components/quill/dist/quill.js"></script>
|
||||
-->
|
||||
<script src="../../y.js"></script>
|
||||
<script src="../../../y-array/y-array.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='../../../y-websockets-client/y-websockets-client.js'></script>
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,7 +4,7 @@ import commonjs from 'rollup-plugin-commonjs'
|
||||
var pkg = require('./package.json')
|
||||
|
||||
export default {
|
||||
input: 'yjs-dist.esm',
|
||||
input: 'yjs-dist.mjs',
|
||||
name: 'Y',
|
||||
output: {
|
||||
file: 'yjs-dist.js',
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<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-array/y-array.js"></script>
|
||||
<script src="../../../y-text/y-text.js"></script>
|
||||
<script src="../../../y-websockets-client/y-websockets-client.js"></script>
|
||||
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
/* global Y */
|
||||
|
||||
// initialize a shared object. This function call returns a promise!
|
||||
Y({
|
||||
db: {
|
||||
name: 'memory'
|
||||
},
|
||||
let y = new Y('textarea-example', {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
room: 'Textarea-example2',
|
||||
// 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'))
|
||||
url: 'http://127.0.0.1:1234'
|
||||
}
|
||||
})
|
||||
|
||||
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>
|
||||
<!-- jquery is not required for y-xml. It is just here for convenience, and to test batch operations. -->
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
|
||||
<script src="../yjs-dist.js"></script>
|
||||
<script src="../../y.js"></script>
|
||||
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
|
||||
<script src="./index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
/* global Y */
|
||||
|
||||
// initialize a shared object. This function call returns a promise!
|
||||
Y({
|
||||
db: {
|
||||
name: 'memory'
|
||||
},
|
||||
let y = new Y('xml-example', {
|
||||
connector: {
|
||||
name: 'websockets-client',
|
||||
// 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"
|
||||
url: 'http://127.0.0.1:1234'
|
||||
}
|
||||
}).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 yWebsocketsClient from '../../y-websockets-client/src/y-websockets-client.js'
|
||||
import extendYIndexedDBPersistence from '../../y-indexeddb/src/y-indexeddb.js'
|
||||
|
||||
Y.extend(yWebsocketsClient)
|
||||
extendYIndexedDBPersistence(Y)
|
||||
|
||||
export default Y
|
||||
274
package-lock.json
generated
274
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yjs",
|
||||
"version": "13.0.0-42",
|
||||
"version": "13.0.0-54",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -1039,7 +1039,7 @@
|
||||
"requires": {
|
||||
"anymatch": "1.3.0",
|
||||
"async-each": "1.0.1",
|
||||
"fsevents": "1.1.2",
|
||||
"fsevents": "1.1.3",
|
||||
"glob-parent": "2.0.0",
|
||||
"inherits": "2.0.3",
|
||||
"is-binary-path": "1.0.1",
|
||||
@@ -1092,12 +1092,6 @@
|
||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
|
||||
"dev": true
|
||||
},
|
||||
"colors": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
|
||||
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
|
||||
"dev": true
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.10.0.tgz",
|
||||
@@ -1261,16 +1255,6 @@
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
||||
"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": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
||||
@@ -1783,12 +1767,6 @@
|
||||
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
|
||||
"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": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
|
||||
@@ -1799,21 +1777,6 @@
|
||||
"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": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
|
||||
@@ -1871,11 +1834,6 @@
|
||||
"integrity": "sha1-ysNCuPqJAm7+c6Jg/p9rgE9J5H8=",
|
||||
"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": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
@@ -2009,12 +1967,6 @@
|
||||
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=",
|
||||
"dev": true
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz",
|
||||
"integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=",
|
||||
"dev": true
|
||||
},
|
||||
"from": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
|
||||
@@ -2040,14 +1992,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz",
|
||||
"integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==",
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz",
|
||||
"integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"nan": "2.7.0",
|
||||
"node-pre-gyp": "0.6.36"
|
||||
"nan": "2.8.0",
|
||||
"node-pre-gyp": "0.6.39"
|
||||
},
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
@@ -2205,7 +2157,6 @@
|
||||
"version": "2.0.5",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"boom": "2.10.1"
|
||||
}
|
||||
@@ -2253,6 +2204,12 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"ecc-jsbn": {
|
||||
"version": "0.1.1",
|
||||
"bundled": true,
|
||||
@@ -2394,7 +2351,6 @@
|
||||
"version": "3.1.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"boom": "2.10.1",
|
||||
"cryptiles": "2.0.5",
|
||||
@@ -2566,11 +2522,13 @@
|
||||
"optional": true
|
||||
},
|
||||
"node-pre-gyp": {
|
||||
"version": "0.6.36",
|
||||
"version": "0.6.39",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"detect-libc": "1.0.2",
|
||||
"hawk": "3.1.3",
|
||||
"mkdirp": "0.5.1",
|
||||
"nopt": "4.0.1",
|
||||
"npmlog": "4.1.0",
|
||||
@@ -2778,7 +2736,6 @@
|
||||
"version": "1.0.9",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"hoek": "2.16.3"
|
||||
}
|
||||
@@ -3539,7 +3496,7 @@
|
||||
"chokidar": "1.7.0",
|
||||
"colors": "1.1.2",
|
||||
"connect": "3.5.1",
|
||||
"cors": "2.8.3",
|
||||
"cors": "2.8.4",
|
||||
"event-stream": "3.3.4",
|
||||
"faye-websocket": "0.11.1",
|
||||
"http-auth": "3.1.3",
|
||||
@@ -3547,15 +3504,127 @@
|
||||
"object-assign": "4.1.1",
|
||||
"opn": "5.1.0",
|
||||
"proxy-middleware": "0.15.0",
|
||||
"send": "0.15.3",
|
||||
"send": "0.16.1",
|
||||
"serve-index": "1.9.0"
|
||||
},
|
||||
"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": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz",
|
||||
"integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=",
|
||||
"dev": true
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz",
|
||||
@@ -3781,9 +3844,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
|
||||
"integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=",
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.8.0.tgz",
|
||||
"integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
@@ -3889,15 +3952,6 @@
|
||||
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
||||
"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": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
|
||||
@@ -4145,12 +4199,6 @@
|
||||
"integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
|
||||
"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": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
|
||||
@@ -4626,38 +4674,6 @@
|
||||
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
|
||||
"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": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.0.tgz",
|
||||
@@ -4879,16 +4895,6 @@
|
||||
"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": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
@@ -5109,20 +5115,6 @@
|
||||
"integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=",
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yjs",
|
||||
"version": "13.0.0-42",
|
||||
"version": "13.0.0-54",
|
||||
"description": "A framework for real-time p2p shared editing on any data",
|
||||
"main": "./y.node.js",
|
||||
"browser": "./y.js",
|
||||
@@ -65,9 +65,6 @@
|
||||
"tag-dist-files": "^0.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "^2.6.8",
|
||||
"fast-diff": "^1.1.2",
|
||||
"utf-8": "^1.0.0",
|
||||
"utf8": "^2.1.2"
|
||||
"debug": "^2.6.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
|
||||
import multiEntry from 'rollup-plugin-multi-entry'
|
||||
|
||||
export default {
|
||||
input: 'test/*.tests.js',
|
||||
input: 'test/index.js',
|
||||
name: 'y-tests',
|
||||
sourcemap: true,
|
||||
output: {
|
||||
@@ -11,12 +11,12 @@ export default {
|
||||
format: 'umd'
|
||||
},
|
||||
plugins: [
|
||||
multiEntry(),
|
||||
nodeResolve({
|
||||
main: true,
|
||||
module: true,
|
||||
browser: true
|
||||
}),
|
||||
commonjs(),
|
||||
multiEntry()
|
||||
commonjs()
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import utf8 from 'utf-8'
|
||||
import ID from '../Util/ID.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++) {
|
||||
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
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import utf8 from 'utf-8'
|
||||
import { RootFakeUserID } from '../Util/RootID.js'
|
||||
|
||||
const bits7 = 0b1111111
|
||||
@@ -62,7 +61,8 @@ export default class BinaryEncoder {
|
||||
}
|
||||
|
||||
writeVarString (str) {
|
||||
let bytes = utf8.setBytesFromString(str)
|
||||
let encodedString = unescape(encodeURIComponent(str))
|
||||
let bytes = encodedString.split('').map(c => c.codePointAt())
|
||||
let len = bytes.length
|
||||
this.writeVarUint(len)
|
||||
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()
|
||||
}
|
||||
this.whenSyncedListeners = []
|
||||
this.y._setContentReady()
|
||||
this.y.emit('synced')
|
||||
}
|
||||
}
|
||||
@@ -268,7 +269,7 @@ export default class AbstractConnector {
|
||||
if (messageType === 'sync step 2' && senderConn.auth === 'write') {
|
||||
readSyncStep2(decoder, encoder, y, senderConn, sender)
|
||||
} else if (messageType === 'update' && (skipAuth || senderConn.auth === 'write')) {
|
||||
integrateRemoteStructs(decoder, encoder, y, senderConn, sender)
|
||||
integrateRemoteStructs(y, decoder)
|
||||
} else {
|
||||
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()
|
||||
for (let i = 0; i < len; i++) {
|
||||
let reference = decoder.readVarUint()
|
||||
|
||||
@@ -30,7 +30,7 @@ export function sendSyncStep1 (connector, syncUser) {
|
||||
connector.send(syncUser, encoder.createBuffer())
|
||||
}
|
||||
|
||||
export default function writeStructs (encoder, decoder, y, ss) {
|
||||
export function writeStructs (y, encoder, ss) {
|
||||
const lenPos = encoder.pos
|
||||
encoder.writeUint32(0)
|
||||
let len = 0
|
||||
@@ -60,7 +60,7 @@ export function readSyncStep1 (decoder, encoder, y, senderConn, sender) {
|
||||
encoder.writeVarString('sync step 2')
|
||||
encoder.writeVarString(y.connector.authInfo || '')
|
||||
const ss = readStateSet(decoder)
|
||||
writeStructs(encoder, decoder, y, ss)
|
||||
writeStructs(y, encoder, ss)
|
||||
writeDeleteSet(y, encoder)
|
||||
y.connector.send(senderConn.uid, encoder.createBuffer())
|
||||
senderConn.receivedSyncStep2 = true
|
||||
|
||||
@@ -22,7 +22,7 @@ export function stringifySyncStep2 (y, decoder, strBuilder) {
|
||||
}
|
||||
|
||||
export function readSyncStep2 (decoder, encoder, y, senderConn, sender) {
|
||||
integrateRemoteStructs(decoder, encoder, y)
|
||||
integrateRemoteStructs(y, decoder)
|
||||
readDeleteSet(y, decoder)
|
||||
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) {
|
||||
class AbstractPersistence {
|
||||
constructor (y, opts) {
|
||||
this.y = y
|
||||
this.opts = opts
|
||||
this.saveOperationsBuffer = []
|
||||
this.log = Y.debug('y:persistence')
|
||||
}
|
||||
function getFreshCnf () {
|
||||
let buffer = new BinaryEncoder()
|
||||
buffer.writeUint32(0)
|
||||
return {
|
||||
len: 0,
|
||||
buffer
|
||||
}
|
||||
}
|
||||
|
||||
saveToMessageQueue (binary) {
|
||||
this.log('Room %s: Save message to message queue', this.y.options.connector.room)
|
||||
}
|
||||
export default class AbstractPersistence {
|
||||
constructor (opts) {
|
||||
this.opts = opts
|
||||
this.ys = new Map()
|
||||
}
|
||||
|
||||
saveOperations (ops) {
|
||||
ops = ops.map(function (op) {
|
||||
return Y.Struct[op.struct].encode(op)
|
||||
})
|
||||
/*
|
||||
const saveOperations = () => {
|
||||
if (this.saveOperationsBuffer.length > 0) {
|
||||
let encoder = new BinaryEncoder()
|
||||
encoder.writeVarString(this.opts.room)
|
||||
encoder.writeVarString('update')
|
||||
let ops = this.saveOperationsBuffer
|
||||
this.saveOperationsBuffer = []
|
||||
let length = ops.length
|
||||
encoder.writeUint32(length)
|
||||
for (var i = 0; i < length; i++) {
|
||||
let op = ops[i]
|
||||
Y.Struct[op.struct].binaryEncode(encoder, op)
|
||||
_init (y) {
|
||||
let cnf = this.ys.get(y)
|
||||
if (cnf === undefined) {
|
||||
cnf = getFreshCnf()
|
||||
cnf.mutualExclude = createMutualExclude()
|
||||
this.ys.set(y, cnf)
|
||||
return this.init(y).then(() => {
|
||||
y.on('afterTransaction', (y, transaction) => {
|
||||
let cnf = this.ys.get(y)
|
||||
if (cnf.len > 0) {
|
||||
cnf.buffer.setUint32(0, cnf.len)
|
||||
this.saveUpdate(y, cnf.buffer.createBuffer(), transaction)
|
||||
let _cnf = getFreshCnf()
|
||||
for (let key in _cnf) {
|
||||
cnf[key] = _cnf[key]
|
||||
}
|
||||
}
|
||||
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 {
|
||||
this.saveOperationsBuffer = this.saveOperationsBuffer.concat(ops)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* overwrite */
|
||||
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)
|
||||
*/
|
||||
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))
|
||||
if (item !== null) {
|
||||
if (!item._deleted) {
|
||||
@@ -70,12 +70,12 @@ export default class Delete {
|
||||
// from remote
|
||||
const id = this._targetID
|
||||
deleteItemRange(y, id.user, id.clock, this._length)
|
||||
} else {
|
||||
} else if (y.connector !== null) {
|
||||
// from local
|
||||
y.connector.broadcastStruct(this)
|
||||
}
|
||||
if (y.persistence !== null) {
|
||||
y.persistence.saveOperations(this)
|
||||
y.persistence.saveStruct(y, this)
|
||||
}
|
||||
}
|
||||
_logString () {
|
||||
|
||||
@@ -51,20 +51,57 @@ export default class Item {
|
||||
this._parent = null
|
||||
this._parentSub = null
|
||||
this._deleted = false
|
||||
this._redone = null
|
||||
}
|
||||
/**
|
||||
* Copy the effect of struct
|
||||
* Create a operation with the same effect (without position effect)
|
||||
*/
|
||||
_copy () {
|
||||
let struct = new this.constructor()
|
||||
struct._origin = this._left
|
||||
struct._left = this._left
|
||||
struct._right = this
|
||||
struct._right_origin = this
|
||||
struct._parent = this._parent
|
||||
return new this.constructor()
|
||||
}
|
||||
/**
|
||||
* Redo the effect of this operation.
|
||||
*/
|
||||
_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._integrate(y)
|
||||
this._redone = struct
|
||||
return struct
|
||||
}
|
||||
|
||||
get _lastId () {
|
||||
return new ID(this._id.user, this._id.clock + this._length - 1)
|
||||
}
|
||||
@@ -87,16 +124,22 @@ export default class Item {
|
||||
return this._right
|
||||
}
|
||||
_delete (y, createDelete = true) {
|
||||
this._deleted = true
|
||||
y.ds.markDeleted(this._id, this._length)
|
||||
if (createDelete) {
|
||||
if (!this._deleted) {
|
||||
this._deleted = true
|
||||
y.ds.markDeleted(this._id, this._length)
|
||||
let del = new Delete()
|
||||
del._targetID = this._id
|
||||
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.
|
||||
@@ -111,9 +154,11 @@ export default class Item {
|
||||
* - Check if this is struct deleted
|
||||
*/
|
||||
_integrate (y) {
|
||||
y._transaction.newTypes.add(this)
|
||||
const parent = this._parent
|
||||
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) {
|
||||
this._id = y.ss.getNextID(this._length)
|
||||
} else if (selfID.user === RootFakeUserID) {
|
||||
@@ -216,11 +261,11 @@ export default class Item {
|
||||
y.os.put(this)
|
||||
transactionTypeChanged(y, parent, parentSub)
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
_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) {
|
||||
const y = this._y
|
||||
if (y !== null) {
|
||||
@@ -133,7 +100,6 @@ export default class Type extends Item {
|
||||
this._deepEventHandler.removeEventListener(f)
|
||||
}
|
||||
_integrate (y) {
|
||||
y._transaction.newTypes.add(this)
|
||||
super._integrate(y)
|
||||
this._y = y
|
||||
// when integrating children we must make sure to
|
||||
|
||||
@@ -9,17 +9,21 @@ class YArrayEvent extends YEvent {
|
||||
super(yarray)
|
||||
this.remote = remote
|
||||
this._transaction = transaction
|
||||
this._addedElements = null
|
||||
}
|
||||
get addedElements () {
|
||||
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)
|
||||
}
|
||||
})
|
||||
return 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
|
||||
|
||||
@@ -56,7 +56,7 @@ export default class YMap extends Type {
|
||||
this._transact(y => {
|
||||
const old = this._map.get(key) || 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
|
||||
// break here
|
||||
return value
|
||||
|
||||
@@ -24,6 +24,9 @@ export default class YText extends YArray {
|
||||
return strBuilder.join('')
|
||||
}
|
||||
insert (pos, text) {
|
||||
if (text.length <= 0) {
|
||||
return
|
||||
}
|
||||
this._transact(y => {
|
||||
let left = null
|
||||
let right = this._start
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// import diff from 'fast-diff'
|
||||
import { defaultDomFilter } from './utils.js'
|
||||
|
||||
import YMap from '../YMap.js'
|
||||
@@ -21,8 +20,8 @@ export default class YXmlElement extends YXmlFragment {
|
||||
this._domFilter = arg2
|
||||
}
|
||||
}
|
||||
_copy (undeleteChildren) {
|
||||
let struct = super._copy(undeleteChildren)
|
||||
_copy () {
|
||||
let struct = super._copy()
|
||||
struct.nodeName = this.nodeName
|
||||
return struct
|
||||
}
|
||||
@@ -37,7 +36,8 @@ export default class YXmlElement extends YXmlFragment {
|
||||
let attributes = new Map()
|
||||
for (let i = 0; i < dom.attributes.length; 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.forEach((value, name) => {
|
||||
@@ -48,6 +48,11 @@ export default class YXmlElement extends YXmlFragment {
|
||||
return dom
|
||||
}
|
||||
}
|
||||
_bindToDom (dom, _document) {
|
||||
_document = _document || document
|
||||
this._dom = dom
|
||||
dom._yxml = this
|
||||
}
|
||||
_fromBinary (y, decoder) {
|
||||
const missing = super._fromBinary(y, decoder)
|
||||
this.nodeName = decoder.readVarString()
|
||||
|
||||
@@ -7,7 +7,7 @@ import YArray from '../YArray.js'
|
||||
import YXmlEvent from './YXmlEvent.js'
|
||||
import { YXmlText, YXmlHook } from './y-xml'
|
||||
import { logID } from '../../MessageHandler/messageToString.js'
|
||||
import diff from 'fast-diff'
|
||||
import diff from '../../Util/simpleDiff.js'
|
||||
|
||||
function domToYXml (parent, doms, _document) {
|
||||
const types = []
|
||||
@@ -101,9 +101,11 @@ export default class YXmlFragment extends YArray {
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
/*
|
||||
if (this._domObserver !== null) {
|
||||
this._domObserver.takeRecords()
|
||||
}
|
||||
*/
|
||||
token = true
|
||||
}
|
||||
}
|
||||
@@ -143,8 +145,27 @@ export default class YXmlFragment extends YArray {
|
||||
}
|
||||
setDomFilter (f) {
|
||||
this._domFilter = f
|
||||
this.forEach(xml => {
|
||||
xml.setDomFilter(f)
|
||||
let attributes = new Map()
|
||||
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) {
|
||||
@@ -166,6 +187,9 @@ export default class YXmlFragment extends YArray {
|
||||
this._dom._yxml = null
|
||||
this._dom = null
|
||||
}
|
||||
if (this._beforeTransactionHandler !== undefined) {
|
||||
this._y.off('beforeTransaction', this._beforeTransactionHandler)
|
||||
}
|
||||
}
|
||||
insertDomElementsAfter (prev, doms, _document) {
|
||||
const types = domToYXml(this, doms, _document)
|
||||
@@ -199,9 +223,7 @@ export default class YXmlFragment extends YArray {
|
||||
_document = _document || document
|
||||
this._dom = dom
|
||||
dom._yxml = this
|
||||
// TODO: refine this..
|
||||
if ((this.constructor !== YXmlFragment && this._parent !== this._y) || this._parent === null) {
|
||||
// TODO: only top level YXmlFragment can bind. Also allow YXmlElements..
|
||||
if (this._parent === null) {
|
||||
return
|
||||
}
|
||||
this._y.on('beforeTransaction', beforeTransactionSelectionFixer)
|
||||
@@ -258,9 +280,10 @@ export default class YXmlFragment extends YArray {
|
||||
})
|
||||
// Apply Dom changes on Y.Xml
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
this._y.on('beforeTransaction', () => {
|
||||
this._beforeTransactionHandler = () => {
|
||||
this._domObserverListener(this._domObserver.takeRecords())
|
||||
})
|
||||
}
|
||||
this._y.on('beforeTransaction', this._beforeTransactionHandler)
|
||||
this._domObserverListener = mutations => {
|
||||
this._mutualExclude(() => {
|
||||
this._y.transact(() => {
|
||||
@@ -274,19 +297,9 @@ export default class YXmlFragment extends YArray {
|
||||
}
|
||||
switch (mutation.type) {
|
||||
case 'characterData':
|
||||
var diffs = diff(yxml.toString(), dom.nodeValue)
|
||||
var pos = 0
|
||||
for (var i = 0; i < diffs.length; i++) {
|
||||
var d = diffs[i]
|
||||
if (d[0] === 0) { // EQUAL
|
||||
pos += d[1].length
|
||||
} else if (d[0] === -1) { // DELETE
|
||||
yxml.delete(pos, d[1].length)
|
||||
} else { // INSERT
|
||||
yxml.insert(pos, d[1])
|
||||
pos += d[1].length
|
||||
}
|
||||
}
|
||||
var change = diff(yxml.toString(), dom.nodeValue)
|
||||
yxml.delete(change.pos, change.remove)
|
||||
yxml.insert(change.pos, change.insert)
|
||||
break
|
||||
case 'attributes':
|
||||
if (yxml.constructor === YXmlFragment) {
|
||||
@@ -313,6 +326,9 @@ export default class YXmlFragment extends YArray {
|
||||
}
|
||||
})
|
||||
for (let dom of diffChildren) {
|
||||
if (dom.yOnChildrenChanged !== undefined) {
|
||||
dom.yOnChildrenChanged()
|
||||
}
|
||||
if (dom._yxml != null && dom._yxml !== false) {
|
||||
applyChangesFromDom(dom)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ export default class YXmlHook extends YMap {
|
||||
getHook(hookName).fillType(dom, this)
|
||||
}
|
||||
}
|
||||
_copy () {
|
||||
const struct = super._copy()
|
||||
struct.hookName = this.hookName
|
||||
return struct
|
||||
}
|
||||
getDom (_document) {
|
||||
_document = _document || document
|
||||
if (this._dom === null) {
|
||||
|
||||
@@ -11,6 +11,10 @@ export default class ID {
|
||||
return id !== null && id.user === this.user && id.clock === this.clock
|
||||
}
|
||||
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 {
|
||||
constructor () {
|
||||
this._eventListener = new Map()
|
||||
this._stateListener = new Map()
|
||||
}
|
||||
_getListener (name) {
|
||||
let listeners = this._eventListener.get(name)
|
||||
@@ -21,16 +22,32 @@ export default class NamedEventHandler {
|
||||
let listeners = this._getListener(name)
|
||||
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) {
|
||||
if (name == null || f == null) {
|
||||
throw new Error('You must specify event name and function!')
|
||||
}
|
||||
const listener = this._eventListener.get(name)
|
||||
if (listener !== undefined) {
|
||||
listener.remove(f)
|
||||
listener.on.delete(f)
|
||||
listener.once.delete(f)
|
||||
}
|
||||
}
|
||||
emit (name, ...args) {
|
||||
this._initStateListener(name).resolve()
|
||||
const listener = this._eventListener.get(name)
|
||||
if (listener !== undefined) {
|
||||
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
|
||||
}
|
||||
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) {
|
||||
this.created = new Date()
|
||||
const beforeState = transaction.beforeState
|
||||
this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1)
|
||||
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))
|
||||
} else {
|
||||
this.fromState = this.toState
|
||||
this.toState = null
|
||||
this.fromState = null
|
||||
}
|
||||
this.deletedStructs = transaction.deletedStructs
|
||||
}
|
||||
@@ -30,28 +31,32 @@ function applyReverseOperation (y, scope, reverseBuffer) {
|
||||
while (!performedUndo && reverseBuffer.length > 0) {
|
||||
let undoOp = reverseBuffer.pop()
|
||||
// make sure that it is possible to iterate {from}-{to}
|
||||
y.os.getItemCleanStart(undoOp.fromState)
|
||||
y.os.getItemCleanEnd(undoOp.toState)
|
||||
y.os.iterate(undoOp.fromState, undoOp.toState, op => {
|
||||
if (!op._deleted && isStructInScope(y, op, scope)) {
|
||||
performedUndo = true
|
||||
op._delete(y)
|
||||
}
|
||||
})
|
||||
if (undoOp.fromState !== null) {
|
||||
y.os.getItemCleanStart(undoOp.fromState)
|
||||
y.os.getItemCleanEnd(undoOp.toState)
|
||||
y.os.iterate(undoOp.fromState, undoOp.toState, op => {
|
||||
while (op._deleted && op._redone !== null) {
|
||||
op = op._redone
|
||||
}
|
||||
if (op._deleted === false && isStructInScope(y, op, scope)) {
|
||||
performedUndo = true
|
||||
op._delete(y)
|
||||
}
|
||||
})
|
||||
}
|
||||
for (let op of undoOp.deletedStructs) {
|
||||
if (
|
||||
isStructInScope(y, op, scope) &&
|
||||
op._parent !== y &&
|
||||
!op._parent._deleted &&
|
||||
(
|
||||
op._parent._id.user !== y.userID ||
|
||||
op._parent._id.clock < undoOp.fromState.clock ||
|
||||
op._parent._id.clock > undoOp.fromState.clock
|
||||
op._id.user !== y.userID ||
|
||||
undoOp.fromState === null ||
|
||||
op._id.clock < undoOp.fromState.clock ||
|
||||
op._id.clock > undoOp.toState.clock
|
||||
)
|
||||
) {
|
||||
performedUndo = true
|
||||
op = op._copy(undoOp.deletedStructs)
|
||||
op._integrate(y)
|
||||
op._redo(y)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +73,7 @@ export default class UndoManager {
|
||||
this._scope = scope
|
||||
this._undoing = false
|
||||
this._redoing = false
|
||||
this._lastTransactionWasUndo = false
|
||||
const y = scope._y
|
||||
this.y = y
|
||||
y.on('afterTransaction', (y, transaction, remote) => {
|
||||
@@ -75,17 +81,29 @@ export default class UndoManager {
|
||||
let reverseOperation = new ReverseOperation(y, transaction)
|
||||
if (!this._undoing) {
|
||||
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.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)
|
||||
} else {
|
||||
this._lastTransactionWasUndo = false
|
||||
this._undoBuffer.push(reverseOperation)
|
||||
}
|
||||
if (!this._redoing) {
|
||||
this._redoBuffer = []
|
||||
}
|
||||
} else {
|
||||
this._lastTransactionWasUndo = true
|
||||
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'
|
||||
|
||||
export function getRelativePosition (type, offset) {
|
||||
if (offset === 0) {
|
||||
return ['startof', type._id.user, type._id.clock || null, type._id.name || null, type._id.type || null]
|
||||
} else {
|
||||
let t = type._start
|
||||
while (t !== null) {
|
||||
if (t._deleted === false) {
|
||||
if (t._length >= offset) {
|
||||
return [t._id.user, t._id.clock + offset - 1]
|
||||
}
|
||||
if (t._right === null) {
|
||||
return [t._id.user, t._id.clock + t._length - 1]
|
||||
}
|
||||
offset -= t._length
|
||||
let t = type._start
|
||||
while (t !== null) {
|
||||
if (t._deleted === false) {
|
||||
if (t._length > offset) {
|
||||
return [t._id.user, t._id.clock + offset]
|
||||
}
|
||||
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) {
|
||||
if (rpos[0] === 'startof') {
|
||||
if (rpos[0] === 'endof') {
|
||||
let id
|
||||
if (rpos[3] === null) {
|
||||
id = new ID(rpos[1], rpos[2])
|
||||
} else {
|
||||
id = new RootID(rpos[3], rpos[4])
|
||||
}
|
||||
const type = y.os.get(id)
|
||||
return {
|
||||
type: y.os.get(id),
|
||||
offset: 0
|
||||
type,
|
||||
offset: type.length
|
||||
}
|
||||
} else {
|
||||
let offset = 0
|
||||
@@ -42,7 +36,7 @@ export function fromRelativePosition (y, rpos) {
|
||||
return null
|
||||
}
|
||||
if (!struct._deleted) {
|
||||
offset = rpos[1] - struct._id.clock + 1
|
||||
offset = rpos[1] - struct._id.clock
|
||||
}
|
||||
struct = struct._left
|
||||
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 NamedEventHandler from './Util/NamedEventHandler.js'
|
||||
import UndoManager from './Util/UndoManager.js'
|
||||
import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.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 Transaction from './Transaction.js'
|
||||
|
||||
import TextareaBinding from './Binding/TextareaBinding.js'
|
||||
|
||||
import { toBinary, fromBinary } from './MessageHandler/binaryEncode.js'
|
||||
|
||||
export default class Y extends NamedEventHandler {
|
||||
constructor (opts) {
|
||||
constructor (room, opts, persistence) {
|
||||
super()
|
||||
this.room = room
|
||||
if (opts != null) {
|
||||
opts.connector.room = room
|
||||
}
|
||||
this._contentReady = false
|
||||
this._opts = opts
|
||||
this.userID = opts._userID != null ? opts._userID : generateUserID()
|
||||
this.userID = generateUserID()
|
||||
this.share = {}
|
||||
this.ds = new DeleteStore(this)
|
||||
this.os = new OperationStore(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._readyToIntegrate = []
|
||||
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 () {}
|
||||
transact (f, remote = false) {
|
||||
@@ -90,9 +123,6 @@ export default class Y extends NamedEventHandler {
|
||||
set _start (start) {
|
||||
return null
|
||||
}
|
||||
get room () {
|
||||
return this._opts.connector.room
|
||||
}
|
||||
define (name, TypeConstructor) {
|
||||
let id = new RootID(name, TypeConstructor)
|
||||
let type = this.os.get(id)
|
||||
@@ -123,11 +153,18 @@ export default class Y extends NamedEventHandler {
|
||||
}
|
||||
}
|
||||
destroy () {
|
||||
super.destroy()
|
||||
this.share = null
|
||||
if (this.connector.destroy != null) {
|
||||
this.connector.destroy()
|
||||
} else {
|
||||
this.connector.disconnect()
|
||||
if (this.connector != null) {
|
||||
if (this.connector.destroy != null) {
|
||||
this.connector.destroy()
|
||||
} else {
|
||||
this.connector.disconnect()
|
||||
}
|
||||
}
|
||||
if (this.persistence !== null) {
|
||||
this.persistence.deinit(this)
|
||||
this.persistence = null
|
||||
}
|
||||
this.os = null
|
||||
this.ds = null
|
||||
@@ -155,7 +192,7 @@ Y.extend = function extendYjs () {
|
||||
|
||||
// TODO: The following assignments should be moved to yjs-dist
|
||||
Y.AbstractConnector = Connector
|
||||
Y.Persisence = Persistence
|
||||
Y.AbstractPersistence = Persistence
|
||||
Y.Array = YArray
|
||||
Y.Map = YMap
|
||||
Y.Text = YText
|
||||
@@ -164,12 +201,17 @@ Y.XmlFragment = YXmlFragment
|
||||
Y.XmlText = YXmlText
|
||||
Y.XmlHook = YXmlHook
|
||||
|
||||
Y.TextareaBinding = TextareaBinding
|
||||
|
||||
Y.utils = {
|
||||
BinaryDecoder,
|
||||
UndoManager,
|
||||
getRelativePosition,
|
||||
fromRelativePosition,
|
||||
addType
|
||||
addType,
|
||||
integrateRemoteStructs,
|
||||
toBinary,
|
||||
fromBinary
|
||||
}
|
||||
|
||||
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, '☺☺☺')
|
||||
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) {
|
||||
@@ -3,6 +3,6 @@
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="./encode-decode.js"></script>
|
||||
<script type="module" src="./index.js"></script>
|
||||
</body>
|
||||
</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'
|
||||
@@ -145,7 +145,7 @@ export async function initArrays (t, opts) {
|
||||
} else {
|
||||
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
|
||||
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
1
y.test.js.map
Normal file
1
y.test.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user