Compare commits

..

16 Commits

Author SHA1 Message Date
Kevin Jahns
55f0abcdfc v13.0.0-54 -- distribution files 2018-03-01 16:45:38 +01:00
Kevin Jahns
079de07eff 13.0.0-54 2018-03-01 16:45:25 +01:00
Kevin Jahns
54453e87fa fix consecutive undo,redo,undo,redo.. (abc test) 2018-03-01 16:44:26 +01:00
Kevin Jahns
1b0e3659c3 undo fixes for consecutive undo-redo 2018-03-01 13:50:01 +01:00
Kevin Jahns
641f426339 13.0.0-53 2018-02-25 02:31:59 +01:00
Kevin Jahns
fcbca65d8f fromBinary is a transaction 2018-02-25 02:31:20 +01:00
Kevin Jahns
5f8ae0dd43 13.0.0-52 2018-02-18 19:20:00 +01:00
Kevin Jahns
de14fe0f3e fix getAttribute vs attributes.value fixes y-js/y-xml#8 2018-02-18 18:58:49 +01:00
Kevin Jahns
5e4b071693 actually use clock in undo-manager 2018-02-15 18:58:43 +01:00
Kevin Jahns
937de2c59f fix fast undo-redo bug 2018-02-15 18:28:53 +01:00
Kevin Jahns
f1f1bff901 preliminary undo-redo fixes 2018-02-15 17:58:14 +01:00
Kevin Jahns
4855b2d590 13.0.0-51 2018-02-07 14:08:43 +01:00
Kevin Jahns
908ce31e2f Merge branch 'master' of github.com:y-js/yjs 2018-02-07 14:08:07 +01:00
Kevin Jahns
e4d4c23f0b bugfix - persist deletes when syncing 2018-02-07 14:07:57 +01:00
Kevin Jahns
fc500a8247 Merge pull request #94 from LukasDrgon/patch-3
Add CDN usage
2018-01-31 20:36:26 -08:00
Lukas Drgon
acb0affa33 Add CDN usage 2018-01-17 22:03:49 +01:00
16 changed files with 227 additions and 162 deletions

View File

@@ -64,6 +64,19 @@ missing modules.
<script src="./bower_components/yjs/y.js"></script>
```
### CDN
```
<script src="https://cdn.jsdelivr.net/npm/yjs@12/src/y.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-array@10/dist/y-array.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-websockets-client@8/dist/y-websockets-client.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-memory@8/dist/y-memory.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-array@10/dist/y-array.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-map@10/dist/y-map.js"></script>
<script src="https://cdn.jsdelivr.net/npm/y-text@9/dist/y-text.js"></script>
// ..
// do the same for all modules you want to use
```
### Npm
```
npm install --save yjs % add all y-* modules you want to use

View File

