mirror of
https://github.com/yjs/yjs.git
synced 2026-02-23 19:49:59 +01:00
[AttributionManager] further improve performance to be almost on-par with toString
This commit is contained in:
@@ -1006,85 +1006,91 @@ export class YText extends AbstractType {
|
||||
getContent (am = noAttributionsManager) {
|
||||
this.doc ?? warnPrematureAccess()
|
||||
const d = delta.create()
|
||||
for (let item = this._start; item !== null; item = item.right) {
|
||||
const cs = am.getContent(item)
|
||||
if (cs != null) {
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
const { content, deleted, attrs } = cs[i]
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').Attribution?}
|
||||
*/
|
||||
let attributions = null
|
||||
if (attrs != null) {
|
||||
attributions = {}
|
||||
if (deleted) {
|
||||
attributions.delete = []
|
||||
} else {
|
||||
attributions.insert = []
|
||||
}
|
||||
attrs.forEach(attr => {
|
||||
switch (attr.name) {
|
||||
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
|
||||
}
|
||||
default: {
|
||||
if (attr.name[0] !== '_') {
|
||||
/** @type {any} */ (attributions)[attr.name] = attr.val
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
/**
|
||||
* @type {Array<import('../internals.js').AttributedContent<any>>}
|
||||
*/
|
||||
const cs = []
|
||||
for (let item = this._start; item !== null; cs.length = 0) {
|
||||
// populate cs
|
||||
for (; item !== null && cs.length < 50; item = item.right) {
|
||||
am.readContent(cs, item)
|
||||
}
|
||||
for (let i = 0; i < cs.length; i++) {
|
||||
const { content, deleted, attrs } = cs[i]
|
||||
/**
|
||||
* @type {import('../utils/Delta.js').Attribution?}
|
||||
*/
|
||||
let attributions = null
|
||||
if (attrs != null) {
|
||||
attributions = {}
|
||||
if (deleted) {
|
||||
attributions.delete = []
|
||||
} else {
|
||||
attributions.insert = []
|
||||
}
|
||||
switch (content.constructor) {
|
||||
case ContentString: {
|
||||
d.insert(/** @type {ContentString} */ (content).str, null, attributions)
|
||||
break
|
||||
}
|
||||
case ContentType:
|
||||
case ContentEmbed: {
|
||||
d.insert(/** @type {ContentEmbed | ContentType} */ (content).getContent()[0], null, attributions)
|
||||
break
|
||||
}
|
||||
case ContentFormat:
|
||||
const contentFormat = /** @type {ContentFormat} */ (content)
|
||||
if (attributions != null) {
|
||||
/**
|
||||
* @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 {
|
||||
const attributedAt = (deleted ? attributions.deletedAt : attributions.insertedAt)
|
||||
if (attributedAt != null) formattingAttributions.attributedAt = attributedAt
|
||||
d.useAttribution(formattingAttributions)
|
||||
attrs.forEach(attr => {
|
||||
switch (attr.name) {
|
||||
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
|
||||
}
|
||||
default: {
|
||||
if (attr.name[0] !== '_') {
|
||||
/** @type {any} */ (attributions)[attr.name] = attr.val
|
||||
}
|
||||
}
|
||||
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 }))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
switch (content.constructor) {
|
||||
case ContentString: {
|
||||
d.insert(/** @type {ContentString} */ (content).str, null, attributions)
|
||||
break
|
||||
}
|
||||
case ContentType:
|
||||
case ContentEmbed: {
|
||||
d.insert(/** @type {ContentEmbed | ContentType} */ (content).getContent()[0], null, attributions)
|
||||
break
|
||||
}
|
||||
case ContentFormat: {
|
||||
const contentFormat = /** @type {ContentFormat} */ (content)
|
||||
if (attributions != null) {
|
||||
/**
|
||||
* @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
|
||||
}
|
||||
break
|
||||
if (object.isEmpty(attributesChanged)) {
|
||||
d.useAttribution(null)
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ export class AttributedContent {
|
||||
*/
|
||||
export class AbstractAttributionManager {
|
||||
/**
|
||||
* @param {Array<AttributedContent<any>>} _contents
|
||||
* @param {Item} _item
|
||||
* @return {Array<AttributedContent<any>>?}
|
||||
*/
|
||||
getContent (_item) {
|
||||
readContent (_contents, _item) {
|
||||
error.methodUnimplemented()
|
||||
}
|
||||
}
|
||||
@@ -49,24 +49,22 @@ export class TwosetAttributionManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<AttributedContent<any>>} contents
|
||||
* @param {Item} item
|
||||
* @return {Array<AttributedContent<any>>}
|
||||
*/
|
||||
getContent (item) {
|
||||
readContent (contents, item) {
|
||||
const deleted = item.deleted
|
||||
const slice = (deleted ? this.deletes : this.inserts).slice(item.id, item.length)
|
||||
let content = slice.length === 1 ? item.content : item.content.copy()
|
||||
let res = slice.map(s => {
|
||||
slice.forEach(s => {
|
||||
const c = content
|
||||
if (s.len < c.getLength()) {
|
||||
content = c.splice(s.len)
|
||||
}
|
||||
return new AttributedContent(c, deleted, s.attrs)
|
||||
if (!deleted || s.attrs != null) {
|
||||
contents.push(new AttributedContent(c, deleted, s.attrs))
|
||||
}
|
||||
})
|
||||
if (deleted) {
|
||||
res = res.filter(s => s.attrs != null)
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,11 +75,13 @@ export class TwosetAttributionManager {
|
||||
*/
|
||||
export class NoAttributionsManager {
|
||||
/**
|
||||
* @param {Array<AttributedContent<any>>} contents
|
||||
* @param {Item} item
|
||||
* @return {Array<AttributedContent<any>>?}
|
||||
*/
|
||||
getContent (item) {
|
||||
return item.deleted ? null : [new AttributedContent(item.content, false, null)]
|
||||
readContent (contents, item) {
|
||||
if (!item.deleted) {
|
||||
contents.push(new AttributedContent(item.content, false, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ export class DeltaBuilder extends Delta {
|
||||
*/
|
||||
useAttributes (attributes) {
|
||||
if (this.usedAttributes === attributes) return this
|
||||
this.usedAttributes = attributes ?? object.assign({}, attributes)
|
||||
this.usedAttributes = attributes && (object.isEmpty(attributes) ? null : object.assign({}, attributes))
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ export class DeltaBuilder extends Delta {
|
||||
*/
|
||||
useAttribution (attribution) {
|
||||
if (this.usedAttribution === attribution) return this
|
||||
this.usedAttribution = attribution ?? object.assign({}, attribution)
|
||||
this.usedAttribution = attribution && (object.isEmpty(attribution) ? null : object.assign({}, attribution))
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user