switched to Gulp
This commit is contained in:
439
build/node/Types/BasicTypes.coffee
Normal file
439
build/node/Types/BasicTypes.coffee
Normal file
@@ -0,0 +1,439 @@
|
||||
module.exports = (HB)->
|
||||
# @see Engine.parse
|
||||
parser = {}
|
||||
execution_listener = []
|
||||
|
||||
#
|
||||
# A generic interface to operations.
|
||||
#
|
||||
# An operation has the following methods:
|
||||
# _encode: encodes an operation (needed only if instance of this operation is sent).
|
||||
# execute: execute the effects of this operations. Good examples are Insert-type and AddName-type
|
||||
# val: in the case that the operation holds a value
|
||||
#
|
||||
# Furthermore an encodable operation has a parser.
|
||||
#
|
||||
class Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @see HistoryBuffer.getNextOperationIdentifier
|
||||
#
|
||||
constructor: (uid)->
|
||||
if not uid?
|
||||
uid = HB.getNextOperationIdentifier()
|
||||
{
|
||||
'creator': @creator
|
||||
'op_number' : @op_number
|
||||
} = uid
|
||||
|
||||
#
|
||||
# Add an event listener. It depends on the operation which events are supported.
|
||||
# @param {String} event Name of the event.
|
||||
# @param {Function} f f is executed in case the event fires.
|
||||
#
|
||||
on: (event, f)->
|
||||
@event_listeners ?= {}
|
||||
@event_listeners[event] ?= []
|
||||
@event_listeners[event].push f
|
||||
|
||||
#
|
||||
# Fire an event.
|
||||
# TODO: Do something with timeouts. You don't want this to fire for every operation (e.g. insert).
|
||||
#
|
||||
callEvent: (event, args)->
|
||||
if @event_listeners[event]?
|
||||
for f in @event_listeners[event]
|
||||
f.call @, event, args
|
||||
|
||||
#
|
||||
# Set the parent of this operation.
|
||||
#
|
||||
setParent: (o)->
|
||||
@parent = o
|
||||
|
||||
#
|
||||
# Computes a unique identifier (uid) that identifies this operation.
|
||||
#
|
||||
getUid: ()->
|
||||
{ 'creator': @creator, 'op_number': @op_number }
|
||||
|
||||
#
|
||||
# @private
|
||||
# Notify the all the listeners.
|
||||
#
|
||||
execute: ()->
|
||||
@is_executed = true
|
||||
for l in execution_listener
|
||||
l @_encode()
|
||||
@
|
||||
|
||||
#
|
||||
# @private
|
||||
# Operations may depend on other operations (linked lists, etc.).
|
||||
# The saveOperation and validateSavedOperations methods provide
|
||||
# an easy way to refer to these operations via an uid or object reference.
|
||||
#
|
||||
# For example: We can create a new Delete operation that deletes the operation $o like this
|
||||
# - var d = new Delete(uid, $o); or
|
||||
# - var d = new Delete(uid, $o.getUid());
|
||||
# Either way we want to access $o via d.deletes. In the second case validateSavedOperations must be called first.
|
||||
#
|
||||
# @overload saveOperation(name, op_uid)
|
||||
# @param {String} name The name of the operation. After validating (with validateSavedOperations) the instantiated operation will be accessible via this[name].
|
||||
# @param {Object} op_uid A uid that refers to an operation
|
||||
# @overload saveOperation(name, op)
|
||||
# @param {String} name The name of the operation. After calling this function op is accessible via this[name].
|
||||
# @param {Operation} op An Operation object
|
||||
#
|
||||
saveOperation: (name, op)->
|
||||
|
||||
#
|
||||
# Every instance of $Operation must have an $execute function.
|
||||
# We use duck-typing to check if op is instantiated since there
|
||||
# could exist multiple classes of $Operation
|
||||
#
|
||||
if op?.execute?
|
||||
# is instantiated
|
||||
@[name] = op
|
||||
else if op?
|
||||
# not initialized. Do it when calling $validateSavedOperations()
|
||||
@unchecked ?= {}
|
||||
@unchecked[name] = op
|
||||
|
||||
#
|
||||
# @private
|
||||
# After calling this function all not instantiated operations will be accessible.
|
||||
# @see Operation.saveOperation
|
||||
#
|
||||
# @return [Boolean] Whether it was possible to instantiate all operations.
|
||||
#
|
||||
validateSavedOperations: ()->
|
||||
uninstantiated = {}
|
||||
success = @
|
||||
for name, op_uid of @unchecked
|
||||
op = HB.getOperation op_uid
|
||||
if op
|
||||
@[name] = op
|
||||
else
|
||||
uninstantiated[name] = op_uid
|
||||
success = false
|
||||
delete @unchecked
|
||||
if not success
|
||||
@unchecked = uninstantiated
|
||||
success
|
||||
|
||||
|
||||
|
||||
#
|
||||
# A simple Delete-type operation that deletes an Insert-type operation.
|
||||
#
|
||||
class Delete extends Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @param {Object} deletes UID or reference of the operation that this to be deleted.
|
||||
#
|
||||
constructor: (uid, deletes)->
|
||||
@saveOperation 'deletes', deletes
|
||||
super uid
|
||||
|
||||
#
|
||||
# @private
|
||||
# Convert all relevant information of this operation to the json-format.
|
||||
# This result can be sent to other clients.
|
||||
#
|
||||
_encode: ()->
|
||||
{
|
||||
'type': "Delete"
|
||||
'uid': @getUid()
|
||||
'deletes': @deletes.getUid()
|
||||
}
|
||||
|
||||
#
|
||||
# @private
|
||||
# Apply the deletion.
|
||||
#
|
||||
execute: ()->
|
||||
if @validateSavedOperations()
|
||||
@deletes.applyDelete @
|
||||
super
|
||||
@
|
||||
else
|
||||
false
|
||||
|
||||
#
|
||||
# Define how to parse Delete operations.
|
||||
#
|
||||
parser['Delete'] = (o)->
|
||||
{
|
||||
'uid' : uid
|
||||
'deletes': deletes_uid
|
||||
} = o
|
||||
new Delete uid, deletes_uid
|
||||
|
||||
#
|
||||
# A simple insert-type operation.
|
||||
#
|
||||
# An insert operation is always positioned between two other insert operations.
|
||||
# Internally this is realized as associative lists, whereby each insert operation has a predecessor and a successor.
|
||||
# For the sake of efficiency we maintain two lists:
|
||||
# - The short-list (abbrev. sl) maintains only the operations that are not deleted
|
||||
# - The complete-list (abbrev. cl) maintains all operations
|
||||
#
|
||||
class Insert extends Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @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)
|
||||
#
|
||||
# @see HistoryBuffer.getNextOperationIdentifier
|
||||
#
|
||||
constructor: (uid, prev_cl, next_cl, origin)->
|
||||
@saveOperation 'prev_cl', prev_cl
|
||||
@saveOperation 'next_cl', next_cl
|
||||
if origin?
|
||||
@saveOperation 'origin', origin
|
||||
else
|
||||
@saveOperation 'origin', prev_cl
|
||||
super uid
|
||||
|
||||
#
|
||||
# @private
|
||||
#
|
||||
applyDelete: (o)->
|
||||
@deleted_by ?= []
|
||||
@deleted_by.push o
|
||||
|
||||
#
|
||||
# If isDeleted() is true this operation won't be maintained in the sl
|
||||
#
|
||||
isDeleted: ()->
|
||||
@deleted_by?.length > 0
|
||||
|
||||
#
|
||||
# @private
|
||||
# The amount of positions that $this operation was moved to the right.
|
||||
#
|
||||
getDistanceToOrigin: ()->
|
||||
d = 0
|
||||
o = @prev_cl
|
||||
while true
|
||||
if @origin is o
|
||||
break
|
||||
d++
|
||||
#TODO: delete this
|
||||
if @ is @prev_cl
|
||||
throw new Error "this should not happen ;) "
|
||||
o = o.prev_cl
|
||||
d
|
||||
|
||||
#
|
||||
# @private
|
||||
# Update the short list
|
||||
# TODO (Unused)
|
||||
update_sl: ()->
|
||||
o = @prev_cl
|
||||
update: (dest_cl,dest_sl)->
|
||||
while true
|
||||
if o.isDeleted()
|
||||
o = o[dest_cl]
|
||||
else
|
||||
@[dest_sl] = o
|
||||
|
||||
break
|
||||
update "prev_cl", "prev_sl"
|
||||
update "next_cl", "prev_sl"
|
||||
|
||||
|
||||
|
||||
#
|
||||
# @private
|
||||
# Include this operation in the associative lists.
|
||||
#
|
||||
execute: ()->
|
||||
if @is_executed?
|
||||
return @
|
||||
if not @validateSavedOperations()
|
||||
return false
|
||||
else
|
||||
if @prev_cl?.validateSavedOperations() and @next_cl?.validateSavedOperations() and @prev_cl.next_cl isnt @
|
||||
distance_to_origin = 0
|
||||
o = @prev_cl.next_cl
|
||||
i = 0
|
||||
# $this has to find a unique position between origin and the next known character
|
||||
# case 1: $origin equals $o.origin: the $creator parameter decides if left or right
|
||||
# let $OL= [o1,o2,o3,o4], whereby $this is to be inserted between o1 and o4
|
||||
# o2,o3 and o4 origin is 1 (the position of o2)
|
||||
# there is the case that $this.creator < o2.creator, but o3.creator < $this.creator
|
||||
# then o2 knows o3. Since on another client $OL could be [o1,o3,o4] the problem is complex
|
||||
# therefore $this would be always to the right of o3
|
||||
# case 2: $origin < $o.origin
|
||||
# if current $this insert_position > $o origin: $this ins
|
||||
# else $insert_position will not change (maybe we encounter case 1 later, then this will be to the right of $o)
|
||||
# case 3: $origin > $o.origin
|
||||
# $this insert_position is to the left of $o (forever!)
|
||||
while true
|
||||
if not o?
|
||||
# TODO: Debugging
|
||||
console.log JSON.stringify @prev_cl.getUid()
|
||||
console.log JSON.stringify @next_cl.getUid()
|
||||
if o isnt @next_cl
|
||||
# $o happened concurrently
|
||||
if o.getDistanceToOrigin() is i
|
||||
# case 1
|
||||
if o.creator < @creator
|
||||
@prev_cl = o
|
||||
distance_to_origin = i + 1
|
||||
else
|
||||
# nop
|
||||
else if o.getDistanceToOrigin() < i
|
||||
# case 2
|
||||
if i - distance_to_origin <= o.getDistanceToOrigin()
|
||||
@prev_cl = o
|
||||
distance_to_origin = i + 1
|
||||
else
|
||||
#nop
|
||||
else
|
||||
# case 3
|
||||
break
|
||||
i++
|
||||
o = o.next_cl
|
||||
else
|
||||
# $this knows that $o exists,
|
||||
break
|
||||
# now reconnect everything
|
||||
@next_cl = @prev_cl.next_cl
|
||||
@prev_cl.next_cl = @
|
||||
@next_cl.prev_cl = @
|
||||
super # notify the execution_listeners
|
||||
@
|
||||
|
||||
#
|
||||
# Defines an object that is cannot be changed. You can use this to set an immutable string, or a number.
|
||||
#
|
||||
class ImmutableObject extends Insert
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @param {Object} content
|
||||
#
|
||||
constructor: (uid, @content, prev, next, origin)->
|
||||
super uid, prev, next, origin
|
||||
|
||||
#
|
||||
# @return [String] The content of this operation.
|
||||
#
|
||||
val : ()->
|
||||
@content
|
||||
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: ()->
|
||||
json = {
|
||||
'type': "ImmutableObject"
|
||||
'uid' : @getUid()
|
||||
'content' : @content
|
||||
}
|
||||
if @prev_cl?
|
||||
json['prev'] = @prev_cl.getUid()
|
||||
if @next_cl?
|
||||
json['next'] = @next_cl.getUid()
|
||||
if @origin? and @origin isnt @prev_cl
|
||||
json["origin"] = @origin.getUid()
|
||||
json
|
||||
|
||||
parser['ImmutableObject'] = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
'content' : content
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
} = json
|
||||
new ImmutableObject uid, content, prev, next, origin
|
||||
|
||||
#
|
||||
# A delimiter is placed at the end and at the beginning of the associative lists.
|
||||
# This is necessary in order to have a beginning and an end even if the content
|
||||
# of the Engine is empty.
|
||||
#
|
||||
class Delimiter extends Operation
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @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)
|
||||
#
|
||||
# @see HistoryBuffer.getNextOperationIdentifier
|
||||
#
|
||||
constructor: (uid, prev_cl, next_cl, origin)->
|
||||
@saveOperation 'prev_cl', prev_cl
|
||||
@saveOperation 'next_cl', next_cl
|
||||
@saveOperation 'origin', prev_cl
|
||||
super uid
|
||||
|
||||
#
|
||||
# If isDeleted() is true this operation won't be maintained in the sl
|
||||
#
|
||||
isDeleted: ()->
|
||||
false
|
||||
|
||||
#
|
||||
# @private
|
||||
#
|
||||
execute: ()->
|
||||
if @unchecked?['next_cl']?
|
||||
super
|
||||
else if @unchecked?['prev_cl']
|
||||
if @validateSavedOperations()
|
||||
if @prev_cl.next_cl?
|
||||
throw new Error "Probably duplicated operations"
|
||||
@prev_cl.next_cl = @
|
||||
delete @prev_cl.unchecked.next_cl
|
||||
super
|
||||
else
|
||||
false
|
||||
else if @prev_cl? and not @prev_cl.next_cl?
|
||||
delete @prev_cl.unchecked.next_cl
|
||||
@prev_cl.next_cl = @
|
||||
else if @prev_cl? or @next_cl?
|
||||
super
|
||||
else
|
||||
throw new Error "Delimiter is unsufficient defined!"
|
||||
|
||||
#
|
||||
# @private
|
||||
#
|
||||
_encode: ()->
|
||||
{
|
||||
'type' : "Delimiter"
|
||||
'uid' : @getUid()
|
||||
'prev' : @prev_cl?.getUid()
|
||||
'next' : @next_cl?.getUid()
|
||||
}
|
||||
|
||||
parser['Delimiter'] = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
'prev' : prev
|
||||
'next' : next
|
||||
} = json
|
||||
new Delimiter uid, prev, next
|
||||
|
||||
# This is what this module exports after initializing it with the HistoryBuffer
|
||||
{
|
||||
'types' :
|
||||
'Delete' : Delete
|
||||
'Insert' : Insert
|
||||
'Delimiter': Delimiter
|
||||
'Operation': Operation
|
||||
'ImmutableObject' : ImmutableObject
|
||||
'parser' : parser
|
||||
'execution_listener' : execution_listener
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
1
build/node/Types/BasicTypes.coffee.map
Executable file
1
build/node/Types/BasicTypes.coffee.map
Executable file
File diff suppressed because one or more lines are too long
2
build/node/Types/BasicTypes.js
Normal file
2
build/node/Types/BasicTypes.js
Normal file
File diff suppressed because one or more lines are too long
1
build/node/Types/BasicTypes.js.map
Executable file
1
build/node/Types/BasicTypes.js.map
Executable file
File diff suppressed because one or more lines are too long
210
build/node/Types/JsonTypes.coffee
Normal file
210
build/node/Types/JsonTypes.coffee
Normal file
@@ -0,0 +1,210 @@
|
||||
text_types_uninitialized = require "./TextTypes"
|
||||
|
||||
module.exports = (HB)->
|
||||
text_types = text_types_uninitialized HB
|
||||
types = text_types.types
|
||||
parser = text_types.parser
|
||||
|
||||
createJsonWrapper = (_jsonType)->
|
||||
|
||||
#
|
||||
# A JsonWrapper was intended to be a convenient wrapper for the JsonType.
|
||||
# But it can make things more difficult than they are.
|
||||
# @see JsonType
|
||||
#
|
||||
# @example create a JsonWrapper
|
||||
# # You get a JsonWrapper from a JsonType by calling
|
||||
# w = yatta.value
|
||||
#
|
||||
# It creates Javascripts -getter and -setter methods for each property that JsonType maintains.
|
||||
# @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
|
||||
#
|
||||
# @example Getter Example
|
||||
# # you can access the x property of yatta by calling
|
||||
# w.x
|
||||
# # instead of
|
||||
# yatta.val('x')
|
||||
#
|
||||
# @note You can only overwrite existing values! Setting a new property won't have any effect!
|
||||
#
|
||||
# @example Setter Example
|
||||
# # you can set an existing x property of yatta by calling
|
||||
# w.x = "text"
|
||||
# # instead of
|
||||
# yatta.val('x', "text")
|
||||
#
|
||||
# In order to set a new property you have to overwrite an existing property.
|
||||
# Therefore the JsonWrapper supports a special feature that should make things more convenient
|
||||
# (we can argue about that, use the JsonType if you don't like it ;).
|
||||
# If you overwrite an object property of the JsonWrapper with a new object, it will result in a merged version of the objects.
|
||||
# Let w.p the property that is to be overwritten and o the new value. E.g. w.p = o
|
||||
# * The result has all properties of o
|
||||
# * The result has all properties of w.p if they don't occur under the same property-name in o.
|
||||
#
|
||||
# @example Conflict Example
|
||||
# yatta.value = {a : "string"}
|
||||
# w = yatta.value
|
||||
# console.log(w) # {a : "string"}
|
||||
# w.a = {a : {b : "string"}}
|
||||
# console.log(w) # {a : {b : "String"}}
|
||||
# w.a = {a : {c : 4}}
|
||||
# console.log(w) # {a : {b : "String", c : 4}}
|
||||
#
|
||||
# @example Common Pitfalls
|
||||
# w = yatta.value
|
||||
# # Setting a new property
|
||||
# w.newProperty = "Awesome"
|
||||
# console.log(w.newProperty == "Awesome") # false, w.newProperty is undefined
|
||||
# # overwrite the w object
|
||||
# w = {newProperty : "Awesome"}
|
||||
# console.log(w.newProperty == "Awesome") # true!, but ..
|
||||
# console.log(yatta.value.newProperty == "Awesome") # false, you are only allowed to set properties!
|
||||
# # The solution
|
||||
# yatta.value = {newProperty : "Awesome"}
|
||||
# console.log(w.newProperty == "Awesome") # true!
|
||||
#
|
||||
class JsonWrapper
|
||||
|
||||
#
|
||||
# @param {JsonType} jsonType Instance of the JsonType that this class wrappes.
|
||||
#
|
||||
constructor: (jsonType)->
|
||||
for name, obj of jsonType.map
|
||||
do (name, obj)->
|
||||
Object.defineProperty JsonWrapper.prototype, name,
|
||||
get : ->
|
||||
x = obj.val()
|
||||
if x instanceof JsonType
|
||||
createJsonWrapper x
|
||||
else if x instanceof types.ImmutableObject
|
||||
x.val()
|
||||
else
|
||||
x
|
||||
set : (o)->
|
||||
if o.constructor is {}.constructor
|
||||
overwrite = jsonType.val(name)
|
||||
for o_name,o_obj of o
|
||||
overwrite.val(o_name, o_obj, 'immutable')
|
||||
else
|
||||
jsonType.val(name, o, 'immutable')
|
||||
enumerable: true
|
||||
configurable: false
|
||||
new JsonWrapper _jsonType
|
||||
|
||||
#
|
||||
# Manages Object-like values.
|
||||
#
|
||||
class JsonType extends types.MapManager
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @param {Object} initial_value Create this operation with an initial value.
|
||||
# @param {String|Boolean} Whether the initial_value should be created as mutable. (Optional - see setMutableDefault)
|
||||
#
|
||||
constructor: (uid, initial_value, mutable)->
|
||||
super uid
|
||||
if initial_value?
|
||||
if typeof initial_value isnt "object"
|
||||
throw new Error "The initial value of JsonTypes must be of type Object! (current type: #{typeof initial_value})"
|
||||
for name,o of initial_value
|
||||
@val name, o, mutable
|
||||
|
||||
#
|
||||
# Whether the default is 'mutable' (true) or 'immutable' (false)
|
||||
#
|
||||
mutable_default:
|
||||
true
|
||||
|
||||
#
|
||||
# Set if the default is 'mutable' or 'immutable'
|
||||
# @param {String|Boolean} mutable Set either 'mutable' / true or 'immutable' / false
|
||||
setMutableDefault: (mutable)->
|
||||
if mutable is true or mutable is 'mutable'
|
||||
JsonType.prototype.mutable_default = true
|
||||
else if mutable is false or mutable is 'immutable'
|
||||
JsonType.prototype.mutable_default = false
|
||||
else
|
||||
throw new Error 'Set mutable either "mutable" or "immutable"!'
|
||||
'OK'
|
||||
|
||||
#
|
||||
# @overload val()
|
||||
# Get this as a Json object.
|
||||
# @return [Json]
|
||||
#
|
||||
# @overload val(name)
|
||||
# Get value of a property.
|
||||
# @param {String} name Name of the object property.
|
||||
# @return [JsonType|Word|String|Object] Depending on the value of the property. If mutable it will return a Operation-type object, if immutable it will return String/Object.
|
||||
#
|
||||
# @overload val(name, content)
|
||||
# Set a new property.
|
||||
# @param {String} name Name of the object property.
|
||||
# @param {Object|String} content Content of the object property.
|
||||
# @return [JsonType] This object. (supports chaining)
|
||||
#
|
||||
val: (name, content, mutable)->
|
||||
if typeof name is 'object'
|
||||
# Special case. First argument is an object. Then the second arg is mutable.
|
||||
# Keep that in mind when reading the following..
|
||||
for o_name,o of name
|
||||
@val(o_name,o,content)
|
||||
@
|
||||
else if name? and content?
|
||||
if mutable?
|
||||
if mutable is true or mutable is 'mutable'
|
||||
mutable = true
|
||||
else
|
||||
mutable = false
|
||||
else
|
||||
mutable = @mutable_default
|
||||
if typeof content is 'function'
|
||||
@ # Just do nothing
|
||||
else if ((not mutable) or typeof content is 'number') and content.constructor isnt Object
|
||||
obj = HB.addOperation(new types.ImmutableObject undefined, content).execute()
|
||||
super name, obj
|
||||
else
|
||||
if typeof content is 'string'
|
||||
word = HB.addOperation(new types.Word undefined).execute()
|
||||
word.insertText 0, content
|
||||
super name, word
|
||||
else if content.constructor is Object
|
||||
json = HB.addOperation(new JsonType undefined, content, mutable).execute()
|
||||
super name, json
|
||||
else
|
||||
throw new Error "You must not set #{typeof content}-types in collaborative Json-objects!"
|
||||
else
|
||||
super name, content
|
||||
|
||||
Object.defineProperty JsonType.prototype, 'value',
|
||||
get : -> createJsonWrapper @
|
||||
set : (o)->
|
||||
if o.constructor is {}.constructor
|
||||
for o_name,o_obj of o
|
||||
@val(o_name, o_obj, 'immutable')
|
||||
else
|
||||
throw new Error "You must only set Object values!"
|
||||
|
||||
#
|
||||
# @private
|
||||
#
|
||||
_encode: ()->
|
||||
{
|
||||
'type' : "JsonType"
|
||||
'uid' : @getUid()
|
||||
}
|
||||
|
||||
parser['JsonType'] = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
} = json
|
||||
new JsonType uid
|
||||
|
||||
|
||||
|
||||
|
||||
types['JsonType'] = JsonType
|
||||
|
||||
text_types
|
||||
|
||||
|
||||
1
build/node/Types/JsonTypes.coffee.map
Executable file
1
build/node/Types/JsonTypes.coffee.map
Executable file
File diff suppressed because one or more lines are too long
2
build/node/Types/JsonTypes.js
Normal file
2
build/node/Types/JsonTypes.js
Normal file
@@ -0,0 +1,2 @@
|
||||
(function(){var t,e={}.hasOwnProperty,r=function(t,r){function n(){this.constructor=t}for(var o in r)e.call(r,o)&&(t[o]=r[o]);return n.prototype=r.prototype,t.prototype=new n,t.__super__=r.prototype,t};t=require("./TextTypes"),module.exports=function(e){var n,o,u,i,a;return i=t(e),a=i.types,u=i.parser,o=function(t){var e;return new(e=function(){function t(e){var r,u,i,l;l=e.map,i=function(r,u){return Object.defineProperty(t.prototype,r,{get:function(){var t;return t=u.val(),t instanceof n?o(t):t instanceof a.ImmutableObject?t.val():t},set:function(t){var n,o,u,i;if(t.constructor==={}.constructor){u=e.val(r),i=[];for(n in t)o=t[n],i.push(u.val(n,o,"immutable"));return i}return e.val(r,t,"immutable")},enumerable:!0,configurable:!1})};for(r in l)u=l[r],i(r,u)}return t}())(t)},n=function(t){function n(t,e,r){var o,u;if(n.__super__.constructor.call(this,t),null!=e){if("object"!=typeof e)throw new Error("The initial value of JsonTypes must be of type Object! (current type: "+typeof e+")");for(o in e)u=e[o],this.val(o,u,r)}}return r(n,t),n.prototype.mutable_default=!0,n.prototype.setMutableDefault=function(t){if(t===!0||"mutable"===t)n.prototype.mutable_default=!0;else{if(t!==!1&&"immutable"!==t)throw new Error('Set mutable either "mutable" or "immutable"!');n.prototype.mutable_default=!1}return"OK"},n.prototype.val=function(t,r,o){var u,i,l,c,p;if("object"==typeof t){for(l in t)i=t[l],this.val(l,i,r);return this}if(null!=t&&null!=r){if(o=null!=o?o===!0||"mutable"===o?!0:!1:this.mutable_default,"function"==typeof r)return this;if(o&&"number"!=typeof r||r.constructor===Object){if("string"==typeof r)return p=e.addOperation(new a.Word(void 0)).execute(),p.insertText(0,r),n.__super__.val.call(this,t,p);if(r.constructor===Object)return u=e.addOperation(new n(void 0,r,o)).execute(),n.__super__.val.call(this,t,u);throw new Error("You must not set "+typeof r+"-types in collaborative Json-objects!")}return c=e.addOperation(new a.ImmutableObject(void 0,r)).execute(),n.__super__.val.call(this,t,c)}return n.__super__.val.call(this,t,r)},Object.defineProperty(n.prototype,"value",{get:function(){return o(this)},set:function(t){var e,r,n;if(t.constructor==={}.constructor){n=[];for(e in t)r=t[e],n.push(this.val(e,r,"immutable"));return n}throw new Error("You must only set Object values!")}}),n.prototype._encode=function(){return{type:"JsonType",uid:this.getUid()}},n}(a.MapManager),u.JsonType=function(t){var e;return e=t.uid,new n(e)},a.JsonType=n,i}}).call(this);
|
||||
//# sourceMappingURL=../Types/JsonTypes.js.map
|
||||
1
build/node/Types/JsonTypes.js.map
Executable file
1
build/node/Types/JsonTypes.js.map
Executable file
File diff suppressed because one or more lines are too long
310
build/node/Types/StructuredTypes.coffee
Normal file
310
build/node/Types/StructuredTypes.coffee
Normal file
@@ -0,0 +1,310 @@
|
||||
basic_types_uninitialized = require "./BasicTypes"
|
||||
|
||||
module.exports = (HB)->
|
||||
basic_types = basic_types_uninitialized HB
|
||||
types = basic_types.types
|
||||
parser = basic_types.parser
|
||||
|
||||
#
|
||||
# Manages map like objects. E.g. Json-Type and XML attributes.
|
||||
#
|
||||
class MapManager extends types.Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
#
|
||||
constructor: (uid)->
|
||||
@map = {}
|
||||
super uid
|
||||
|
||||
#
|
||||
# @see JsonTypes.val
|
||||
#
|
||||
val: (name, content)->
|
||||
if content?
|
||||
if not @map[name]?
|
||||
HB.addOperation(new AddName undefined, @, name).execute()
|
||||
@map[name].replace content
|
||||
@
|
||||
else if name?
|
||||
obj = @map[name]?.val()
|
||||
if obj instanceof types.ImmutableObject
|
||||
obj.val()
|
||||
else
|
||||
obj
|
||||
else
|
||||
result = {}
|
||||
for name,o of @map
|
||||
obj = o.val()
|
||||
if obj instanceof types.ImmutableObject or obj instanceof MapManager
|
||||
obj = obj.val()
|
||||
result[name] = obj
|
||||
result
|
||||
|
||||
#
|
||||
# When a new property in a map manager is created, then the uids of the inserted Operations
|
||||
# must be unique (think about concurrent operations). Therefore only an AddName operation is allowed to
|
||||
# add a property in a MapManager. If two AddName operations on the same MapManager name happen concurrently
|
||||
# only one will AddName operation will be executed.
|
||||
#
|
||||
class AddName extends types.Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @param {Object} map_manager Uid or reference to the MapManager.
|
||||
# @param {String} name Name of the property that will be added.
|
||||
#
|
||||
constructor: (uid, map_manager, @name)->
|
||||
@saveOperation 'map_manager', map_manager
|
||||
super uid
|
||||
|
||||
#
|
||||
# If map_manager doesn't have the property name, then add it.
|
||||
# The ReplaceManager that is being written on the property is unique
|
||||
# in such a way that if AddName is executed (from another peer) it will
|
||||
# always have the same result (ReplaceManager, and its beginning and end are the same)
|
||||
#
|
||||
execute: ()->
|
||||
if not @validateSavedOperations()
|
||||
return false
|
||||
else
|
||||
uid_r = @map_manager.getUid()
|
||||
uid_r.op_number = "_#{uid_r.op_number}_RM_#{@name}"
|
||||
if not HB.getOperation(uid_r)?
|
||||
uid_beg = @map_manager.getUid()
|
||||
uid_beg.op_number = "_#{uid_beg.op_number}_RM_#{@name}_beginning"
|
||||
uid_end = @map_manager.getUid()
|
||||
uid_end.op_number = "_#{uid_end.op_number}_RM_#{@name}_end"
|
||||
beg = HB.addOperation(new types.Delimiter uid_beg, undefined, uid_end).execute()
|
||||
end = HB.addOperation(new types.Delimiter uid_end, beg, undefined).execute()
|
||||
#beg.execute()
|
||||
@map_manager.map[@name] = HB.addOperation(new ReplaceManager undefined, uid_r, beg, end).execute()
|
||||
super
|
||||
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: ()->
|
||||
{
|
||||
'type' : "AddName"
|
||||
'uid' : @getUid()
|
||||
'map_manager' : @map_manager.getUid()
|
||||
'name' : @name
|
||||
}
|
||||
|
||||
parser['AddName'] = (json)->
|
||||
{
|
||||
'map_manager' : map_manager
|
||||
'uid' : uid
|
||||
'name' : name
|
||||
} = json
|
||||
new AddName uid, map_manager, name
|
||||
|
||||
#
|
||||
# Manages a list of Insert-type operations.
|
||||
#
|
||||
class ListManager extends types.Insert
|
||||
|
||||
#
|
||||
# A ListManager maintains a non-empty list that has a beginning and an end (both Delimiters!)
|
||||
# @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: (uid, beginning, end, prev, next, origin)->
|
||||
if beginning? and end?
|
||||
@saveOperation 'beginning', beginning
|
||||
@saveOperation 'end', end
|
||||
else
|
||||
@beginning = HB.addOperation new types.Delimiter undefined, undefined, undefined
|
||||
@end = HB.addOperation new types.Delimiter undefined, @beginning, undefined
|
||||
@beginning.next_cl = @end
|
||||
@beginning.execute()
|
||||
@end.execute()
|
||||
super uid, prev, next, origin
|
||||
|
||||
|
||||
# Get the element previous to the delemiter at the end
|
||||
getLastOperation: ()->
|
||||
@end.prev_cl
|
||||
|
||||
# similar to the above
|
||||
getFirstOperation: ()->
|
||||
@beginning.next_cl
|
||||
|
||||
# Transforms the the list to an array
|
||||
# Doesn't return left-right delimiter.
|
||||
toArray: ()->
|
||||
o = @beginning.next_cl
|
||||
result = []
|
||||
while o isnt @end
|
||||
result.push o
|
||||
o = o.next_cl
|
||||
result
|
||||
|
||||
#
|
||||
# Retrieves the x-th not deleted element.
|
||||
#
|
||||
getOperationByPosition: (position)->
|
||||
o = @beginning.next_cl
|
||||
if position > 0
|
||||
while true
|
||||
o = o.next_cl
|
||||
if not o.isDeleted()
|
||||
position -= 1
|
||||
if position is 0
|
||||
break
|
||||
if o instanceof types.Delimiter
|
||||
throw new Error "position parameter exceeded the length of the document!"
|
||||
o
|
||||
|
||||
#
|
||||
# Adds support for replace. The ReplaceManager manages Replaceable operations.
|
||||
# Each Replaceable holds a value that is now replaceable.
|
||||
#
|
||||
# The Word-type has implemented support for replace
|
||||
# @see Word
|
||||
#
|
||||
class ReplaceManager extends ListManager
|
||||
#
|
||||
# @param {Operation} initial_content Initialize this with a Replaceable that holds the initial_content.
|
||||
# @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: (initial_content, uid, beginning, end, prev, next, origin)->
|
||||
super uid, beginning, end, prev, next, origin
|
||||
if initial_content?
|
||||
@replace initial_content
|
||||
|
||||
#
|
||||
# Replace the existing word with a new word.
|
||||
#
|
||||
replace: (content)->
|
||||
o = @getLastOperation()
|
||||
op = new Replaceable content, @, undefined, o, o.next_cl
|
||||
HB.addOperation(op).execute()
|
||||
|
||||
#
|
||||
# Get the value of this Word
|
||||
# @return {String}
|
||||
#
|
||||
val: ()->
|
||||
o = @getLastOperation()
|
||||
if o instanceof types.Delimiter
|
||||
throw new Error "dtrn"
|
||||
o.val()
|
||||
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: ()->
|
||||
json =
|
||||
{
|
||||
'type': "ReplaceManager"
|
||||
'uid' : @getUid()
|
||||
'beginning' : @beginning.getUid()
|
||||
'end' : @end.getUid()
|
||||
}
|
||||
if @prev_cl? and @next_cl?
|
||||
json['prev'] = @prev_cl.getUid()
|
||||
json['next'] = @next_cl.getUid()
|
||||
if @origin? and @origin isnt @prev_cl
|
||||
json["origin"] = @origin.getUid()
|
||||
json
|
||||
|
||||
parser["ReplaceManager"] = (json)->
|
||||
{
|
||||
'content' : content
|
||||
'uid' : uid
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
'beginning' : beginning
|
||||
'end' : end
|
||||
} = json
|
||||
new ReplaceManager content, uid, beginning, end, prev, next, origin
|
||||
|
||||
|
||||
#
|
||||
# The ReplaceManager manages Replaceables.
|
||||
# @see ReplaceManager
|
||||
#
|
||||
class Replaceable extends types.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: (content, parent, uid, prev, next, origin)->
|
||||
@saveOperation 'content', content
|
||||
@saveOperation 'parent', parent
|
||||
if not (prev? and next? and content?)
|
||||
throw new Error "You must define content, prev, and next for Replaceable-types!"
|
||||
super uid, prev, next, origin
|
||||
|
||||
#
|
||||
# Return the content that this operation holds.
|
||||
#
|
||||
val: ()->
|
||||
@content
|
||||
|
||||
#
|
||||
# Replace the content of this replaceable with new content.
|
||||
#
|
||||
replace: (content)->
|
||||
@parent.replace content
|
||||
|
||||
#
|
||||
# If possible set the replace manager in the content.
|
||||
# @see Word.setReplaceManager
|
||||
#
|
||||
execute: ()->
|
||||
if not @validateSavedOperations()
|
||||
return false
|
||||
else
|
||||
@content.setReplaceManager?(@parent)
|
||||
super
|
||||
@
|
||||
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: ()->
|
||||
json =
|
||||
{
|
||||
'type': "Replaceable"
|
||||
'content': @content.getUid()
|
||||
'ReplaceManager' : @parent.getUid()
|
||||
'prev': @prev_cl.getUid()
|
||||
'next': @next_cl.getUid()
|
||||
'uid' : @getUid()
|
||||
}
|
||||
if @origin? and @origin isnt @prev_cl
|
||||
json["origin"] = @origin.getUid()
|
||||
json
|
||||
|
||||
parser["Replaceable"] = (json)->
|
||||
{
|
||||
'content' : content
|
||||
'ReplaceManager' : parent
|
||||
'uid' : uid
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
} = json
|
||||
new Replaceable content, parent, uid, prev, next, origin
|
||||
|
||||
|
||||
|
||||
types['ListManager'] = ListManager
|
||||
types['MapManager'] = MapManager
|
||||
types['ReplaceManager'] = ReplaceManager
|
||||
types['Replaceable'] = Replaceable
|
||||
|
||||
basic_types
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1
build/node/Types/StructuredTypes.coffee.map
Executable file
1
build/node/Types/StructuredTypes.coffee.map
Executable file
File diff suppressed because one or more lines are too long
2
build/node/Types/StructuredTypes.js
Normal file
2
build/node/Types/StructuredTypes.js
Normal file
@@ -0,0 +1,2 @@
|
||||
(function(){var e,t={}.hasOwnProperty,n=function(e,n){function i(){this.constructor=e}for(var r in n)t.call(n,r)&&(e[r]=n[r]);return i.prototype=n.prototype,e.prototype=new i,e.__super__=n.prototype,e};e=require("./BasicTypes"),module.exports=function(t){var i,r,a,o,p,s,u,c;return s=e(t),c=s.types,u=s.parser,a=function(e){function r(e){this.map={},r.__super__.constructor.call(this,e)}return n(r,e),r.prototype.val=function(e,n){var a,o,p,s,u;if(null!=n)return null==this.map[e]&&t.addOperation(new i(void 0,this,e)).execute(),this.map[e].replace(n),this;if(null!=e)return o=null!=(s=this.map[e])?s.val():void 0,o instanceof c.ImmutableObject?o.val():o;p={},u=this.map;for(e in u)a=u[e],o=a.val(),(o instanceof c.ImmutableObject||o instanceof r)&&(o=o.val()),p[e]=o;return p},r}(c.Operation),i=function(e){function i(e,t,n){this.name=n,this.saveOperation("map_manager",t),i.__super__.constructor.call(this,e)}return n(i,e),i.prototype.execute=function(){var e,n,r,a,p;return this.validateSavedOperations()?(p=this.map_manager.getUid(),p.op_number="_"+p.op_number+"_RM_"+this.name,null==t.getOperation(p)&&(r=this.map_manager.getUid(),r.op_number="_"+r.op_number+"_RM_"+this.name+"_beginning",a=this.map_manager.getUid(),a.op_number="_"+a.op_number+"_RM_"+this.name+"_end",e=t.addOperation(new c.Delimiter(r,void 0,a)).execute(),n=t.addOperation(new c.Delimiter(a,e,void 0)).execute(),this.map_manager.map[this.name]=t.addOperation(new o(void 0,p,e,n)).execute()),i.__super__.execute.apply(this,arguments)):!1},i.prototype._encode=function(){return{type:"AddName",uid:this.getUid(),map_manager:this.map_manager.getUid(),name:this.name}},i}(c.Operation),u.AddName=function(e){var t,n,r;return t=e.map_manager,r=e.uid,n=e.name,new i(r,t,n)},r=function(e){function i(e,n,r,a,o,p){null!=n&&null!=r?(this.saveOperation("beginning",n),this.saveOperation("end",r)):(this.beginning=t.addOperation(new c.Delimiter(void 0,void 0,void 0)),this.end=t.addOperation(new c.Delimiter(void 0,this.beginning,void 0)),this.beginning.next_cl=this.end,this.beginning.execute(),this.end.execute()),i.__super__.constructor.call(this,e,a,o,p)}return n(i,e),i.prototype.getLastOperation=function(){return this.end.prev_cl},i.prototype.getFirstOperation=function(){return this.beginning.next_cl},i.prototype.toArray=function(){var e,t;for(e=this.beginning.next_cl,t=[];e!==this.end;)t.push(e),e=e.next_cl;return t},i.prototype.getOperationByPosition=function(e){var t;if(t=this.beginning.next_cl,e>0)for(;;){if(t=t.next_cl,t.isDeleted()||(e-=1),0===e)break;if(t instanceof c.Delimiter)throw new Error("position parameter exceeded the length of the document!")}return t},i}(c.Insert),o=function(e){function i(e,t,n,r,a,o,p){i.__super__.constructor.call(this,t,n,r,a,o,p),null!=e&&this.replace(e)}return n(i,e),i.prototype.replace=function(e){var n,i;return n=this.getLastOperation(),i=new p(e,this,void 0,n,n.next_cl),t.addOperation(i).execute()},i.prototype.val=function(){var e;if(e=this.getLastOperation(),e instanceof c.Delimiter)throw new Error("dtrn");return e.val()},i.prototype._encode=function(){var e;return e={type:"ReplaceManager",uid:this.getUid(),beginning:this.beginning.getUid(),end:this.end.getUid()},null!=this.prev_cl&&null!=this.next_cl&&(e.prev=this.prev_cl.getUid(),e.next=this.next_cl.getUid()),null!=this.origin&&this.origin!==this.prev_cl&&(e.origin=this.origin.getUid()),e},i}(r),u.ReplaceManager=function(e){var t,n,i,r,a,p,s;return n=e.content,s=e.uid,p=e.prev,r=e.next,a=e.origin,t=e.beginning,i=e.end,new o(n,s,t,i,p,r,a)},p=function(e){function t(e,n,i,r,a,o){if(this.saveOperation("content",e),this.saveOperation("parent",n),null==r||null==a||null==e)throw new Error("You must define content, prev, and next for Replaceable-types!");t.__super__.constructor.call(this,i,r,a,o)}return n(t,e),t.prototype.val=function(){return this.content},t.prototype.replace=function(e){return this.parent.replace(e)},t.prototype.execute=function(){var e;return this.validateSavedOperations()?("function"==typeof(e=this.content).setReplaceManager&&e.setReplaceManager(this.parent),t.__super__.execute.apply(this,arguments),this):!1},t.prototype._encode=function(){var e;return e={type:"Replaceable",content:this.content.getUid(),ReplaceManager:this.parent.getUid(),prev:this.prev_cl.getUid(),next:this.next_cl.getUid(),uid:this.getUid()},null!=this.origin&&this.origin!==this.prev_cl&&(e.origin=this.origin.getUid()),e},t}(c.Insert),u.Replaceable=function(e){var t,n,i,r,a,o;return t=e.content,r=e.ReplaceManager,o=e.uid,a=e.prev,n=e.next,i=e.origin,new p(t,r,o,a,n,i)},c.ListManager=r,c.MapManager=a,c.ReplaceManager=o,c.Replaceable=p,s}}).call(this);
|
||||
//# sourceMappingURL=../Types/StructuredTypes.js.map
|
||||
1
build/node/Types/StructuredTypes.js.map
Executable file
1
build/node/Types/StructuredTypes.js.map
Executable file
File diff suppressed because one or more lines are too long
177
build/node/Types/TextTypes.coffee
Normal file
177
build/node/Types/TextTypes.coffee
Normal file
@@ -0,0 +1,177 @@
|
||||
structured_types_uninitialized = require "./StructuredTypes"
|
||||
|
||||
module.exports = (HB)->
|
||||
structured_types = structured_types_uninitialized HB
|
||||
types = structured_types.types
|
||||
parser = structured_types.parser
|
||||
|
||||
#
|
||||
# At the moment TextDelete type equals the Delete type in BasicTypes.
|
||||
# @see BasicTypes.Delete
|
||||
#
|
||||
class TextDelete extends types.Delete
|
||||
parser["TextDelete"] = parser["Delete"]
|
||||
|
||||
#
|
||||
# Extends the basic Insert type to an operation that holds a text value
|
||||
#
|
||||
class TextInsert extends types.Insert
|
||||
#
|
||||
# @param {String} content The content of this Insert-type Operation. Usually you restrict the length of content to size 1
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
#
|
||||
constructor: (@content, uid, prev, next, origin)->
|
||||
if not (prev? and next?)
|
||||
throw new Error "You must define prev, and next for TextInsert-types!"
|
||||
super uid, prev, next, origin
|
||||
#
|
||||
# Retrieve the effective length of the $content of this operation.
|
||||
#
|
||||
getLength: ()->
|
||||
if @isDeleted()
|
||||
0
|
||||
else
|
||||
@content.length
|
||||
|
||||
#
|
||||
# The result will be concatenated with the results from the other insert operations
|
||||
# in order to retrieve the content of the engine.
|
||||
# @see HistoryBuffer.toExecutedArray
|
||||
#
|
||||
val: (current_position)->
|
||||
if @isDeleted()
|
||||
""
|
||||
else
|
||||
@content
|
||||
|
||||
#
|
||||
# Convert all relevant information of this operation to the json-format.
|
||||
# This result can be send to other clients.
|
||||
#
|
||||
_encode: ()->
|
||||
json =
|
||||
{
|
||||
'type': "TextInsert"
|
||||
'content': @content
|
||||
'uid' : @getUid()
|
||||
'prev': @prev_cl.getUid()
|
||||
'next': @next_cl.getUid()
|
||||
}
|
||||
if @origin? and @origin isnt @prev_cl
|
||||
json["origin"] = @origin.getUid()
|
||||
json
|
||||
|
||||
parser["TextInsert"] = (json)->
|
||||
{
|
||||
'content' : content
|
||||
'uid' : uid
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
} = json
|
||||
new TextInsert content, uid, prev, next, origin
|
||||
|
||||
#
|
||||
# Handles a Text-like data structures with support for insertText/deleteText at a word-position.
|
||||
#
|
||||
class Word extends types.ListManager
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
#
|
||||
constructor: (uid, beginning, end, prev, next, origin)->
|
||||
super uid, beginning, end, prev, next, origin
|
||||
|
||||
#
|
||||
# Inserts a string into the word
|
||||
#
|
||||
insertText: (position, content)->
|
||||
o = @getOperationByPosition position
|
||||
for c in content
|
||||
op = new TextInsert c, undefined, o.prev_cl, o
|
||||
HB.addOperation(op).execute()
|
||||
|
||||
#
|
||||
# Deletes a part of the word.
|
||||
#
|
||||
deleteText: (position, length)->
|
||||
o = @getOperationByPosition position
|
||||
|
||||
for i in [0...length]
|
||||
d = HB.addOperation(new TextDelete undefined, o).execute()
|
||||
o = o.next_cl
|
||||
while o.isDeleted()
|
||||
if o instanceof types.Delimiter
|
||||
throw new Error "You can't delete more than there is.."
|
||||
o = o.next_cl
|
||||
d._encode()
|
||||
|
||||
#
|
||||
# Replace the content of this word with another one. Concurrent replacements are not merged!
|
||||
# Only one of the replacements will be used.
|
||||
#
|
||||
# Can only be used if the ReplaceManager was set!
|
||||
# @see Word.setReplaceManager
|
||||
#
|
||||
replaceText: (text)->
|
||||
if @replace_manager?
|
||||
word = HB.addOperation(new Word undefined).execute()
|
||||
word.insertText 0, text
|
||||
@replace_manager.replace(word)
|
||||
else
|
||||
throw new Error "This type is currently not maintained by a ReplaceManager!"
|
||||
|
||||
#
|
||||
# @returns [Json] A Json object.
|
||||
#
|
||||
val: ()->
|
||||
c = for o in @toArray()
|
||||
if o.val?
|
||||
o.val()
|
||||
else
|
||||
""
|
||||
c.join('')
|
||||
|
||||
#
|
||||
# In most cases you would embed a Word in a Replaceable, wich is handled by the ReplaceManager in order
|
||||
# to provide replace functionality.
|
||||
#
|
||||
setReplaceManager: (op)->
|
||||
@saveOperation 'replace_manager', op
|
||||
@validateSavedOperations
|
||||
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: ()->
|
||||
json = {
|
||||
'type': "Word"
|
||||
'uid' : @getUid()
|
||||
'beginning' : @beginning.getUid()
|
||||
'end' : @end.getUid()
|
||||
}
|
||||
if @prev_cl?
|
||||
json['prev'] = @prev_cl.getUid()
|
||||
if @next_cl?
|
||||
json['next'] = @next_cl.getUid()
|
||||
if @origin? and @origin isnt @prev_cl
|
||||
json["origin"] = @origin.getUid()
|
||||
json
|
||||
|
||||
parser['Word'] = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
'beginning' : beginning
|
||||
'end' : end
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
} = json
|
||||
new Word uid, beginning, end, prev, next, origin
|
||||
|
||||
types['TextInsert'] = TextInsert
|
||||
types['TextDelete'] = TextDelete
|
||||
types['Word'] = Word
|
||||
structured_types
|
||||
|
||||
|
||||
1
build/node/Types/TextTypes.coffee.map
Executable file
1
build/node/Types/TextTypes.coffee.map
Executable file
File diff suppressed because one or more lines are too long
2
build/node/Types/TextTypes.js
Normal file
2
build/node/Types/TextTypes.js
Normal file
@@ -0,0 +1,2 @@
|
||||
(function(){var t,e={}.hasOwnProperty,n=function(t,n){function r(){this.constructor=t}for(var i in n)e.call(n,i)&&(t[i]=n[i]);return r.prototype=n.prototype,t.prototype=new r,t.__super__=n.prototype,t};t=require("./StructuredTypes"),module.exports=function(e){var r,i,o,s,u,c;return u=t(e),c=u.types,s=u.parser,r=function(t){function e(){return e.__super__.constructor.apply(this,arguments)}return n(e,t),e}(c.Delete),s.TextDelete=s.Delete,i=function(t){function e(t,n,r,i,o){if(this.content=t,null==r||null==i)throw new Error("You must define prev, and next for TextInsert-types!");e.__super__.constructor.call(this,n,r,i,o)}return n(e,t),e.prototype.getLength=function(){return this.isDeleted()?0:this.content.length},e.prototype.val=function(){return this.isDeleted()?"":this.content},e.prototype._encode=function(){var t;return t={type:"TextInsert",content:this.content,uid:this.getUid(),prev:this.prev_cl.getUid(),next:this.next_cl.getUid()},null!=this.origin&&this.origin!==this.prev_cl&&(t.origin=this.origin.getUid()),t},e}(c.Insert),s.TextInsert=function(t){var e,n,r,o,s;return e=t.content,s=t.uid,o=t.prev,n=t.next,r=t.origin,new i(e,s,o,n,r)},o=function(t){function o(t,e,n,r,i,s){o.__super__.constructor.call(this,t,e,n,r,i,s)}return n(o,t),o.prototype.insertText=function(t,n){var r,o,s,u,c,p;for(o=this.getOperationByPosition(t),p=[],u=0,c=n.length;c>u;u++)r=n[u],s=new i(r,void 0,o.prev_cl,o),p.push(e.addOperation(s).execute());return p},o.prototype.deleteText=function(t,n){var i,o,s,u,p;for(s=this.getOperationByPosition(t),p=[],o=u=0;n>=0?n>u:u>n;o=n>=0?++u:--u){for(i=e.addOperation(new r(void 0,s)).execute(),s=s.next_cl;s.isDeleted();){if(s instanceof c.Delimiter)throw new Error("You can't delete more than there is..");s=s.next_cl}p.push(i._encode())}return p},o.prototype.replaceText=function(t){var n;if(null!=this.replace_manager)return n=e.addOperation(new o(void 0)).execute(),n.insertText(0,t),this.replace_manager.replace(n);throw new Error("This type is currently not maintained by a ReplaceManager!")},o.prototype.val=function(){var t,e;return t=function(){var t,n,r,i;for(r=this.toArray(),i=[],t=0,n=r.length;n>t;t++)e=r[t],i.push(null!=e.val?e.val():"");return i}.call(this),t.join("")},o.prototype.setReplaceManager=function(t){return this.saveOperation("replace_manager",t),this.validateSavedOperations},o.prototype._encode=function(){var t;return t={type:"Word",uid:this.getUid(),beginning:this.beginning.getUid(),end:this.end.getUid()},null!=this.prev_cl&&(t.prev=this.prev_cl.getUid()),null!=this.next_cl&&(t.next=this.next_cl.getUid()),null!=this.origin&&this.origin!==this.prev_cl&&(t.origin=this.origin.getUid()),t},o}(c.ListManager),s.Word=function(t){var e,n,r,i,s,u;return u=t.uid,e=t.beginning,n=t.end,s=t.prev,r=t.next,i=t.origin,new o(u,e,n,s,r,i)},c.TextInsert=i,c.TextDelete=r,c.Word=o,u}}).call(this);
|
||||
//# sourceMappingURL=../Types/TextTypes.js.map
|
||||
1
build/node/Types/TextTypes.js.map
Executable file
1
build/node/Types/TextTypes.js.map
Executable file
File diff suppressed because one or more lines are too long
0
build/node/Types/XmlTypes.coffee
Normal file
0
build/node/Types/XmlTypes.coffee
Normal file
1
build/node/Types/XmlTypes.coffee.map
Executable file
1
build/node/Types/XmlTypes.coffee.map
Executable file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Types/XmlTypes.coffee","names":[],"mappings":"","sources":["Types/XmlTypes.coffee"],"sourcesContent":[""],"sourceRoot":"/source/"}
|
||||
2
build/node/Types/XmlTypes.js
Normal file
2
build/node/Types/XmlTypes.js
Normal file
@@ -0,0 +1,2 @@
|
||||
(function(){}).call(this);
|
||||
//# sourceMappingURL=../Types/XmlTypes.js.map
|
||||
1
build/node/Types/XmlTypes.js.map
Executable file
1
build/node/Types/XmlTypes.js.map
Executable file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Types/XmlTypes.js","sources":["Types/XmlTypes.coffee"],"names":[],"mappings":"CA8KkB","sourcesContent":[""],"sourceRoot":"/source/"}
|
||||
Reference in New Issue
Block a user