@@ -18,7 +18,7 @@ window.undoManager = new Y.utils.UndoManager(window.yXmlType, {
})
document.onkeydown = function interceptUndoRedo (e) {
if (e.keyCode === 90 && e.metaKey) {
if (e.keyCode === 90 && (e.metaKey || e.ctrlKey)) {
if (!e.shiftKey) {
window.undoManager.undo()
} else {

2
package-lock.json generated
View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "yjs",
"version": "13.0.0-50",
"version": "13.0.0-54",
"description": "A framework for real-time p2p shared editing on any data",
"main": "./y.node.js",
"browser": "./y.js",

View File

@@ -4,8 +4,10 @@ import { readDeleteSet, writeDeleteSet } from './deleteSet.js'
import BinaryEncoder from '../Binary/Encoder.js'
export function fromBinary (y, decoder) {
integrateRemoteStructs(y, decoder)
readDeleteSet(y, decoder)
y.transact(function () {
integrateRemoteStructs(y, decoder)
readDeleteSet(y, decoder)
})
}
export function toBinary (y) {

View File

@@ -51,22 +51,57 @@ export default class Item {
this._parent = null
this._parentSub = null
this._deleted = false
this._redone = null
}
/**
* Copy the effect of struct
* Create a operation with the same effect (without position effect)
*/
_copy (undeleteChildren, copyPosition) {
let struct = new this.constructor()
if (copyPosition) {
struct._origin = this._left
struct._left = this._left
struct._right = this
struct._right_origin = this
struct._parent = this._parent
struct._parentSub = this._parentSub
_copy () {
return new this.constructor()
}
/**
* Redo the effect of this operation.
*/
_redo (y) {
if (this._redone !== null) {
return this._redone
}
let struct = this._copy()
let left = this._left
let right = this
let parent = this._parent
// make sure that parent is redone
if (parent._deleted === true && parent._redone === null) {
parent._redo(y)
}
if (parent._redone !== null) {
parent = parent._redone
// find next cloned items
while (left !== null) {
if (left._redone !== null && left._redone._parent === parent) {
left = left._redone
break
}
left = left._left
}
while (right !== null) {
if (right._redone !== null && right._redone._parent === parent) {
right = right._redone
}
right = right._right
}
}
struct._origin = left
struct._left = left
struct._right = right
struct._right_origin = right
struct._parent = parent
struct._parentSub = this._parentSub
struct._integrate(y)
this._redone = struct
return struct
}
get _lastId () {
return new ID(this._id.user, this._id.clock + this._length - 1)
}
@@ -92,11 +127,15 @@ export default class Item {
if (!this._deleted) {
this._deleted = true
y.ds.markDeleted(this._id, this._length)
let del = new Delete()
del._targetID = this._id
del._length = this._length
if (createDelete) {
let del = new Delete()
del._targetID = this._id
del._length = this._length
// broadcast and persists Delete
del._integrate(y, true)
} else if (y.persistence !== null) {
// only persist Delete
y.persistence.saveStruct(y, del)
}
transactionTypeChanged(y, this._parent, this._parentSub)
y._transaction.deletedStructs.add(this)

View File

@@ -6,8 +6,8 @@ export default class ItemJSON extends Item {
super()
this._content = null
}
_copy (undeleteChildren, copyPosition) {
let struct = super._copy(undeleteChildren, copyPosition)
_copy () {
let struct = super._copy()
struct._content = this._content
return struct
}

View File

@@ -6,8 +6,8 @@ export default class ItemString extends Item {
super()
this._content = null
}
_copy (undeleteChildren, copyPosition) {
let struct = super._copy(undeleteChildren, copyPosition)
_copy () {
let struct = super._copy()
struct._content = this._content
return struct
}

View File

@@ -79,40 +79,6 @@ export default class Type extends Item {
type = type._parent
}
}
_copy (undeleteChildren, copyPosition) {
let copy = super._copy(undeleteChildren, copyPosition)
let map = new Map()
copy._map = map
for (let [key, value] of this._map) {
if (undeleteChildren.has(value) || !value.deleted) {
let _item = value._copy(undeleteChildren, false)
_item._parent = copy
_item._parentSub = key
map.set(key, _item)
}
}
let prevUndeleted = null
copy._start = null
let item = this._start
while (item !== null) {
if (undeleteChildren.has(item) || !item.deleted) {
let _item = item._copy(undeleteChildren, false)
_item._left = prevUndeleted
_item._origin = prevUndeleted
_item._right = null
_item._right_origin = null
_item._parent = copy
if (prevUndeleted === null) {
copy._start = _item
} else {
prevUndeleted._right = _item
}
prevUndeleted = _item
}
item = item._right
}
return copy
}
_transact (f) {
const y = this._y
if (y !== null) {

View File

@@ -20,8 +20,8 @@ export default class YXmlElement extends YXmlFragment {
this._domFilter = arg2
}
}
_copy (undeleteChildren, copyPosition) {
let struct = super._copy(undeleteChildren, copyPosition)
_copy () {
let struct = super._copy()
struct.nodeName = this.nodeName
return struct
}
@@ -36,7 +36,8 @@ export default class YXmlElement extends YXmlFragment {
let attributes = new Map()
for (let i = 0; i < dom.attributes.length; i++) {
let attr = dom.attributes[i]
attributes.set(attr.name, attr.value)
// get attribute via getAttribute for custom element support (some write something different in attr.value)
attributes.set(attr.name, dom.getAttribute(attr.name))
}
attributes = this._domFilter(dom, attributes)
attributes.forEach((value, name) => {

View File

@@ -14,8 +14,8 @@ export default class YXmlHook extends YMap {
getHook(hookName).fillType(dom, this)
}
}
_copy (undeleteChildren, copyPosition) {
const struct = super._copy(undeleteChildren, copyPosition)
_copy () {
const struct = super._copy()
struct.hookName = this.hookName
return struct
}

View File

@@ -4,11 +4,12 @@ class ReverseOperation {
constructor (y, transaction) {
this.created = new Date()
const beforeState = transaction.beforeState
this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1)
if (beforeState.has(y.userID)) {
this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1)
this.fromState = new ID(y.userID, beforeState.get(y.userID))
} else {
this.fromState = this.toState
this.toState = null
this.fromState = null
}
this.deletedStructs = transaction.deletedStructs
}
@@ -30,28 +31,32 @@ function applyReverseOperation (y, scope, reverseBuffer) {
while (!performedUndo && reverseBuffer.length > 0) {
let undoOp = reverseBuffer.pop()
// make sure that it is possible to iterate {from}-{to}
y.os.getItemCleanStart(undoOp.fromState)
y.os.getItemCleanEnd(undoOp.toState)
y.os.iterate(undoOp.fromState, undoOp.toState, op => {
if (!op._deleted && isStructInScope(y, op, scope)) {
performedUndo = true
op._delete(y)
}
})
if (undoOp.fromState !== null) {
y.os.getItemCleanStart(undoOp.fromState)
y.os.getItemCleanEnd(undoOp.toState)
y.os.iterate(undoOp.fromState, undoOp.toState, op => {
while (op._deleted && op._redone !== null) {
op = op._redone
}
if (op._deleted === false && isStructInScope(y, op, scope)) {
performedUndo = true
op._delete(y)
}
})
}
for (let op of undoOp.deletedStructs) {
if (
isStructInScope(y, op, scope) &&
op._parent !== y &&
!op._parent._deleted &&
(
op._parent._id.user !== y.userID ||
op._parent._id.clock < undoOp.fromState.clock ||
op._parent._id.clock > undoOp.fromState.clock
op._id.user !== y.userID ||
undoOp.fromState === null ||
op._id.clock < undoOp.fromState.clock ||
op._id.clock > undoOp.toState.clock
)
) {
performedUndo = true
op = op._copy(undoOp.deletedStructs, true)
op._integrate(y)
op._redo(y)
}
}
}
@@ -68,6 +73,7 @@ export default class UndoManager {
this._scope = scope
this._undoing = false
this._redoing = false
this._lastTransactionWasUndo = false
const y = scope._y
this.y = y
y.on('afterTransaction', (y, transaction, remote) => {
@@ -75,17 +81,29 @@ export default class UndoManager {
let reverseOperation = new ReverseOperation(y, transaction)
if (!this._undoing) {
let lastUndoOp = this._undoBuffer.length > 0 ? this._undoBuffer[this._undoBuffer.length - 1] : null
if (lastUndoOp !== null && reverseOperation.created - lastUndoOp.created <= options.captureTimeout) {
if (
this._redoing === false &&
this._lastTransactionWasUndo === false &&
lastUndoOp !== null &&
reverseOperation.created - lastUndoOp.created <= options.captureTimeout
) {
lastUndoOp.created = reverseOperation.created
lastUndoOp.toState = reverseOperation.toState
if (reverseOperation.toState !== null) {
lastUndoOp.toState = reverseOperation.toState
if (lastUndoOp.fromState === null) {
lastUndoOp.fromState = reverseOperation.fromState
}
}
reverseOperation.deletedStructs.forEach(lastUndoOp.deletedStructs.add, lastUndoOp.deletedStructs)
} else {
this._lastTransactionWasUndo = false
this._undoBuffer.push(reverseOperation)
}
if (!this._redoing) {
this._redoBuffer = []
}
} else {
this._lastTransactionWasUndo = true
this._redoBuffer.push(reverseOperation)
}
}

8
y.js

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

180
y.node.js
View File

@@ -1,7 +1,7 @@
/**
* yjs - A framework for real-time p2p shared editing on any data
* @version v13.0.0-50
* @version v13.0.0-54
* @license MIT
*/
@@ -1340,22 +1340,57 @@ class Item {
this._parent = null;
this._parentSub = null;
this._deleted = false;
this._redone = null;
}
/**
* Copy the effect of struct
* Create a operation with the same effect (without position effect)
*/
_copy (undeleteChildren, copyPosition) {
let struct = new this.constructor();
if (copyPosition) {
struct._origin = this._left;
struct._left = this._left;
struct._right = this;
struct._right_origin = this;
struct._parent = this._parent;
struct._parentSub = this._parentSub;
_copy () {
return new this.constructor()
}
/**
* Redo the effect of this operation.
*/
_redo (y) {
if (this._redone !== null) {
return this._redone
}
let struct = this._copy();
let left = this._left;
let right = this;
let parent = this._parent;
// make sure that parent is redone
if (parent._deleted === true && parent._redone === null) {
parent._redo(y);
}
if (parent._redone !== null) {
parent = parent._redone;
// find next cloned items
while (left !== null) {
if (left._redone !== null && left._redone._parent === parent) {
left = left._redone;
break
}
left = left._left;
}
while (right !== null) {
if (right._redone !== null && right._redone._parent === parent) {
right = right._redone;
}
right = right._right;
}
}
struct._origin = left;
struct._left = left;
struct._right = right;
struct._right_origin = right;
struct._parent = parent;
struct._parentSub = this._parentSub;
struct._integrate(y);
this._redone = struct;
return struct
}
get _lastId () {
return new ID(this._id.user, this._id.clock + this._length - 1)
}
@@ -1381,11 +1416,15 @@ class Item {
if (!this._deleted) {
this._deleted = true;
y.ds.markDeleted(this._id, this._length);
let del = new Delete();
del._targetID = this._id;
del._length = this._length;
if (createDelete) {
let del = new Delete();
del._targetID = this._id;
del._length = this._length;
// broadcast and persists Delete
del._integrate(y, true);
} else if (y.persistence !== null) {
// only persist Delete
y.persistence.saveStruct(y, del);
}
transactionTypeChanged(y, this._parent, this._parentSub);
y._transaction.deletedStructs.add(this);
@@ -1720,40 +1759,6 @@ class Type extends Item {
type = type._parent;
}
}
_copy (undeleteChildren, copyPosition) {
let copy = super._copy(undeleteChildren, copyPosition);
let map = new Map();
copy._map = map;
for (let [key, value] of this._map) {
if (undeleteChildren.has(value) || !value.deleted) {
let _item = value._copy(undeleteChildren, false);
_item._parent = copy;
_item._parentSub = key;
map.set(key, _item);
}
}
let prevUndeleted = null;
copy._start = null;
let item = this._start;
while (item !== null) {
if (undeleteChildren.has(item) || !item.deleted) {
let _item = item._copy(undeleteChildren, false);
_item._left = prevUndeleted;
_item._origin = prevUndeleted;
_item._right = null;
_item._right_origin = null;
_item._parent = copy;
if (prevUndeleted === null) {
copy._start = _item;
} else {
prevUndeleted._right = _item;
}
prevUndeleted = _item;
}
item = item._right;
}
return copy
}
_transact (f) {
const y = this._y;
if (y !== null) {
@@ -1817,8 +1822,8 @@ class ItemJSON extends Item {
super();
this._content = null;
}
_copy (undeleteChildren, copyPosition) {
let struct = super._copy(undeleteChildren, copyPosition);
_copy () {
let struct = super._copy();
struct._content = this._content;
return struct
}
@@ -1879,8 +1884,8 @@ class ItemString extends Item {
super();
this._content = null;
}
_copy (undeleteChildren, copyPosition) {
let struct = super._copy(undeleteChildren, copyPosition);
_copy () {
let struct = super._copy();
struct._content = this._content;
return struct
}
@@ -3133,8 +3138,8 @@ class YXmlElement extends YXmlFragment {
this._domFilter = arg2;
}
}
_copy (undeleteChildren, copyPosition) {
let struct = super._copy(undeleteChildren, copyPosition);
_copy () {
let struct = super._copy();
struct.nodeName = this.nodeName;
return struct
}
@@ -3149,7 +3154,8 @@ class YXmlElement extends YXmlFragment {
let attributes = new Map();
for (let i = 0; i < dom.attributes.length; i++) {
let attr = dom.attributes[i];
attributes.set(attr.name, attr.value);
// get attribute via getAttribute for custom element support (some write something different in attr.value)
attributes.set(attr.name, dom.getAttribute(attr.name));
}
attributes = this._domFilter(dom, attributes);
attributes.forEach((value, name) => {
@@ -3272,8 +3278,8 @@ class YXmlHook extends YMap {
getHook(hookName).fillType(dom, this);
}
}
_copy (undeleteChildren, copyPosition) {
const struct = super._copy(undeleteChildren, copyPosition);
_copy () {
const struct = super._copy();
struct.hookName = this.hookName;
return struct
}
@@ -3671,11 +3677,12 @@ class ReverseOperation {
constructor (y, transaction) {
this.created = new Date();
const beforeState = transaction.beforeState;
this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1);
if (beforeState.has(y.userID)) {
this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1);
this.fromState = new ID(y.userID, beforeState.get(y.userID));
} else {
this.fromState = this.toState;
this.toState = null;
this.fromState = null;
}
this.deletedStructs = transaction.deletedStructs;
}
@@ -3697,28 +3704,32 @@ function applyReverseOperation (y, scope, reverseBuffer) {
while (!performedUndo && reverseBuffer.length > 0) {
let undoOp = reverseBuffer.pop();
// make sure that it is possible to iterate {from}-{to}
y.os.getItemCleanStart(undoOp.fromState);
y.os.getItemCleanEnd(undoOp.toState);
y.os.iterate(undoOp.fromState, undoOp.toState, op => {
if (!op._deleted && isStructInScope(y, op, scope)) {
performedUndo = true;
op._delete(y);
}
});
if (undoOp.fromState !== null) {
y.os.getItemCleanStart(undoOp.fromState);
y.os.getItemCleanEnd(undoOp.toState);
y.os.iterate(undoOp.fromState, undoOp.toState, op => {
while (op._deleted && op._redone !== null) {
op = op._redone;
}
if (op._deleted === false && isStructInScope(y, op, scope)) {
performedUndo = true;
op._delete(y);
}
});
}
for (let op of undoOp.deletedStructs) {
if (
isStructInScope(y, op, scope) &&
op._parent !== y &&
!op._parent._deleted &&
(
op._parent._id.user !== y.userID ||
op._parent._id.clock < undoOp.fromState.clock ||
op._parent._id.clock > undoOp.fromState.clock
op._id.user !== y.userID ||
undoOp.fromState === null ||
op._id.clock < undoOp.fromState.clock ||
op._id.clock > undoOp.toState.clock
)
) {
performedUndo = true;
op = op._copy(undoOp.deletedStructs, true);
op._integrate(y);
op._redo(y);
}
}
}
@@ -3735,6 +3746,7 @@ class UndoManager {
this._scope = scope;
this._undoing = false;
this._redoing = false;
this._lastTransactionWasUndo = false;
const y = scope._y;
this.y = y;
y.on('afterTransaction', (y, transaction, remote) => {
@@ -3742,17 +3754,29 @@ class UndoManager {
let reverseOperation = new ReverseOperation(y, transaction);
if (!this._undoing) {
let lastUndoOp = this._undoBuffer.length > 0 ? this._undoBuffer[this._undoBuffer.length - 1] : null;
if (lastUndoOp !== null && reverseOperation.created - lastUndoOp.created <= options.captureTimeout) {
if (
this._redoing === false &&
this._lastTransactionWasUndo === false &&
lastUndoOp !== null &&
reverseOperation.created - lastUndoOp.created <= options.captureTimeout
) {
lastUndoOp.created = reverseOperation.created;
lastUndoOp.toState = reverseOperation.toState;
if (reverseOperation.toState !== null) {
lastUndoOp.toState = reverseOperation.toState;
if (lastUndoOp.fromState === null) {
lastUndoOp.fromState = reverseOperation.fromState;
}
}
reverseOperation.deletedStructs.forEach(lastUndoOp.deletedStructs.add, lastUndoOp.deletedStructs);
} else {
this._lastTransactionWasUndo = false;
this._undoBuffer.push(reverseOperation);
}
if (!this._redoing) {
this._redoBuffer = [];
}
} else {
this._lastTransactionWasUndo = true;
this._redoBuffer.push(reverseOperation);
}
}
@@ -4626,8 +4650,10 @@ class AbstractConnector {
}
function fromBinary (y, decoder) {
integrateRemoteStructs(y, decoder);
readDeleteSet(y, decoder);
y.transact(function () {
integrateRemoteStructs(y, decoder);
readDeleteSet(y, decoder);
});
}
function toBinary (y) {

File diff suppressed because one or more lines are too long