Compare commits

..

11 Commits

Author SHA1 Message Date
Kevin Jahns
fdcc19d8f5 v13.0.0-49 -- distribution files 2018-01-30 15:53:50 -08:00
Kevin Jahns
bbd3317d62 13.0.0-49 2018-01-30 15:53:33 -08:00
Kevin Jahns
5d3922cb64 fix undo-redo 2018-01-30 15:52:36 -08:00
Kevin Jahns
a81a2cd553 13.0.0-48 2018-01-29 16:41:52 -08:00
Kevin Jahns
c0d24bdba4 lint 2018-01-29 16:41:27 -08:00
Kevin Jahns
40e913e9c5 add toBinary and fromBinary to Y.utils 2018-01-29 16:39:09 -08:00
Kevin Jahns
94f6a0fd9c implement Y.*Binding approach 2018-01-29 11:55:28 -08:00
Kevin Jahns
41a88dbc43 fix examples for Yjs@13 2018-01-25 17:28:33 -07:00
Kevin Jahns
1d4f283955 13.0.0-47 2018-01-18 18:44:56 +01:00
Kevin Jahns
fc3a4c376c implement when-handler 2018-01-18 18:44:20 +01:00
Kevin Jahns
0b510b64a3 persistence updates + make Persistence.init async 2018-01-16 16:13:47 +01:00
49 changed files with 6324 additions and 6198 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -107,15 +107,13 @@ Y.XmlHook.addHook('magic-drawing', {
} }
}) })
// initialize a shared object. This function call returns a promise! let y = new Y('html-editor-drawing-hook-example', {
let y = new Y({
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
url: 'http://127.0.0.1:1234', url: 'http://127.0.0.1:1234'
room: 'html-editor-example6'
// maxBufferLength: 100
} }
}) })
window.yXml = y window.yXml = y
window.yXmlType = y.define('xml', Y.XmlFragment) window.yXmlType = y.define('xml', Y.XmlFragment)
window.undoManager = new Y.utils.UndoManager(window.yXmlType, { window.undoManager = new Y.utils.UndoManager(window.yXmlType, {

View File

@@ -1,7 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
</head> </head>
<script src="../yjs-dist.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</head> </head>
<body contenteditable="true"> <body contenteditable="true">

View File

@@ -4,18 +4,14 @@ window.onload = function () {
window.yXmlType.bindToDom(document.body) window.yXmlType.bindToDom(document.body)
} }
let persistence = null // new Y.IndexedDBPersistence() let y = new Y('htmleditor', {
// 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: 'x'
// maxBufferLength: 100
} }
}, persistence) })
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +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 IndexedDBPersistence from '../../y-indexeddb/src/y-indexeddb.js' import extendYIndexedDBPersistence from '../../y-indexeddb/src/y-indexeddb.js'
Y.extend(yWebsocketsClient) Y.extend(yWebsocketsClient)
Y.IndexedDBPersistence = IndexedDBPersistence extendYIndexedDBPersistence(Y)
export default Y export default Y

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.0-46", "version": "13.0.0-49",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.0-46", "version": "13.0.0-49",
"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",

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

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

View File

View File

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

View File

