implement quill binding for y-text
This commit is contained in:
@@ -90,7 +90,7 @@ export default class BinaryDecoder {
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = this.uint8arr[this.pos++]
|
||||
}
|
||||
let encodedString = String.fromCodePoint(...bytes)
|
||||
let encodedString = bytes.map(b => String.fromCodePoint(b)).join('')
|
||||
return decodeURIComponent(escape(encodedString))
|
||||
}
|
||||
/**
|
||||
|
||||
37
src/Binding/QuillBinding.js
Normal file
37
src/Binding/QuillBinding.js
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
import Binding from './Binding.js'
|
||||
|
||||
function typeObserver (event) {
|
||||
const quill = this.target
|
||||
quill.update('yjs')
|
||||
this._mutualExclude(function () {
|
||||
quill.updateContents(event.delta, 'yjs')
|
||||
quill.update('yjs') // ignore applied changes
|
||||
})
|
||||
}
|
||||
|
||||
function quillObserver (delta) {
|
||||
this._mutualExclude(() => {
|
||||
this.type.applyDelta(delta.ops)
|
||||
})
|
||||
}
|
||||
|
||||
export default class QuillBinding extends Binding {
|
||||
constructor (textType, quillInstance) {
|
||||
// Binding handles textType as this.type and quillInstance as this.target
|
||||
super(textType, quillInstance)
|
||||
// set initial value
|
||||
quillInstance.setContents(textType.toDelta(), 'yjs')
|
||||
// Observers are handled by this class
|
||||
this._typeObserver = typeObserver.bind(this)
|
||||
this._quillObserver = quillObserver.bind(this)
|
||||
textType.observe(this._typeObserver)
|
||||
quillInstance.on('text-change', this._quillObserver)
|
||||
}
|
||||
destroy () {
|
||||
// Remove everything that is handled by this class
|
||||
this.type.unobserve(this._typeObserver)
|
||||
this.target.unobserve(this._quillObserver)
|
||||
super.destroy()
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,11 @@ export function splitHelper (y, a, b, diff) {
|
||||
o = o._right
|
||||
}
|
||||
y.os.put(b)
|
||||
if (y._transaction.newTypes.has(a)) {
|
||||
y._transaction.newTypes.add(b)
|
||||
} else if (y._transaction.deletedStructs.has(a)) {
|
||||
y._transaction.deletedStructs.add(b)
|
||||
}
|
||||
}
|
||||
|
||||
export default class Item {
|
||||
|
||||
31
src/Struct/ItemEmbed.js
Normal file
31
src/Struct/ItemEmbed.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { default as Item } from './Item.js'
|
||||
import { logID } from '../MessageHandler/messageToString.js'
|
||||
|
||||
export default class ItemEmbed extends Item {
|
||||
constructor () {
|
||||
super()
|
||||
this.embed = null
|
||||
}
|
||||
_copy (undeleteChildren, copyPosition) {
|
||||
let struct = super._copy(undeleteChildren, copyPosition)
|
||||
struct.embed = this.embed
|
||||
return struct
|
||||
}
|
||||
get _length () {
|
||||
return 1
|
||||
}
|
||||
_fromBinary (y, decoder) {
|
||||
const missing = super._fromBinary(y, decoder)
|
||||
this.embed = JSON.parse(decoder.readVarString())
|
||||
return missing
|
||||
}
|
||||
_toBinary (encoder) {
|
||||
super._toBinary(encoder)
|
||||
encoder.writeVarString(JSON.stringify(this.embed))
|
||||
}
|
||||
_logString () {
|
||||
const left = this._left !== null ? this._left._lastId : null
|
||||
const origin = this._origin !== null ? this._origin._lastId : null
|
||||
return `ItemEmbed(id:${logID(this._id)},embed:${JSON.stringify(this.embed)},left:${logID(left)},origin:${logID(origin)},right:${logID(this._right)},parent:${logID(this._parent)},parentSub:${this._parentSub})`
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { default as Item } from './Item.js'
|
||||
import { logID } from '../MessageHandler/messageToString.js'
|
||||
|
||||
export default class ItemString extends Item {
|
||||
export default class ItemFormat extends Item {
|
||||
constructor () {
|
||||
super()
|
||||
this.key = null
|
||||
@@ -20,26 +20,19 @@ export default class ItemString extends Item {
|
||||
return false
|
||||
}
|
||||
_fromBinary (y, decoder) {
|
||||
let missing = super._fromBinary(y, decoder)
|
||||
const missing = super._fromBinary(y, decoder)
|
||||
this.key = decoder.readVarString()
|
||||
this.value = decoder.readVarString()
|
||||
this.value = JSON.parse(decoder.readVarString())
|
||||
return missing
|
||||
}
|
||||
_toBinary (encoder) {
|
||||
super._toBinary(encoder)
|
||||
encoder.writeVarString(this.key)
|
||||
encoder.writeVarString(this.value)
|
||||
encoder.writeVarString(JSON.stringify(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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ import ItemString from '../Struct/ItemString.js'
|
||||
import { logID } from '../MessageHandler/messageToString.js'
|
||||
import YEvent from '../Util/YEvent.js'
|
||||
|
||||
class YArrayEvent extends YEvent {
|
||||
export class YArrayEvent extends YEvent {
|
||||
constructor (yarray, remote, transaction) {
|
||||
super(yarray)
|
||||
this.remote = remote
|
||||
this._transaction = transaction
|
||||
this._addedElements = null
|
||||
this._removedElements = null
|
||||
}
|
||||
get addedElements () {
|
||||
if (this._addedElements === null) {
|
||||
@@ -26,15 +27,18 @@ class YArrayEvent extends YEvent {
|
||||
return this._addedElements
|
||||
}
|
||||
get removedElements () {
|
||||
const target = this.target
|
||||
const transaction = this._transaction
|
||||
const removedElements = new Set()
|
||||
transaction.deletedStructs.forEach(function (struct) {
|
||||
if (struct._parent === target && !transaction.newTypes.has(struct)) {
|
||||
removedElements.add(struct)
|
||||
}
|
||||
})
|
||||
return removedElements
|
||||
if (this._removedElements === null) {
|
||||
const target = this.target
|
||||
const transaction = this._transaction
|
||||
const removedElements = new Set()
|
||||
transaction.deletedStructs.forEach(function (struct) {
|
||||
if (struct._parent === target && !transaction.newTypes.has(struct)) {
|
||||
removedElements.add(struct)
|
||||
}
|
||||
})
|
||||
this._removedElements = removedElements
|
||||
}
|
||||
return this._removedElements
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import ItemString from '../Struct/ItemString.js'
|
||||
import ItemEmbed from '../Struct/ItemEmbed.js'
|
||||
import ItemFormat from '../Struct/ItemFormat.js'
|
||||
import YArray from './YArray.js'
|
||||
import { logID } from '../MessageHandler/messageToString.js'
|
||||
import { YArrayEvent, default as YArray } from './YArray.js'
|
||||
|
||||
function integrateItem (item, parent, y, left, right) {
|
||||
item._origin = left
|
||||
@@ -10,7 +11,7 @@ function integrateItem (item, parent, y, left, right) {
|
||||
item._right_origin = right
|
||||
item._parent = parent
|
||||
if (y !== null) {
|
||||
item._integrate(this._y)
|
||||
item._integrate(y)
|
||||
} else if (left === null) {
|
||||
parent._start = item
|
||||
} else {
|
||||
@@ -18,49 +19,365 @@ function integrateItem (item, parent, y, left, right) {
|
||||
}
|
||||
}
|
||||
|
||||
function findPosition (parent, pos, attributes) {
|
||||
let currentAttributes = new Map()
|
||||
let left = null
|
||||
let right = parent._start
|
||||
let count = 0
|
||||
while (right !== null) {
|
||||
function findNextPosition (currentAttributes, parent, left, right, count) {
|
||||
while (right !== null && count > 0) {
|
||||
switch (right.constructor) {
|
||||
// case ItemBlockFormat: do not break..
|
||||
case ItemEmbed:
|
||||
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)
|
||||
if (count <= rightLen) {
|
||||
right = right._splitAt(parent._y, count)
|
||||
left = right._left
|
||||
count += splitDiff
|
||||
break
|
||||
return [left, right, currentAttributes]
|
||||
}
|
||||
if (!right._deleted) {
|
||||
count += right._length
|
||||
if (right._deleted === false) {
|
||||
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)
|
||||
}
|
||||
updateCurrentAttributes(currentAttributes, right)
|
||||
}
|
||||
break
|
||||
}
|
||||
left = right
|
||||
right = right._right
|
||||
}
|
||||
if (pos > count) {
|
||||
throw new Error('Position exceeds array range!')
|
||||
}
|
||||
return [left, right, currentAttributes]
|
||||
}
|
||||
|
||||
function findPosition (parent, pos) {
|
||||
let currentAttributes = new Map()
|
||||
let left = null
|
||||
let right = parent._start
|
||||
return findNextPosition(currentAttributes, parent, left, right, pos)
|
||||
}
|
||||
|
||||
// negate applied formats
|
||||
function insertNegatedAttributes (y, parent, left, right, negatedAttributes) {
|
||||
// check if we really need to remove attributes
|
||||
while (
|
||||
right !== null && (
|
||||
right._deleted === true || (
|
||||
right.constructor === ItemFormat &&
|
||||
(negatedAttributes.get(right.key) === right.value)
|
||||
)
|
||||
)
|
||||
) {
|
||||
if (right._deleted === false) {
|
||||
negatedAttributes.delete(right.key)
|
||||
}
|
||||
left = right
|
||||
right = right._right
|
||||
}
|
||||
for (let [key, val] of negatedAttributes) {
|
||||
let format = new ItemFormat()
|
||||
format.key = key
|
||||
format.value = val
|
||||
integrateItem(format, parent, y, left, right)
|
||||
left = format
|
||||
}
|
||||
return [left, right]
|
||||
}
|
||||
|
||||
function updateCurrentAttributes (currentAttributes, item) {
|
||||
const value = item.value
|
||||
const key = item.key
|
||||
if (value === null) {
|
||||
currentAttributes.delete(key)
|
||||
} else {
|
||||
currentAttributes.set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
function minimizeAttributeChanges (left, right, currentAttributes, attributes) {
|
||||
// go right while attributes[right.key] === right.value (or right is deleted)
|
||||
while (true) {
|
||||
if (right === null) {
|
||||
break
|
||||
} else if (right._deleted === true) {
|
||||
// continue
|
||||
} else if (right.constructor === ItemFormat && (attributes[right.key] || null) === right.value) {
|
||||
// found a format, update currentAttributes and continue
|
||||
updateCurrentAttributes(currentAttributes, right)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
left = right
|
||||
right = right._right
|
||||
}
|
||||
return [left, right]
|
||||
}
|
||||
|
||||
function insertText (y, text, parent, left, right, currentAttributes, attributes) {
|
||||
for (let [key] of currentAttributes) {
|
||||
if (attributes.hasOwnProperty(key) === false) {
|
||||
attributes[key] = null
|
||||
}
|
||||
}
|
||||
[left, right] = minimizeAttributeChanges(left, right, currentAttributes, 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, parent, y, left, right)
|
||||
left = format
|
||||
}
|
||||
}
|
||||
// insert content
|
||||
let item
|
||||
if (text.constructor === String) {
|
||||
item = new ItemString()
|
||||
item._content = text
|
||||
} else {
|
||||
item = new ItemEmbed()
|
||||
item.embed = text
|
||||
}
|
||||
integrateItem(item, parent, y, left, right)
|
||||
left = item
|
||||
return insertNegatedAttributes(y, parent, left, right, negatedAttributes)
|
||||
}
|
||||
|
||||
function formatText (y, length, parent, left, right, currentAttributes, attributes) {
|
||||
[left, right] = minimizeAttributeChanges(left, right, currentAttributes, 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, parent, 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 && right !== null) {
|
||||
if (right._deleted === false) {
|
||||
switch (right.constructor) {
|
||||
case ItemFormat:
|
||||
if (attributes.hasOwnProperty(right.key)) {
|
||||
if (attributes[right.key] === right.value) {
|
||||
negatedAttributes.delete(right.key)
|
||||
} else {
|
||||
negatedAttributes.set(right.key, right.value)
|
||||
}
|
||||
right._delete(y)
|
||||
}
|
||||
updateCurrentAttributes(currentAttributes, right)
|
||||
break
|
||||
case ItemEmbed:
|
||||
case ItemString:
|
||||
right._splitAt(y, length)
|
||||
length -= right._length
|
||||
break
|
||||
}
|
||||
}
|
||||
left = right
|
||||
right = right._right
|
||||
}
|
||||
return insertNegatedAttributes(y, parent, left, right, negatedAttributes)
|
||||
}
|
||||
|
||||
function deleteText (y, length, parent, left, right, currentAttributes) {
|
||||
while (length > 0 && right !== null) {
|
||||
if (right._deleted === false) {
|
||||
switch (right.constructor) {
|
||||
case ItemFormat:
|
||||
updateCurrentAttributes(currentAttributes, right)
|
||||
break
|
||||
case ItemEmbed:
|
||||
case ItemString:
|
||||
right._splitAt(y, length)
|
||||
length -= right._length
|
||||
right._delete(y)
|
||||
break
|
||||
}
|
||||
}
|
||||
left = right
|
||||
right = right._right
|
||||
}
|
||||
return [left, right]
|
||||
}
|
||||
|
||||
class YTextEvent extends YArrayEvent {
|
||||
constructor (ytext, remote, transaction) {
|
||||
super(ytext, remote, transaction)
|
||||
this._delta = null
|
||||
}
|
||||
get delta () {
|
||||
if (this._delta === null) {
|
||||
const y = this.target._y
|
||||
y.transact(() => {
|
||||
let item = this.target._start
|
||||
const delta = []
|
||||
const added = this.addedElements
|
||||
const removed = this.removedElements
|
||||
this._delta = delta
|
||||
let action = null
|
||||
let attributes = {} // counts added or removed new attributes for retain
|
||||
const currentAttributes = new Map() // saves all current attributes for insert
|
||||
const oldAttributes = new Map()
|
||||
let insert = ''
|
||||
let retain = 0
|
||||
let deleteLen = 0
|
||||
const addOp = function addOp () {
|
||||
if (action !== null) {
|
||||
let op
|
||||
switch (action) {
|
||||
case 'delete':
|
||||
op = { delete: deleteLen }
|
||||
deleteLen = 0
|
||||
break
|
||||
case 'insert':
|
||||
op = { insert }
|
||||
if (currentAttributes.size > 0) {
|
||||
op.attributes = {}
|
||||
for (let [key, value] of currentAttributes) {
|
||||
if (value !== null) {
|
||||
op.attributes[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
insert = ''
|
||||
break
|
||||
case 'retain':
|
||||
op = { retain }
|
||||
if (Object.keys(attributes).length > 0) {
|
||||
op.attributes = {}
|
||||
for (let key in attributes) {
|
||||
op.attributes[key] = attributes[key]
|
||||
}
|
||||
}
|
||||
retain = 0
|
||||
break
|
||||
}
|
||||
delta.push(op)
|
||||
action = null
|
||||
}
|
||||
}
|
||||
while (item !== null) {
|
||||
switch (item.constructor) {
|
||||
case ItemEmbed:
|
||||
if (added.has(item)) {
|
||||
addOp()
|
||||
action = 'insert'
|
||||
insert = item.embed
|
||||
addOp()
|
||||
} else if (removed.has(item)) {
|
||||
if (action !== 'delete') {
|
||||
addOp()
|
||||
action = 'delete'
|
||||
}
|
||||
deleteLen += 1
|
||||
} else if (item._deleted === false) {
|
||||
if (action !== 'retain') {
|
||||
addOp()
|
||||
action = 'retain'
|
||||
}
|
||||
retain += 1
|
||||
}
|
||||
break
|
||||
case ItemString:
|
||||
if (added.has(item)) {
|
||||
if (action !== 'insert') {
|
||||
addOp()
|
||||
action = 'insert'
|
||||
}
|
||||
insert += item._content
|
||||
} else if (removed.has(item)) {
|
||||
if (action !== 'delete') {
|
||||
addOp()
|
||||
action = 'delete'
|
||||
}
|
||||
deleteLen += item._length
|
||||
} else if (item._deleted === false) {
|
||||
if (action !== 'retain') {
|
||||
addOp()
|
||||
action = 'retain'
|
||||
}
|
||||
retain += item._length
|
||||
}
|
||||
break
|
||||
case ItemFormat:
|
||||
if (added.has(item)) {
|
||||
const curVal = currentAttributes.get(item.key) || null
|
||||
if (curVal !== item.value) {
|
||||
if (action === 'retain') {
|
||||
addOp()
|
||||
}
|
||||
if (item.value === (oldAttributes.get(item.key) || null)) {
|
||||
delete attributes[item.key]
|
||||
} else {
|
||||
attributes[item.key] = item.value
|
||||
}
|
||||
} else {
|
||||
item._delete(y)
|
||||
}
|
||||
} else if (removed.has(item)) {
|
||||
oldAttributes.set(item.key, item.value)
|
||||
const curVal = currentAttributes.get(item.key) || null
|
||||
if (curVal !== item.value) {
|
||||
if (action === 'retain') {
|
||||
addOp()
|
||||
}
|
||||
attributes[item.key] = curVal
|
||||
}
|
||||
} else if (item._deleted === false) {
|
||||
oldAttributes.set(item.key, item.value)
|
||||
if (attributes.hasOwnProperty(item.key)) {
|
||||
if (attributes[item.key] !== item.value) {
|
||||
if (action === 'retain') {
|
||||
addOp()
|
||||
}
|
||||
if (item.value === null) {
|
||||
attributes[item.key] = item.value
|
||||
} else {
|
||||
delete attributes[item.key]
|
||||
}
|
||||
} else {
|
||||
item._delete(y)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item._deleted === false) {
|
||||
if (action === 'insert') {
|
||||
addOp()
|
||||
}
|
||||
updateCurrentAttributes(currentAttributes, item)
|
||||
}
|
||||
break
|
||||
}
|
||||
item = item._right
|
||||
}
|
||||
addOp()
|
||||
while (this._delta.length > 0) {
|
||||
let lastOp = this._delta[this._delta.length - 1]
|
||||
if (lastOp.hasOwnProperty('retain') && !lastOp.hasOwnProperty('attributes')) {
|
||||
// retain delta's if they don't assign attributes
|
||||
this._delta.pop()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return this._delta
|
||||
}
|
||||
}
|
||||
|
||||
export default class YText extends YArray {
|
||||
constructor (string) {
|
||||
super()
|
||||
@@ -71,6 +388,9 @@ export default class YText extends YArray {
|
||||
this._start = start
|
||||
}
|
||||
}
|
||||
_callObserver (transaction, parentSubs, remote) {
|
||||
this._callEventHandler(transaction, new YTextEvent(this, remote, transaction))
|
||||
}
|
||||
toString () {
|
||||
let str = ''
|
||||
let n = this._start
|
||||
@@ -82,10 +402,27 @@ export default class YText extends YArray {
|
||||
}
|
||||
return str
|
||||
}
|
||||
applyDelta (delta) {
|
||||
this._transact(y => {
|
||||
let left = null
|
||||
let right = this._start
|
||||
const currentAttributes = new Map()
|
||||
for (let i = 0; i < delta.length; i++) {
|
||||
let op = delta[i]
|
||||
if (op.hasOwnProperty('insert')) {
|
||||
;[left, right] = insertText(y, op.insert, this, left, right, currentAttributes, op.attributes || {})
|
||||
} else if (op.hasOwnProperty('retain')) {
|
||||
;[left, right] = formatText(y, op.retain, this, left, right, currentAttributes, op.attributes || {})
|
||||
} else if (op.hasOwnProperty('delete')) {
|
||||
;[left, right] = deleteText(y, op.delete, this, left, right, currentAttributes)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
/**
|
||||
* As defined by Quilljs - https://quilljs.com/docs/delta/
|
||||
*/
|
||||
toRichtextDelta () {
|
||||
toDelta () {
|
||||
let ops = []
|
||||
let currentAttributes = new Map()
|
||||
let str = ''
|
||||
@@ -94,10 +431,16 @@ export default class YText extends YArray {
|
||||
if (str.length > 0) {
|
||||
// pack str with attributes to ops
|
||||
let attributes = {}
|
||||
let addAttributes = false
|
||||
for (let [key, value] of currentAttributes) {
|
||||
addAttributes = true
|
||||
attributes[key] = value
|
||||
}
|
||||
ops.push({ insert: str, attributes })
|
||||
let op = { insert: str }
|
||||
if (addAttributes) {
|
||||
op.attributes = attributes
|
||||
}
|
||||
ops.push(op)
|
||||
str = ''
|
||||
}
|
||||
}
|
||||
@@ -109,13 +452,7 @@ export default class YText extends YArray {
|
||||
break
|
||||
case ItemFormat:
|
||||
packStr()
|
||||
const value = n.value
|
||||
const key = n.key
|
||||
if (value === null) {
|
||||
currentAttributes.delete(key)
|
||||
} else {
|
||||
currentAttributes.set(key, value)
|
||||
}
|
||||
updateCurrentAttributes(currentAttributes, n)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -129,72 +466,35 @@ export default class YText extends YArray {
|
||||
return
|
||||
}
|
||||
this._transact(y => {
|
||||
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
|
||||
}
|
||||
}
|
||||
// insert text content
|
||||
let item = new ItemString()
|
||||
item._content = text
|
||||
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
|
||||
}
|
||||
let [left, right, currentAttributes] = findPosition(this, pos)
|
||||
insertText(y, text, this, left, right, currentAttributes, attributes)
|
||||
})
|
||||
}
|
||||
insertEmbed (pos, embed, attributes = {}) {
|
||||
if (embed.constructor !== Object) {
|
||||
throw new Error('Embed must be an Object')
|
||||
}
|
||||
this._transact(y => {
|
||||
let [left, right, currentAttributes] = findPosition(this, pos)
|
||||
insertText(y, embed, this, left, right, currentAttributes, attributes)
|
||||
})
|
||||
}
|
||||
delete (pos, length) {
|
||||
if (length === 0) {
|
||||
return
|
||||
}
|
||||
this._transact(y => {
|
||||
let [left, right, currentAttributes] = findPosition(this, pos)
|
||||
deleteText(y, length, this, left, right, currentAttributes)
|
||||
})
|
||||
}
|
||||
format (pos, length, attributes) {
|
||||
this._transact(y => {
|
||||
let [left, _right, currentAttributes] = findPosition(this, pos, attributes)
|
||||
if (_right === null) {
|
||||
let [left, right, currentAttributes] = findPosition(this, pos)
|
||||
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
|
||||
}
|
||||
formatText(y, length, this, left, right, currentAttributes, attributes)
|
||||
})
|
||||
}
|
||||
_logString () {
|
||||
|
||||
@@ -6,6 +6,8 @@ import { YXmlFragment, YXmlElement, YXmlText, YXmlHook } from '../Type/y-xml/y-x
|
||||
import Delete from '../Struct/Delete.js'
|
||||
import ItemJSON from '../Struct/ItemJSON.js'
|
||||
import ItemString from '../Struct/ItemString.js'
|
||||
import ItemFormat from '../Struct/ItemFormat.js'
|
||||
import ItemEmbed from '../Struct/ItemEmbed.js'
|
||||
|
||||
const structs = new Map()
|
||||
const references = new Map()
|
||||
@@ -23,8 +25,11 @@ export function getReference (typeConstructor) {
|
||||
return references.get(typeConstructor)
|
||||
}
|
||||
|
||||
// TODO: reorder (Item* should have low numbers)
|
||||
addStruct(0, ItemJSON)
|
||||
addStruct(1, ItemString)
|
||||
addStruct(10, ItemFormat)
|
||||
addStruct(11, ItemEmbed)
|
||||
addStruct(2, Delete)
|
||||
|
||||
addStruct(3, YArray)
|
||||
|
||||
2
src/Y.js
2
src/Y.js
@@ -23,6 +23,7 @@ import debug from 'debug'
|
||||
import Transaction from './Transaction.js'
|
||||
|
||||
import TextareaBinding from './Binding/TextareaBinding.js'
|
||||
import QuillBinding from './Binding/QuillBinding.js'
|
||||
|
||||
import { toBinary, fromBinary } from './MessageHandler/binaryEncode.js'
|
||||
|
||||
@@ -202,6 +203,7 @@ Y.XmlText = YXmlText
|
||||
Y.XmlHook = YXmlHook
|
||||
|
||||
Y.TextareaBinding = TextareaBinding
|
||||
Y.QuillBinding = QuillBinding
|
||||
|
||||
Y.utils = {
|
||||
BinaryDecoder,
|
||||
|
||||
Reference in New Issue
Block a user