Compare commits

..

1 Commits

Author SHA1 Message Date
Kevin Jahns
ed963dca41 v13.0.0-60 -- distribution files 2018-05-08 13:46:43 +02:00
19 changed files with 20421 additions and 6157 deletions

View File

@@ -70,6 +70,7 @@ missing modules.
<script src="https://cdn.jsdelivr.net/npm/y-array@10/dist/y-array.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-websockets-client@8/dist/y-websockets-client.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-memory@8/dist/y-memory.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-array@10/dist/y-array.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-map@10/dist/y-map.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-text@9/dist/y-text.js"></script>
// ..
@@ -88,6 +89,7 @@ var Y = require('yjs')
require('y-array')(Y) // add the y-array type to Yjs
require('y-websockets-client')(Y)
require('y-memory')(Y)
require('y-array')(Y)
require('y-map')(Y)
require('y-text')(Y)
// ..
@@ -100,6 +102,7 @@ import Y from 'yjs'
import yArray from 'y-array'
import yWebsocketsClient from 'y-webrtc'
import yMemory from 'y-memory'
import yArray from 'y-array'
import yMap from 'y-map'
import yText from 'y-text'
// ..

28
package-lock.json generated
View File

@@ -1,21 +1,9 @@
{
"name": "yjs",
"version": "13.0.0-61",
"version": "13.0.0-60",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/estree": {
"version": "0.0.38",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.38.tgz",
"integrity": "sha512-F/v7t1LwS4vnXuPooJQGBRKRGIoxWUTmA4VHfqjOccFsNDThD5bfUNpITive6s352O7o384wcpEaDV8rHCehDA==",
"dev": true
},
"@types/node": {
"version": "6.0.110",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.110.tgz",
"integrity": "sha512-LiaH3mF+OAqR+9Wo1OTJDbZDtCewAVjTbMhF1ZgUJ3fc8xqOJq6VqbpBh9dJVCVzByGmYIg2fREbuXNX0TKiJA==",
"dev": true
},
"abab": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
@@ -1510,7 +1498,6 @@
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
"integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
"dev": true,
"requires": {
"ms": "2.0.0"
}
@@ -4819,8 +4806,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"mute-stream": {
"version": "0.0.5",
@@ -5643,16 +5629,6 @@
"glob": "7.1.2"
}
},
"rollup": {
"version": "0.58.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-0.58.2.tgz",
"integrity": "sha512-RZVvCWm9BHOYloaE6LLiE/ibpjv1CmI8F8k0B0Cp+q1eezo3cswszJH1DN0djgzSlo0hjuuCmyeI+1XOYLl4wg==",
"dev": true,
"requires": {
"@types/estree": "0.0.38",
"@types/node": "6.0.110"
}
},
"rollup-plugin-babel": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-2.7.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "yjs",
"version": "13.0.0-61",
"version": "13.0.0-60",
"description": "A framework for real-time p2p shared editing on any data",
"main": "./y.node.js",
"browser": "./y.js",
@@ -61,7 +61,6 @@
"esdoc-standard-plugin": "^1.0.0",
"quill": "^1.3.5",
"quill-cursors": "^1.0.2",
"rollup": "^0.58.2",
"rollup-plugin-babel": "^2.7.1",
"rollup-plugin-commonjs": "^8.0.2",
"rollup-plugin-inject": "^2.0.0",

View File

