Compare commits

..

6 Commits

Author SHA1 Message Date
Kevin Jahns
ed963dca41 v13.0.0-60 -- distribution files 2018-05-08 13:46:43 +02:00
Kevin Jahns
e56457a0ef 13.0.0-60 2018-05-08 13:46:27 +02:00
Kevin Jahns
ca13849828 fix domBinding infinite loop 2018-05-08 13:45:51 +02:00
Kevin Jahns
92c2fbd6d3 remove debug dependency 2018-05-07 11:52:37 +02:00
Kevin Jahns
65b8921f05 13.0.0-59 2018-05-07 11:45:39 +02:00
Kevin Jahns
1ace7f4b73 fix parentNode binding in case parent doesn't fire MO 2018-05-07 11:44:22 +02:00
9 changed files with 282 additions and 55 deletions

2
package-lock.json generated
View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.0-58", "version": "13.0.0-60",
"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",
@@ -71,8 +71,5 @@
"rollup-watch": "^3.2.2", "rollup-watch": "^3.2.2",
"standard": "^10.0.2", "standard": "^10.0.2",
"tag-dist-files": "^0.1.6" "tag-dist-files": "^0.1.6"
},
"dependencies": {
"debug": "^2.6.8"
} }
} }

View File

@@ -90,21 +90,20 @@ export default function domObserver (mutations, _document) {
mutations.forEach(mutation => { mutations.forEach(mutation => {
const dom = mutation.target const dom = mutation.target
const yxml = this.domToType.get(dom) const yxml = this.domToType.get(dom)
if (yxml === false || yxml === undefined || yxml.constructor === YXmlHook) { if (yxml === undefined) { // In case yxml is undefined, we double check if we forgot to bind the dom
// dom element is filtered let parent = dom
if (yxml === undefined) { // In case yxml is undefined, we double check if we forgot to bind the dom let yParent
console.error('Yjs DomBinding: Reconstructing DomBinding, please report how to reproduce this.') do {
let parent parent = parent.parentNode
let yParent yParent = this.domToType.get(parent)
do { } while (yParent === undefined && parent !== null)
parent = dom.parentNode if (yParent !== false && yParent !== undefined && yParent.constructor !== YXmlHook) {
yParent = this.domToType.get(parent) diffChildren.add(parent)
} while (yParent === undefined)
if (yParent !== false && yParent !== undefined && yParent.constructor !== YXmlHook) {
diffChildren.add(parent)
}
} }
return return
} else if (yxml === false || yxml.constructor === YXmlHook) {
// dom element is filtered / a dom hook
return
} }
switch (mutation.type) { switch (mutation.type) {
case 'characterData': case 'characterData':

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-58 * @version v13.0.0-60
* @license MIT * @license MIT
*/ */
@@ -5494,21 +5494,20 @@ function domObserver (mutations, _document) {
mutations.forEach(mutation => { mutations.forEach(mutation => {
const dom = mutation.target; const dom = mutation.target;
const yxml = this.domToType.get(dom); const yxml = this.domToType.get(dom);
if (yxml === false || yxml === undefined || yxml.constructor === YXmlHook) { if (yxml === undefined) { // In case yxml is undefined, we double check if we forgot to bind the dom
// dom element is filtered let parent = dom;
if (yxml === undefined) { // In case yxml is undefined, we double check if we forgot to bind the dom let yParent;
console.error('Yjs DomBinding: Reconstructing DomBinding, please report how to reproduce this.'); do {
let parent; parent = parent.parentNode;
let yParent; yParent = this.domToType.get(parent);
do { } while (yParent === undefined && parent !== null)
parent = dom.parentNode; if (yParent !== false && yParent !== undefined && yParent.constructor !== YXmlHook) {
yParent = this.domToType.get(parent); diffChildren.add(parent);
} while (yParent === undefined)
if (yParent !== false && yParent !== undefined && yParent.constructor !== YXmlHook) {
diffChildren.add(parent);
}
} }
return return
} else if (yxml === false || yxml.constructor === YXmlHook) {
// dom element is filtered / a dom hook
return
} }
switch (mutation.type) { switch (mutation.type) {
case 'characterData': case 'characterData':

File diff suppressed because one or more lines are too long

264
y.test.js
View File

@@ -13552,9 +13552,6 @@ function test (testDescription, ...args) {
testHandler.register(testCase); testHandler.register(testCase);
} }
//# sourceMappingURL=cutest.mjs.map
proxyConsole(); proxyConsole();
var numberOfRBTreeTests = 10000; var numberOfRBTreeTests = 10000;
@@ -13830,6 +13827,9 @@ class DeleteStore extends Tree {
} }
} }
/**
* A BinaryDecoder handles the decoding of an ArrayBuffer.
*/
class BinaryDecoder { class BinaryDecoder {
/** /**
* @param {Uint8Array|Buffer} buffer The binary data that this instance * @param {Uint8Array|Buffer} buffer The binary data that this instance
@@ -13976,6 +13976,7 @@ class BinaryDecoder {
} }
} }
// TODO should have the same base class as Item
class GC { class GC {
constructor () { constructor () {
this._id = null; this._id = null;
@@ -14612,6 +14613,11 @@ function logItemHelper (name, item, 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 : ''})` 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 : ''})`
} }
/**
* @private
* Delete all items in an ID-range
* TODO: implement getItemCleanStartNode for better performance (only one lookup)
*/
function deleteItemRange (y, user, clock, range, gcChildren) { function deleteItemRange (y, user, clock, range, gcChildren) {
const createDelete = y.connector !== null && y.connector._forwardAppliedStructs; const createDelete = y.connector !== null && y.connector._forwardAppliedStructs;
let item = y.os.getItemCleanStart(new ID(user, clock)); let item = y.os.getItemCleanStart(new ID(user, clock));
@@ -14804,6 +14810,14 @@ function transactionTypeChanged (y, type, sub) {
} }
} }
/**
* @private
* Helper utility to split an Item (see {@link Item#_splitAt})
* - copies all properties from a to b
* - connects a to b
* - assigns the correct _id
* - saves b to os
*/
function splitHelper (y, a, b, diff) { function splitHelper (y, a, b, diff) {
const aID = a._id; const aID = a._id;
b._id = new ID(aID.user, aID.clock + diff); b._id = new ID(aID.user, aID.clock + diff);
@@ -15386,6 +15400,7 @@ class EventHandler {
} }
} }
// restructure children as if they were inserted one after another
function integrateChildren (y, start) { function integrateChildren (y, start) {
let right; let right;
do { do {
@@ -15759,6 +15774,13 @@ class YEvent {
} }
} }
/**
* Event that describes the changes on a YArray
*
* @param {YArray} yarray The changed type
* @param {Boolean} remote Whether the changed was caused by a remote peer
* @param {Transaction} transaction The transaction object
*/
class YArrayEvent extends YEvent { class YArrayEvent extends YEvent {
constructor (yarray, remote, transaction) { constructor (yarray, remote, transaction) {
super(yarray); super(yarray);
@@ -16125,6 +16147,13 @@ class YArray extends Type {
} }
} }
/**
* Event that describes the changes on a YMap.
*
* @param {YMap} ymap The YArray that changed.
* @param {Set<any>} subs The keys that changed.
* @param {boolean} remote Whether the change was created by a remote peer.
*/
class YMapEvent extends YEvent { class YMapEvent extends YEvent {
constructor (ymap, subs, remote) { constructor (ymap, subs, remote) {
super(ymap); super(ymap);
@@ -16361,6 +16390,9 @@ class ItemFormat extends Item {
} }
} }
/**
* @private
*/
function integrateItem (item, parent, y, left, right) { function integrateItem (item, parent, y, left, right) {
item._origin = left; item._origin = left;
item._left = left; item._left = left;
@@ -17027,6 +17059,14 @@ function isParentOf (parent, child) {
return false 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) { function defaultFilter (nodeName, attrs) {
// TODO: implement basic filter that filters out dangerous properties! // TODO: implement basic filter that filters out dangerous properties!
return attrs return attrs
@@ -17078,6 +17118,16 @@ function applyFilterOnType (y, binding, type) {
} }
} }
/**
* 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) { function domToType (element, _document = document, hooks = {}, filter = defaultFilter, binding) {
let type; let type;
switch (element.nodeType) { switch (element.nodeType) {
@@ -17123,6 +17173,11 @@ function domToType (element, _document = document, hooks = {}, filter = defaultF
return type return type
} }
/**
* Iterates items until an undeleted item is found.
*
* @private
*/
function iterateUntilUndeleted (item) { function iterateUntilUndeleted (item) {
while (item !== null && item._deleted) { while (item !== null && item._deleted) {
item = item._right; item = item._right;
@@ -17240,6 +17295,26 @@ function removeDomChildrenUntilElementFound (parent, currentChild, elem) {
} }
} }
/**
* Define the elements to which a set of CSS queries apply.
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors}
*
* @example
* query = '.classSelector'
* query = 'nodeSelector'
* query = '#idSelector'
*
* @typedef {string} CSS_Selector
*/
/**
* Represents a subset of the nodes of a YXmlElement / YXmlFragment and a
* position within them.
*
* Can be created with {@link YXmlFragment#createTreeWalker}
*
* @public
*/
class YXmlTreeWalker { class YXmlTreeWalker {
constructor (root, f) { constructor (root, f) {
this._filter = f || (() => true); this._filter = f || (() => true);
@@ -17295,6 +17370,11 @@ class YXmlTreeWalker {
} }
} }
/**
* An Event that describes changes on a YXml Element or Yxml Fragment
*
* @protected
*/
class YXmlEvent extends YEvent { class YXmlEvent extends YEvent {
/** /**
* @param {YType} target The target on which the event is created. * @param {YType} target The target on which the event is created.
@@ -17336,6 +17416,35 @@ class YXmlEvent extends YEvent {
} }
} }
/**
* Dom filter function.
*
* @callback domFilter
* @param {string} nodeName The nodeName of the element
* @param {Map} attributes The map of attributes.
* @return {boolean} Whether to include the Dom node in the YXmlElement.
*/
/**
* Define the elements to which a set of CSS queries apply.
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors}
*
* @example
* query = '.classSelector'
* query = 'nodeSelector'
* query = '#idSelector'
*
* @typedef {string} CSS_Selector
*/
/**
* Represents a list of {@link YXmlElement}.and {@link YXmlText} types.
* A YxmlFragment is similar to a {@link YXmlElement}, but it does not have a
* nodeName and it does not have attributes. Though it can be bound to a DOM
* element - in this case the attributes and the nodeName are not shared.
*
* @public
*/
class YXmlFragment extends YArray { class YXmlFragment extends YArray {
/** /**
* Create a subtree of childNodes. * Create a subtree of childNodes.
@@ -17468,6 +17577,15 @@ class YXmlFragment extends YArray {
} }
} }
/**
* An YXmlElement imitates the behavior of a
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}.
*
* * An YXmlElement has attributes (key value pairs)
* * An YXmlElement has childElements that must inherit from YXmlElement
*
* @param {String} nodeName Node name
*/
class YXmlElement extends YXmlFragment { class YXmlElement extends YXmlFragment {
constructor (nodeName = 'UNDEFINED') { constructor (nodeName = 'UNDEFINED') {
super(); super();
@@ -17644,6 +17762,11 @@ class YXmlElement extends YXmlFragment {
} }
} }
/**
* You can manage binding to a custom type with YXmlHook.
*
* @public
*/
class YXmlHook extends YMap { class YXmlHook extends YMap {
/** /**
* @param {String} hookName nodeName of the Dom Node. * @param {String} hookName nodeName of the Dom Node.
@@ -17745,6 +17868,12 @@ class YXmlHook extends YMap {
} }
} }
/**
* Represents text in a Dom Element. In the future this type will also handle
* simple formatting information like bold and italic.
*
* @param {String} arg1 Initial value.
*/
class YXmlText extends YText { class YXmlText extends YText {
/** /**
* Creates a Dom Element that mirrors this YXmlText. * Creates a Dom Element that mirrors this YXmlText.
@@ -18152,6 +18281,21 @@ function createMutualExclude () {
} }
} }
/**
* Abstract class for bindings.
*
* A binding handles data binding from a Yjs type to a data object. For example,
* you can bind a Quill editor instance to a YText instance with the `QuillBinding` class.
*
* It is expected that a concrete implementation accepts two parameters
* (type and binding target).
*
* @example
* const quill = new Quill(document.createElement('div'))
* const type = y.define('quill', Y.Text)
* const binding = new Y.QuillBinding(quill, type)
*
*/
class Binding { class Binding {
/** /**
* @param {YType} type Yjs type. * @param {YType} type Yjs type.
@@ -18182,6 +18326,43 @@ class Binding {
} }
} }
// TODO: Implement function to describe ranges
/**
* A relative position that is based on the Yjs model. In contrast to an
* absolute position (position by index), the relative position can be
* recomputed when remote changes are received. For example:
*
* ```Insert(0, 'x')('a|bc') = 'xa|bc'``` Where | is the cursor position.
*
* A relative cursor position can be obtained with the function
* {@link getRelativePosition} and it can be transformed to an absolute position
* with {@link fromRelativePosition}.
*
* Pro tip: Use this to implement shared cursor locations in YText or YXml!
* The relative position is {@link encodable}, so you can send it to other
* clients.
*
* @example
* // Current cursor position is at position 10
* let relativePosition = getRelativePosition(yText, 10)
* // modify yText
* yText.insert(0, 'abc')
* yText.delete(3, 10)
* // Compute the cursor position
* let absolutePosition = fromRelativePosition(y, relativePosition)
* absolutePosition.type // => yText
* console.log('cursor location is ' + absolutePosition.offset) // => cursor location is 3
*
* @typedef {encodable} RelativePosition
*/
/**
* Create a relativePosition based on a absolute position.
*
* @param {YType} type The base type (e.g. YText or YArray).
* @param {Integer} offset The absolute position.
*/
function getRelativePosition (type, offset) { function getRelativePosition (type, offset) {
// TODO: rename to createRelativePosition // TODO: rename to createRelativePosition
let t = type._start; let t = type._start;
@@ -18561,21 +18742,20 @@ function domObserver (mutations, _document) {
mutations.forEach(mutation => { mutations.forEach(mutation => {
const dom = mutation.target; const dom = mutation.target;
const yxml = this.domToType.get(dom); const yxml = this.domToType.get(dom);
if (yxml === false || yxml === undefined || yxml.constructor === YXmlHook) { if (yxml === undefined) { // In case yxml is undefined, we double check if we forgot to bind the dom
// dom element is filtered let parent;
if (yxml === undefined) { // In case yxml is undefined, we double check if we forgot to bind the dom let yParent;
console.error('Yjs DomBinding: Reconstructing DomBinding, please report how to reproduce this.'); do {
let parent; parent = dom.parentNode;
let yParent; yParent = this.domToType.get(parent);
do { } while (yParent === undefined && parent !== null)
parent = dom.parentNode; if (yParent !== false && yParent !== undefined && yParent.constructor !== YXmlHook) {
yParent = this.domToType.get(parent); diffChildren.add(parent);
} while (yParent === undefined)
if (yParent !== false && yParent !== undefined && yParent.constructor !== YXmlHook) {
diffChildren.add(parent);
}
} }
return return
} else if (yxml === false || yxml.constructor === YXmlHook) {
// dom element is filtered / a dom hook
return
} }
switch (mutation.type) { switch (mutation.type) {
case 'characterData': case 'characterData':
@@ -18617,6 +18797,17 @@ function domObserver (mutations, _document) {
/* global MutationObserver */ /* global MutationObserver */
/**
* A binding that binds the children of a YXmlFragment to a DOM element.
*
* This binding is automatically destroyed when its parent is deleted.
*
* @example
* const div = document.createElement('div')
* const type = y.define('xml', Y.XmlFragment)
* const binding = new Y.QuillBinding(type, div)
*
*/
class DomBinding extends Binding { class DomBinding extends Binding {
/** /**
* @param {YXmlFragment} type The bind source. This is the ultimate source of * @param {YXmlFragment} type The bind source. This is the ultimate source of
@@ -18739,6 +18930,25 @@ class DomBinding extends Binding {
* @typedef {function(nodeName: String, attrs: Map): Map|null} FilterFunction * @typedef {function(nodeName: String, attrs: Map): Map|null} FilterFunction
*/ */
/**
* Anything that can be encoded with `JSON.stringify` and can be decoded with
* `JSON.parse`.
*
* The following property should hold:
* `JSON.parse(JSON.stringify(key))===key`
*
* At the moment the only safe values are number and string.
*
* @typedef {(number|string)} encodable
*/
/**
* A Yjs instance handles the state of shared data.
*
* @param {string} room Users in the same room share the same content
* @param {Object} opts Connector definition
* @param {AbstractPersistence} persistence Persistence adapter instance
*/
class Y$1 extends NamedEventHandler { class Y$1 extends NamedEventHandler {
constructor (room, opts, persistence, conf = {}) { constructor (room, opts, persistence, conf = {}) {
super(); super();
@@ -19685,6 +19895,8 @@ var browser_5 = browser.useColors;
var browser_6 = browser.storage; var browser_6 = browser.storage;
var browser_7 = browser.colors; var browser_7 = browser.colors;
// TODO: rename Connector
class AbstractConnector { class AbstractConnector {
constructor (y, opts) { constructor (y, opts) {
this.y = y; this.y = y;
@@ -19972,6 +20184,12 @@ class AbstractConnector {
} }
} }
/**
* Read the Decoder and fill the Yjs instance with data in the decoder.
*
* @param {Y} y The Yjs instance
* @param {BinaryDecoder} decoder The BinaryDecoder to read from.
*/
function fromBinary (y, decoder) { function fromBinary (y, decoder) {
y.transact(function () { y.transact(function () {
integrateRemoteStructs(y, decoder); integrateRemoteStructs(y, decoder);
@@ -20214,6 +20432,7 @@ class QuillBinding extends Binding {
} }
} }
// TODO: The following assignments should be moved to yjs-dist
Y$1.AbstractConnector = AbstractConnector; Y$1.AbstractConnector = AbstractConnector;
Y$1.AbstractPersistence = AbstractPersistence; Y$1.AbstractPersistence = AbstractPersistence;
Y$1.Array = YArray; Y$1.Array = YArray;
@@ -20406,6 +20625,19 @@ class TestConnector extends AbstractConnector {
} }
} }
/**
* Try to merge all items in os with their successors.
*
* Some transformations (like delete) fragment items.
* Item(c: 'ab') + Delete(1,1) + Delete(0, 1) -> Item(c: 'a',deleted);Item(c: 'b',deleted)
*
* This functions merges the fragmented nodes together:
* Item(c: 'a',deleted);Item(c: 'b',deleted) -> Item(c: 'ab', deleted)
*
* TODO: The Tree implementation does not support deletions in-spot.
* This is why all deletions must be performed after the traversal.
*
*/
function defragmentItemContent (y) { function defragmentItemContent (y) {
const os = y.os; const os = y.os;
if (os.length < 2) { if (os.length < 2) {

File diff suppressed because one or more lines are too long