@@ -25,32 +25,55 @@ export default class AbstractPersistence {
if (cnf === undefined) { if (cnf === undefined) {
cnf = getFreshCnf() cnf = getFreshCnf()
this.ys.set(y, cnf) this.ys.set(y, cnf)
this.init(y) return this.init(y).then(() => {
y.on('afterTransaction', (y, transaction) => { y.on('afterTransaction', (y, transaction) => {
let cnf = this.ys.get(y) let cnf = this.ys.get(y)
if (cnf.len > 0) { if (cnf.len > 0) {
cnf.buffer.setUint32(0, cnf.len) cnf.buffer.setUint32(0, cnf.len)
this.saveUpdate(y, cnf.buffer.createBuffer(), transaction) this.saveUpdate(y, cnf.buffer.createBuffer(), transaction)
let _cnf = getFreshCnf() let _cnf = getFreshCnf()
for (let key in _cnf) { for (let key in _cnf) {
cnf[key] = _cnf[key] cnf[key] = _cnf[key]
}
} }
} })
return this.retrieve(y)
}).then(function () {
return Promise.resolve(cnf)
}) })
} } else {
return this.retrieve(y).then(function () {
return Promise.resolve(cnf) return Promise.resolve(cnf)
}) }
} }
deinit (y) { deinit (y) {
this.ys.delete(y) this.ys.delete(y)
y.persistence = null
} }
destroy () { destroy () {
this.ys = null 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)
}
}
})
}
/* overwrite */ /* overwrite */
saveUpdate (buffer) { saveUpdate (buffer) {
} }
@@ -75,17 +98,17 @@ export default class AbstractPersistence {
y.transact(function () { y.transact(function () {
if (model != null) { if (model != null) {
fromBinary(y, new BinaryDecoder(new Uint8Array(model))) fromBinary(y, new BinaryDecoder(new Uint8Array(model)))
y._setContentReady()
} }
if (updates != null) { if (updates != null) {
for (let i = 0; i < updates.length; i++) { for (let i = 0; i < updates.length; i++) {
integrateRemoteStructs(y, new BinaryDecoder(new Uint8Array(updates[i]))) integrateRemoteStructs(y, new BinaryDecoder(new Uint8Array(updates[i])))
y._setContentReady()
} }
} }
}) })
y.emit('persistenceReady')
}) })
} }
/* overwrite */ /* overwrite */
persist (y) { persist (y) {
return toBinary(y).createBuffer() return toBinary(y).createBuffer()

View File

@@ -55,14 +55,16 @@ export default class Item {
/** /**
* Copy the effect of struct * Copy the effect of struct
*/ */
_copy () { _copy (undeleteChildren, copyPosition) {
let struct = new this.constructor() let struct = new this.constructor()
struct._origin = this._left if (copyPosition) {
struct._left = this._left struct._origin = this._left
struct._right = this struct._left = this._left
struct._right_origin = this struct._right = this
struct._parent = this._parent struct._right_origin = this
struct._parentSub = this._parentSub struct._parent = this._parent
struct._parentSub = this._parentSub
}
return struct return struct
} }
get _lastId () { get _lastId () {
@@ -113,9 +115,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) {

View File

@@ -6,8 +6,8 @@ export default class ItemJSON extends Item {
super() super()
this._content = null this._content = null
} }
_copy () { _copy (undeleteChildren, copyPosition) {
let struct = super._copy() let struct = super._copy(undeleteChildren, copyPosition)
struct._content = this._content struct._content = this._content
return struct return struct
} }

View File

@@ -6,8 +6,8 @@ export default class ItemString extends Item {
super() super()
this._content = null this._content = null
} }
_copy () { _copy (undeleteChildren, copyPosition) {
let struct = super._copy() let struct = super._copy(undeleteChildren, copyPosition)
struct._content = this._content struct._content = this._content
return struct return struct
} }

View File

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

View File

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

View File

@@ -20,8 +20,8 @@ export default class YXmlElement extends YXmlFragment {
this._domFilter = arg2 this._domFilter = arg2
} }
} }
_copy (undeleteChildren) { _copy (undeleteChildren, copyPosition) {
let struct = super._copy(undeleteChildren) let struct = super._copy(undeleteChildren, copyPosition)
struct.nodeName = this.nodeName struct.nodeName = this.nodeName
return struct return struct
} }

View File

