cleanup docs

This commit is contained in:
Kevin Jahns 2018-03-23 04:35:52 +01:00
parent 026675b438
commit 6dd43cde17
25 changed files with 218 additions and 82 deletions

View File

@ -2,7 +2,7 @@
import { createMutualExclude } from '../Util/mutualExclude.js'
/**
* Abstract class for bindings
* 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.
@ -18,16 +18,27 @@ import { createMutualExclude } from '../Util/mutualExclude.js'
*/
export default class Binding {
/**
* @param {YType} type Yjs type
* @param {any} target Binding Target
* @param {YType} type Yjs type.
* @param {any} target Binding Target.
*/
constructor (type, target) {
/**
* The Yjs type that is bound to `target`
* @type {YType}
*/
this.type = type
/**
* The target that `type` is bound to.
* @type {*}
*/
this.target = target
/**
* @private
*/
this._mutualExclude = createMutualExclude()
}
/**
* Remove all data observers (both from the type and th target).
* Remove all data observers (both from the type and the target).
*/
destroy () {
this.type = null

View File

@ -17,9 +17,9 @@ import { removeAssociation } from './util.js'
* 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)
* const div = document.createElement('div')
* const type = y.define('xml', Y.XmlFragment)
* const binding = new Y.QuillBinding(type, div)
*
*/
export default class DomBinding extends Binding {
@ -27,12 +27,28 @@ export default class DomBinding extends Binding {
* @param {YXmlFragment} type The bind source. This is the ultimate source of
* truth.
* @param {Element} target The bind target. Mirrors the target.
* @param {Object} [opts] Optional configurations
* @param {FilterFunction} [opts.filter=defaultFilter] The filter function to use.
*/
constructor (type, target, opts = {}) {
// Binding handles textType as this.type and domTextarea as this.target
super(type, target)
/**
* Maps each DOM element to the type that it is associated with.
* @type {Map}
*/
this.domToType = new Map()
/**
* Maps each YXml type to the DOM element that it is associated with.
* @type {Map}
*/
this.typeToDom = new Map()
/**
* Defines which DOM attributes and elements to filter out.
* Also filters remote changes.
* @type {FilterFunction}
*/
this.filter = opts.filter || defaultFilter
// set initial value
target.innerHTML = ''
@ -103,6 +119,7 @@ export default class DomBinding extends Binding {
/**
* NOTE: currently does not apply filter to existing elements!
* @param {FilterFunction} filter The filter function to use from now on.
*/
setFilter (filter) {
this.filter = filter
@ -110,7 +127,7 @@ export default class DomBinding extends Binding {
}
/**
* Remove all properties that are handled by this class
* Remove all properties that are handled by this class.
*/
destroy () {
this.domToType = null
@ -125,3 +142,11 @@ export default class DomBinding extends Binding {
super.destroy()
}
}
/**
* A filter defines which elements and attributes to share.
* Return null if the node should be filtered. Otherwise return the Map of
* accepted attributes.
*
* @typedef {function(nodeName: String, attrs: Map): Map|null} FilterFunction
*/

View File

@ -7,7 +7,7 @@ import {
import diff from '../../Util/simpleDiff.js'
import YXmlFragment from '../../Types/YXml/YXmlFragment.js'
/*
/**
* 1. Check if any of the nodes was deleted
* 2. Iterate over the children.
* 2.1 If a node exists that is not yet bound to a type, insert a new node
@ -17,6 +17,7 @@ import YXmlFragment from '../../Types/YXml/YXmlFragment.js'
* recreate a new yxml element that is bound to that node.
* You can detect that a node was moved because expectedId
* !== actualId in the list
* @private
*/
function applyChangesFromDom (binding, dom, yxml, _document) {
if (yxml == null || yxml === false || yxml.constructor === YXmlHook) {
@ -79,6 +80,9 @@ function applyChangesFromDom (binding, dom, yxml, _document) {
}
}
/**
* @private
*/
export default function domObserver (mutations, _document) {
this._mutualExclude(() => {
this.type._y.transact(() => {

View File

@ -5,7 +5,11 @@ import { createAssociation } from './util.js'
/**
* Creates a Yjs type (YXml) based on the contents of a DOM Element.
*
* @param {Element|TextNode}
* @param {Element|TextNode} element The DOM Element
* @param {?Document} _document Optional. Provide the global document object.
* @param {?DomBinding} binding This property should only be set if the type
* is going to be bound with the dom-binding.
* @return {YXmlElement | YXmlText}
*/
export default function domToType (element, _document = document, binding) {
let type

View File

@ -1,10 +1,27 @@
import isParentOf from '../../Util/isParentOf.js'
/**
* 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.
*/
export function defaultFilter (nodeName, attrs) {
// TODO: implement basic filter that filters out dangerous properties!
return 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
*/
export function applyFilterOnType (y, binding, type) {
if (isParentOf(binding.type, type)) {
const nodeName = type.nodeName

View File

@ -5,6 +5,9 @@ import { getRelativePosition, fromRelativePosition } from '../../Util/relativePo
let browserSelection = null
let relativeSelection = null
/**
* @private
*/
export let beforeTransactionSelectionFixer
if (typeof getSelection !== 'undefined') {
beforeTransactionSelectionFixer = function _beforeTransactionSelectionFixer (y, domBinding, transaction, remote) {
@ -30,6 +33,9 @@ if (typeof getSelection !== 'undefined') {
beforeTransactionSelectionFixer = function _fakeBeforeTransactionSelectionFixer () {}
}
/**
* @private
*/
export function afterTransactionSelectionFixer (y, domBinding, transaction, remote) {
if (relativeSelection === null || !remote) {
return

View File

@ -3,6 +3,9 @@ import YXmlText from '../../Types/YXml/YXmlText.js'
import YXmlHook from '../../Types/YXml/YXmlHook.js'
import { removeDomChildrenUntilElementFound } from './util.js'
/**
* @private
*/
export default function typeObserver (events, _document) {
this._mutualExclude(() => {
events.forEach(event => {

View File

@ -1,6 +1,11 @@
import domToType from './domToType.js'
/**
* Iterates items until an undeleted item is found.
*
* @private
*/
export function iterateUntilUndeleted (item) {
while (item !== null && item._deleted) {
item = item._right
@ -8,11 +13,23 @@ export function iterateUntilUndeleted (item) {
return item
}
/**
* Removes an association (the information that a DOM element belongs to a
* type).
*
* @private
*/
export 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).
*
* @private
*/
export function createAssociation (domBinding, dom, type) {
if (domBinding !== undefined) {
domBinding.domToType.set(dom, type)
@ -31,12 +48,18 @@ export function createAssociation (domBinding, dom, type) {
* 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
*/
export function insertDomElementsAfter (type, prev, doms, _document, binding) {
return type.insertAfter(prev, doms.map(dom => domToType(dom, _document, binding)))
}
/**
* @private
*/
export function insertNodeHelper (yxml, prevExpectedNode, child, _document, binding) {
let insertedNodes = insertDomElementsAfter(yxml, prevExpectedNode, [child], _document, binding)
if (insertedNodes.length > 0) {
@ -54,6 +77,8 @@ export function insertNodeHelper (yxml, prevExpectedNode, child, _document, bind
* @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
*/
export function removeDomChildrenUntilElementFound (parent, currentChild, elem) {
while (currentChild !== elem) {

View File

@ -7,6 +7,8 @@ import { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.
import debug from 'debug'
// TODO: rename Connector
export default class AbstractConnector {
constructor (y, opts) {
this.y = y

View File

@ -14,7 +14,6 @@ function getFreshCnf () {
}
/**
* @private
* Abstract persistence class.
*/
export default class AbstractPersistence {

View File

@ -1,26 +1,69 @@
/**
* Changes that are created within a transaction are bundled and sent as one
* message to the remote peers. This implies that the changes are applied
* in one flush and at most one {@link YEvent} per type is created.
* A transaction is created for every change on the Yjs model. It is possible
* to bundle changes on the Yjs model in a single transaction to
* minimize the number on messages sent and the number of observer calls.
* If possible the user of this library should bundle as many changes as
* possible. Here is an example to illustrate the advantages of bundling:
*
* @example
* const map = y.define('map', YMap)
* // Log content when change is triggered
* map.observe(function () {
* console.log('change triggered')
* })
* // Each change on the map type triggers a log message:
* map.set('a', 0) // => "change triggered"
* map.set('b', 0) // => "change triggered"
* // When put in a transaction, it will trigger the log after the transaction:
* y.transact(function () {
* map.set('a', 1)
* map.set('b', 1)
* }) // => "change triggered"
*
* It is best to bundle as many changes in a single Transaction as possible.
* This way only few changes need to be computed
*/
export default class Transaction {
constructor (y) {
/**
* @type {Y} The Yjs instance.
*/
this.y = y
// types added during transaction
/**
* All new types that are added during a transaction.
* @type {Set<Item>}
*/
this.newTypes = new Set()
// changed types (does not include new types)
// maps from type to parentSubs (item._parentSub = null for array elements)
/**
* All types that were directly modified (property added or child
* inserted/deleted). New types are not included in this Set.
* Maps from type to parentSubs (`item._parentSub = null` for YArray)
* @type {Set<YType,String>}
*/
this.changedTypes = new Map()
// TODO: rename deletedTypes
/**
* Set of all deleted Types and Structs.
* @type {Set<Item>}
*/
this.deletedStructs = new Set()
/**
* Saves the old state set of the Yjs instance. If a state was modified,
* the original value is saved here.
* @type {Map<Number,Number>}
*/
this.beforeState = new Map()
/**
* Stores the events for the types that observe also child elements.
* It is mainly used by `observeDeep`.
* @type {Map<YType,Array<YEvent>>}
*/
this.changedParentTypes = new Map()
}
}
/**
* @private
*/
export function transactionTypeChanged (y, type, sub) {
if (type !== y && !type._deleted && !y._transaction.newTypes.has(type)) {
const changedTypes = y._transaction.changedTypes

View File

@ -9,6 +9,7 @@ const bits8 = 0b11111111
export default class BinaryEncoder {
constructor () {
// TODO: implement chained Uint8Array buffers instead of Array buffer
// TODO: Rewrite all methods as functions!
this.data = []
}

View File

@ -467,5 +467,4 @@ export default class Tree {
}
}
}
flush () {}
}

View File

@ -1,4 +1,5 @@
import ID from './ID/ID.js'
import isParentOf from './isParentOf.js'
class ReverseOperation {
constructor (y, transaction) {
@ -15,16 +16,6 @@ class ReverseOperation {
}
}
function isStructInScope (y, struct, scope) {
while (struct !== y) {
if (struct === scope) {
return true
}
struct = struct._parent
}
return false
}
function applyReverseOperation (y, scope, reverseBuffer) {
let performedUndo = false
y.transact(() => {
@ -38,7 +29,7 @@ function applyReverseOperation (y, scope, reverseBuffer) {
while (op._deleted && op._redone !== null) {
op = op._redone
}
if (op._deleted === false && isStructInScope(y, op, scope)) {
if (op._deleted === false && isParentOf(scope, op)) {
performedUndo = true
op._delete(y)
}
@ -46,7 +37,7 @@ function applyReverseOperation (y, scope, reverseBuffer) {
}
for (let op of undoOp.deletedStructs) {
if (
isStructInScope(y, op, scope) &&
isParentOf(scope, op) &&
op._parent !== y &&
(
op._id.user !== y.userID ||

View File

@ -7,7 +7,15 @@ export default class YEvent {
* @param {YType} target The changed type.
*/
constructor (target) {
/**
* The type on which this event was created on.
* @type {YType}
*/
this.target = target
/**
* The current target on which the observe callback is called.
* @type {YType}
*/
this.currentTarget = target
}

View File

@ -1,6 +1,6 @@
/* global crypto */
export function generateUserID () {
export function generateRandomUint32 () {
if (typeof crypto !== 'undefined' && crypto.getRandomValue != null) {
// browser
let arr = new Uint32Array(1)

View File

@ -5,6 +5,8 @@
* @param {Type} parent
* @param {Type} child
* @return {Boolean} Whether `parent` is a parent of `child`.
*
* @public
*/
export default function isParentOf (parent, child) {
child = child._parent

View File

@ -1,4 +1,22 @@
// TODO: rename mutex
/**
* Creates a mutual exclude function with the following property:
*
* @example
* const mutualExclude = createMutualExclude()
* mutualExclude(function () {
* // This function is immediately executed
* mutualExclude(function () {
* // This function is never executed, as it is called with the same
* // mutualExclude
* })
* })
*
* @return {Function} A mutual exclude function
* @public
*/
export function createMutualExclude () {
var token = true
return function mutualExclude (f) {

View File

@ -1,6 +1,8 @@
import ID from './ID/ID.js'
import RootID from './ID/RootID.js'
// 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

View File

@ -11,16 +11,16 @@
* a === b // values match
*
* @typedef {Object} SimpleDiff
* @property {NaturalNumber} pos The index where changes were applied
* @property {NaturalNumber} delete The number of characters to delete starting
* @property {Number} pos The index where changes were applied
* @property {Number} delete The number of characters to delete starting
* at `index`.
* @property {String} insert The new text to insert at `index` after applying
* `delete`
*/
/**
* Create a diff between two strings. This diff implementation is intentionally
* not very smart.
* Create a diff between two strings. This diff implementation is highly
* efficient, but not very sophisticated.
*
* @public
* @param {String} a The old version of the string

View File

@ -12,15 +12,30 @@ import ItemEmbed from '../Struct/ItemEmbed.js'
const structs = new Map()
const references = new Map()
/**
* Register a new Yjs types. The same type must be defined with the same
* reference on all clients!
*
* @param {Number} reference
* @param {class} structConstructor
*
* @public
*/
export function registerStruct (reference, structConstructor) {
structs.set(reference, structConstructor)
references.set(structConstructor, reference)
}
/**
* @private
*/
export function getStruct (reference) {
return structs.get(reference)
}
/**
* @private
*/
export function getStructReference (typeConstructor) {
return references.get(typeConstructor)
}

View File

@ -1,33 +0,0 @@
import YMap from '../Types/YMap'
import YArray from '../Types/YArray'
export function writeObjectToYMap (object, type) {
for (var key in object) {
var val = object[key]
if (Array.isArray(val)) {
type.set(key, YArray)
writeArrayToYArray(val, type.get(key))
} else if (typeof val === 'object') {
type.set(key, YMap)
writeObjectToYMap(val, type.get(key))
} else {
type.set(key, val)
}
}
}
export function writeArrayToYArray (array, type) {
for (var i = array.length - 1; i >= 0; i--) {
var val = array[i]
if (Array.isArray(val)) {
type.insert(0, [YArray])
writeArrayToYArray(val, type.get(0))
} else if (typeof val === 'object') {
type.insert(0, [YMap])
writeObjectToYMap(val, type.get(0))
} else {
type.insert(0, [val])
}
}
}

View File

@ -1,19 +1,13 @@
import DeleteStore from './Store/DeleteStore.js'
import OperationStore from './Store/OperationStore.js'
import StateStore from './Store/StateStore.js'
import { generateUserID } from './Util/generateUserID.js'
import { generateRandomUint32 } from './Util/generateRandomUint32.js'
import RootID from './Util/ID/RootID.js'
import NamedEventHandler from './Util/NamedEventHandler.js'
import Transaction from './Transaction.js'
export { default as DomBinding } from './Bindings/DomBinding/DomBinding.js'
/**
* A positive natural number including zero: 0, 1, 2, ..
*
* @typedef {number} NaturalNumber
*/
/**
* Anything that can be encoded with `JSON.stringify` and can be decoded with
* `JSON.parse`.
@ -47,7 +41,7 @@ export default class Y extends NamedEventHandler {
this._contentReady = false
this._opts = opts
if (typeof opts.userID !== 'number') {
this.userID = generateUserID()
this.userID = generateRandomUint32()
} else {
this.userID = opts.userID
}

View File

@ -1,7 +1,7 @@
import { test } from '../node_modules/cutest/cutest.mjs'
import BinaryEncoder from '../src/Util/Binary/Encoder.js'
import BinaryDecoder from '../src/Util/Binary/Decoder.js'
import { generateUserID } from '../src/Util/generateUserID.js'
import { generateRandomUint32 } from '../src/Util/generateRandomUint32.js'
import Chance from 'chance'
function testEncoding (t, write, read, val) {
@ -43,7 +43,7 @@ test('varUint random', async function varUintRandom (t) {
test('varUint random user id', async function varUintRandomUserId (t) {
t.getSeed() // enforces that this test is repeated
testEncoding(t, writeVarUint, readVarUint, generateUserID())
testEncoding(t, writeVarUint, readVarUint, generateRandomUint32())
})
const writeVarString = (encoder, val) => encoder.writeVarString(val)

View File

@ -156,7 +156,7 @@ export async function initArrays (t, opts) {
users: []
}
var chance = opts.chance || new Chance(t.getSeed() * 1000000000)
var conn = Object.assign({ room: 'debugging_' + t.name, generateUserId: false, testContext: t, chance }, connector)
var conn = Object.assign({ room: 'debugging_' + t.name, testContext: t, chance }, connector)
for (let i = 0; i < opts.users; i++) {
let connOpts
if (i === 0) {