yjs/lib/Types/TextTypes.coffee
2014-08-15 23:55:38 +02:00

258 lines
7.4 KiB
CoffeeScript

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
delete_ops = []
for i in [0...length]
d = HB.addOperation(new TextDelete undefined, o).execute()
o = o.next_cl
while o.isDeleted() and not (o instanceof types.Delimiter)
if o instanceof types.Delimiter
throw new Error "You can't delete more than there is.."
o = o.next_cl
delete_ops.push d._encode()
if o instanceof types.Delimiter
break
#
# 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
#
# Bind this Word to a textfield.
# TODO:
# insert_pressing+mouse new position
# concurrent pressing (two user pressing stuff)
bind: (textfield)->
word = @
textfield.value = @val()
position_start = null
document_length = null
@on "insert", (event, op)->
if op.creator isnt HB.getUserId()
o_pos = op.getPosition()
fix = (cursor)->
if cursor <= o_pos
cursor
else
cursor += 1
cursor
left = fix textfield.selectionStart
right = fix textfield.selectionEnd
if position_start?
document_length += 1
position_start = fix position_start
textfield.value = word.val()
textfield.setSelectionRange left, right
@on "delete", (event, op)->
if op.creator isnt HB.getUserId()
o_pos = op.getPosition()
fix = (cursor)->
if cursor <= o_pos
cursor
else
cursor -= 1
cursor
left = fix textfield.selectionStart
right = fix textfield.selectionEnd
if position_start?
document_length -= 1
position_start = fix position_start
textfield.value = word.val()
textfield.setSelectionRange left, right
update_yatta = ()->
if position_start?
document_length_diff = textfield.value.length - document_length
current_position = Math.min textfield.selectionStart, textfield.selectionEnd
if document_length_diff < 0 # deletion
deletion_position = Math.min current_position, position_start
word.deleteText deletion_position, Math.abs document_length_diff
else if document_length_diff > 0 # insertion
text_insert = textfield.value.substring position_start, (position_start + document_length_diff)
word.insertText position_start, text_insert
position_start = null
document_length = null
textfield.onkeydown = (event)->
#console.log "down"
if position_start?
update_yatta()
selection_range = Math.abs(textfield.selectionEnd - textfield.selectionStart)
position_start = Math.min(textfield.selectionStart, textfield.selectionEnd)
word.deleteText position_start, selection_range
document_length = textfield.value.length - selection_range
textfield.onkeyup = (event)->
#console.log "up"
update_yatta()
#
# 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