Compare commits

..

1 Commits

Author SHA1 Message Date
Kevin Jahns
dc1fe7957b v13.0.0-39 -- distribution files 2017-12-05 17:06:12 -08:00
19 changed files with 944 additions and 1176 deletions

View File

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

View File

@@ -1,35 +0,0 @@
<!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

@@ -1,134 +0,0 @@
/* 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-42", "version": "13.0.0-39",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.0-42", "version": "13.0.0-39",
"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/*.tests.js', input: 'test/y-xml.tests.js',
name: 'y-tests', name: 'y-tests',
sourcemap: true, sourcemap: true,
output: { output: {

View File

@@ -5,38 +5,15 @@ 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, transaction) { constructor (yarray, remote) {
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, transaction)) this._callEventHandler(transaction, new YArrayEvent(this, remote))
} }
get (pos) { get (pos) {
let n = this._start let n = this._start
@@ -207,12 +184,8 @@ export default class YArray extends Type {
prevJsonIns._content.push(c) prevJsonIns._content.push(c)
} }
} }
if (prevJsonIns !== null) { if (prevJsonIns !== null && y !== null) {
if (y !== null) { prevJsonIns._integrate(y)
prevJsonIns._integrate(y)
} else if (prevJsonIns._left === null) {
this._start = prevJsonIns
}
} }
}) })
} }
@@ -241,17 +214,6 @@ 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

@@ -55,7 +55,7 @@ class YXmlTreeWalker {
} }
} }
do { do {
if (!n._deleted && (n.constructor === YXmlFragment._YXmlElement || n.constructor === YXmlFragment) && n._start !== null) { if (!n._deleted && n.constructor === YXmlFragment._YXmlElement && n._start !== null) {
// walk down in the tree // walk down in the tree
n = n._start n = n._start
} else { } else {
@@ -254,7 +254,9 @@ export default class YXmlFragment extends YArray {
}) })
// Apply Y.Xml events to dom // Apply Y.Xml events to dom
this.observeDeep(events => { this.observeDeep(events => {
reflectChangesOnDom.call(this, events, _document) this._mutualExclude(() => {
reflectChangesOnDom.call(this, events, _document)
})
}) })
// Apply Dom changes on Y.Xml // Apply Dom changes on Y.Xml
if (typeof MutationObserver !== 'undefined') { if (typeof MutationObserver !== 'undefined') {

View File

@@ -24,11 +24,6 @@ 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,6 +68,7 @@ export function afterTransactionSelectionFixer (y, transaction, remote) {
} }
} }
if (shouldUpdate) { if (shouldUpdate) {
console.info('updating selection!!')
browserSelection.setBaseAndExtent( browserSelection.setBaseAndExtent(
anchorNode, anchorNode,
anchorOffset, anchorOffset,

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 if (event.attributesChanged !== undefined) { } else {
// update attributes // update attributes
event.attributesChanged.forEach(attributeName => { event.attributesChanged.forEach(attributeName => {
const value = yxml.getAttribute(attributeName) const value = yxml.getAttribute(attributeName)
@@ -193,27 +193,19 @@ 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) {
let currentChild = dom.firstChild // create fragment of undeleted nodes
const fragment = _document.createDocumentFragment()
yxml.forEach(function (t) { yxml.forEach(function (t) {
let expectedChild = t.getDom(_document) fragment.appendChild(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)
}
}) })
while (currentChild !== null) { // remove remainding nodes
let tmp = currentChild.nextSibling let lastChild = dom.lastChild
dom.removeChild(currentChild) while (lastChild !== null) {
currentChild = tmp dom.removeChild(lastChild)
lastChild = dom.lastChild
} }
// insert fragment of undeleted nodes
dom.appendChild(fragment)
} }
} }
/* TODO: smartscrolling /* TODO: smartscrolling

View File

@@ -280,6 +280,7 @@ 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
@@ -339,6 +340,7 @@ 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

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-42 * @version v13.0.0-39
* @license MIT * @license MIT
*/ */
@@ -2204,38 +2204,15 @@ class YEvent {
} }
class YArrayEvent extends YEvent { class YArrayEvent extends YEvent {
constructor (yarray, remote, transaction) { constructor (yarray, remote) {
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, transaction)); this._callEventHandler(transaction, new YArrayEvent(this, remote));
} }
get (pos) { get (pos) {
let n = this._start; let n = this._start;
@@ -2406,12 +2383,8 @@ class YArray extends Type {
prevJsonIns._content.push(c); prevJsonIns._content.push(c);
} }
} }
if (prevJsonIns !== null) { if (prevJsonIns !== null && y !== null) {
if (y !== null) { prevJsonIns._integrate(y);
prevJsonIns._integrate(y);
} else if (prevJsonIns._left === null) {
this._start = prevJsonIns;
}
} }
}); });
} }
@@ -2440,17 +2413,6 @@ 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;
@@ -2764,7 +2726,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 if (event.attributesChanged !== undefined) { } else {
// update attributes // update attributes
event.attributesChanged.forEach(attributeName => { event.attributesChanged.forEach(attributeName => {
const value = yxml.getAttribute(attributeName); const value = yxml.getAttribute(attributeName);
@@ -2783,27 +2745,19 @@ 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) {
let currentChild = dom.firstChild; // create fragment of undeleted nodes
const fragment = _document.createDocumentFragment();
yxml.forEach(function (t) { yxml.forEach(function (t) {
let expectedChild = t.getDom(_document); fragment.appendChild(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);
}
}); });
while (currentChild !== null) { // remove remainding nodes
let tmp = currentChild.nextSibling; let lastChild = dom.lastChild;
dom.removeChild(currentChild); while (lastChild !== null) {
currentChild = tmp; dom.removeChild(lastChild);
lastChild = dom.lastChild;
} }
// insert fragment of undeleted nodes
dom.appendChild(fragment);
} }
} }
/* TODO: smartscrolling /* TODO: smartscrolling
@@ -2985,6 +2939,7 @@ function afterTransactionSelectionFixer (y, transaction, remote) {
} }
} }
if (shouldUpdate) { if (shouldUpdate) {
console.info('updating selection!!');
browserSelection.setBaseAndExtent( browserSelection.setBaseAndExtent(
anchorNode, anchorNode,
anchorOffset, anchorOffset,
@@ -3797,7 +3752,7 @@ class YXmlTreeWalker {
} }
} }
do { do {
if (!n._deleted && (n.constructor === YXmlFragment._YXmlElement || n.constructor === YXmlFragment) && n._start !== null) { if (!n._deleted && n.constructor === YXmlFragment._YXmlElement && n._start !== null) {
// walk down in the tree // walk down in the tree
n = n._start; n = n._start;
} else { } else {
@@ -3996,7 +3951,9 @@ class YXmlFragment extends YArray {
}); });
// Apply Y.Xml events to dom // Apply Y.Xml events to dom
this.observeDeep(events => { this.observeDeep(events => {
reflectChangesOnDom.call(this, events, _document); this._mutualExclude(() => {
reflectChangesOnDom.call(this, events, _document);
});
}); });
// Apply Dom changes on Y.Xml // Apply Dom changes on Y.Xml
if (typeof MutationObserver !== 'undefined') { if (typeof MutationObserver !== 'undefined') {
@@ -4241,11 +4198,6 @@ 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