Compare commits

..

10 Commits

Author SHA1 Message Date
Kevin Jahns
b1192fbb46 v13.0.0-32 -- distribution files 2017-11-14 21:31:19 -08:00
Kevin Jahns
2e3240b379 13.0.0-32 2017-11-14 21:31:11 -08:00
Kevin Jahns
2558652356 fix attribute filter (it used to filter everything) 2017-11-14 21:19:39 -08:00
Kevin Jahns
783cbd63fc 13.0.0-31 2017-11-14 20:44:12 -08:00
Kevin Jahns
41be80e751 fix y-xml server environment 2017-11-14 20:43:30 -08:00
Kevin Jahns
3d6050d8a2 13.0.0-30 2017-11-12 13:37:37 -08:00
Kevin Jahns
3d5ba7b4cc fix the case that a new transaction starts in an event listener (afterTransaction, observe, observeDeep) 2017-11-12 13:37:06 -08:00
Kevin Jahns
415b66607c fixed filtering 2017-11-10 19:04:00 -08:00
Kevin Jahns
05cd1d0575 13.0.0-29 2017-11-10 18:46:10 -08:00
Kevin Jahns
4edc22bedb remove prematurely commited dom-filter update 2017-11-10 18:45:41 -08:00
23 changed files with 841 additions and 890 deletions

View File