@@ -121,11 +121,12 @@ export default class DomBinding extends Binding {
destroy () {
this.domToType = null
this.typeToDom = null
this.type.unobserveDeep(this._typeObserver)
this.type.unobserve(this._typeObserver)
this._mutationObserver.disconnect()
const y = this.type._y
y.off('beforeTransaction', this._beforeTransactionHandler)
y.off('beforeObserverCalls', this._beforeObserverCallsHandler)
y.off('afterObserverCalls', this._afterObserverCallsHandler)
y.off('afterTransaction', this._afterTransactionHandler)
super.destroy()
}

View File

@@ -94,7 +94,7 @@ export default function domObserver (mutations, _document) {
let parent = dom
let yParent
do {
parent = parent.parentElement
parent = parent.parentNode
yParent = this.domToType.get(parent)
} while (yParent === undefined && parent !== null)
if (yParent !== false && yParent !== undefined && yParent.constructor !== YXmlHook) {

View File

@@ -1,7 +1,5 @@
import YXmlText from '../../Types/YXml/YXmlText.js'
import YXmlHook from '../../Types/YXml/YXmlHook.js'
import YXmlElement from '../../Types/YXml/YXmlElement.js'
import { YXmlText, YXmlElement, YXmlHook } from '../../Types/YXml/YXml.js'
import { createAssociation, domsToTypes } from './util.js'
import { filterDomAttributes, defaultFilter } from './filter.js'

View File

@@ -50,6 +50,7 @@ export default function typeObserver (events) {
if (dom !== undefined && dom !== false) {
if (yxml.constructor === YXmlText) {
dom.nodeValue = yxml.toString()
// TODO: use hasOwnProperty instead of === undefined check
} else if (event.attributesChanged !== undefined) {
// update attributes
event.attributesChanged.forEach(attributeName => {

View File

@@ -61,5 +61,5 @@ export function logID (id) {
export function logItemHelper (name, item, append) {
const left = item._left !== null ? item._left._lastId : null
const origin = item._origin !== null ? item._origin._lastId : null
return `${name}(id:${logID(item._id)},left:${logID(left)},origin:${logID(origin)},right:${logID(item._right)},parent:${logID(item._parent)},parentSub:${item._parentSub}${append !== undefined ? ' - ' + append : ''})`
return `${name}(id:${logID(item._id)},start:${logID(item._start)},left:${logID(left)},origin:${logID(origin)},right:${logID(item._right)},parent:${logID(item._parent)},parentSub:${item._parentSub}${append !== undefined ? ' - ' + append : ''})`
}

View File

@@ -155,7 +155,7 @@ function insertAttributes (y, parent, left, right, attributes, currentAttributes
*/
function insertText (y, text, parent, left, right, currentAttributes, attributes) {
for (let [key] of currentAttributes) {
if (attributes[key] === undefined) {
if (attributes.hasOwnProperty(key) === false) {
attributes[key] = null
}
}
@@ -189,9 +189,8 @@ function formatText (y, length, parent, left, right, currentAttributes, attribut
if (right._deleted === false) {
switch (right.constructor) {
case ItemFormat:
const attr = attributes[right.key]
if (attr !== undefined) {
if (attr === right.value) {
if (attributes.hasOwnProperty(right.key)) {
if (attributes[right.key] === right.value) {
negatedAttributes.delete(right.key)
} else {
negatedAttributes.set(right.key, right.value)
@@ -406,9 +405,8 @@ class YTextEvent extends YArrayEvent {
}
} else if (item._deleted === false) {
oldAttributes.set(item.key, item.value)
const attr = attributes[item.key]
if (attr !== undefined) {
if (attr !== item.value) {
if (attributes.hasOwnProperty(item.key)) {
if (attributes[item.key] !== item.value) {
if (action === 'retain') {
addOp()
}
@@ -435,7 +433,7 @@ class YTextEvent extends YArrayEvent {
addOp()
while (this._delta.length > 0) {
let lastOp = this._delta[this._delta.length - 1]
if (lastOp.retain !== undefined && lastOp.attributes === undefined) {
if (lastOp.hasOwnProperty('retain') && !lastOp.hasOwnProperty('attributes')) {
// retain delta's if they don't assign attributes
this._delta.pop()
} else {
@@ -507,11 +505,11 @@ export default class YText extends YArray {
const currentAttributes = new Map()
for (let i = 0; i < delta.length; i++) {
let op = delta[i]
if (op.insert !== undefined) {
if (op.hasOwnProperty('insert')) {
;[left, right] = insertText(y, op.insert, this, left, right, currentAttributes, op.attributes || {})
} else if (op.retain !== undefined) {
} else if (op.hasOwnProperty('retain')) {
;[left, right] = formatText(y, op.retain, this, left, right, currentAttributes, op.attributes || {})
} else if (op.delete !== undefined) {
} else if (op.hasOwnProperty('delete')) {
;[left, right] = deleteText(y, op.delete, this, left, right, currentAttributes)
}
}

12
src/Types/YXml/YXml.js Normal file
View File

@@ -0,0 +1,12 @@
import YXmlFragment from './YXmlFragment.js'
import YXmlElement from './YXmlElement.js'
import YXmlHook from './YXmlHook.js'
export { default as YXmlFragment } from './YXmlFragment.js'
export { default as YXmlElement } from './YXmlElement.js'
export { default as YXmlText } from './YXmlText.js'
export { default as YXmlHook } from './YXmlHook.js'
YXmlFragment._YXmlElement = YXmlElement
YXmlFragment._YXmlHook = YXmlHook

View File

@@ -1,5 +1,5 @@
import YMap from '../YMap/YMap.js'
import YXmlFragment from './YXmlFragment.js'
import { YXmlFragment } from './YXml.js'
import { createAssociation } from '../../Bindings/DomBinding/util.js'
/**
@@ -186,5 +186,3 @@ export default class YXmlElement extends YXmlFragment {
return dom
}
}
YXmlFragment._YXmlElement = YXmlElement

View File

@@ -1,10 +1,7 @@
import YArray from '../Types/YArray/YArray.js'
import YMap from '../Types/YMap/YMap.js'
import YText from '../Types/YText/YText.js'
import YXmlText from '../Types/YXml/YXmlText.js'
import YXmlHook from '../Types/YXml/YXmlHook.js'
import YXmlFragment from '../Types/YXml/YXmlFragment.js'
import YXmlElement from '../Types/YXml/YXmlElement.js'
import { YXmlFragment, YXmlElement, YXmlText, YXmlHook } from '../Types/YXml/YXml.js'
import Delete from '../Struct/Delete.js'
import ItemJSON from '../Struct/ItemJSON.js'

View File

@@ -10,10 +10,7 @@ import Persistence from './Persistence.js'
import YArray from './Types/YArray/YArray.js'
import YMap from './Types/YMap/YMap.js'
import YText from './Types/YText/YText.js'
import YXmlText from './Types/YXml/YXmlText.js'
import YXmlHook from './Types/YXml/YXmlHook.js'
import YXmlFragment from './Types/YXml/YXmlFragment.js'
import YXmlElement from './Types/YXml/YXmlElement.js'
import { YXmlFragment, YXmlElement, YXmlText, YXmlHook } from './Types/YXml/YXml.js'
import BinaryDecoder from './Util/Binary/Decoder.js'
import { getRelativePosition, fromRelativePosition } from './Util/relativePosition.js'
import { registerStruct } from './Util/structReferences.js'

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

779
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-61
* @version v13.0.0-60
* @license MIT
*/
@@ -1341,7 +1341,7 @@ function logID (id) {
return `(${id.user},${id.clock})`
} else if (id instanceof RootID) {
return `(${id.name},${id.type})`
} else if (id.constructor === Y) {
} else if (id.constructor === Y$1) {
return `y`
} else {
throw new Error('This is not a valid ID!')
@@ -1362,7 +1362,7 @@ function logID (id) {
function logItemHelper (name, item, append) {
const left = item._left !== null ? item._left._lastId : null;
const origin = item._origin !== null ? item._origin._lastId : null;
return `${name}(id:${logID(item._id)},left:${logID(left)},origin:${logID(origin)},right:${logID(item._right)},parent:${logID(item._parent)},parentSub:${item._parentSub}${append !== undefined ? ' - ' + append : ''})`
return `${name}(id:${logID(item._id)},start:${logID(item._start)},left:${logID(left)},origin:${logID(origin)},right:${logID(item._right)},parent:${logID(item._parent)},parentSub:${item._parentSub}${append !== undefined ? ' - ' + append : ''})`
}
/**
@@ -1835,7 +1835,9 @@ class Item {
const userState = y.ss.getState(user);
if (selfID === null) {
this._id = y.ss.getNextID(this._length);
} else if (selfID.user === RootFakeUserID) ; else if (selfID.clock < userState) {
} else if (selfID.user === RootFakeUserID) {
// nop
} else if (selfID.clock < userState) {
// already applied..
return []
} else if (selfID.clock === userState) {
@@ -2163,6 +2165,8 @@ function integrateChildren (y, start) {
} while (right !== null)
}
function gcChildren (y, item) {
while (item !== null) {
item._delete(y, false, true);
@@ -3248,7 +3252,9 @@ function minimizeAttributeChanges (left, right, currentAttributes, attributes) {
while (true) {
if (right === null) {
break
} else if (right._deleted === true) ; else if (right.constructor === ItemFormat && (attributes[right.key] || null) === right.value) {
} else if (right._deleted === true) {
// continue
} else if (right.constructor === ItemFormat && (attributes[right.key] || null) === right.value) {
// found a format, update currentAttributes and continue
updateCurrentAttributes(currentAttributes, right);
} else {
@@ -3287,7 +3293,7 @@ function insertAttributes (y, parent, left, right, attributes, currentAttributes
*/
function insertText (y, text, parent, left, right, currentAttributes, attributes) {
for (let [key] of currentAttributes) {
if (attributes[key] === undefined) {
if (attributes.hasOwnProperty(key) === false) {
attributes[key] = null;
}
}
@@ -3321,9 +3327,8 @@ function formatText (y, length, parent, left, right, currentAttributes, attribut
if (right._deleted === false) {
switch (right.constructor) {
case ItemFormat:
const attr = attributes[right.key];
if (attr !== undefined) {
if (attr === right.value) {
if (attributes.hasOwnProperty(right.key)) {
if (attributes[right.key] === right.value) {
negatedAttributes.delete(right.key);
} else {
negatedAttributes.set(right.key, right.value);
@@ -3538,9 +3543,8 @@ class YTextEvent extends YArrayEvent {
}
} else if (item._deleted === false) {
oldAttributes.set(item.key, item.value);
const attr = attributes[item.key];
if (attr !== undefined) {
if (attr !== item.value) {
if (attributes.hasOwnProperty(item.key)) {
if (attributes[item.key] !== item.value) {
if (action === 'retain') {
addOp();
}
@@ -3567,7 +3571,7 @@ class YTextEvent extends YArrayEvent {
addOp();
while (this._delta.length > 0) {
let lastOp = this._delta[this._delta.length - 1];
if (lastOp.retain !== undefined && lastOp.attributes === undefined) {
if (lastOp.hasOwnProperty('retain') && !lastOp.hasOwnProperty('attributes')) {
// retain delta's if they don't assign attributes
this._delta.pop();
} else {
@@ -3639,12 +3643,12 @@ class YText extends YArray {
const currentAttributes = new Map();
for (let i = 0; i < delta.length; i++) {
let op = delta[i];
if (op.insert !== undefined) {
[left, right] = insertText(y, op.insert, this, left, right, currentAttributes, op.attributes || {});
} else if (op.retain !== undefined) {
[left, right] = formatText(y, op.retain, this, left, right, currentAttributes, op.attributes || {});
} else if (op.delete !== undefined) {
[left, right] = deleteText(y, op.delete, this, left, right, currentAttributes);
if (op.hasOwnProperty('insert')) {
[left, right] = insertText(y, op.insert, this, left, right, currentAttributes, op.attributes || {});
} else if (op.hasOwnProperty('retain')) {
[left, right] = formatText(y, op.retain, this, left, right, currentAttributes, op.attributes || {});
} else if (op.hasOwnProperty('delete')) {
[left, right] = deleteText(y, op.delete, this, left, right, currentAttributes);
}
}
});
@@ -3788,108 +3792,258 @@ class YText extends YArray {
}
/**
* You can manage binding to a custom type with YXmlHook.
* Check if `parent` is a parent of `child`.
*
* @param {Type} parent
* @param {Type} child
* @return {Boolean} Whether `parent` is a parent of `child`.
*
* @public
*/
class YXmlHook extends YMap {
/**
* @param {String} hookName nodeName of the Dom Node.
*/
constructor (hookName) {
super();
this.hookName = null;
if (hookName !== undefined) {
this.hookName = hookName;
function isParentOf (parent, child) {
child = child._parent;
while (child !== null) {
if (child === parent) {
return true
}
child = child._parent;
}
return false
}
/**
* Creates an Item with the same effect as this Item (without position effect)
*
* @private
*/
_copy () {
const struct = super._copy();
struct.hookName = this.hookName;
return struct
/**
* Default filter method (does nothing).
*
* @param {String} nodeName The nodeName of the element
* @param {Map} attrs Map of key-value pairs that are attributes of the node.
* @return {Map | null} The allowed attributes or null, if the element should be
* filtered.
*/
function defaultFilter (nodeName, attrs) {
// TODO: implement basic filter that filters out dangerous properties!
return attrs
}
/**
*
*/
function filterDomAttributes (dom, filter) {
const attrs = new Map();
for (let i = dom.attributes.length - 1; i >= 0; i--) {
const attr = dom.attributes[i];
attrs.set(attr.name, attr.value);
}
return filter(dom.nodeName, attrs)
}
/**
* Creates a Dom Element that mirrors this YXmlElement.
*
* @param {Document} [_document=document] The document object (you must define
* this when calling this method in
* nodejs)
* @param {Object<key:hookDefinition>} [hooks] Optional property to customize how hooks
* are presented in the DOM
* @param {DomBinding} [binding] You should not set this property. This is
* used if DomBinding wants to create a
* association to the created DOM type
* @return {Element} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
*
* @public
*/
toDom (_document = document, hooks = {}, binding) {
const hook = hooks[this.hookName];
let dom;
if (hook !== undefined) {
dom = hook.createDom(this);
/**
* Applies a filter on a type.
*
* @param {Y} y The Yjs instance.
* @param {DomBinding} binding The DOM binding instance that has the dom filter.
* @param {YXmlElement | YXmlFragment } type The type to apply the filter to.
*
* @private
*/
function applyFilterOnType (y, binding, type) {
if (isParentOf(binding.type, type)) {
const nodeName = type.nodeName;
let attributes = new Map();
if (type.getAttributes !== undefined) {
let attrs = type.getAttributes();
for (let key in attrs) {
attributes.set(key, attrs[key]);
}
}
const filteredAttributes = binding.filter(nodeName, new Map(attributes));
if (filteredAttributes === null) {
type._delete(y);
} else {
dom = document.createElement(this.hookName);
// iterate original attributes
attributes.forEach((value, key) => {
// delete all attributes that are not in filteredAttributes
if (filteredAttributes.has(key) === false) {
type.removeAttribute(key);
}
});
}
dom.setAttribute('data-yjs-hook', this.hookName);
createAssociation(binding, dom, this);
return dom
}
}
/**
* Read the next Item in a Decoder and fill this Item with the read data.
*
* This is called when data is received from a remote peer.
*
* @param {Y} y The Yjs instance that this Item belongs to.
* @param {BinaryDecoder} decoder The decoder object to read data from.
*
* @private
*/
_fromBinary (y, decoder) {
const missing = super._fromBinary(y, decoder);
this.hookName = decoder.readVarString();
return missing
/**
* Creates a Yjs type (YXml) based on the contents of a DOM Element.
*
* @param {Element|TextNode} element The DOM Element
* @param {?Document} _document Optional. Provide the global document object
* @param {Hooks} [hooks = {}] Optional. Set of Yjs Hooks
* @param {Filter} [filter=defaultFilter] Optional. Dom element filter
* @param {?DomBinding} binding Warning: This property is for internal use only!
* @return {YXmlElement | YXmlText}
*/
function domToType (element, _document = document, hooks = {}, filter = defaultFilter, binding) {
let type;
switch (element.nodeType) {
case _document.ELEMENT_NODE:
let hookName = null;
let hook;
// configure `hookName !== undefined` if element is a hook.
if (element.hasAttribute('data-yjs-hook')) {
hookName = element.getAttribute('data-yjs-hook');
hook = hooks[hookName];
if (hook === undefined) {
console.error(`Unknown hook "${hookName}". Deleting yjsHook dataset property.`);
delete element.removeAttribute('data-yjs-hook');
hookName = null;
}
}
if (hookName === null) {
// Not a hook
const attrs = filterDomAttributes(element, filter);
if (attrs === null) {
type = false;
} else {
type = new YXmlElement(element.nodeName);
attrs.forEach((val, key) => {
type.setAttribute(key, val);
});
type.insert(0, domsToTypes(element.childNodes, document, hooks, filter, binding));
}
} else {
// Is a hook
type = new YXmlHook(hookName);
hook.fillType(element, type);
}
break
case _document.TEXT_NODE:
type = new YXmlText();
type.insert(0, element.nodeValue);
break
default:
throw new Error('Can\'t transform this node type to a YXml type!')
}
createAssociation(binding, element, type);
return type
}
/**
* Transform the properties of this type to binary and write it to an
* BinaryEncoder.
*
* This is called when this Item is sent to a remote peer.
*
* @param {BinaryEncoder} encoder The encoder to write data to.
*
* @private
*/
_toBinary (encoder) {
super._toBinary(encoder);
encoder.writeVarString(this.hookName);
/**
* Iterates items until an undeleted item is found.
*
* @private
*/
function iterateUntilUndeleted (item) {
while (item !== null && item._deleted) {
item = item._right;
}
return item
}
/**
* Integrate this type into the Yjs instance.
*
* * Save this struct in the os
* * This type is sent to other client
* * Observer functions are fired
*
* @param {Y} y The Yjs instance
*
* @private
*/
_integrate (y) {
if (this.hookName === null) {
throw new Error('hookName must be defined!')
/**
* Removes an association (the information that a DOM element belongs to a
* type).
*
* @param {DomBinding} domBinding The binding object
* @param {Element} dom The dom that is to be associated with type
* @param {YXmlElement|YXmlHook} type The type that is to be associated with dom
*
*/
function removeAssociation (domBinding, dom, type) {
domBinding.domToType.delete(dom);
domBinding.typeToDom.delete(type);
}
/**
* Creates an association (the information that a DOM element belongs to a
* type).
*
* @param {DomBinding} domBinding The binding object
* @param {Element} dom The dom that is to be associated with type
* @param {YXmlElement|YXmlHook} type The type that is to be associated with dom
*
*/
function createAssociation (domBinding, dom, type) {
if (domBinding !== undefined) {
domBinding.domToType.set(dom, type);
domBinding.typeToDom.set(type, dom);
}
}
/**
* If oldDom is associated with a type, associate newDom with the type and
* forget about oldDom. If oldDom is not associated with any type, nothing happens.
*
* @param {DomBinding} domBinding The binding object
* @param {Element} oldDom The existing dom
* @param {Element} newDom The new dom object
*/
function switchAssociation (domBinding, oldDom, newDom) {
if (domBinding !== undefined) {
const type = domBinding.domToType.get(oldDom);
if (type !== undefined) {
removeAssociation(domBinding, oldDom, type);
createAssociation(domBinding, newDom, type);
}
super._integrate(y);
}
}
/**
* Insert Dom Elements after one of the children of this YXmlFragment.
* The Dom elements will be bound to a new YXmlElement and inserted at the
* specified position.
*
* @param {YXmlElement} type The type in which to insert DOM elements.
* @param {YXmlElement|null} prev The reference node. New YxmlElements are
* inserted after this node. Set null to insert at
* the beginning.
* @param {Array<Element>} doms The Dom elements to insert.
* @param {?Document} _document Optional. Provide the global document object.
* @param {DomBinding} binding The dom binding
* @return {Array<YXmlElement>} The YxmlElements that are inserted.
*
* @private
*/
function insertDomElementsAfter (type, prev, doms, _document, binding) {
const types = domsToTypes(doms, _document, binding.opts.hooks, binding.filter, binding);
return type.insertAfter(prev, types)
}
function domsToTypes (doms, _document, hooks, filter, binding) {
const types = [];
for (let dom of doms) {
const t = domToType(dom, _document, hooks, filter, binding);
if (t !== false) {
types.push(t);
}
}
return types
}
/**
* @private
*/
function insertNodeHelper (yxml, prevExpectedNode, child, _document, binding) {
let insertedNodes = insertDomElementsAfter(yxml, prevExpectedNode, [child], _document, binding);
if (insertedNodes.length > 0) {
return insertedNodes[0]
} else {
return prevExpectedNode
}
}
/**
* Remove children until `elem` is found.
*
* @param {Element} parent The parent of `elem` and `currentChild`.
* @param {Element} currentChild Start removing elements with `currentChild`. If
* `currentChild` is `elem` it won't be removed.
* @param {Element|null} elem The elemnt to look for.
*
* @private
*/
function removeDomChildrenUntilElementFound (parent, currentChild, elem) {
while (currentChild !== elem) {
const del = currentChild;
currentChild = currentChild.nextSibling;
parent.removeChild(del);
}
}
@@ -4360,261 +4514,109 @@ class YXmlElement extends YXmlFragment {
}
}
YXmlFragment._YXmlElement = YXmlElement;
/**
* Check if `parent` is a parent of `child`.
*
* @param {Type} parent
* @param {Type} child
* @return {Boolean} Whether `parent` is a parent of `child`.
* You can manage binding to a custom type with YXmlHook.
*
* @public
*/
function isParentOf (parent, child) {
child = child._parent;
while (child !== null) {
if (child === parent) {
return true
class YXmlHook extends YMap {
/**
* @param {String} hookName nodeName of the Dom Node.
*/
constructor (hookName) {
super();
this.hookName = null;
if (hookName !== undefined) {
this.hookName = hookName;
}
child = child._parent;
}
return false
}
/**
* Default filter method (does nothing).
*
* @param {String} nodeName The nodeName of the element
* @param {Map} attrs Map of key-value pairs that are attributes of the node.
* @return {Map | null} The allowed attributes or null, if the element should be
* filtered.
*/
function defaultFilter (nodeName, attrs) {
// TODO: implement basic filter that filters out dangerous properties!
return attrs
}
/**
*
*/
function filterDomAttributes (dom, filter) {
const attrs = new Map();
for (let i = dom.attributes.length - 1; i >= 0; i--) {
const attr = dom.attributes[i];
attrs.set(attr.name, attr.value);
/**
* Creates an Item with the same effect as this Item (without position effect)
*
* @private
*/
_copy () {
const struct = super._copy();
struct.hookName = this.hookName;
return struct
}
return filter(dom.nodeName, attrs)
}
/**
* Applies a filter on a type.
*
* @param {Y} y The Yjs instance.
* @param {DomBinding} binding The DOM binding instance that has the dom filter.
* @param {YXmlElement | YXmlFragment } type The type to apply the filter to.
*
* @private
*/
function applyFilterOnType (y, binding, type) {
if (isParentOf(binding.type, type)) {
const nodeName = type.nodeName;
let attributes = new Map();
if (type.getAttributes !== undefined) {
let attrs = type.getAttributes();
for (let key in attrs) {
attributes.set(key, attrs[key]);
}
}
const filteredAttributes = binding.filter(nodeName, new Map(attributes));
if (filteredAttributes === null) {
type._delete(y);
/**
* Creates a Dom Element that mirrors this YXmlElement.
*
* @param {Document} [_document=document] The document object (you must define
* this when calling this method in
* nodejs)
* @param {Object<key:hookDefinition>} [hooks] Optional property to customize how hooks
* are presented in the DOM
* @param {DomBinding} [binding] You should not set this property. This is
* used if DomBinding wants to create a
* association to the created DOM type
* @return {Element} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
*
* @public
*/
toDom (_document = document, hooks = {}, binding) {
const hook = hooks[this.hookName];
let dom;
if (hook !== undefined) {
dom = hook.createDom(this);
} else {
// iterate original attributes
attributes.forEach((value, key) => {
// delete all attributes that are not in filteredAttributes
if (filteredAttributes.has(key) === false) {
type.removeAttribute(key);
}
});
dom = document.createElement(this.hookName);
}
dom.setAttribute('data-yjs-hook', this.hookName);
createAssociation(binding, dom, this);
return dom
}
}
/**
* Creates a Yjs type (YXml) based on the contents of a DOM Element.
*
* @param {Element|TextNode} element The DOM Element
* @param {?Document} _document Optional. Provide the global document object
* @param {Hooks} [hooks = {}] Optional. Set of Yjs Hooks
* @param {Filter} [filter=defaultFilter] Optional. Dom element filter
* @param {?DomBinding} binding Warning: This property is for internal use only!
* @return {YXmlElement | YXmlText}
*/
function domToType (element, _document = document, hooks = {}, filter = defaultFilter, binding) {
let type;
switch (element.nodeType) {
case _document.ELEMENT_NODE:
let hookName = null;
let hook;
// configure `hookName !== undefined` if element is a hook.
if (element.hasAttribute('data-yjs-hook')) {
hookName = element.getAttribute('data-yjs-hook');
hook = hooks[hookName];
if (hook === undefined) {
console.error(`Unknown hook "${hookName}". Deleting yjsHook dataset property.`);
delete element.removeAttribute('data-yjs-hook');
hookName = null;
}
}
if (hookName === null) {
// Not a hook
const attrs = filterDomAttributes(element, filter);
if (attrs === null) {
type = false;
} else {
type = new YXmlElement(element.nodeName);
attrs.forEach((val, key) => {
type.setAttribute(key, val);
});
type.insert(0, domsToTypes(element.childNodes, document, hooks, filter, binding));
}
} else {
// Is a hook
type = new YXmlHook(hookName);
hook.fillType(element, type);
}
break
case _document.TEXT_NODE:
type = new YXmlText();
type.insert(0, element.nodeValue);
break
default:
throw new Error('Can\'t transform this node type to a YXml type!')
/**
* Read the next Item in a Decoder and fill this Item with the read data.
*
* This is called when data is received from a remote peer.
*
* @param {Y} y The Yjs instance that this Item belongs to.
* @param {BinaryDecoder} decoder The decoder object to read data from.
*
* @private
*/
_fromBinary (y, decoder) {
const missing = super._fromBinary(y, decoder);
this.hookName = decoder.readVarString();
return missing
}
createAssociation(binding, element, type);
return type
}
/**
* Iterates items until an undeleted item is found.
*
* @private
*/
function iterateUntilUndeleted (item) {
while (item !== null && item._deleted) {
item = item._right;
/**
* Transform the properties of this type to binary and write it to an
* BinaryEncoder.
*
* This is called when this Item is sent to a remote peer.
*
* @param {BinaryEncoder} encoder The encoder to write data to.
*
* @private
*/
_toBinary (encoder) {
super._toBinary(encoder);
encoder.writeVarString(this.hookName);
}
return item
}
/**
* Removes an association (the information that a DOM element belongs to a
* type).
*
* @param {DomBinding} domBinding The binding object
* @param {Element} dom The dom that is to be associated with type
* @param {YXmlElement|YXmlHook} type The type that is to be associated with dom
*
*/
function removeAssociation (domBinding, dom, type) {
domBinding.domToType.delete(dom);
domBinding.typeToDom.delete(type);
}
/**
* Creates an association (the information that a DOM element belongs to a
* type).
*
* @param {DomBinding} domBinding The binding object
* @param {Element} dom The dom that is to be associated with type
* @param {YXmlElement|YXmlHook} type The type that is to be associated with dom
*
*/
function createAssociation (domBinding, dom, type) {
if (domBinding !== undefined) {
domBinding.domToType.set(dom, type);
domBinding.typeToDom.set(type, dom);
}
}
/**
* If oldDom is associated with a type, associate newDom with the type and
* forget about oldDom. If oldDom is not associated with any type, nothing happens.
*
* @param {DomBinding} domBinding The binding object
* @param {Element} oldDom The existing dom
* @param {Element} newDom The new dom object
*/
function switchAssociation (domBinding, oldDom, newDom) {
if (domBinding !== undefined) {
const type = domBinding.domToType.get(oldDom);
if (type !== undefined) {
removeAssociation(domBinding, oldDom, type);
createAssociation(domBinding, newDom, type);
/**
* Integrate this type into the Yjs instance.
*
* * Save this struct in the os
* * This type is sent to other client
* * Observer functions are fired
*
* @param {Y} y The Yjs instance
*
* @private
*/
_integrate (y) {
if (this.hookName === null) {
throw new Error('hookName must be defined!')
}
}
}
/**
* Insert Dom Elements after one of the children of this YXmlFragment.
* The Dom elements will be bound to a new YXmlElement and inserted at the
* specified position.
*
* @param {YXmlElement} type The type in which to insert DOM elements.
* @param {YXmlElement|null} prev The reference node. New YxmlElements are
* inserted after this node. Set null to insert at
* the beginning.
* @param {Array<Element>} doms The Dom elements to insert.
* @param {?Document} _document Optional. Provide the global document object.
* @param {DomBinding} binding The dom binding
* @return {Array<YXmlElement>} The YxmlElements that are inserted.
*
* @private
*/
function insertDomElementsAfter (type, prev, doms, _document, binding) {
const types = domsToTypes(doms, _document, binding.opts.hooks, binding.filter, binding);
return type.insertAfter(prev, types)
}
function domsToTypes (doms, _document, hooks, filter, binding) {
const types = [];
for (let dom of doms) {
const t = domToType(dom, _document, hooks, filter, binding);
if (t !== false) {
types.push(t);
}
}
return types
}
/**
* @private
*/
function insertNodeHelper (yxml, prevExpectedNode, child, _document, binding) {
let insertedNodes = insertDomElementsAfter(yxml, prevExpectedNode, [child], _document, binding);
if (insertedNodes.length > 0) {
return insertedNodes[0]
} else {
return prevExpectedNode
}
}
/**
* Remove children until `elem` is found.
*
* @param {Element} parent The parent of `elem` and `currentChild`.
* @param {Element} currentChild Start removing elements with `currentChild`. If
* `currentChild` is `elem` it won't be removed.
* @param {Element|null} elem The elemnt to look for.
*
* @private
*/
function removeDomChildrenUntilElementFound (parent, currentChild, elem) {
while (currentChild !== elem) {
const del = currentChild;
currentChild = currentChild.nextSibling;
parent.removeChild(del);
super._integrate(y);
}
}
@@ -4662,6 +4664,9 @@ class YXmlText extends YText {
}
}
YXmlFragment._YXmlElement = YXmlElement;
YXmlFragment._YXmlHook = YXmlHook;
const structs = new Map();
const references = new Map();
@@ -5310,6 +5315,7 @@ function typeObserver (events) {
if (dom !== undefined && dom !== false) {
if (yxml.constructor === YXmlText) {
dom.nodeValue = yxml.toString();
// TODO: use hasOwnProperty instead of === undefined check
} else if (event.attributesChanged !== undefined) {
// update attributes
event.attributesChanged.forEach(attributeName => {
@@ -5492,7 +5498,7 @@ function domObserver (mutations, _document) {
let parent = dom;
let yParent;
do {
parent = parent.parentElement;
parent = parent.parentNode;
yParent = this.domToType.get(parent);
} while (yParent === undefined && parent !== null)
if (yParent !== false && yParent !== undefined && yParent.constructor !== YXmlHook) {
@@ -5657,11 +5663,12 @@ class DomBinding extends Binding {
destroy () {
this.domToType = null;
this.typeToDom = null;
this.type.unobserveDeep(this._typeObserver);
this.type.unobserve(this._typeObserver);
this._mutationObserver.disconnect();
const y = this.type._y;
y.off('beforeTransaction', this._beforeTransactionHandler);
y.off('beforeObserverCalls', this._beforeObserverCallsHandler);
y.off('afterObserverCalls', this._afterObserverCallsHandler);
y.off('afterTransaction', this._afterTransactionHandler);
super.destroy();
}
@@ -5694,7 +5701,7 @@ class DomBinding extends Binding {
* @param {Object} opts Connector definition
* @param {AbstractPersistence} persistence Persistence adapter instance
*/
class Y extends NamedEventHandler {
class Y$1 extends NamedEventHandler {
constructor (room, opts, persistence, conf = {}) {
super();
this.gcEnabled = conf.gc || false;
@@ -5729,7 +5736,7 @@ class Y extends NamedEventHandler {
this.connected = false;
let initConnection = () => {
if (opts != null) {
this.connector = new Y[opts.connector.name](this, opts.connector);
this.connector = new Y$1[opts.connector.name](this, opts.connector);
this.connected = true;
this.emit('connectorReady');
}
@@ -5937,11 +5944,11 @@ class Y extends NamedEventHandler {
}
}
Y.extend = function extendYjs () {
Y$1.extend = function extendYjs () {
for (var i = 0; i < arguments.length; i++) {
var f = arguments[i];
if (typeof f === 'function') {
f(Y);
f(Y$1);
} else {
throw new Error('Expected a function!')
}
@@ -6235,14 +6242,7 @@ function plural(ms, n, name) {
return Math.ceil(ms / n) + ' ' + name + 's';
}
var ms$1 = /*#__PURE__*/Object.freeze({
default: ms,
__moduleExports: ms
});
var require$$0 = ( ms$1 && ms ) || ms$1;
var debug = createCommonjsModule(function (module, exports) {
var debug$1 = createCommonjsModule(function (module, exports) {
/**
* This is the common logic for both the Node.js and web browser
* implementations of `debug()`.
@@ -6255,7 +6255,7 @@ exports.coerce = coerce;
exports.disable = disable;
exports.enable = enable;
exports.enabled = enabled;
exports.humanize = require$$0;
exports.humanize = ms;
/**
* The currently active debug mode names, and names to skip.
@@ -6314,8 +6314,8 @@ function createDebug(namespace) {
// set `diff` timestamp
var curr = +new Date();
var ms = curr - (prevTime || curr);
self.diff = ms;
var ms$$1 = curr - (prevTime || curr);
self.diff = ms$$1;
self.prev = prevTime;
self.curr = curr;
prevTime = curr;
@@ -6445,29 +6445,15 @@ function coerce(val) {
return val;
}
});
var debug_1 = debug.coerce;
var debug_2 = debug.disable;
var debug_3 = debug.enable;
var debug_4 = debug.enabled;
var debug_5 = debug.humanize;
var debug_6 = debug.names;
var debug_7 = debug.skips;
var debug_8 = debug.formatters;
var debug$1 = /*#__PURE__*/Object.freeze({
default: debug,
__moduleExports: debug,
coerce: debug_1,
disable: debug_2,
enable: debug_3,
enabled: debug_4,
humanize: debug_5,
names: debug_6,
skips: debug_7,
formatters: debug_8
});
var require$$0$1 = ( debug$1 && debug ) || debug$1;
var debug_1 = debug$1.coerce;
var debug_2 = debug$1.disable;
var debug_3 = debug$1.enable;
var debug_4 = debug$1.enabled;
var debug_5 = debug$1.humanize;
var debug_6 = debug$1.names;
var debug_7 = debug$1.skips;
var debug_8 = debug$1.formatters;
var browser = createCommonjsModule(function (module, exports) {
/**
@@ -6476,7 +6462,7 @@ var browser = createCommonjsModule(function (module, exports) {
* Expose `debug()` as the module.
*/
exports = module.exports = require$$0$1;
exports = module.exports = debug$1;
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
@@ -6656,6 +6642,7 @@ function localstorage() {
} catch (e) {}
}
});
var browser_1 = browser.log;
var browser_2 = browser.formatArgs;
var browser_3 = browser.save;
@@ -7202,25 +7189,25 @@ class QuillBinding extends Binding {
}
// TODO: The following assignments should be moved to yjs-dist
Y.AbstractConnector = AbstractConnector;
Y.AbstractPersistence = AbstractPersistence;
Y.Array = YArray;
Y.Map = YMap;
Y.Text = YText;
Y.XmlElement = YXmlElement;
Y.XmlFragment = YXmlFragment;
Y.XmlText = YXmlText;
Y.XmlHook = YXmlHook;
Y$1.AbstractConnector = AbstractConnector;
Y$1.AbstractPersistence = AbstractPersistence;
Y$1.Array = YArray;
Y$1.Map = YMap;
Y$1.Text = YText;
Y$1.XmlElement = YXmlElement;
Y$1.XmlFragment = YXmlFragment;
Y$1.XmlText = YXmlText;
Y$1.XmlHook = YXmlHook;
Y.TextareaBinding = TextareaBinding;
Y.QuillBinding = QuillBinding;
Y.DomBinding = DomBinding;
Y$1.TextareaBinding = TextareaBinding;
Y$1.QuillBinding = QuillBinding;
Y$1.DomBinding = DomBinding;
DomBinding.domToType = domToType;
DomBinding.domsToTypes = domsToTypes;
DomBinding.switchAssociation = switchAssociation;
Y.utils = {
Y$1.utils = {
BinaryDecoder,
UndoManager,
getRelativePosition,
@@ -7231,9 +7218,9 @@ Y.utils = {
fromBinary
};
Y.debug = browser;
Y$1.debug = browser;
browser.formatters.Y = messageToString;
browser.formatters.y = messageToRoomname;
module.exports = Y;
module.exports = Y$1;
//# sourceMappingURL=y.node.js.map

File diff suppressed because one or more lines are too long

25693
y.test.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long