Compare commits

...

14 Commits

Author SHA1 Message Date
Kevin Jahns
f4c919d9ec 13.0.0-101 2019-10-08 18:33:50 +02:00
Kevin Jahns
aeb23dbaa9 follow redone items to prevent some undo-redo issues. Fixes #162 2019-10-08 18:31:56 +02:00
Kevin Jahns
6d4f0c0cdd 13.0.0-100 2019-10-08 17:40:32 +02:00
Kevin Jahns
303138f309 sanitize items before undoing. fixes #165 2019-10-08 17:36:00 +02:00
Kevin Jahns
ad373a3dce Merge pull request #172 from istvank/patch-1
Fixing Y.Map's documentation of forEach
2019-10-05 20:09:53 +02:00
István Koren
2150fa58f2 Fixing Y.Map's documentation of forEach
fixes #171 As always, it's an honor to submit a PR! 🐒 There was also a missing dot in the Y.XmlFragment title.
2019-10-05 15:14:30 +02:00
Kevin Jahns
ece4841b5c update stackItem.meta doc 2019-10-03 22:06:07 +02:00
Kevin Jahns
8103220c05 Merge branch 'master' of github.com:yjs/yjs 2019-09-30 11:10:13 +02:00
Kevin Jahns
66d500f08d YEvent: consider case that item was added & removed in the same transaction 2019-09-30 11:10:03 +02:00
Kevin Jahns
5f8e7c7ba7 Merge pull request #169 from yjs/improve-readme
update quill cursors support
2019-09-23 11:22:51 +02:00
Nik Graf
7b8eee6b25 update quill cursors support 2019-09-23 11:22:24 +02:00
Kevin Jahns
1d5947c602 13.0.0-99 2019-09-23 11:11:45 +02:00
Kevin Jahns
53e4028952 Merge pull request #168 from yjs/fix-absolute-position-calculation
fix absolute position calculation
2019-09-23 11:09:48 +02:00
Nik Graf
b38a8d99e5 fix absolute position calculation 2019-09-23 11:05:50 +02:00
8 changed files with 69 additions and 17 deletions

View File