@@ -152,18 +152,20 @@ export default class YXmlFragment extends YArray {
attributes.set(key, attrs[key]) attributes.set(key, attrs[key])
} }
} }
let result = this._domFilter(this.nodeName, new Map(attributes)) this._y.transact(() => {
if (result === null) { let result = this._domFilter(this.nodeName, new Map(attributes))
this._delete(this._y) if (result === null) {
} else { this._delete(this._y)
attributes.forEach((value, key) => { } else {
if (!result.has(key)) { attributes.forEach((value, key) => {
this.removeAttribute(key) if (!result.has(key)) {
} this.removeAttribute(key)
}
})
}
this.forEach(xml => {
xml.setDomFilter(f)
}) })
}
this.forEach(xml => {
xml.setDomFilter(f)
}) })
} }
_callObserver (transaction, parentSubs, remote) { _callObserver (transaction, parentSubs, remote) {

View File

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

View File

@@ -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,6 +22,20 @@ 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!')
@@ -32,6 +47,7 @@ export default class NamedEventHandler {
} }
} }
emit (name, ...args) { emit (name, ...args) {
this._initStateListener(name).resolve()
const listener = this._eventListener.get(name) const listener = this._eventListener.get(name)
if (listener !== undefined) { if (listener !== undefined) {
listener.on.forEach(f => f.apply(null, args)) listener.on.forEach(f => f.apply(null, args))

View File

@@ -50,7 +50,7 @@ function applyReverseOperation (y, scope, reverseBuffer) {
) )
) { ) {
performedUndo = true performedUndo = true
op = op._copy(undoOp.deletedStructs) op = op._copy(undoOp.deletedStructs, true)
op._integrate(y) op._integrate(y)
} }
} }

View File

