mirror of
https://github.com/yjs/yjs.git
synced 2026-02-23 19:49:59 +01:00
attribution tests for adding and removing attributes
This commit is contained in:
@@ -1011,18 +1011,22 @@ export class YText extends AbstractType {
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
const { content, deleted, attrs } = cs[i]
|
||||
/**
|
||||
* @type {{ [key: string]: any }?}
|
||||
* @type {import('../utils/Delta.js').Attribution?}
|
||||
*/
|
||||
let attributions = null
|
||||
if (attrs != null) {
|
||||
attributions = {}
|
||||
attributions.changeType = deleted ? 'delete' : 'insert'
|
||||
if (deleted) {
|
||||
attributions.delete = []
|
||||
} else {
|
||||
attributions.insert = []
|
||||
}
|
||||
attrs.forEach(attr => {
|
||||
switch (attr.name) {
|
||||
case 'insertedBy':
|
||||
case 'deletedBy':
|
||||
case 'suggestedBy': {
|
||||
const as = /** @type {any} */ (attributions)
|
||||
case 'insert':
|
||||
case 'delete':
|
||||
case 'suggest': {
|
||||
const as = /** @type {import('../utils/Delta.js').Attribution} */ (attributions)
|
||||
const ls = as[attr.name] = as[attr.name] ?? []
|
||||
ls.push(attr.val)
|
||||
break
|
||||
@@ -1046,15 +1050,37 @@ export class YText extends AbstractType {
|
||||
break
|
||||
}
|
||||
case ContentFormat:
|
||||
const contentFormat = /** @type {ContentFormat} */ (content)
|
||||
if (attributions != null) {
|
||||
if (deleted) {
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').Attribution}
|
||||
*/
|
||||
const formattingAttributions = object.assign({}, d.usedAttribution)
|
||||
const attributesChanged = /** @type {{ [key: string]: Array<any> }} */ (formattingAttributions.attributes = object.assign({}, formattingAttributions.attributes ?? {}))
|
||||
if (contentFormat.value === null) {
|
||||
delete attributesChanged[contentFormat.key]
|
||||
} else {
|
||||
const by = attributesChanged[contentFormat.key] = attributesChanged[contentFormat.key]?.slice() ?? []
|
||||
by.push(...((deleted ? attributions.delete : attributions.insert) ?? []))
|
||||
const attributedAt = (deleted ? attributions.deletedAt : attributions.insertedAt)
|
||||
if (attributedAt) formattingAttributions.attributedAt = attributedAt
|
||||
}
|
||||
if (object.isEmpty(attributesChanged)) {
|
||||
d.useAttribution(null)
|
||||
} else {
|
||||
attributions.formattedBy = (deleted ? attributions.deletedBy : attributions.insertedBy) ?? []
|
||||
attributions.changeType = 'format'
|
||||
delete attributions.deletedBy
|
||||
delete attributions.insertedBy
|
||||
d.useAttribution(attributions)
|
||||
const attributedAt = (deleted ? attributions.deletedAt : attributions.insertedAt)
|
||||
if (attributedAt != null) formattingAttributions.attributedAt = attributedAt
|
||||
d.useAttribution(formattingAttributions)
|
||||
}
|
||||
}
|
||||
if (!deleted) {
|
||||
const currAttrs = d.usedAttributes
|
||||
if (contentFormat.value == null) {
|
||||
const nextAttrs = object.assign({}, currAttrs)
|
||||
delete nextAttrs[contentFormat.key]
|
||||
d.useAttributes(nextAttrs)
|
||||
} else {
|
||||
d.useAttributes(object.assign({}, currAttrs, { [contentFormat.key]: contentFormat.value }))
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
@@ -10,13 +10,15 @@ import * as fun from 'lib0/function'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @todo specify this better
|
||||
*
|
||||
* @typedef {Object} Attribution
|
||||
* @property {boolean} [Attribution.isDeleted]
|
||||
* @property {boolean} [Attribution.isAdded]
|
||||
* @property {string} [Attribution.creator]
|
||||
* @property {number} [Attribution.timestamp]
|
||||
* @property {Array<any>} [Attribution.insert]
|
||||
* @property {number} [Attribution.insertedAt]
|
||||
* @property {Array<any>} [Attribution.suggest]
|
||||
* @property {number} [Attribution.suggestedAt]
|
||||
* @property {Array<any>} [Attribution.delete]
|
||||
* @property {number} [Attribution.deletedAt]
|
||||
* @property {{ [key: string]: Array<any> }} [Attribution.attributes]
|
||||
* @property {number} [Attribution.attributedAt]
|
||||
*/
|
||||
|
||||
export class InsertOp {
|
||||
@@ -136,15 +138,13 @@ export class DeltaBuilder extends Delta {
|
||||
constructor () {
|
||||
super()
|
||||
/**
|
||||
* @private
|
||||
* @type {FormattingAttributes?}
|
||||
*/
|
||||
this._useAttributes = null
|
||||
this.usedAttributes = null
|
||||
/**
|
||||
* @private
|
||||
* @type {Attribution?}
|
||||
*/
|
||||
this._useAttribution = null
|
||||
this.usedAttribution = null
|
||||
/**
|
||||
* @private
|
||||
* @type {DeltaOp?}
|
||||
@@ -157,8 +157,8 @@ export class DeltaBuilder extends Delta {
|
||||
* @return {this}
|
||||
*/
|
||||
useAttributes (attributes) {
|
||||
if (this._useAttributes === attributes) return this
|
||||
this._useAttributes = attributes && object.assign({}, attributes)
|
||||
if (this.usedAttributes === attributes) return this
|
||||
this.usedAttributes = attributes && object.assign({}, attributes)
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -166,8 +166,8 @@ export class DeltaBuilder extends Delta {
|
||||
* @param {Attribution?} attribution
|
||||
*/
|
||||
useAttribution (attribution) {
|
||||
if (this._useAttribution === attribution) return this
|
||||
this._useAttribution = attribution && object.assign({}, attribution)
|
||||
if (this.usedAttribution === attribution) return this
|
||||
this.usedAttribution = attribution && object.assign({}, attribution)
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -178,8 +178,8 @@ export class DeltaBuilder extends Delta {
|
||||
* @return {this}
|
||||
*/
|
||||
insert (insert, attributes = null, attribution = null) {
|
||||
const mergedAttributes = mergeAttrs(this._useAttributes, attributes)
|
||||
const mergedAttribution = mergeAttrs(this._useAttribution, attribution)
|
||||
const mergedAttributes = mergeAttrs(this.usedAttributes, attributes)
|
||||
const mergedAttribution = mergeAttrs(this.usedAttribution, attribution)
|
||||
if (this._lastOp instanceof InsertOp && fun.equalityDeep(mergedAttributes, this._lastOp.attributes) && fun.equalityDeep(mergedAttribution, this._lastOp.attribution)) {
|
||||
this._lastOp.insert += insert
|
||||
} else {
|
||||
@@ -195,8 +195,8 @@ export class DeltaBuilder extends Delta {
|
||||
* @return {this}
|
||||
*/
|
||||
retain (retain, attributes = null, attribution = null) {
|
||||
const mergedAttributes = mergeAttrs(this._useAttributes, attributes)
|
||||
const mergedAttribution = mergeAttrs(this._useAttribution, attribution)
|
||||
const mergedAttributes = mergeAttrs(this.usedAttributes, attributes)
|
||||
const mergedAttribution = mergeAttrs(this.usedAttribution, attribution)
|
||||
if (this._lastOp instanceof RetainOp && fun.equalityDeep(mergedAttributes, this._lastOp.attributes) && fun.equalityDeep(mergedAttribution, this._lastOp.attribution)) {
|
||||
this._lastOp.retain += retain
|
||||
} else {
|
||||
|
||||
@@ -121,6 +121,8 @@ export class AttrRange {
|
||||
export const createMaybeAttrRange = (clock, len, attrs) => new AttrRange(clock, len, /** @type {any} */ (attrs))
|
||||
|
||||
/**
|
||||
* Whenever this is instantiated, it must receive a fresh array of ops, not something copied.
|
||||
*
|
||||
* @template Attrs
|
||||
*/
|
||||
export class AttrRanges {
|
||||
|
||||
@@ -2302,9 +2302,9 @@ export const testDeleteFormatting = _tc => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} tc
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testAttributedContent = tc => {
|
||||
export const testAttributedContent = _tc => {
|
||||
const ydoc = new Y.Doc({ gc: false })
|
||||
const ytext = ydoc.getText()
|
||||
ytext.insert(0, 'Hello World!')
|
||||
@@ -2312,11 +2312,20 @@ export const testAttributedContent = tc => {
|
||||
ydoc.on('afterTransaction', tr => {
|
||||
am = new TwosetAttributionManager(createIdMapFromIdSet(tr.insertSet, []), createIdMapFromIdSet(tr.deleteSet, []))
|
||||
})
|
||||
ytext.applyDelta([{ retain: 6 }, { delete: 5 }, { insert: 'attributions' }])
|
||||
const attributedContent = ytext.getContent(am)
|
||||
const expectedContent = delta.create().insert('Hello ').insert('World', {}, { changeType: 'delete' }).insert('attributions', {}, { changeType: 'insert' }).insert('!')
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
debugger
|
||||
t.group('insert / delete / format', () => {
|
||||
ytext.applyDelta([{ retain: 4, attributes: { italic: true } }, { retain: 2 }, { delete: 5 }, { insert: 'attributions' }])
|
||||
let expectedContent = delta.create().insert('Hell', { italic: true }, { attributes: { italic: [] } }).insert('o ').insert('World', {}, { delete: [] }).insert('attributions', {}, { insert: [] }).insert('!')
|
||||
let attributedContent = ytext.getContent(am)
|
||||
console.log(attributedContent.toJSON().ops)
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
})
|
||||
t.group('unformat', () => {
|
||||
ytext.applyDelta([{retain: 5, attributes: { italic: null }}])
|
||||
let expectedContent = delta.create().insert('Hell', null, { attributes: { italic: [] } }).insert('o attributions!')
|
||||
let attributedContent = ytext.getContent(am)
|
||||
console.log(attributedContent.toJSON().ops)
|
||||
t.assert(attributedContent.equals(expectedContent))
|
||||
})
|
||||
}
|
||||
|
||||
// RANDOM TESTS
|
||||
|
||||
Reference in New Issue
Block a user