large scale refactoring
This commit is contained in:
parent
ade3e1949d
commit
9c0da271eb
@ -1,6 +1,7 @@
|
||||
{
|
||||
"source": "./src",
|
||||
"source": ".",
|
||||
"destination": "./docs",
|
||||
"excludes": ["build", "node_modules", "tests-lib", "test"],
|
||||
"plugins": [{
|
||||
"name": "esdoc-standard-plugin",
|
||||
"option": {
|
||||
|
51
.jsdoc.json
Normal file
51
.jsdoc.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"sourceType": "module",
|
||||
"tags": {
|
||||
"allowUnknownTags": true,
|
||||
"dictionaries": ["jsdoc"]
|
||||
},
|
||||
"source": {
|
||||
"include": ["types", "utils/UndoManager.js", "utils/Y.js", "provider", "bindings"],
|
||||
"includePattern": ".js$",
|
||||
"excludePattern": "(node_modules/|docs|build|examples)"
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown"
|
||||
],
|
||||
"templates": {
|
||||
"referenceTitle": "Yjs",
|
||||
"disableSort": false,
|
||||
"useCollapsibles": true,
|
||||
"collapse": true,
|
||||
"resources": {
|
||||
"y-js.org": "y-js.org"
|
||||
},
|
||||
"logo": {
|
||||
"url": "http://y-js.org/images/yjs.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"
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
|
||||
import { createMutex } from '../lib/mutex.js'
|
||||
|
||||
/**
|
||||
* Abstract class for bindings.
|
||||
*
|
||||
* A binding handles data binding from a Yjs type to a data object. For example,
|
||||
* you can bind a Quill editor instance to a YText instance with the `QuillBinding` class.
|
||||
*
|
||||
* It is expected that a concrete implementation accepts two parameters
|
||||
* (type and binding target).
|
||||
*
|
||||
* @example
|
||||
* const quill = new Quill(document.createElement('div'))
|
||||
* const type = y.define('quill', Y.Text)
|
||||
* const binding = new Y.QuillBinding(quill, type)
|
||||
*
|
||||
*/
|
||||
export default class Binding {
|
||||
/**
|
||||
* @param {YType} type Yjs type.
|
||||
* @param {any} target Binding Target.
|
||||
*/
|
||||
constructor (type, target) {
|
||||
/**
|
||||
* The Yjs type that is bound to `target`
|
||||
* @type {YType}
|
||||
*/
|
||||
this.type = type
|
||||
/**
|
||||
* The target that `type` is bound to.
|
||||
* @type {*}
|
||||
*/
|
||||
this.target = target
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this._mutualExclude = createMutex()
|
||||
}
|
||||
/**
|
||||
* Remove all data observers (both from the type and the target).
|
||||
*/
|
||||
destroy () {
|
||||
this.type = null
|
||||
this.target = null
|
||||
}
|
||||
}
|
1
bindings/dom.js
Normal file
1
bindings/dom.js
Normal file
@ -0,0 +1 @@
|
||||
export * from './dom/DomBinding.js'
|
@ -1,15 +1,23 @@
|
||||
/**
|
||||
* @module bindings/dom
|
||||
*/
|
||||
|
||||
/* global MutationObserver, getSelection */
|
||||
|
||||
import { fromRelativePosition } from '../../src/Util/relativePosition.js'
|
||||
import Binding from '../Binding.js'
|
||||
import { fromRelativePosition } from '../../utils/relativePosition.js'
|
||||
import { createMutex } from '../../lib/mutex.js'
|
||||
import { createAssociation, removeAssociation } from './util.js'
|
||||
import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer, getCurrentRelativeSelection } from './selection.js'
|
||||
import { defaultFilter, applyFilterOnType } from './filter.js'
|
||||
import typeObserver from './typeObserver.js'
|
||||
import domObserver from './domObserver.js'
|
||||
import { typeObserver } from './typeObserver.js'
|
||||
import { domObserver } from './domObserver.js'
|
||||
import { YXmlFragment } from '../../types/YXmlElement.js' // 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 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
|
||||
* truth.
|
||||
@ -31,10 +40,26 @@ export default class DomBinding extends Binding {
|
||||
* @param {Object} [opts] Optional configurations
|
||||
|
||||
* @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 = {}) {
|
||||
// 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
|
||||
opts.document = opts.document || document
|
||||
opts.hooks = opts.hooks || {}
|
||||
@ -81,16 +106,16 @@ export default class DomBinding extends Binding {
|
||||
this.y = y
|
||||
// Force flush dom changes before Type changes are applied (they might
|
||||
// modify the dom)
|
||||
this._beforeTransactionHandler = (y, transaction, remote) => {
|
||||
this._beforeTransactionHandler = y => {
|
||||
this._domObserver(this._mutationObserver.takeRecords())
|
||||
this._mutualExclude(() => {
|
||||
beforeTransactionSelectionFixer(this, remote)
|
||||
beforeTransactionSelectionFixer(this)
|
||||
})
|
||||
}
|
||||
y.on('beforeTransaction', this._beforeTransactionHandler)
|
||||
this._afterTransactionHandler = (y, transaction, remote) => {
|
||||
this._afterTransactionHandler = (y, transaction) => {
|
||||
this._mutualExclude(() => {
|
||||
afterTransactionSelectionFixer(this, remote)
|
||||
afterTransactionSelectionFixer(this)
|
||||
})
|
||||
// remove associations
|
||||
// TODO: this could be done more efficiently
|
||||
@ -206,13 +231,18 @@ export default class DomBinding extends Binding {
|
||||
y.off('beforeObserverCalls', this._beforeObserverCallsHandler)
|
||||
y.off('afterTransaction', this._afterTransactionHandler)
|
||||
document.removeEventListener('selectionchange', this._selectionchange)
|
||||
super.destroy()
|
||||
this.type = null
|
||||
this.target = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A filter defines which elements and attributes to share.
|
||||
* Return null if the node should be filtered. Otherwise return the Map of
|
||||
* accepted attributes.
|
||||
*
|
||||
* @typedef {function(nodeName: String, attrs: Map): Map|null} FilterFunction
|
||||
* @callback FilterFunction
|
||||
* @param {string} nodeName
|
||||
* @param {Map} attrs
|
||||
* @return {Map|null}
|
||||
*/
|
@ -1,11 +1,14 @@
|
||||
/**
|
||||
* @module bindings/dom
|
||||
*/
|
||||
|
||||
import YXmlHook from '../../src/Types/YXml/YXmlHook.js'
|
||||
import { YXmlHook } from '../../types/YXmlHook.js'
|
||||
import {
|
||||
iterateUntilUndeleted,
|
||||
removeAssociation,
|
||||
insertNodeHelper } from './util.js'
|
||||
import diff from '../../lib/simpleDiff.js'
|
||||
import YXmlFragment from '../../src/Types/YXml/YXmlFragment.js'
|
||||
import { simpleDiff } from '../../lib/diff.js'
|
||||
import { YXmlFragment } from '../../types/YXmlElement.js'
|
||||
|
||||
/**
|
||||
* 1. Check if any of the nodes was deleted
|
||||
@ -19,7 +22,7 @@ import YXmlFragment from '../../src/Types/YXml/YXmlFragment.js'
|
||||
* !== actualId in the list
|
||||
* @private
|
||||
*/
|
||||
function applyChangesFromDom (binding, dom, yxml, _document) {
|
||||
const applyChangesFromDom = (binding, dom, yxml, _document) => {
|
||||
if (yxml == null || yxml === false || yxml.constructor === YXmlHook) {
|
||||
return
|
||||
}
|
||||
@ -32,7 +35,7 @@ function applyChangesFromDom (binding, dom, yxml, _document) {
|
||||
}
|
||||
}
|
||||
// 1. Check if any of the nodes was deleted
|
||||
yxml.forEach(function (childType) {
|
||||
yxml.forEach(childType => {
|
||||
if (knownChildren.has(childType) === false) {
|
||||
childType._delete(y)
|
||||
removeAssociation(binding, binding.typeToDom.get(childType), childType)
|
||||
@ -83,7 +86,7 @@ function applyChangesFromDom (binding, dom, yxml, _document) {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export default function domObserver (mutations, _document) {
|
||||
export function domObserver (mutations, _document) {
|
||||
this._mutualExclude(() => {
|
||||
this.type._y.transact(() => {
|
||||
let diffChildren = new Set()
|
||||
@ -107,7 +110,7 @@ export default function domObserver (mutations, _document) {
|
||||
}
|
||||
switch (mutation.type) {
|
||||
case 'characterData':
|
||||
var change = diff(yxml.toString(), dom.nodeValue)
|
||||
var change = simpleDiff(yxml.toString(), dom.nodeValue)
|
||||
yxml.delete(change.pos, change.remove)
|
||||
yxml.insert(change.pos, change.insert)
|
||||
break
|
@ -1,13 +1,20 @@
|
||||
/**
|
||||
* @module bindings/dom
|
||||
*/
|
||||
|
||||
/* eslint-env browser */
|
||||
import YXmlText from '../../src/Types/YXml/YXmlText.js'
|
||||
import YXmlHook from '../../src/Types/YXml/YXmlHook.js'
|
||||
import YXmlElement from '../../src/Types/YXml/YXmlElement.js'
|
||||
import { YXmlText } from '../../types/YXmlText.js'
|
||||
import { YXmlHook } from '../../types/YXmlHook.js'
|
||||
import { YXmlElement } from '../../types/YXmlElement.js'
|
||||
import { createAssociation, domsToTypes } from './util.js'
|
||||
import { filterDomAttributes, defaultFilter } from './filter.js'
|
||||
import { DomBinding } from './DomBinding.js' // eslint-disable-line
|
||||
|
||||
/**
|
||||
* @typedef {import('./filter.js').DomFilter} DomFilter
|
||||
* @typedef {import('./DomBinding.js').default} DomBinding
|
||||
* @callback DomFilter
|
||||
* @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!
|
||||
* @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}
|
||||
*/
|
@ -1,12 +1,11 @@
|
||||
import isParentOf from '../../src/Util/isParentOf.js'
|
||||
|
||||
/**
|
||||
* @callback DomFilter
|
||||
* @param {string} nodeName
|
||||
* @param {Map<string, string>} attrs
|
||||
* @return {Map | null}
|
||||
* @module bindings/dom
|
||||
*/
|
||||
|
||||
import { isParentOf } from '../../utils/isParentOf.js'
|
||||
import * as Y from '../../index.js'
|
||||
import { DomBinding } from './DomBinding.js' // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Default filter method (does nothing).
|
||||
*
|
||||
@ -15,7 +14,7 @@ import isParentOf from '../../src/Util/isParentOf.js'
|
||||
* @return {Map | null} The allowed attributes or null, if the element should be
|
||||
* filtered.
|
||||
*/
|
||||
export function defaultFilter (nodeName, attrs) {
|
||||
export const defaultFilter = (nodeName, attrs) => {
|
||||
// TODO: implement basic filter that filters out dangerous properties!
|
||||
return attrs
|
||||
}
|
||||
@ -23,7 +22,7 @@ export function defaultFilter (nodeName, attrs) {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export function filterDomAttributes (dom, filter) {
|
||||
export const filterDomAttributes = (dom, filter) => {
|
||||
const attrs = new Map()
|
||||
for (let i = dom.attributes.length - 1; i >= 0; i--) {
|
||||
const attr = dom.attributes[i]
|
||||
@ -35,14 +34,14 @@ export function filterDomAttributes (dom, filter) {
|
||||
/**
|
||||
* Applies a filter on a type.
|
||||
*
|
||||
* @param {Y} y The Yjs instance.
|
||||
* @param {Y.Y} y The Yjs instance.
|
||||
* @param {DomBinding} binding The DOM binding instance that has the dom filter.
|
||||
* @param {YXmlElement | YXmlFragment } type The type to apply the filter to.
|
||||
* @param {Y.XmlElement | Y.XmlFragment } type The type to apply the filter to.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
export function applyFilterOnType (y, binding, type) {
|
||||
if (isParentOf(binding.type, type)) {
|
||||
export const applyFilterOnType = (y, binding, type) => {
|
||||
if (isParentOf(binding.type, type) && type instanceof Y.XmlElement) {
|
||||
const nodeName = type.nodeName
|
||||
let attributes = new Map()
|
||||
if (type.getAttributes !== undefined) {
|
||||
@ -53,7 +52,7 @@ export function applyFilterOnType (y, binding, type) {
|
||||
}
|
||||
const filteredAttributes = binding.filter(nodeName, new Map(attributes))
|
||||
if (filteredAttributes === null) {
|
||||
type._delete(y)
|
||||
type._delete(y, true)
|
||||
} else {
|
||||
// iterate original attributes
|
||||
attributes.forEach((value, key) => {
|
@ -1,10 +1,14 @@
|
||||
/**
|
||||
* @module bindings/dom
|
||||
*/
|
||||
|
||||
/* globals getSelection */
|
||||
|
||||
import { getRelativePosition } from '../../src/Util/relativePosition.js'
|
||||
import { getRelativePosition } from '../../utils/relativePosition.js'
|
||||
|
||||
let relativeSelection = null
|
||||
|
||||
function _getCurrentRelativeSelection (domBinding) {
|
||||
const _getCurrentRelativeSelection = domBinding => {
|
||||
const { baseNode, baseOffset, extentNode, extentOffset } = getSelection()
|
||||
const baseNodeType = domBinding.domToType.get(baseNode)
|
||||
const extentNodeType = domBinding.domToType.get(extentNode)
|
||||
@ -19,7 +23,7 @@ function _getCurrentRelativeSelection (domBinding) {
|
||||
|
||||
export const getCurrentRelativeSelection = typeof getSelection !== 'undefined' ? _getCurrentRelativeSelection : domBinding => null
|
||||
|
||||
export function beforeTransactionSelectionFixer (domBinding) {
|
||||
export const beforeTransactionSelectionFixer = domBinding => {
|
||||
relativeSelection = getCurrentRelativeSelection(domBinding)
|
||||
}
|
||||
|
||||
@ -28,7 +32,7 @@ export function beforeTransactionSelectionFixer (domBinding) {
|
||||
* This prevents any collapsing issues with the local selection.
|
||||
* @private
|
||||
*/
|
||||
export function afterTransactionSelectionFixer (domBinding) {
|
||||
export const afterTransactionSelectionFixer = domBinding => {
|
||||
if (relativeSelection !== null) {
|
||||
domBinding.restoreSelection(relativeSelection)
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
/**
|
||||
* @module bindings/dom
|
||||
*/
|
||||
|
||||
/* eslint-env browser */
|
||||
/* global getSelection */
|
||||
|
||||
import YXmlText from '../../src/Types/YXml/YXmlText.js'
|
||||
import YXmlHook from '../../src/Types/YXml/YXmlHook.js'
|
||||
import { YXmlText } from '../../types/YXmlText.js'
|
||||
import { YXmlHook } from '../../types/YXmlHook.js'
|
||||
import { removeDomChildrenUntilElementFound } from './util.js'
|
||||
|
||||
function findScrollReference (scrollingElement) {
|
||||
const findScrollReference = scrollingElement => {
|
||||
if (scrollingElement !== null) {
|
||||
let anchor = getSelection().anchorNode
|
||||
if (anchor == null) {
|
||||
@ -34,7 +38,7 @@ function findScrollReference (scrollingElement) {
|
||||
return null
|
||||
}
|
||||
|
||||
function fixScroll (scrollingElement, ref) {
|
||||
const fixScroll = (scrollingElement, ref) => {
|
||||
if (ref !== null) {
|
||||
const { elem, top } = ref
|
||||
const currentTop = elem.getBoundingClientRect().top
|
||||
@ -48,7 +52,7 @@ function fixScroll (scrollingElement, ref) {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export default function typeObserver (events) {
|
||||
export const typeObserver = function (events) {
|
||||
this._mutualExclude(() => {
|
||||
const scrollRef = findScrollReference(this.scrollingElement)
|
||||
events.forEach(event => {
|
@ -1,19 +1,19 @@
|
||||
|
||||
import domToType from './domToType.js'
|
||||
|
||||
/**
|
||||
* @typedef {import('../../src/Types/YXml/YXmlText.js').default} YXmlText
|
||||
* @typedef {import('../../src/Types/YXml/YXmlElement.js').default} YXmlElement
|
||||
* @typedef {import('../../src/Types/YXml/YXmlHook.js').default} YXmlHook
|
||||
* @typedef {import('./DomBinding.js').default} DomBinding
|
||||
* @module bindings/dom
|
||||
*/
|
||||
|
||||
import { domToType } from './domToType.js'
|
||||
import { DomBinding } from './DomBinding.js' // eslint-disable-line
|
||||
import { YXmlHook } from '../../types/YXmlHook.js' // eslint-disable-line
|
||||
import { YXmlText } from '../../types/YXmlText.js' // eslint-disable-line
|
||||
import { YXmlElement, YXmlFragment } from '../../types/YXmlElement.js' // eslint-disable-line
|
||||
|
||||
/**
|
||||
* Iterates items until an undeleted item is found.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
export function iterateUntilUndeleted (item) {
|
||||
export const iterateUntilUndeleted = item => {
|
||||
while (item !== null && item._deleted) {
|
||||
item = item._right
|
||||
}
|
||||
@ -24,12 +24,13 @@ export function iterateUntilUndeleted (item) {
|
||||
* Removes an association (the information that a DOM element belongs to a
|
||||
* type).
|
||||
*
|
||||
* @private
|
||||
* @param {DomBinding} domBinding The binding object
|
||||
* @param {Element} dom The dom that is to be associated with type
|
||||
* @param {YXmlElement|YXmlHook} type The type that is to be associated with dom
|
||||
*
|
||||
*/
|
||||
export function removeAssociation (domBinding, dom, type) {
|
||||
export const removeAssociation = (domBinding, dom, type) => {
|
||||
domBinding.domToType.delete(dom)
|
||||
domBinding.typeToDom.delete(type)
|
||||
}
|
||||
@ -38,12 +39,13 @@ export function removeAssociation (domBinding, dom, type) {
|
||||
* Creates an association (the information that a DOM element belongs to a
|
||||
* type).
|
||||
*
|
||||
* @private
|
||||
* @param {DomBinding} domBinding The binding object
|
||||
* @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) {
|
||||
domBinding.domToType.set(dom, type)
|
||||
domBinding.typeToDom.set(type, dom)
|
||||
@ -54,11 +56,12 @@ export function createAssociation (domBinding, dom, type) {
|
||||
* 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.
|
||||
*
|
||||
* @private
|
||||
* @param {DomBinding} domBinding The binding object
|
||||
* @param {Element} oldDom The existing dom
|
||||
* @param {Element} newDom The new dom object
|
||||
*/
|
||||
export function switchAssociation (domBinding, oldDom, newDom) {
|
||||
export const switchAssociation = (domBinding, oldDom, newDom) => {
|
||||
if (domBinding !== undefined) {
|
||||
const type = domBinding.domToType.get(oldDom)
|
||||
if (type !== undefined) {
|
||||
@ -73,6 +76,7 @@ export function switchAssociation (domBinding, oldDom, newDom) {
|
||||
* The Dom elements will be bound to a new YXmlElement and inserted at the
|
||||
* specified position.
|
||||
*
|
||||
* @private
|
||||
* @param {YXmlElement} type The type in which to insert DOM elements.
|
||||
* @param {YXmlElement|null} prev The reference node. New YxmlElements are
|
||||
* inserted after this node. Set null to insert at
|
||||
@ -81,15 +85,13 @@ export function switchAssociation (domBinding, oldDom, newDom) {
|
||||
* @param {?Document} _document Optional. Provide the global document object.
|
||||
* @param {DomBinding} binding The dom binding
|
||||
* @return {Array<YXmlElement>} The YxmlElements that are inserted.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
export function insertDomElementsAfter (type, prev, doms, _document, binding) {
|
||||
export const insertDomElementsAfter = (type, prev, doms, _document, binding) => {
|
||||
const types = domsToTypes(doms, _document, binding.opts.hooks, binding.filter, binding)
|
||||
return type.insertAfter(prev, types)
|
||||
}
|
||||
|
||||
export function domsToTypes (doms, _document, hooks, filter, binding) {
|
||||
export const domsToTypes = (doms, _document, hooks, filter, binding) => {
|
||||
const types = []
|
||||
for (let dom of doms) {
|
||||
const t = domToType(dom, _document, hooks, filter, binding)
|
||||
@ -103,7 +105,7 @@ export function domsToTypes (doms, _document, hooks, filter, binding) {
|
||||
/**
|
||||
* @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)
|
||||
if (insertedNodes.length > 0) {
|
||||
return insertedNodes[0]
|
||||
@ -115,14 +117,13 @@ export function insertNodeHelper (yxml, prevExpectedNode, child, _document, bind
|
||||
/**
|
||||
* Remove children until `elem` is found.
|
||||
*
|
||||
* @private
|
||||
* @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.
|
||||
* @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) {
|
||||
const del = currentChild
|
||||
currentChild = currentChild.nextSibling
|
@ -1,7 +1,11 @@
|
||||
import BindMapping from '../BindMapping.js'
|
||||
/**
|
||||
* @module bindings/prosemirror
|
||||
*/
|
||||
|
||||
import { BindMapping } from '../utils/BindMapping.js'
|
||||
import * as Y from '../index.js'
|
||||
import { createMutex } from '../lib/mutex.js'
|
||||
import * as PModel from 'prosemirror-model'
|
||||
import * as Y from '../../src/index.js'
|
||||
import { createMutex } from '../../lib/mutex.js'
|
||||
import { Plugin, PluginKey } from 'prosemirror-state'
|
||||
import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||
|
||||
@ -11,6 +15,11 @@ import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||
* @typedef {BindMapping<Y.Text | Y.XmlElement, PModel.Node>} ProsemirrorMapping
|
||||
*/
|
||||
|
||||
/**
|
||||
* The unique prosemirror plugin key for prosemirrorPlugin.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const prosemirrorPluginKey = new PluginKey('yjs')
|
||||
|
||||
/**
|
||||
@ -18,6 +27,7 @@ export const prosemirrorPluginKey = new PluginKey('yjs')
|
||||
*
|
||||
* This plugin also keeps references to the type and the shared document so other plugins can access it.
|
||||
* @param {Y.XmlFragment} yXmlFragment
|
||||
* @return {Plugin} Returns a prosemirror plugin that binds to this type
|
||||
*/
|
||||
export const prosemirrorPlugin = yXmlFragment => {
|
||||
const pluginState = {
|
||||
@ -51,8 +61,19 @@ export const prosemirrorPlugin = yXmlFragment => {
|
||||
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: {
|
||||
@ -104,7 +125,12 @@ export const cursorPlugin = new Plugin({
|
||||
}
|
||||
})
|
||||
|
||||
export default class ProsemirrorBinding {
|
||||
/**
|
||||
* Binding for prosemirror.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export class ProsemirrorBinding {
|
||||
/**
|
||||
* @param {Y.XmlFragment} yXmlFragment The bind source
|
||||
* @param {EditorView} prosemirrorView The target binding
|
||||
@ -154,6 +180,7 @@ export default class ProsemirrorBinding {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Y.XmlElement} el
|
||||
* @param {PModel.Schema} schema
|
||||
* @param {ProsemirrorMapping} mapping
|
||||
@ -168,6 +195,7 @@ export const createNodeIfNotExists = (el, schema, mapping) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Y.XmlElement} el
|
||||
* @param {PModel.Schema} schema
|
||||
* @param {ProsemirrorMapping} mapping
|
||||
@ -188,6 +216,7 @@ export const createNodeFromYElement = (el, schema, mapping) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Y.Text} text
|
||||
* @param {PModel.Schema} schema
|
||||
* @param {ProsemirrorMapping} mapping
|
||||
@ -211,6 +240,7 @@ export const createTextNodesFromYText = (text, schema, mapping) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {PModel.Node} node
|
||||
* @param {ProsemirrorMapping} mapping
|
||||
* @return {Y.XmlElement | Y.Text}
|
||||
@ -234,6 +264,7 @@ export const createTypeFromNode = (node, mapping) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Y.XmlFragment} yDomFragment
|
||||
* @param {EditorState} state
|
||||
* @param {BindMapping} mapping
|
@ -1,10 +1,14 @@
|
||||
import Binding from '../Binding.js'
|
||||
/**
|
||||
* @module bindings/quill
|
||||
*/
|
||||
|
||||
function typeObserver (event) {
|
||||
import { createMutex } from '../lib/mutex.js'
|
||||
|
||||
const typeObserver = function (event) {
|
||||
const quill = this.target
|
||||
// Force flush Quill changes.
|
||||
quill.update('yjs')
|
||||
this._mutualExclude(function () {
|
||||
this._mutualExclude(() => {
|
||||
// Apply computed delta.
|
||||
quill.updateContents(event.delta, 'yjs')
|
||||
// Force flush Quill changes. Ignore applied changes.
|
||||
@ -12,7 +16,7 @@ function typeObserver (event) {
|
||||
})
|
||||
}
|
||||
|
||||
function quillObserver (delta) {
|
||||
const quillObserver = function (delta) {
|
||||
this._mutualExclude(() => {
|
||||
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
|
||||
* // way around!
|
||||
*/
|
||||
export default class QuillBinding extends Binding {
|
||||
export class QuillBinding {
|
||||
/**
|
||||
* @param {YText} textType
|
||||
* @param {Quill} quill
|
||||
*/
|
||||
constructor (textType, quill) {
|
||||
// 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.
|
||||
quill.setContents(textType.toDelta(), 'yjs')
|
||||
// Observers are handled by this class.
|
||||
@ -48,6 +66,7 @@ export default class QuillBinding extends Binding {
|
||||
// Remove everything that is handled by this class.
|
||||
this.type.unobserve(this._typeObserver)
|
||||
this.target.off('text-change', this._quillObserver)
|
||||
super.destroy()
|
||||
this.type = null
|
||||
this.target = null
|
||||
}
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
/**
|
||||
* @module bindings/textarea
|
||||
*/
|
||||
|
||||
import Binding from '../Binding.js'
|
||||
import simpleDiff from '../../lib/simpleDiff.js'
|
||||
import { getRelativePosition, fromRelativePosition } from '../../src/Util/relativePosition.js'
|
||||
import { simpleDiff } from '../lib/diff.js'
|
||||
import { getRelativePosition, fromRelativePosition } from '../utils/relativePosition.js'
|
||||
import { createMutex } from '../lib/mutex.js'
|
||||
|
||||
function typeObserver () {
|
||||
this._mutualExclude(() => {
|
||||
@ -35,10 +38,22 @@ function domObserver () {
|
||||
* const binding = new Y.QuillBinding(type, textarea)
|
||||
*
|
||||
*/
|
||||
export default class TextareaBinding extends Binding {
|
||||
export class TextareaBinding {
|
||||
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
|
||||
domTextarea.value = textType.toString()
|
||||
// Observers are handled by this class
|
||||
@ -51,6 +66,7 @@ export default class TextareaBinding extends Binding {
|
||||
// Remove everything that is handled by this class
|
||||
this.type.unobserve(this._typeObserver)
|
||||
this.target.unobserve(this._domObserver)
|
||||
super.destroy()
|
||||
this.type = null
|
||||
this.target = null
|
||||
}
|
||||
}
|
1
examples/.gitignore
vendored
Normal file
1
examples/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
build
|
11
examples/examples.json
Normal file
11
examples/examples.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"prosemirror": {
|
||||
"title": "Prosemirror Binding"
|
||||
},
|
||||
"textarea": {
|
||||
"title": "Textarea Binding"
|
||||
},
|
||||
"quill": {
|
||||
"title": "Quill Binding"
|
||||
}
|
||||
}
|
75
examples/prosemirror.html
Normal file
75
examples/prosemirror.html
Normal file
@ -0,0 +1,75 @@
|
||||
<!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">
|
||||
<h3>Hello User</h3>
|
||||
<p>type here ...</p>
|
||||
</div>
|
||||
</div>
|
||||
<script class="code-js" src="./build/prosemirror.js">
|
||||
import * as Y from 'yjs'
|
||||
import { prosemirrorPlugin, cursorPlugin } from 'yjs/bindings/prosemirror.js'
|
||||
import { WebsocketProvider } from 'yjs/provider/websocket.js'
|
||||
|
||||
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('ws://localhost:1234/')
|
||||
const ydocument = provider.get('prosemirror')
|
||||
const type = ydocument.define('prosemirror', Y.XmlFragment)
|
||||
|
||||
window.prosemirrorView = new EditorView(document.querySelector('#editor'), {
|
||||
state: EditorState.create({
|
||||
doc: DOMParser.fromSchema(schema).parse(document.querySelector('#content')),
|
||||
plugins: exampleSetup({schema}).concat([prosemirrorPlugin(type), cursorPlugin])
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
20
examples/prosemirror.js
Normal file
20
examples/prosemirror.js
Normal file
@ -0,0 +1,20 @@
|
||||
import * as Y from '../index.js'
|
||||
import { prosemirrorPlugin, cursorPlugin } from '../bindings/prosemirror.js'
|
||||
import { WebsocketProvider } from '../provider/websocket.js'
|
||||
|
||||
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('ws://localhost:1234/')
|
||||
const ydocument = provider.get('prosemirror')
|
||||
const type = ydocument.define('prosemirror', Y.XmlFragment)
|
||||
|
||||
window.prosemirrorView = new EditorView(document.querySelector('#editor'), {
|
||||
state: EditorState.create({
|
||||
doc: DOMParser.fromSchema(schema).parse(document.querySelector('#content')),
|
||||
plugins: exampleSetup({schema}).concat([prosemirrorPlugin(type), cursorPlugin])
|
||||
})
|
||||
})
|
@ -1,73 +0,0 @@
|
||||
/* eslint-env browser */
|
||||
|
||||
import {Plugin} from 'prosemirror-state'
|
||||
import {Decoration, DecorationSet} from 'prosemirror-view'
|
||||
import {schema} from 'prosemirror-schema-basic'
|
||||
|
||||
const findPlaceholder = (state, id) => {
|
||||
let decos = PlaceholderPlugin.getState(state)
|
||||
let found = decos.find(null, null, spec => spec.id === id)
|
||||
return found.length ? found[0].from : null
|
||||
}
|
||||
|
||||
export const startImageUpload = (view, file) => {
|
||||
// A fresh object to act as the ID for this upload
|
||||
let id = {}
|
||||
|
||||
// Replace the selection with a placeholder
|
||||
let tr = view.state.tr
|
||||
if (!tr.selection.empty) tr.deleteSelection()
|
||||
tr.setMeta(PlaceholderPlugin, {add: {id, pos: tr.selection.from}})
|
||||
view.dispatch(tr)
|
||||
|
||||
uploadFile(file).then(url => {
|
||||
let pos = findPlaceholder(view.state, id)
|
||||
// If the content around the placeholder has been deleted, drop
|
||||
// the image
|
||||
if (pos == null) return
|
||||
// Otherwise, insert it at the placeholder's position, and remove
|
||||
// the placeholder
|
||||
view.dispatch(view.state.tr
|
||||
.replaceWith(pos, pos, schema.nodes.image.create({src: url}))
|
||||
.setMeta(PlaceholderPlugin, {remove: {id}}))
|
||||
}, () => {
|
||||
// On failure, just clean up the placeholder
|
||||
view.dispatch(tr.setMeta(PlaceholderPlugin, {remove: {id}}))
|
||||
})
|
||||
}
|
||||
|
||||
// This is just a dummy that loads the file and creates a data URL.
|
||||
// You could swap it out with a function that does an actual upload
|
||||
// and returns a regular URL for the uploaded file.
|
||||
function uploadFile (file) {
|
||||
let reader = new FileReader()
|
||||
return new Promise((resolve, reject) => {
|
||||
reader.onload = () => resolve(reader.result)
|
||||
reader.onerror = () => reject(reader.error)
|
||||
// Some extra delay to make the asynchronicity visible
|
||||
setTimeout(() => reader.readAsDataURL(file), 1500)
|
||||
})
|
||||
}
|
||||
|
||||
export const PlaceholderPlugin = new Plugin({
|
||||
state: {
|
||||
init () { return DecorationSet.empty },
|
||||
apply (tr, set) {
|
||||
// Adjust decoration positions to changes made by the transaction
|
||||
set = set.map(tr.mapping, tr.doc)
|
||||
// See if the transaction adds or removes any placeholders
|
||||
let action = tr.getMeta(this)
|
||||
if (action && action.add) {
|
||||
let widget = document.createElement('placeholder')
|
||||
let deco = Decoration.widget(action.add.pos, widget, {id: action.add.id})
|
||||
set = set.add(tr.doc, [deco])
|
||||
} else if (action && action.remove) {
|
||||
set = set.remove(set.find(null, null, spec => spec.id === action.remove.id))
|
||||
}
|
||||
return set
|
||||
}
|
||||
},
|
||||
props: {
|
||||
decorations (state) { return this.getState(state) }
|
||||
}
|
||||
})
|
@ -1,21 +0,0 @@
|
||||
|
||||
# Prosemirror Example
|
||||
|
||||
### Run basic websockets server
|
||||
|
||||
```sh
|
||||
node /provider/websocket/server.js
|
||||
```
|
||||
|
||||
### Bundle Prosemirror Example
|
||||
|
||||
This example requires external modules and needs to be bundled before shipping it to the browser.
|
||||
|
||||
```sh
|
||||
cd /examples/prosemirror/
|
||||
# bundle prosemirror example
|
||||
npx rollup -wc
|
||||
# serve example
|
||||
npx serve .
|
||||
```
|
||||
|
@ -1,51 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="module" src="./index.dist.js"></script>
|
||||
<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 id="editor" style="margin-bottom: 23px"></div>
|
||||
<div style="display: none" id="content">
|
||||
<h3>Hello User</h3>
|
||||
<p>type something ...</p>
|
||||
</div>
|
||||
<div>Insert image: <input type=file id=image-upload></div>
|
||||
</body>
|
||||
</html>
|
@ -1,45 +0,0 @@
|
||||
/* eslint-env browser */
|
||||
import * as Y from '../../src/index.js'
|
||||
import { prosemirrorPlugin, cursorPlugin } from '../../bindings/ProsemirrorBinding/ProsemirrorBinding.js'
|
||||
import WebsocketProvider from '../../provider/websocket/WebSocketProvider.js'
|
||||
|
||||
import {EditorState} from 'prosemirror-state'
|
||||
import {EditorView} from 'prosemirror-view'
|
||||
import {Schema, DOMParser, Mark, Fragment, Node, Slice} from 'prosemirror-model'
|
||||
import {schema} from 'prosemirror-schema-basic'
|
||||
import {exampleSetup} from 'prosemirror-example-setup'
|
||||
import { PlaceholderPlugin, startImageUpload } from './PlaceholderPlugin.js'
|
||||
|
||||
const provider = new WebsocketProvider('ws://localhost:1234/')
|
||||
const ydocument = provider.get('prosemirror')
|
||||
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
const type = ydocument.define('prosemirror', Y.XmlFragment)
|
||||
|
||||
const view = new EditorView(document.querySelector('#editor'), {
|
||||
state: EditorState.create({
|
||||
doc: DOMParser.fromSchema(schema).parse(document.querySelector('#content')),
|
||||
plugins: exampleSetup({schema}).concat([PlaceholderPlugin, prosemirrorPlugin(type), cursorPlugin])
|
||||
})
|
||||
})
|
||||
|
||||
window.provider = provider
|
||||
window.ydocument = ydocument
|
||||
window.type = type
|
||||
window.view = view
|
||||
window.EditorState = EditorState
|
||||
window.EditorView = EditorView
|
||||
window.Mark = Mark
|
||||
window.Fragment = Fragment
|
||||
window.Node = Node
|
||||
window.Schema = Schema
|
||||
window.Slice = Slice
|
||||
|
||||
document.querySelector('#image-upload').addEventListener('change', e => {
|
||||
if (view.state.selection.$from.parent.inlineContent && e.target.files.length) {
|
||||
startImageUpload(view, e.target.files[0])
|
||||
}
|
||||
view.focus()
|
||||
})
|
@ -1,20 +0,0 @@
|
||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
||||
import commonjs from 'rollup-plugin-commonjs'
|
||||
|
||||
export default {
|
||||
input: './index.js',
|
||||
output: {
|
||||
name: 'index',
|
||||
file: 'index.dist.js',
|
||||
format: 'umd',
|
||||
sourcemap: true
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve({
|
||||
main: true,
|
||||
module: true,
|
||||
browser: true
|
||||
}),
|
||||
commonjs()
|
||||
]
|
||||
}
|
54
examples/quill.html
Normal file
54
examples/quill.html
Normal file
@ -0,0 +1,54 @@
|
||||
<!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>
|
||||
<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">
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
27
examples/quill.js
Normal file
27
examples/quill.js
Normal file
@ -0,0 +1,27 @@
|
||||
import * as Y from '../index.js'
|
||||
import { QuillBinding } from '../bindings/quill.js'
|
||||
import { WebsocketProvider } from '../provider/websocket.js'
|
||||
import Quill from 'quill'
|
||||
|
||||
const provider = new WebsocketProvider('ws://localhost:1234/')
|
||||
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)
|
29
examples/style.css
Normal file
29
examples/style.css
Normal 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: "The Shared Editing Library";
|
||||
font-size: 2em;
|
||||
}
|
64
examples/textarea.html
Normal file
64
examples/textarea.html
Normal file
@ -0,0 +1,64 @@
|
||||
<!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">
|
||||
|
||||
<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 '../index.js'
|
||||
import { TextareaBinding } from '../bindings/textarea.js'
|
||||
import { WebsocketProvider } from '../provider/websocket.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 TextareaBinding(type, textarea)
|
||||
|
||||
window.textareaExample = {
|
||||
provider, ydocument, type, textarea, binding
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,12 +1,12 @@
|
||||
/* eslint-env browser */
|
||||
import * as Y from '../../src/index.js'
|
||||
import WebsocketProvider from '../../provider/websocket/WebSocketProvider.js'
|
||||
import * as Y from '../index.js'
|
||||
import { TextareaBinding } from '../bindings/textarea.js'
|
||||
import { WebsocketProvider } from '../provider/websocket.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)
|
||||
const binding = new TextareaBinding(type, textarea)
|
||||
|
||||
window.textareaExample = {
|
||||
provider, ydocument, type, textarea, binding
|
@ -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>
|
@ -14,7 +14,7 @@ let chatprotocol = y.define('chatprotocol', Y.Array)
|
||||
let chatcontainer = document.querySelector('#chat')
|
||||
|
||||
// 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 uname = document.createElement('span')
|
||||
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.
|
||||
// The rest is deleted
|
||||
function cleanupChat () {
|
||||
const cleanupChat = () => {
|
||||
if (chatprotocol.length > 7) {
|
||||
chatprotocol.delete(0, chatprotocol.length - 7)
|
||||
}
|
||||
@ -36,7 +36,7 @@ cleanupChat()
|
||||
chatprotocol.toArray().forEach(appendMessage)
|
||||
|
||||
// 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
|
||||
cleanupChat()
|
||||
chatcontainer.innerHTML = ''
|
@ -36,13 +36,13 @@ let quill = new Quill('#quill-container', {
|
||||
|
||||
let cursors = quill.getModule('cursors')
|
||||
|
||||
function drawCursors () {
|
||||
const drawCursors = () => {
|
||||
cursors.clearCursors()
|
||||
users.map((user, userId) => {
|
||||
if (user !== myUserInfo) {
|
||||
let relativeRange = user.get('range')
|
||||
let lastUpdated = new Date(user.get('last updated'))
|
||||
if (lastUpdated != null && new Date() - lastUpdated < 20000 && relativeRange != null) {
|
||||
let lastUpdated = new Date(user.get('last updated')).getTime()
|
||||
if (lastUpdated != null && new Date().getTime() - lastUpdated < 20000 && relativeRange != null) {
|
||||
let start = Y.utils.fromRelativePosition(y, relativeRange.start).offset
|
||||
let end = Y.utils.fromRelativePosition(y, relativeRange.end).offset
|
||||
let range = { index: start, length: end - start }
|
@ -26,8 +26,4 @@ let quill = new Quill('#quill-container', {
|
||||
|
||||
let yText = y.define('quill', Y.Text)
|
||||
|
||||
let quillBinding = new Y.QuillBinding(yText, quill)
|
||||
window.quillBinding = quillBinding
|
||||
window.yText = yText
|
||||
window.y = y
|
||||
window.quill = quill
|
||||
window.quillBinding = new Y.QuillBinding(yText, quill)
|
52
index.js
Normal file
52
index.js
Normal file
@ -0,0 +1,52 @@
|
||||
|
||||
import { Delete } from './structs/Delete.js'
|
||||
import { ItemJSON } from './structs/ItemJSON.js'
|
||||
import { ItemString } from './structs/ItemString.js'
|
||||
import { ItemFormat } from './structs/ItemFormat.js'
|
||||
import { ItemEmbed } from './structs/ItemEmbed.js'
|
||||
import { GC } from './structs/GC.js'
|
||||
|
||||
import { YArray } from './types/YArray.js'
|
||||
import { YMap } from './types/YMap.js'
|
||||
import { YText } from './types/YText.js'
|
||||
import { YXmlText } from './types/YXmlText.js'
|
||||
import { YXmlHook } from './types/YXmlHook.js'
|
||||
import { YXmlElement, YXmlFragment } from './types/YXmlElement.js'
|
||||
|
||||
import { registerStruct } from './utils/structReferences.js'
|
||||
|
||||
export { Y } from './utils/Y.js'
|
||||
export { UndoManager } from './utils/UndoManager.js'
|
||||
export { Transaction } from './utils/Transaction.js'
|
||||
|
||||
export { YArray as Array } from './types/YArray.js'
|
||||
export { YMap as Map } from './types/YMap.js'
|
||||
export { YText as Text } from './types/YText.js'
|
||||
export { YXmlText as XmlText } from './types/YXmlText.js'
|
||||
export { YXmlHook as XmlHook } from './types/YXmlHook.js'
|
||||
export { YXmlElement as XmlElement, YXmlFragment as XmlFragment } from './types/YXmlElement.js'
|
||||
|
||||
export { getRelativePosition, fromRelativePosition } from './utils/relativePosition.js'
|
||||
export { registerStruct } from './utils/structReferences.js'
|
||||
export * from './protocols/syncProtocol.js'
|
||||
export * from './protocols/awarenessProtocol.js'
|
||||
export * from './lib/encoding.js'
|
||||
export * from './lib/decoding.js'
|
||||
export * from './lib/mutex.js'
|
||||
|
||||
export { WebsocketProvider } from './provider/websocket/WebSocketProvider.js'
|
||||
|
||||
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)
|
@ -2,7 +2,7 @@
|
||||
/**
|
||||
* Handles named events.
|
||||
*/
|
||||
export default class NamedEventHandler {
|
||||
export class NamedEventHandler {
|
||||
constructor () {
|
||||
this._eventListener = new Map()
|
||||
this._stateListener = new Map()
|
||||
@ -57,7 +57,7 @@ export default class NamedEventHandler {
|
||||
let state = this._stateListener.get(name)
|
||||
if (state === undefined) {
|
||||
state = {}
|
||||
state.promise = new Promise(function (resolve) {
|
||||
state.promise = new Promise(resolve => {
|
||||
state.resolve = resolve
|
||||
})
|
||||
this._stateListener.set(name, state)
|
||||
|
19
lib/Tree.js
19
lib/Tree.js
@ -1,5 +1,8 @@
|
||||
/**
|
||||
* @module tree
|
||||
*/
|
||||
|
||||
function rotate (tree, parent, newParent, n) {
|
||||
const rotate = (tree, parent, newParent, n) => {
|
||||
if (parent === null) {
|
||||
tree.root = newParent
|
||||
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
|
||||
*/
|
||||
export default class Tree {
|
||||
export class Tree {
|
||||
constructor () {
|
||||
this.root = null
|
||||
this.length = 0
|
||||
@ -310,12 +319,6 @@ export default class Tree {
|
||||
}
|
||||
}
|
||||
_fixDelete (n) {
|
||||
function isBlack (node) {
|
||||
return node !== null ? node.isBlack() : true
|
||||
}
|
||||
function isRed (node) {
|
||||
return node !== null ? node.isRed() : false
|
||||
}
|
||||
if (n.parent === null) {
|
||||
// this can only be called after the first iteration of fixDelete.
|
||||
return
|
||||
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @module binary
|
||||
*/
|
||||
|
||||
export const BITS32 = 0xFFFFFFFF
|
||||
export const BITS21 = (1 << 21) - 1
|
||||
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @module decoding
|
||||
*/
|
||||
|
||||
/* global Buffer */
|
||||
|
||||
@ -17,17 +20,26 @@ export class Decoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {ArrayBuffer} buffer
|
||||
* @return {Decoder}
|
||||
*/
|
||||
export const createDecoder = buffer => new Decoder(buffer)
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {Decoder} decoder
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const hasContent = decoder => decoder.pos !== decoder.arr.length
|
||||
|
||||
/**
|
||||
* Clone a decoder instance.
|
||||
* Optionally set a new position parameter.
|
||||
*
|
||||
* @function
|
||||
* @param {Decoder} decoder The decoder instance
|
||||
* @param {number} [newPos] Defaults to current position
|
||||
* @return {Decoder} A clone of `decoder`
|
||||
*/
|
||||
export const clone = (decoder, newPos = decoder.pos) => {
|
||||
@ -38,6 +50,7 @@ export const clone = (decoder, newPos = decoder.pos) => {
|
||||
|
||||
/**
|
||||
* Read `len` bytes as an ArrayBuffer.
|
||||
* @function
|
||||
* @param {Decoder} decoder The decoder instance
|
||||
* @param {number} len The length of bytes to read
|
||||
* @return {ArrayBuffer}
|
||||
@ -52,6 +65,7 @@ export const readArrayBuffer = (decoder, len) => {
|
||||
|
||||
/**
|
||||
* Read variable length payload as ArrayBuffer
|
||||
* @function
|
||||
* @param {Decoder} decoder
|
||||
* @return {ArrayBuffer}
|
||||
*/
|
||||
@ -59,6 +73,7 @@ export const readPayload = decoder => readArrayBuffer(decoder, readVarUint(decod
|
||||
|
||||
/**
|
||||
* Read the rest of the content as an ArrayBuffer
|
||||
* @function
|
||||
* @param {Decoder} decoder
|
||||
* @return {ArrayBuffer}
|
||||
*/
|
||||
@ -66,6 +81,7 @@ export const readTail = decoder => readArrayBuffer(decoder, decoder.arr.length -
|
||||
|
||||
/**
|
||||
* Skip one byte, jump to the next position.
|
||||
* @function
|
||||
* @param {Decoder} decoder The decoder instance
|
||||
* @return {number} The next position
|
||||
*/
|
||||
@ -73,6 +89,7 @@ export const skip8 = decoder => decoder.pos++
|
||||
|
||||
/**
|
||||
* Read one byte as unsigned integer.
|
||||
* @function
|
||||
* @param {Decoder} decoder The decoder instance
|
||||
* @return {number} Unsigned 8-bit integer
|
||||
*/
|
||||
@ -81,6 +98,7 @@ export const readUint8 = decoder => decoder.arr[decoder.pos++]
|
||||
/**
|
||||
* Read 4 bytes as unsigned integer.
|
||||
*
|
||||
* @function
|
||||
* @param {Decoder} decoder
|
||||
* @return {number} An unsigned integer.
|
||||
*/
|
||||
@ -98,6 +116,7 @@ export const readUint32 = decoder => {
|
||||
* Look ahead without incrementing position.
|
||||
* to the next byte and read it as unsigned integer.
|
||||
*
|
||||
* @function
|
||||
* @param {Decoder} decoder
|
||||
* @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^14 is stored in two bylength
|
||||
*
|
||||
* @function
|
||||
* @param {Decoder} decoder
|
||||
* @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.
|
||||
* For effiency reasons we apply a maximum of 10000 characters at once.
|
||||
*
|
||||
* @function
|
||||
* @param {Decoder} decoder
|
||||
* @return {String} The read String.
|
||||
*/
|
||||
@ -157,6 +178,8 @@ export const readVarString = decoder => {
|
||||
|
||||
/**
|
||||
* Look ahead and read varString without incrementing position
|
||||
*
|
||||
* @function
|
||||
* @param {Decoder} decoder
|
||||
* @return {string}
|
||||
*/
|
||||
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @module diff
|
||||
*/
|
||||
|
||||
/**
|
||||
* A SimpleDiff describes a change on a String.
|
||||
@ -27,7 +30,7 @@
|
||||
* @param {String} b The updated version of the string
|
||||
* @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 right = 0 // number of same characters counting from right
|
||||
while (left < a.length && left < b.length && a[left] === b[left]) {
|
@ -1,4 +1,6 @@
|
||||
|
||||
/**
|
||||
* @module encoding
|
||||
*/
|
||||
import * as globals from './globals.js'
|
||||
|
||||
const bits7 = 0b1111111
|
||||
@ -15,10 +17,18 @@ export class Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @return {Encoder}
|
||||
*/
|
||||
export const createEncoder = () => new Encoder()
|
||||
|
||||
/**
|
||||
* The current length of the encoded data.
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @return {number}
|
||||
*/
|
||||
export const length = encoder => {
|
||||
let len = encoder.cpos
|
||||
@ -30,6 +40,8 @@ export const length = encoder => {
|
||||
|
||||
/**
|
||||
* Transform to ArrayBuffer. TODO: rename to .toArrayBuffer
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @return {ArrayBuffer} The created ArrayBuffer.
|
||||
*/
|
||||
@ -48,6 +60,7 @@ export const toBuffer = encoder => {
|
||||
/**
|
||||
* Write one byte to the encoder.
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @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.
|
||||
* Position must already be written (i.e. encoder.length > pos)
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @param {number} pos Position to which to write data
|
||||
* @param {number} num Unsigned 8-bit integer
|
||||
@ -89,6 +103,7 @@ export const set = (encoder, pos, num) => {
|
||||
/**
|
||||
* Write one byte as an unsigned integer.
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @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.
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @param {number} pos The location where the data will be written.
|
||||
* @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.
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @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.
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @param {number} pos The location where the data will be written.
|
||||
* @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
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @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.
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @param {number} pos The location where the data will be written.
|
||||
* @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)
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @param {number} num The number that is to be encoded.
|
||||
*/
|
||||
@ -171,6 +192,7 @@ export const writeVarUint = (encoder, num) => {
|
||||
/**
|
||||
* Write a variable length string.
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @param {String} str The string that is to be encoded.
|
||||
*/
|
||||
@ -188,6 +210,7 @@ export const writeVarString = (encoder, str) => {
|
||||
*
|
||||
* TODO: can be improved!
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder The enUint8Arr
|
||||
* @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.
|
||||
*
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @param {ArrayBuffer} arrayBuffer
|
||||
*/
|
||||
@ -209,6 +233,7 @@ export const writeArrayBuffer = (encoder, arrayBuffer) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {Encoder} encoder
|
||||
* @param {ArrayBuffer} arrayBuffer
|
||||
*/
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @module globals
|
||||
*/
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
export const Uint8Array_ = Uint8Array
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @module idb
|
||||
*/
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
import * as globals from './globals.js'
|
||||
@ -115,7 +119,7 @@ export const getAllKeysValues = (store, range) =>
|
||||
* Iterate on keys and values
|
||||
* @param {IDBObjectStore} store
|
||||
* @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) => {
|
||||
const request = store.openCursor(keyrange)
|
||||
@ -135,9 +139,10 @@ export const iterate = (store, keyrange, f) => globals.createPromise((resolve, r
|
||||
|
||||
/**
|
||||
* Iterate on the keys (no values)
|
||||
*
|
||||
* @param {IDBObjectStore} store
|
||||
* @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) => {
|
||||
/**
|
||||
|
@ -1,4 +1,4 @@
|
||||
import * as test from './test.js'
|
||||
import * as test from './testing.js'
|
||||
import * as idb from './idb.js'
|
||||
import * as logging from './logging.js'
|
||||
|
||||
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @module logging
|
||||
*/
|
||||
|
||||
import * as globals from './globals.js'
|
||||
|
||||
|
@ -1,2 +1,4 @@
|
||||
|
||||
/**
|
||||
* @module math
|
||||
*/
|
||||
export const floor = Math.floor
|
||||
|
@ -4,11 +4,10 @@
|
||||
*
|
||||
* @example
|
||||
* const mutex = createMutex()
|
||||
* mutex(function () {
|
||||
* mutex(() => {
|
||||
* // This function is immediately executed
|
||||
* mutex(function () {
|
||||
* // This function is never executed, as it is called with the same
|
||||
* // mutex function
|
||||
* mutex(() => {
|
||||
* // This function is not executed, as the mutex is already active.
|
||||
* })
|
||||
* })
|
||||
*
|
||||
|
@ -1,2 +1,6 @@
|
||||
/**
|
||||
* @module number
|
||||
*/
|
||||
|
||||
export const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER
|
||||
export const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER
|
||||
|
@ -1,11 +1,12 @@
|
||||
/**
|
||||
* @module prng
|
||||
*/
|
||||
const N = 624
|
||||
const M = 397
|
||||
|
||||
function twist (u, v) {
|
||||
return ((((u & 0x80000000) | (v & 0x7fffffff)) >>> 1) ^ ((v & 1) ? 0x9908b0df : 0))
|
||||
}
|
||||
const twist = (u, v) => ((((u & 0x80000000) | (v & 0x7fffffff)) >>> 1) ^ ((v & 1) ? 0x9908b0df : 0))
|
||||
|
||||
function nextState (state) {
|
||||
const nextState = (state) => {
|
||||
let p = 0
|
||||
let j
|
||||
for (j = N - M + 1; --j; p++) {
|
||||
@ -29,7 +30,7 @@ function nextState (state) {
|
||||
*
|
||||
* @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.
|
||||
*/
|
@ -1,13 +1,16 @@
|
||||
/**
|
||||
* @module prng
|
||||
*/
|
||||
|
||||
import Mt19937 from './Mt19937.js'
|
||||
import Xoroshiro128plus from './Xoroshiro128plus.js'
|
||||
import Xorshift32 from './Xorshift32.js'
|
||||
import { Mt19937 } from './Mt19937.js'
|
||||
import { Xoroshiro128plus } from './Xoroshiro128plus.js'
|
||||
import { Xorshift32 } from './Xorshift32.js'
|
||||
import * as time from '../../time.js'
|
||||
|
||||
const DIAMETER = 300
|
||||
const NUMBERS = 10000
|
||||
|
||||
function runPRNG (name, Gen) {
|
||||
const runPRNG = (name, Gen) => {
|
||||
console.log('== ' + name + ' ==')
|
||||
const gen = new Gen(1234)
|
||||
let head = 0
|
@ -1,5 +1,8 @@
|
||||
/**
|
||||
* @module prng
|
||||
*/
|
||||
|
||||
import Xorshift32 from './Xorshift32.js'
|
||||
import { Xorshift32 } from './Xorshift32.js'
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
export default class Xoroshiro128plus {
|
||||
export class Xoroshiro128plus {
|
||||
constructor (seed) {
|
||||
this.seed = seed
|
||||
// This is a variant of Xoroshiro128plus to fill the initial state
|
@ -1,8 +1,11 @@
|
||||
/**
|
||||
* @module prng
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
@ -1,10 +1,13 @@
|
||||
/**
|
||||
* @module prng
|
||||
*/
|
||||
|
||||
import * as binary from '../binary.js'
|
||||
import { fromCharCode, fromCodePoint } from '../string.js'
|
||||
import { MAX_SAFE_INTEGER, MIN_SAFE_INTEGER } from '../number.js'
|
||||
import * as math from '../math.js'
|
||||
|
||||
import DefaultPRNG from './PRNG/Xoroshiro128plus.js'
|
||||
import { Xoroshiro128plus as DefaultPRNG } from './PRNG/Xoroshiro128plus.js'
|
||||
|
||||
/**
|
||||
* Description of the function
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @module prng
|
||||
*/
|
||||
|
||||
/**
|
||||
*TODO: enable tests
|
||||
import * as rt from '../rich-text/formatters.mjs'
|
3
lib/random.js
Normal file
3
lib/random.js
Normal file
@ -0,0 +1,3 @@
|
||||
/**
|
||||
* @module random
|
||||
*/
|
@ -1,2 +1,6 @@
|
||||
/**
|
||||
* @module string
|
||||
*/
|
||||
|
||||
export const fromCharCode = String.fromCharCode
|
||||
export const fromCodePoint = String.fromCodePoint
|
||||
|
@ -1,5 +1,9 @@
|
||||
/**
|
||||
* @module testing
|
||||
*/
|
||||
|
||||
import * as logging from './logging.js'
|
||||
import simpleDiff from './simpleDiff.js'
|
||||
import { simpleDiff } from './diff.js'
|
||||
|
||||
export const run = async (name, f) => {
|
||||
console.log(`%cStart:%c ${name}`, 'color:blue;', '')
|
376
package-lock.json
generated
376
package-lock.json
generated
@ -4,6 +4,63 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"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": {
|
||||
"version": "0.0.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.38.tgz",
|
||||
@ -110,17 +167,6 @@
|
||||
"integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=",
|
||||
"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": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
|
||||
@ -1577,6 +1623,12 @@
|
||||
"integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=",
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
@ -1631,12 +1683,6 @@
|
||||
"integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=",
|
||||
"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": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
|
||||
@ -1662,22 +1708,13 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"center-align": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
|
||||
"integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
|
||||
"catharsis": {
|
||||
"version": "0.8.9",
|
||||
"resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz",
|
||||
"integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"align-text": "^0.1.3",
|
||||
"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
|
||||
}
|
||||
"underscore-contrib": "~0.3.0"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
@ -1761,17 +1798,6 @@
|
||||
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
|
||||
"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": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||
@ -3079,9 +3105,9 @@
|
||||
}
|
||||
},
|
||||
"extend": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
|
||||
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
|
||||
"dev": true
|
||||
},
|
||||
"extend-shallow": {
|
||||
@ -3984,6 +4010,12 @@
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
|
||||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
||||
@ -4585,6 +4617,15 @@
|
||||
"dev": 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": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
|
||||
@ -4609,12 +4650,69 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsdoc-template": {
|
||||
"version": "git+ssh://git@github.com/braintree/jsdoc-template.git#ec0e3c0ec95de6b4e928af3285bbf69aac2f25b6",
|
||||
"from": "git+ssh://git@github.com/braintree/jsdoc-template.git#3.3.0",
|
||||
"dev": true
|
||||
},
|
||||
"jsdom": {
|
||||
"version": "7.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz",
|
||||
@ -5009,12 +5107,6 @@
|
||||
"integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=",
|
||||
"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": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
|
||||
@ -5120,6 +5212,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": {
|
||||
"version": "2.3.11",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
|
||||
@ -5424,7 +5525,7 @@
|
||||
},
|
||||
"parchment": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
|
||||
"resolved": "http://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
|
||||
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==",
|
||||
"dev": true
|
||||
},
|
||||
@ -5848,7 +5949,7 @@
|
||||
},
|
||||
"quill": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.6.tgz",
|
||||
"resolved": "http://registry.npmjs.org/quill/-/quill-1.3.6.tgz",
|
||||
"integrity": "sha512-K0mvhimWZN6s+9OQ249CH2IEPZ9JmkFuCQeHAOQax3EZ2nDJ3wfGh59mnlQaZV2i7u8eFarx6wAtvQKgShojug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@ -5879,14 +5980,6 @@
|
||||
"deep-equal": "^1.0.1",
|
||||
"extend": "^3.0.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": {
|
||||
@ -6198,6 +6291,23 @@
|
||||
"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": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz",
|
||||
@ -6233,15 +6343,6 @@
|
||||
"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": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
||||
@ -6380,12 +6481,46 @@
|
||||
}
|
||||
},
|
||||
"rollup-plugin-uglify": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-1.0.2.tgz",
|
||||
"integrity": "sha1-1KpvXfE1Iurhuhd4DHxMcJYDg1k=",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-6.0.0.tgz",
|
||||
"integrity": "sha512-XtzZd159QuOaXNvcxyBcbUCSoBsv5YYWK+7ZwUyujSmISst8avRfjWlp7cGu8T2O52OJnpEBvl+D4WLV1k1iQQ==",
|
||||
"dev": true,
|
||||
"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": {
|
||||
@ -6483,6 +6618,12 @@
|
||||
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
|
||||
"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": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
|
||||
@ -6974,6 +7115,15 @@
|
||||
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
|
||||
"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": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
@ -7012,14 +7162,27 @@
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "2.8.29",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
|
||||
"integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=",
|
||||
"version": "3.4.9",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
|
||||
"integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"source-map": "~0.5.1",
|
||||
"uglify-to-browserify": "~1.0.0",
|
||||
"yargs": "~3.10.0"
|
||||
"commander": "~2.17.1",
|
||||
"source-map": "~0.6.1"
|
||||
},
|
||||
"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": {
|
||||
@ -7029,6 +7192,29 @@
|
||||
"dev": 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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
|
||||
@ -7173,18 +7359,6 @@
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
@ -7215,6 +7389,12 @@
|
||||
"dev": 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": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
|
||||
@ -7226,18 +7406,6 @@
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
package.json
30
package.json
@ -2,17 +2,17 @@
|
||||
"name": "yjs",
|
||||
"version": "13.0.0-72",
|
||||
"description": "A framework for real-time p2p shared editing on any data",
|
||||
"main": "./build/node/index.js",
|
||||
"module": "./src/index.js",
|
||||
"main": "./build/yjs.js",
|
||||
"module": "./index.js",
|
||||
"scripts": {
|
||||
"test": "npm run lint",
|
||||
"debug": "concurrently 'rollup -wc rollup.test.js' 'cutest-serve y.test.js -o'",
|
||||
"lint": "standard src/**/*.js test/**/*.js tests-lib/**/*.js",
|
||||
"docs": "esdoc",
|
||||
"build": "rollup -c",
|
||||
"watch": "rollup -wc",
|
||||
"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.md --package ./package.json",
|
||||
"serve-docs": "npm run docs && serve ./docs/",
|
||||
"dist": "rollup -c rollup.browser.js; rollup -c rollup.node.js",
|
||||
"watch": "concurrently 'rollup -wc rollup.browser.js' 'rollup -wc rollup.node.js'",
|
||||
"postversion": "npm run dist"
|
||||
"postversion": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
"src/*",
|
||||
@ -28,8 +28,10 @@
|
||||
},
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"/y.js",
|
||||
"/y.js.map"
|
||||
"/build",
|
||||
"/node_modules",
|
||||
"/rollup.test.js",
|
||||
"/rollup.test.js"
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
@ -62,6 +64,8 @@
|
||||
"cutest": "^0.1.9",
|
||||
"esdoc": "^1.1.0",
|
||||
"esdoc-standard-plugin": "^1.0.0",
|
||||
"jsdoc": "^3.5.5",
|
||||
"jsdoc-template": "git@github.com:braintree/jsdoc-template.git#3.3.0",
|
||||
"prosemirror-example-setup": "^1.0.1",
|
||||
"prosemirror-schema-basic": "^1.0.0",
|
||||
"prosemirror-state": "^1.2.2",
|
||||
@ -74,10 +78,12 @@
|
||||
"rollup-plugin-inject": "^2.2.0",
|
||||
"rollup-plugin-multi-entry": "^2.0.2",
|
||||
"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-watch": "^3.2.2",
|
||||
"standard": "^11.0.1"
|
||||
"standard": "^11.0.1",
|
||||
"tui-jsdoc-template": "^1.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": "^6.1.0"
|
||||
|
@ -1,8 +1,9 @@
|
||||
/*
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import * as encoding from '../../lib/encoding.js'
|
||||
import * as decoding from '../../lib/decoding.js'
|
||||
import { createMutualExclude } from '../../lib/mutualExclude.js'
|
||||
import * as encoding from '../lib/encoding.js'
|
||||
import * as decoding from '../lib/decoding.js'
|
||||
import { createMutex } from '../lib/mutex.js'
|
||||
import { encodeUpdate, encodeStructsDS, decodePersisted } from './decodePersisted.js'
|
||||
|
||||
function createFilePath (persistence, roomName) {
|
||||
@ -10,10 +11,10 @@ function createFilePath (persistence, roomName) {
|
||||
return path.join(persistence.dir, roomName)
|
||||
}
|
||||
|
||||
export default class FilePersistence {
|
||||
export class FilePersistence {
|
||||
constructor (dir) {
|
||||
this.dir = dir
|
||||
this._mutex = createMutualExclude()
|
||||
this._mutex = createMutex()
|
||||
}
|
||||
setRemoteUpdateCounter (roomName, remoteUpdateCounter) {
|
||||
// TODO: implement
|
||||
@ -70,3 +71,4 @@ export default class FilePersistence {
|
||||
})
|
||||
}
|
||||
}
|
||||
*/
|
@ -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.js'
|
||||
import { createMutex } from '../lib/mutex.js'
|
||||
import { decodePersisted, encodeStructsDS, encodeUpdate, PERSIST_STRUCTS_DS, PERSIST_UPDATE } from './decodePersisted.js'
|
||||
|
||||
function rtop (request) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
request.onerror = function (event) {
|
||||
@ -50,21 +46,21 @@ function persist (room) {
|
||||
let t = room.db.transaction(['updates'], 'readwrite')
|
||||
let updatesStore = t.objectStore('updates')
|
||||
return rtop(updatesStore.getAll())
|
||||
.then(updates => {
|
||||
// apply all previous updates before deleting them
|
||||
room.mutex(() => {
|
||||
updates.forEach(update => {
|
||||
decodePersisted(y, new BinaryDecoder(update))
|
||||
.then(updates => {
|
||||
// apply all previous updates before deleting them
|
||||
room.mutex(() => {
|
||||
updates.forEach(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) {
|
||||
@ -99,7 +95,7 @@ function registerRoomInPersistence (documentsDB, roomName) {
|
||||
|
||||
const PREFERRED_TRIM_SIZE = 400
|
||||
|
||||
export default class IndexedDBPersistence {
|
||||
export class IndexedDBPersistence {
|
||||
constructor () {
|
||||
this._rooms = new Map()
|
||||
this._documentsDB = new Promise(function (resolve, reject) {
|
||||
@ -115,7 +111,17 @@ export default class IndexedDBPersistence {
|
||||
reject(new Error(event.target.error))
|
||||
}
|
||||
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) {
|
||||
const db = event.target.result
|
||||
@ -194,7 +200,7 @@ export default class IndexedDBPersistence {
|
||||
db: null,
|
||||
dbPromise: null,
|
||||
channel: null,
|
||||
mutex: createMutualExclude(),
|
||||
mutex: createMutex(),
|
||||
y
|
||||
}
|
||||
if (typeof BroadcastChannel !== 'undefined') {
|
||||
@ -228,7 +234,7 @@ export default class IndexedDBPersistence {
|
||||
})
|
||||
// register document in documentsDB
|
||||
this._documentsDB.then(
|
||||
db =>
|
||||
db =>
|
||||
rtop(db.transaction(['documents'], 'readonly').objectStore('documents').get(roomName))
|
||||
.then(
|
||||
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
|
||||
* the room. If `destroyYjsInstances = false` the persistence functionality
|
||||
* will be removed from the Yjs instances.
|
||||
*/
|
||||
*
|
||||
removePersistedData (roomName, destroyYjsInstances = true) {
|
||||
this.disconnectY(roomName)
|
||||
return rtop(indexedDB.deleteDatabase(roomName))
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,3 +1,4 @@
|
||||
/*
|
||||
import { integrateRemoteStructs } from '../MessageHandler/integrateRemoteStructs.js'
|
||||
import { writeStructs } from '../MessageHandler/syncStep1.js'
|
||||
import { writeDeleteSet, readDeleteSet } from '../MessageHandler/deleteSet.js'
|
||||
@ -6,10 +7,10 @@ export const PERSIST_UPDATE = 0
|
||||
/**
|
||||
* Write an update to an encoder.
|
||||
*
|
||||
* @param {Yjs} y A Yjs instance
|
||||
* @param {BinaryEncoder} updateEncoder I.e. transaction.encodedStructs
|
||||
*/
|
||||
export function encodeUpdate (y, updateEncoder, encoder) {
|
||||
* @param {Y} y A Yjs instance
|
||||
* @param {Encoder} updateEncoder I.e. transaction.encodedStructs
|
||||
*
|
||||
export const encodeUpdate = (y, updateEncoder, encoder) => {
|
||||
encoder.writeVarUint(PERSIST_UPDATE)
|
||||
encoder.writeBinaryEncoder(updateEncoder)
|
||||
}
|
||||
@ -19,10 +20,10 @@ export const PERSIST_STRUCTS_DS = 1
|
||||
/**
|
||||
* Write the current Yjs data model to an encoder.
|
||||
*
|
||||
* @param {Yjs} y A Yjs instance
|
||||
* @param {BinaryEncoder} encoder An encoder to write to
|
||||
*/
|
||||
export function encodeStructsDS (y, encoder) {
|
||||
* @param {Y} y A Yjs instance
|
||||
* @param {Encoder} encoder An encoder to write to
|
||||
*
|
||||
export const encodeStructsDS = (y, encoder) => {
|
||||
encoder.writeVarUint(PERSIST_STRUCTS_DS)
|
||||
writeStructs(y, encoder, new Map())
|
||||
writeDeleteSet(y, encoder)
|
||||
@ -30,10 +31,10 @@ export function encodeStructsDS (y, encoder) {
|
||||
|
||||
/**
|
||||
* Feed the Yjs instance with the persisted state
|
||||
* @param {Yjs} y A Yjs instance.
|
||||
* @param {BinaryDecoder} decoder A Decoder instance that holds the file content.
|
||||
*/
|
||||
export function decodePersisted (y, decoder) {
|
||||
* @param {Y} y A Yjs instance.
|
||||
* @param {Decoder} decoder A Decoder instance that holds the file content.
|
||||
*
|
||||
export const decodePersisted = (y, decoder) => {
|
||||
y.transact(() => {
|
||||
while (decoder.hasContent()) {
|
||||
const contentType = decoder.readVarUint()
|
||||
@ -49,3 +50,4 @@ export function decodePersisted (y, decoder) {
|
||||
}
|
||||
}, true)
|
||||
}
|
||||
*/
|
@ -1,6 +1,10 @@
|
||||
/**
|
||||
* @module awareness-protocol
|
||||
*/
|
||||
|
||||
import * as encoding from '../../lib/encoding.js'
|
||||
import * as decoding from '../../lib/decoding.js'
|
||||
import * as encoding from '../lib/encoding.js'
|
||||
import * as decoding from '../lib/decoding.js'
|
||||
import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
|
||||
const messageUsersStateChanged = 0
|
||||
|
||||
@ -77,6 +81,7 @@ export const forwardUsersStateChange = (decoder, encoder) => {
|
||||
|
||||
/**
|
||||
* @param {decoding.Decoder} decoder
|
||||
* @param {Y} y
|
||||
*/
|
||||
export const readAwarenessMessage = (decoder, y) => {
|
||||
switch (decoding.readVarUint(decoder)) {
|
@ -1,17 +1,18 @@
|
||||
/**
|
||||
* @module sync-protocol
|
||||
*/
|
||||
|
||||
import * as encoding from '../../lib/encoding.js'
|
||||
import * as decoding from '../../lib/decoding.js'
|
||||
import * as ID from '../Util/ID.js'
|
||||
import { getStruct } from '../Util/structReferences.js'
|
||||
import { deleteItemRange } from '../Struct/Delete.js'
|
||||
import { integrateRemoteStruct } from '../Util/integrateRemoteStructs.js'
|
||||
import Item from '../Struct/Item.js'
|
||||
import * as encoding from '../lib/encoding.js'
|
||||
import * as decoding from '../lib/decoding.js'
|
||||
import * as ID from '../utils/ID.js'
|
||||
import { getStruct } from '../utils/structReferences.js'
|
||||
import { deleteItemRange } from '../structs/Delete.js'
|
||||
import { integrateRemoteStruct } from '../utils/integrateRemoteStructs.js'
|
||||
import { Y } from '../utils/Y.js' // eslint-disable-line
|
||||
import { Item } from '../structs/Item.js'
|
||||
|
||||
/**
|
||||
* @typedef {import('../Store/StateStore.js').default} StateStore
|
||||
* @typedef {import('../Y.js').default} Y
|
||||
* @typedef {import('../Struct/Item.js').default} Item
|
||||
* @typedef {import('../Store/StateStore.js').StateSet} StateSet
|
||||
* @typedef {Map<number, number>} StateSet
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -82,7 +83,8 @@ export const writeDeleteSet = (encoder, y) => {
|
||||
const gc = n.gc
|
||||
if (currentUser !== user) {
|
||||
numberOfUsers++
|
||||
// a new user was found
|
||||
// a new user was foundimport { StateSet } from '../Store/StateStore.js' // eslint-disable-line
|
||||
|
||||
if (currentUser !== null) { // happens on first iteration
|
||||
encoding.setUint32(encoder, lastLenPos, currentLength)
|
||||
}
|
||||
@ -201,8 +203,8 @@ export const stringifyStateSet = decoder => {
|
||||
/**
|
||||
* Write StateSet to Encoder
|
||||
*
|
||||
* @param {Y} y
|
||||
* @param {encoding.Encoder} encoder
|
||||
* @param {Y} y
|
||||
*/
|
||||
export const writeStateSet = (encoder, y) => {
|
||||
const state = y.ss.state
|
||||
@ -426,6 +428,7 @@ export const stringifyUpdate = (decoder, y) =>
|
||||
|
||||
/**
|
||||
* @param {encoding.Encoder} encoder
|
||||
* @param {number} numOfStructs
|
||||
* @param {encoding.Encoder} updates
|
||||
*/
|
||||
export const writeUpdate = (encoder, numOfStructs, updates) => {
|
5
provider/websocket.js
Normal file
5
provider/websocket.js
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* @module provider/websocket
|
||||
*/
|
||||
|
||||
export * from './websocket/WebSocketProvider.js'
|
@ -1,7 +1,11 @@
|
||||
/**
|
||||
* @module provider/websocket
|
||||
*/
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
import * as Y from '../../src/index.js'
|
||||
export * from '../../src/index.js'
|
||||
import * as Y from '../../index.js'
|
||||
export * from '../../index.js'
|
||||
|
||||
const messageSync = 0
|
||||
const messageAwareness = 1
|
||||
@ -95,7 +99,7 @@ class WebsocketsSharedDocument extends Y.Y {
|
||||
}
|
||||
}
|
||||
|
||||
export default class WebsocketProvider {
|
||||
export class WebsocketProvider {
|
||||
constructor (url) {
|
||||
// ensure that url is always ends with /
|
||||
while (url[url.length - 1] === '/') {
|
||||
|
@ -1,4 +1,8 @@
|
||||
const Y = require('../../build/node/index.js')
|
||||
/**
|
||||
* @module provider/websocket
|
||||
*/
|
||||
|
||||
const Y = require('../../build/yjs.umd.js')
|
||||
const WebSocket = require('ws')
|
||||
const wss = new WebSocket.Server({ port: 1234 })
|
||||
const docs = new Map()
|
||||
|
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @module provider/ydb
|
||||
*/
|
||||
|
||||
import * as globals from './globals.js'
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @module provider/ydb
|
||||
*/
|
||||
|
||||
/* eslint-env browser */
|
||||
import * as idbactions from './idbactions.js'
|
||||
import * as globals from '../../lib/globals.js'
|
||||
@ -7,7 +11,7 @@ import * as encoding from '../../lib/encoding.js'
|
||||
import * as logging from '../../lib/logging.js'
|
||||
import * as idb from '../../lib/idb.js'
|
||||
import * as decoding from '../../lib/decoding.js'
|
||||
import Y from '../Y.js'
|
||||
import { Y } from '../../utils/Y.js'
|
||||
import { integrateRemoteStruct } from '../MessageHandler/integrateRemoteStructs.js'
|
||||
import { createMutualExclude } from '../../lib/mutualExclude.js'
|
||||
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @module provider/ydb
|
||||
*/
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
import * as test from './test.js'
|
@ -1,8 +1,12 @@
|
||||
/**
|
||||
* @module provider/ydb
|
||||
*/
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
import * as decoding from './decoding.js'
|
||||
import * as encoding from './encoding.js'
|
||||
import * as globals from './globals.js'
|
||||
import * as decoding from '../../lib/decoding.js'
|
||||
import * as encoding from '../../lib/encoding.js'
|
||||
import * as globals from '../../lib/globals.js'
|
||||
import * as NamedEventHandler from './NamedEventHandler.js'
|
||||
|
||||
const bc = new BroadcastChannel('ydb-client')
|
||||
@ -52,17 +56,17 @@ const fireRoomStateUpdate = (ydb, room) => {
|
||||
if (roomStatesUpdating.length === 1) {
|
||||
// first time this is called, trigger actual publisher
|
||||
// setTimeout(() => {
|
||||
const updated = new Map()
|
||||
const unconfirmedRooms = getUnconfirmedRooms(ydb)
|
||||
roomStatesUpdating.forEach(room => {
|
||||
if (!updated.has(room)) {
|
||||
updated.set(room, computeRoomState(ydb, unconfirmedRooms, room))
|
||||
}
|
||||
})
|
||||
NamedEventHandler.fire(ydb, 'syncstate', {
|
||||
updated
|
||||
})
|
||||
roomStatesUpdating = []
|
||||
const updated = new Map()
|
||||
const unconfirmedRooms = getUnconfirmedRooms(ydb)
|
||||
roomStatesUpdating.forEach(room => {
|
||||
if (!updated.has(room)) {
|
||||
updated.set(room, computeRoomState(ydb, unconfirmedRooms, room))
|
||||
}
|
||||
})
|
||||
NamedEventHandler.fire(ydb, 'syncstate', {
|
||||
updated
|
||||
})
|
||||
roomStatesUpdating = []
|
||||
// }, 0)
|
||||
}
|
||||
}
|
||||
@ -274,7 +278,7 @@ export const _broadcastYdbSyncFromServer = subs => {
|
||||
|
||||
/**
|
||||
* @param {string} room
|
||||
* @param {function(ArrayBuffer)} f
|
||||
* @param {Function} f
|
||||
*/
|
||||
export const subscribeRoomData = (room, f) => {
|
||||
let rsubs = datasubs.get(room)
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* @module provider/ydb
|
||||
*/
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
/**
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user