Compare commits

..

16 Commits

Author SHA1 Message Date
Kevin Jahns
c4c718e094 v13.0.0-42 -- distribution files 2017-12-19 17:39:27 +01:00
Kevin Jahns
8f3bd7170a 13.0.0-42 2017-12-19 17:39:01 +01:00
Kevin Jahns
5586334549 fix initial content in y-array 2017-12-19 17:37:04 +01:00
Kevin Jahns
24c1e4dcc8 13.0.0-41 2017-12-14 14:30:02 +01:00
Kevin Jahns
d61bbecf4e fix tree walker on YXmlFragment 2017-12-14 14:29:16 +01:00
Kevin Jahns
85492ad2e0 fix drawing example. Add drawing hook for y-xml 2017-12-13 12:49:34 +01:00
Kevin Jahns
02253f9a8d fix log outputs 2017-12-13 10:28:19 +01:00
Kevin Jahns
8105bef1af work on drawing demo 2017-12-06 19:20:52 -08:00
Kevin Jahns
4efa16e2dd 13.0.0-40 2017-12-05 21:50:34 -08:00
Kevin Jahns
ad44f59def implement new dom update algorithm 2017-12-05 21:50:00 -08:00
Kevin Jahns
9c471ea24d 13.0.0-39 2017-12-05 17:06:01 -08:00
Kevin Jahns
d9e76014f5 fix remaining cursor relocation issues 2017-12-05 17:05:12 -08:00
Kevin Jahns
4091b7d004 13.0.0-38 2017-12-05 00:53:25 -08:00
Kevin Jahns
dfc183643d support data-yjs-hook attribute for yjs hooks 2017-12-05 00:52:52 -08:00
Kevin Jahns
cf8698f2b6 13.0.0-37 2017-12-02 01:45:55 -08:00
Kevin Jahns
3595f14da7 fix insert in y-text 2017-12-02 01:45:22 -08:00
20 changed files with 1192 additions and 956 deletions

View File

@@ -12,12 +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="../../y.js"></script> <script src="../yjs-dist.js"></script>
<script src="../../../y-array/y-array.js"></script> <script src="../bower_components/d3/d3.min.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="../bower_components/d3/d3.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,84 +1,76 @@
/* globals Y, d3 */ /* globals Y, d3 */
'strict mode'
Y({ let y = new Y({
db: {
name: 'memory'
},
connector: { connector: {
name: 'websockets-client', name: 'websockets-client',
room: 'drawing-example', url: 'http://127.0.0.1:1234',
url: 'localhost:1234' room: 'drawing-example'
}, // maxBufferLength: 100
sourceDir: '/bower_components',
share: {
drawing: 'Array'
}
}).then(function (y) {
window.yDrawing = y
var drawing = y.share.drawing
var renderPath = d3.svg.line()
.x(function (d) { return d[0] })
.y(function (d) { return d[1] })
.interpolate('basis')
var svg = d3.select('#drawingCanvas')
.call(d3.behavior.drag()
.on('dragstart', dragstart)
.on('drag', drag)
.on('dragend', dragend))
// create line from a shared array object and update the line when the array changes
function drawLine (yarray) {
var line = svg.append('path').datum(yarray.toArray())
line.attr('d', renderPath)
yarray.observe(function (event) {
// we only implement insert events that are appended to the end of the array
event.values.forEach(function (value) {
line.datum().push(value)
})
line.attr('d', renderPath)
})
}
// call drawLine every time an array is appended
y.share.drawing.observe(function (event) {
if (event.type === 'insert') {
event.values.forEach(drawLine)
} else {
// just remove all elements (thats what we do anyway)
svg.selectAll('path').remove()
}
})
// draw all existing content
for (var i = 0; i < drawing.length; i++) {
drawLine(drawing.get(i))
}
// clear canvas on request
document.querySelector('#clearDrawingCanvas').onclick = function () {
drawing.delete(0, drawing.length)
}
var sharedLine = null
function dragstart () {
drawing.insert(drawing.length, [Y.Array])
sharedLine = drawing.get(drawing.length - 1)
}
// After one dragged event is recognized, we ignore them for 33ms.
var ignoreDrag = null
function drag () {
if (sharedLine != null && ignoreDrag == null) {
ignoreDrag = window.setTimeout(function () {
ignoreDrag = null
}, 33)
sharedLine.push([d3.mouse(this)])
}
}
function dragend () {
sharedLine = null
window.clearTimeout(ignoreDrag)
ignoreDrag = null
} }
}) })
window.yDrawing = y
var drawing = y.define('drawing', Y.Array)
var renderPath = d3.svg.line()
.x(function (d) { return d[0] })
.y(function (d) { return d[1] })
.interpolate('basic')
var svg = d3.select('#drawingCanvas')
.call(d3.behavior.drag()
.on('dragstart', dragstart)
.on('drag', drag)
.on('dragend', dragend))
// create line from a shared array object and update the line when the array changes
function drawLine (yarray) {
var line = svg.append('path').datum(yarray.toArray())
line.attr('d', renderPath)
yarray.observe(function (event) {
line.remove()
line = svg.append('path').datum(yarray.toArray())
line.attr('d', renderPath)
})
}
// call drawLine every time an array is appended
drawing.observe(function (event) {
event.removedElements.forEach(function () {
// if one is deleted, all will be deleted!!
svg.selectAll('path').remove()
})
event.addedElements.forEach(function (path) {
drawLine(path)
})
})
// draw all existing content
for (var i = 0; i < drawing.length; i++) {
drawLine(drawing.get(i))
}
// clear canvas on request
document.querySelector('#clearDrawingCanvas').onclick = function () {
drawing.delete(0, drawing.length)
}
var sharedLine = null
function dragstart () {
drawing.insert(drawing.length, [Y.Array])
sharedLine = drawing.get(drawing.length - 1)
}
// After one dragged event is recognized, we ignore them for 33ms.
var ignoreDrag = null
function drag () {
if (sharedLine != null && ignoreDrag == null) {
ignoreDrag = window.setTimeout(function () {
ignoreDrag = null
}, 10)
sharedLine.push([d3.mouse(this)])
}
}
function dragend () {
sharedLine = null
window.clearTimeout(ignoreDrag)
ignoreDrag = null
}