@@ -1,4 +1,15 @@
/* global Y */
/* global Y, HTMLElement, customElements */
class MagicTable extends HTMLElement {
constructor () {
super()
var shadow = this.attachShadow({mode: 'open'})
setTimeout(() => {
shadow.append(this.childNodes[0])
}, 1000)
}
}
customElements.define('magic-table', MagicTable)
// initialize a shared object. This function call returns a promise!
let y = new Y({
@@ -17,17 +28,14 @@ window.onload = function () {
window.yXmlType.bindToDom(document.body)
}
window.undoManager = new Y.utils.UndoManager(window.yXmlType, {
captureTimeout: 0
captureTimeout: 500
})
document.onkeydown = function interceptUndoRedo (e) {
if (e.keyCode === 90 && e.metaKey) {
console.log('uidtaren')
if (!e.shiftKey) {
console.info('Undo!')
window.undoManager.undo()
} else {
console.info('Redo!')
window.undoManager.redo()
}
e.preventDefault()

2
package-lock.json generated
View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "yjs",
"version": "13.0.0-28",
"version": "13.0.0-32",
"description": "A framework for real-time p2p shared editing on any data",
"main": "./y.node.js",
"browser": "./y.js",

View File

@@ -5,9 +5,13 @@ import commonjs from 'rollup-plugin-commonjs'
var pkg = require('./package.json')
export default {
entry: 'src/Y.js',
moduleName: 'Y',
format: 'umd',
input: 'src/Y.js',
name: 'Y',
sourcemap: true,
output: {
file: 'y.js',
format: 'umd'
},
plugins: [
nodeResolve({
main: true,
@@ -32,8 +36,6 @@ export default {
}
})
],
dest: 'y.js',
sourceMap: true,
banner: `
/**
* ${pkg.name} - ${pkg.description}

View File

@@ -3,9 +3,13 @@ import commonjs from 'rollup-plugin-commonjs'
var pkg = require('./package.json')
export default {
entry: 'src/y-dist.cjs.js',
moduleName: 'Y',
format: 'cjs',
input: 'src/y-dist.cjs.js',
nameame: 'Y',
sourcemap: true,
output: {
file: 'y.node.js',
format: 'cjs'
},
plugins: [
nodeResolve({
main: true,
@@ -14,8 +18,6 @@ export default {
}),
commonjs()
],
dest: 'y.node.js',
sourceMap: true,
banner: `
/**
* ${pkg.name} - ${pkg.description}

View File

@@ -3,9 +3,13 @@ import commonjs from 'rollup-plugin-commonjs'
import multiEntry from 'rollup-plugin-multi-entry'
export default {
entry: 'test/y-map.tests.js',
moduleName: 'y-tests',
format: 'umd',
input: 'test/y-xml.tests.js',
name: 'y-tests',
sourcemap: true,
output: {
file: 'y.test.js',
format: 'umd'
},
plugins: [
nodeResolve({
main: true,
@@ -14,7 +18,5 @@ export default {
}),
commonjs(),
multiEntry()
],
dest: 'y.test.js',
sourceMap: true
]
}

View File

@@ -65,9 +65,9 @@ export default class Type extends Item {
}
return path
}
_callEventHandler (event) {
const changedParentTypes = this._y._transaction.changedParentTypes
this._eventHandler.callEventListeners(event)
_callEventHandler (transaction, event) {
const changedParentTypes = transaction.changedParentTypes
this._eventHandler.callEventListeners(transaction, event)
let type = this
while (type !== this._y) {
let events = changedParentTypes.get(type)

View File

@@ -12,8 +12,8 @@ class YArrayEvent extends YEvent {
}
export default class YArray extends Type {
_callObserver (parentSubs, remote) {
this._callEventHandler(new YArrayEvent(this, remote))
_callObserver (transaction, parentSubs, remote) {
this._callEventHandler(transaction, new YArrayEvent(this, remote))
}
get (pos) {
let n = this._start

View File

@@ -13,8 +13,8 @@ class YMapEvent extends YEvent {
}
export default class YMap extends Type {
_callObserver (parentSubs, remote) {
this._callEventHandler(new YMapEvent(this, parentSubs, remote))
_callObserver (transaction, parentSubs, remote) {
this._callEventHandler(transaction, new YMapEvent(this, parentSubs, remote))
}
toJSON () {
const map = {}

View File

@@ -5,7 +5,7 @@ import YMap from '../YMap.js'
import YXmlFragment from './YXmlFragment.js'
export default class YXmlElement extends YXmlFragment {
constructor (arg1, arg2) {
constructor (arg1, arg2, _document) {
super()
this.nodeName = null
this._scrollElement = null
@@ -13,7 +13,7 @@ export default class YXmlElement extends YXmlFragment {
this.nodeName = arg1.toUpperCase()
} else if (arg1 != null && arg1.nodeType != null && arg1.nodeType === arg1.ELEMENT_NODE) {
this.nodeName = arg1.nodeName
this._setDom(arg1)
this._setDom(arg1, _document)
} else {
this.nodeName = 'UNDEFINED'
}
@@ -26,14 +26,12 @@ export default class YXmlElement extends YXmlFragment {
struct.nodeName = this.nodeName
return struct
}
_setDom (dom) {
_setDom (dom, _document) {
if (this._dom != null) {
throw new Error('Only call this method if you know what you are doing ;)')
} else if (dom._yxml != null) { // TODO do i need to check this? - no.. but for dev purps..
throw new Error('Already bound to an YXml type')
} else {
this._dom = dom
dom._yxml = this
// tag is already set in constructor
// set attributes
let attrNames = []
@@ -46,8 +44,8 @@ export default class YXmlElement extends YXmlFragment {
let attrValue = dom.getAttribute(attrName)
this.setAttribute(attrName, attrValue)
}
this.insertDomElements(0, Array.prototype.slice.call(dom.childNodes))
this._bindToDom(dom)
this.insertDomElements(0, Array.prototype.slice.call(dom.childNodes), _document)
this._bindToDom(dom, _document)
return dom
}
}
@@ -115,7 +113,6 @@ export default class YXmlElement extends YXmlFragment {
let dom = this._dom
if (dom == null) {
dom = _document.createElement(this.nodeName)
this._dom = dom
dom._yxml = this
let attrs = this.getAttributes()
for (let key in attrs) {
@@ -124,7 +121,7 @@ export default class YXmlElement extends YXmlFragment {
this.forEach(yxml => {
dom.appendChild(yxml.getDom(_document))
})
this._bindToDom(dom)
this._bindToDom(dom, _document)
}
return dom
}

View File

@@ -9,7 +9,7 @@ import YXmlEvent from './YXmlEvent.js'
import { logID } from '../../MessageHandler/messageToString.js'
import diff from 'fast-diff'
function domToYXml (parent, doms) {
function domToYXml (parent, doms, _document) {
const types = []
doms.forEach(d => {
if (d._yxml != null && d._yxml !== false) {
@@ -20,7 +20,7 @@ function domToYXml (parent, doms) {
if (d.nodeType === d.TEXT_NODE) {
type = new YXmlText(d)
} else if (d.nodeType === d.ELEMENT_NODE) {
type = new YXmlFragment._YXmlElement(d, parent._domFilter)
type = new YXmlFragment._YXmlElement(d, parent._domFilter, _document)
} else {
throw new Error('Unsupported node!')
}
@@ -98,7 +98,9 @@ export default class YXmlFragment extends YArray {
} catch (e) {
console.error(e)
}
this._domObserver.takeRecords()
if (this._domObserver !== null) {
this._domObserver.takeRecords()
}
token = true
}
}
@@ -142,8 +144,8 @@ export default class YXmlFragment extends YArray {
xml.setDomFilter(f)
})
}
_callObserver (parentSubs, remote) {
this._callEventHandler(new YXmlEvent(this, parentSubs, remote))
_callObserver (transaction, parentSubs, remote) {
this._callEventHandler(transaction, new YXmlEvent(this, parentSubs, remote))
}
toString () {
return this.map(xml => xml.toString()).join('')
@@ -162,113 +164,116 @@ export default class YXmlFragment extends YArray {
this._dom = null
}
}
insertDomElementsAfter (prev, doms) {
const types = domToYXml(this, doms)
insertDomElementsAfter (prev, doms, _document) {
const types = domToYXml(this, doms, _document)
this.insertAfter(prev, types)
return types
}
insertDomElements (pos, doms) {
const types = domToYXml(this, doms)
insertDomElements (pos, doms, _document) {
const types = domToYXml(this, doms, _document)
this.insert(pos, types)
return types
}
getDom () {
return this._dom
}
bindToDom (dom) {
bindToDom (dom, _document) {
if (this._dom != null) {
this._unbindFromDom()
}
if (dom._yxml != null) {
dom._yxml._unbindFromDom()
}
if (MutationObserver == null) {
throw new Error('Not able to bind to a DOM element, because MutationObserver is not available!')
}
dom.innerHTML = ''
this._dom = dom
dom._yxml = this
this.forEach(t => {
dom.insertBefore(t.getDom(), null)
dom.insertBefore(t.getDom(_document), null)
})
this._bindToDom(dom)
this._bindToDom(dom, _document)
}
// binds to a dom element
// Only call if dom and YXml are isomorph
_bindToDom (dom) {
if (this._parent === null || this._parent._dom != null || typeof MutationObserver === 'undefined') {
// only bind if parent did not already bind
_bindToDom (dom, _document) {
_document = _document || document
this._dom = dom
dom._yxml = this
// TODO: refine this..
if ((this.constructor !== YXmlFragment && this._parent !== this._y) || this._parent === null) {
// TODO: only top level YXmlFragment can bind. Also allow YXmlElements..
return
}
this._y.on('beforeTransaction', () => {
this._domObserverListener(this._domObserver.takeRecords())
})
this._y.on('beforeTransaction', beforeTransactionSelectionFixer)
this._y.on('afterTransaction', afterTransactionSelectionFixer)
// Apply Y.Xml events to dom
this.observeDeep(reflectChangesOnDom.bind(this))
this.observeDeep(events => {
reflectChangesOnDom.call(this, events, _document)
})
// Apply Dom changes on Y.Xml
this._domObserverListener = mutations => {
this._mutualExclude(() => {
this._y.transact(() => {
let diffChildren = new Set()
mutations.forEach(mutation => {
const dom = mutation.target
const yxml = dom._yxml
if (yxml == null) {
// dom element is filtered
return
}
switch (mutation.type) {
case 'characterData':
var diffs = diff(yxml.toString(), dom.nodeValue)
var pos = 0
for (var i = 0; i < diffs.length; i++) {
var d = diffs[i]
if (d[0] === 0) { // EQUAL
pos += d[1].length
} else if (d[0] === -1) { // DELETE
yxml.delete(pos, d[1].length)
} else { // INSERT
yxml.insert(pos, d[1])
pos += d[1].length
}
}
break
case 'attributes':
let name = mutation.attributeName
// check if filter accepts attribute
if (this._domFilter(dom, [name]).length > 0 && this.constructor !== YXmlFragment) {
var val = dom.getAttribute(name)
if (yxml.getAttribute(name) !== val) {
if (val == null) {
yxml.removeAttribute(name)
} else {
yxml.setAttribute(name, val)
if (typeof MutationObserver !== 'undefined') {
this._y.on('beforeTransaction', () => {
this._domObserverListener(this._domObserver.takeRecords())
})
this._domObserverListener = mutations => {
this._mutualExclude(() => {
this._y.transact(() => {
let diffChildren = new Set()
mutations.forEach(mutation => {
const dom = mutation.target
const yxml = dom._yxml
if (yxml == null) {
// dom element is filtered
return
}
switch (mutation.type) {
case 'characterData':
var diffs = diff(yxml.toString(), dom.nodeValue)
var pos = 0
for (var i = 0; i < diffs.length; i++) {
var d = diffs[i]
if (d[0] === 0) { // EQUAL
pos += d[1].length
} else if (d[0] === -1) { // DELETE
yxml.delete(pos, d[1].length)
} else { // INSERT
yxml.insert(pos, d[1])
pos += d[1].length
}
}
}
break
case 'childList':
diffChildren.add(mutation.target)
break
break
case 'attributes':
let name = mutation.attributeName
// check if filter accepts attribute
if (this._domFilter(dom, [name]).length > 0 && yxml.constructor !== YXmlFragment) {
var val = dom.getAttribute(name)
if (yxml.getAttribute(name) !== val) {
if (val == null) {
yxml.removeAttribute(name)
} else {
yxml.setAttribute(name, val)
}
}
}
break
case 'childList':
diffChildren.add(mutation.target)
break
}
})
for (let dom of diffChildren) {
if (dom._yxml != null && dom._yxml !== false) {
applyChangesFromDom(dom)
}
}
})
for (let dom of diffChildren) {
if (dom._yxml != null && dom._yxml !== false) {
applyChangesFromDom(dom)
}
}
})
}
this._domObserver = new MutationObserver(this._domObserverListener)
this._domObserver.observe(dom, {
childList: true,
attributes: true,
characterData: true,
subtree: true
})
}
this._domObserver = new MutationObserver(this._domObserverListener)
this._domObserver.observe(dom, {
childList: true,
attributes: true,
characterData: true,
subtree: true
})
return dom
}
_logString () {

View File

@@ -7,7 +7,7 @@ let relativeSelection = null
export let beforeTransactionSelectionFixer
if (typeof getSelection !== 'undefined') {
beforeTransactionSelectionFixer = function _beforeTransactionSelectionFixer (y, remote) {
beforeTransactionSelectionFixer = function _beforeTransactionSelectionFixer (y, transaction, remote) {
if (!remote) {
return
}
@@ -30,7 +30,7 @@ if (typeof getSelection !== 'undefined') {
beforeTransactionSelectionFixer = function _fakeBeforeTransactionSelectionFixer () {}
}
export function afterTransactionSelectionFixer (y, remote) {
export function afterTransactionSelectionFixer (y, transaction, remote) {
if (relativeSelection === null || !remote) {
return
}

View File

@@ -135,17 +135,22 @@ export function applyChangesFromDom (dom) {
}
}
export function reflectChangesOnDom (events) {
export function reflectChangesOnDom (events, _document) {
// Make sure that no filtered attributes are applied to the structure
// if they were, delete them
/*
events.forEach(event => {
const target = event.target
const keys = this._domFilter(target.nodeName, Array.from(event.keysChanged))
if (event.attributesChanged === undefined) {
// event.target is Y.XmlText
return
}
const keys = this._domFilter(target.nodeName, Array.from(event.attributesChanged))
if (keys === null) {
target._delete()
} else {
const removeKeys = new Set() // is a copy of event.keysChanged
event.keysChanged.forEach(key => { removeKeys.add(key) })
const removeKeys = new Set() // is a copy of event.attributesChanged
event.attributesChanged.forEach(key => { removeKeys.add(key) })
keys.forEach(key => {
// remove all accepted keys from removeKeys
removeKeys.delete(key)
@@ -156,6 +161,7 @@ export function reflectChangesOnDom (events) {
})
}
})
*/
this._mutualExclude(() => {
events.forEach(event => {
const yxml = event.target
@@ -177,9 +183,9 @@ export function reflectChangesOnDom (events) {
})
if (event.childListChanged) {
// create fragment of undeleted nodes
const fragment = document.createDocumentFragment()
const fragment = _document.createDocumentFragment()
yxml.forEach(function (t) {
fragment.append(t.getDom())
fragment.appendChild(t.getDom(_document))
})
// remove remainding nodes
let lastChild = dom.lastChild
@@ -188,7 +194,7 @@ export function reflectChangesOnDom (events) {
lastChild = dom.lastChild
}
// insert fragment of undeleted nodes
dom.append(fragment)
dom.appendChild(fragment)
}
}
/* TODO: smartscrolling

View File

@@ -17,7 +17,7 @@ export default class EventHandler {
removeAllEventListeners () {
this.eventListeners = []
}
callEventListeners (event) {
callEventListeners (transaction, event) {
for (var i = 0; i < this.eventListeners.length; i++) {
try {
const f = this.eventListeners[i]

View File

@@ -1,16 +1,16 @@
import ID from './ID.js'
class ReverseOperation {
constructor (y) {
constructor (y, transaction) {
this.created = new Date()
const beforeState = y._transaction.beforeState
const beforeState = transaction.beforeState
this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1)
if (beforeState.has(y.userID)) {
this.fromState = new ID(y.userID, beforeState.get(y.userID))
} else {
this.fromState = this.toState
}
this.deletedStructs = y._transaction.deletedStructs
this.deletedStructs = transaction.deletedStructs
}
}
@@ -70,9 +70,9 @@ export default class UndoManager {
this._redoing = false
const y = scope._y
this.y = y
y.on('afterTransaction', (y, remote) => {
if (!remote && y._transaction.changedParentTypes.has(scope)) {
let reverseOperation = new ReverseOperation(y)
y.on('afterTransaction', (y, transaction, remote) => {
if (!remote && transaction.changedParentTypes.has(scope)) {
let reverseOperation = new ReverseOperation(y, transaction)
if (!this._undoing) {
let lastUndoOp = this._undoBuffer.length > 0 ? this._undoBuffer[this._undoBuffer.length - 1] : null
if (lastUndoOp !== null && reverseOperation.created - lastUndoOp.created <= options.captureTimeout) {

View File

@@ -15,6 +15,7 @@ import YMap from './Type/YMap.js'
import YText from './Type/YText.js'
import { YXmlFragment, YXmlElement, YXmlText } from './Type/y-xml/y-xml.js'
import BinaryDecoder from './Binary/Decoder.js'
import { getRelativePosition, fromRelativePosition } from './Util/relativePosition.js'
import debug from 'debug'
import Transaction from './Transaction.js'
@@ -44,8 +45,8 @@ export default class Y extends NamedEventHandler {
transact (f, remote = false) {
let initialCall = this._transaction === null
if (initialCall) {
this.emit('beforeTransaction', this, remote)
this._transaction = new Transaction(this)
this.emit('beforeTransaction', this, this._transaction, remote)
}
try {
f(this)
@@ -53,13 +54,15 @@ export default class Y extends NamedEventHandler {
console.error(e)
}
if (initialCall) {
const transaction = this._transaction
this._transaction = null
// emit change events on changed types
this._transaction.changedTypes.forEach(function (subs, type) {
transaction.changedTypes.forEach(function (subs, type) {
if (!type._deleted) {
type._callObserver(subs, remote)
type._callObserver(transaction, subs, remote)
}
})
this._transaction.changedParentTypes.forEach(function (events, type) {
transaction.changedParentTypes.forEach(function (events, type) {
if (!type._deleted) {
events = events
.filter(event =>
@@ -71,12 +74,11 @@ export default class Y extends NamedEventHandler {
})
// we don't have to check for events.length
// because there is no way events is empty..
type._deepEventHandler.callEventListeners(events)
type._deepEventHandler.callEventListeners(transaction, events)
}
})
// when all changes & events are processed, emit afterTransaction event
this.emit('afterTransaction', this, remote)
this._transaction = null
this.emit('afterTransaction', this, transaction, remote)
}
}
// fake _start for root properties (y.set('name', type))
@@ -168,7 +170,9 @@ Y.XmlText = YXmlText
Y.utils = {
BinaryDecoder,
UndoManager
UndoManager,
getRelativePosition,
fromRelativePosition
}
Y.debug = debug

View File

@@ -153,7 +153,7 @@ export async function initArrays (t, opts) {
result['array' + i] = y.define('array', Y.Array)
result['map' + i] = y.define('map', Y.Map)
result['xml' + i] = y.define('xml', Y.XmlElement)
y.get('xml', Y.Xml).setDomFilter(function (d, attrs) {
y.get('xml').setDomFilter(function (d, attrs) {
if (d.nodeName === 'HIDDEN') {
return null
} else {

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

265
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-28
* @version v13.0.0-32
* @license MIT
*/
@@ -1899,7 +1899,7 @@ class EventHandler {
removeAllEventListeners () {
this.eventListeners = [];
}
callEventListeners (event) {
callEventListeners (transaction, event) {
for (var i = 0; i < this.eventListeners.length; i++) {
try {
const f = this.eventListeners[i];
@@ -1966,9 +1966,9 @@ class Type extends Item {
}
return path
}
_callEventHandler (event) {
const changedParentTypes = this._y._transaction.changedParentTypes;
this._eventHandler.callEventListeners(event);
_callEventHandler (transaction, event) {
const changedParentTypes = transaction.changedParentTypes;
this._eventHandler.callEventListeners(transaction, event);
let type = this;
while (type !== this._y) {
let events = changedParentTypes.get(type);
@@ -2211,8 +2211,8 @@ class YArrayEvent extends YEvent {
}
class YArray extends Type {
_callObserver (parentSubs, remote) {
this._callEventHandler(new YArrayEvent(this, remote));
_callObserver (transaction, parentSubs, remote) {
this._callEventHandler(transaction, new YArrayEvent(this, remote));
}
get (pos) {
let n = this._start;
@@ -2429,8 +2429,8 @@ class YMapEvent extends YEvent {
}
class YMap extends Type {
_callObserver (parentSubs, remote) {
this._callEventHandler(new YMapEvent(this, parentSubs, remote));
_callObserver (transaction, parentSubs, remote) {
this._callEventHandler(transaction, new YMapEvent(this, parentSubs, remote));
}
toJSON () {
const map = {};
@@ -2776,27 +2776,33 @@ function applyChangesFromDom (dom) {
}
}
function reflectChangesOnDom (events) {
function reflectChangesOnDom (events, _document) {
// Make sure that no filtered attributes are applied to the structure
// if they were, delete them
/*
events.forEach(event => {
const target = event.target;
const keys = this._domFilter(target.nodeName, Array.from(event.keysChanged));
const target = event.target
if (event.attributesChanged === undefined) {
// event.target is Y.XmlText
return
}
const keys = this._domFilter(target.nodeName, Array.from(event.attributesChanged))
if (keys === null) {
target._delete();
target._delete()
} else {
const removeKeys = new Set(); // is a copy of event.keysChanged
event.keysChanged.forEach(key => { removeKeys.add(key); });
const removeKeys = new Set() // is a copy of event.attributesChanged
event.attributesChanged.forEach(key => { removeKeys.add(key) })
keys.forEach(key => {
// remove all accepted keys from removeKeys
removeKeys.delete(key);
});
removeKeys.delete(key)
})
// remove the filtered attribute
removeKeys.forEach(key => {
target.removeAttribute(key);
});
target.removeAttribute(key)
})
}
});
})
*/
this._mutualExclude(() => {
events.forEach(event => {
const yxml = event.target;
@@ -2818,9 +2824,9 @@ function reflectChangesOnDom (events) {
});
if (event.childListChanged) {
// create fragment of undeleted nodes
const fragment = document.createDocumentFragment();
const fragment = _document.createDocumentFragment();
yxml.forEach(function (t) {
fragment.append(t.getDom());
fragment.appendChild(t.getDom(_document));
});
// remove remainding nodes
let lastChild = dom.lastChild;
@@ -2829,7 +2835,7 @@ function reflectChangesOnDom (events) {
lastChild = dom.lastChild;
}
// insert fragment of undeleted nodes
dom.append(fragment);
dom.appendChild(fragment);
}
}
/* TODO: smartscrolling
@@ -2950,7 +2956,7 @@ let relativeSelection = null;
let beforeTransactionSelectionFixer;
if (typeof getSelection !== 'undefined') {
beforeTransactionSelectionFixer = function _beforeTransactionSelectionFixer (y, remote) {
beforeTransactionSelectionFixer = function _beforeTransactionSelectionFixer (y, transaction, remote) {
if (!remote) {
return
}
@@ -2973,7 +2979,7 @@ if (typeof getSelection !== 'undefined') {
beforeTransactionSelectionFixer = function _fakeBeforeTransactionSelectionFixer () {};
}
function afterTransactionSelectionFixer (y, remote) {
function afterTransactionSelectionFixer (y, transaction, remote) {
if (relativeSelection === null || !remote) {
return
}
@@ -3772,7 +3778,7 @@ function merge_tuples (diffs, start, length) {
/* global MutationObserver */
function domToYXml (parent, doms) {
function domToYXml (parent, doms, _document) {
const types = [];
doms.forEach(d => {
if (d._yxml != null && d._yxml !== false) {
@@ -3783,7 +3789,7 @@ function domToYXml (parent, doms) {
if (d.nodeType === d.TEXT_NODE) {
type = new YXmlText(d);
} else if (d.nodeType === d.ELEMENT_NODE) {
type = new YXmlFragment._YXmlElement(d, parent._domFilter);
type = new YXmlFragment._YXmlElement(d, parent._domFilter, _document);
} else {
throw new Error('Unsupported node!')
}
@@ -3861,7 +3867,9 @@ class YXmlFragment extends YArray {
} catch (e) {
console.error(e);
}
this._domObserver.takeRecords();
if (this._domObserver !== null) {
this._domObserver.takeRecords();
}
token = true;
}
};
@@ -3905,8 +3913,8 @@ class YXmlFragment extends YArray {
xml.setDomFilter(f);
});
}
_callObserver (parentSubs, remote) {
this._callEventHandler(new YXmlEvent(this, parentSubs, remote));
_callObserver (transaction, parentSubs, remote) {
this._callEventHandler(transaction, new YXmlEvent(this, parentSubs, remote));
}
toString () {
return this.map(xml => xml.toString()).join('')
@@ -3925,113 +3933,116 @@ class YXmlFragment extends YArray {
this._dom = null;
}
}
insertDomElementsAfter (prev, doms) {
const types = domToYXml(this, doms);
insertDomElementsAfter (prev, doms, _document) {
const types = domToYXml(this, doms, _document);
this.insertAfter(prev, types);
return types
}
insertDomElements (pos, doms) {
const types = domToYXml(this, doms);
insertDomElements (pos, doms, _document) {
const types = domToYXml(this, doms, _document);
this.insert(pos, types);
return types
}
getDom () {
return this._dom
}
bindToDom (dom) {
bindToDom (dom, _document) {
if (this._dom != null) {
this._unbindFromDom();
}
if (dom._yxml != null) {
dom._yxml._unbindFromDom();
}
if (MutationObserver == null) {
throw new Error('Not able to bind to a DOM element, because MutationObserver is not available!')
}
dom.innerHTML = '';
this._dom = dom;
dom._yxml = this;
this.forEach(t => {
dom.insertBefore(t.getDom(), null);
dom.insertBefore(t.getDom(_document), null);
});
this._bindToDom(dom);
this._bindToDom(dom, _document);
}
// binds to a dom element
// Only call if dom and YXml are isomorph
_bindToDom (dom) {
if (this._parent === null || this._parent._dom != null || typeof MutationObserver === 'undefined') {
// only bind if parent did not already bind
_bindToDom (dom, _document) {
_document = _document || document;
this._dom = dom;
dom._yxml = this;
// TODO: refine this..
if ((this.constructor !== YXmlFragment && this._parent !== this._y) || this._parent === null) {
// TODO: only top level YXmlFragment can bind. Also allow YXmlElements..
return
}
this._y.on('beforeTransaction', () => {
this._domObserverListener(this._domObserver.takeRecords());
});
this._y.on('beforeTransaction', beforeTransactionSelectionFixer);
this._y.on('afterTransaction', afterTransactionSelectionFixer);
// Apply Y.Xml events to dom
this.observeDeep(reflectChangesOnDom.bind(this));
this.observeDeep(events => {
reflectChangesOnDom.call(this, events, _document);
});
// Apply Dom changes on Y.Xml
this._domObserverListener = mutations => {
this._mutualExclude(() => {
this._y.transact(() => {
let diffChildren = new Set();
mutations.forEach(mutation => {
const dom = mutation.target;
const yxml = dom._yxml;
if (yxml == null) {
// dom element is filtered
return
}
switch (mutation.type) {
case 'characterData':
var diffs = diff_1(yxml.toString(), dom.nodeValue);
var pos = 0;
for (var i = 0; i < diffs.length; i++) {
var d = diffs[i];
if (d[0] === 0) { // EQUAL
pos += d[1].length;
} else if (d[0] === -1) { // DELETE
yxml.delete(pos, d[1].length);
} else { // INSERT
yxml.insert(pos, d[1]);
pos += d[1].length;
}
}
break
case 'attributes':
let name = mutation.attributeName;
// check if filter accepts attribute
if (this._domFilter(dom, [name]).length > 0 && this.constructor !== YXmlFragment) {
var val = dom.getAttribute(name);
if (yxml.getAttribute(name) !== val) {
if (val == null) {
yxml.removeAttribute(name);
} else {
yxml.setAttribute(name, val);
if (typeof MutationObserver !== 'undefined') {
this._y.on('beforeTransaction', () => {
this._domObserverListener(this._domObserver.takeRecords());
});
this._domObserverListener = mutations => {
this._mutualExclude(() => {
this._y.transact(() => {
let diffChildren = new Set();
mutations.forEach(mutation => {
const dom = mutation.target;
const yxml = dom._yxml;
if (yxml == null) {
// dom element is filtered
return
}
switch (mutation.type) {
case 'characterData':
var diffs = diff_1(yxml.toString(), dom.nodeValue);
var pos = 0;
for (var i = 0; i < diffs.length; i++) {
var d = diffs[i];
if (d[0] === 0) { // EQUAL
pos += d[1].length;
} else if (d[0] === -1) { // DELETE
yxml.delete(pos, d[1].length);
} else { // INSERT
yxml.insert(pos, d[1]);
pos += d[1].length;
}
}
}
break
case 'childList':
diffChildren.add(mutation.target);
break
break
case 'attributes':
let name = mutation.attributeName;
// check if filter accepts attribute
if (this._domFilter(dom, [name]).length > 0 && yxml.constructor !== YXmlFragment) {
var val = dom.getAttribute(name);
if (yxml.getAttribute(name) !== val) {
if (val == null) {
yxml.removeAttribute(name);
} else {
yxml.setAttribute(name, val);
}
}
}
break
case 'childList':
diffChildren.add(mutation.target);
break
}
});
for (let dom of diffChildren) {
if (dom._yxml != null && dom._yxml !== false) {
applyChangesFromDom(dom);
}
}
});
for (let dom of diffChildren) {
if (dom._yxml != null && dom._yxml !== false) {
applyChangesFromDom(dom);
}
}
});
};
this._domObserver = new MutationObserver(this._domObserverListener);
this._domObserver.observe(dom, {
childList: true,
attributes: true,
characterData: true,
subtree: true
});
};
this._domObserver = new MutationObserver(this._domObserverListener);
this._domObserver.observe(dom, {
childList: true,
attributes: true,
characterData: true,
subtree: true
});
}
return dom
}
_logString () {
@@ -4043,7 +4054,7 @@ class YXmlFragment extends YArray {
// import diff from 'fast-diff'
class YXmlElement extends YXmlFragment {
constructor (arg1, arg2) {
constructor (arg1, arg2, _document) {
super();
this.nodeName = null;
this._scrollElement = null;
@@ -4051,7 +4062,7 @@ class YXmlElement extends YXmlFragment {
this.nodeName = arg1.toUpperCase();
} else if (arg1 != null && arg1.nodeType != null && arg1.nodeType === arg1.ELEMENT_NODE) {
this.nodeName = arg1.nodeName;
this._setDom(arg1);
this._setDom(arg1, _document);
} else {
this.nodeName = 'UNDEFINED';
}
@@ -4064,14 +4075,12 @@ class YXmlElement extends YXmlFragment {
struct.nodeName = this.nodeName;
return struct
}
_setDom (dom) {
_setDom (dom, _document) {
if (this._dom != null) {
throw new Error('Only call this method if you know what you are doing ;)')
} else if (dom._yxml != null) { // TODO do i need to check this? - no.. but for dev purps..
throw new Error('Already bound to an YXml type')
} else {
this._dom = dom;
dom._yxml = this;
// tag is already set in constructor
// set attributes
let attrNames = [];
@@ -4084,8 +4093,8 @@ class YXmlElement extends YXmlFragment {
let attrValue = dom.getAttribute(attrName);
this.setAttribute(attrName, attrValue);
}
this.insertDomElements(0, Array.prototype.slice.call(dom.childNodes));
this._bindToDom(dom);
this.insertDomElements(0, Array.prototype.slice.call(dom.childNodes), _document);
this._bindToDom(dom, _document);
return dom
}
}
@@ -4153,7 +4162,6 @@ class YXmlElement extends YXmlFragment {
let dom = this._dom;
if (dom == null) {
dom = _document.createElement(this.nodeName);
this._dom = dom;
dom._yxml = this;
let attrs = this.getAttributes();
for (let key in attrs) {
@@ -4162,7 +4170,7 @@ class YXmlElement extends YXmlFragment {
this.forEach(yxml => {
dom.appendChild(yxml.getDom(_document));
});
this._bindToDom(dom);
this._bindToDom(dom, _document);
}
return dom
}
@@ -4403,16 +4411,16 @@ class NamedEventHandler {
}
class ReverseOperation {
constructor (y) {
constructor (y, transaction) {
this.created = new Date();
const beforeState = y._transaction.beforeState;
const beforeState = transaction.beforeState;
this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1);
if (beforeState.has(y.userID)) {
this.fromState = new ID(y.userID, beforeState.get(y.userID));
} else {
this.fromState = this.toState;
}
this.deletedStructs = y._transaction.deletedStructs;
this.deletedStructs = transaction.deletedStructs;
}
}
@@ -4472,9 +4480,9 @@ class UndoManager {
this._redoing = false;
const y = scope._y;
this.y = y;
y.on('afterTransaction', (y, remote) => {
if (!remote && y._transaction.changedParentTypes.has(scope)) {
let reverseOperation = new ReverseOperation(y);
y.on('afterTransaction', (y, transaction, remote) => {
if (!remote && transaction.changedParentTypes.has(scope)) {
let reverseOperation = new ReverseOperation(y, transaction);
if (!this._undoing) {
let lastUndoOp = this._undoBuffer.length > 0 ? this._undoBuffer[this._undoBuffer.length - 1] : null;
if (lastUndoOp !== null && reverseOperation.created - lastUndoOp.created <= options.captureTimeout) {
@@ -5430,8 +5438,8 @@ class Y$1 extends NamedEventHandler {
transact (f, remote = false) {
let initialCall = this._transaction === null;
if (initialCall) {
this.emit('beforeTransaction', this, remote);
this._transaction = new Transaction(this);
this.emit('beforeTransaction', this, this._transaction, remote);
}
try {
f(this);
@@ -5439,13 +5447,15 @@ class Y$1 extends NamedEventHandler {
console.error(e);
}
if (initialCall) {
const transaction = this._transaction;
this._transaction = null;
// emit change events on changed types
this._transaction.changedTypes.forEach(function (subs, type) {
transaction.changedTypes.forEach(function (subs, type) {
if (!type._deleted) {
type._callObserver(subs, remote);
type._callObserver(transaction, subs, remote);
}
});
this._transaction.changedParentTypes.forEach(function (events, type) {
transaction.changedParentTypes.forEach(function (events, type) {
if (!type._deleted) {
events = events
.filter(event =>
@@ -5457,12 +5467,11 @@ class Y$1 extends NamedEventHandler {
});
// we don't have to check for events.length
// because there is no way events is empty..
type._deepEventHandler.callEventListeners(events);
type._deepEventHandler.callEventListeners(transaction, events);
}
});
// when all changes & events are processed, emit afterTransaction event
this.emit('afterTransaction', this, remote);
this._transaction = null;
this.emit('afterTransaction', this, transaction, remote);
}
}
// fake _start for root properties (y.set('name', type))
@@ -5554,7 +5563,9 @@ Y$1.XmlText = YXmlText;
Y$1.utils = {
BinaryDecoder,
UndoManager
UndoManager,
getRelativePosition,
fromRelativePosition
};
Y$1.debug = browser;

File diff suppressed because one or more lines are too long

1142
y.test.js

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long