From da748a78f41a9fd40ddcbe77a0c0256234c18c9c Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Thu, 15 Feb 2018 01:25:08 +0100 Subject: [PATCH] start rewriting y-richtext --- src/Struct/Item.js | 7 ++ src/Struct/ItemFormat.js | 45 +++++++++ src/Type/YArray.js | 8 +- src/Type/YText.js | 202 ++++++++++++++++++++++++++++++++------- 4 files changed, 223 insertions(+), 39 deletions(-) create mode 100644 src/Struct/ItemFormat.js diff --git a/src/Struct/Item.js b/src/Struct/Item.js index 27f4224b..96a75cc8 100644 --- a/src/Struct/Item.js +++ b/src/Struct/Item.js @@ -73,6 +73,13 @@ export default class Item { get _length () { return 1 } + /** + * Some elements are not supposed to be addressable. For example, an + * ItemFormat should not be retrievable via yarray.get(pos) + */ + get _countable () { + return true + } /** * Splits this struct so that another struct can be inserted in-between. * This must be overwritten if _length > 1 diff --git a/src/Struct/ItemFormat.js b/src/Struct/ItemFormat.js new file mode 100644 index 00000000..de33f8f5 --- /dev/null +++ b/src/Struct/ItemFormat.js @@ -0,0 +1,45 @@ +import { default as Item } from './Item.js' +import { logID } from '../MessageHandler/messageToString.js' + +export default class ItemString extends Item { + constructor () { + super() + this.key = null + this.value = null + } + _copy (undeleteChildren, copyPosition) { + let struct = super._copy(undeleteChildren, copyPosition) + struct.key = this.key + struct.value = this.value + return struct + } + get _length () { + return 1 + } + get _countable () { + return false + } + _fromBinary (y, decoder) { + let missing = super._fromBinary(y, decoder) + this.key = decoder.readVarString() + this.value = decoder.readVarString() + return missing + } + _toBinary (encoder) { + super._toBinary(encoder) + encoder.writeVarString(this.key) + encoder.writeVarString(this.value) + } + _logString () { + const left = this._left !== null ? this._left._lastId : null + const origin = this._origin !== null ? this._origin._lastId : null + return `ItemFormat(id:${logID(this._id)},key:${JSON.stringify(this.key)},value:${JSON.stringify(this.value)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${this._parentSub})` + } + _splitAt (y, diff) { + if (diff === 0) { + return this + } else { + return this._right + } + } +} diff --git a/src/Type/YArray.js b/src/Type/YArray.js index 16c64034..886af1af 100644 --- a/src/Type/YArray.js +++ b/src/Type/YArray.js @@ -45,7 +45,7 @@ export default class YArray extends Type { get (pos) { let n = this._start while (n !== null) { - if (!n._deleted) { + if (!n._deleted && n._countable) { if (pos < n._length) { if (n.constructor === ItemJSON || n.constructor === ItemString) { return n._content[pos] @@ -84,7 +84,7 @@ export default class YArray extends Type { let pos = 0 let n = this._start while (n !== null) { - if (!n._deleted) { + if (!n._deleted && n._countable) { if (n instanceof Type) { f(n, pos++, this) } else { @@ -103,7 +103,7 @@ export default class YArray extends Type { let length = 0 let n = this._start while (n !== null) { - if (!n._deleted) { + if (!n._deleted && n._countable) { length += n._length } n = n._right @@ -144,7 +144,7 @@ export default class YArray extends Type { let item = this._start let count = 0 while (item !== null && length > 0) { - if (!item._deleted) { + if (!item._deleted && item._countable) { if (count <= pos && pos < count + item._length) { const diffDel = pos - count item = item._splitAt(this._y, diffDel) diff --git a/src/Type/YText.js b/src/Type/YText.js index 959b92fe..02650ea2 100644 --- a/src/Type/YText.js +++ b/src/Type/YText.js @@ -1,7 +1,66 @@ import ItemString from '../Struct/ItemString.js' +import ItemFormat from '../Struct/ItemFormat.js' import YArray from './YArray.js' import { logID } from '../MessageHandler/messageToString.js' +function integrateItem (item, parent, y, left, right) { + item._origin = left + item._left = left + item._right = right + item._right_origin = right + item._parent = parent + if (y !== null) { + item._integrate(this._y) + } else if (left === null) { + parent._start = item + } else { + left._right = item + } +} + +function findPosition (parent, pos, attributes) { + let currentAttributes = new Map() + let left = null + let right = parent._start + let count = 0 + while (right !== null) { + switch (right.constructor) { + // case ItemBlockFormat: do not break.. + case ItemString: + const rightLen = right._deleted ? 0 : (right._length - 1) + if (count <= pos && pos <= count + rightLen) { + const splitDiff = pos - count + right = right._splitAt(parent._y, splitDiff) + left = right._left + count += splitDiff + break + } + if (!right._deleted) { + count += right._length + } + break + case ItemFormat: + if (right._deleted === false) { + const key = right.key + const value = right.value + if (value === null) { + currentAttributes.delete(key) + } else if (attributes.hasOwnProperty(key)) { + // only set if relevant + currentAttributes.set(key, value) + } + } + break + } + left = right + right = right._right + } + if (pos > count) { + throw new Error('Position exceeds array range!') + } + return [left, right, currentAttributes] +} + export default class YText extends YArray { constructor (string) { super() @@ -13,55 +72,128 @@ export default class YText extends YArray { } } toString () { - const strBuilder = [] + let str = '' let n = this._start while (n !== null) { - if (!n._deleted) { - strBuilder.push(n._content) + if (!n._deleted && n._countable) { + str += n._content } n = n._right } - return strBuilder.join('') + return str } - insert (pos, text) { + /** + * As defined by Quilljs - https://quilljs.com/docs/delta/ + */ + toRichtextDelta () { + let ops = [] + let currentAttributes = new Map() + let str = '' + let n = this._start + function packStr () { + if (str.length > 0) { + // pack str with attributes to ops + let attributes = {} + for (let [key, value] of currentAttributes) { + attributes[key] = value + } + ops.push({ insert: str, attributes }) + str = '' + } + } + while (n !== null) { + if (!n._deleted) { + switch (n.constructor) { + case ItemString: + str += n._content + break + case ItemFormat: + packStr() + const value = n.value + const key = n.key + if (value === null) { + currentAttributes.delete(key) + } else { + currentAttributes.set(key, value) + } + break + } + } + n = n._right + } + packStr() + return ops + } + insert (pos, text, attributes = {}) { if (text.length <= 0) { return } this._transact(y => { - let left = null - let right = this._start - let count = 0 - while (right !== null) { - const rightLen = right._deleted ? 0 : (right._length - 1) - if (count <= pos && pos <= count + rightLen) { - const splitDiff = pos - count - right = right._splitAt(this._y, splitDiff) - left = right._left - count += splitDiff - break + let [left, right, currentAttributes] = findPosition(this, pos, attributes) + let negatedAttributes = new Map() + // insert format-start items + for (let key in attributes) { + const val = attributes[key] + const currentVal = currentAttributes.get(key) + if (currentVal !== val) { + // save negated attribute (set null if currentVal undefined) + negatedAttributes.set(key, currentVal || null) + let format = new ItemFormat() + format.key = key + format.value = val + integrateItem(format, this, y, left, right) + left = format } - if (!right._deleted) { - count += right._length - } - left = right - right = right._right - } - if (pos > count) { - throw new Error('Position exceeds array range!') } + // insert text content let item = new ItemString() - item._origin = left - item._left = left - item._right = right - item._right_origin = right - item._parent = this item._content = text - if (y !== null) { - item._integrate(this._y) - } else if (left === null) { - this._start = item - } else { - left._right = item + integrateItem(item, this, y, left, right) + left = item + // negate applied formats + for (let [key, value] of negatedAttributes) { + let format = new ItemFormat() + format.key = key + format.value = value + integrateItem(format, this, y, left, right) + left = format + } + }) + } + format (pos, length, attributes) { + this._transact(y => { + let [left, _right, currentAttributes] = findPosition(this, pos, attributes) + if (_right === null) { + return + } + let negatedAttributes = new Map() + // insert format-start items + for (let key in attributes) { + const val = attributes[key] + const currentVal = currentAttributes.get(key) + if (currentVal !== val) { + // save negated attribute (set null if currentVal undefined) + negatedAttributes.set(key, currentVal || null) + let format = new ItemFormat() + format.key = key + format.value = val + integrateItem(format, this, y, left, _right) + left = format + } + } + // iterate until first non-format or null is found + // delete all formats with attributes[format.key] != null + while (length > 0 && left !== null) { + if (left._deleted === false) { + if (left.constructor === ItemFormat) { + if (attributes[left.key] != null) { + left.delete(y) + } + } else if (length < left._length) { + + } + } + left = left._right } }) }