@@ -5,6 +5,7 @@ import { generateUserID } from './Util/generateUserID.js'
import RootID from './Util/RootID.js' import RootID from './Util/RootID.js'
import NamedEventHandler from './Util/NamedEventHandler.js' import NamedEventHandler from './Util/NamedEventHandler.js'
import UndoManager from './Util/UndoManager.js' import UndoManager from './Util/UndoManager.js'
import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.js'
import { messageToString, messageToRoomname } from './MessageHandler/messageToString.js' import { messageToString, messageToRoomname } from './MessageHandler/messageToString.js'
@@ -21,6 +22,10 @@ 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 (room, opts, persistence) { constructor (room, opts, persistence) {
super() super()
@@ -61,6 +66,15 @@ export default class Y extends NamedEventHandler {
this.emit('content') 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) {
let initialCall = this._transaction === null let initialCall = this._transaction === null
@@ -187,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

8
y.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

241
y.node.js
View File

@@ -1,7 +1,7 @@
/** /**
* yjs - A framework for real-time p2p shared editing on any data * yjs - A framework for real-time p2p shared editing on any data
* @version v13.0.0-46 * @version v13.0.0-49
* @license MIT * @license MIT
*/ */
@@ -1344,14 +1344,16 @@ class Item {
/** /**
* Copy the effect of struct * Copy the effect of struct
*/ */
_copy () { _copy (undeleteChildren, copyPosition) {
let struct = new this.constructor(); let struct = new this.constructor();
struct._origin = this._left; if (copyPosition) {
struct._left = this._left; struct._origin = this._left;
struct._right = this; struct._left = this._left;
struct._right_origin = this; struct._right = this;
struct._parent = this._parent; struct._right_origin = this;
struct._parentSub = this._parentSub; struct._parent = this._parent;
struct._parentSub = this._parentSub;
}
return struct return struct
} }
get _lastId () { get _lastId () {
@@ -1402,9 +1404,11 @@ 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) {
@@ -1716,15 +1720,16 @@ class Type extends Item {
type = type._parent; type = type._parent;
} }
} }
_copy (undeleteChildren) { _copy (undeleteChildren, copyPosition) {
let copy = super._copy(); let copy = super._copy(undeleteChildren, copyPosition);
let map = new Map(); let map = new Map();
copy._map = map; copy._map = map;
for (let [key, value] of this._map) { for (let [key, value] of this._map) {
if (undeleteChildren.has(value) || !value.deleted) { if (undeleteChildren.has(value) || !value.deleted) {
let _item = value._copy(undeleteChildren); let _item = value._copy(undeleteChildren, false);
_item._parent = copy; _item._parent = copy;
map.set(key, value._copy(undeleteChildren)); _item._parentSub = key;
map.set(key, _item);
} }
} }
let prevUndeleted = null; let prevUndeleted = null;
@@ -1732,7 +1737,7 @@ class Type extends Item {
let item = this._start; let item = this._start;
while (item !== null) { while (item !== null) {
if (undeleteChildren.has(item) || !item.deleted) { if (undeleteChildren.has(item) || !item.deleted) {
let _item = item._copy(undeleteChildren); let _item = item._copy(undeleteChildren, false);
_item._left = prevUndeleted; _item._left = prevUndeleted;
_item._origin = prevUndeleted; _item._origin = prevUndeleted;
_item._right = null; _item._right = null;
@@ -1770,7 +1775,6 @@ 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
@@ -1813,8 +1817,8 @@ class ItemJSON extends Item {
super(); super();
this._content = null; this._content = null;
} }
_copy () { _copy (undeleteChildren, copyPosition) {
let struct = super._copy(); let struct = super._copy(undeleteChildren, copyPosition);
struct._content = this._content; struct._content = this._content;
return struct return struct
} }
@@ -1875,8 +1879,8 @@ class ItemString extends Item {
super(); super();
this._content = null; this._content = null;
} }
_copy () { _copy (undeleteChildren, copyPosition) {
let struct = super._copy(); let struct = super._copy(undeleteChildren, copyPosition);
struct._content = this._content; struct._content = this._content;
return struct return struct
} }
@@ -1944,17 +1948,21 @@ class YArrayEvent extends YEvent {
super(yarray); super(yarray);
this.remote = remote; this.remote = remote;
this._transaction = transaction; this._transaction = transaction;
this._addedElements = null;
} }
get addedElements () { get addedElements () {
const target = this.target; if (this._addedElements === null) {
const transaction = this._transaction; const target = this.target;
const addedElements = new Set(); const transaction = this._transaction;
transaction.newTypes.forEach(function (type) { const addedElements = new Set();
if (type._parent === target && !transaction.deletedStructs.has(type)) { transaction.newTypes.forEach(function (type) {
addedElements.add(type); if (type._parent === target && !transaction.deletedStructs.has(type)) {
} addedElements.add(type);
}); }
return addedElements });
this._addedElements = addedElements;
}
return this._addedElements
} }
get removedElements () { get removedElements () {
const target = this.target; const target = this.target;
@@ -2907,18 +2915,20 @@ class YXmlFragment extends YArray {
attributes.set(key, attrs[key]); attributes.set(key, attrs[key]);
} }
} }
let result = this._domFilter(this.nodeName, new Map(attributes)); this._y.transact(() => {
if (result === null) { let result = this._domFilter(this.nodeName, new Map(attributes));
this._delete(this._y); if (result === null) {
} else { this._delete(this._y);
attributes.forEach((value, key) => { } else {
if (!result.has(key)) { attributes.forEach((value, key) => {
this.removeAttribute(key); if (!result.has(key)) {
} this.removeAttribute(key);
}
});
}
this.forEach(xml => {
xml.setDomFilter(f);
}); });
}
this.forEach(xml => {
xml.setDomFilter(f);
}); });
} }
_callObserver (transaction, parentSubs, remote) { _callObserver (transaction, parentSubs, remote) {
@@ -3123,8 +3133,8 @@ class YXmlElement extends YXmlFragment {
this._domFilter = arg2; this._domFilter = arg2;
} }
} }
_copy (undeleteChildren) { _copy (undeleteChildren, copyPosition) {
let struct = super._copy(undeleteChildren); let struct = super._copy(undeleteChildren, copyPosition);
struct.nodeName = this.nodeName; struct.nodeName = this.nodeName;
return struct return struct
} }
@@ -3262,6 +3272,11 @@ class YXmlHook extends YMap {
getHook(hookName).fillType(dom, this); getHook(hookName).fillType(dom, this);
} }
} }
_copy (undeleteChildren, copyPosition) {
const struct = super._copy(undeleteChildren, copyPosition);
struct.hookName = this.hookName;
return struct
}
getDom (_document) { getDom (_document) {
_document = _document || document; _document = _document || document;
if (this._dom === null) { if (this._dom === null) {
@@ -3591,6 +3606,7 @@ function generateUserID () {
class NamedEventHandler { 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);
@@ -3611,6 +3627,20 @@ 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!')
@@ -3622,6 +3652,7 @@ class NamedEventHandler {
} }
} }
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));
@@ -3686,7 +3717,7 @@ function applyReverseOperation (y, scope, reverseBuffer) {
) )
) { ) {
performedUndo = true; performedUndo = true;
op = op._copy(undoOp.deletedStructs); op = op._copy(undoOp.deletedStructs, true);
op._integrate(y); op._integrate(y);
} }
} }
@@ -4642,32 +4673,55 @@ class AbstractPersistence {
if (cnf === undefined) { if (cnf === undefined) {
cnf = getFreshCnf(); cnf = getFreshCnf();
this.ys.set(y, cnf); this.ys.set(y, cnf);
this.init(y); return this.init(y).then(() => {
y.on('afterTransaction', (y, transaction) => { y.on('afterTransaction', (y, transaction) => {
let cnf = this.ys.get(y); let cnf = this.ys.get(y);
if (cnf.len > 0) { if (cnf.len > 0) {
cnf.buffer.setUint32(0, cnf.len); cnf.buffer.setUint32(0, cnf.len);
this.saveUpdate(y, cnf.buffer.createBuffer(), transaction); this.saveUpdate(y, cnf.buffer.createBuffer(), transaction);
let _cnf = getFreshCnf(); let _cnf = getFreshCnf();
for (let key in _cnf) { for (let key in _cnf) {
cnf[key] = _cnf[key]; cnf[key] = _cnf[key];
}
} }
} });
}); return this.retrieve(y)
} }).then(function () {
return this.retrieve(y).then(function () { return Promise.resolve(cnf)
})
} else {
return Promise.resolve(cnf) return Promise.resolve(cnf)
}) }
} }
deinit (y) { deinit (y) {
this.ys.delete(y); this.ys.delete(y);
y.persistence = null;
} }
destroy () { destroy () {
this.ys = null; 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);
}
}
});
}
/* overwrite */ /* overwrite */
saveUpdate (buffer) { saveUpdate (buffer) {
} }
@@ -4692,23 +4746,76 @@ class AbstractPersistence {
y.transact(function () { y.transact(function () {
if (model != null) { if (model != null) {
fromBinary(y, new BinaryDecoder(new Uint8Array(model))); fromBinary(y, new BinaryDecoder(new Uint8Array(model)));
y._setContentReady();
} }
if (updates != null) { if (updates != null) {
for (let i = 0; i < updates.length; i++) { for (let i = 0; i < updates.length; i++) {
integrateRemoteStructs(y, new BinaryDecoder(new Uint8Array(updates[i]))); integrateRemoteStructs(y, new BinaryDecoder(new Uint8Array(updates[i])));
y._setContentReady();
} }
} }
}); });
y.emit('persistenceReady');
}); });
} }
/* overwrite */ /* overwrite */
persist (y) { persist (y) {
return toBinary(y).createBuffer() return toBinary(y).createBuffer()
} }
} }
class Binding {
constructor (type, target) {
this.type = type;
this.target = target;
this._mutualExclude = createMutualExclude();
}
destroy () {
this.type = null;
this.target = null;
}
}
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);
});
}
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();
}
}
class Y$1 extends NamedEventHandler { class Y$1 extends NamedEventHandler {
constructor (room, opts, persistence) { constructor (room, opts, persistence) {
super(); super();
@@ -4749,6 +4856,15 @@ class Y$1 extends NamedEventHandler {
this.emit('content'); 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) {
let initialCall = this._transaction === null; let initialCall = this._transaction === null;
@@ -4875,12 +4991,17 @@ Y$1.XmlFragment = YXmlFragment;
Y$1.XmlText = YXmlText; Y$1.XmlText = YXmlText;
Y$1.XmlHook = YXmlHook; Y$1.XmlHook = YXmlHook;
Y$1.TextareaBinding = TextareaBinding;
Y$1.utils = { Y$1.utils = {
BinaryDecoder, BinaryDecoder,
UndoManager, UndoManager,
getRelativePosition, getRelativePosition,
fromRelativePosition, fromRelativePosition,
addType: addStruct addType: addStruct,
integrateRemoteStructs,
toBinary,
fromBinary
}; };
Y$1.debug = browser; Y$1.debug = browser;

File diff suppressed because one or more lines are too long

11533
y.test.js

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long