View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>
</head>
<script src="../yjs-dist.js"></script>
<script src="../bower_components/d3/d3.min.js"></script>
<script src="./index.js"></script>
<style>
magic-drawing .drawingCanvas path {
fill: none;
stroke: blue;
stroke-width: 2px;
stroke-linejoin: round;
stroke-linecap: round;
}
magic-drawing .drawingCanvas {
width: 500px;
height: 500px;
cursor: default;
padding:1px;
border:1px solid #021a40;
}
magic-drawing .clearDrawingButton {
position: absolute;
top: 0;
left: 0;
}
magic-drawing {
position: relative;
display: block;
}
</style>
</head>
<body contenteditable="true">
</body>
</html>

View File

@@ -0,0 +1,134 @@
/* global Y, d3 */
window.onload = function () {
window.yXmlType.bindToDom(document.body)
}
window.addMagicDrawing = function addMagicDrawing () {
let mt = document.createElement('magic-drawing')
mt.dataset.yjsHook = 'magic-drawing'
document.body.append(mt)
}
var renderPath = d3.svg.line()
.x(function (d) { return d[0] })
.y(function (d) { return d[1] })
.interpolate('basic')
function initDrawingBindings (type, dom) {
dom.contentEditable = 'false'
dom.dataset.yjsHook = 'magic-drawing'
var drawing = type.get('drawing')
if (drawing === undefined) {
drawing = type.set('drawing', new Y.Array())
}
var canvas = dom.querySelector('.drawingCanvas')
if (canvas == null) {
canvas = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
canvas.setAttribute('class', 'drawingCanvas')
canvas.setAttribute('viewbox', '0 0 100 100')
dom.insertBefore(canvas, null)
}
var clearDrawingButton = dom.querySelector('.clearDrawingButton')
if (clearDrawingButton == null) {
clearDrawingButton = document.createElement('button')
clearDrawingButton.setAttribute('type', 'button')
clearDrawingButton.setAttribute('class', 'clearDrawingButton')
clearDrawingButton.innerText = 'Clear Drawing'
dom.insertBefore(clearDrawingButton, null)
}
var svg = d3.select(canvas)
.call(d3.behavior.drag()
.on('dragstart', dragstart)
.on('drag', drag)
.on('dragend', dragend))
// create line from a shared array object and update the line when the array changes
function drawLine (yarray, svg) {
var line = svg.append('path').datum(yarray.toArray())
line.attr('d', renderPath)
yarray.observe(function (event) {
line.remove()
line = svg.append('path').datum(yarray.toArray())
line.attr('d', renderPath)
})
}
// call drawLine every time an array is appended
drawing.observe(function (event) {
event.removedElements.forEach(function () {
// if one is deleted, all will be deleted!!
svg.selectAll('path').remove()
})
event.addedElements.forEach(function (path) {
drawLine(path, svg)
})
})
// draw all existing content
for (var i = 0; i < drawing.length; i++) {
drawLine(drawing.get(i), svg)
}
// clear canvas on request
clearDrawingButton.onclick = function () {
drawing.delete(0, drawing.length)
}
var sharedLine = null
function dragstart () {
drawing.insert(drawing.length, [Y.Array])
sharedLine = drawing.get(drawing.length - 1)
}
// After one dragged event is recognized, we ignore them for 33ms.
var ignoreDrag = null
function drag () {
if (sharedLine != null && ignoreDrag == null) {
ignoreDrag = window.setTimeout(function () {
ignoreDrag = null
}, 10)
sharedLine.push([d3.mouse(this)])
}
}
function dragend () {
sharedLine = null
window.clearTimeout(ignoreDrag)
ignoreDrag = null
}
}
Y.XmlHook.addHook('magic-drawing', {
fillType: function (dom, type) {
initDrawingBindings(type, dom)
},
createDom: function (type) {
const dom = document.createElement('magic-drawing')
initDrawingBindings(type, dom)
return dom
}
})
// initialize a shared object. This function call returns a promise!
let y = new Y({
connector: {
name: 'websockets-client',
url: 'http://127.0.0.1:1234',
room: 'html-editor-example6'
// maxBufferLength: 100
}
})
window.yXml = y
window.yXmlType = y.define('xml', Y.XmlFragment)
window.undoManager = new Y.utils.UndoManager(window.yXmlType, {
captureTimeout: 500
})
document.onkeydown = function interceptUndoRedo (e) {
if (e.keyCode === 90 && e.metaKey) {
if (!e.shiftKey) {
window.undoManager.undo()
} else {
window.undoManager.redo()
}
e.preventDefault()
}
}

