Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f34777201 | ||
|
|
24eddb2d75 | ||
|
|
8ce107bd17 | ||
|
|
2d1e3fde43 | ||
|
|
04009f0d42 | ||
|
|
d69d93f812 | ||
|
|
931a37a331 | ||
|
|
84e95f11cb | ||
|
|
164b38f0cd |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.5.25",
|
"version": "13.5.27",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "yjs",
|
"name": "yjs",
|
||||||
"version": "13.5.25",
|
"version": "13.5.27",
|
||||||
"description": "Shared Editing Library",
|
"description": "Shared Editing Library",
|
||||||
"main": "./dist/yjs.cjs",
|
"main": "./dist/yjs.cjs",
|
||||||
"module": "./dist/yjs.mjs",
|
"module": "./dist/yjs.mjs",
|
||||||
|
|||||||
@@ -338,14 +338,16 @@ const formatText = (transaction, parent, currPos, length, attributes) => {
|
|||||||
*
|
*
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
* @param {Item} start
|
* @param {Item} start
|
||||||
* @param {Item|null} end exclusive end, automatically iterates to the next Content Item
|
* @param {Item|null} curr exclusive end, automatically iterates to the next Content Item
|
||||||
* @param {Map<string,any>} startAttributes
|
* @param {Map<string,any>} startAttributes
|
||||||
* @param {Map<string,any>} endAttributes This attribute is modified!
|
* @param {Map<string,any>} currAttributes
|
||||||
* @return {number} The amount of formatting Items deleted.
|
* @return {number} The amount of formatting Items deleted.
|
||||||
*
|
*
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
const cleanupFormattingGap = (transaction, start, end, startAttributes, endAttributes) => {
|
const cleanupFormattingGap = (transaction, start, curr, startAttributes, currAttributes) => {
|
||||||
|
let end = curr
|
||||||
|
const endAttributes = map.copy(currAttributes)
|
||||||
while (end && (!end.countable || end.deleted)) {
|
while (end && (!end.countable || end.deleted)) {
|
||||||
if (!end.deleted && end.content.constructor === ContentFormat) {
|
if (!end.deleted && end.content.constructor === ContentFormat) {
|
||||||
updateCurrentAttributes(endAttributes, /** @type {ContentFormat} */ (end.content))
|
updateCurrentAttributes(endAttributes, /** @type {ContentFormat} */ (end.content))
|
||||||
@@ -353,7 +355,11 @@ const cleanupFormattingGap = (transaction, start, end, startAttributes, endAttri
|
|||||||
end = end.right
|
end = end.right
|
||||||
}
|
}
|
||||||
let cleanups = 0
|
let cleanups = 0
|
||||||
|
let reachedEndOfCurr = false
|
||||||
while (start !== end) {
|
while (start !== end) {
|
||||||
|
if (curr === start) {
|
||||||
|
reachedEndOfCurr = true
|
||||||
|
}
|
||||||
if (!start.deleted) {
|
if (!start.deleted) {
|
||||||
const content = start.content
|
const content = start.content
|
||||||
switch (content.constructor) {
|
switch (content.constructor) {
|
||||||
@@ -363,6 +369,9 @@ const cleanupFormattingGap = (transaction, start, end, startAttributes, endAttri
|
|||||||
// Either this format is overwritten or it is not necessary because the attribute already existed.
|
// Either this format is overwritten or it is not necessary because the attribute already existed.
|
||||||
start.delete(transaction)
|
start.delete(transaction)
|
||||||
cleanups++
|
cleanups++
|
||||||
|
if (!reachedEndOfCurr && (currAttributes.get(key) || null) === value && (startAttributes.get(key) || null) !== value) {
|
||||||
|
currAttributes.delete(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -465,7 +474,7 @@ const deleteText = (transaction, currPos, length) => {
|
|||||||
currPos.forward()
|
currPos.forward()
|
||||||
}
|
}
|
||||||
if (start) {
|
if (start) {
|
||||||
cleanupFormattingGap(transaction, start, currPos.right, startAttrs, map.copy(currPos.currentAttributes))
|
cleanupFormattingGap(transaction, start, currPos.right, startAttrs, currPos.currentAttributes)
|
||||||
}
|
}
|
||||||
const parent = /** @type {AbstractType<any>} */ (/** @type {Item} */ (currPos.left || currPos.right).parent)
|
const parent = /** @type {AbstractType<any>} */ (/** @type {Item} */ (currPos.left || currPos.right).parent)
|
||||||
if (parent._searchMarker) {
|
if (parent._searchMarker) {
|
||||||
@@ -684,7 +693,7 @@ export class YTextEvent extends YEvent {
|
|||||||
} else {
|
} else {
|
||||||
attributes[key] = value
|
attributes[key] = value
|
||||||
}
|
}
|
||||||
} else {
|
} else if (value !== null) {
|
||||||
item.delete(transaction)
|
item.delete(transaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -710,7 +719,7 @@ export class YTextEvent extends YEvent {
|
|||||||
} else {
|
} else {
|
||||||
attributes[key] = value
|
attributes[key] = value
|
||||||
}
|
}
|
||||||
} else {
|
} else if (attr !== null) { // this will be cleaned up automatically by the contextless cleanup function
|
||||||
item.delete(transaction)
|
item.delete(transaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ export class UndoManager extends Observable {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const now = time.getUnixTime()
|
const now = time.getUnixTime()
|
||||||
|
let didAdd = false
|
||||||
if (now - this.lastChange < captureTimeout && stack.length > 0 && !undoing && !redoing) {
|
if (now - this.lastChange < captureTimeout && stack.length > 0 && !undoing && !redoing) {
|
||||||
// append change to last stack op
|
// append change to last stack op
|
||||||
const lastOp = stack[stack.length - 1]
|
const lastOp = stack[stack.length - 1]
|
||||||
@@ -194,6 +195,7 @@ export class UndoManager extends Observable {
|
|||||||
} else {
|
} else {
|
||||||
// create a new stack op
|
// create a new stack op
|
||||||
stack.push(new StackItem(transaction.deleteSet, insertions))
|
stack.push(new StackItem(transaction.deleteSet, insertions))
|
||||||
|
didAdd = true
|
||||||
}
|
}
|
||||||
if (!undoing && !redoing) {
|
if (!undoing && !redoing) {
|
||||||
this.lastChange = now
|
this.lastChange = now
|
||||||
@@ -204,7 +206,9 @@ export class UndoManager extends Observable {
|
|||||||
keepItem(item, true)
|
keepItem(item, true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.emit('stack-item-added', [{ stackItem: stack[stack.length - 1], origin: transaction.origin, type: undoing ? 'redo' : 'undo', changedParentTypes: transaction.changedParentTypes }, this])
|
if (didAdd) {
|
||||||
|
this.emit('stack-item-added', [{ stackItem: stack[stack.length - 1], origin: transaction.origin, type: undoing ? 'redo' : 'undo', changedParentTypes: transaction.changedParentTypes }, this])
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import * as math from 'lib0/math'
|
|||||||
const { init, compare } = Y
|
const { init, compare } = Y
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* In this test we are mainly interested in the cleanup behavior and whether the resulting delta makes sense.
|
||||||
|
* It is fine if the resulting delta is not minimal. But applying the delta to a rich-text editor should result in a
|
||||||
|
* synced document.
|
||||||
|
*
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testDeltaAfterConcurrentFormatting = tc => {
|
export const testDeltaAfterConcurrentFormatting = tc => {
|
||||||
@@ -14,12 +18,17 @@ export const testDeltaAfterConcurrentFormatting = tc => {
|
|||||||
testConnector.flushAllMessages()
|
testConnector.flushAllMessages()
|
||||||
text0.format(0, 3, { bold: true })
|
text0.format(0, 3, { bold: true })
|
||||||
text1.format(2, 2, { bold: true })
|
text1.format(2, 2, { bold: true })
|
||||||
let delta = null
|
/**
|
||||||
|
* @type {any}
|
||||||
|
*/
|
||||||
|
const deltas = []
|
||||||
text1.observe(event => {
|
text1.observe(event => {
|
||||||
delta = event.delta
|
if (event.delta.length > 0) {
|
||||||
|
deltas.push(event.delta)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
testConnector.flushAllMessages()
|
testConnector.flushAllMessages()
|
||||||
t.compare(delta, [])
|
t.compare(deltas, [[{ retain: 3, attributes: { bold: true } }, { retain: 2, attributes: { bold: null } }]])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,6 +147,28 @@ export const testNotMergeEmptyLinesFormat = tc => {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testPreserveAttributesThroughDelete = tc => {
|
||||||
|
const ydoc = new Y.Doc()
|
||||||
|
const testText = ydoc.getText('test')
|
||||||
|
testText.applyDelta([
|
||||||
|
{ insert: 'Text' },
|
||||||
|
{ insert: '\n', attributes: { title: true } },
|
||||||
|
{ insert: '\n' }
|
||||||
|
])
|
||||||
|
testText.applyDelta([
|
||||||
|
{ retain: 4 },
|
||||||
|
{ delete: 1 },
|
||||||
|
{ retain: 1, attributes: { title: true } }
|
||||||
|
])
|
||||||
|
t.compare(testText.toDelta(), [
|
||||||
|
{ insert: 'Text' },
|
||||||
|
{ insert: '\n', attributes: { title: true } }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user