@@ -55,7 +55,7 @@ are implemented in separate modules.
| Name | Cursors | Binding | Demo |
|---|:-:|---|---|
| [ProseMirror](https://prosemirror.net/)                                                   | ✔ | [y-prosemirror](http://github.com/yjs/y-prosemirror) | [demo](https://yjs-demos.now.sh/prosemirror/) |
| [Quill](https://quilljs.com/) | | [y-quill](http://github.com/yjs/y-quill) | [demo](https://yjs-demos.now.sh/quill/) |
| [Quill](https://quilljs.com/) | | [y-quill](http://github.com/yjs/y-quill) | [demo](https://yjs-demos.now.sh/quill/) |
| [CodeMirror](https://codemirror.net/) | ✔ | [y-codemirror](http://github.com/yjs/y-codemirror) | [demo](https://yjs-demos.now.sh/codemirror/) |
| [Monaco](https://microsoft.github.io/monaco-editor/) | ✔ | [y-monaco](http://github.com/yjs/y-monaco) | [demo](https://yjs-demos.now.sh/monaco/) |
| [Ace](https://ace.c9.io/) | | [y-ace](http://github.com/yjs/y-ace) | [demo](https://yjs-demos.now.sh/ace/) |
@@ -235,7 +235,8 @@ or any of its children.
Copies the <code>[key,value]</code> pairs of this YMap to a new Object.It
transforms all child types to JSON using their <code>toJSON</code> method.
</dd>
<b><code>forEach(function(key:string,value:object|boolean|Array|string|number|Uint8Array|Y.Type))</code></b>
<b><code>forEach(function(value:object|boolean|Array|string|number|Uint8Array|Y.Type,
key:string, map: Y.Map))</code></b>
<dd>
Execute the provided function once for every key-value pair.
</dd>
@@ -343,7 +344,7 @@ or any of its children.
</details>
<details>
<summary><b>YXmlFragment</b></summary>
<summary><b>Y.XmlFragment</b></summary>
<br>
<p>
A container that holds an Array of Y.XmlElements.

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "yjs",
"version": "13.0.0-98",
"version": "13.0.0-101",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "yjs",
"version": "13.0.0-98",
"version": "13.0.0-101",
"description": "Shared Editing Library",
"main": "./dist/yjs.js",
"module": "./dist/yjs.mjs",

View File

@@ -35,6 +35,8 @@ import * as set from 'lib0/set.js'
import * as binary from 'lib0/binary.js'
/**
* @todo This should return several items
*
* @param {StructStore} store
* @param {ID} id
* @return {{item:Item, diff:number}}

View File

@@ -228,7 +228,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc) => {
return null
}
type = right.parent
if (type._item !== null && !type._item.deleted) {
if (type._item === null || !type._item.deleted) {
index = right.deleted || !right.countable ? 0 : res.diff
let n = right.left
while (n !== null) {

View File

@@ -9,6 +9,7 @@ import {
createID,
followRedone,
getItemCleanStart,
getState,
Transaction, Doc, Item, GC, DeleteSet, AbstractType // eslint-disable-line
} from '../internals.js'
@@ -49,35 +50,53 @@ const popStackItem = (undoManager, stack, eventType) => {
transact(doc, transaction => {
while (stack.length > 0 && result === null) {
const store = doc.store
const clientID = doc.clientID
const stackItem = /** @type {StackItem} */ (stack.pop())
const stackStartClock = stackItem.start
const stackEndClock = stackItem.start + stackItem.len
const itemsToRedo = new Set()
// @todo iterateStructs should not need the structs parameter
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(clientID))
let performedChange = false
if (stackStartClock !== stackEndClock) {
// make sure structs don't overlap with the range of created operations [stackItem.start, stackItem.start + stackItem.end)
getItemCleanStart(transaction, createID(clientID, stackStartClock))
if (stackEndClock < getState(doc.store, clientID)) {
getItemCleanStart(transaction, createID(clientID, stackEndClock))
}
}
iterateDeletedStructs(transaction, stackItem.ds, struct => {
if (struct instanceof Item && scope.some(type => isParentOf(type, struct))) {
if (
struct instanceof Item &&
scope.some(type => isParentOf(type, struct)) &&
// Never redo structs in [stackItem.start, stackItem.start + stackItem.end) because they were created and deleted in the same capture interval.
!(struct.id.client === clientID && struct.id.clock >= stackStartClock && struct.id.clock < stackEndClock)
) {
itemsToRedo.add(struct)
}
})
itemsToRedo.forEach(item => {
performedChange = redoItem(transaction, item, itemsToRedo) !== null || performedChange
itemsToRedo.forEach(struct => {
performedChange = redoItem(transaction, struct, itemsToRedo) !== null || performedChange
})
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(doc.clientID))
/**
* @type {Array<Item>}
*/
const itemsToDelete = []
iterateStructs(transaction, structs, stackItem.start, stackItem.len, struct => {
if (struct instanceof Item && !struct.deleted && scope.some(type => isParentOf(type, /** @type {Item} */ (struct)))) {
iterateStructs(transaction, structs, stackStartClock, stackItem.len, struct => {
if (struct instanceof Item) {
if (struct.redone !== null) {
let { item, diff } = followRedone(store, struct.id)
if (diff > 0) {
item = getItemCleanStart(transaction, createID(item.id.client, item.id.clock + diff))
}
if (item.length > stackItem.len) {
getItemCleanStart(transaction, createID(item.id.client, item.id.clock + stackItem.len))
getItemCleanStart(transaction, createID(item.id.client, stackEndClock))
}
struct = item
}
itemsToDelete.push(struct)
if (!struct.deleted && scope.some(type => isParentOf(type, /** @type {Item} */ (struct)))) {
itemsToDelete.push(struct)
}
}
})
// We want to delete in reverse order so that children are deleted before
@@ -111,9 +130,9 @@ const popStackItem = (undoManager, stack, eventType) => {
/**
* Fires 'stack-item-added' event when a stack item was added to either the undo- or
* the redo-stack. You may store additional stack information via the
* metadata property on `event.stackItem.metadata` (it is a `Map` of metadata properties).
* metadata property on `event.stackItem.meta` (it is a `Map` of metadata properties).
* Fires 'stack-item-popped' event when a stack item was popped from either the
* undo- or the redo-stack. You may restore the saved stack information from `event.stackItem.metadata`.
* undo- or the redo-stack. You may restore the saved stack information from `event.stackItem.meta`.
*
* @extends {Observable<'stack-item-added'|'stack-item-popped'>}
*/

View File

@@ -56,6 +56,8 @@ export class YEvent {
/**
* Check if a struct is deleted by this event.
*
* In contrast to change.deleted, this method also returns true if the struct was added and then deleted.
*
* @param {AbstractStruct} struct
* @return {boolean}
*/
@@ -66,6 +68,8 @@ export class YEvent {
/**
* Check if a struct is added by this event.
*
* In contrast to change.deleted, this method also returns true if the struct was added and then deleted.
*
* @param {AbstractStruct} struct
* @return {boolean}
*/
@@ -106,7 +110,7 @@ export class YEvent {
}
for (let item = target._start; item !== null; item = item.right) {
if (item.deleted) {
if (this.deletes(item)) {
if (this.deletes(item) && !this.adds(item)) {
if (lastOp === null || lastOp.delete === undefined) {
packOp()
lastOp = { delete: 0 }

View File

@@ -13,6 +13,23 @@ import * as t from 'lib0/testing.js'
export const testUndoText = tc => {
const { testConnector, text0, text1 } = init(tc, { users: 3 })
const undoManager = new UndoManager(text0)
// items that are added & deleted in the same transaction won't be undo
text0.insert(0, 'test')
text0.delete(0, 4)
undoManager.undo()
t.assert(text0.toString() === '')
// follow redone items
text0.insert(0, 'a')
undoManager.stopCapturing()
text0.delete(0, 1)
undoManager.stopCapturing()
undoManager.undo()
t.assert(text0.toString() === 'a')
undoManager.undo()
t.assert(text0.toString() === '')
text0.insert(0, 'abc')
text1.insert(0, 'xyz')
testConnector.syncAll()
@@ -65,6 +82,15 @@ export const testUndoMap = tc => {
t.assert(map0.get('a') === 44)
undoManager.redo()
t.assert(map0.get('a') === 44)
// test setting value multiple times
map0.set('b', 'initial')
undoManager.stopCapturing()
map0.set('b', 'val1')
map0.set('b', 'val2')
undoManager.stopCapturing()
undoManager.undo()
t.assert(map0.get('b') === 'initial')
}
/**