Compare commits

...

30 Commits

Author SHA1 Message Date
Kevin Jahns
01d0825ae6 13.0.0-73 2018-11-26 17:14:48 +01:00
Kevin Jahns
e2f98525d2 clean examples build 2018-11-26 17:14:45 +01:00
Kevin Jahns
70a0a03130 no start content in prosemirror example 2018-11-26 16:59:01 +01:00
Kevin Jahns
656d85c62e add dom example 2018-11-26 16:06:17 +01:00
Kevin Jahns
e168dd48fb proper api endpoints for examples 2018-11-26 14:54:46 +01:00
Kevin Jahns
12d43199d5 add http listener to websocket-server 2018-11-26 13:08:23 +01:00
Kevin Jahns
539fa8b21d examples use hosted server 2018-11-26 02:13:06 +01:00
Kevin Jahns
f572f94586 port support 2018-11-25 23:41:17 +01:00
Kevin Jahns
c12d00b227 mjs nodejs support 2018-11-25 22:39:50 +01:00
Kevin Jahns
e4a5f2caec jsdoc fixes 2018-11-25 05:43:18 +01:00
Kevin Jahns
9f9f465238 update logo link 2018-11-25 04:50:23 +01:00
Kevin Jahns
8450ff86d7 make npm build ready for netlify 2018-11-25 04:41:52 +01:00
Kevin Jahns
70139262c5 add rollup-cli as dependency 2018-11-25 03:36:06 +01:00
Kevin Jahns
9c0da271eb large scale refactoring 2018-11-25 03:17:00 +01:00
Kevin Jahns
ade3e1949d update cdn destination. closes #128 2018-11-20 15:03:28 +01:00
Kevin Jahns
eec63a008f 13.0.0-72 2018-11-20 03:53:55 +01:00
Kevin Jahns
52abcdd043 fix all tests 2018-11-16 12:33:41 +01:00
Kevin Jahns
f94653424a add prosemirror tests 2018-11-14 07:20:06 +01:00
Kevin Jahns
d67a794e2c 13.0.0-71 2018-11-09 01:49:59 +01:00
Kevin Jahns
60318083a6 make websocket-server a binary and add bindings and provider to npm package 2018-11-09 01:49:43 +01:00
Kevin Jahns
7607070452 13.0.0-70 2018-11-09 01:24:06 +01:00
Kevin Jahns
28fb7b6e9c remove logging in prosemirror binding 2018-11-09 01:23:16 +01:00
Kevin Jahns
aafe15757f implemented awareness protocol and added cursor support 2018-11-09 00:13:30 +01:00
Kevin Jahns
31d6ef6296 cleanup prosemirror example 2018-11-06 15:15:27 +01:00
Kevin Jahns
32b8fac37f added prosemirror binding 2018-11-06 13:44:35 +01:00
Kevin Jahns
e8060de914 13.0.0-69 2018-11-02 01:54:53 +01:00
Kevin Jahns
22b036527c further refine build process to also include lib 2018-11-02 01:54:40 +01:00
Kevin Jahns
feb1e030d7 13.0.0-68 2018-11-02 01:52:24 +01:00
Kevin Jahns
bd271e3952 update publish process 2018-11-02 01:52:20 +01:00
Kevin Jahns
df80938190 13.0.0-67 2018-11-02 00:47:09 +01:00
167 changed files with 3253 additions and 1779 deletions

View File

