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>
<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>

View File

@@ -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)

View File

@@ -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>

View File

@@ -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

View File

@@ -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">

View File

@@ -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)

View File

@@ -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>

View File

@@ -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'
}
})

View File

@@ -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>

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({
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, {

View File

@@ -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="./index.js"></script>
</head>
<body contenteditable="true">

View File

@@ -4,18 +4,14 @@ window.onload = function () {
window.yXmlType.bindToDom(document.body)
}
let persistence = null // new Y.IndexedDBPersistence()
// 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: 'x'
// maxBufferLength: 100
url: 'http://127.0.0.1:1234'
}
}, persistence)
window.yXml = y
})
window.y = y
window.yXmlType = y.define('xml', Y.XmlFragment)
window.undoManager = new Y.utils.UndoManager(window.yXmlType, {
captureTimeout: 500

View File

@@ -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>

View File

@@ -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)

View File

@@ -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>

View File

@@ -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')

View File

@@ -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>

View File

@@ -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 + ')'
})
})
})

View File

@@ -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>

View File

@@ -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)
})

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)

View File

@@ -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>

View File

@@ -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)

View File

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

2
package-lock.json generated
View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "yjs",
"version": "13.0.0-46",
"version": "13.0.0-49",
"description": "A framework for real-time p2p shared editing on any data",
"main": "./y.node.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) {
cnf = getFreshCnf()
this.ys.set(y, cnf)
this.init(y)
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]
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]
}
}
}
})
return this.retrieve(y)
}).then(function () {
return Promise.resolve(cnf)
})
}
return this.retrieve(y).then(function () {
} 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)
}
}
})
}
/* overwrite */
saveUpdate (buffer) {
}
@@ -75,17 +98,17 @@ export default class AbstractPersistence {
y.transact(function () {
if (model != null) {
fromBinary(y, new BinaryDecoder(new Uint8Array(model)))
y._setContentReady()
}
if (updates != null) {
for (let i = 0; i < updates.length; i++) {
integrateRemoteStructs(y, new BinaryDecoder(new Uint8Array(updates[i])))
y._setContentReady()
}
}
})
y.emit('persistenceReady')
})
}
/* overwrite */
persist (y) {
return toBinary(y).createBuffer()

View File

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

View File

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

View File

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

View File

@@ -79,15 +79,16 @@ export default class Type extends Item {
type = type._parent
}
}
_copy (undeleteChildren) {
let copy = super._copy()
_copy (undeleteChildren, copyPosition) {
let copy = super._copy(undeleteChildren, copyPosition)
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)
let _item = value._copy(undeleteChildren, false)
_item._parent = copy
map.set(key, value._copy(undeleteChildren))
_item._parentSub = key
map.set(key, _item)
}
}
let prevUndeleted = null
@@ -95,7 +96,7 @@ export default class Type extends Item {
let item = this._start
while (item !== null) {
if (undeleteChildren.has(item) || !item.deleted) {
let _item = item._copy(undeleteChildren)
let _item = item._copy(undeleteChildren, false)
_item._left = prevUndeleted
_item._origin = prevUndeleted
_item._right = null
@@ -133,7 +134,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

View File

@@ -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

View File

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

View File

@@ -152,18 +152,20 @@ export default class YXmlFragment extends YArray {
attributes.set(key, attrs[key])
}
}
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._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)
})
}
this.forEach(xml => {
xml.setDomFilter(f)
})
}
_callObserver (transaction, parentSubs, remote) {

View File

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

View File

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

View File

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

View File

@@ -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,6 +22,10 @@ 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 (room, opts, persistence) {
super()
@@ -61,6 +66,15 @@ export default class Y extends NamedEventHandler {
this.emit('content')
}
}
whenContentReady () {
if (this._contentReady) {
return Promise.resolve()
} else {
return new Promise(resolve => {
this.once('content', resolve)
})
}
}
_beforeChange () {}
transact (f, remote = false) {
let initialCall = this._transaction === null
@@ -187,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

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