Introduced a new model for custom collaborative types.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
module.exports = (HB)->
|
||||
# @see Engine.parse
|
||||
parser = {}
|
||||
types = {}
|
||||
execution_listener = []
|
||||
|
||||
#
|
||||
@@ -16,7 +16,7 @@ module.exports = (HB)->
|
||||
#
|
||||
# Furthermore an encodable operation has a parser. We extend the parser object in order to parse encoded operations.
|
||||
#
|
||||
class Operation
|
||||
class types.Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier.
|
||||
@@ -192,12 +192,11 @@ module.exports = (HB)->
|
||||
@unchecked = uninstantiated
|
||||
success
|
||||
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# A simple Delete-type operation that deletes an operation.
|
||||
#
|
||||
class Delete extends Operation
|
||||
class types.Delete extends types.Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
@@ -237,12 +236,12 @@ module.exports = (HB)->
|
||||
#
|
||||
# Define how to parse Delete operations.
|
||||
#
|
||||
parser['Delete'] = (o)->
|
||||
types.Delete.parse = (o)->
|
||||
{
|
||||
'uid' : uid
|
||||
'deletes': deletes_uid
|
||||
} = o
|
||||
new Delete uid, deletes_uid
|
||||
new this(uid, deletes_uid)
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
@@ -254,7 +253,7 @@ module.exports = (HB)->
|
||||
# - 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
|
||||
class types.Insert extends types.Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
@@ -422,7 +421,7 @@ module.exports = (HB)->
|
||||
position = 0
|
||||
prev = @prev_cl
|
||||
while true
|
||||
if prev instanceof Delimiter
|
||||
if prev instanceof types.Delimiter
|
||||
break
|
||||
if not prev.isDeleted()
|
||||
position++
|
||||
@@ -433,7 +432,7 @@ module.exports = (HB)->
|
||||
# @nodoc
|
||||
# Defines an object that is cannot be changed. You can use this to set an immutable string, or a number.
|
||||
#
|
||||
class ImmutableObject extends Operation
|
||||
class types.ImmutableObject extends types.Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
@@ -455,18 +454,18 @@ module.exports = (HB)->
|
||||
#
|
||||
_encode: ()->
|
||||
json = {
|
||||
'type': "ImmutableObject"
|
||||
'type': @type
|
||||
'uid' : @getUid()
|
||||
'content' : @content
|
||||
}
|
||||
json
|
||||
|
||||
parser['ImmutableObject'] = (json)->
|
||||
types.ImmutableObject.parse = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
'content' : content
|
||||
} = json
|
||||
new ImmutableObject uid, content
|
||||
new this(uid, content)
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
@@ -474,7 +473,7 @@ module.exports = (HB)->
|
||||
# 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
|
||||
class types.Delimiter extends types.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)
|
||||
@@ -527,29 +526,23 @@ module.exports = (HB)->
|
||||
#
|
||||
_encode: ()->
|
||||
{
|
||||
'type' : "Delimiter"
|
||||
'type' : @type
|
||||
'uid' : @getUid()
|
||||
'prev' : @prev_cl?.getUid()
|
||||
'next' : @next_cl?.getUid()
|
||||
}
|
||||
|
||||
parser['Delimiter'] = (json)->
|
||||
types.Delimiter.parse = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
'prev' : prev
|
||||
'next' : next
|
||||
} = json
|
||||
new Delimiter uid, prev, next
|
||||
new this(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
|
||||
'types' : types
|
||||
'execution_listener' : execution_listener
|
||||
}
|
||||
|
||||
|
||||
@@ -3,100 +3,11 @@ text_types_uninitialized = require "./TextTypes"
|
||||
module.exports = (HB)->
|
||||
text_types = text_types_uninitialized HB
|
||||
types = text_types.types
|
||||
parser = text_types.parser
|
||||
|
||||
createJsonTypeWrapper = (_jsonType)->
|
||||
|
||||
#
|
||||
# @note EXPERIMENTAL
|
||||
#
|
||||
# A JsonTypeWrapper 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 JsonTypeWrapper
|
||||
# # You get a JsonTypeWrapper 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 JsonTypeWrapper 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 JsonTypeWrapper with a new object, it will result in a merged version of the objects.
|
||||
# Let `yatta.value.p` the property that is to be overwritten and o the new value. E.g. `yatta.value.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 JsonTypeWrapper
|
||||
|
||||
#
|
||||
# @param {JsonType} jsonType Instance of the JsonType that this class wrappes.
|
||||
#
|
||||
constructor: (jsonType)->
|
||||
for name, obj of jsonType.map
|
||||
do (name, obj)->
|
||||
Object.defineProperty JsonTypeWrapper.prototype, name,
|
||||
get : ->
|
||||
x = obj.val()
|
||||
if x instanceof JsonType
|
||||
createJsonTypeWrapper x
|
||||
else if x instanceof types.ImmutableObject
|
||||
x.val()
|
||||
else
|
||||
x
|
||||
set : (o)->
|
||||
overwrite = jsonType.val(name)
|
||||
if o.constructor is {}.constructor and overwrite instanceof types.Operation
|
||||
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 JsonTypeWrapper _jsonType
|
||||
|
||||
#
|
||||
# Manages Object-like values.
|
||||
#
|
||||
class JsonType extends types.MapManager
|
||||
class types.Object extends types.MapManager
|
||||
|
||||
#
|
||||
# Identifies this class.
|
||||
@@ -104,11 +15,11 @@ module.exports = (HB)->
|
||||
#
|
||||
# @example
|
||||
# var x = yatta.val('unknown')
|
||||
# if (x.type === "JsonType") {
|
||||
# if (x.type === "Object") {
|
||||
# console.log JSON.stringify(x.toJson())
|
||||
# }
|
||||
#
|
||||
type: "JsonType"
|
||||
type: "Object"
|
||||
|
||||
applyDelete: ()->
|
||||
super()
|
||||
@@ -130,7 +41,7 @@ module.exports = (HB)->
|
||||
val = @val()
|
||||
json = {}
|
||||
for name, o of val
|
||||
if o instanceof JsonType
|
||||
if o instanceof types.Object
|
||||
json[name] = o.toJson(transform_to_value)
|
||||
else if transform_to_value and o instanceof types.Operation
|
||||
json[name] = o.val()
|
||||
@@ -171,24 +82,6 @@ module.exports = (HB)->
|
||||
changedBy:event.changedBy
|
||||
@bound_json
|
||||
|
||||
#
|
||||
# Whether the default is 'mutable' (true) or 'immutable' (false)
|
||||
#
|
||||
mutable_default:
|
||||
false
|
||||
|
||||
#
|
||||
# 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.
|
||||
@@ -197,70 +90,56 @@ module.exports = (HB)->
|
||||
# @overload val(name)
|
||||
# Get value of a property.
|
||||
# @param {String} name Name of the object property.
|
||||
# @return [JsonType|WordType|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.
|
||||
# @return [Object Type||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)
|
||||
# @return [Object Type] This object. (supports chaining)
|
||||
#
|
||||
val: (name, content, mutable)->
|
||||
val: (name, content)->
|
||||
if name? and arguments.length > 1
|
||||
if mutable?
|
||||
if mutable is true or mutable is 'mutable'
|
||||
mutable = true
|
||||
if content? and content.constructor?
|
||||
type = types[content.constructor.name]
|
||||
if type? and type.create?
|
||||
args = []
|
||||
for i in [1...arguments.length]
|
||||
args.push arguments[i]
|
||||
o = type.create.apply null, args
|
||||
super name, o
|
||||
else
|
||||
mutable = false
|
||||
throw new Error "The #{content.constructor.name}-type is not (yet) supported in Yatta."
|
||||
else
|
||||
mutable = @mutable_default
|
||||
if typeof content is 'function'
|
||||
@ # Just do nothing
|
||||
else if (not content?) or (((not mutable) or typeof content is 'number') and content.constructor isnt Object)
|
||||
super name, (new types.ImmutableObject undefined, content).execute()
|
||||
else
|
||||
if typeof content is 'string'
|
||||
word = (new types.WordType undefined).execute()
|
||||
word.insertText 0, content
|
||||
super name, word
|
||||
else if content.constructor is Object
|
||||
json = new JsonType().execute()
|
||||
for n,o of content
|
||||
json.val n, o, mutable
|
||||
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 : -> createJsonTypeWrapper @
|
||||
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!"
|
||||
else # is this even necessary ? I have to define every type anyway.. (see Number type below)
|
||||
super name
|
||||
|
||||
#
|
||||
# @private
|
||||
#
|
||||
_encode: ()->
|
||||
{
|
||||
'type' : "JsonType"
|
||||
'type' : @type
|
||||
'uid' : @getUid()
|
||||
}
|
||||
|
||||
parser['JsonType'] = (json)->
|
||||
types.Object.parse = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
} = json
|
||||
new JsonType uid
|
||||
new this(uid)
|
||||
|
||||
types.Object.create = (content, mutable)->
|
||||
json = new types.Object().execute()
|
||||
for n,o of content
|
||||
json.val n, o, mutable
|
||||
json
|
||||
|
||||
|
||||
|
||||
|
||||
types['JsonType'] = JsonType
|
||||
types.Number = {}
|
||||
types.Number.create = (content)->
|
||||
(new types.ImmutableObject undefined, content).execute()
|
||||
|
||||
text_types
|
||||
|
||||
|
||||
@@ -3,13 +3,12 @@ basic_types_uninitialized = require "./BasicTypes"
|
||||
module.exports = (HB)->
|
||||
basic_types = basic_types_uninitialized HB
|
||||
types = basic_types.types
|
||||
parser = basic_types.parser
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# Manages map like objects. E.g. Json-Type and XML attributes.
|
||||
#
|
||||
class MapManager extends types.Operation
|
||||
class types.MapManager extends types.Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
@@ -69,7 +68,7 @@ module.exports = (HB)->
|
||||
rm_uid =
|
||||
noOperation: true
|
||||
alt: map_uid
|
||||
rm = new ReplaceManager event_properties, event_this, rm_uid # this operation shall not be saved in the HB
|
||||
rm = new types.ReplaceManager event_properties, event_this, rm_uid # this operation shall not be saved in the HB
|
||||
@map[property_name] = rm
|
||||
rm.setParent @, property_name
|
||||
rm.execute()
|
||||
@@ -79,7 +78,7 @@ module.exports = (HB)->
|
||||
# @nodoc
|
||||
# Manages a list of Insert-type operations.
|
||||
#
|
||||
class ListManager extends types.Operation
|
||||
class types.ListManager extends types.Operation
|
||||
|
||||
#
|
||||
# A ListManager maintains a non-empty list that has a beginning and an end (both Delimiters!)
|
||||
@@ -156,10 +155,10 @@ module.exports = (HB)->
|
||||
# Adds support for replace. The ReplaceManager manages Replaceable operations.
|
||||
# Each Replaceable holds a value that is now replaceable.
|
||||
#
|
||||
# The WordType-type has implemented support for replace
|
||||
# @see WordType
|
||||
# The TextType-type has implemented support for replace
|
||||
# @see TextType
|
||||
#
|
||||
class ReplaceManager extends ListManager
|
||||
class types.ReplaceManager extends types.ListManager
|
||||
#
|
||||
# @param {Object} event_properties Decorates the event that is thrown by the RM
|
||||
# @param {Object} event_this The object on which the event shall be executed
|
||||
@@ -207,7 +206,7 @@ module.exports = (HB)->
|
||||
#
|
||||
replace: (content, replaceable_uid)->
|
||||
o = @getLastOperation()
|
||||
relp = (new Replaceable content, @, replaceable_uid, o, o.next_cl).execute()
|
||||
relp = (new types.Replaceable content, @, replaceable_uid, o, o.next_cl).execute()
|
||||
# TODO: delete repl (for debugging)
|
||||
undefined
|
||||
|
||||
@@ -219,7 +218,7 @@ module.exports = (HB)->
|
||||
undefined
|
||||
|
||||
#
|
||||
# Get the value of this WordType
|
||||
# Get the value of this
|
||||
# @return {String}
|
||||
#
|
||||
val: ()->
|
||||
@@ -234,7 +233,7 @@ module.exports = (HB)->
|
||||
_encode: ()->
|
||||
json =
|
||||
{
|
||||
'type': "ReplaceManager"
|
||||
'type': @type
|
||||
'uid' : @getUid()
|
||||
'beginning' : @beginning.getUid()
|
||||
'end' : @end.getUid()
|
||||
@@ -246,7 +245,7 @@ module.exports = (HB)->
|
||||
# The ReplaceManager manages Replaceables.
|
||||
# @see ReplaceManager
|
||||
#
|
||||
class Replaceable extends types.Insert
|
||||
class types.Replaceable extends types.Insert
|
||||
|
||||
#
|
||||
# @param {Operation} content The value that this Replaceable holds.
|
||||
@@ -321,7 +320,7 @@ module.exports = (HB)->
|
||||
_encode: ()->
|
||||
json =
|
||||
{
|
||||
'type': "Replaceable"
|
||||
'type': @type
|
||||
'content': @content?.getUid()
|
||||
'parent' : @parent.getUid()
|
||||
'prev': @prev_cl.getUid()
|
||||
@@ -332,7 +331,7 @@ module.exports = (HB)->
|
||||
}
|
||||
json
|
||||
|
||||
parser["Replaceable"] = (json)->
|
||||
types.Replaceable.parse = (json)->
|
||||
{
|
||||
'content' : content
|
||||
'parent' : parent
|
||||
@@ -342,12 +341,8 @@ module.exports = (HB)->
|
||||
'origin' : origin
|
||||
'is_deleted': is_deleted
|
||||
} = json
|
||||
new Replaceable content, parent, uid, prev, next, origin, is_deleted
|
||||
new this(content, parent, uid, prev, next, origin, is_deleted)
|
||||
|
||||
types['ListManager'] = ListManager
|
||||
types['MapManager'] = MapManager
|
||||
types['ReplaceManager'] = ReplaceManager
|
||||
types['Replaceable'] = Replaceable
|
||||
|
||||
basic_types
|
||||
|
||||
|
||||
@@ -5,19 +5,11 @@ module.exports = (HB)->
|
||||
types = structured_types.types
|
||||
parser = structured_types.parser
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# At the moment TextDelete type equals the Delete type in BasicTypes.
|
||||
# @see BasicTypes.Delete
|
||||
#
|
||||
class TextDelete extends types.Delete
|
||||
parser["TextDelete"] = parser["Delete"]
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# Extends the basic Insert type to an operation that holds a text value
|
||||
#
|
||||
class TextInsert extends types.Insert
|
||||
class types.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.
|
||||
@@ -72,7 +64,7 @@ module.exports = (HB)->
|
||||
_encode: ()->
|
||||
json =
|
||||
{
|
||||
'type': "TextInsert"
|
||||
'type': @type
|
||||
'uid' : @getUid()
|
||||
'prev': @prev_cl.getUid()
|
||||
'next': @next_cl.getUid()
|
||||
@@ -86,7 +78,7 @@ module.exports = (HB)->
|
||||
json['content'] = @content
|
||||
json
|
||||
|
||||
parser["TextInsert"] = (json)->
|
||||
types.TextInsert.parse = (json)->
|
||||
{
|
||||
'content' : content
|
||||
'uid' : uid
|
||||
@@ -95,13 +87,109 @@ module.exports = (HB)->
|
||||
'origin' : origin
|
||||
'parent' : parent
|
||||
} = json
|
||||
new TextInsert content, uid, prev, next, origin, parent
|
||||
new types.TextInsert content, uid, prev, next, origin, parent
|
||||
|
||||
|
||||
class types.Array extends types.ListManager
|
||||
|
||||
type: "Array"
|
||||
|
||||
applyDelete: ()->
|
||||
o = @end
|
||||
while o?
|
||||
o.applyDelete()
|
||||
o = o.prev_cl
|
||||
super()
|
||||
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
val: ()->
|
||||
o = @beginning.next_cl
|
||||
result = []
|
||||
while o isnt @end
|
||||
result.push o.val()
|
||||
o = o.next_cl
|
||||
result
|
||||
|
||||
push: (content)->
|
||||
@insertAfter @end.prev_cl, content
|
||||
|
||||
insertAfter: (left, content)->
|
||||
right = left.next_cl
|
||||
while right.isDeleted()
|
||||
right = right.next_cl # find the first character to the right, that is not deleted. In the case that position is 0, its the Delimiter.
|
||||
left = right.prev_cl
|
||||
if content.type?
|
||||
(new types.TextInsert content, undefined, left, right).execute()
|
||||
else
|
||||
for c in content
|
||||
tmp = (new types.TextInsert c, undefined, left, right).execute()
|
||||
left = tmp
|
||||
@
|
||||
|
||||
#
|
||||
# Inserts a string into the word.
|
||||
#
|
||||
# @return {Array Type} This String object.
|
||||
#
|
||||
insert: (position, content)->
|
||||
ith = @getOperationByPosition position
|
||||
# the (i-1)th character. e.g. "abc" the 1th character is "a"
|
||||
# the 0th character is the left Delimiter
|
||||
@insertAfter ith, content
|
||||
|
||||
#
|
||||
# Deletes a part of the word.
|
||||
#
|
||||
# @return {Array Type} This String object
|
||||
#
|
||||
delete: (position, length)->
|
||||
o = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character
|
||||
|
||||
delete_ops = []
|
||||
for i in [0...length]
|
||||
if o instanceof types.Delimiter
|
||||
break
|
||||
d = (new types.Delete undefined, o).execute()
|
||||
o = o.next_cl
|
||||
while not (o instanceof types.Delimiter) and o.isDeleted()
|
||||
o = o.next_cl
|
||||
delete_ops.push d._encode()
|
||||
@
|
||||
|
||||
#
|
||||
# @private
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: ()->
|
||||
json = {
|
||||
'type': @type
|
||||
'uid' : @getUid()
|
||||
}
|
||||
json
|
||||
|
||||
types.Array.parse = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
} = json
|
||||
new this(uid)
|
||||
|
||||
types.Array.create = (content, mutable)->
|
||||
if (mutable is "mutable")
|
||||
list = new types.Array().execute()
|
||||
list.insert 0, content
|
||||
list
|
||||
else if (not mutable?) or (mutable is "immutable")
|
||||
(new types.ImmutableObject undefined, content).execute()
|
||||
else
|
||||
throw new Error "Specify either \"mutable\" or \"immutable\"!!"
|
||||
|
||||
#
|
||||
# Handles a WordType-like data structures with support for insertText/deleteText at a word-position.
|
||||
# Handles a String-like data structures with support for insert/delete at a word-position.
|
||||
# @note Currently, only Text is supported!
|
||||
#
|
||||
class WordType extends types.ListManager
|
||||
class types.String extends types.Array
|
||||
|
||||
#
|
||||
# @private
|
||||
@@ -117,66 +205,11 @@ module.exports = (HB)->
|
||||
#
|
||||
# @example
|
||||
# var x = yatta.val('unknown')
|
||||
# if (x.type === "WordType") {
|
||||
# if (x.type === "String") {
|
||||
# console.log JSON.stringify(x.toJson())
|
||||
# }
|
||||
#
|
||||
type: "WordType"
|
||||
|
||||
applyDelete: ()->
|
||||
o = @end
|
||||
while o?
|
||||
o.applyDelete()
|
||||
o = o.prev_cl
|
||||
super()
|
||||
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
push: (content)->
|
||||
@insertAfter @end.prev_cl, content
|
||||
|
||||
insertAfter: (left, content)->
|
||||
right = left.next_cl
|
||||
while right.isDeleted()
|
||||
right = right.next_cl # find the first character to the right, that is not deleted. In the case that position is 0, its the Delimiter.
|
||||
left = right.prev_cl
|
||||
if content.type?
|
||||
(new TextInsert content, undefined, left, right).execute()
|
||||
else
|
||||
for c in content
|
||||
tmp = (new TextInsert c, undefined, left, right).execute()
|
||||
left = tmp
|
||||
@
|
||||
#
|
||||
# Inserts a string into the word.
|
||||
#
|
||||
# @return {WordType} This WordType object.
|
||||
#
|
||||
insertText: (position, content)->
|
||||
ith = @getOperationByPosition position
|
||||
# the (i-1)th character. e.g. "abc" the 1th character is "a"
|
||||
# the 0th character is the left Delimiter
|
||||
@insertAfter ith, content
|
||||
|
||||
#
|
||||
# Deletes a part of the word.
|
||||
#
|
||||
# @return {WordType} This WordType object
|
||||
#
|
||||
deleteText: (position, length)->
|
||||
o = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character
|
||||
|
||||
delete_ops = []
|
||||
for i in [0...length]
|
||||
if o instanceof types.Delimiter
|
||||
break
|
||||
d = (new TextDelete undefined, o).execute()
|
||||
o = o.next_cl
|
||||
while not (o instanceof types.Delimiter) and o.isDeleted()
|
||||
o = o.next_cl
|
||||
delete_ops.push d._encode()
|
||||
@
|
||||
type: "String"
|
||||
|
||||
#
|
||||
# Get the String-representation of this word.
|
||||
@@ -191,14 +224,14 @@ module.exports = (HB)->
|
||||
c.join('')
|
||||
|
||||
#
|
||||
# Same as WordType.val
|
||||
# @see WordType.val
|
||||
# Same as String.val
|
||||
# @see String.val
|
||||
#
|
||||
toString: ()->
|
||||
@val()
|
||||
|
||||
#
|
||||
# Bind this WordType to a textfield or input field.
|
||||
# Bind this String to a textfield or input field.
|
||||
#
|
||||
# @example
|
||||
# var textbox = document.getElementById("textfield");
|
||||
@@ -257,8 +290,8 @@ module.exports = (HB)->
|
||||
if char.length > 0
|
||||
pos = Math.min textfield.selectionStart, textfield.selectionEnd
|
||||
diff = Math.abs(textfield.selectionEnd - textfield.selectionStart)
|
||||
word.deleteText (pos), diff
|
||||
word.insertText pos, char
|
||||
word.delete (pos), diff
|
||||
word.insert pos, char
|
||||
new_pos = pos + char.length
|
||||
textfield.setSelectionRange new_pos, new_pos
|
||||
event.preventDefault()
|
||||
@@ -294,7 +327,7 @@ module.exports = (HB)->
|
||||
diff = Math.abs(textfield.selectionEnd - textfield.selectionStart)
|
||||
if event.keyCode? and event.keyCode is 8 # Backspace
|
||||
if diff > 0
|
||||
word.deleteText pos, diff
|
||||
word.delete pos, diff
|
||||
textfield.setSelectionRange pos, pos
|
||||
else
|
||||
if event.ctrlKey? and event.ctrlKey
|
||||
@@ -307,42 +340,48 @@ module.exports = (HB)->
|
||||
while new_pos > 0 and val[new_pos] isnt " " and val[new_pos] isnt '\n'
|
||||
new_pos--
|
||||
del_length++
|
||||
word.deleteText new_pos, (pos-new_pos)
|
||||
word.delete new_pos, (pos-new_pos)
|
||||
textfield.setSelectionRange new_pos, new_pos
|
||||
else
|
||||
word.deleteText (pos-1), 1
|
||||
word.delete (pos-1), 1
|
||||
event.preventDefault()
|
||||
else if event.keyCode? and event.keyCode is 46 # Delete
|
||||
if diff > 0
|
||||
word.deleteText pos, diff
|
||||
word.delete pos, diff
|
||||
textfield.setSelectionRange pos, pos
|
||||
else
|
||||
word.deleteText pos, 1
|
||||
word.delete pos, 1
|
||||
textfield.setSelectionRange pos, pos
|
||||
event.preventDefault()
|
||||
|
||||
|
||||
|
||||
#
|
||||
# @private
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: ()->
|
||||
json = {
|
||||
'type': "WordType"
|
||||
'type': @type
|
||||
'uid' : @getUid()
|
||||
}
|
||||
json
|
||||
|
||||
parser['WordType'] = (json)->
|
||||
types.String.parse = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
} = json
|
||||
new WordType uid
|
||||
new this(uid)
|
||||
|
||||
types.String.create = (content, mutable)->
|
||||
if (mutable is "mutable")
|
||||
word = new types.String().execute()
|
||||
word.insert 0, content
|
||||
word
|
||||
else if (not mutable?) or (mutable is "immutable")
|
||||
(new types.ImmutableObject undefined, content).execute()
|
||||
else
|
||||
throw new Error "Specify either \"mutable\" or \"immutable\"!!"
|
||||
|
||||
|
||||
types['TextInsert'] = TextInsert
|
||||
types['TextDelete'] = TextDelete
|
||||
types['WordType'] = WordType
|
||||
structured_types
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user