when KEY_BACKSPACE
return false unless @cursor?
return false unless @cursor.i > 0
- @cursor.n.text = @cursor.n.text.substr(0, @cursor.i - 1) + @cursor.n.text.substr(@cursor.i)
- @cursor.n.el.nodeValue = @cursor.n.text
+ @remove_character @cursor.n, @cursor.i - 1
+ @adjust_whitespace_style @cursor.n
+ @changed()
new_cursor = new_cursor_position n: @cursor.n, i: @cursor.i - 1
if new_cursor?
@move_cursor new_cursor
else
@kill_cursor()
- @changed()
return false
when KEY_DELETE
return false unless @cursor?
return false unless @cursor.i < @cursor.n.text.length
- @cursor.n.text = @cursor.n.text.substr(0, @cursor.i) + @cursor.n.text.substr(@cursor.i + 1)
- @cursor.n.el.nodeValue = @cursor.n.text
+ @remove_character @cursor.n, @cursor.i
+ @adjust_whitespace_style @cursor.n
+ @changed()
new_cursor = new_cursor_position n: @cursor.n, i: @cursor.i
if new_cursor?
@move_cursor new_cursor
else
@kill_cursor()
- @changed()
return false
when KEY_ENTER
return false
if char and @cursor?
char = String.fromCharCode char
@insert_character @cursor.n, @cursor.i, char
+ @adjust_whitespace_style @cursor.n
+ @changed()
new_cursor = new_cursor_position n: @cursor.n, i: @cursor.i + 1
if new_cursor
@move_cursor new_cursor
else
console.log "ERROR: couldn't find cursor position after insert"
@kill_cursor()
- @changed()
return false
clear_dom: -> # remove all the editable content (and cursor, overlays, etc)
while @idoc.body.childNodes.length
@iframe.style.height = "0"
@iframe.style.height = "#{h}px"
@wrap2.scrollTop = s
- # Warning: this does not call changed() for you
+ # does this node have whitespace that would be collapsed by white-space: normal?
+ # note: this checks direct text children, and does _not_ recurse into child tags
+ # tag is a node with type:"tag"
+ has_collapsable_space: (tag) ->
+ for n in tag.children
+ if n.type is 'text'
+ for i in [0...n.text.length]
+ code = n.text.charCodeAt i
+ if code isnt 32 and is_space_code code
+ # tab, return
+ return true
+ # check for double spaces that don't surround insert location
+ continue if i is 0
+ if n.text.substr(i - 1, 2) is ' '
+ return true
+ if n.text.length > 0
+ if is_space_code n.text.charCodeAt 0
+ return true
+ if is_space_code n.text.charCodeAt n.text.length - 1
+ return true
+ # add/remove "white-space: pre[-wrap]" to/from style="" on tags with direct
+ # child text nodes with multiple spaces in a row, or spaces at the
+ # start/end.
+ #
+ # text inside child tags are not consulted. Child tags are expected to have
+ # this function applied to them when their content changes.
+ adjust_whitespace_style: (n) ->
+ if n.type is 'text'
+ n = n.parent
+ return unless n?.el?
+ # which css rule should be used to preserve spaces (should we need to)
+ style = @iframe.contentWindow.getComputedStyle n.el, null
+ ws = style.getPropertyValue 'white-space'
+ if ws_props[ws].space
+ preserve_rule = ws
+ else
+ preserve_rule = ws_props[ws].to_preserve
+ preserve_rule = "white-space: #{preserve_rule}"
+ if @has_collapsable_space n
+ # make sure preserve_rule exists
+ if n.el.style['white-space']
+ # FIXME check that it matches
+ return
+ if n.attrs[style]?
+ n.attrs.style += "; #{preserve_rule}"
+ else
+ n.attrs.style = preserve_rule
+ n.el.setAttribute 'style', n.attrs.style
+ else
+ # remove preserve_rule if it exists
+ return unless n.attrs.style?
+ # FIXME don't assume whitespace is just so
+ if n.attrs.style is "white-space: #{ws}"
+ delete n.attrs.style
+ n.el.removeAttribute 'style'
+ else
+ # FIXME find it in the middle and at the start
+ needle = "; white-space: #{ws}"
+ if needle is n.attrs.style.substr n.attrs.style.length - needle
+ n.attrs.style = n.attrs.style.substr 0, n.attrs.style.length - needle
+ n.el.setAttribute n.attrs.style
+ # after calling this, you MUST call changed() and adjust_whitespace_style()
insert_character: (n, i, char) ->
- # TODO handle newlines, tabs, etc
parent = @cursor.n.parent
return unless parent
return unless parent.el?
- style = @iframe.contentWindow.getComputedStyle parent.el, null
- ws = style.getPropertyValue 'white-space'
- if char is ' '
- unless ws_props[ws].space
- change = false
- if i is 0
- # TODO check if a space at the beginning would actually get collapsed
- change = true
- else if i is n.text.length
- change = true
- # TODO check if a space at the end would actually get collapsed
- else
- if n.text.charAt(i - 1) is ' ' or n.text.charAt(i) is ' '
- change = true
- if change
- rule = "white-space: #{ws_props[ws].to_preserve}"
- if parent.attrs[style]?
- parent.attrs.style += "; #{rule}"
- else
- parent.attrs.style = rule
- parent.el.setAttribute 'style', parent.attrs.style
- else
- # TODO test this
- # inserting a visible (non-space) character
- if ws_props[ws].space
- if parent.el.style?['white-space']
- # This node has a "white-space" property on it
- # probably created automatically by this editor
- # when the user pressed space.
- # Check if that's no longer needed.
- need = false
- for ti in [0...n.text.length]
- code = n.text.charCodeAt ti
- if code isnt 32 and is_space_code code
- # tab, return
- need = true
- break
- # check for double spaces that don't surround insert location
- continue if ti is i
- continue if ti is 0
- if n.text.substr(ti - 1, 2) is ' '
- need = true
- break
- if i > 0
- if 32 is n.text.charCodeAt 0
- need = true
- if i < n.text.length
- if 32 is n.text.charCodeAt n.text.length - 1
- need = true
- unless need
- # TODO don't assume whitespace is just so
- if parent.attrs.style is "white-space: #{ws}"
- delete parent.attrs.style
- parent.el.removeAttribute 'style'
- else
- # FIXME find it in the middle and at the start
- needle = "; white-space: #{ws}"
- if needle is parent.attrs.style.substr parent.attrs.style.length - needle
- parent.attrs.style = parent.attrs.style.substr 0, parent.attrs.style.length - needle
- parent.el.setAttribute parent.attrs.style
# insert the character
if i is 0
n.text = char + n.text
char +
n.text.substr(i)
n.el.nodeValue = n.text
- # TODO call this when the user types
- # TODO detect when typing produces a collapsing space
+ # after calling this, you MUST call changed() and adjust_whitespace_style()
remove_character: (n, i) ->
- # TODO call this from delete and backspace key handlers
- # TODO detect if this would result in collapsing space
+ n.text = n.text.substr(0, i) + n.text.substr(i + 1)
+ n.el.nodeValue = n.text
kill_cursor: -> # remove it, forget where it was
if @cursor_visible
@cursor_el.parentNode.removeChild @cursor_el