@@ -1,6 +1,7 @@
{ {
"source": "./src", "source": ".",
"destination": "./docs", "destination": "./docs",
"excludes": ["build", "node_modules", "tests-lib", "test"],
"plugins": [{ "plugins": [{
"name": "esdoc-standard-plugin", "name": "esdoc-standard-plugin",
"option": { "option": {

4
.gitignore vendored
View File

@@ -2,7 +2,7 @@ node_modules
bower_components bower_components
docs docs
/y.* /y.*
/examples/yjs-dist.js* /examples_all/*/index.dist.*
.vscode .vscode
.yjsPersisted .yjsPersisted
build build

50
.jsdoc.json Normal file
View File

@@ -0,0 +1,50 @@
{
"sourceType": "module",
"tags": {
"allowUnknownTags": true,
"dictionaries": ["jsdoc"]
},
"source": {
"include": ["./types", "./utils/UndoManager.mjs", "./utils/Y.mjs", "./provider", "./bindings"],
"includePattern": ".js$"
},
"plugins": [
"plugins/markdown"
],
"templates": {
"referenceTitle": "Yjs",
"disableSort": false,
"useCollapsibles": true,
"collapse": true,
"resources": {
"y-js.org": "yjs.website"
},
"logo": {
"url": "https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png",
"width": "162px",
"height": "162px",
"link": "/"
},
"tabNames": {
"api": "API",
"tutorials": "Examples"
},
"footerText": "Shared Editing",
"css": [
"./style.css"
],
"default": {
"staticFiles": {
"include": ["examples/"]
}
}
},
"opts": {
"destination": "./docs/",
"encoding": "utf8",
"private": false,
"recurse": true,
"template": "./node_modules/tui-jsdoc-template",
"tutorials": "./examples"
}
}

View File

@@ -1,5 +1,5 @@
# ![Yjs](http://y-js.org/images/yjs.png) # ![Yjs](https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png)
Yjs is a framework for offline-first p2p shared editing on structured data like Yjs is a framework for offline-first p2p shared editing on structured data like
text, richtext, json, or XML. It is fairly easy to get started, as Yjs hides text, richtext, json, or XML. It is fairly easy to get started, as Yjs hides
@@ -66,7 +66,7 @@ missing modules.
### CDN ### CDN
``` ```
<script src="https://cdn.jsdelivr.net/npm/yjs@12/src/y.js"></script> <script src="https://cdn.jsdelivr.net/npm/yjs@12/dist/y.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-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-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-memory@8/dist/y-memory.js"></script>

18
README.v13.md Normal file
View File

@@ -0,0 +1,18 @@
# ![Yjs](https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png)
> A CRDT library with a powerful abstraction of shared data
Yjs v13 is a work in progress.
### Typescript Declarations
Until [this](https://github.com/Microsoft/TypeScript/issues/7546) is fixed, the only way to get type declarations is by adding Yjs to the list of checked files:
```json
{
"checkJs": true,
"include": [
"./node_modules/yjs/"
]
..
}
```

1
bindings/dom.mjs Normal file
View File

@@ -0,0 +1 @@
export * from './dom/DomBinding.mjs'

View File

@@ -1,15 +1,23 @@
/**
* @module bindings/dom
*/
/* global MutationObserver, getSelection */ /* global MutationObserver, getSelection */
import { fromRelativePosition } from '../../Util/relativePosition.js' import { fromRelativePosition } from '../../utils/relativePosition.mjs'
import Binding from '../Binding.js' import { createMutex } from '../../lib/mutex.mjs'
import { createAssociation, removeAssociation } from './util.js' import { createAssociation, removeAssociation } from './util.mjs'
import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer, getCurrentRelativeSelection } from './selection.js' import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer, getCurrentRelativeSelection } from './selection.mjs'
import { defaultFilter, applyFilterOnType } from './filter.js' import { defaultFilter, applyFilterOnType } from './filter.mjs'
import typeObserver from './typeObserver.js' import { typeObserver } from './typeObserver.mjs'
import domObserver from './domObserver.js' import { domObserver } from './domObserver.mjs'
import { YXmlFragment } from '../../types/YXmlElement.mjs' // eslint-disable-line
/** /**
* @typedef {import('./filter.js').DomFilter} DomFilter * @callback DomFilter
* @param {string} nodeName
* @param {Map<string, string>} attrs
* @return {Map | null}
*/ */
/** /**
@@ -22,8 +30,9 @@ import domObserver from './domObserver.js'
* const type = y.define('xml', Y.XmlFragment) * const type = y.define('xml', Y.XmlFragment)
* const binding = new Y.QuillBinding(type, div) * const binding = new Y.QuillBinding(type, div)
* *
* @class
*/ */
export default class DomBinding extends Binding { export class DomBinding {
/** /**
* @param {YXmlFragment} type The bind source. This is the ultimate source of * @param {YXmlFragment} type The bind source. This is the ultimate source of
* truth. * truth.
@@ -31,10 +40,26 @@ export default class DomBinding extends Binding {
* @param {Object} [opts] Optional configurations * @param {Object} [opts] Optional configurations
* @param {DomFilter} [opts.filter=defaultFilter] The filter function to use. * @param {DomFilter} [opts.filter=defaultFilter] The filter function to use.
* @param {Document} [opts.document=document] The filter function to use.
* @param {Object} [opts.hooks] The filter function to use.
* @param {Element} [opts.scrollingElement=null] The filter function to use.
*/ */
constructor (type, target, opts = {}) { constructor (type, target, opts = {}) {
// Binding handles textType as this.type and domTextarea as this.target // Binding handles textType as this.type and domTextarea as this.target
super(type, target) /**
* The Yjs type that is bound to `target`
* @type {YXmlFragment}
*/
this.type = type
/**
* The target that `type` is bound to.
* @type {Element}
*/
this.target = target
/**
* @private
*/
this._mutualExclude = createMutex()
this.opts = opts this.opts = opts
opts.document = opts.document || document opts.document = opts.document || document
opts.hooks = opts.hooks || {} opts.hooks = opts.hooks || {}
@@ -81,16 +106,16 @@ export default class DomBinding extends Binding {
this.y = y this.y = y
// Force flush dom changes before Type changes are applied (they might // Force flush dom changes before Type changes are applied (they might
// modify the dom) // modify the dom)
this._beforeTransactionHandler = (y, transaction, remote) => { this._beforeTransactionHandler = y => {
this._domObserver(this._mutationObserver.takeRecords()) this._domObserver(this._mutationObserver.takeRecords())
this._mutualExclude(() => { this._mutualExclude(() => {
beforeTransactionSelectionFixer(this, remote) beforeTransactionSelectionFixer(this)
}) })
} }
y.on('beforeTransaction', this._beforeTransactionHandler) y.on('beforeTransaction', this._beforeTransactionHandler)
this._afterTransactionHandler = (y, transaction, remote) => { this._afterTransactionHandler = (y, transaction) => {
this._mutualExclude(() => { this._mutualExclude(() => {
afterTransactionSelectionFixer(this, remote) afterTransactionSelectionFixer(this)
}) })
// remove associations // remove associations
// TODO: this could be done more efficiently // TODO: this could be done more efficiently
@@ -121,8 +146,15 @@ export default class DomBinding extends Binding {
createAssociation(this, target, type) createAssociation(this, target, type)
} }
flushDomChanges () {
this._domObserver(this._mutationObserver.takeRecords())
}
/** /**
* NOTE: currently does not apply filter to existing elements! * NOTE:
* * does not apply filter to existing elements!
* * only guarantees that changes are filtered locally. Remote sites may see different content.
*
* @param {DomFilter} filter The filter function to use from now on. * @param {DomFilter} filter The filter function to use from now on.
*/ */
setFilter (filter) { setFilter (filter) {
@@ -199,13 +231,18 @@ export default class DomBinding extends Binding {
y.off('beforeObserverCalls', this._beforeObserverCallsHandler) y.off('beforeObserverCalls', this._beforeObserverCallsHandler)
y.off('afterTransaction', this._afterTransactionHandler) y.off('afterTransaction', this._afterTransactionHandler)
document.removeEventListener('selectionchange', this._selectionchange) document.removeEventListener('selectionchange', this._selectionchange)
super.destroy() this.type = null
this.target = null
} }
} }
/** /**
* A filter defines which elements and attributes to share. * A filter defines which elements and attributes to share.
* Return null if the node should be filtered. Otherwise return the Map of * Return null if the node should be filtered. Otherwise return the Map of
* accepted attributes. * accepted attributes.
* *
* @typedef {function(nodeName: String, attrs: Map): Map|null} FilterFunction * @callback FilterFunction
* @param {string} nodeName
* @param {Map} attrs
* @return {Map|null}
*/ */

View File

@@ -1,11 +1,14 @@
/**
* @module bindings/dom
*/
import YXmlHook from '../../Types/YXml/YXmlHook.js' import { YXmlHook } from '../../types/YXmlHook.mjs'
import { import {
iterateUntilUndeleted, iterateUntilUndeleted,
removeAssociation, removeAssociation,
insertNodeHelper } from './util.js' insertNodeHelper } from './util.mjs'
import diff from '../../../lib/simpleDiff.js' import { simpleDiff } from '../../lib/diff.mjs'
import YXmlFragment from '../../Types/YXml/YXmlFragment.js' import { YXmlFragment } from '../../types/YXmlElement.mjs'
/** /**
* 1. Check if any of the nodes was deleted * 1. Check if any of the nodes was deleted
@@ -19,7 +22,7 @@ import YXmlFragment from '../../Types/YXml/YXmlFragment.js'
* !== actualId in the list * !== actualId in the list
* @private * @private
*/ */
function applyChangesFromDom (binding, dom, yxml, _document) { const applyChangesFromDom = (binding, dom, yxml, _document) => {
if (yxml == null || yxml === false || yxml.constructor === YXmlHook) { if (yxml == null || yxml === false || yxml.constructor === YXmlHook) {
return return
} }
@@ -32,7 +35,7 @@ function applyChangesFromDom (binding, dom, yxml, _document) {
} }
} }
// 1. Check if any of the nodes was deleted // 1. Check if any of the nodes was deleted
yxml.forEach(function (childType) { yxml.forEach(childType => {
if (knownChildren.has(childType) === false) { if (knownChildren.has(childType) === false) {
childType._delete(y) childType._delete(y)
removeAssociation(binding, binding.typeToDom.get(childType), childType) removeAssociation(binding, binding.typeToDom.get(childType), childType)
@@ -83,7 +86,7 @@ function applyChangesFromDom (binding, dom, yxml, _document) {
/** /**
* @private * @private
*/ */
export default function domObserver (mutations, _document) { export function domObserver (mutations, _document) {
this._mutualExclude(() => { this._mutualExclude(() => {
this.type._y.transact(() => { this.type._y.transact(() => {
let diffChildren = new Set() let diffChildren = new Set()
@@ -107,7 +110,7 @@ export default function domObserver (mutations, _document) {
} }
switch (mutation.type) { switch (mutation.type) {
case 'characterData': case 'characterData':
var change = diff(yxml.toString(), dom.nodeValue) var change = simpleDiff(yxml.toString(), dom.nodeValue)
yxml.delete(change.pos, change.remove) yxml.delete(change.pos, change.remove)
yxml.insert(change.pos, change.insert) yxml.insert(change.pos, change.insert)
break break

View File

@@ -1,13 +1,20 @@
/**
* @module bindings/dom
*/
/* eslint-env browser */ /* eslint-env browser */
import YXmlText from '../../Types/YXml/YXmlText.js' import { YXmlText } from '../../types/YXmlText.mjs'
import YXmlHook from '../../Types/YXml/YXmlHook.js' import { YXmlHook } from '../../types/YXmlHook.mjs'
import YXmlElement from '../../Types/YXml/YXmlElement.js' import { YXmlElement } from '../../types/YXmlElement.mjs'
import { createAssociation, domsToTypes } from './util.js' import { createAssociation, domsToTypes } from './util.mjs'
import { filterDomAttributes, defaultFilter } from './filter.js' import { filterDomAttributes, defaultFilter } from './filter.mjs'
import { DomBinding } from './DomBinding.mjs' // eslint-disable-line
/** /**
* @typedef {import('./filter.js').DomFilter} DomFilter * @callback DomFilter
* @typedef {import('./DomBinding.js').default} DomBinding * @param {string} nodeName
* @param {Map<string, string>} attrs
* @return {Map | null}
*/ */
/** /**
@@ -20,7 +27,7 @@ import { filterDomAttributes, defaultFilter } from './filter.js'
* @param {?DomBinding} binding Warning: This property is for internal use only! * @param {?DomBinding} binding Warning: This property is for internal use only!
* @return {YXmlElement | YXmlText | false} * @return {YXmlElement | YXmlText | false}
*/ */
export default function domToType (element, _document = document, hooks = {}, filter = defaultFilter, binding) { export const domToType = (element, _document = document, hooks = {}, filter = defaultFilter, binding) => {
/** /**
* @type {any} * @type {any}
*/ */

View File

@@ -1,12 +1,12 @@
import isParentOf from '../../Util/isParentOf.js'
/** /**
* @callback DomFilter * @module bindings/dom
* @param {string} nodeName
* @param {Map<string, string>} attrs
* @return {Map | null}
*/ */
import { Y } from '../../utils/Y.mjs' // eslint-disable-line
import { YXmlElement, YXmlFragment } from '../../types/YXmlElement.mjs' // eslint-disable-line
import { isParentOf } from '../../utils/isParentOf.mjs'
import { DomBinding } from './DomBinding.mjs' // eslint-disable-line
/** /**
* Default filter method (does nothing). * Default filter method (does nothing).
* *
@@ -15,7 +15,7 @@ import isParentOf from '../../Util/isParentOf.js'
* @return {Map | null} The allowed attributes or null, if the element should be * @return {Map | null} The allowed attributes or null, if the element should be
* filtered. * filtered.
*/ */
export function defaultFilter (nodeName, attrs) { export const 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
} }
@@ -23,7 +23,7 @@ export function defaultFilter (nodeName, attrs) {
/** /**
* *
*/ */
export function filterDomAttributes (dom, filter) { export const filterDomAttributes = (dom, filter) => {
const attrs = new Map() const attrs = new Map()
for (let i = dom.attributes.length - 1; i >= 0; i--) { for (let i = dom.attributes.length - 1; i >= 0; i--) {
const attr = dom.attributes[i] const attr = dom.attributes[i]
@@ -41,8 +41,8 @@ export function filterDomAttributes (dom, filter) {
* *
* @private * @private
*/ */
export function applyFilterOnType (y, binding, type) { export const applyFilterOnType = (y, binding, type) => {
if (isParentOf(binding.type, type)) { if (isParentOf(binding.type, type) && type instanceof YXmlElement) {
const nodeName = type.nodeName const nodeName = type.nodeName
let attributes = new Map() let attributes = new Map()
if (type.getAttributes !== undefined) { if (type.getAttributes !== undefined) {
@@ -53,7 +53,7 @@ export function applyFilterOnType (y, binding, type) {
} }
const filteredAttributes = binding.filter(nodeName, new Map(attributes)) const filteredAttributes = binding.filter(nodeName, new Map(attributes))
if (filteredAttributes === null) { if (filteredAttributes === null) {
type._delete(y) type._delete(y, true)
} else { } else {
// iterate original attributes // iterate original attributes
attributes.forEach((value, key) => { attributes.forEach((value, key) => {

View File

@@ -1,10 +1,14 @@
/**
* @module bindings/dom
*/
/* globals getSelection */ /* globals getSelection */
import { getRelativePosition } from '../../Util/relativePosition.js' import { getRelativePosition } from '../../utils/relativePosition.mjs'
let relativeSelection = null let relativeSelection = null
function _getCurrentRelativeSelection (domBinding) { const _getCurrentRelativeSelection = domBinding => {
const { baseNode, baseOffset, extentNode, extentOffset } = getSelection() const { baseNode, baseOffset, extentNode, extentOffset } = getSelection()
const baseNodeType = domBinding.domToType.get(baseNode) const baseNodeType = domBinding.domToType.get(baseNode)
const extentNodeType = domBinding.domToType.get(extentNode) const extentNodeType = domBinding.domToType.get(extentNode)
@@ -19,7 +23,7 @@ function _getCurrentRelativeSelection (domBinding) {
export const getCurrentRelativeSelection = typeof getSelection !== 'undefined' ? _getCurrentRelativeSelection : domBinding => null export const getCurrentRelativeSelection = typeof getSelection !== 'undefined' ? _getCurrentRelativeSelection : domBinding => null
export function beforeTransactionSelectionFixer (domBinding) { export const beforeTransactionSelectionFixer = domBinding => {
relativeSelection = getCurrentRelativeSelection(domBinding) relativeSelection = getCurrentRelativeSelection(domBinding)
} }
@@ -28,7 +32,7 @@ export function beforeTransactionSelectionFixer (domBinding) {
* This prevents any collapsing issues with the local selection. * This prevents any collapsing issues with the local selection.
* @private * @private
*/ */
export function afterTransactionSelectionFixer (domBinding) { export const afterTransactionSelectionFixer = domBinding => {
if (relativeSelection !== null) { if (relativeSelection !== null) {
domBinding.restoreSelection(relativeSelection) domBinding.restoreSelection(relativeSelection)
} }

View File

@@ -1,11 +1,15 @@
/**
* @module bindings/dom
*/
/* eslint-env browser */ /* eslint-env browser */
/* global getSelection */ /* global getSelection */
import YXmlText from '../../Types/YXml/YXmlText.js' import { YXmlText } from '../../types/YXmlText.mjs'
import YXmlHook from '../../Types/YXml/YXmlHook.js' import { YXmlHook } from '../../types/YXmlHook.mjs'
import { removeDomChildrenUntilElementFound } from './util.js' import { removeDomChildrenUntilElementFound } from './util.mjs'
function findScrollReference (scrollingElement) { const findScrollReference = scrollingElement => {
if (scrollingElement !== null) { if (scrollingElement !== null) {
let anchor = getSelection().anchorNode let anchor = getSelection().anchorNode
if (anchor == null) { if (anchor == null) {
@@ -34,7 +38,7 @@ function findScrollReference (scrollingElement) {
return null return null
} }
function fixScroll (scrollingElement, ref) { const fixScroll = (scrollingElement, ref) => {
if (ref !== null) { if (ref !== null) {
const { elem, top } = ref const { elem, top } = ref
const currentTop = elem.getBoundingClientRect().top const currentTop = elem.getBoundingClientRect().top
@@ -48,7 +52,7 @@ function fixScroll (scrollingElement, ref) {
/** /**
* @private * @private
*/ */
export default function typeObserver (events) { export const typeObserver = function (events) {
this._mutualExclude(() => { this._mutualExclude(() => {
const scrollRef = findScrollReference(this.scrollingElement) const scrollRef = findScrollReference(this.scrollingElement)
events.forEach(event => { events.forEach(event => {

View File

@@ -1,19 +1,16 @@
import domToType from './domToType.js'
/** /**
* @typedef {import('../../Types/YXml/YXmlText.js').default} YXmlText * @module bindings/dom
* @typedef {import('../../Types/YXml/YXmlElement.js').default} YXmlElement
* @typedef {import('../../Types/YXml/YXmlHook.js').default} YXmlHook
* @typedef {import('./DomBinding.js').default} DomBinding
*/ */
import { domToType } from './domToType.mjs'
import { DomBinding } from './DomBinding.mjs' // eslint-disable-line
/** /**
* Iterates items until an undeleted item is found. * Iterates items until an undeleted item is found.
* *
* @private * @private
*/ */
export function iterateUntilUndeleted (item) { export const iterateUntilUndeleted = item => {
while (item !== null && item._deleted) { while (item !== null && item._deleted) {
item = item._right item = item._right
} }
@@ -24,12 +21,13 @@ export function iterateUntilUndeleted (item) {
* Removes an association (the information that a DOM element belongs to a * Removes an association (the information that a DOM element belongs to a
* type). * type).
* *
* @private
* @param {DomBinding} domBinding The binding object * @param {DomBinding} domBinding The binding object
* @param {Element} dom The dom that is to be associated with type * @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 * @param {YXmlElement|YXmlHook} type The type that is to be associated with dom
* *
*/ */
export function removeAssociation (domBinding, dom, type) { export const removeAssociation = (domBinding, dom, type) => {
domBinding.domToType.delete(dom) domBinding.domToType.delete(dom)
domBinding.typeToDom.delete(type) domBinding.typeToDom.delete(type)
} }
@@ -38,12 +36,13 @@ export function removeAssociation (domBinding, dom, type) {
* Creates an association (the information that a DOM element belongs to a * Creates an association (the information that a DOM element belongs to a
* type). * type).
* *
* @private
* @param {DomBinding} domBinding The binding object * @param {DomBinding} domBinding The binding object
* @param {DocumentFragment|Element|Text} dom The dom that is to be associated with type * @param {DocumentFragment|Element|Text} dom The dom that is to be associated with type
* @param {YXmlElement|YXmlHook|YXmlText} type The type that is to be associated with dom * @param {YXmlFragment|YXmlElement|YXmlHook|YXmlText} type The type that is to be associated with dom
* *
*/ */
export function createAssociation (domBinding, dom, type) { export const createAssociation = (domBinding, dom, type) => {
if (domBinding !== undefined) { if (domBinding !== undefined) {
domBinding.domToType.set(dom, type) domBinding.domToType.set(dom, type)
domBinding.typeToDom.set(type, dom) domBinding.typeToDom.set(type, dom)
@@ -54,11 +53,12 @@ export function createAssociation (domBinding, dom, type) {
* If oldDom is associated with a type, associate newDom with the type and * 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. * forget about oldDom. If oldDom is not associated with any type, nothing happens.
* *
* @private
* @param {DomBinding} domBinding The binding object * @param {DomBinding} domBinding The binding object
* @param {Element} oldDom The existing dom * @param {Element} oldDom The existing dom
* @param {Element} newDom The new dom object * @param {Element} newDom The new dom object
*/ */
export function switchAssociation (domBinding, oldDom, newDom) { export const switchAssociation = (domBinding, oldDom, newDom) => {
if (domBinding !== undefined) { if (domBinding !== undefined) {
const type = domBinding.domToType.get(oldDom) const type = domBinding.domToType.get(oldDom)
if (type !== undefined) { if (type !== undefined) {
@@ -73,6 +73,7 @@ export function switchAssociation (domBinding, oldDom, newDom) {
* The Dom elements will be bound to a new YXmlElement and inserted at the * The Dom elements will be bound to a new YXmlElement and inserted at the
* specified position. * specified position.
* *
* @private
* @param {YXmlElement} type The type in which to insert DOM elements. * @param {YXmlElement} type The type in which to insert DOM elements.
* @param {YXmlElement|null} prev The reference node. New YxmlElements are * @param {YXmlElement|null} prev The reference node. New YxmlElements are
* inserted after this node. Set null to insert at * inserted after this node. Set null to insert at
@@ -81,15 +82,13 @@ export function switchAssociation (domBinding, oldDom, newDom) {
* @param {?Document} _document Optional. Provide the global document object. * @param {?Document} _document Optional. Provide the global document object.
* @param {DomBinding} binding The dom binding * @param {DomBinding} binding The dom binding
* @return {Array<YXmlElement>} The YxmlElements that are inserted. * @return {Array<YXmlElement>} The YxmlElements that are inserted.
*
* @private
*/ */
export function insertDomElementsAfter (type, prev, doms, _document, binding) { export const insertDomElementsAfter = (type, prev, doms, _document, binding) => {
const types = domsToTypes(doms, _document, binding.opts.hooks, binding.filter, binding) const types = domsToTypes(doms, _document, binding.opts.hooks, binding.filter, binding)
return type.insertAfter(prev, types) return type.insertAfter(prev, types)
} }
export function domsToTypes (doms, _document, hooks, filter, binding) { export const domsToTypes = (doms, _document, hooks, filter, binding) => {
const types = [] const types = []
for (let dom of doms) { for (let dom of doms) {
const t = domToType(dom, _document, hooks, filter, binding) const t = domToType(dom, _document, hooks, filter, binding)
@@ -103,7 +102,7 @@ export function domsToTypes (doms, _document, hooks, filter, binding) {
/** /**
* @private * @private
*/ */
export function insertNodeHelper (yxml, prevExpectedNode, child, _document, binding) { export const insertNodeHelper = (yxml, prevExpectedNode, child, _document, binding) => {
let insertedNodes = insertDomElementsAfter(yxml, prevExpectedNode, [child], _document, binding) let insertedNodes = insertDomElementsAfter(yxml, prevExpectedNode, [child], _document, binding)
if (insertedNodes.length > 0) { if (insertedNodes.length > 0) {
return insertedNodes[0] return insertedNodes[0]
@@ -115,14 +114,13 @@ export function insertNodeHelper (yxml, prevExpectedNode, child, _document, bind
/** /**
* Remove children until `elem` is found. * Remove children until `elem` is found.
* *
* @private
* @param {Element} parent The parent of `elem` and `currentChild`. * @param {Element} parent The parent of `elem` and `currentChild`.
* @param {Element} currentChild Start removing elements with `currentChild`. If * @param {Node} currentChild Start removing elements with `currentChild`. If
* `currentChild` is `elem` it won't be removed. * `currentChild` is `elem` it won't be removed.
* @param {Element|null} elem The elemnt to look for. * @param {Element|null} elem The elemnt to look for.
*
* @private
*/ */
export function removeDomChildrenUntilElementFound (parent, currentChild, elem) { export const removeDomChildrenUntilElementFound = (parent, currentChild, elem) => {
while (currentChild !== elem) { while (currentChild !== elem) {
const del = currentChild const del = currentChild
currentChild = currentChild.nextSibling currentChild = currentChild.nextSibling

299
bindings/prosemirror.mjs Normal file
View File

@@ -0,0 +1,299 @@
/**
* @module bindings/prosemirror
*/
import { BindMapping } from '../utils/BindMapping.mjs'
import { YText } from '../types/YText.mjs' // eslint-disable-line
import { YXmlElement, YXmlFragment } from '../types/YXmlElement.mjs' // eslint-disable-line
import { createMutex } from '../lib/mutex.mjs'
import * as PModel from 'prosemirror-model'
import { EditorView, Decoration, DecorationSet } from 'prosemirror-view' // eslint-disable-line
import { Plugin, PluginKey, EditorState } from 'prosemirror-state' // eslint-disable-line
/**
* @typedef {BindMapping<YText | YXmlElement, PModel.Node>} ProsemirrorMapping
*/
/**
* The unique prosemirror plugin key for prosemirrorPlugin.
*
* @public
*/
export const prosemirrorPluginKey = new PluginKey('yjs')
/**
* This plugin listens to changes in prosemirror view and keeps yXmlState and view in sync.
*
* This plugin also keeps references to the type and the shared document so other plugins can access it.
* @param {YXmlFragment} yXmlFragment
* @return {Plugin} Returns a prosemirror plugin that binds to this type
*/
export const prosemirrorPlugin = yXmlFragment => {
const pluginState = {
type: yXmlFragment,
y: yXmlFragment._y,
binding: null
}
const plugin = new Plugin({
key: prosemirrorPluginKey,
state: {
init: (initargs, state) => {
return pluginState
},
apply: (tr, pluginState) => {
return pluginState
}
},
view: view => {
const binding = new ProsemirrorBinding(yXmlFragment, view)
pluginState.binding = binding
return {
update: () => {
binding._prosemirrorChanged()
},
destroy: () => {
binding.destroy()
}
}
}
})
return plugin
}
/**
* The unique prosemirror plugin key for cursorPlugin.
*
* @public
*/
export const cursorPluginKey = new PluginKey('yjs-cursor')
/**
* A prosemirror plugin that listens to awareness information on Yjs.
* This requires that a `prosemirrorPlugin` is also bound to the prosemirror.
*
* @public
*/
export const cursorPlugin = new Plugin({
key: cursorPluginKey,
props: {
decorations: state => {
const y = prosemirrorPluginKey.getState(state).y
const awareness = y.getAwarenessInfo()
const decorations = []
awareness.forEach((state, userID) => {
if (state.cursor != null) {
const username = `User: ${userID}`
decorations.push(Decoration.widget(state.cursor.from, () => {
const cursor = document.createElement('span')
cursor.classList.add('ProseMirror-yjs-cursor')
const user = document.createElement('div')
user.insertBefore(document.createTextNode(username), null)
cursor.insertBefore(user, null)
return cursor
}, { key: username }))
decorations.push(Decoration.inline(state.cursor.from, state.cursor.to, { style: 'background-color: #ffa50070' }))
}
})
return DecorationSet.create(state.doc, decorations)
}
},
view: view => {
const y = prosemirrorPluginKey.getState(view.state).y
const awarenessListener = () => {
view.updateState(view.state)
}
y.on('awareness', awarenessListener)
return {
update: () => {
const y = prosemirrorPluginKey.getState(view.state).y
const from = view.state.selection.from
const to = view.state.selection.to
const current = y.getLocalAwarenessInfo()
if (current.cursor == null || current.cursor.to !== to || current.cursor.from !== from) {
y.setAwarenessField('cursor', {
from, to
})
}
},
destroy: () => {
const y = prosemirrorPluginKey.getState(view.state).y
y.setAwarenessField('cursor', null)
y.off('awareness', awarenessListener)
}
}
}
})
/**
* Binding for prosemirror.
*
* @protected
*/
export class ProsemirrorBinding {
/**
* @param {YXmlFragment} yXmlFragment The bind source
* @param {EditorView} prosemirrorView The target binding
*/
constructor (yXmlFragment, prosemirrorView) {
this.type = yXmlFragment
this.prosemirrorView = prosemirrorView
this.mux = createMutex()
/**
* @type {ProsemirrorMapping}
*/
this.mapping = new BindMapping()
this._observeFunction = this._typeChanged.bind(this)
yXmlFragment.observeDeep(this._observeFunction)
}
_typeChanged (events) {
if (events.length === 0) {
return
}
this.mux(() => {
events.forEach(event => {
// recompute node for each parent
// except main node, compute main node in the end
let target = event.target
if (target !== this.type) {
do {
if (target.constructor === YXmlElement) {
createNodeFromYElement(target, this.prosemirrorView.state.schema, this.mapping)
}
target = target._parent
} while (target._parent !== this.type)
}
})
const fragmentContent = this.type.toArray().map(t => createNodeIfNotExists(t, this.prosemirrorView.state.schema, this.mapping))
const tr = this.prosemirrorView.state.tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(new PModel.Fragment(fragmentContent), 0, 0))
this.prosemirrorView.updateState(this.prosemirrorView.state.apply(tr))
})
}
_prosemirrorChanged () {
this.mux(() => {
updateYFragment(this.type, this.prosemirrorView.state, this.mapping)
})
}
destroy () {
this.type.unobserveDeep(this._observeFunction)
}
}
/**
* @private
* @param {Y.XmlElement} el
* @param {PModel.Schema} schema
* @param {ProsemirrorMapping} mapping
* @return {PModel.Node}
*/
export const createNodeIfNotExists = (el, schema, mapping) => {
const node = mapping.getY(el)
if (node === undefined) {
return createNodeFromYElement(el, schema, mapping)
}
return node
}
/**
* @private
* @param {Y.XmlElement} el
* @param {PModel.Schema} schema
* @param {ProsemirrorMapping} mapping
* @return {PModel.Node}
*/
export const createNodeFromYElement = (el, schema, mapping) => {
const children = []
el.toArray().forEach(type => {
if (type.constructor === YXmlElement) {
children.push(createNodeIfNotExists(type, schema, mapping))
} else {
children.concat(createTextNodesFromYText(type, schema, mapping)).forEach(textchild => children.push(textchild))
}
})
const node = schema.node(el.nodeName.toLowerCase(), el.getAttributes(), el.toArray().map(t => createNodeIfNotExists(t, schema, mapping)))
mapping.bind(el, node)
return node
}
/**
* @private
* @param {Y.Text} text
* @param {PModel.Schema} schema
* @param {ProsemirrorMapping} mapping
* @return {Array<PModel.Node>}
*/
export const createTextNodesFromYText = (text, schema, mapping) => {
const nodes = []
const deltas = text.toDelta()
for (let i = 0; i < deltas.length; i++) {
const delta = deltas[i]
const marks = []
for (let markName in delta.attributes) {
marks.push(schema.mark(markName, delta.attributes[markName]))
}
nodes.push(schema.text(delta.insert, marks))
}
if (nodes.length > 0) {
mapping.bind(text, nodes[0]) // only map to first child, all following children are also considered bound to this type
}
return nodes
}
/**
* @private
* @param {PModel.Node} node
* @param {ProsemirrorMapping} mapping
* @return {YXmlElement | YText}
*/
export const createTypeFromNode = (node, mapping) => {
let type
if (node.isText) {
type = new YText()
const attrs = {}
node.marks.forEach(mark => { attrs[mark.type.name] = mark.attrs })
type.insert(0, node.text, attrs)
} else {
type = new YXmlElement(node.type.name)
for (let key in node.attrs) {
type.setAttribute(key, node.attrs[key])
}
type.insert(0, node.content.content.map(node => createTypeFromNode(node, mapping)))
}
mapping.bind(type, node)
return type
}
/**
* @private
* @param {YXmlFragment} yDomFragment
* @param {EditorState} state
* @param {BindMapping} mapping
*/
const updateYFragment = (yDomFragment, state, mapping) => {
const pChildCnt = state.doc.content.childCount
const yChildren = yDomFragment.toArray()
const yChildCnt = yChildren.length
const minCnt = pChildCnt < yChildCnt ? pChildCnt : yChildCnt
let left = 0
let right = 0
// find number of matching elements from left
for (;left < minCnt; left++) {
if (state.doc.content.child(left) !== mapping.getY(yChildren[left])) {
break
}
}
// find number of matching elements from right
for (;right < minCnt; right++) {
if (state.doc.content.child(pChildCnt - right - 1) !== mapping.getY(yChildren[yChildCnt - right - 1])) {
break
}
}
if (left + right > pChildCnt) {
// nothing changed
return
}
yDomFragment._y.transact(() => {
// now update y to match editor state
yDomFragment.delete(left, yChildCnt - left - right)
yDomFragment.insert(left, state.doc.content.content.slice(left, pChildCnt - right).map(node => createTypeFromNode(node, mapping)))
})
}

View File

@@ -1,10 +1,14 @@
import Binding from '../Binding.js' /**
* @module bindings/quill
*/
function typeObserver (event) { import { createMutex } from '../lib/mutex.mjs'
const typeObserver = function (event) {
const quill = this.target const quill = this.target
// Force flush Quill changes. // Force flush Quill changes.
quill.update('yjs') quill.update('yjs')
this._mutualExclude(function () { this._mutualExclude(() => {
// Apply computed delta. // Apply computed delta.
quill.updateContents(event.delta, 'yjs') quill.updateContents(event.delta, 'yjs')
// Force flush Quill changes. Ignore applied changes. // Force flush Quill changes. Ignore applied changes.
@@ -12,7 +16,7 @@ function typeObserver (event) {
}) })
} }
function quillObserver (delta) { const quillObserver = function (delta) {
this._mutualExclude(() => { this._mutualExclude(() => {
this.type.applyDelta(delta.ops) this.type.applyDelta(delta.ops)
}) })
@@ -28,14 +32,28 @@ function quillObserver (delta) {
* // Now modifications on the DOM will be reflected in the Type, and the other * // Now modifications on the DOM will be reflected in the Type, and the other
* // way around! * // way around!
*/ */
export default class QuillBinding extends Binding { export class QuillBinding {
/** /**
* @param {YText} textType * @param {YText} textType
* @param {Quill} quill * @param {Quill} quill
*/ */
constructor (textType, quill) { constructor (textType, quill) {
// Binding handles textType as this.type and quill as this.target. // Binding handles textType as this.type and quill as this.target.
super(textType, quill) /**
* The Yjs type that is bound to `target`
* @type {YText}
*/
this.type = textType
/**
* The target that `type` is bound to.
* @type {Quill}
*/
this.target = quill
/**
* @private
*/
this._mutualExclude = createMutex()
// Set initial value. // Set initial value.
quill.setContents(textType.toDelta(), 'yjs') quill.setContents(textType.toDelta(), 'yjs')
// Observers are handled by this class. // Observers are handled by this class.
@@ -48,6 +66,7 @@ export default class QuillBinding extends Binding {
// Remove everything that is handled by this class. // Remove everything that is handled by this class.
this.type.unobserve(this._typeObserver) this.type.unobserve(this._typeObserver)
this.target.off('text-change', this._quillObserver) this.target.off('text-change', this._quillObserver)
super.destroy() this.type = null
this.target = null
} }
} }

View File

@@ -1,7 +1,10 @@
/**
* @module bindings/textarea
*/
import Binding from '../Binding.js' import { simpleDiff } from '../lib/diff.mjs'
import simpleDiff from '../../../lib/simpleDiff.js' import { getRelativePosition, fromRelativePosition } from '../utils/relativePosition.mjs'
import { getRelativePosition, fromRelativePosition } from '../../Util/relativePosition.js' import { createMutex } from '../lib/mutex.mjs'
function typeObserver () { function typeObserver () {
this._mutualExclude(() => { this._mutualExclude(() => {
@@ -35,10 +38,22 @@ function domObserver () {
* const binding = new Y.QuillBinding(type, textarea) * const binding = new Y.QuillBinding(type, textarea)
* *
*/ */
export default class TextareaBinding extends Binding { export class TextareaBinding {
constructor (textType, domTextarea) { constructor (textType, domTextarea) {
// Binding handles textType as this.type and domTextarea as this.target /**
super(textType, domTextarea) * The Yjs type that is bound to `target`
* @type {Type}
*/
this.type = textType
/**
* The target that `type` is bound to.
* @type {*}
*/
this.target = domTextarea
/**
* @private
*/
this._mutualExclude = createMutex()
// set initial value // set initial value
domTextarea.value = textType.toString() domTextarea.value = textType.toString()
// Observers are handled by this class // Observers are handled by this class
@@ -51,6 +66,7 @@ export default class TextareaBinding extends Binding {
// Remove everything that is handled by this class // Remove everything that is handled by this class
this.type.unobserve(this._typeObserver) this.type.unobserve(this._typeObserver)
this.target.unobserve(this._domObserver) this.target.unobserve(this._domObserver)
super.destroy() this.type = null
this.target = null
} }
} }

1
examples/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build

View File

@@ -1,19 +0,0 @@
{
"name": "yjs-examples",
"version": "0.0.0",
"homepage": "y-js.org",
"authors": [
"Kevin Jahns <kevin.jahns@rwth-aachen.de>"
],
"description": "Examples for Yjs",
"license": "MIT",
"ignore": [],
"dependencies": {
"quill": "^1.0.0-rc.2",
"ace": "~1.2.3",
"ace-builds": "~1.2.3",
"jquery": "~2.2.2",
"d3": "^3.5.16",
"codemirror": "^5.25.0"
}
}

31
examples/dom.html Normal file
View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<title>Yjs Prosemirror Example</title>
<link rel=stylesheet href="https://prosemirror.net/css/editor.css">
<style>
#content {
min-height: 500px;
}
</style>
</head>
<body>
<div class="code-html">
<div id="content" contenteditable=""></div>
</div>
<script class="code-js" src="./build/dom.js">
import * as Y from 'yjs/index.mjs'
import { WebsocketProvider } from 'yjs/provider/websocket.mjs'
import { DomBinding } from 'yjs/bindings/dom.mjs'
const provider = new WebsocketProvider('wss://api.yjs.website')
const ydocument = provider.get('dom')
const type = ydocument.define('xml', Y.XmlFragment)
const binding = new DomBinding(type, document.querySelector('#content'), { scrollingElement: document.scrollingElement })
window.example = {
provider, ydocument, type, binding
}
</script>
</body>
</html>

12
examples/dom.mjs Normal file
View File

@@ -0,0 +1,12 @@
import * as Y from '../index.mjs'
import { WebsocketProvider } from '../provider/websocket.mjs'
import { DomBinding } from '../bindings/dom.mjs'
const provider = new WebsocketProvider('wss://api.yjs.website')
const ydocument = provider.get('dom')
const type = ydocument.define('xml', Y.XmlFragment)
const binding = new DomBinding(type, document.querySelector('#content'), { scrollingElement: document.scrollingElement })
window.example = {
provider, ydocument, type, binding
}

14
examples/examples.json Normal file
View File

@@ -0,0 +1,14 @@
{
"prosemirror": {
"title": "Prosemirror Binding"
},
"textarea": {
"title": "Textarea Binding"
},
"quill": {
"title": "Quill Binding"
},
"dom": {
"title": "Dom Binding"
}
}

View File

@@ -1,55 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<style>
.wrapper {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 7px;
}
.one {
grid-column: 1 ;
}
.two {
grid-column: 2;
}
.three {
grid-column: 3;
}
textarea {
width: calc(100% - 10px)
}
.editor-container {
background-color: #4caf50;
padding: 4px 5px 10px 5px;
border-radius: 11px;
}
.editor-container[disconnected] {
background-color: red;
}
.disconnected-info {
display: none;
}
.editor-container[disconnected] .disconnected-info {
display: inline;
}
</style>
<div class="wrapper">
<div id="container1" class="one editor-container">
<h1>Server 1 <span class="disconnected-info">(disconnected)</span></h1>
<textarea id="textarea1" rows=40 autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
</div>
<div id="container2" class="two editor-container">
<h1>Server 2 <span class="disconnected-info">(disconnected)</span></h1>
<textarea id="textarea2" rows=40 autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
</div>
<div id="container3" class="three editor-container">
<h1>Server 3 <span class="disconnected-info">(disconnected)</span></h1>
<textarea id="textarea3" rows=40 autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
</div>
</div>
<script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="./index.js"></script>
</body>
</html>

View File

@@ -1,38 +0,0 @@
/* global Y */
function bindYjsInstance (y, suffix) {
y.define('textarea', Y.Text).bind(document.getElementById('textarea' + suffix))
y.connector.socket.on('connection', function () {
document.getElementById('container' + suffix).removeAttribute('disconnected')
})
y.connector.socket.on('disconnect', function () {
document.getElementById('container' + suffix).setAttribute('disconnected', true)
})
}
let y1 = new Y('infinite-example', {
connector: {
name: 'websockets-client',
url: 'http://127.0.0.1:1234'
}
})
window.y1 = y1
bindYjsInstance(y1, '1')
let y2 = new Y('infinite-example', {
connector: {
name: 'websockets-client',
url: 'http://127.0.0.1:1234'
}
})
window.y2 = y2
bindYjsInstance(y2, '2')
let y3 = new Y('infinite-example', {
connector: {
name: 'websockets-client',
url: 'http://127.0.0.1:1234'
}
})
window.y3 = y3
bindYjsInstance(y1, '3')

View File

@@ -1,23 +0,0 @@
{
"name": "examples",
"version": "0.0.0",
"description": "",
"scripts": {
"dist": "rollup -c",
"watch": "rollup -cw"
},
"author": "Kevin Jahns",
"license": "MIT",
"dependencies": {
"monaco-editor": "^0.8.3",
"rollup": "^0.52.3"
},
"devDependencies": {
"standard": "^10.0.2"
},
"standard": {
"ignore": [
"bower_components"
]
}
}

73
examples/prosemirror.html Normal file
View File

@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html>
<head>
<title>Yjs Prosemirror Example</title>
<link rel=stylesheet href="https://prosemirror.net/css/editor.css">
<style>
placeholder {
display: inline;
border: 1px solid #ccc;
color: #ccc;
}
placeholder:after {
content: "☁";
font-size: 200%;
line-height: 0.1;
font-weight: bold;
}
.ProseMirror img { max-width: 100px }
.ProseMirror-yjs-cursor {
position: absolute;
border-left: black;
border-left-style: solid;
border-left-width: 2px;
border-color: orange;
height: 1em;
}
.ProseMirror-yjs-cursor > div {
position: relative;
top: -1.05em;
font-size: 13px;
background-color: rgb(250, 129, 0);
font-family: serif;
font-style: normal;
font-weight: normal;
line-height: normal;
user-select: none;
color: white;
padding-left: 2px;
padding-right: 2px;
}
</style>
</head>
<body>
<div class="code-html">
<div id="editor" style="margin-bottom: 23px"></div>
<div style="display: none" id="content"></div>
</div>
<script class="code-js" src="./build/prosemirror.js">
import * as Y from 'yjs'
import { WebsocketProvider } from '../provider/websocket.mjs'
import { prosemirrorPlugin, cursorPlugin } from '../bindings/prosemirror'
import { EditorState } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import { DOMParser } from 'prosemirror-model'
import { schema } from 'prosemirror-schema-basic'
import { exampleSetup } from 'prosemirror-example-setup'
const provider = new WebsocketProvider('wss://api.yjs.website')
const ydocument = provider.get('prosemirror')
const type = ydocument.define('prosemirror', Y.XmlFragment)
const prosemirrorView = new EditorView(document.querySelector('#editor'), {
state: EditorState.create({
doc: DOMParser.fromSchema(schema).parse(document.querySelector('#content')),
plugins: exampleSetup({schema}).concat([prosemirrorPlugin(type), cursorPlugin])
})
})
window.example = { provider, ydocument, type, prosemirrorView }
</script>
</body>
</html>

22
examples/prosemirror.mjs Normal file
View File

@@ -0,0 +1,22 @@
import * as Y from '../index.mjs'
import { WebsocketProvider } from '../provider/websocket.mjs'
import { prosemirrorPlugin, cursorPlugin } from '../bindings/prosemirror'
import { EditorState } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import { DOMParser } from 'prosemirror-model'
import { schema } from 'prosemirror-schema-basic'
import { exampleSetup } from 'prosemirror-example-setup'
const provider = new WebsocketProvider('wss://api.yjs.website')
const ydocument = provider.get('prosemirror')
const type = ydocument.define('prosemirror', Y.XmlFragment)
const prosemirrorView = new EditorView(document.querySelector('#editor'), {
state: EditorState.create({
doc: DOMParser.fromSchema(schema).parse(document.querySelector('#content')),
plugins: exampleSetup({schema}).concat([prosemirrorPlugin(type), cursorPlugin])
})
})
window.example = { provider, ydocument, type, prosemirrorView }

47
examples/quill.html Normal file
View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<title>Yjs Prosemirror Example</title>
<link rel=stylesheet href="https://prosemirror.net/css/editor.css">
</head>
<body>
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<div class="code-html">
<div id="quill-container">
<div id="quill">
</div>
</div>
</div>
<script class="code-js" src="./build/quill.js">
import * as Y from 'yjs'
import { WebsocketProvider } from 'yjs/provider/websocket.mjs'
import { QuillBinding } from 'yjs/bindings/quill.mjs'
import Quill from 'quill'
const provider = new WebsocketProvider('wss://api.yjs.website')
const ydocument = provider.get('quill')
const ytext = ydocument.define('quill', Y.Text)
const quill = new Quill('#quill-container', {
modules: {
toolbar: [
[{ header: [1, 2, false] }],
['bold', 'italic', 'underline'],
['image', 'code-block'],
[{ color: [] }, { background: [] }], // Snow theme fills in values
[{ script: 'sub' }, { script: 'super' }],
['link', 'image'],
['link', 'code-block'],
[{ list: 'ordered' }, { list: 'bullet' }]
]
},
placeholder: 'Compose an epic...',
theme: 'snow' // or 'bubble'
})
window.quillBinding = new QuillBinding(ytext, quill)
</script>
</body>
</html>

View File

@@ -1,13 +1,14 @@
/* global Y, Quill */ import * as Y from '../index.mjs'
import { WebsocketProvider } from '../provider/websocket.mjs'
import { QuillBinding } from '../bindings/quill.mjs'
let y = new Y('quill-cursors-0', { import Quill from 'quill'
connector: {
name: 'websockets-client',
url: 'http://127.0.0.1:1234'
}
})
let quill = new Quill('#quill-container', { const provider = new WebsocketProvider('wss://api.yjs.website')
const ydocument = provider.get('quill')
const ytext = ydocument.define('quill', Y.Text)
const quill = new Quill('#quill-container', {
modules: { modules: {
toolbar: [ toolbar: [
[{ header: [1, 2, false] }], [{ header: [1, 2, false] }],
@@ -24,10 +25,4 @@ let quill = new Quill('#quill-container', {
theme: 'snow' // or 'bubble' theme: 'snow' // or 'bubble'
}) })
let yText = y.define('quill', Y.Text) window.quillBinding = new QuillBinding(ytext, quill)
let quillBinding = new Y.QuillBinding(yText, quill)
window.quillBinding = quillBinding
window.yText = yText
window.y = y
window.quill = quill

View File

@@ -1,18 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<!-- Main Quill library -->
<script src="../../node_modules/quill/dist/quill.min.js"></script>
<link href="../../node_modules/quill/dist/quill.snow.css" rel="stylesheet">
<!-- Yjs Library and connector -->
<script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
</head>
<body>
<div id="quill-container">
<div id="quill">
</div>
</div>
<script src="./index.js"></script>
</body>
</html>

View File

@@ -1,29 +0,0 @@
import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
var pkg = require('./package.json')
export default {
input: 'yjs-dist.js',
name: 'Y',
output: {
file: 'yjs-dist.js',
format: 'umd'
},
plugins: [
nodeResolve({
main: true,
module: true,
browser: true
}),
commonjs()
],
sourcemap: true,
banner: `
/**
* ${pkg.name} - ${pkg.description}
* @version v${pkg.version}
* @license ${pkg.license}
*/
`
}

29
examples/style.css Normal file
View File

@@ -0,0 +1,29 @@
footer img {
display: none;
}
nav .title h1 a {
display: none;
}
footer {
background-color: #b93c1d;
}
#resizer {
background-color: #b93c1d;
}
.main section article.readme h1:first-child img {
display: none;
}
.main section article.readme h1:first-child {
margin-bottom: 16px;
margin-top: 30px;
}
.main section article.readme h1:first-child::before {
content: "Yjs";
font-size: 2em;
}

27
examples/textarea.html Normal file
View File

@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<title>Yjs Textarea Example</title>
<link rel=stylesheet href="https://prosemirror.net/css/editor.css">
</head>
<body>
<div class="code-html">
<textarea style="width:80%;" rows=40 autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
</div>
<script class="code-js" src="./build/textarea.js">
import * as Y from 'yjs'
import { WebsocketProvider } from 'yjs/provider/websocket.mjs'
import { TextareaBinding } from 'yjs/bindings/textarea.mjs'
const provider = new WebsocketProvider('wss://api.yjs.website')
const ydocument = provider.get('textarea')
const type = ydocument.define('textarea', Y.Text)
const textarea = document.querySelector('textarea')
const binding = new TextareaBinding(type, textarea)
window.textareaExample = {
provider, ydocument, type, textarea, binding
}
</script>
</body>
</html>

13
examples/textarea.mjs Normal file
View File

@@ -0,0 +1,13 @@
import * as Y from '../index.mjs'
import { WebsocketProvider } from '../provider/websocket.mjs'
import { TextareaBinding } from '../bindings/textarea.mjs'
const provider = new WebsocketProvider('wss://api.yjs.website')
const ydocument = provider.get('textarea')
const type = ydocument.define('textarea', Y.Text)
const textarea = document.querySelector('textarea')
const binding = new TextareaBinding(type, textarea)
window.textareaExample = {
provider, ydocument, type, textarea, binding
}

View File

@@ -1,7 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<textarea style="width:80%;" rows=40 autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea>
<script type="module" src="./index.js"></script>
</body>
</html>

View File

@@ -1,13 +0,0 @@
/* eslint-env browser */
import * as Y from '../../src/index.js'
import WebsocketProvider from '../../provider/websocket/WebSocketProvider.js'
const provider = new WebsocketProvider('ws://localhost:1234/')
const ydocument = provider.get('textarea')
const type = ydocument.define('textarea', Y.Text)
const textarea = document.querySelector('textarea')
const binding = new Y.TextareaBinding(type, textarea)
window.textareaExample = {
provider, ydocument, type, textarea, binding
}

View File

@@ -1,43 +0,0 @@
<!DOCTYPE html>
<html>
</head>
<!-- jquery is not required for YXml. It is just here for convenience, and to test batch operations. -->
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script>
<script src="./index.js"></script>
</head>
<body>
<h1> Shared DOM Example </h1>
<p> Use native DOM function or jQuery to manipulate the shared DOM (window.sharedDom). </p>
<div class="command">
<button type="button">Execute</button>
<input type="text" value='$(sharedDom).append("<h3>Appended headline</h3>")' size="40"/>
</div>
<div class="command">
<button type="button">Execute</button>
<input type="text" value='$(sharedDom).attr("align","right")' size="40"/>
</div>
<div class="command">
<button type="button">Execute</button>
<input type="text" value='$(sharedDom).attr("style","color:blue;")' size="40"/>
</div>
<script>
/* global $ */
var commands = document.querySelectorAll('.command')
Array.prototype.forEach.call(commands, function (command) {
var execute = function () {
// eslint-disable-next-line no-eval
eval(command.querySelector('input').value)
}
command.querySelector('button').onclick = execute
$(command.querySelector('input')).keyup(function (e) {
if (e.keyCode === 13) {
execute()
}
})
})
</script>
</body>
</html>

View File

@@ -1,13 +0,0 @@
/* global Y */
let y = new Y('xml-example', {
connector: {
name: 'websockets-client',
url: 'http://127.0.0.1:1234'
}
})
window.yXml = y
// bind xml type to a dom, and put it in body
window.sharedDom = y.define('xml', Y.XmlElement).toDom()
document.body.appendChild(window.sharedDom)

View File

@@ -25,7 +25,7 @@
<div id="aceContainer"></div> <div id="aceContainer"></div>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script> <script src='../../../y-websockets-client/y-websockets-client.mjs'></script>
<script src="../bower_components/ace-builds/src/ace.js"></script> <script src="../bower_components/ace-builds/src/ace.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>

View File

@@ -13,7 +13,7 @@
<input type="submit" value="Send"> <input type="submit" value="Send">
</form> </form>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script> <script src='../../../y-websockets-client/y-websockets-client.mjs'></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>
</html> </html>

View File

@@ -14,7 +14,7 @@ let chatprotocol = y.define('chatprotocol', Y.Array)
let chatcontainer = document.querySelector('#chat') let chatcontainer = document.querySelector('#chat')
// This functions inserts a message at the specified position in the DOM // This functions inserts a message at the specified position in the DOM
function appendMessage (message, position) { const appendMessage = (message, position) => {
var p = document.createElement('p') var p = document.createElement('p')
var uname = document.createElement('span') var uname = document.createElement('span')
uname.appendChild(document.createTextNode(message.username + ': ')) uname.appendChild(document.createTextNode(message.username + ': '))
@@ -25,7 +25,7 @@ function appendMessage (message, position) {
// This function makes sure that only 7 messages exist in the chat history. // This function makes sure that only 7 messages exist in the chat history.
// The rest is deleted // The rest is deleted
function cleanupChat () { const cleanupChat = () => {
if (chatprotocol.length > 7) { if (chatprotocol.length > 7) {
chatprotocol.delete(0, chatprotocol.length - 7) chatprotocol.delete(0, chatprotocol.length - 7)
} }
@@ -36,7 +36,7 @@ cleanupChat()
chatprotocol.toArray().forEach(appendMessage) chatprotocol.toArray().forEach(appendMessage)
// whenever content changes, make sure to reflect the changes in the DOM // whenever content changes, make sure to reflect the changes in the DOM
chatprotocol.observe(function (event) { chatprotocol.observe(event => {
// concurrent insertions may result in a history > 7, so cleanup here // concurrent insertions may result in a history > 7, so cleanup here
cleanupChat() cleanupChat()
chatcontainer.innerHTML = '' chatcontainer.innerHTML = ''

View File

@@ -6,7 +6,7 @@
<div id="codeMirrorContainer"></div> <div id="codeMirrorContainer"></div>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script> <script src='../../../y-websockets-client/y-websockets-client.mjs'></script>
<script src="../bower_components/codemirror/lib/codemirror.js"></script> <script src="../bower_components/codemirror/lib/codemirror.js"></script>
<script src="../bower_components/codemirror/mode/javascript/javascript.js"></script> <script src="../bower_components/codemirror/mode/javascript/javascript.js"></script>
<link rel="stylesheet" href="../bower_components/codemirror/lib/codemirror.css"> <link rel="stylesheet" href="../bower_components/codemirror/lib/codemirror.css">

View File

@@ -13,7 +13,7 @@
<button type="button" id="clearDrawingCanvas">Clear Drawing</button> <button type="button" id="clearDrawingCanvas">Clear Drawing</button>
<svg id="drawingCanvas" viewbox="0 0 100 100" width="100%"></svg> <svg id="drawingCanvas" viewbox="0 0 100 100" width="100%"></svg>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script> <script src='../../../y-websockets-client/y-websockets-client.mjs'></script>
<script src="../bower_components/d3/d3.min.js"></script> <script src="../bower_components/d3/d3.min.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>

View File

@@ -2,7 +2,7 @@
<html> <html>
</head> </head>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script> <script src='../../../y-websockets-client/y-websockets-client.mjs'></script>
<script src="../bower_components/d3/d3.min.js"></script> <script src="../bower_components/d3/d3.min.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
<style> <style>

View File

@@ -1,12 +1,12 @@
import YWebsocketsConnector from '../../src/Connectors/WebsocketsConnector/WebsocketsConnector.js' import YWebsocketsConnector from '../../src/Connectors/WebsocketsConnector/WebsocketsConnector.mjs'
import Y from '../../src/Y.js' import Y from '../../src/Y.mjs'
import DomBinding from '../../src/Bindings/DomBinding/DomBinding.js' import DomBinding from '../../bindings/DomBinding/DomBinding.mjs'
import UndoManager from '../../src/Util/UndoManager.js' import UndoManager from '../../src/Util/UndoManager.mjs'
import YXmlFragment from '../../src/Types/YXml/YXmlFragment.js' import YXmlFragment from '../../src/Types/YXml/YXmlFragment.mjs'
import YXmlText from '../../src/Types/YXml/YXmlText.js' import YXmlText from '../../src/Types/YXml/YXmlText.mjs'
import YXmlElement from '../../src/Types/YXml/YXmlElement.js' import YXmlElement from '../../src/Types/YXml/YXmlElement.mjs'
import YIndexdDBPersistence from '../../src/Persistences/IndexedDBPersistence.js' import YIndexdDBPersistence from '../../src/Persistences/IndexedDBPersistence.mjs'
const connector = new YWebsocketsConnector() const connector = new YWebsocketsConnector()
const persistence = new YIndexdDBPersistence() const persistence = new YIndexdDBPersistence()

View File

@@ -17,8 +17,7 @@
} }
</style> </style>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script> <script src='../../../y-websockets-client/y-websockets-client.mjs'></script>
<script src='../../../y-indexeddb/y-indexeddb.js'></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>
</html> </html>

View File

@@ -17,7 +17,7 @@
</g> </g>
</svg> </svg>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script> <script src='../../../y-websockets-client/y-websockets-client.mjs'></script>
<script src="../bower_components/d3/d3.js"></script> <script src="../bower_components/d3/d3.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>

View File

@@ -14,7 +14,7 @@
} }
</style> </style>
<script src="../../y.js"></script> <script src="../../y.js"></script>
<script src='../../../y-websockets-client/y-websockets-client.js'></script> <script src='../../../y-websockets-client/y-websockets-client.mjs'></script>
<script src="../node_modules/monaco-editor/min/vs/loader.js"></script> <script src="../node_modules/monaco-editor/min/vs/loader.js"></script>
<script src="./index.js"></script> <script src="./index.js"></script>
</body> </body>

View File

@@ -1,9 +1,9 @@
/* eslint-env browser */ /* eslint-env browser */
import { createYdbClient } from '../../YdbClient/index.js' import { createYdbClient } from '../../YdbClient/index.mjs'
import Y from '../../src/Y.dist.js' import Y from '../../src/Y.dist.mjs'
import * as ydb from '../../YdbClient/YdbClient.js' import * as ydb from '../../YdbClient/YdbClient.mjs'
import DomBinding from '../../src/Bindings/DomBinding/DomBinding.js' import DomBinding from '../../bindings/DomBinding/DomBinding.mjs'
const uuidv4 = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const uuidv4 = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = Math.random() * 16 | 0 const r = Math.random() * 16 | 0

View File

@@ -36,13 +36,13 @@ let quill = new Quill('#quill-container', {
let cursors = quill.getModule('cursors') let cursors = quill.getModule('cursors')
function drawCursors () { const drawCursors = () => {
cursors.clearCursors() cursors.clearCursors()
users.map((user, userId) => { users.map((user, userId) => {
if (user !== myUserInfo) { if (user !== myUserInfo) {
let relativeRange = user.get('range') let relativeRange = user.get('range')
let lastUpdated = new Date(user.get('last updated')) let lastUpdated = new Date(user.get('last updated')).getTime()
if (lastUpdated != null && new Date() - lastUpdated < 20000 && relativeRange != null) { if (lastUpdated != null && new Date().getTime() - lastUpdated < 20000 && relativeRange != null) {
let start = Y.utils.fromRelativePosition(y, relativeRange.start).offset let start = Y.utils.fromRelativePosition(y, relativeRange.start).offset
let end = Y.utils.fromRelativePosition(y, relativeRange.end).offset let end = Y.utils.fromRelativePosition(y, relativeRange.end).offset
let range = { index: start, length: end - start } let range = { index: start, length: end - start }

50
index.mjs Normal file
View File

@@ -0,0 +1,50 @@
import { Delete } from './structs/Delete.mjs'
import { ItemJSON } from './structs/ItemJSON.mjs'
import { ItemString } from './structs/ItemString.mjs'
import { ItemFormat } from './structs/ItemFormat.mjs'
import { ItemEmbed } from './structs/ItemEmbed.mjs'
import { GC } from './structs/GC.mjs'
import { YArray } from './types/YArray.mjs'
import { YMap } from './types/YMap.mjs'
import { YText } from './types/YText.mjs'
import { YXmlText } from './types/YXmlText.mjs'
import { YXmlHook } from './types/YXmlHook.mjs'
import { YXmlElement, YXmlFragment } from './types/YXmlElement.mjs'
import { registerStruct } from './utils/structReferences.mjs'
export { Y } from './utils/Y.mjs'
export { UndoManager } from './utils/UndoManager.mjs'
export { Transaction } from './utils/Transaction.mjs'
export { YArray as Array } from './types/YArray.mjs'
export { YMap as Map } from './types/YMap.mjs'
export { YText as Text } from './types/YText.mjs'
export { YXmlText as XmlText } from './types/YXmlText.mjs'
export { YXmlHook as XmlHook } from './types/YXmlHook.mjs'
export { YXmlElement as XmlElement, YXmlFragment as XmlFragment } from './types/YXmlElement.mjs'
export { getRelativePosition, fromRelativePosition } from './utils/relativePosition.mjs'
export { registerStruct } from './utils/structReferences.mjs'
export * from './protocols/syncProtocol.mjs'
export * from './protocols/awarenessProtocol.mjs'
export * from './lib/encoding.mjs'
export * from './lib/decoding.mjs'
export * from './lib/mutex.mjs'
registerStruct(0, GC)
registerStruct(1, ItemJSON)
registerStruct(2, ItemString)
registerStruct(3, ItemFormat)
registerStruct(4, Delete)
registerStruct(5, YArray)
registerStruct(6, YMap)
registerStruct(7, YText)
registerStruct(8, YXmlFragment)
registerStruct(9, YXmlElement)
registerStruct(10, YXmlText)
registerStruct(11, YXmlHook)
registerStruct(12, ItemEmbed)

View File

@@ -2,7 +2,7 @@
/** /**
* Handles named events. * Handles named events.
*/ */
export default class NamedEventHandler { export class NamedEventHandler {
constructor () { constructor () {
this._eventListener = new Map() this._eventListener = new Map()
this._stateListener = new Map() this._stateListener = new Map()
@@ -57,7 +57,7 @@ export default class NamedEventHandler {
let state = this._stateListener.get(name) let state = this._stateListener.get(name)
if (state === undefined) { if (state === undefined) {
state = {} state = {}
state.promise = new Promise(function (resolve) { state.promise = new Promise(resolve => {
state.resolve = resolve state.resolve = resolve
}) })
this._stateListener.set(name, state) this._stateListener.set(name, state)

View File

@@ -1,5 +1,8 @@
/**
* @module tree
*/
function rotate (tree, parent, newParent, n) { const rotate = (tree, parent, newParent, n) => {
if (parent === null) { if (parent === null) {
tree.root = newParent tree.root = newParent
newParent._parent = null newParent._parent = null
@@ -111,10 +114,16 @@ class N {
} }
} }
const isBlack = node =>
node !== null ? node.isBlack() : true
const isRed = (node) =>
node !== null ? node.isRed() : false
/* /*
* This is a Red Black Tree implementation * This is a Red Black Tree implementation
*/ */
export default class Tree { export class Tree {
constructor () { constructor () {
this.root = null this.root = null
this.length = 0 this.length = 0
@@ -310,12 +319,6 @@ export default class Tree {
} }
} }
_fixDelete (n) { _fixDelete (n) {
function isBlack (node) {
return node !== null ? node.isBlack() : true
}
function isRed (node) {
return node !== null ? node.isRed() : false
}
if (n.parent === null) { if (n.parent === null) {
// this can only be called after the first iteration of fixDelete. // this can only be called after the first iteration of fixDelete.
return return

View File

@@ -1,3 +1,6 @@
/**
* @module binary
*/
export const BITS32 = 0xFFFFFFFF export const BITS32 = 0xFFFFFFFF
export const BITS21 = (1 << 21) - 1 export const BITS21 = (1 << 21) - 1

View File

@@ -1,7 +1,10 @@
/**
* @module decoding
*/
/* global Buffer */ /* global Buffer */
import * as globals from './globals.js' import * as globals from './globals.mjs'
/** /**
* A Decoder handles the decoding of an ArrayBuffer. * A Decoder handles the decoding of an ArrayBuffer.
@@ -17,17 +20,26 @@ export class Decoder {
} }
/** /**
* @function
* @param {ArrayBuffer} buffer * @param {ArrayBuffer} buffer
* @return {Decoder} * @return {Decoder}
*/ */
export const createDecoder = buffer => new Decoder(buffer) export const createDecoder = buffer => new Decoder(buffer)
/**
* @function
* @param {Decoder} decoder
* @return {boolean}
*/
export const hasContent = decoder => decoder.pos !== decoder.arr.length export const hasContent = decoder => decoder.pos !== decoder.arr.length
/** /**
* Clone a decoder instance. * Clone a decoder instance.
* Optionally set a new position parameter. * Optionally set a new position parameter.
*
* @function
* @param {Decoder} decoder The decoder instance * @param {Decoder} decoder The decoder instance
* @param {number} [newPos] Defaults to current position
* @return {Decoder} A clone of `decoder` * @return {Decoder} A clone of `decoder`
*/ */
export const clone = (decoder, newPos = decoder.pos) => { export const clone = (decoder, newPos = decoder.pos) => {
@@ -38,6 +50,7 @@ export const clone = (decoder, newPos = decoder.pos) => {
/** /**
* Read `len` bytes as an ArrayBuffer. * Read `len` bytes as an ArrayBuffer.
* @function
* @param {Decoder} decoder The decoder instance * @param {Decoder} decoder The decoder instance
* @param {number} len The length of bytes to read * @param {number} len The length of bytes to read
* @return {ArrayBuffer} * @return {ArrayBuffer}
@@ -52,6 +65,7 @@ export const readArrayBuffer = (decoder, len) => {
/** /**
* Read variable length payload as ArrayBuffer * Read variable length payload as ArrayBuffer
* @function
* @param {Decoder} decoder * @param {Decoder} decoder
* @return {ArrayBuffer} * @return {ArrayBuffer}
*/ */
@@ -59,6 +73,7 @@ export const readPayload = decoder => readArrayBuffer(decoder, readVarUint(decod
/** /**
* Read the rest of the content as an ArrayBuffer * Read the rest of the content as an ArrayBuffer
* @function
* @param {Decoder} decoder * @param {Decoder} decoder
* @return {ArrayBuffer} * @return {ArrayBuffer}
*/ */
@@ -66,6 +81,7 @@ export const readTail = decoder => readArrayBuffer(decoder, decoder.arr.length -
/** /**
* Skip one byte, jump to the next position. * Skip one byte, jump to the next position.
* @function
* @param {Decoder} decoder The decoder instance * @param {Decoder} decoder The decoder instance
* @return {number} The next position * @return {number} The next position
*/ */
@@ -73,6 +89,7 @@ export const skip8 = decoder => decoder.pos++
/** /**
* Read one byte as unsigned integer. * Read one byte as unsigned integer.
* @function
* @param {Decoder} decoder The decoder instance * @param {Decoder} decoder The decoder instance
* @return {number} Unsigned 8-bit integer * @return {number} Unsigned 8-bit integer
*/ */
@@ -81,6 +98,7 @@ export const readUint8 = decoder => decoder.arr[decoder.pos++]
/** /**
* Read 4 bytes as unsigned integer. * Read 4 bytes as unsigned integer.
* *
* @function
* @param {Decoder} decoder * @param {Decoder} decoder
* @return {number} An unsigned integer. * @return {number} An unsigned integer.
*/ */
@@ -98,6 +116,7 @@ export const readUint32 = decoder => {
* Look ahead without incrementing position. * Look ahead without incrementing position.
* to the next byte and read it as unsigned integer. * to the next byte and read it as unsigned integer.
* *
* @function
* @param {Decoder} decoder * @param {Decoder} decoder
* @return {number} An unsigned integer. * @return {number} An unsigned integer.
*/ */
@@ -109,6 +128,7 @@ export const peekUint8 = decoder => decoder.arr[decoder.pos]
* * numbers < 2^7 is stored in one bytlength * * numbers < 2^7 is stored in one bytlength
* * numbers < 2^14 is stored in two bylength * * numbers < 2^14 is stored in two bylength
* *
* @function
* @param {Decoder} decoder * @param {Decoder} decoder
* @return {number} An unsigned integer.length * @return {number} An unsigned integer.length
*/ */
@@ -137,6 +157,7 @@ export const readVarUint = decoder => {
* But most environments have a maximum number of arguments per functions. * But most environments have a maximum number of arguments per functions.
* For effiency reasons we apply a maximum of 10000 characters at once. * For effiency reasons we apply a maximum of 10000 characters at once.
* *
* @function
* @param {Decoder} decoder * @param {Decoder} decoder
* @return {String} The read String. * @return {String} The read String.
*/ */
@@ -157,6 +178,8 @@ export const readVarString = decoder => {
/** /**
* Look ahead and read varString without incrementing position * Look ahead and read varString without incrementing position
*
* @function
* @param {Decoder} decoder * @param {Decoder} decoder
* @return {string} * @return {string}
*/ */

View File

@@ -1,3 +1,6 @@
/**
* @module diff
*/
/** /**
* A SimpleDiff describes a change on a String. * A SimpleDiff describes a change on a String.
@@ -27,7 +30,7 @@
* @param {String} b The updated version of the string * @param {String} b The updated version of the string
* @return {SimpleDiff} The diff description. * @return {SimpleDiff} The diff description.
*/ */
export default function simpleDiff (a, b) { export const simpleDiff = (a, b) => {
let left = 0 // number of same characters counting from left let left = 0 // number of same characters counting from left
let right = 0 // number of same characters counting from right let right = 0 // number of same characters counting from right
while (left < a.length && left < b.length && a[left] === b[left]) { while (left < a.length && left < b.length && a[left] === b[left]) {

View File

@@ -1,5 +1,7 @@
/**
import * as globals from './globals.js' * @module encoding
*/
import * as globals from './globals.mjs'
const bits7 = 0b1111111 const bits7 = 0b1111111
const bits8 = 0b11111111 const bits8 = 0b11111111
@@ -15,10 +17,18 @@ export class Encoder {
} }
} }
/**
* @function
* @return {Encoder}
*/
export const createEncoder = () => new Encoder() export const createEncoder = () => new Encoder()
/** /**
* The current length of the encoded data. * The current length of the encoded data.
*
* @function
* @param {Encoder} encoder
* @return {number}
*/ */
export const length = encoder => { export const length = encoder => {
let len = encoder.cpos let len = encoder.cpos
@@ -30,6 +40,8 @@ export const length = encoder => {
/** /**
* Transform to ArrayBuffer. TODO: rename to .toArrayBuffer * Transform to ArrayBuffer. TODO: rename to .toArrayBuffer
*
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @return {ArrayBuffer} The created ArrayBuffer. * @return {ArrayBuffer} The created ArrayBuffer.
*/ */
@@ -48,6 +60,7 @@ export const toBuffer = encoder => {
/** /**
* Write one byte to the encoder. * Write one byte to the encoder.
* *
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {number} num The byte that is to be encoded. * @param {number} num The byte that is to be encoded.
*/ */
@@ -64,6 +77,7 @@ export const write = (encoder, num) => {
* Write one byte at a specific position. * Write one byte at a specific position.
* Position must already be written (i.e. encoder.length > pos) * Position must already be written (i.e. encoder.length > pos)
* *
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {number} pos Position to which to write data * @param {number} pos Position to which to write data
* @param {number} num Unsigned 8-bit integer * @param {number} num Unsigned 8-bit integer
@@ -89,6 +103,7 @@ export const set = (encoder, pos, num) => {
/** /**
* Write one byte as an unsigned integer. * Write one byte as an unsigned integer.
* *
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {number} num The number that is to be encoded. * @param {number} num The number that is to be encoded.
*/ */
@@ -97,6 +112,7 @@ export const writeUint8 = (encoder, num) => write(encoder, num & bits8)
/** /**
* Write one byte as an unsigned Integer at a specific location. * Write one byte as an unsigned Integer at a specific location.
* *
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {number} pos The location where the data will be written. * @param {number} pos The location where the data will be written.
* @param {number} num The number that is to be encoded. * @param {number} num The number that is to be encoded.
@@ -106,6 +122,7 @@ export const setUint8 = (encoder, pos, num) => set(encoder, pos, num & bits8)
/** /**
* Write two bytes as an unsigned integer. * Write two bytes as an unsigned integer.
* *
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {number} num The number that is to be encoded. * @param {number} num The number that is to be encoded.
*/ */
@@ -116,6 +133,7 @@ export const writeUint16 = (encoder, num) => {
/** /**
* Write two bytes as an unsigned integer at a specific location. * Write two bytes as an unsigned integer at a specific location.
* *
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {number} pos The location where the data will be written. * @param {number} pos The location where the data will be written.
* @param {number} num The number that is to be encoded. * @param {number} num The number that is to be encoded.
@@ -128,6 +146,7 @@ export const setUint16 = (encoder, pos, num) => {
/** /**
* Write two bytes as an unsigned integer * Write two bytes as an unsigned integer
* *
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {number} num The number that is to be encoded. * @param {number} num The number that is to be encoded.
*/ */
@@ -141,6 +160,7 @@ export const writeUint32 = (encoder, num) => {
/** /**
* Write two bytes as an unsigned integer at a specific location. * Write two bytes as an unsigned integer at a specific location.
* *
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {number} pos The location where the data will be written. * @param {number} pos The location where the data will be written.
* @param {number} num The number that is to be encoded. * @param {number} num The number that is to be encoded.
@@ -157,6 +177,7 @@ export const setUint32 = (encoder, pos, num) => {
* *
* Encodes integers in the range from [0, 4294967295] / [0, 0xffffffff]. (max 32 bit unsigned integer) * Encodes integers in the range from [0, 4294967295] / [0, 0xffffffff]. (max 32 bit unsigned integer)
* *
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {number} num The number that is to be encoded. * @param {number} num The number that is to be encoded.
*/ */
@@ -171,6 +192,7 @@ export const writeVarUint = (encoder, num) => {
/** /**
* Write a variable length string. * Write a variable length string.
* *
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {String} str The string that is to be encoded. * @param {String} str The string that is to be encoded.
*/ */
@@ -188,6 +210,7 @@ export const writeVarString = (encoder, str) => {
* *
* TODO: can be improved! * TODO: can be improved!
* *
* @function
* @param {Encoder} encoder The enUint8Arr * @param {Encoder} encoder The enUint8Arr
* @param {Encoder} append The BinaryEncoder to be written. * @param {Encoder} append The BinaryEncoder to be written.
*/ */
@@ -196,6 +219,7 @@ export const writeBinaryEncoder = (encoder, append) => writeArrayBuffer(encoder,
/** /**
* Append an arrayBuffer to the encoder. * Append an arrayBuffer to the encoder.
* *
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {ArrayBuffer} arrayBuffer * @param {ArrayBuffer} arrayBuffer
*/ */
@@ -209,6 +233,7 @@ export const writeArrayBuffer = (encoder, arrayBuffer) => {
} }
/** /**
* @function
* @param {Encoder} encoder * @param {Encoder} encoder
* @param {ArrayBuffer} arrayBuffer * @param {ArrayBuffer} arrayBuffer
*/ */

View File

@@ -1,4 +1,4 @@
import * as encoding from './encoding.js' import * as encoding from './encoding.mjs'
/** /**
* Check if binary encoding is compatible with golang binary encoding - binary.PutVarUint. * Check if binary encoding is compatible with golang binary encoding - binary.PutVarUint.

View File

@@ -1,3 +1,7 @@
/**
* @module globals
*/
/* eslint-env browser */ /* eslint-env browser */
export const Uint8Array_ = Uint8Array export const Uint8Array_ = Uint8Array

View File

@@ -1,6 +1,10 @@
/**
* @module idb
*/
/* eslint-env browser */ /* eslint-env browser */
import * as globals from './globals.js' import * as globals from './globals.mjs'
/* /*
* IDB Request to Promise transformer * IDB Request to Promise transformer
@@ -12,6 +16,8 @@ export const rtop = request => globals.createPromise((resolve, reject) => {
}) })
/** /**
* @param {string} name
* @param {Function} initDB Called when the database is first created
* @return {Promise<IDBDatabase>} * @return {Promise<IDBDatabase>}
*/ */
export const openDB = (name, initDB) => globals.createPromise((resolve, reject) => { export const openDB = (name, initDB) => globals.createPromise((resolve, reject) => {
@@ -115,7 +121,7 @@ export const getAllKeysValues = (store, range) =>
* Iterate on keys and values * Iterate on keys and values
* @param {IDBObjectStore} store * @param {IDBObjectStore} store
* @param {IDBKeyRange?} keyrange * @param {IDBKeyRange?} keyrange
* @param {function(any, any)} f Return true in order to continue the cursor * @param {Function} f Return true in order to continue the cursor
*/ */
export const iterate = (store, keyrange, f) => globals.createPromise((resolve, reject) => { export const iterate = (store, keyrange, f) => globals.createPromise((resolve, reject) => {
const request = store.openCursor(keyrange) const request = store.openCursor(keyrange)
@@ -135,9 +141,10 @@ export const iterate = (store, keyrange, f) => globals.createPromise((resolve, r
/** /**
* Iterate on the keys (no values) * Iterate on the keys (no values)
*
* @param {IDBObjectStore} store * @param {IDBObjectStore} store
* @param {IDBKeyRange} keyrange * @param {IDBKeyRange} keyrange
* @param {function(IDBCursor)} f Call `idbcursor.continue()` to iterate further * @param {function} f Call `idbcursor.continue()` to iterate further
*/ */
export const iterateKeys = (store, keyrange, f) => { export const iterateKeys = (store, keyrange, f) => {
/** /**

View File

@@ -1,6 +1,6 @@
import * as test from './test.js' import * as test from './testing.mjs'
import * as idb from './idb.js' import * as idb from './idb.mjs'
import * as logging from './logging.js' import * as logging from './logging.mjs'
const initTestDB = db => idb.createStores(db, [['test']]) const initTestDB = db => idb.createStores(db, [['test']])
const testDBName = 'idb-test' const testDBName = 'idb-test'

View File

@@ -1,5 +1,8 @@
/**
* @module logging
*/
import * as globals from './globals.js' import * as globals from './globals.mjs'
let date = new Date().getTime() let date = new Date().getTime()

View File

@@ -1,2 +1,4 @@
/**
* @module math
*/
export const floor = Math.floor export const floor = Math.floor

View File

@@ -4,11 +4,10 @@
* *
* @example * @example
* const mutex = createMutex() * const mutex = createMutex()
* mutex(function () { * mutex(() => {
* // This function is immediately executed * // This function is immediately executed
* mutex(function () { * mutex(() => {
* // This function is never executed, as it is called with the same * // This function is not executed, as the mutex is already active.
* // mutex function
* }) * })
* }) * })
* *

View File

@@ -1,2 +1,6 @@
/**
* @module number
*/
export const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER export const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER
export const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER export const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER

View File

@@ -1,11 +1,12 @@
/**
* @module prng
*/
const N = 624 const N = 624
const M = 397 const M = 397
function twist (u, v) { const twist = (u, v) => ((((u & 0x80000000) | (v & 0x7fffffff)) >>> 1) ^ ((v & 1) ? 0x9908b0df : 0))
return ((((u & 0x80000000) | (v & 0x7fffffff)) >>> 1) ^ ((v & 1) ? 0x9908b0df : 0))
}
function nextState (state) { const nextState = (state) => {
let p = 0 let p = 0
let j let j
for (j = N - M + 1; --j; p++) { for (j = N - M + 1; --j; p++) {
@@ -29,7 +30,7 @@ function nextState (state) {
* *
* @public * @public
*/ */
export default class Mt19937 { export class Mt19937 {
/** /**
* @param {Number} seed The starting point for the random number generation. If you use the same seed, the generator will return the same sequence of random numbers. * @param {Number} seed The starting point for the random number generation. If you use the same seed, the generator will return the same sequence of random numbers.
*/ */

View File

@@ -1,13 +1,16 @@
/**
* @module prng
*/
import Mt19937 from './Mt19937.js' import { Mt19937 } from './Mt19937.mjs'
import Xoroshiro128plus from './Xoroshiro128plus.js' import { Xoroshiro128plus } from './Xoroshiro128plus.mjs'
import Xorshift32 from './Xorshift32.js' import { Xorshift32 } from './Xorshift32.mjs'
import * as time from '../../time.js' import * as time from '../../time.mjs'
const DIAMETER = 300 const DIAMETER = 300
const NUMBERS = 10000 const NUMBERS = 10000
function runPRNG (name, Gen) { const runPRNG = (name, Gen) => {
console.log('== ' + name + ' ==') console.log('== ' + name + ' ==')
const gen = new Gen(1234) const gen = new Gen(1234)
let head = 0 let head = 0

View File

@@ -1,5 +1,8 @@
/**
* @module prng
*/
import Xorshift32 from './Xorshift32.js' import { Xorshift32 } from './Xorshift32.mjs'
/** /**
* This is a variant of xoroshiro128plus - the fastest full-period generator passing BigCrush without systematic failures. * This is a variant of xoroshiro128plus - the fastest full-period generator passing BigCrush without systematic failures.
@@ -14,7 +17,7 @@ import Xorshift32 from './Xorshift32.js'
* *
* [Reference implementation](http://vigna.di.unimi.it/xorshift/xoroshiro128plus.c) * [Reference implementation](http://vigna.di.unimi.it/xorshift/xoroshiro128plus.c)
*/ */
export default class Xoroshiro128plus { export class Xoroshiro128plus {
constructor (seed) { constructor (seed) {
this.seed = seed this.seed = seed
// This is a variant of Xoroshiro128plus to fill the initial state // This is a variant of Xoroshiro128plus to fill the initial state

View File

@@ -1,8 +1,11 @@
/**
* @module prng
*/
/** /**
* Xorshift32 is a very simple but elegang PRNG with a period of `2^32-1`. * Xorshift32 is a very simple but elegang PRNG with a period of `2^32-1`.
*/ */
export default class Xorshift32 { export class Xorshift32 {
/** /**
* @param {number} seed The starting point for the random number generation. If you use the same seed, the generator will return the same sequence of random numbers. * @param {number} seed The starting point for the random number generation. If you use the same seed, the generator will return the same sequence of random numbers.
*/ */

View File

@@ -1,10 +1,13 @@
/**
* @module prng
*/
import * as binary from '../binary.js' import * as binary from '../binary.mjs'
import { fromCharCode, fromCodePoint } from '../string.js' import { fromCharCode, fromCodePoint } from '../string.mjs'
import { MAX_SAFE_INTEGER, MIN_SAFE_INTEGER } from '../number.js' import { MAX_SAFE_INTEGER, MIN_SAFE_INTEGER } from '../number.mjs'
import * as math from '../math.js' import * as math from '../math.mjs'
import DefaultPRNG from './PRNG/Xoroshiro128plus.js' import { Xoroshiro128plus as DefaultPRNG } from './PRNG/Xoroshiro128plus.mjs'
/** /**
* Description of the function * Description of the function

134
lib/prng/prng.mjs Normal file
View File

@@ -0,0 +1,134 @@
/**
* @module prng
*/
import * as binary from '../binary.mjs'
import { fromCharCode, fromCodePoint } from '../string.mjs'
import { MAX_SAFE_INTEGER, MIN_SAFE_INTEGER } from '../number.mjs'
import * as math from '../math.mjs'
import { Xoroshiro128plus as DefaultPRNG } from './PRNG/Xoroshiro128plus.mjs'
/**
* Description of the function
* @callback generatorNext
* @return {number} A 32bit integer
*/
/**
* A random type generator.
*
* @typedef {Object} PRNG
* @property {generatorNext} next Generate new number
*/
/**
* Create a Xoroshiro128plus Pseudo-Random-Number-Generator.
* This is the fastest full-period generator passing BigCrush without systematic failures.
* But there are more PRNGs available in ./PRNG/.
*
* @param {number} seed A positive 32bit integer. Do not use negative numbers.
* @return {PRNG}
*/
export const createPRNG = seed => new DefaultPRNG(Math.floor(seed < 1 ? seed * binary.BITS32 : seed))
/**
* Generates a single random bool.
*
* @param {PRNG} gen A random number generator.
* @return {Boolean} A random boolean
*/
export const bool = gen => (gen.next() & 2) === 2 // brackets are non-optional!
/**
* Generates a random integer with 53 bit resolution.
*
* @param {PRNG} gen A random number generator.
* @param {Number} [min = MIN_SAFE_INTEGER] The lower bound of the allowed return values (inclusive).
* @param {Number} [max = MAX_SAFE_INTEGER] The upper bound of the allowed return values (inclusive).
* @return {Number} A random integer on [min, max]
*/
export const int53 = (gen, min = MIN_SAFE_INTEGER, max = MAX_SAFE_INTEGER) => math.floor(real53(gen) * (max + 1 - min) + min)
/**
* Generates a random integer with 32 bit resolution.
*
* @param {PRNG} gen A random number generator.
* @param {Number} [min = MIN_SAFE_INTEGER] The lower bound of the allowed return values (inclusive).
* @param {Number} [max = MAX_SAFE_INTEGER] The upper bound of the allowed return values (inclusive).
* @return {Number} A random integer on [min, max]
*/
export const int32 = (gen, min = MIN_SAFE_INTEGER, max = MAX_SAFE_INTEGER) => min + ((gen.next() >>> 0) % (max + 1 - min))
/**
* Generates a random real on [0, 1) with 32 bit resolution.
*
* @param {PRNG} gen A random number generator.
* @return {Number} A random real number on [0, 1).
*/
export const real32 = gen => (gen.next() >>> 0) / binary.BITS32
/**
* Generates a random real on [0, 1) with 53 bit resolution.
*
* @param {PRNG} gen A random number generator.
* @return {Number} A random real number on [0, 1).
*/
export const real53 = gen => (((gen.next() >>> 5) * binary.BIT26) + (gen.next() >>> 6)) / MAX_SAFE_INTEGER
/**
* Generates a random character from char code 32 - 126. I.e. Characters, Numbers, special characters, and Space:
*
* (Space)!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`abcdefghijklmnopqrstuvwxyz{|}~
*/
export const char = gen => fromCharCode(int32(gen, 32, 126))
/**
* @param {PRNG} gen
* @return {string} A single letter (a-z)
*/
export const letter = gen => fromCharCode(int32(gen, 97, 122))
/**
* @param {PRNG} gen
* @return {string} A random word without spaces consisting of letters (a-z)
*/
export const word = gen => {
const len = int32(gen, 0, 20)
let str = ''
for (let i = 0; i < len; i++) {
str += letter(gen)
}
return str
}
/**
* TODO: this function produces invalid runes. Does not cover all of utf16!!
*/
export const utf16Rune = gen => {
const codepoint = int32(gen, 0, 256)
return fromCodePoint(codepoint)
}
/**
* @param {PRNG} gen
* @param {number} [maxlen = 20]
*/
export const utf16String = (gen, maxlen = 20) => {
const len = int32(gen, 0, maxlen)
let str = ''
for (let i = 0; i < len; i++) {
str += utf16Rune(gen)
}
return str
}
/**
* Returns one element of a given array.
*
* @param {PRNG} gen A random number generator.
* @param {Array<T>} array Non empty Array of possible values.
* @return {T} One of the values of the supplied Array.
* @template T
*/
export const oneOf = (gen, array) => array[int32(gen, 0, array.length - 1)]

View File

@@ -1,13 +1,17 @@
/**
* @module prng
*/
/** /**
*TODO: enable tests *TODO: enable tests
import * as rt from '../rich-text/formatters.mjs' import * as rt from '../rich-text/formatters.mjs''
import { test } from '../test/test.mjs' import { test } from '../test/test.mjs''
import Xoroshiro128plus from './PRNG/Xoroshiro128plus.mjs' import Xoroshiro128plus from './PRNG/Xoroshiro128plus.mjs''
import Xorshift32 from './PRNG/Xorshift32.mjs' import Xorshift32 from './PRNG/Xorshift32.mjs''
import MT19937 from './PRNG/Mt19937.mjs' import MT19937 from './PRNG/Mt19937.mjs''
import { generateBool, generateInt, generateInt32, generateReal, generateChar } from './random.mjs' import { generateBool, generateInt, generateInt32, generateReal, generateChar } from './random.mjs''
import { MAX_SAFE_INTEGER } from '../number/constants.mjs' import { MAX_SAFE_INTEGER } from '../number/constants.mjs''
import { BIT32 } from '../binary/constants.mjs' import { BIT32 } from '../binary/constants.mjs''
function init (Gen) { function init (Gen) {
return { return {

3
lib/random.mjs Normal file
View File

@@ -0,0 +1,3 @@
/**
* @module random
*/

View File

@@ -1,2 +1,6 @@
/**
* @module string
*/
export const fromCharCode = String.fromCharCode export const fromCharCode = String.fromCharCode
export const fromCodePoint = String.fromCodePoint export const fromCodePoint = String.fromCodePoint

View File

@@ -1,5 +1,9 @@
import * as logging from './logging.js' /**
import simpleDiff from './simpleDiff.js' * @module testing
*/
import * as logging from './logging.mjs'
import { simpleDiff } from './diff.mjs'
export const run = async (name, f) => { export const run = async (name, f) => {
console.log(`%cStart:%c ${name}`, 'color:blue;', '') console.log(`%cStart:%c ${name}`, 'color:blue;', '')

561
package-lock.json generated
View File

@@ -1,9 +1,66 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.0-66", "version": "13.0.0-73",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@babel/code-frame": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
"integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
"dev": true,
"requires": {
"@babel/highlight": "^7.0.0"
}
},
"@babel/highlight": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
"integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
"dev": true,
"requires": {
"chalk": "^2.0.0",
"esutils": "^2.0.2",
"js-tokens": "^4.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"@types/estree": { "@types/estree": {
"version": "0.0.38", "version": "0.0.38",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.38.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.38.tgz",
@@ -110,17 +167,6 @@
"integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=",
"dev": true "dev": true
}, },
"align-text": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
"integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
"dev": true,
"requires": {
"kind-of": "^3.0.2",
"longest": "^1.0.1",
"repeat-string": "^1.5.2"
}
},
"ansi-escapes": { "ansi-escapes": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
@@ -1577,6 +1623,12 @@
"integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=", "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=",
"dev": true "dev": true
}, },
"bluebird": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz",
"integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==",
"dev": true
},
"boolbase": { "boolbase": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -1631,12 +1683,6 @@
"integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
"dev": true "dev": true
}, },
"camelcase": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
"integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=",
"dev": true
},
"camelcase-keys": { "camelcase-keys": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
@@ -1662,22 +1708,13 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"center-align": { "catharsis": {
"version": "0.1.3", "version": "0.8.9",
"resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz",
"integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=",
"dev": true, "dev": true,
"requires": { "requires": {
"align-text": "^0.1.3", "underscore-contrib": "~0.3.0"
"lazy-cache": "^1.0.3"
},
"dependencies": {
"lazy-cache": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
"integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=",
"dev": true
}
} }
}, },
"chalk": { "chalk": {
@@ -1761,17 +1798,6 @@
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
"dev": true "dev": true
}, },
"cliui": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
"integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
"dev": true,
"requires": {
"center-align": "^0.1.1",
"right-align": "^0.1.1",
"wordwrap": "0.0.2"
}
},
"clone": { "clone": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
@@ -2012,6 +2038,12 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true "dev": true
}, },
"crel": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/crel/-/crel-3.1.0.tgz",
"integrity": "sha512-VIGY44ERxx8lXVkOEfcB0A49OkjxkQNK+j+fHvoLy7GsGX1KKgAaQ+p9N0YgvQXu+X+ryUWGDeLx/fSI+w7+eg==",
"dev": true
},
"cross-spawn": { "cross-spawn": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
@@ -3073,9 +3105,9 @@
} }
}, },
"extend": { "extend": {
"version": "3.0.1", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true "dev": true
}, },
"extend-shallow": { "extend-shallow": {
@@ -3978,6 +4010,12 @@
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
"dev": true "dev": true
}, },
"graceful-readlink": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
"dev": true
},
"har-schema": { "har-schema": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@@ -4082,7 +4120,7 @@
}, },
"http-errors": { "http-errors": {
"version": "1.6.3", "version": "1.6.3",
"resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -4579,6 +4617,15 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"jest-worker": {
"version": "23.2.0",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz",
"integrity": "sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=",
"dev": true,
"requires": {
"merge-stream": "^1.0.1"
}
},
"js-tokens": { "js-tokens": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
@@ -4603,12 +4650,64 @@
} }
} }
}, },
"js2xmlparser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz",
"integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=",
"dev": true,
"requires": {
"xmlcreate": "^1.0.1"
}
},
"jsbn": { "jsbn": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"dev": true "dev": true
}, },
"jsdoc": {
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.5.tgz",
"integrity": "sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==",
"dev": true,
"requires": {
"babylon": "7.0.0-beta.19",
"bluebird": "~3.5.0",
"catharsis": "~0.8.9",
"escape-string-regexp": "~1.0.5",
"js2xmlparser": "~3.0.0",
"klaw": "~2.0.0",
"marked": "~0.3.6",
"mkdirp": "~0.5.1",
"requizzle": "~0.2.1",
"strip-json-comments": "~2.0.1",
"taffydb": "2.6.2",
"underscore": "~1.8.3"
},
"dependencies": {
"babylon": {
"version": "7.0.0-beta.19",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz",
"integrity": "sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==",
"dev": true
},
"klaw": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz",
"integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.9"
}
},
"taffydb": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz",
"integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=",
"dev": true
}
}
},
"jsdom": { "jsdom": {
"version": "7.2.2", "version": "7.2.2",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz",
@@ -5003,12 +5102,6 @@
"integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=",
"dev": true "dev": true
}, },
"longest": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
"dev": true
},
"loose-envify": { "loose-envify": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
@@ -5040,7 +5133,7 @@
}, },
"magic-string": { "magic-string": {
"version": "0.22.5", "version": "0.22.5",
"resolved": "http://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz",
"integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -5061,7 +5154,7 @@
}, },
"marked": { "marked": {
"version": "0.3.19", "version": "0.3.19",
"resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz",
"integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==",
"dev": true "dev": true
}, },
@@ -5114,6 +5207,15 @@
} }
} }
}, },
"merge-stream": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz",
"integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=",
"dev": true,
"requires": {
"readable-stream": "^2.0.1"
}
},
"micromatch": { "micromatch": {
"version": "2.3.11", "version": "2.3.11",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
@@ -5363,6 +5465,12 @@
} }
} }
}, },
"orderedmap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-1.0.0.tgz",
"integrity": "sha1-2Q/Cuh7QhRkJB9YB3sbmpT+NQbo=",
"dev": true
},
"os-homedir": { "os-homedir": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
@@ -5412,7 +5520,7 @@
}, },
"parchment": { "parchment": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "http://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==", "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==",
"dev": true "dev": true
}, },
@@ -5656,6 +5764,158 @@
"object-assign": "^4.1.1" "object-assign": "^4.1.1"
} }
}, },
"prosemirror-commands": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.0.7.tgz",
"integrity": "sha512-IR8yMSdw7XlKuF68tydAak1J9P/lLD5ohsrL7pzoLsJAJAQU7mVPDXtGbQrrm0mesddFjcc1zNo/cJQN3lRYnA==",
"dev": true,
"requires": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"prosemirror-dropcursor": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.1.1.tgz",
"integrity": "sha512-GeUyMO/tOEf8MXrP7Xb7UIMrfK86OGh0fnyBrHfhav4VjY9cw65mNoqHy87CklE5711AhCP5Qzfp8RL/hVKusg==",
"dev": true,
"requires": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0",
"prosemirror-view": "^1.1.0"
}
},
"prosemirror-example-setup": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prosemirror-example-setup/-/prosemirror-example-setup-1.0.1.tgz",
"integrity": "sha512-4NKWpdmm75Zzgq/dIrypRnkBNPx+ONKyoGF42a9g3VIVv0TWglf1CBNxt5kzCgli9xdfut/xE5B42F9DR6BLHw==",
"dev": true,
"requires": {
"prosemirror-commands": "^1.0.0",
"prosemirror-dropcursor": "^1.0.0",
"prosemirror-gapcursor": "^1.0.0",
"prosemirror-history": "^1.0.0",
"prosemirror-inputrules": "^1.0.0",
"prosemirror-keymap": "^1.0.0",
"prosemirror-menu": "^1.0.0",
"prosemirror-schema-list": "^1.0.0",
"prosemirror-state": "^1.0.0"
}
},
"prosemirror-gapcursor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.0.3.tgz",
"integrity": "sha512-X+hJhr42PcHWiSWL+lI5f/UeOhXCxlBFb8M6O8aG1hssmaRrW7sS2/Fjg5jFV+pTdS1REFkmm1occh01FMdDIQ==",
"dev": true,
"requires": {
"prosemirror-keymap": "^1.0.0",
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-view": "^1.0.0"
}
},
"prosemirror-history": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.0.3.tgz",
"integrity": "sha512-IfFGbhafSx+R3aq7nLJGkXeu2iaUiP8mkU3aRu2uQcIIjU8Fq7RJfuvhIOJ2RNUoSyqF/ANkdTjnZ74F5eHs1Q==",
"dev": true,
"requires": {
"prosemirror-state": "^1.2.2",
"prosemirror-transform": "^1.0.0",
"rope-sequence": "^1.2.0"
}
},
"prosemirror-inputrules": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.0.1.tgz",
"integrity": "sha512-UHy22NmwxS5WIMQYkzraDttQAF8mpP82FfbJsmKFfx6jwkR/SZa+ZhbkLY0zKQ5fBdJN7euj36JG/B5iAlrpxA==",
"dev": true,
"requires": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"prosemirror-keymap": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.0.1.tgz",
"integrity": "sha512-e79ApE7PXXZMFtPz7WbjycjAFd1NPjgY1MkecVz98tqwlBSggXWXYQnWFk6x7UkmnBYRHHbXHkR/RXmu2wyBJg==",
"dev": true,
"requires": {
"prosemirror-state": "^1.0.0",
"w3c-keyname": "^1.1.8"
}
},
"prosemirror-menu": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.0.5.tgz",
"integrity": "sha512-9Vrn7CC191v7FA4QrAkL8W1SrR73V3CRIYCDuk94R8oFVk4VxSFdoKVLHuvGzxZ8b5LCu3DMJfh86YW9uL4RkQ==",
"dev": true,
"requires": {
"crel": "^3.0.0",
"prosemirror-commands": "^1.0.0",
"prosemirror-history": "^1.0.0",
"prosemirror-state": "^1.0.0"
}
},
"prosemirror-model": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.6.3.tgz",
"integrity": "sha512-iqIml664X9MUVGLz2nzK4xfAofX8+o7gs2mi2/k+pVD0qZ7th1Jm5eG3AsqWoEUIZuWeaOWCKpBl/dPnhIIWew==",
"dev": true,
"requires": {
"orderedmap": "^1.0.0"
}
},
"prosemirror-schema-basic": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.0.0.tgz",
"integrity": "sha512-xTFjtuLZgcRS4MoDbUyI9NSk/k/ACLGKZQcDXH18ctM9BOmP4z5rGZcA014fCF2FnMFOU+lKwusL0JjVrEectQ==",
"dev": true,
"requires": {
"prosemirror-model": "^1.0.0"
}
},
"prosemirror-schema-list": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.0.1.tgz",
"integrity": "sha512-AiLIX6qm6PEeDtMCKZLcSLi55WXo1ls7DnRK+4hSkoi0IIzNdxGsRlecCd3MzEu//DVz3nAEh+zEmslyW+uk8g==",
"dev": true,
"requires": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"prosemirror-state": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.2.2.tgz",
"integrity": "sha512-j8aC/kf9BJSCQau485I/9pj39XQoce+TqH5xzekT7WWFARTsRYFLJtiXBcCKakv1VSeev+sC3bJP0pLfz7Ft8g==",
"dev": true,
"requires": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"prosemirror-transform": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.1.3.tgz",
"integrity": "sha512-1O6Di5lOL1mp4nuCnQNkHY7l2roIW5y8RH4ZG3hMYmkmDEWzTaFFnxxAAHsE5ipGLBSRcTlP7SsDhYBIdSuLpQ==",
"dev": true,
"requires": {
"prosemirror-model": "^1.0.0"
}
},
"prosemirror-view": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.6.5.tgz",
"integrity": "sha512-brg8fExNrmklbLs8VJ7uvmo/Lh93EHErH47alI55hkJ12EF73K+t2+IyrlkJF84tt5wFBJ20LeSxF8HlJHXiYg==",
"dev": true,
"requires": {
"prosemirror-model": "^1.1.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0"
}
},
"pseudomap": { "pseudomap": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@@ -5715,14 +5975,6 @@
"deep-equal": "^1.0.1", "deep-equal": "^1.0.1",
"extend": "^3.0.2", "extend": "^3.0.2",
"fast-diff": "1.1.2" "fast-diff": "1.1.2"
},
"dependencies": {
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
}
} }
}, },
"randomatic": { "randomatic": {
@@ -6034,6 +6286,23 @@
"resolve-from": "^1.0.0" "resolve-from": "^1.0.0"
} }
}, },
"requizzle": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz",
"integrity": "sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=",
"dev": true,
"requires": {
"underscore": "~1.6.0"
},
"dependencies": {
"underscore": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
"integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=",
"dev": true
}
}
},
"resolve": { "resolve": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz",
@@ -6069,15 +6338,6 @@
"signal-exit": "^3.0.2" "signal-exit": "^3.0.2"
} }
}, },
"right-align": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
"integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
"dev": true,
"requires": {
"align-text": "^0.1.1"
}
},
"rimraf": { "rimraf": {
"version": "2.6.2", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
@@ -6097,6 +6357,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"rollup-cli": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/rollup-cli/-/rollup-cli-1.0.9.tgz",
"integrity": "sha1-N/ShwgYxHikuMpfql3eduKIduZQ=",
"dev": true
},
"rollup-plugin-babel": { "rollup-plugin-babel": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-2.7.1.tgz", "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-2.7.1.tgz",
@@ -6111,7 +6377,7 @@
}, },
"rollup-plugin-commonjs": { "rollup-plugin-commonjs": {
"version": "8.4.1", "version": "8.4.1",
"resolved": "http://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.4.1.tgz", "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.4.1.tgz",
"integrity": "sha512-mg+WuD+jlwoo8bJtW3Mvx7Tz6TsIdMsdhuvCnDMoyjh0oxsVgsjB/N0X984RJCWwc5IIiqNVJhXeeITcc73++A==", "integrity": "sha512-mg+WuD+jlwoo8bJtW3Mvx7Tz6TsIdMsdhuvCnDMoyjh0oxsVgsjB/N0X984RJCWwc5IIiqNVJhXeeITcc73++A==",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -6216,12 +6482,46 @@
} }
}, },
"rollup-plugin-uglify": { "rollup-plugin-uglify": {
"version": "1.0.2", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-1.0.2.tgz", "resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-6.0.0.tgz",
"integrity": "sha1-1KpvXfE1Iurhuhd4DHxMcJYDg1k=", "integrity": "sha512-XtzZd159QuOaXNvcxyBcbUCSoBsv5YYWK+7ZwUyujSmISst8avRfjWlp7cGu8T2O52OJnpEBvl+D4WLV1k1iQQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"uglify-js": "^2.6.1" "@babel/code-frame": "^7.0.0",
"jest-worker": "^23.2.0",
"serialize-javascript": "^1.5.0",
"uglify-js": "^3.4.9"
}
},
"rollup-plugin-uglify-es": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/rollup-plugin-uglify-es/-/rollup-plugin-uglify-es-0.0.1.tgz",
"integrity": "sha1-5FZE8raFpZq9uTY0ByB6A6e1qbc=",
"dev": true,
"requires": {
"uglify-es": "3.0.3"
},
"dependencies": {
"commander": {
"version": "2.9.0",
"resolved": "http://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
"integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=",
"dev": true,
"requires": {
"graceful-readlink": ">= 1.0.0"
}
},
"uglify-es": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.0.3.tgz",
"integrity": "sha1-Y8yEqpRos0lzpIh3h8ZMAaiodXY=",
"dev": true,
"requires": {
"commander": "~2.9.0",
"source-map": "~0.5.1",
"uglify-to-browserify": "~1.0.0"
}
}
} }
}, },
"rollup-pluginutils": { "rollup-pluginutils": {
@@ -6252,6 +6552,12 @@
"require-relative": "0.8.7" "require-relative": "0.8.7"
} }
}, },
"rope-sequence": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.2.2.tgz",
"integrity": "sha1-ScTlwvVKSOmQsFCSZ3HihxvLMc4=",
"dev": true
},
"run-async": { "run-async": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
@@ -6313,6 +6619,12 @@
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true "dev": true
}, },
"serialize-javascript": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz",
"integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==",
"dev": true
},
"serve-index": { "serve-index": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
@@ -6804,6 +7116,15 @@
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
"dev": true "dev": true
}, },
"tui-jsdoc-template": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tui-jsdoc-template/-/tui-jsdoc-template-1.2.2.tgz",
"integrity": "sha512-oqw0IYaot86VJ2owKBozJnilgta0Z55x8r9PeHj7vb+jDoSvJGRUQUcgs56SZh9HE20fx54Pe75p84X85/ygLA==",
"dev": true,
"requires": {
"cheerio": "^0.22.0"
}
},
"tunnel-agent": { "tunnel-agent": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -6842,14 +7163,27 @@
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {
"version": "2.8.29", "version": "3.4.9",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
"integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"source-map": "~0.5.1", "commander": "~2.17.1",
"uglify-to-browserify": "~1.0.0", "source-map": "~0.6.1"
"yargs": "~3.10.0" },
"dependencies": {
"commander": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
} }
}, },
"uglify-to-browserify": { "uglify-to-browserify": {
@@ -6859,6 +7193,29 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"underscore": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=",
"dev": true
},
"underscore-contrib": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/underscore-contrib/-/underscore-contrib-0.3.0.tgz",
"integrity": "sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=",
"dev": true,
"requires": {
"underscore": "1.6.0"
},
"dependencies": {
"underscore": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
"integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=",
"dev": true
}
}
},
"uniq": { "uniq": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
@@ -6950,6 +7307,12 @@
"integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==",
"dev": true "dev": true
}, },
"w3c-keyname": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-1.1.8.tgz",
"integrity": "sha512-2HAdug8GTiu3b4NYhssdtY8PXRue3ICnh1IlxvZYl+hiINRq0GfNWei3XOPDg8L0PsxbmYjWVLuLj6BMRR/9vA==",
"dev": true
},
"webidl-conversions": { "webidl-conversions": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz",
@@ -6997,18 +7360,6 @@
"isexe": "^2.0.0" "isexe": "^2.0.0"
} }
}, },
"window-size": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
"integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=",
"dev": true
},
"wordwrap": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
"integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=",
"dev": true
},
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -7039,6 +7390,12 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"xmlcreate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz",
"integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=",
"dev": true
},
"xtend": { "xtend": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
@@ -7050,18 +7407,6 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true "dev": true
},
"yargs": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
"integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
"dev": true,
"requires": {
"camelcase": "^1.0.2",
"cliui": "^2.1.0",
"decamelize": "^1.0.0",
"window-size": "0.1.0"
}
} }
} }
} }

View File

@@ -1,30 +1,53 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.0.0-66", "version": "13.0.0-73",
"description": "A framework for real-time p2p shared editing on any data", "description": "A ",
"main": "./y.node.js", "module": "./index.mjs'",
"browser": "./y.js", "sideEffects": false,
"module": "./src/index.js",
"scripts": { "scripts": {
"test": "npm run lint", "test": "npm run lint",
"debug": "concurrently 'rollup -wc rollup.test.js' 'cutest-serve y.test.js -o'", "build": "rm -rf build examples/build && rollup -c",
"lint": "standard src/**/*.js test/**/*.js tests-lib/**/*.js", "watch": "rollup -wc",
"docs": "esdoc", "debug": "concurrently 'rollup -wc' 'cutest-serve build/y.test.js -o'",
"lint": "standard **/*.js",
"docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.v13.md --package ./package.json || true",
"serve-docs": "npm run docs && serve ./docs/", "serve-docs": "npm run docs && serve ./docs/",
"dist": "rollup -c rollup.browser.js; rollup -c rollup.node.js", "postversion": "npm run build",
"watch": "concurrently 'rollup -wc rollup.browser.js' 'rollup -wc rollup.node.js'", "websocket-server": "node --experimental-modules ./provider/websocket/server.mjs",
"postversion": "npm run dist" "now-start": "npm run websocket-server"
}, },
"files": [ "files": [
"y.*", "bindings/*",
"src/*", "docs/*",
".esdoc.json", "examples/*",
"docs/*" "lib/*",
"persistences/*",
"protocols/*",
"provider/*",
"bindings/*",
"structs/*",
"tests/*",
"types/*",
"utils/*",
"index.js",
"README.md",
"LICENSE"
], ],
"dictionaries": {
"doc": "docs",
"example": "examples",
"test": "tests",
"lib": "./"
},
"bin": {
"y-websockets": "provider/websocket/server.js"
},
"standard": { "standard": {
"ignore": [ "ignore": [
"/y.js", "/build",
"/y.js.map" "/node_modules",
"/rollup.test.js",
"/rollup.test.js"
] ]
}, },
"repository": { "repository": {
@@ -32,13 +55,7 @@
"url": "https://github.com/y-js/yjs.git" "url": "https://github.com/y-js/yjs.git"
}, },
"keywords": [ "keywords": [
"Yjs", "crdt"
"OT",
"Collaboration",
"Synchronization",
"ShareJS",
"Coweb",
"Concurrency"
], ],
"author": "Kevin Jahns", "author": "Kevin Jahns",
"email": "kevin.jahns@rwth-aachen.de", "email": "kevin.jahns@rwth-aachen.de",
@@ -57,18 +74,26 @@
"cutest": "^0.1.9", "cutest": "^0.1.9",
"esdoc": "^1.1.0", "esdoc": "^1.1.0",
"esdoc-standard-plugin": "^1.0.0", "esdoc-standard-plugin": "^1.0.0",
"jsdoc": "^3.5.5",
"prosemirror-example-setup": "^1.0.1",
"prosemirror-schema-basic": "^1.0.0",
"prosemirror-state": "^1.2.2",
"prosemirror-view": "^1.6.5",
"quill": "^1.3.6", "quill": "^1.3.6",
"quill-cursors": "^1.0.3", "quill-cursors": "^1.0.3",
"rollup": "^0.58.2", "rollup": "^0.58.2",
"rollup-cli": "^1.0.9",
"rollup-plugin-babel": "^2.7.1", "rollup-plugin-babel": "^2.7.1",
"rollup-plugin-commonjs": "^8.4.1", "rollup-plugin-commonjs": "^8.4.1",
"rollup-plugin-inject": "^2.2.0", "rollup-plugin-inject": "^2.2.0",
"rollup-plugin-multi-entry": "^2.0.2", "rollup-plugin-multi-entry": "^2.0.2",
"rollup-plugin-node-resolve": "^3.4.0", "rollup-plugin-node-resolve": "^3.4.0",
"rollup-plugin-uglify": "^1.0.2", "rollup-plugin-uglify": "^6.0.0",
"rollup-plugin-uglify-es": "0.0.1",
"rollup-regenerator-runtime": "^6.23.1", "rollup-regenerator-runtime": "^6.23.1",
"rollup-watch": "^3.2.2", "rollup-watch": "^3.2.2",
"standard": "^11.0.1" "standard": "^11.0.1",
"tui-jsdoc-template": "^1.2.2"
}, },
"dependencies": { "dependencies": {
"ws": "^6.1.0" "ws": "^6.1.0"

View File

@@ -1,19 +1,20 @@
/*
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import * as encoding from '../../lib/encoding.js' import * as encoding from '../lib/encoding.mjs'
import * as decoding from '../../lib/decoding.js' import * as decoding from '../lib/decoding.mjs'
import { createMutualExclude } from '../../lib/mutualExclude.js' import { createMutex } from '../lib/mutex.mjs'
import { encodeUpdate, encodeStructsDS, decodePersisted } from './decodePersisted.js' import { encodeUpdate, encodeStructsDS, decodePersisted } from './decodePersisted.mjs'
function createFilePath (persistence, roomName) { function createFilePath (persistence, roomName) {
// TODO: filename checking! // TODO: filename checking!
return path.join(persistence.dir, roomName) return path.join(persistence.dir, roomName)
} }
export default class FilePersistence { export class FilePersistence {
constructor (dir) { constructor (dir) {
this.dir = dir this.dir = dir
this._mutex = createMutualExclude() this._mutex = createMutex()
} }
setRemoteUpdateCounter (roomName, remoteUpdateCounter) { setRemoteUpdateCounter (roomName, remoteUpdateCounter) {
// TODO: implement // TODO: implement
@@ -70,3 +71,4 @@ export default class FilePersistence {
}) })
} }
} }
*/

View File

@@ -1,13 +1,9 @@
/* global indexedDB, location, BroadcastChannel */
import Y from '../Y.js'
import { createMutualExclude } from '../../lib/mutualExclude.js'
import { decodePersisted, encodeStructsDS, encodeUpdate, PERSIST_STRUCTS_DS, PERSIST_UPDATE } from './decodePersisted.js'
import * as decoding from '../../lib/decoding.js'
import * as encoding from '../../lib/encoding.js'
/* /*
* Request to Promise transformer import { Y } from '../utils/Y.mjs'
*/ import { createMutex } from '../lib/mutex.mjs'
import { decodePersisted, encodeStructsDS, encodeUpdate, PERSIST_STRUCTS_DS, PERSIST_UPDATE } from './decodePersisted.mjs'
function rtop (request) { function rtop (request) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
request.onerror = function (event) { request.onerror = function (event) {
@@ -50,21 +46,21 @@ function persist (room) {
let t = room.db.transaction(['updates'], 'readwrite') let t = room.db.transaction(['updates'], 'readwrite')
let updatesStore = t.objectStore('updates') let updatesStore = t.objectStore('updates')
return rtop(updatesStore.getAll()) return rtop(updatesStore.getAll())
.then(updates => { .then(updates => {
// apply all previous updates before deleting them // apply all previous updates before deleting them
room.mutex(() => { room.mutex(() => {
updates.forEach(update => { updates.forEach(update => {
decodePersisted(y, new BinaryDecoder(update)) decodePersisted(y, new BinaryDecoder(update))
})
})
const encoder = new BinaryEncoder()
encodeStructsDS(y, encoder)
// delete all pending updates
rtop(updatesStore.clear()).then(() => {
// write current model
updatesStore.put(encoder.createBuffer())
}) })
}) })
const encoder = new BinaryEncoder()
encodeStructsDS(y, encoder)
// delete all pending updates
rtop(updatesStore.clear()).then(() => {
// write current model
updatesStore.put(encoder.createBuffer())
})
})
} }
function saveUpdate (room, updateBuffer) { function saveUpdate (room, updateBuffer) {
@@ -99,7 +95,7 @@ function registerRoomInPersistence (documentsDB, roomName) {
const PREFERRED_TRIM_SIZE = 400 const PREFERRED_TRIM_SIZE = 400
export default class IndexedDBPersistence { export class IndexedDBPersistence {
constructor () { constructor () {
this._rooms = new Map() this._rooms = new Map()
this._documentsDB = new Promise(function (resolve, reject) { this._documentsDB = new Promise(function (resolve, reject) {
@@ -115,7 +111,17 @@ export default class IndexedDBPersistence {
reject(new Error(event.target.error)) reject(new Error(event.target.error))
} }
request.onblocked = function () { request.onblocked = function () {
location.reload() location.reload()EventListener' is not defined.
/home/dmonad/go/src/github.com/y-js/yjs/persistences/IndexedDBPersistence.js:160:36: 'BinaryDecoder' is not defined.
/home/dmonad/go/src/github.com/y-js/yjs/persistences/IndexedDBPersistence.js:167:25: 'BinaryEncoder' is not defined.
/home/dmonad/go/src/github.com/y-js/yjs/persistences/IndexedDBPersistence.js:178:25: 'BinaryEncoder' is not defined.
/home/dmonad/go/src/github.com/y-js/yjs/persistences/IndexedDBPersistence.js:203:34: 'BinaryDecoder' is not defined.
/home/dmonad/go/src/github.com/y-js/yjs/persistences/IndexedDBPersistence.js:213:17: 'encoder' is assigned a value but never used.
/home/dmonad/go/src/github.com/y-js/yjs/persistences/IndexedDBPersistence.js:213:31: 'BinaryEncoder' is not defined.
/home/dmonad/go/src/github.com/y-js/yjs/persistences/IndexedDBPersistence.js:214:30: 'BinaryEncoder' is not defined.
/home/dmonad/go/src/github.com/y-js/yjs/persistences/IndexedDBPersistence.js:230:12: Trailing spaces not allowed.
/home/dmonad/go/src/github.com/y-js/yjs/persistences/IndexedDBPersistence.js:237:5: Return statement should not contain assignment.
/home/dmonad/go/src/github.com/y-js/yjs/persistences/IndexedDBPersistence.js:243:29: 'BinaryEncoder' i
} }
request.onsuccess = function (event) { request.onsuccess = function (event) {
const db = event.target.result const db = event.target.result
@@ -194,7 +200,7 @@ export default class IndexedDBPersistence {
db: null, db: null,
dbPromise: null, dbPromise: null,
channel: null, channel: null,
mutex: createMutualExclude(), mutex: createMutex(),
y y
} }
if (typeof BroadcastChannel !== 'undefined') { if (typeof BroadcastChannel !== 'undefined') {
@@ -228,7 +234,7 @@ export default class IndexedDBPersistence {
}) })
// register document in documentsDB // register document in documentsDB
this._documentsDB.then( this._documentsDB.then(
db => db =>
rtop(db.transaction(['documents'], 'readonly').objectStore('documents').get(roomName)) rtop(db.transaction(['documents'], 'readonly').objectStore('documents').get(roomName))
.then( .then(
doc => doc === undefined && rtop(db.transaction(['documents'], 'readwrite').objectStore('documents').add({ roomName, serverUpdateCounter: -1 })) doc => doc === undefined && rtop(db.transaction(['documents'], 'readwrite').objectStore('documents').add({ roomName, serverUpdateCounter: -1 }))
@@ -273,9 +279,10 @@ export default class IndexedDBPersistence {
* Automatically destroys all Yjs all Yjs instances that persist to * Automatically destroys all Yjs all Yjs instances that persist to
* the room. If `destroyYjsInstances = false` the persistence functionality * the room. If `destroyYjsInstances = false` the persistence functionality
* will be removed from the Yjs instances. * will be removed from the Yjs instances.
*/ *
removePersistedData (roomName, destroyYjsInstances = true) { removePersistedData (roomName, destroyYjsInstances = true) {
this.disconnectY(roomName) this.disconnectY(roomName)
return rtop(indexedDB.deleteDatabase(roomName)) return rtop(indexedDB.deleteDatabase(roomName))
} }
} }
*/

View File

@@ -1,15 +1,16 @@
import { integrateRemoteStructs } from '../MessageHandler/integrateRemoteStructs.js' /*
import { writeStructs } from '../MessageHandler/syncStep1.js' import { integrateRemoteStructs } from '../MessageHandler/integrateRemoteStructs.mjs'
import { writeDeleteSet, readDeleteSet } from '../MessageHandler/deleteSet.js' import { writeStructs } from '../MessageHandler/syncStep1.mjs'
import { writeDeleteSet, readDeleteSet } from '../MessageHandler/deleteSet.mjs'
export const PERSIST_UPDATE = 0 export const PERSIST_UPDATE = 0
/** /**
* Write an update to an encoder. * Write an update to an encoder.
* *
* @param {Yjs} y A Yjs instance * @param {Y} y A Yjs instance
* @param {BinaryEncoder} updateEncoder I.e. transaction.encodedStructs * @param {Encoder} updateEncoder I.e. transaction.encodedStructs
*/ *
export function encodeUpdate (y, updateEncoder, encoder) { export const encodeUpdate = (y, updateEncoder, encoder) => {
encoder.writeVarUint(PERSIST_UPDATE) encoder.writeVarUint(PERSIST_UPDATE)
encoder.writeBinaryEncoder(updateEncoder) encoder.writeBinaryEncoder(updateEncoder)
} }
@@ -19,10 +20,10 @@ export const PERSIST_STRUCTS_DS = 1
/** /**
* Write the current Yjs data model to an encoder. * Write the current Yjs data model to an encoder.
* *
* @param {Yjs} y A Yjs instance * @param {Y} y A Yjs instance
* @param {BinaryEncoder} encoder An encoder to write to * @param {Encoder} encoder An encoder to write to
*/ *
export function encodeStructsDS (y, encoder) { export const encodeStructsDS = (y, encoder) => {
encoder.writeVarUint(PERSIST_STRUCTS_DS) encoder.writeVarUint(PERSIST_STRUCTS_DS)
writeStructs(y, encoder, new Map()) writeStructs(y, encoder, new Map())
writeDeleteSet(y, encoder) writeDeleteSet(y, encoder)
@@ -30,10 +31,10 @@ export function encodeStructsDS (y, encoder) {
/** /**
* Feed the Yjs instance with the persisted state * Feed the Yjs instance with the persisted state
* @param {Yjs} y A Yjs instance. * @param {Y} y A Yjs instance.
* @param {BinaryDecoder} decoder A Decoder instance that holds the file content. * @param {Decoder} decoder A Decoder instance that holds the file content.
*/ *
export function decodePersisted (y, decoder) { export const decodePersisted = (y, decoder) => {
y.transact(() => { y.transact(() => {
while (decoder.hasContent()) { while (decoder.hasContent()) {
const contentType = decoder.readVarUint() const contentType = decoder.readVarUint()
@@ -49,3 +50,4 @@ export function decodePersisted (y, decoder) {
} }
}, true) }, true)
} }
*/

View File

@@ -0,0 +1,103 @@
/**
* @module awareness-protocol
*/
import * as encoding from '../lib/encoding.mjs'
import * as decoding from '../lib/decoding.mjs'
import { Y } from '../utils/Y.mjs' // eslint-disable-line
const messageUsersStateChanged = 0
/**
* @typedef {Object} UserStateUpdate
* @property {number} UserStateUpdate.userID
* @property {Object} state
*/
/**
* @param {encoding.Encoder} encoder
* @param {Array<UserStateUpdate>} stateUpdates
*/
export const writeUsersStateChange = (encoder, stateUpdates) => {
const len = stateUpdates.length
encoding.writeVarUint(encoder, messageUsersStateChanged)
encoding.writeVarUint(encoder, len)
for (let i = 0; i < len; i++) {
const {userID, state} = stateUpdates[i]
encoding.writeVarUint(encoder, userID)
encoding.writeVarString(encoder, JSON.stringify(state))
}
}
export const readUsersStateChange = (decoder, y) => {
const added = []
const updated = []
const removed = []
const len = decoding.readVarUint(decoder)
for (let i = 0; i < len; i++) {
const userID = decoding.readVarUint(decoder)
const state = JSON.parse(decoding.readVarString(decoder))
if (userID !== y.userID) {
if (state === null) {
if (y.awareness.has(userID)) {
y.awareness.delete(userID)
removed.push(userID)
}
} else {
if (y.awareness.has(userID)) {
updated.push(userID)
} else {
added.push(userID)
}
y.awareness.set(userID, state)
}
}
}
if (added.length > 0 || updated.length > 0 || removed.length > 0) {
y.emit('awareness', {
added, updated, removed
})
}
}
/**
* @param {decoding.Decoder} decoder
* @param {encoding.Encoder} encoder
*/
export const forwardUsersStateChange = (decoder, encoder) => {
const len = decoding.readVarUint(decoder)
const updates = []
encoding.writeVarUint(encoder, messageUsersStateChanged)
encoding.writeVarUint(encoder, len)
for (let i = 0; i < len; i++) {
const userID = decoding.readVarUint(decoder)
const state = decoding.readVarString(decoder)
encoding.writeVarUint(encoder, userID)
encoding.writeVarString(encoder, state)
updates.push({userID, state: JSON.parse(state)})
}
return updates
}
/**
* @param {decoding.Decoder} decoder
* @param {Y} y
*/
export const readAwarenessMessage = (decoder, y) => {
switch (decoding.readVarUint(decoder)) {
case messageUsersStateChanged:
readUsersStateChange(decoder, y)
break
}
}
/**
* @param {decoding.Decoder} decoder
* @param {encoding.Encoder} encoder
*/
export const forwardAwarenessMessage = (decoder, encoder) => {
switch (decoding.readVarUint(decoder)) {
case messageUsersStateChanged:
return forwardUsersStateChange(decoder, encoder)
}
}

View File

@@ -1,17 +1,18 @@
/**
* @module sync-protocol
*/
import * as encoding from '../lib/encoding.js' import * as encoding from '../lib/encoding.mjs'
import * as decoding from '../lib/decoding.js' import * as decoding from '../lib/decoding.mjs'
import * as ID from './Util/ID.js' import * as ID from '../utils/ID.mjs'
import { getStruct } from './Util/structReferences.js' import { getStruct } from '../utils/structReferences.mjs'
import { deleteItemRange } from './Struct/Delete.js' import { deleteItemRange } from '../utils/structManipulation.mjs'
import { integrateRemoteStruct } from './Util/integrateRemoteStructs.js' import { integrateRemoteStruct } from '../utils/integrateRemoteStructs.mjs'
import Item from './Struct/Item.js' import { Y } from '../utils/Y.mjs' // eslint-disable-line
import { Item } from '../structs/Item.mjs'
/** /**
* @typedef {import('./Store/StateStore.js').default} StateStore * @typedef {Map<number, number>} StateSet
* @typedef {import('./Y.js').default} Y
* @typedef {import('./Struct/Item.js').default} Item
* @typedef {import('./Store/StateStore.js').StateSet} StateSet
*/ */
/** /**
@@ -54,7 +55,7 @@ export const stringifyDeleteSet = (decoder) => {
const dsLength = decoding.readUint32(decoder) const dsLength = decoding.readUint32(decoder)
for (let i = 0; i < dsLength; i++) { for (let i = 0; i < dsLength; i++) {
str += ' -' + decoding.readVarUint(decoder) + ':\n' // decodes user str += ' -' + decoding.readVarUint(decoder) + ':\n' // decodes user
const dvLength = decoding.readVarUint(decoder) const dvLength = decoding.readUint32(decoder)
for (let j = 0; j < dvLength; j++) { for (let j = 0; j < dvLength; j++) {
str += `clock: ${decoding.readVarUint(decoder)}, length: ${decoding.readVarUint(decoder)}, gc: ${decoding.readUint8(decoder) === 1}\n` str += `clock: ${decoding.readVarUint(decoder)}, length: ${decoding.readVarUint(decoder)}, gc: ${decoding.readUint8(decoder) === 1}\n`
} }
@@ -82,7 +83,8 @@ export const writeDeleteSet = (encoder, y) => {
const gc = n.gc const gc = n.gc
if (currentUser !== user) { if (currentUser !== user) {
numberOfUsers++ numberOfUsers++
// a new user was found // a new user was foundimport { StateSet } from '../Store/StateStore.mjs' // eslint-disable-line
if (currentUser !== null) { // happens on first iteration if (currentUser !== null) { // happens on first iteration
encoding.setUint32(encoder, lastLenPos, currentLength) encoding.setUint32(encoder, lastLenPos, currentLength)
} }
@@ -192,8 +194,8 @@ export const readDeleteSet = (decoder, y) => {
*/ */
export const stringifyStateSet = decoder => { export const stringifyStateSet = decoder => {
let s = 'State Set: ' let s = 'State Set: '
readStateSet(decoder).forEach((user, userState) => { readStateSet(decoder).forEach((clock, user) => {
s += `(${user}: ${userState}), ` s += `(${user}: ${clock}), `
}) })
return s return s
} }
@@ -201,15 +203,15 @@ export const stringifyStateSet = decoder => {
/** /**
* Write StateSet to Encoder * Write StateSet to Encoder
* *
* @param {Y} y
* @param {encoding.Encoder} encoder * @param {encoding.Encoder} encoder
* @param {Y} y
*/ */
export const writeStateSet = (encoder, y) => { export const writeStateSet = (encoder, y) => {
const state = y.ss.state const state = y.ss.state
// write as fixed-size number to stay consistent with the other encode functions. // write as fixed-size number to stay consistent with the other encode functions.
// => anytime we write the number of objects that follow, encode as fixed-size number. // => anytime we write the number of objects that follow, encode as fixed-size number.
encoding.writeUint32(encoder, state.size) encoding.writeUint32(encoder, state.size)
state.forEach((user, clock) => { state.forEach((clock, user) => {
encoding.writeVarUint(encoder, user) encoding.writeVarUint(encoder, user)
encoding.writeVarUint(encoder, clock) encoding.writeVarUint(encoder, clock)
}) })
@@ -317,7 +319,9 @@ export const writeStructs = (encoder, y, ss) => {
const overlappingLeft = y.os.findPrev(minBound) const overlappingLeft = y.os.findPrev(minBound)
const rightID = overlappingLeft === null ? null : overlappingLeft._id const rightID = overlappingLeft === null ? null : overlappingLeft._id
if (rightID !== null && rightID.user === user && rightID.clock + overlappingLeft._length > clock) { if (rightID !== null && rightID.user === user && rightID.clock + overlappingLeft._length > clock) {
const struct = overlappingLeft._clonePartial(clock - rightID.clock) // TODO: only write partial content (only missing content)
// const struct = overlappingLeft._clonePartial(clock - rightID.clock)
const struct = overlappingLeft
struct._toBinary(encoder) struct._toBinary(encoder)
len++ len++
} }
@@ -424,6 +428,7 @@ export const stringifyUpdate = (decoder, y) =>
/** /**
* @param {encoding.Encoder} encoder * @param {encoding.Encoder} encoder
* @param {number} numOfStructs
* @param {encoding.Encoder} updates * @param {encoding.Encoder} updates
*/ */
export const writeUpdate = (encoder, numOfStructs, updates) => { export const writeUpdate = (encoder, numOfStructs, updates) => {
@@ -439,7 +444,7 @@ export const readUpdate = readStructs
* @param {Y} y * @param {Y} y
* @return {string} The message converted to string * @return {string} The message converted to string
*/ */
export const stringifyMessage = (decoder, y) => { export const stringifySyncMessage = (decoder, y) => {
const messageType = decoding.readVarUint(decoder) const messageType = decoding.readVarUint(decoder)
let stringifiedMessage let stringifiedMessage
let stringifiedMessageType let stringifiedMessageType
@@ -468,7 +473,7 @@ export const stringifyMessage = (decoder, y) => {
* @param {encoding.Encoder} encoder The reply message. Will not be sent if empty. * @param {encoding.Encoder} encoder The reply message. Will not be sent if empty.
* @param {Y} y * @param {Y} y
*/ */
export const readMessage = (decoder, encoder, y) => { export const readSyncMessage = (decoder, encoder, y) => {
const messageType = decoding.readVarUint(decoder) const messageType = decoding.readVarUint(decoder)
switch (messageType) { switch (messageType) {
case messageYjsSyncStep1: case messageYjsSyncStep1:

5
provider/websocket.mjs Normal file
View File

@@ -0,0 +1,5 @@
/**
* @module provider/websocket
*/
export * from './websocket/WebSocketProvider.mjs'

View File

@@ -1,7 +1,14 @@
/**
* @module provider/websocket
*/
/* eslint-env browser */ /* eslint-env browser */
import * as Y from '../../src/index.js' import * as Y from '../../index.mjs'
export * from '../../src/index.js' export * from '../../index.mjs'
const messageSync = 0
const messageAwareness = 1
const reconnectTimeout = 100 const reconnectTimeout = 100
@@ -12,10 +19,19 @@ const setupWS = (doc, url) => {
websocket.onmessage = event => { websocket.onmessage = event => {
const decoder = Y.createDecoder(event.data) const decoder = Y.createDecoder(event.data)
const encoder = Y.createEncoder() const encoder = Y.createEncoder()
doc.mux(() => const messageType = Y.readVarUint(decoder)
Y.readMessage(decoder, encoder, doc) switch (messageType) {
) case messageSync:
if (Y.length(encoder) > 0) { Y.writeVarUint(encoder, messageSync)
doc.mux(() =>
Y.readSyncMessage(decoder, encoder, doc)
)
break
case messageAwareness:
Y.readAwarenessMessage(decoder, doc)
break
}
if (Y.length(encoder) > 1) {
websocket.send(Y.toBuffer(encoder)) websocket.send(Y.toBuffer(encoder))
} }
} }
@@ -34,8 +50,11 @@ const setupWS = (doc, url) => {
}) })
// always send sync step 1 when connected // always send sync step 1 when connected
const encoder = Y.createEncoder() const encoder = Y.createEncoder()
Y.writeVarUint(encoder, messageSync)
Y.writeSyncStep1(encoder, doc) Y.writeSyncStep1(encoder, doc)
websocket.send(Y.toBuffer(encoder)) websocket.send(Y.toBuffer(encoder))
// force send stored awareness info
doc.setAwarenessField(null, null)
} }
} }
@@ -43,6 +62,7 @@ const broadcastUpdate = (y, transaction) => {
if (y.wsconnected && transaction.encodedStructsLen > 0) { if (y.wsconnected && transaction.encodedStructsLen > 0) {
y.mux(() => { y.mux(() => {
const encoder = Y.createEncoder() const encoder = Y.createEncoder()
Y.writeVarUint(encoder, messageSync)
Y.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs) Y.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs)
y.ws.send(Y.toBuffer(encoder)) y.ws.send(Y.toBuffer(encoder))
}) })
@@ -54,12 +74,32 @@ class WebsocketsSharedDocument extends Y.Y {
super() super()
this.wsconnected = false this.wsconnected = false
this.mux = Y.createMutex() this.mux = Y.createMutex()
this.ws = null
this._localAwarenessState = {}
this.awareness = new Map()
setupWS(this, url) setupWS(this, url)
this.on('afterTransaction', broadcastUpdate) this.on('afterTransaction', broadcastUpdate)
} }
getLocalAwarenessInfo () {
return this._localAwarenessState
}
getAwarenessInfo () {
return this.awareness
}
setAwarenessField (field, value) {
if (field !== null) {
this._localAwarenessState[field] = value
}
if (this.wsconnected) {
const encoder = Y.createEncoder()
Y.writeVarUint(encoder, messageAwareness)
Y.writeUsersStateChange(encoder, [{ userID: this.userID, state: this._localAwarenessState }])
this.ws.send(Y.toBuffer(encoder))
}
}
} }
export default class WebsocketProvider { export class WebsocketProvider {
constructor (url) { constructor (url) {
// ensure that url is always ends with / // ensure that url is always ends with /
while (url[url.length - 1] === '/') { while (url[url.length - 1] === '/') {

View File

@@ -1,53 +0,0 @@
const Y = require('../../build/node/index.js')
const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 1234 })
const docs = new Map()
const afterTransaction = (doc, transaction) => {
if (transaction.encodedStructsLen > 0) {
const encoder = Y.createEncoder()
Y.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs)
const message = Y.toBuffer(encoder)
doc.conns.forEach(conn => conn.send(message))
}
}
class WSSharedDoc extends Y.Y {
constructor () {
super()
this.mux = Y.createMutex()
this.conns = new Set()
this.on('afterTransaction', afterTransaction)
}
}
const messageListener = (conn, doc, message) => {
const encoder = Y.createEncoder()
const decoder = Y.createDecoder(message)
Y.readMessage(decoder, encoder, doc)
if (Y.length(encoder) > 0) {
conn.send(Y.toBuffer(encoder))
}
}
const setupConnection = (conn, req) => {
conn.binaryType = 'arraybuffer'
// get doc, create if it does not exist yet
let doc = docs.get(req.url.slice(1))
if (doc === undefined) {
doc = new WSSharedDoc()
docs.set(req.url.slice(1), doc)
}
doc.conns.add(conn)
// listen and reply to events
conn.on('message', message => messageListener(conn, doc, message))
conn.on('close', () =>
doc.conns.delete(conn)
)
// send sync step 1
const encoder = Y.createEncoder()
Y.writeSyncStep1(encoder, doc)
conn.send(Y.toBuffer(encoder))
}
wss.on('connection', setupConnection)

Some files were not shown because too many files have changed in this diff Show More