cleaned up, removed Replaceable operation, changed Operation specific events method (put it into the Manager types), created Composition type
This commit is contained in:
parent
b24de43fe2
commit
bb0bfcc5c8
@ -86,7 +86,8 @@ var y = new Y(connector);
|
||||
* Delete a property
|
||||
* .observe(observer)
|
||||
* The `observer` is called whenever something on this object changes. Throws *add*, *update*, and *delete* events
|
||||
|
||||
* .unobserve(f)
|
||||
* Delete an observer
|
||||
|
||||
# A note on intention preservation
|
||||
When users create/update/delete the same property concurrently, only one change will prevail. Changes on different properties do not conflict with each other.
|
||||
@ -115,7 +116,7 @@ But it would be really nice to get some feedback :)
|
||||
* The History Buffer should be able to store operations in a database
|
||||
|
||||
## Get help
|
||||
[](https://gitter.im/rwth-acis/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](https://gitter.im/y-js/yjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
Please report _any_ issues to the [Github issue page](https://github.com/rwth-acis/yjs/issues)! I try to fix them very soon, if possible.
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -299,7 +299,7 @@ module.exports = function() {
|
||||
}
|
||||
Insert.__super__.applyDelete.call(this, garbagecollect);
|
||||
if (callLater) {
|
||||
this.callOperationSpecificDeleteEvents(o);
|
||||
this.parent.callOperationSpecificDeleteEvents(this, o);
|
||||
}
|
||||
if ((_ref = this.prev_cl) != null ? _ref.isDeleted() : void 0) {
|
||||
return this.prev_cl.applyDelete();
|
||||
@ -408,44 +408,11 @@ module.exports = function() {
|
||||
}
|
||||
this.setParent(this.prev_cl.getParent());
|
||||
Insert.__super__.execute.apply(this, arguments);
|
||||
this.callOperationSpecificInsertEvents();
|
||||
this.parent.callOperationSpecificInsertEvents(this);
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
Insert.prototype.callOperationSpecificInsertEvents = function() {
|
||||
var getContentType, _ref;
|
||||
getContentType = function(content) {
|
||||
if (content instanceof ops.Operation) {
|
||||
return content.getCustomType();
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
};
|
||||
return (_ref = this.parent) != null ? _ref.callEvent([
|
||||
{
|
||||
type: "insert",
|
||||
position: this.getPosition(),
|
||||
object: this.parent.getCustomType(),
|
||||
changedBy: this.uid.creator,
|
||||
value: getContentType(this.content)
|
||||
}
|
||||
]) : void 0;
|
||||
};
|
||||
|
||||
Insert.prototype.callOperationSpecificDeleteEvents = function(o) {
|
||||
return this.parent.callEvent([
|
||||
{
|
||||
type: "delete",
|
||||
position: this.getPosition(),
|
||||
object: this.parent.getCustomType(),
|
||||
length: 1,
|
||||
changedBy: o.uid.creator,
|
||||
oldValue: this.val()
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
Insert.prototype.getPosition = function() {
|
||||
var position, prev;
|
||||
position = 0;
|
||||
|
@ -308,6 +308,39 @@ module.exports = function() {
|
||||
return this;
|
||||
};
|
||||
|
||||
ListManager.prototype.callOperationSpecificInsertEvents = function(op) {
|
||||
var getContentType;
|
||||
getContentType = function(content) {
|
||||
if (content instanceof ops.Operation) {
|
||||
return content.getCustomType();
|
||||
} else {
|
||||
return content;
|
||||
}
|
||||
};
|
||||
return this.callEvent([
|
||||
{
|
||||
type: "insert",
|
||||
position: op.getPosition(),
|
||||
object: this.getCustomType(),
|
||||
changedBy: op.uid.creator,
|
||||
value: getContentType(op.content)
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
ListManager.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {
|
||||
return this.callEvent([
|
||||
{
|
||||
type: "delete",
|
||||
position: op.getPosition(),
|
||||
object: this.getCustomType(),
|
||||
length: 1,
|
||||
changedBy: del_op.uid.creator,
|
||||
oldValue: op.val()
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
return ListManager;
|
||||
|
||||
})(ops.Operation);
|
||||
@ -360,6 +393,45 @@ module.exports = function() {
|
||||
return void 0;
|
||||
};
|
||||
|
||||
ReplaceManager.prototype.callOperationSpecificInsertEvents = function(op) {
|
||||
var old_value;
|
||||
if (op.next_cl.type === "Delimiter" && op.prev_cl.type !== "Delimiter") {
|
||||
if (!op.is_deleted) {
|
||||
old_value = op.prev_cl.val();
|
||||
this.callEventDecorator([
|
||||
{
|
||||
type: "update",
|
||||
changedBy: op.uid.creator,
|
||||
oldValue: old_value
|
||||
}
|
||||
]);
|
||||
}
|
||||
op.prev_cl.applyDelete();
|
||||
} else if (op.next_cl.type !== "Delimiter") {
|
||||
op.applyDelete();
|
||||
} else {
|
||||
this.callEventDecorator([
|
||||
{
|
||||
type: "add",
|
||||
changedBy: op.uid.creator
|
||||
}
|
||||
]);
|
||||
}
|
||||
return void 0;
|
||||
};
|
||||
|
||||
ReplaceManager.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {
|
||||
if (op.next_cl.type === "Delimiter") {
|
||||
return this.callEventDecorator([
|
||||
{
|
||||
type: "delete",
|
||||
changedBy: del_op.uid.creator,
|
||||
oldValue: op.val()
|
||||
}
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
ReplaceManager.prototype.replace = function(content, replaceable_uid) {
|
||||
var o, relp;
|
||||
o = this.getLastOperation();
|
||||
@ -394,55 +466,35 @@ module.exports = function() {
|
||||
return ReplaceManager;
|
||||
|
||||
})(ops.ListManager);
|
||||
ops.Composition = (function(_super) {
|
||||
__extends(Composition, _super);
|
||||
|
||||
function Composition(custom_type, _at_composition_value, uid, beginning, end) {
|
||||
this.composition_value = _at_composition_value;
|
||||
if (this.composition_value == null) {
|
||||
throw new Error("You must instanziate ops.Composition with a composition_value!");
|
||||
}
|
||||
Composition.__super__.constructor.call(this, custom_type, null, null, uid, beginning, end);
|
||||
}
|
||||
|
||||
Composition.prototype.type = "Composition";
|
||||
|
||||
Composition.prototype.val = function() {
|
||||
return this.composition_value;
|
||||
};
|
||||
|
||||
return Composition;
|
||||
|
||||
})(ops.ReplaceManager);
|
||||
ops.Replaceable = (function(_super) {
|
||||
__extends(Replaceable, _super);
|
||||
|
||||
function Replaceable(custom_type, content, parent, uid, prev, next, origin) {
|
||||
this.saveOperation('parent', parent);
|
||||
Replaceable.__super__.constructor.call(this, custom_type, content, uid, prev, next, origin);
|
||||
Replaceable.__super__.constructor.call(this, custom_type, content, uid, prev, next, origin, parent);
|
||||
}
|
||||
|
||||
Replaceable.prototype.type = "Replaceable";
|
||||
|
||||
Replaceable.prototype.callOperationSpecificInsertEvents = function() {
|
||||
var old_value;
|
||||
if (this.next_cl.type === "Delimiter" && this.prev_cl.type !== "Delimiter") {
|
||||
if (!this.is_deleted) {
|
||||
old_value = this.prev_cl.val();
|
||||
this.parent.callEventDecorator([
|
||||
{
|
||||
type: "update",
|
||||
changedBy: this.uid.creator,
|
||||
oldValue: old_value
|
||||
}
|
||||
]);
|
||||
}
|
||||
this.prev_cl.applyDelete();
|
||||
} else if (this.next_cl.type !== "Delimiter") {
|
||||
this.applyDelete();
|
||||
} else {
|
||||
this.parent.callEventDecorator([
|
||||
{
|
||||
type: "add",
|
||||
changedBy: this.uid.creator
|
||||
}
|
||||
]);
|
||||
}
|
||||
return void 0;
|
||||
};
|
||||
|
||||
Replaceable.prototype.callOperationSpecificDeleteEvents = function(o) {
|
||||
if (this.next_cl.type === "Delimiter") {
|
||||
return this.parent.callEventDecorator([
|
||||
{
|
||||
type: "delete",
|
||||
changedBy: o.uid.creator,
|
||||
oldValue: this.val()
|
||||
}
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
Replaceable.prototype._encode = function(json) {
|
||||
if (json == null) {
|
||||
json = {};
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -303,7 +303,7 @@ module.exports = ()->
|
||||
# @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl)
|
||||
# @param {Operation} next_cl The successor of this operation in the complete-list (cl)
|
||||
#
|
||||
constructor: (custom_type, content, uid, prev_cl, next_cl, origin, parent)->
|
||||
constructor: (custom_type, content, parent, uid, prev_cl, next_cl, origin)->
|
||||
# see encode to see, why we are doing it this way
|
||||
if content is undefined
|
||||
# nop
|
||||
@ -345,7 +345,7 @@ module.exports = ()->
|
||||
garbagecollect = true
|
||||
super garbagecollect
|
||||
if callLater
|
||||
@callOperationSpecificDeleteEvents(o)
|
||||
@parent.callOperationSpecificDeleteEvents(this, o)
|
||||
if @prev_cl?.isDeleted()
|
||||
# garbage collect prev_cl
|
||||
@prev_cl.applyDelete()
|
||||
@ -466,33 +466,9 @@ module.exports = ()->
|
||||
|
||||
@setParent @prev_cl.getParent() # do Insertions always have a parent?
|
||||
super # notify the execution_listeners
|
||||
@callOperationSpecificInsertEvents()
|
||||
@parent.callOperationSpecificInsertEvents(this)
|
||||
@
|
||||
|
||||
callOperationSpecificInsertEvents: ()->
|
||||
getContentType = (content)->
|
||||
if content instanceof ops.Operation
|
||||
content.getCustomType()
|
||||
else
|
||||
content
|
||||
@parent?.callEvent [
|
||||
type: "insert"
|
||||
position: @getPosition()
|
||||
object: @parent.getCustomType()
|
||||
changedBy: @uid.creator
|
||||
value: getContentType @content
|
||||
]
|
||||
|
||||
callOperationSpecificDeleteEvents: (o)->
|
||||
@parent.callEvent [
|
||||
type: "delete"
|
||||
position: @getPosition()
|
||||
object: @parent.getCustomType() # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
|
||||
length: 1
|
||||
changedBy: o.uid.creator
|
||||
oldValue: @val()
|
||||
]
|
||||
|
||||
#
|
||||
# Compute the position of this operation.
|
||||
#
|
||||
@ -538,7 +514,7 @@ module.exports = ()->
|
||||
} = json
|
||||
if typeof content is "string"
|
||||
content = JSON.parse(content)
|
||||
new this null, content, uid, prev, next, origin, parent
|
||||
new this null, content, parent, uid, prev, next, origin
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
|
@ -109,16 +109,18 @@ module.exports = ()->
|
||||
|
||||
type: "ListManager"
|
||||
|
||||
|
||||
applyDelete: ()->
|
||||
o = @end
|
||||
o = @beginning
|
||||
while o?
|
||||
o.applyDelete()
|
||||
o = o.prev_cl
|
||||
o = o.next_cl
|
||||
super()
|
||||
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
|
||||
toJson: (transform_to_value = false)->
|
||||
val = @val()
|
||||
for i, o in val
|
||||
@ -226,12 +228,12 @@ module.exports = ()->
|
||||
|
||||
# TODO: always expect an array as content. Then you can combine this with the other option (else)
|
||||
if contents instanceof ops.Operation
|
||||
(new ops.Insert null, content, undefined, left, right).execute()
|
||||
(new ops.Insert null, content, undefined, undefined, left, right).execute()
|
||||
else
|
||||
for c in contents
|
||||
if c? and c._name? and c._getModel?
|
||||
c = c._getModel(@custom_types, @operations)
|
||||
tmp = (new ops.Insert null, c, undefined, left, right).execute()
|
||||
tmp = (new ops.Insert null, c, undefined, undefined, left, right).execute()
|
||||
left = tmp
|
||||
@
|
||||
|
||||
@ -266,6 +268,31 @@ module.exports = ()->
|
||||
delete_ops.push d._encode()
|
||||
@
|
||||
|
||||
|
||||
callOperationSpecificInsertEvents: (op)->
|
||||
getContentType = (content)->
|
||||
if content instanceof ops.Operation
|
||||
content.getCustomType()
|
||||
else
|
||||
content
|
||||
@callEvent [
|
||||
type: "insert"
|
||||
position: op.getPosition()
|
||||
object: @getCustomType()
|
||||
changedBy: op.uid.creator
|
||||
value: getContentType op.content
|
||||
]
|
||||
|
||||
callOperationSpecificDeleteEvents: (op, del_op)->
|
||||
@callEvent [
|
||||
type: "delete"
|
||||
position: op.getPosition()
|
||||
object: @getCustomType() # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
|
||||
length: 1
|
||||
changedBy: del_op.uid.creator
|
||||
oldValue: op.val()
|
||||
]
|
||||
|
||||
ops.ListManager.parse = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
@ -273,6 +300,79 @@ module.exports = ()->
|
||||
} = json
|
||||
new this(custom_type, uid)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ops.Composition extends ops.ListManager
|
||||
|
||||
constructor: (custom_type, @composition_value, uid, composition_ref)->
|
||||
super custom_type, null, null, uid
|
||||
if composition_ref
|
||||
@saveOperation 'composition_ref', composition_ref
|
||||
else
|
||||
@composition_ref = @beginning
|
||||
|
||||
type: "Composition"
|
||||
|
||||
val: ()->
|
||||
@composition_value
|
||||
|
||||
#
|
||||
# This is called, when the Insert-operation was successfully executed.
|
||||
#
|
||||
callOperationSpecificInsertEvents: (op)->
|
||||
if @composition_ref.next_cl is op
|
||||
@custom_type._apply op.content
|
||||
else
|
||||
o = @end.prev_cl
|
||||
while o isnt op
|
||||
@custom_type._undo o.content
|
||||
o = o.next_cl
|
||||
while o isnt @end
|
||||
@custom_type._apply o.content
|
||||
o = o.next_cl
|
||||
@composition_ref = @end.prev_cl
|
||||
|
||||
@callEvent [
|
||||
type: "update"
|
||||
changedBy: op.uid.creator
|
||||
newValue: @val()
|
||||
]
|
||||
|
||||
callOperationSpecificDeleteEvents: (op, del_op)->
|
||||
return
|
||||
|
||||
#
|
||||
# Create a new Delta
|
||||
# - inserts new Content at the end of the list
|
||||
# - updates the composition_value
|
||||
# - updates the composition_ref
|
||||
#
|
||||
# @param delta The delta that is applied to the composition_value
|
||||
#
|
||||
applyDelta: (delta)->
|
||||
(new ops.Insert null, content, @, null, @end.prev_cl, @end).execute()
|
||||
undefined
|
||||
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: (json = {})->
|
||||
json.composition_value = JSON.stringify @composition_value
|
||||
json.composition_ref = @composition_ref.getUid()
|
||||
super json
|
||||
|
||||
ops.Composition.parse = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
'custom_type': custom_type
|
||||
'composition_value' : composition_value
|
||||
'composition_ref' : composition_ref
|
||||
} = json
|
||||
new this(custom_type, JSON.parse(composition_value), uid, composition_ref)
|
||||
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# Adds support for replace. The ReplaceManager manages Replaceable operations.
|
||||
@ -289,23 +389,13 @@ module.exports = ()->
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @param {Delimiter} beginning Reference or Object.
|
||||
# @param {Delimiter} end Reference or Object.
|
||||
constructor: (custom_type, @event_properties, @event_this, uid, beginning, end)->
|
||||
constructor: (custom_type, @event_properties, @event_this, uid)->
|
||||
if not @event_properties['object']?
|
||||
@event_properties['object'] = @event_this.getCustomType()
|
||||
super custom_type, uid, beginning, end
|
||||
super custom_type, uid
|
||||
|
||||
type: "ReplaceManager"
|
||||
|
||||
applyDelete: ()->
|
||||
o = @beginning
|
||||
while o?
|
||||
o.applyDelete()
|
||||
o = o.next_cl
|
||||
super()
|
||||
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
#
|
||||
# This doesn't throw the same events as the ListManager. Therefore, the
|
||||
# Replaceables also not throw the same events.
|
||||
@ -321,6 +411,42 @@ module.exports = ()->
|
||||
@event_this.callEvent events
|
||||
undefined
|
||||
|
||||
#
|
||||
# This is called, when the Insert-type was successfully executed.
|
||||
# TODO: consider doing this in a more consistent manner. This could also be
|
||||
# done with execute. But currently, there are no specital Insert-ops for ListManager.
|
||||
#
|
||||
callOperationSpecificInsertEvents: (op)->
|
||||
if op.next_cl.type is "Delimiter" and op.prev_cl.type isnt "Delimiter"
|
||||
# this replaces another Replaceable
|
||||
if not op.is_deleted # When this is received from the HB, this could already be deleted!
|
||||
old_value = op.prev_cl.val()
|
||||
@callEventDecorator [
|
||||
type: "update"
|
||||
changedBy: op.uid.creator
|
||||
oldValue: old_value
|
||||
]
|
||||
op.prev_cl.applyDelete()
|
||||
else if op.next_cl.type isnt "Delimiter"
|
||||
# This won't be recognized by the user, because another
|
||||
# concurrent operation is set as the current value of the RM
|
||||
op.applyDelete()
|
||||
else # prev _and_ next are Delimiters. This is the first created Replaceable in the RM
|
||||
@callEventDecorator [
|
||||
type: "add"
|
||||
changedBy: op.uid.creator
|
||||
]
|
||||
undefined
|
||||
|
||||
callOperationSpecificDeleteEvents: (op, del_op)->
|
||||
if op.next_cl.type is "Delimiter"
|
||||
@callEventDecorator [
|
||||
type: "delete"
|
||||
changedBy: del_op.uid.creator
|
||||
oldValue: op.val()
|
||||
]
|
||||
|
||||
|
||||
#
|
||||
# Replace the existing word with a new word.
|
||||
#
|
||||
@ -329,7 +455,7 @@ module.exports = ()->
|
||||
#
|
||||
replace: (content, replaceable_uid)->
|
||||
o = @getLastOperation()
|
||||
relp = (new ops.Replaceable null, content, @, replaceable_uid, o, o.next_cl).execute()
|
||||
relp = (new ops.Insert null, content, @, replaceable_uid, o, o.next_cl).execute()
|
||||
# TODO: delete repl (for debugging)
|
||||
undefined
|
||||
|
||||
@ -350,86 +476,6 @@ module.exports = ()->
|
||||
# throw new Error "Replace Manager doesn't contain anything."
|
||||
o.val?() # ? - for the case that (currently) the RM does not contain anything (then o is a Delimiter)
|
||||
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: (json = {})->
|
||||
json.beginning = @beginning.getUid()
|
||||
json.end = @end.getUid()
|
||||
super json
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# The ReplaceManager manages Replaceables.
|
||||
# @see ReplaceManager
|
||||
#
|
||||
class ops.Replaceable extends ops.Insert
|
||||
|
||||
#
|
||||
# @param {Operation} content The value that this Replaceable holds.
|
||||
# @param {ReplaceManager} parent Used to replace this Replaceable with another one.
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
#
|
||||
constructor: (custom_type, content, parent, uid, prev, next, origin)->
|
||||
@saveOperation 'parent', parent
|
||||
super custom_type, content, uid, prev, next, origin # Parent is already saved by Replaceable
|
||||
|
||||
type: "Replaceable"
|
||||
|
||||
#
|
||||
# This is called, when the Insert-type was successfully executed.
|
||||
# TODO: consider doing this in a more consistent manner. This could also be
|
||||
# done with execute. But currently, there are no specital Insert-ops for ListManager.
|
||||
#
|
||||
callOperationSpecificInsertEvents: ()->
|
||||
if @next_cl.type is "Delimiter" and @prev_cl.type isnt "Delimiter"
|
||||
# this replaces another Replaceable
|
||||
if not @is_deleted # When this is received from the HB, this could already be deleted!
|
||||
old_value = @prev_cl.val()
|
||||
@parent.callEventDecorator [
|
||||
type: "update"
|
||||
changedBy: @uid.creator
|
||||
oldValue: old_value
|
||||
]
|
||||
@prev_cl.applyDelete()
|
||||
else if @next_cl.type isnt "Delimiter"
|
||||
# This won't be recognized by the user, because another
|
||||
# concurrent operation is set as the current value of the RM
|
||||
@applyDelete()
|
||||
else # prev _and_ next are Delimiters. This is the first created Replaceable in the RM
|
||||
@parent.callEventDecorator [
|
||||
type: "add"
|
||||
changedBy: @uid.creator
|
||||
]
|
||||
undefined
|
||||
|
||||
callOperationSpecificDeleteEvents: (o)->
|
||||
if @next_cl.type is "Delimiter"
|
||||
@parent.callEventDecorator [
|
||||
type: "delete"
|
||||
changedBy: o.uid.creator
|
||||
oldValue: @val()
|
||||
]
|
||||
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: (json = {})->
|
||||
super json
|
||||
|
||||
ops.Replaceable.parse = (json)->
|
||||
{
|
||||
'content' : content
|
||||
'parent' : parent
|
||||
'uid' : uid
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
'custom_type' : custom_type
|
||||
} = json
|
||||
if typeof content is "string"
|
||||
content = JSON.parse(content)
|
||||
new this(custom_type, content, parent, uid, prev, next, origin)
|
||||
|
||||
|
||||
basic_ops
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user