allow types as Y.Text embeds
This commit is contained in:
parent
df9bfbe778
commit
0ec67170d3
@ -32,6 +32,7 @@ import {
|
||||
import * as object from 'lib0/object'
|
||||
import * as map from 'lib0/map'
|
||||
import * as error from 'lib0/error'
|
||||
import { ContentType } from 'yjs'
|
||||
|
||||
/**
|
||||
* @param {any} a
|
||||
@ -62,17 +63,16 @@ export class ItemTextListPosition {
|
||||
error.unexpectedCase()
|
||||
}
|
||||
switch (this.right.content.constructor) {
|
||||
case ContentEmbed:
|
||||
case ContentString:
|
||||
if (!this.right.deleted) {
|
||||
this.index += this.right.length
|
||||
}
|
||||
break
|
||||
case ContentFormat:
|
||||
if (!this.right.deleted) {
|
||||
updateCurrentAttributes(this.currentAttributes, /** @type {ContentFormat} */ (this.right.content))
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (!this.right.deleted) {
|
||||
this.index += this.right.length
|
||||
}
|
||||
break
|
||||
}
|
||||
this.left = this.right
|
||||
this.right = this.right.right
|
||||
@ -91,8 +91,12 @@ export class ItemTextListPosition {
|
||||
const findNextPosition = (transaction, pos, count) => {
|
||||
while (pos.right !== null && count > 0) {
|
||||
switch (pos.right.content.constructor) {
|
||||
case ContentEmbed:
|
||||
case ContentString:
|
||||
case ContentFormat:
|
||||
if (!pos.right.deleted) {
|
||||
updateCurrentAttributes(pos.currentAttributes, /** @type {ContentFormat} */ (pos.right.content))
|
||||
}
|
||||
break
|
||||
default:
|
||||
if (!pos.right.deleted) {
|
||||
if (count < pos.right.length) {
|
||||
// split right
|
||||
@ -102,11 +106,6 @@ const findNextPosition = (transaction, pos, count) => {
|
||||
count -= pos.right.length
|
||||
}
|
||||
break
|
||||
case ContentFormat:
|
||||
if (!pos.right.deleted) {
|
||||
updateCurrentAttributes(pos.currentAttributes, /** @type {ContentFormat} */ (pos.right.content))
|
||||
}
|
||||
break
|
||||
}
|
||||
pos.left = pos.right
|
||||
pos.right = pos.right.right
|
||||
@ -245,7 +244,7 @@ const insertAttributes = (transaction, parent, currPos, attributes) => {
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any>} parent
|
||||
* @param {ItemTextListPosition} currPos
|
||||
* @param {string|object} text
|
||||
* @param {string|object|AbstractType<any>} text
|
||||
* @param {Object<string,any>} attributes
|
||||
*
|
||||
* @private
|
||||
@ -262,7 +261,7 @@ const insertText = (transaction, parent, currPos, text, attributes) => {
|
||||
minimizeAttributeChanges(currPos, attributes)
|
||||
const negatedAttributes = insertAttributes(transaction, parent, currPos, attributes)
|
||||
// insert content
|
||||
const content = text.constructor === String ? new ContentString(/** @type {string} */ (text)) : new ContentEmbed(text)
|
||||
const content = text.constructor === String ? new ContentString(/** @type {string} */ (text)) : (text instanceof AbstractType ? new ContentType(text) : new ContentEmbed(text))
|
||||
let { left, right, index } = currPos
|
||||
if (parent._searchMarker) {
|
||||
updateMarkerChanges(parent._searchMarker, currPos.index, content.getLength())
|
||||
@ -308,8 +307,7 @@ const formatText = (transaction, parent, currPos, length, attributes) => {
|
||||
}
|
||||
break
|
||||
}
|
||||
case ContentEmbed:
|
||||
case ContentString:
|
||||
default:
|
||||
if (length < currPos.right.length) {
|
||||
getItemCleanStart(transaction, createID(currPos.right.id.client, currPos.right.id.clock + length))
|
||||
}
|
||||
@ -348,7 +346,7 @@ const formatText = (transaction, parent, currPos, length, attributes) => {
|
||||
* @function
|
||||
*/
|
||||
const cleanupFormattingGap = (transaction, start, end, startAttributes, endAttributes) => {
|
||||
while (end && end.content.constructor !== ContentString && end.content.constructor !== ContentEmbed) {
|
||||
while (end && (!end.countable || end.deleted)) {
|
||||
if (!end.deleted && end.content.constructor === ContentFormat) {
|
||||
updateCurrentAttributes(endAttributes, /** @type {ContentFormat} */ (end.content))
|
||||
}
|
||||
@ -381,12 +379,12 @@ const cleanupFormattingGap = (transaction, start, end, startAttributes, endAttri
|
||||
*/
|
||||
const cleanupContextlessFormattingGap = (transaction, item) => {
|
||||
// iterate until item.right is null or content
|
||||
while (item && item.right && (item.right.deleted || (item.right.content.constructor !== ContentString && item.right.content.constructor !== ContentEmbed))) {
|
||||
while (item && item.right && (item.right.deleted || !item.right.countable)) {
|
||||
item = item.right
|
||||
}
|
||||
const attrs = new Set()
|
||||
// iterate back until a content item is found
|
||||
while (item && (item.deleted || (item.content.constructor !== ContentString && item.content.constructor !== ContentEmbed))) {
|
||||
while (item && (item.deleted || !item.countable)) {
|
||||
if (!item.deleted && item.content.constructor === ContentFormat) {
|
||||
const key = /** @type {ContentFormat} */ (item.content).key
|
||||
if (attrs.has(key)) {
|
||||
@ -424,8 +422,7 @@ export const cleanupYTextFormatting = type => {
|
||||
case ContentFormat:
|
||||
updateCurrentAttributes(currentAttributes, /** @type {ContentFormat} */ (end.content))
|
||||
break
|
||||
case ContentEmbed:
|
||||
case ContentString:
|
||||
default:
|
||||
res += cleanupFormattingGap(transaction, start, end, startAttributes, currentAttributes)
|
||||
startAttributes = map.copy(currentAttributes)
|
||||
start = end
|
||||
@ -454,6 +451,7 @@ const deleteText = (transaction, currPos, length) => {
|
||||
while (length > 0 && currPos.right !== null) {
|
||||
if (currPos.right.deleted === false) {
|
||||
switch (currPos.right.content.constructor) {
|
||||
case ContentType:
|
||||
case ContentEmbed:
|
||||
case ContentString:
|
||||
if (length < currPos.right.length) {
|
||||
@ -540,7 +538,7 @@ export class YTextEvent extends YEvent {
|
||||
get changes () {
|
||||
if (this._changes === null) {
|
||||
/**
|
||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert?:Array<any>|string, delete?:number, retain?:number}>}}
|
||||
* @type {{added:Set<Item>,deleted:Set<Item>,keys:Map<string,{action:'add'|'update'|'delete',oldValue:any}>,delta:Array<{insert?:Array<any>|string|AbstractType<any>|object, delete?:number, retain?:number}>}}
|
||||
*/
|
||||
const changes = {
|
||||
keys: this.keys,
|
||||
@ -557,7 +555,7 @@ export class YTextEvent extends YEvent {
|
||||
* Compute the changes in the delta format.
|
||||
* A {@link https://quilljs.com/docs/delta/|Quill Delta}) that represents the changes on the document.
|
||||
*
|
||||
* @type {Array<{insert?:string, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
||||
* @type {Array<{insert?:string|object|AbstractType<any>, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@ -565,7 +563,7 @@ export class YTextEvent extends YEvent {
|
||||
if (this._delta === null) {
|
||||
const y = /** @type {Doc} */ (this.target.doc)
|
||||
/**
|
||||
* @type {Array<{insert?:string, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
||||
* @type {Array<{insert?:string|object|AbstractType<any>, delete?:number, retain?:number, attributes?: Object<string,any>}>}
|
||||
*/
|
||||
const delta = []
|
||||
transact(y, transaction => {
|
||||
@ -626,12 +624,13 @@ export class YTextEvent extends YEvent {
|
||||
}
|
||||
while (item !== null) {
|
||||
switch (item.content.constructor) {
|
||||
case ContentType:
|
||||
case ContentEmbed:
|
||||
if (this.adds(item)) {
|
||||
if (!this.deletes(item)) {
|
||||
addOp()
|
||||
action = 'insert'
|
||||
insert = /** @type {ContentEmbed} */ (item.content).embed
|
||||
insert = item.content.getContent()[0]
|
||||
addOp()
|
||||
}
|
||||
} else if (this.deletes(item)) {
|
||||
@ -1008,13 +1007,14 @@ export class YText extends AbstractType {
|
||||
str += /** @type {ContentString} */ (n.content).str
|
||||
break
|
||||
}
|
||||
case ContentType:
|
||||
case ContentEmbed: {
|
||||
packStr()
|
||||
/**
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const op = {
|
||||
insert: /** @type {ContentEmbed} */ (n.content).embed
|
||||
insert: n.content.getContent()[0]
|
||||
}
|
||||
if (currentAttributes.size > 0) {
|
||||
const attrs = /** @type {Object<string,any>} */ ({})
|
||||
@ -1075,16 +1075,13 @@ export class YText extends AbstractType {
|
||||
* Inserts an embed at a index.
|
||||
*
|
||||
* @param {number} index The index to insert the embed at.
|
||||
* @param {Object} embed The Object that represents the embed.
|
||||
* @param {Object | AbstractType<any>} embed The Object that represents the embed.
|
||||
* @param {TextAttributes} attributes Attribute information to apply on the
|
||||
* embed
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
insertEmbed (index, embed, attributes = {}) {
|
||||
if (embed.constructor !== Object) {
|
||||
throw new Error('Embed must be an Object')
|
||||
}
|
||||
const y = this.doc
|
||||
if (y !== null) {
|
||||
transact(y, transaction => {
|
||||
|
@ -40,7 +40,7 @@ export class YEvent {
|
||||
*/
|
||||
this._keys = null
|
||||
/**
|
||||
* @type {null | Array<{ insert?: string | Array<any>, retain?: number, delete?: number, attributes?: Object<string, any> }>}
|
||||
* @type {null | Array<{ insert?: string | Array<any> | object | AbstractType<any>, retain?: number, delete?: number, attributes?: Object<string, any> }>}
|
||||
*/
|
||||
this._delta = null
|
||||
}
|
||||
@ -129,7 +129,7 @@ export class YEvent {
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Array<{insert?: string | Array<any>, retain?: number, delete?: number, attributes?: Object<string, any>}>}
|
||||
* @type {Array<{insert?: string | Array<any> | object | AbstractType<any>, retain?: number, delete?: number, attributes?: Object<string, any>}>}
|
||||
*/
|
||||
get delta () {
|
||||
return this.changes.delta
|
||||
|
@ -362,7 +362,14 @@ export const compare = users => {
|
||||
t.compare(userMapValues[i], userMapValues[i + 1])
|
||||
t.compare(userXmlValues[i], userXmlValues[i + 1])
|
||||
t.compare(userTextValues[i].map(/** @param {any} a */ a => typeof a.insert === 'string' ? a.insert : ' ').join('').length, users[i].getText('text').length)
|
||||
t.compare(userTextValues[i], userTextValues[i + 1])
|
||||
t.compare(userTextValues[i], userTextValues[i + 1], '', (constructor, a, b) => {
|
||||
if (a instanceof Y.AbstractType) {
|
||||
t.compare(a.toJSON(), b.toJSON())
|
||||
} else if (a !== b) {
|
||||
t.fail('Deltas dont match')
|
||||
}
|
||||
return true
|
||||
})
|
||||
t.compare(Y.getStateVector(users[i].store), Y.getStateVector(users[i + 1].store))
|
||||
compareDS(Y.createDeleteSetFromStructStore(users[i].store), Y.createDeleteSetFromStructStore(users[i + 1].store))
|
||||
compareStructStores(users[i].store, users[i + 1].store)
|
||||
|
@ -151,6 +151,29 @@ export const testGetDeltaWithEmbeds = tc => {
|
||||
}])
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
export const testTypesAsEmbed = tc => {
|
||||
const { text0, text1, testConnector } = init(tc, { users: 2 })
|
||||
text0.applyDelta([{
|
||||
insert: new Y.YMap([['key', 'val']])
|
||||
}])
|
||||
t.compare(text0.toDelta()[0].insert.toJSON(), { key: 'val' })
|
||||
let firedEvent = false
|
||||
text1.observe(event => {
|
||||
const d = event.delta
|
||||
t.assert(d.length === 1)
|
||||
t.compare(d.map(x => /** @type {Y.AbstractType<any>} */ (x.insert).toJSON()), [{ key: 'val' }])
|
||||
firedEvent = true
|
||||
})
|
||||
testConnector.flushAllMessages()
|
||||
const delta = text1.toDelta()
|
||||
t.assert(delta.length === 1)
|
||||
t.compare(delta[0].insert.toJSON(), { key: 'val' })
|
||||
t.assert(firedEvent, 'fired the event observer containing a Type-Embed')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
@ -628,7 +651,11 @@ const qChanges = [
|
||||
(y, gen) => { // insert embed
|
||||
const ytext = y.getText('text')
|
||||
const insertPos = prng.int32(gen, 0, ytext.length)
|
||||
ytext.insertEmbed(insertPos, { image: 'https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png' })
|
||||
if (prng.bool(gen)) {
|
||||
ytext.insertEmbed(insertPos, { image: 'https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png' })
|
||||
} else {
|
||||
ytext.insertEmbed(insertPos, new Y.YMap())
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {Y.Doc} y
|
||||
@ -675,8 +702,12 @@ const qChanges = [
|
||||
*/
|
||||
const checkResult = result => {
|
||||
for (let i = 1; i < result.testObjects.length; i++) {
|
||||
const p1 = result.users[i].getText('text').toDelta()
|
||||
const p2 = result.users[i].getText('text').toDelta()
|
||||
/**
|
||||
* @param {any} d
|
||||
*/
|
||||
const typeToObject = d => d.insert instanceof Y.AbstractType ? d.insert.toJSON() : d
|
||||
const p1 = result.users[i].getText('text').toDelta().map(typeToObject)
|
||||
const p2 = result.users[i].getText('text').toDelta().map(typeToObject)
|
||||
t.compare(p1, p2)
|
||||
}
|
||||
// Uncomment this to find formatting-cleanup issues
|
||||
|
Loading…
x
Reference in New Issue
Block a user