gc more efficiently
This commit is contained in:
parent
bb6f6cd141
commit
45237571b7
@ -12,8 +12,8 @@ import { exampleSetup } from 'prosemirror-example-setup'
|
|||||||
// import { noteHistoryPlugin } from './prosemirror-history.js'
|
// import { noteHistoryPlugin } from './prosemirror-history.js'
|
||||||
|
|
||||||
const provider = new WebsocketProvider(conf.serverAddress)
|
const provider = new WebsocketProvider(conf.serverAddress)
|
||||||
const ydocument = provider.get('prosemirror', { gc: false })
|
const ydocument = provider.get('prosemirror' /*, { gc: false } */)
|
||||||
const type = ydocument.define('prosemirror', Y.XmlFragment)
|
const type = ydocument.get('prosemirror', Y.XmlFragment)
|
||||||
|
|
||||||
const prosemirrorView = new EditorView(document.querySelector('#editor'), {
|
const prosemirrorView = new EditorView(document.querySelector('#editor'), {
|
||||||
state: EditorState.create({
|
state: EditorState.create({
|
||||||
|
1975
package-lock.json
generated
1975
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,7 @@
|
|||||||
"version": "13.0.0-79",
|
"version": "13.0.0-79",
|
||||||
"description": "A ",
|
"description": "A ",
|
||||||
"main": "./dist/yjs.js",
|
"main": "./dist/yjs.js",
|
||||||
"module": "./dist/yjs.mjs'",
|
"module": "./src/index.js",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "npm run dist && PRODUCTION=1 node ./dist/tests.js --repitition-time 50 --production",
|
"test": "npm run dist && PRODUCTION=1 node ./dist/tests.js --repitition-time 50 --production",
|
||||||
@ -13,7 +13,8 @@
|
|||||||
"lint": "standard && tsc",
|
"lint": "standard && tsc",
|
||||||
"docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.v13.md --package ./package.json || true",
|
"docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.v13.md --package ./package.json || true",
|
||||||
"serve-docs": "npm run docs && serve ./docs/",
|
"serve-docs": "npm run docs && serve ./docs/",
|
||||||
"postversion": "npm run lint && PRODUCTION=1 npm run dist && node ./dist/tests.js --repitition-time 1000",
|
"preversion": "PRODUCTION=1 npm run dist && node ./dist/tests.js --repitition-time 10000",
|
||||||
|
"postversion": "git push && git push --tags",
|
||||||
"debug": "concurrently 'live-server --port=3443 --entry-file=test.html' 'npm run watch'",
|
"debug": "concurrently 'live-server --port=3443 --entry-file=test.html' 'npm run watch'",
|
||||||
"trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.js",
|
"trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.js",
|
||||||
"trace-opt": "clear && rollup -c && node --trace-opt dist/test.js"
|
"trace-opt": "clear && rollup -c && node --trace-opt dist/test.js"
|
||||||
@ -58,6 +59,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"codemirror": "^5.42.0",
|
"codemirror": "^5.42.0",
|
||||||
"concurrently": "^3.6.1",
|
"concurrently": "^3.6.1",
|
||||||
|
"jsdoc": "^3.5.5",
|
||||||
"live-server": "^1.2.1",
|
"live-server": "^1.2.1",
|
||||||
"prosemirror-example-setup": "^1.0.1",
|
"prosemirror-example-setup": "^1.0.1",
|
||||||
"prosemirror-schema-basic": "^1.0.0",
|
"prosemirror-schema-basic": "^1.0.0",
|
||||||
@ -71,7 +73,6 @@
|
|||||||
"rollup-plugin-node-resolve": "^4.0.0",
|
"rollup-plugin-node-resolve": "^4.0.0",
|
||||||
"rollup-plugin-terser": "^4.0.4",
|
"rollup-plugin-terser": "^4.0.4",
|
||||||
"standard": "^11.0.1",
|
"standard": "^11.0.1",
|
||||||
"jsdoc": "^3.5.5",
|
|
||||||
"tui-jsdoc-template": "^1.2.2",
|
"tui-jsdoc-template": "^1.2.2",
|
||||||
"typescript": "^3.3.3333"
|
"typescript": "^3.3.3333"
|
||||||
}
|
}
|
||||||
|
@ -85,8 +85,8 @@ export default [{
|
|||||||
}),
|
}),
|
||||||
commonjs()
|
commonjs()
|
||||||
]
|
]
|
||||||
}, /*{
|
}, {
|
||||||
input: ['./examples/codemirror.js', './examples/textarea.js'], // './examples/quill.js', './examples/dom.js', './examples/prosemirror.js'
|
input: ['./examples/textarea.js', './examples/prosemirror.js'], // './examples/quill.js', './examples/dom.js', './examples/codemirror.js'
|
||||||
output: {
|
output: {
|
||||||
dir: 'examples/build',
|
dir: 'examples/build',
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
@ -102,4 +102,4 @@ export default [{
|
|||||||
commonjs(),
|
commonjs(),
|
||||||
...minificationPlugins
|
...minificationPlugins
|
||||||
]
|
]
|
||||||
}*/]
|
}]
|
||||||
|
24
src/index.js
24
src/index.js
@ -9,6 +9,23 @@ export {
|
|||||||
YXmlHook as XmlHook,
|
YXmlHook as XmlHook,
|
||||||
YXmlElement as XmlElement,
|
YXmlElement as XmlElement,
|
||||||
YXmlFragment as XmlFragment,
|
YXmlFragment as XmlFragment,
|
||||||
|
YXmlEvent,
|
||||||
|
YMapEvent,
|
||||||
|
YArrayEvent,
|
||||||
|
YEvent,
|
||||||
|
AbstractItem,
|
||||||
|
AbstractStruct,
|
||||||
|
GC,
|
||||||
|
ItemBinary,
|
||||||
|
ItemDeleted,
|
||||||
|
ItemEmbed,
|
||||||
|
ItemFormat,
|
||||||
|
ItemJSON,
|
||||||
|
ItemString,
|
||||||
|
ItemType,
|
||||||
|
AbstractType,
|
||||||
|
compareCursors,
|
||||||
|
Cursor,
|
||||||
createCursorFromTypeOffset,
|
createCursorFromTypeOffset,
|
||||||
createCursorFromJSON,
|
createCursorFromJSON,
|
||||||
createAbsolutePositionFromCursor,
|
createAbsolutePositionFromCursor,
|
||||||
@ -22,5 +39,10 @@ export {
|
|||||||
readStatesAsMap,
|
readStatesAsMap,
|
||||||
writeStates,
|
writeStates,
|
||||||
writeModel,
|
writeModel,
|
||||||
readModel
|
readModel,
|
||||||
|
Snapshot,
|
||||||
|
findRootTypeKey,
|
||||||
|
typeArrayToArraySnapshot,
|
||||||
|
typeMapGetSnapshot,
|
||||||
|
iterateDeletedStructs
|
||||||
} from './internals.js'
|
} from './internals.js'
|
||||||
|
@ -14,6 +14,7 @@ export * from './types/AbstractType.js'
|
|||||||
export * from './types/YArray.js'
|
export * from './types/YArray.js'
|
||||||
export * from './types/YMap.js'
|
export * from './types/YMap.js'
|
||||||
export * from './types/YText.js'
|
export * from './types/YText.js'
|
||||||
|
export * from './types/YXmlFragment.js'
|
||||||
export * from './types/YXmlElement.js'
|
export * from './types/YXmlElement.js'
|
||||||
export * from './types/YXmlEvent.js'
|
export * from './types/YXmlEvent.js'
|
||||||
export * from './types/YXmlHook.js'
|
export * from './types/YXmlHook.js'
|
||||||
|
@ -103,7 +103,6 @@ export class ItemType extends AbstractItem {
|
|||||||
super.delete(transaction)
|
super.delete(transaction)
|
||||||
transaction.changed.delete(this.type)
|
transaction.changed.delete(this.type)
|
||||||
transaction.changedParentTypes.delete(this.type)
|
transaction.changedParentTypes.delete(this.type)
|
||||||
this.gcChildren(transaction, transaction.y.store)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -132,8 +131,8 @@ export class ItemType extends AbstractItem {
|
|||||||
* @param {StructStore} store
|
* @param {StructStore} store
|
||||||
*/
|
*/
|
||||||
gc (transaction, store) {
|
gc (transaction, store) {
|
||||||
super.gc(transaction, store)
|
|
||||||
this.gcChildren(transaction, store)
|
this.gcChildren(transaction, store)
|
||||||
|
super.gc(transaction, store)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,6 +197,29 @@ export const typeArrayToArray = type => {
|
|||||||
return cs
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {AbstractType<any>} type
|
||||||
|
* @param {Snapshot} snapshot
|
||||||
|
* @return {Array<any>}
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
export const typeArrayToArraySnapshot = (type, snapshot) => {
|
||||||
|
const cs = []
|
||||||
|
let n = type._start
|
||||||
|
while (n !== null) {
|
||||||
|
if (n.countable && isVisible(n, snapshot)) {
|
||||||
|
const c = n.getContent()
|
||||||
|
for (let i = 0; i < c.length; i++) {
|
||||||
|
cs.push(c[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n = n.right
|
||||||
|
}
|
||||||
|
return cs
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a provided function on once on overy element of this YArray.
|
* Executes a provided function on once on overy element of this YArray.
|
||||||
*
|
*
|
||||||
|
@ -147,7 +147,7 @@ const insertNegatedAttributes = (transaction, parent, left, right, negatedAttrib
|
|||||||
left = new ItemFormat(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, key, val)
|
left = new ItemFormat(nextID(transaction), left, left === null ? null : left.lastId, right, right === null ? null : right.id, parent, null, key, val)
|
||||||
left.integrate(transaction)
|
left.integrate(transaction)
|
||||||
}
|
}
|
||||||
return {left, right}
|
return { left, right }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -617,10 +617,11 @@ export class YText extends AbstractType {
|
|||||||
constructor (string) {
|
constructor (string) {
|
||||||
super()
|
super()
|
||||||
/**
|
/**
|
||||||
* @type {Array<string>?}
|
* Array of pending operations on this type
|
||||||
|
* @type {Array<function():void>?}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this._prelimContent = string !== undefined ? [string] : []
|
this._pending = string !== undefined ? [() => this.insert(0, string)] : []
|
||||||
}
|
}
|
||||||
|
|
||||||
get length () {
|
get length () {
|
||||||
@ -635,9 +636,13 @@ export class YText extends AbstractType {
|
|||||||
*/
|
*/
|
||||||
_integrate (y, item) {
|
_integrate (y, item) {
|
||||||
super._integrate(y, item)
|
super._integrate(y, item)
|
||||||
// @ts-ignore this._prelimContent is still defined
|
try {
|
||||||
this.insert(0, this._prelimContent.join(''))
|
// @ts-ignore this._prelimContent is still defined
|
||||||
this._prelimContent = null
|
this._pending.forEach(f => f())
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
this._pending = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -737,6 +742,9 @@ export class YText extends AbstractType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
this._pending.push(() => this.applyDelta(delta))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -836,9 +844,12 @@ export class YText extends AbstractType {
|
|||||||
const y = this._y
|
const y = this._y
|
||||||
if (y !== null) {
|
if (y !== null) {
|
||||||
transact(y, transaction => {
|
transact(y, transaction => {
|
||||||
const {left, right, currentAttributes} = findPosition(transaction, y.store, this, index)
|
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
||||||
insertText(transaction, this, left, right, currentAttributes, text, attributes)
|
insertText(transaction, this, left, right, currentAttributes, text, attributes)
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
this._pending.push(() => this.insert(index, text, attributes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -862,6 +873,9 @@ export class YText extends AbstractType {
|
|||||||
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
||||||
insertText(transaction, this, left, right, currentAttributes, embed, attributes)
|
insertText(transaction, this, left, right, currentAttributes, embed, attributes)
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
this._pending.push(() => this.insertEmbed(index, embed, attributes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -883,6 +897,9 @@ export class YText extends AbstractType {
|
|||||||
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
const { left, right, currentAttributes } = findPosition(transaction, y.store, this, index)
|
||||||
deleteText(transaction, left, right, currentAttributes, length)
|
deleteText(transaction, left, right, currentAttributes, length)
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
this._pending.push(() => this.delete(index, length))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -906,6 +923,9 @@ export class YText extends AbstractType {
|
|||||||
}
|
}
|
||||||
formatText(transaction, this, left, right, currentAttributes, length, attributes)
|
formatText(transaction, this, left, right, currentAttributes, length, attributes)
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
this._pending.push(() => this.format(index, length, attributes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,244 +1,19 @@
|
|||||||
/**
|
|
||||||
* @module YXml
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
YXmlEvent,
|
YXmlFragment,
|
||||||
AbstractType,
|
transact,
|
||||||
typeArrayMap,
|
typeMapDelete,
|
||||||
typeArrayForEach,
|
typeMapSet,
|
||||||
typeMapGet,
|
typeMapGet,
|
||||||
typeMapGetAll,
|
typeMapGetAll,
|
||||||
typeArrayInsertGenerics,
|
typeArrayForEach,
|
||||||
typeArrayDelete,
|
|
||||||
typeMapSet,
|
|
||||||
typeMapDelete,
|
|
||||||
YXmlElementRefID,
|
YXmlElementRefID,
|
||||||
callTypeObservers,
|
Snapshot, Y, ItemType // eslint-disable-line
|
||||||
transact,
|
|
||||||
Y, Transaction, ItemType, YXmlText, YXmlHook, Snapshot // eslint-disable-line
|
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as encoding from 'lib0/encoding.js'
|
import * as encoding from 'lib0/encoding.js'
|
||||||
import * as decoding from 'lib0/decoding.js'
|
import * as decoding from 'lib0/decoding.js'
|
||||||
|
|
||||||
/**
|
|
||||||
* Define the elements to which a set of CSS queries apply.
|
|
||||||
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors}
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* query = '.classSelector'
|
|
||||||
* query = 'nodeSelector'
|
|
||||||
* query = '#idSelector'
|
|
||||||
*
|
|
||||||
* @typedef {string} CSS_Selector
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dom filter function.
|
|
||||||
*
|
|
||||||
* @callback domFilter
|
|
||||||
* @param {string} nodeName The nodeName of the element
|
|
||||||
* @param {Map} attributes The map of attributes.
|
|
||||||
* @return {boolean} Whether to include the Dom node in the YXmlElement.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a subset of the nodes of a YXmlElement / YXmlFragment and a
|
|
||||||
* position within them.
|
|
||||||
*
|
|
||||||
* Can be created with {@link YXmlFragment#createTreeWalker}
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
* @implements {IterableIterator}
|
|
||||||
*/
|
|
||||||
export class YXmlTreeWalker {
|
|
||||||
/**
|
|
||||||
* @param {YXmlFragment | YXmlElement} root
|
|
||||||
* @param {function(AbstractType<any>):boolean} [f]
|
|
||||||
*/
|
|
||||||
constructor (root, f = () => true) {
|
|
||||||
this._filter = f
|
|
||||||
this._root = root
|
|
||||||
/**
|
|
||||||
* @type {ItemType | null}
|
|
||||||
*/
|
|
||||||
// @ts-ignore
|
|
||||||
this._currentNode = root._start
|
|
||||||
this._firstCall = true
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.iterator] () {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get the next node.
|
|
||||||
*
|
|
||||||
* @return {IteratorResult<YXmlElement|YXmlText|YXmlHook>} The next node.
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
next () {
|
|
||||||
let n = this._currentNode
|
|
||||||
if (n !== null && (!this._firstCall || n.deleted || !this._filter(n.type))) { // if first call, we check if we can use the first item
|
|
||||||
do {
|
|
||||||
if (!n.deleted && (n.type.constructor === YXmlElement || n.type.constructor === YXmlFragment) && n.type._start !== null) {
|
|
||||||
// walk down in the tree
|
|
||||||
// @ts-ignore
|
|
||||||
n = n.type._start
|
|
||||||
} else {
|
|
||||||
// walk right or up in the tree
|
|
||||||
while (n !== null) {
|
|
||||||
if (n.right !== null) {
|
|
||||||
// @ts-ignore
|
|
||||||
n = n.right
|
|
||||||
break
|
|
||||||
} else if (n.parent === this._root) {
|
|
||||||
n = null
|
|
||||||
} else {
|
|
||||||
n = n.parent._item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (n !== null && (n.deleted || !this._filter(n.type)))
|
|
||||||
}
|
|
||||||
this._firstCall = false
|
|
||||||
this._currentNode = n
|
|
||||||
if (n === null) {
|
|
||||||
// @ts-ignore return undefined if done=true (the expected result)
|
|
||||||
return { value: undefined, done: true }
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
return { value: n.type, done: false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a list of {@link YXmlElement}.and {@link YXmlText} types.
|
|
||||||
* A YxmlFragment is similar to a {@link YXmlElement}, but it does not have a
|
|
||||||
* nodeName and it does not have attributes. Though it can be bound to a DOM
|
|
||||||
* element - in this case the attributes and the nodeName are not shared.
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
* @extends AbstractType<YXmlEvent>
|
|
||||||
*/
|
|
||||||
export class YXmlFragment extends AbstractType {
|
|
||||||
/**
|
|
||||||
* Create a subtree of childNodes.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const walker = elem.createTreeWalker(dom => dom.nodeName === 'div')
|
|
||||||
* for (let node in walker) {
|
|
||||||
* // `node` is a div node
|
|
||||||
* nop(node)
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @param {function(AbstractType<any>):boolean} filter Function that is called on each child element and
|
|
||||||
* returns a Boolean indicating whether the child
|
|
||||||
* is to be included in the subtree.
|
|
||||||
* @return {YXmlTreeWalker} A subtree and a position within it.
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
createTreeWalker (filter) {
|
|
||||||
return new YXmlTreeWalker(this, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the first YXmlElement that matches the query.
|
|
||||||
* Similar to DOM's {@link querySelector}.
|
|
||||||
*
|
|
||||||
* Query support:
|
|
||||||
* - tagname
|
|
||||||
* TODO:
|
|
||||||
* - id
|
|
||||||
* - attribute
|
|
||||||
*
|
|
||||||
* @param {CSS_Selector} query The query on the children.
|
|
||||||
* @return {YXmlElement|YXmlText|YXmlHook|null} The first element that matches the query or null.
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
querySelector (query) {
|
|
||||||
query = query.toUpperCase()
|
|
||||||
// @ts-ignore
|
|
||||||
const iterator = new YXmlTreeWalker(this, element => element.nodeName === query)
|
|
||||||
const next = iterator.next()
|
|
||||||
if (next.done) {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
return next.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all YXmlElements that match the query.
|
|
||||||
* Similar to Dom's {@link querySelectorAll}.
|
|
||||||
*
|
|
||||||
* @todo Does not yet support all queries. Currently only query by tagName.
|
|
||||||
*
|
|
||||||
* @param {CSS_Selector} query The query on the children
|
|
||||||
* @return {Array<YXmlElement|YXmlText|YXmlHook|null>} The elements that match this query.
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
querySelectorAll (query) {
|
|
||||||
query = query.toUpperCase()
|
|
||||||
// @ts-ignore
|
|
||||||
return Array.from(new YXmlTreeWalker(this, element => element.nodeName === query))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates YXmlEvent and calls observers.
|
|
||||||
* @private
|
|
||||||
*
|
|
||||||
* @param {Transaction} transaction
|
|
||||||
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
|
||||||
*/
|
|
||||||
_callObserver (transaction, parentSubs) {
|
|
||||||
callTypeObservers(this, transaction, new YXmlEvent(this, parentSubs, transaction))
|
|
||||||
}
|
|
||||||
|
|
||||||
toString () {
|
|
||||||
return this.toDomString()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the string representation of all the children of this YXmlFragment.
|
|
||||||
*
|
|
||||||
* @return {string} The string representation of all children.
|
|
||||||
*/
|
|
||||||
toDomString () {
|
|
||||||
return typeArrayMap(this, xml => xml.toDomString()).join('')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Dom Element that mirrors this YXmlElement.
|
|
||||||
*
|
|
||||||
* @param {Document} [_document=document] The document object (you must define
|
|
||||||
* this when calling this method in
|
|
||||||
* nodejs)
|
|
||||||
* @param {Object<string, any>} [hooks={}] Optional property to customize how hooks
|
|
||||||
* are presented in the DOM
|
|
||||||
* @param {any} [binding] You should not set this property. This is
|
|
||||||
* used if DomBinding wants to create a
|
|
||||||
* association to the created DOM type.
|
|
||||||
* @return {Node} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
|
|
||||||
*
|
|
||||||
* @public
|
|
||||||
*/
|
|
||||||
toDom (_document = document, hooks = {}, binding) {
|
|
||||||
const fragment = _document.createDocumentFragment()
|
|
||||||
if (binding !== undefined) {
|
|
||||||
binding._createAssociation(fragment, this)
|
|
||||||
}
|
|
||||||
typeArrayForEach(this, xmlType => {
|
|
||||||
fragment.insertBefore(xmlType.toDom(_document, hooks, binding), null)
|
|
||||||
})
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An YXmlElement imitates the behavior of a
|
* An YXmlElement imitates the behavior of a
|
||||||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}.
|
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}.
|
||||||
@ -250,11 +25,6 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
constructor (nodeName = 'UNDEFINED') {
|
constructor (nodeName = 'UNDEFINED') {
|
||||||
super()
|
super()
|
||||||
this.nodeName = nodeName.toUpperCase()
|
this.nodeName = nodeName.toUpperCase()
|
||||||
/**
|
|
||||||
* @type {Array<any>|null}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this._prelimContent = []
|
|
||||||
/**
|
/**
|
||||||
* @type {Map<string, any>|null}
|
* @type {Map<string, any>|null}
|
||||||
* @private
|
* @private
|
||||||
@ -389,44 +159,6 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
return typeMapGetAll(this)
|
return typeMapGetAll(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts new content at an index.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* // Insert character 'a' at position 0
|
|
||||||
* xml.insert(0, [new Y.XmlText('text')])
|
|
||||||
*
|
|
||||||
* @param {number} index The index to insert content at
|
|
||||||
* @param {Array<YXmlElement|YXmlText>} content The array of content
|
|
||||||
*/
|
|
||||||
insert (index, content) {
|
|
||||||
if (this._y !== null) {
|
|
||||||
transact(this._y, transaction => {
|
|
||||||
typeArrayInsertGenerics(transaction, this, index, content)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// @ts-ignore _prelimContent is defined because this is not yet integrated
|
|
||||||
this._prelimContent.splice(index, 0, ...content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes elements starting from an index.
|
|
||||||
*
|
|
||||||
* @param {number} index Index at which to start deleting elements
|
|
||||||
* @param {number} [length=1] The number of elements to remove. Defaults to 1.
|
|
||||||
*/
|
|
||||||
delete (index, length = 1) {
|
|
||||||
if (this._y !== null) {
|
|
||||||
transact(this._y, transaction => {
|
|
||||||
typeArrayDelete(transaction, this, index, length)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// @ts-ignore _prelimContent is defined because this is not yet integrated
|
|
||||||
this._prelimContent.splice(index, length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Dom Element that mirrors this YXmlElement.
|
* Creates a Dom Element that mirrors this YXmlElement.
|
||||||
*
|
*
|
||||||
@ -480,11 +212,3 @@ export class YXmlElement extends YXmlFragment {
|
|||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
export const readYXmlElement = decoder => new YXmlElement(decoding.readVarString(decoder))
|
export const readYXmlElement = decoder => new YXmlElement(decoding.readVarString(decoder))
|
||||||
/**
|
|
||||||
* @param {decoding.Decoder} decoder
|
|
||||||
* @return {YXmlFragment}
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @function
|
|
||||||
*/
|
|
||||||
export const readYXmlFragment = decoder => new YXmlFragment()
|
|
||||||
|
313
src/types/YXmlFragment.js
Normal file
313
src/types/YXmlFragment.js
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
/**
|
||||||
|
* @module YXml
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
YXmlEvent,
|
||||||
|
YXmlElement,
|
||||||
|
AbstractType,
|
||||||
|
typeArrayMap,
|
||||||
|
typeArrayForEach,
|
||||||
|
typeArrayInsertGenerics,
|
||||||
|
typeArrayDelete,
|
||||||
|
typeArrayToArray,
|
||||||
|
YXmlFragmentRefID,
|
||||||
|
callTypeObservers,
|
||||||
|
transact,
|
||||||
|
Transaction, ItemType, YXmlText, YXmlHook, Snapshot // eslint-disable-line
|
||||||
|
} from '../internals.js'
|
||||||
|
|
||||||
|
import * as encoding from 'lib0/encoding.js'
|
||||||
|
import * as decoding from 'lib0/decoding.js' // eslint-disable-line
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the elements to which a set of CSS queries apply.
|
||||||
|
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* query = '.classSelector'
|
||||||
|
* query = 'nodeSelector'
|
||||||
|
* query = '#idSelector'
|
||||||
|
*
|
||||||
|
* @typedef {string} CSS_Selector
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dom filter function.
|
||||||
|
*
|
||||||
|
* @callback domFilter
|
||||||
|
* @param {string} nodeName The nodeName of the element
|
||||||
|
* @param {Map} attributes The map of attributes.
|
||||||
|
* @return {boolean} Whether to include the Dom node in the YXmlElement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a subset of the nodes of a YXmlElement / YXmlFragment and a
|
||||||
|
* position within them.
|
||||||
|
*
|
||||||
|
* Can be created with {@link YXmlFragment#createTreeWalker}
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @implements {IterableIterator}
|
||||||
|
*/
|
||||||
|
export class YXmlTreeWalker {
|
||||||
|
/**
|
||||||
|
* @param {YXmlFragment | YXmlElement} root
|
||||||
|
* @param {function(AbstractType<any>):boolean} [f]
|
||||||
|
*/
|
||||||
|
constructor (root, f = () => true) {
|
||||||
|
this._filter = f
|
||||||
|
this._root = root
|
||||||
|
/**
|
||||||
|
* @type {ItemType | null}
|
||||||
|
*/
|
||||||
|
// @ts-ignore
|
||||||
|
this._currentNode = root._start
|
||||||
|
this._firstCall = true
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator] () {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the next node.
|
||||||
|
*
|
||||||
|
* @return {IteratorResult<YXmlElement|YXmlText|YXmlHook>} The next node.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
next () {
|
||||||
|
let n = this._currentNode
|
||||||
|
if (n !== null && (!this._firstCall || n.deleted || !this._filter(n.type))) { // if first call, we check if we can use the first item
|
||||||
|
do {
|
||||||
|
if (!n.deleted && (n.type.constructor === YXmlElement || n.type.constructor === YXmlFragment) && n.type._start !== null) {
|
||||||
|
// walk down in the tree
|
||||||
|
// @ts-ignore
|
||||||
|
n = n.type._start
|
||||||
|
} else {
|
||||||
|
// walk right or up in the tree
|
||||||
|
while (n !== null) {
|
||||||
|
if (n.right !== null) {
|
||||||
|
// @ts-ignore
|
||||||
|
n = n.right
|
||||||
|
break
|
||||||
|
} else if (n.parent === this._root) {
|
||||||
|
n = null
|
||||||
|
} else {
|
||||||
|
n = n.parent._item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (n !== null && (n.deleted || !this._filter(n.type)))
|
||||||
|
}
|
||||||
|
this._firstCall = false
|
||||||
|
this._currentNode = n
|
||||||
|
if (n === null) {
|
||||||
|
// @ts-ignore return undefined if done=true (the expected result)
|
||||||
|
return { value: undefined, done: true }
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
return { value: n.type, done: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a list of {@link YXmlElement}.and {@link YXmlText} types.
|
||||||
|
* A YxmlFragment is similar to a {@link YXmlElement}, but it does not have a
|
||||||
|
* nodeName and it does not have attributes. Though it can be bound to a DOM
|
||||||
|
* element - in this case the attributes and the nodeName are not shared.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @extends AbstractType<YXmlEvent>
|
||||||
|
*/
|
||||||
|
export class YXmlFragment extends AbstractType {
|
||||||
|
constructor () {
|
||||||
|
super()
|
||||||
|
/**
|
||||||
|
* @type {Array<any>|null}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this._prelimContent = []
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create a subtree of childNodes.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const walker = elem.createTreeWalker(dom => dom.nodeName === 'div')
|
||||||
|
* for (let node in walker) {
|
||||||
|
* // `node` is a div node
|
||||||
|
* nop(node)
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param {function(AbstractType<any>):boolean} filter Function that is called on each child element and
|
||||||
|
* returns a Boolean indicating whether the child
|
||||||
|
* is to be included in the subtree.
|
||||||
|
* @return {YXmlTreeWalker} A subtree and a position within it.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
createTreeWalker (filter) {
|
||||||
|
return new YXmlTreeWalker(this, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first YXmlElement that matches the query.
|
||||||
|
* Similar to DOM's {@link querySelector}.
|
||||||
|
*
|
||||||
|
* Query support:
|
||||||
|
* - tagname
|
||||||
|
* TODO:
|
||||||
|
* - id
|
||||||
|
* - attribute
|
||||||
|
*
|
||||||
|
* @param {CSS_Selector} query The query on the children.
|
||||||
|
* @return {YXmlElement|YXmlText|YXmlHook|null} The first element that matches the query or null.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
querySelector (query) {
|
||||||
|
query = query.toUpperCase()
|
||||||
|
// @ts-ignore
|
||||||
|
const iterator = new YXmlTreeWalker(this, element => element.nodeName === query)
|
||||||
|
const next = iterator.next()
|
||||||
|
if (next.done) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return next.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all YXmlElements that match the query.
|
||||||
|
* Similar to Dom's {@link querySelectorAll}.
|
||||||
|
*
|
||||||
|
* @todo Does not yet support all queries. Currently only query by tagName.
|
||||||
|
*
|
||||||
|
* @param {CSS_Selector} query The query on the children
|
||||||
|
* @return {Array<YXmlElement|YXmlText|YXmlHook|null>} The elements that match this query.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
querySelectorAll (query) {
|
||||||
|
query = query.toUpperCase()
|
||||||
|
// @ts-ignore
|
||||||
|
return Array.from(new YXmlTreeWalker(this, element => element.nodeName === query))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates YXmlEvent and calls observers.
|
||||||
|
* @private
|
||||||
|
*
|
||||||
|
* @param {Transaction} transaction
|
||||||
|
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
||||||
|
*/
|
||||||
|
_callObserver (transaction, parentSubs) {
|
||||||
|
callTypeObservers(this, transaction, new YXmlEvent(this, parentSubs, transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
toString () {
|
||||||
|
return this.toDomString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the string representation of all the children of this YXmlFragment.
|
||||||
|
*
|
||||||
|
* @return {string} The string representation of all children.
|
||||||
|
*/
|
||||||
|
toDomString () {
|
||||||
|
return typeArrayMap(this, xml => xml.toDomString()).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Dom Element that mirrors this YXmlElement.
|
||||||
|
*
|
||||||
|
* @param {Document} [_document=document] The document object (you must define
|
||||||
|
* this when calling this method in
|
||||||
|
* nodejs)
|
||||||
|
* @param {Object<string, any>} [hooks={}] Optional property to customize how hooks
|
||||||
|
* are presented in the DOM
|
||||||
|
* @param {any} [binding] You should not set this property. This is
|
||||||
|
* used if DomBinding wants to create a
|
||||||
|
* association to the created DOM type.
|
||||||
|
* @return {Node} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
toDom (_document = document, hooks = {}, binding) {
|
||||||
|
const fragment = _document.createDocumentFragment()
|
||||||
|
if (binding !== undefined) {
|
||||||
|
binding._createAssociation(fragment, this)
|
||||||
|
}
|
||||||
|
typeArrayForEach(this, xmlType => {
|
||||||
|
fragment.insertBefore(xmlType.toDom(_document, hooks, binding), null)
|
||||||
|
})
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts new content at an index.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Insert character 'a' at position 0
|
||||||
|
* xml.insert(0, [new Y.XmlText('text')])
|
||||||
|
*
|
||||||
|
* @param {number} index The index to insert content at
|
||||||
|
* @param {Array<YXmlElement|YXmlText>} content The array of content
|
||||||
|
*/
|
||||||
|
insert (index, content) {
|
||||||
|
if (this._y !== null) {
|
||||||
|
transact(this._y, transaction => {
|
||||||
|
typeArrayInsertGenerics(transaction, this, index, content)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// @ts-ignore _prelimContent is defined because this is not yet integrated
|
||||||
|
this._prelimContent.splice(index, 0, ...content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes elements starting from an index.
|
||||||
|
*
|
||||||
|
* @param {number} index Index at which to start deleting elements
|
||||||
|
* @param {number} [length=1] The number of elements to remove. Defaults to 1.
|
||||||
|
*/
|
||||||
|
delete (index, length = 1) {
|
||||||
|
if (this._y !== null) {
|
||||||
|
transact(this._y, transaction => {
|
||||||
|
typeArrayDelete(transaction, this, index, length)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// @ts-ignore _prelimContent is defined because this is not yet integrated
|
||||||
|
this._prelimContent.splice(index, length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Transforms this YArray to a JavaScript Array.
|
||||||
|
*
|
||||||
|
* @return {Array<YXmlElement|YXmlText|YXmlHook>}
|
||||||
|
*/
|
||||||
|
toArray () {
|
||||||
|
return typeArrayToArray(this)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Transform the properties of this type to binary and write it to an
|
||||||
|
* BinaryEncoder.
|
||||||
|
*
|
||||||
|
* This is called when this Item is sent to a remote peer.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {encoding.Encoder} encoder The encoder to write data to.
|
||||||
|
*/
|
||||||
|
_write (encoder) {
|
||||||
|
encoding.writeVarUint(encoder, YXmlFragmentRefID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {decoding.Decoder} decoder
|
||||||
|
* @return {YXmlFragment}
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
export const readYXmlFragment = decoder => new YXmlFragment()
|
@ -3,7 +3,7 @@ import {
|
|||||||
findIndexSS,
|
findIndexSS,
|
||||||
createID,
|
createID,
|
||||||
getState,
|
getState,
|
||||||
AbstractItem, StructStore, Transaction, ID // eslint-disable-line
|
AbstractStruct, AbstractItem, StructStore, Transaction, ID // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as math from 'lib0/math.js'
|
import * as math from 'lib0/math.js'
|
||||||
@ -11,9 +11,6 @@ import * as map from 'lib0/map.js'
|
|||||||
import * as encoding from 'lib0/encoding.js'
|
import * as encoding from 'lib0/encoding.js'
|
||||||
import * as decoding from 'lib0/decoding.js'
|
import * as decoding from 'lib0/decoding.js'
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
class DeleteItem {
|
class DeleteItem {
|
||||||
/**
|
/**
|
||||||
* @param {number} clock
|
* @param {number} clock
|
||||||
@ -37,8 +34,6 @@ class DeleteItem {
|
|||||||
* - This DeleteSet is send to other clients
|
* - This DeleteSet is send to other clients
|
||||||
* - We do not create a DeleteSet when we send a sync message. The DeleteSet message is created directly from StructStore
|
* - We do not create a DeleteSet when we send a sync message. The DeleteSet message is created directly from StructStore
|
||||||
* - We read a DeleteSet as part of a sync/update message. In this case the DeleteSet is already sorted and merged.
|
* - We read a DeleteSet as part of a sync/update message. In this case the DeleteSet is already sorted and merged.
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
*/
|
||||||
export class DeleteSet {
|
export class DeleteSet {
|
||||||
constructor () {
|
constructor () {
|
||||||
@ -50,6 +45,33 @@ export class DeleteSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over all structs that were deleted.
|
||||||
|
*
|
||||||
|
* This function expects that the deletes structs are not deleted. Hence, you can
|
||||||
|
* probably only use it in type observes and `afterTransaction` events. But not
|
||||||
|
* in `afterTransactionCleanup`.
|
||||||
|
*
|
||||||
|
* @param {DeleteSet} ds
|
||||||
|
* @param {StructStore} store
|
||||||
|
* @param {function(AbstractStruct):void} f
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
export const iterateDeletedStructs = (ds, store, f) =>
|
||||||
|
ds.clients.forEach((deletes, clientid) => {
|
||||||
|
const structs = /** @type {Array<AbstractStruct>} */ (store.clients.get(clientid))
|
||||||
|
for (let i = 0; i < deletes.length; i++) {
|
||||||
|
const del = deletes[i]
|
||||||
|
let index = findIndexSS(structs, del.clock)
|
||||||
|
let struct
|
||||||
|
do {
|
||||||
|
struct = structs[index++]
|
||||||
|
f(struct)
|
||||||
|
} while (index < structs.length && structs[index].id.clock < del.clock + del.len)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array<DeleteItem>} dis
|
* @param {Array<DeleteItem>} dis
|
||||||
* @param {number} clock
|
* @param {number} clock
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
DeleteSet,
|
|
||||||
isDeleted,
|
isDeleted,
|
||||||
AbstractItem // eslint-disable-line
|
DeleteSet, AbstractItem // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
export class Snapshot {
|
export class Snapshot {
|
||||||
@ -17,7 +16,7 @@ export class Snapshot {
|
|||||||
* @type {DeleteSet}
|
* @type {DeleteSet}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.ds = new DeleteSet()
|
this.ds = ds
|
||||||
/**
|
/**
|
||||||
* State Map
|
* State Map
|
||||||
* @type {Map<number,number>}
|
* @type {Map<number,number>}
|
||||||
|
@ -181,9 +181,14 @@ export const transact = (y, f) => {
|
|||||||
if (deleteItem.clock + deleteItem.len <= struct.id.clock) {
|
if (deleteItem.clock + deleteItem.len <= struct.id.clock) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (struct.deleted && struct instanceof AbstractItem && (struct.constructor !== ItemDeleted || (struct.parent._item !== null && struct.parent._item.deleted))) {
|
if (struct.deleted && struct instanceof AbstractItem) {
|
||||||
// check if we can GC
|
if (struct.constructor !== ItemDeleted || (struct.parent._item !== null && struct.parent._item.deleted)) {
|
||||||
struct.gc(transaction, store)
|
// check if we can GC
|
||||||
|
struct.gc(transaction, store)
|
||||||
|
} else {
|
||||||
|
// otherwise only gc children (if there are any)
|
||||||
|
struct.gcChildren(transaction, store)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
getState,
|
getState,
|
||||||
findRootTypeKey,
|
findRootTypeKey,
|
||||||
AbstractItem,
|
AbstractItem,
|
||||||
|
ItemType,
|
||||||
ID, StructStore, Y, AbstractType // eslint-disable-line
|
ID, StructStore, Y, AbstractType // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@ -240,7 +241,13 @@ export const createAbsolutePositionFromCursor = (cursor, y) => {
|
|||||||
if (tname !== null) {
|
if (tname !== null) {
|
||||||
type = y.get(tname)
|
type = y.get(tname)
|
||||||
} else if (typeID !== null) {
|
} else if (typeID !== null) {
|
||||||
type = getItemType(store, typeID).type
|
const struct = getItemType(store, typeID)
|
||||||
|
if (struct instanceof ItemType) {
|
||||||
|
type = struct.type
|
||||||
|
} else {
|
||||||
|
// struct is garbage collected
|
||||||
|
return null
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw error.unexpectedCase()
|
throw error.unexpectedCase()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user