large scale refactoring

This commit is contained in:
Kevin Jahns
2018-11-25 03:17:00 +01:00
parent ade3e1949d
commit 9c0da271eb
154 changed files with 1769 additions and 1199 deletions

View File

@@ -1,67 +0,0 @@
/**
* Type that maps from Yjs type to Target type.
* Used to implement double bindings.
*
* @template Y
* @template T
*/
export default class BindMapping {
/**
*/
constructor () {
/**
* @type Map<Y, T>
*/
this.yt = new Map()
/**
* @type Map<T, Y>
*/
this.ty = new Map()
}
/**
* Map y to t. Removes all existing bindings from y and t
* @param {Y} y
* @param {T} t
*/
bind (y, t) {
const existingT = this.yt.get(y)
if (existingT !== undefined) {
this.ty.delete(existingT)
}
const existingY = this.ty.get(t)
if (existingY !== undefined) {
this.yt.delete(existingY)
}
this.yt.set(y, t)
this.ty.set(t, y)
}
/**
* @param {Y} y
* @return {boolean}
*/
hasY (y) {
return this.yt.has(y)
}
/**
* @param {T} t
* @return {boolean}
*/
hasT (t) {
return this.ty.has(t)
}
/**
* @param {Y} y
* @return {T}
*/
getY (y) {
return this.yt.get(y)
}
/**
* @param {T} t
* @return {Y}
*/
getT (t) {
return this.ty.get(t)
}
}

View File

@@ -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
View File

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

View File

@@ -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}
*/

View File

@@ -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

View File

@@ -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}
*/

View File

@@ -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) => {

View File

@@ -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)
}

View File

@@ -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 => {

View File

@@ -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

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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
}
}