2
package-lock.json generated
View File

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

View File

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

View File

@@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
import multiEntry from 'rollup-plugin-multi-entry' import multiEntry from 'rollup-plugin-multi-entry'
export default { export default {
input: 'test/y-xml.tests.js', input: 'test/*.tests.js',
name: 'y-tests', name: 'y-tests',
sourcemap: true, sourcemap: true,
output: { output: {

View File

@@ -5,15 +5,38 @@ import { logID } from '../MessageHandler/messageToString.js'
import YEvent from '../Util/YEvent.js' import YEvent from '../Util/YEvent.js'
class YArrayEvent extends YEvent { class YArrayEvent extends YEvent {
constructor (yarray, remote) { constructor (yarray, remote, transaction) {
super(yarray) super(yarray)
this.remote = remote this.remote = remote
this._transaction = transaction
}
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
}
get removedElements () {
const target = this.target
const transaction = this._transaction
const removedElements = new Set()
transaction.deletedStructs.forEach(function (struct) {
if (struct._parent === target && !transaction.newTypes.has(struct)) {
removedElements.add(struct)
}
})
return removedElements
} }
} }
export default class YArray extends Type { export default class YArray extends Type {
_callObserver (transaction, parentSubs, remote) { _callObserver (transaction, parentSubs, remote) {
this._callEventHandler(transaction, new YArrayEvent(this, remote)) this._callEventHandler(transaction, new YArrayEvent(this, remote, transaction))
} }
get (pos) { get (pos) {
let n = this._start let n = this._start
@@ -184,8 +207,12 @@ export default class YArray extends Type {
prevJsonIns._content.push(c) prevJsonIns._content.push(c)
} }
} }
if (prevJsonIns !== null && y !== null) { if (prevJsonIns !== null) {
prevJsonIns._integrate(y) if (y !== null) {
prevJsonIns._integrate(y)
} else if (prevJsonIns._left === null) {
this._start = prevJsonIns
}
} }
}) })
} }
@@ -214,6 +241,17 @@ export default class YArray extends Type {
} }
this.insertAfter(left, content) this.insertAfter(left, content)
} }
push (content) {
let n = this._start
let lastUndeleted = null
while (n !== null) {
if (!n._deleted) {
lastUndeleted = n
}
n = n._right
}
this.insertAfter(lastUndeleted, content)
}
_logString () { _logString () {
const left = this._left !== null ? this._left._lastId : null const left = this._left !== null ? this._left._lastId : null
const origin = this._origin !== null ? this._origin._lastId : null const origin = this._origin !== null ? this._origin._lastId : null

View File

@@ -29,14 +29,17 @@ export default class YText extends YArray {
let right = this._start let right = this._start
let count = 0 let count = 0
while (right !== null) { while (right !== null) {
if (count <= pos && pos < count + right._length) { const rightLen = right._deleted ? 0 : (right._length - 1)
if (count <= pos && pos <= count + rightLen) {
const splitDiff = pos - count const splitDiff = pos - count
right = right._splitAt(this._y, pos - count) right = right._splitAt(this._y, splitDiff)
left = right._left left = right._left
count += splitDiff count += splitDiff
break break
} }
count += right._length if (!right._deleted) {
count += right._length
}
left = right left = right
right = right._right right = right._right
} }

View File

@@ -17,7 +17,7 @@ function domToYXml (parent, doms, _document) {
} }
if (parent._domFilter(d.nodeName, new Map()) !== null) { if (parent._domFilter(d.nodeName, new Map()) !== null) {
let type let type
const hookName = d._yjsHook const hookName = d._yjsHook || (d.dataset != null ? d.dataset.yjsHook : undefined)
if (hookName !== undefined) { if (hookName !== undefined) {
type = new YXmlHook(hookName, d) type = new YXmlHook(hookName, d)
} else if (d.nodeType === d.TEXT_NODE) { } else if (d.nodeType === d.TEXT_NODE) {
@@ -55,7 +55,7 @@ class YXmlTreeWalker {
} }
} }
do { do {
if (!n._deleted && n.constructor === YXmlFragment._YXmlElement && n._start !== null) { if (!n._deleted && (n.constructor === YXmlFragment._YXmlElement || n.constructor === YXmlFragment) && n._start !== null) {
// walk down in the tree // walk down in the tree
n = n._start n = n._start
} else { } else {

View File

@@ -24,6 +24,11 @@ export default class YXmlHook extends YMap {
} }
return this._dom return this._dom
} }
_unbindFromDom () {
this._dom._yxml = null
this._yxml = null
// TODO: cleanup hook?
}
_fromBinary (y, decoder) { _fromBinary (y, decoder) {
const missing = super._fromBinary(y, decoder) const missing = super._fromBinary(y, decoder)
this.hookName = decoder.readVarString() this.hookName = decoder.readVarString()

View File

@@ -68,7 +68,6 @@ export function afterTransactionSelectionFixer (y, transaction, remote) {
} }
} }
if (shouldUpdate) { if (shouldUpdate) {
console.info('updating selection!!')
browserSelection.setBaseAndExtent( browserSelection.setBaseAndExtent(
anchorNode, anchorNode,
anchorOffset, anchorOffset,
@@ -76,7 +75,4 @@ export function afterTransactionSelectionFixer (y, transaction, remote) {
focusOffset focusOffset
) )
} }
// delete, so the objects can be gc'd
relativeSelection = null
browserSelection = null
} }

View File

@@ -174,7 +174,7 @@ export function reflectChangesOnDom (events, _document) {
// let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement) // let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement)
if (yxml.constructor === YXmlText) { if (yxml.constructor === YXmlText) {
yxml._dom.nodeValue = yxml.toString() yxml._dom.nodeValue = yxml.toString()
} else { } else if (event.attributesChanged !== undefined) {
// update attributes // update attributes
event.attributesChanged.forEach(attributeName => { event.attributesChanged.forEach(attributeName => {
const value = yxml.getAttribute(attributeName) const value = yxml.getAttribute(attributeName)
@@ -193,19 +193,27 @@ export function reflectChangesOnDom (events, _document) {
* only in the attributes (above) * only in the attributes (above)
*/ */
if (event.childListChanged && yxml.constructor !== YXmlHook) { if (event.childListChanged && yxml.constructor !== YXmlHook) {
// create fragment of undeleted nodes let currentChild = dom.firstChild
const fragment = _document.createDocumentFragment()
yxml.forEach(function (t) { yxml.forEach(function (t) {
fragment.appendChild(t.getDom(_document)) let expectedChild = t.getDom(_document)
if (expectedChild.parentNode === dom) {
// is already attached to the dom. Look for it
while (currentChild !== expectedChild) {
let del = currentChild
currentChild = currentChild.nextSibling
dom.removeChild(del)
}
currentChild = currentChild.nextSibling
} else {
// this dom is not yet attached to dom
dom.insertBefore(expectedChild, currentChild)
}
}) })
// remove remainding nodes while (currentChild !== null) {
let lastChild = dom.lastChild let tmp = currentChild.nextSibling
while (lastChild !== null) { dom.removeChild(currentChild)
dom.removeChild(lastChild) currentChild = tmp
lastChild = dom.lastChild
} }
// insert fragment of undeleted nodes
dom.appendChild(fragment)
} }
} }
/* TODO: smartscrolling /* TODO: smartscrolling

View File

@@ -280,7 +280,6 @@ test('deep element insert', async function xml16 (t) {
deepElement.append(boldElement) deepElement.append(boldElement)
deepElement.append(attrElement) deepElement.append(attrElement)
dom0.append(deepElement) dom0.append(deepElement)
console.log(dom0.outerHTML)
let str0 = dom0.outerHTML let str0 = dom0.outerHTML
await flushAll(t, users) await flushAll(t, users)
let str1 = dom1.outerHTML let str1 = dom1.outerHTML
@@ -340,7 +339,6 @@ test('Filtering remote changes', async function xmlFilteringRemote (t) {
// check dom // check dom
paragraph.getDom().setAttribute('malicious', 'true') paragraph.getDom().setAttribute('malicious', 'true')
span.getDom().setAttribute('malicious', 'true') span.getDom().setAttribute('malicious', 'true')
console.log(xml0.toString())
// check incoming attributes // check incoming attributes
xml1.get(0).get(0).setAttribute('malicious', 'true') xml1.get(0).get(0).setAttribute('malicious', 'true')
xml1.insert(0, [new Y.XmlElement('hideMe')]) xml1.insert(0, [new Y.XmlElement('hideMe')])

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

100
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-36 * @version v13.0.0-42
* @license MIT * @license MIT
*/ */
@@ -2204,15 +2204,38 @@ class YEvent {
} }
class YArrayEvent extends YEvent { class YArrayEvent extends YEvent {
constructor (yarray, remote) { constructor (yarray, remote, transaction) {
super(yarray); super(yarray);
this.remote = remote; this.remote = remote;
this._transaction = transaction;
}
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
}
get removedElements () {
const target = this.target;
const transaction = this._transaction;
const removedElements = new Set();
transaction.deletedStructs.forEach(function (struct) {
if (struct._parent === target && !transaction.newTypes.has(struct)) {
removedElements.add(struct);
}
});
return removedElements
} }
} }
class YArray extends Type { class YArray extends Type {
_callObserver (transaction, parentSubs, remote) { _callObserver (transaction, parentSubs, remote) {
this._callEventHandler(transaction, new YArrayEvent(this, remote)); this._callEventHandler(transaction, new YArrayEvent(this, remote, transaction));
} }
get (pos) { get (pos) {
let n = this._start; let n = this._start;
@@ -2383,8 +2406,12 @@ class YArray extends Type {
prevJsonIns._content.push(c); prevJsonIns._content.push(c);
} }
} }
if (prevJsonIns !== null && y !== null) { if (prevJsonIns !== null) {
prevJsonIns._integrate(y); if (y !== null) {
prevJsonIns._integrate(y);
} else if (prevJsonIns._left === null) {
this._start = prevJsonIns;
}
} }
}); });
} }
@@ -2413,6 +2440,17 @@ class YArray extends Type {
} }
this.insertAfter(left, content); this.insertAfter(left, content);
} }
push (content) {
let n = this._start;
let lastUndeleted = null;
while (n !== null) {
if (!n._deleted) {
lastUndeleted = n;
}
n = n._right;
}
this.insertAfter(lastUndeleted, content);
}
_logString () { _logString () {
const left = this._left !== null ? this._left._lastId : null; const left = this._left !== null ? this._left._lastId : null;
const origin = this._origin !== null ? this._origin._lastId : null; const origin = this._origin !== null ? this._origin._lastId : null;
@@ -2556,14 +2594,17 @@ class YText extends YArray {
let right = this._start; let right = this._start;
let count = 0; let count = 0;
while (right !== null) { while (right !== null) {
if (count <= pos && pos < count + right._length) { const rightLen = right._deleted ? 0 : (right._length - 1);
if (count <= pos && pos <= count + rightLen) {
const splitDiff = pos - count; const splitDiff = pos - count;
right = right._splitAt(this._y, pos - count); right = right._splitAt(this._y, splitDiff);
left = right._left; left = right._left;
count += splitDiff; count += splitDiff;
break break
} }
count += right._length; if (!right._deleted) {
count += right._length;
}
left = right; left = right;
right = right._right; right = right._right;
} }
@@ -2723,7 +2764,7 @@ function reflectChangesOnDom (events, _document) {
// let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement) // let anchorViewPosition = getAnchorViewPosition(yxml._scrollElement)
if (yxml.constructor === YXmlText) { if (yxml.constructor === YXmlText) {
yxml._dom.nodeValue = yxml.toString(); yxml._dom.nodeValue = yxml.toString();
} else { } else if (event.attributesChanged !== undefined) {
// update attributes // update attributes
event.attributesChanged.forEach(attributeName => { event.attributesChanged.forEach(attributeName => {
const value = yxml.getAttribute(attributeName); const value = yxml.getAttribute(attributeName);
@@ -2742,19 +2783,27 @@ function reflectChangesOnDom (events, _document) {
* only in the attributes (above) * only in the attributes (above)
*/ */
if (event.childListChanged && yxml.constructor !== YXmlHook) { if (event.childListChanged && yxml.constructor !== YXmlHook) {
// create fragment of undeleted nodes let currentChild = dom.firstChild;
const fragment = _document.createDocumentFragment();
yxml.forEach(function (t) { yxml.forEach(function (t) {
fragment.appendChild(t.getDom(_document)); let expectedChild = t.getDom(_document);
if (expectedChild.parentNode === dom) {
// is already attached to the dom. Look for it
while (currentChild !== expectedChild) {
let del = currentChild;
currentChild = currentChild.nextSibling;
dom.removeChild(del);
}
currentChild = currentChild.nextSibling;
} else {
// this dom is not yet attached to dom
dom.insertBefore(expectedChild, currentChild);
}
}); });
// remove remainding nodes while (currentChild !== null) {
let lastChild = dom.lastChild; let tmp = currentChild.nextSibling;
while (lastChild !== null) { dom.removeChild(currentChild);
dom.removeChild(lastChild); currentChild = tmp;
lastChild = dom.lastChild;
} }
// insert fragment of undeleted nodes
dom.appendChild(fragment);
} }
} }
/* TODO: smartscrolling /* TODO: smartscrolling
@@ -2936,7 +2985,6 @@ function afterTransactionSelectionFixer (y, transaction, remote) {
} }
} }
if (shouldUpdate) { if (shouldUpdate) {
console.info('updating selection!!');
browserSelection.setBaseAndExtent( browserSelection.setBaseAndExtent(
anchorNode, anchorNode,
anchorOffset, anchorOffset,
@@ -2944,9 +2992,6 @@ function afterTransactionSelectionFixer (y, transaction, remote) {
focusOffset focusOffset
); );
} }
// delete, so the objects can be gc'd
relativeSelection = null;
browserSelection = null;
} }
class YXmlEvent extends YEvent { class YXmlEvent extends YEvent {
@@ -3714,7 +3759,7 @@ function domToYXml (parent, doms, _document) {
} }
if (parent._domFilter(d.nodeName, new Map()) !== null) { if (parent._domFilter(d.nodeName, new Map()) !== null) {
let type; let type;
const hookName = d._yjsHook; const hookName = d._yjsHook || (d.dataset != null ? d.dataset.yjsHook : undefined);
if (hookName !== undefined) { if (hookName !== undefined) {
type = new YXmlHook(hookName, d); type = new YXmlHook(hookName, d);
} else if (d.nodeType === d.TEXT_NODE) { } else if (d.nodeType === d.TEXT_NODE) {
@@ -3752,7 +3797,7 @@ class YXmlTreeWalker {
} }
} }
do { do {
if (!n._deleted && n.constructor === YXmlFragment._YXmlElement && n._start !== null) { if (!n._deleted && (n.constructor === YXmlFragment._YXmlElement || n.constructor === YXmlFragment) && n._start !== null) {
// walk down in the tree // walk down in the tree
n = n._start; n = n._start;
} else { } else {
@@ -4196,6 +4241,11 @@ class YXmlHook extends YMap {
} }
return this._dom return this._dom
} }
_unbindFromDom () {
this._dom._yxml = null;
this._yxml = null;
// TODO: cleanup hook?
}
_fromBinary (y, decoder) { _fromBinary (y, decoder) {
const missing = super._fromBinary(y, decoder); const missing = super._fromBinary(y, decoder);
this.hookName = decoder.readVarString(); this.hookName = decoder.readVarString();

File diff suppressed because one or more lines are too long

1603
y.test.js

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long