JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
fix backspace of last character in a block
[peach-html5-editor.git] / editor.coffee
index 4211c11..8367d09 100644 (file)
@@ -664,7 +664,8 @@ class PeachHTML5Editor
        constructor: (in_el, options) ->
                @options = options ? {}
                @in_el = in_el
-               @tree = null
+               @tree = null # array of Nodes, all editable content
+               @tree_parent = null # @tree is this.children. .el might === @idoc.body
                @matting = []
                @init_1_called = false # when iframes have loaded
                @outer_iframe # iframe to hold editor
@@ -922,16 +923,7 @@ class PeachHTML5Editor
                                        @move_cursor new_cursor
                                return false
                        when KEY_BACKSPACE
-                               return false unless @cursor?
-                               return false unless @cursor.i > 0
-                               @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()
+                               @on_key_backspace e
                                return false
                        when KEY_DELETE
                                return false unless @cursor?
@@ -990,12 +982,12 @@ class PeachHTML5Editor
                        return unless cur_block.parent?
                        cur_block = cur_block.parent
                # find array to insert new element into
-               if cur_block.parent?.el?
-                       parent_el = cur_block.parent.el
-                       pc = cur_block.parent.children
-               else
+               if cur_block.parent is @tree_parent # top-level
                        parent_el = @idoc.body
                        pc = @tree
+               else
+                       parent_el = cur_block.parent.el
+                       pc = cur_block.parent.children
                # find index of current block in its parent
                for n, i in pc
                        break if n is cur_block
@@ -1013,10 +1005,74 @@ class PeachHTML5Editor
                new_node.el = domify @idoc, p: style: 'white-space: pre-wrap', children: [new_text.el]
                pc.splice i, 0, new_node
                parent_el.insertBefore new_node.el, before
+               @changed()
                new_cursor = new_cursor_position n: new_text, i: 0
                throw 'bork bork' unless new_cursor?
                @move_cursor new_cursor
                # TODO move content past cursor into this new block
+       on_key_backspace: (e) ->
+               return false unless @cursor?
+               if @is_lone_space @cursor.n # false if it's not in a tag
+                       if @cursor.i is 1
+                               # don't delete the space, because then it would collapse
+                               # instead leave a space after the cursor
+                               new_cursor = new_cursor_position n: @cursor.n, i: 0
+                               if new_cursor?
+                                       @move_cursor new_cursor
+                               else
+                                       @kill_cursor()
+                       else
+                               parent = @cursor.n.parent
+                               new_cursor = find_prev_cursor_position @tree, @cursor
+                               if new_cursor?
+                                       if new_cursor.n is @cursor.n or new_cursor.n is parent
+                                               new_cursor = null
+                               tag = @cursor.n.parent
+                               if tag is @tree_parent
+                                       console.log "top-level text not supported" # FIXME
+                                       return false
+                               for n, i in tag.parent.children
+                                       if n is tag
+                                               tag.parent.el.removeChild tag.el
+                                               tag.parent.children.splice i, 1
+                                               break
+                               @changed()
+                               if new_cursor?
+                                       # re-check, in case it moved or is invalid now
+                                       new_cursor = new_cursor_position n: new_cursor.n, i: new_cursor.i
+                                       if new_cursor?
+                                               @move_cursor new_cursor
+                                               return
+                               new_cursor = first_cursor_position @tree
+                               if new_cursor?
+                                       @move_cursor new_cursor
+                               else
+                                       @kill_cursor
+                               return
+               else if @cursor.i is 0
+                       console.log 'unimplemented: backspace at start of non-empty tag'
+                       # TODO if block, merge parent into prev
+                       # TODO if inline, delete char from prev text node
+                       return false
+               else
+                       # TODO handle case of removing last char
+                       # CONTINUE
+                       if @is_only_char_in_tag @cursor.n
+                               if is_display_block @cursor.n.parent.el
+                                       @cursor.n.el.textContent = @cursor.n.text = ' '
+                               else
+                                       console.log "unimplemented: delete last char in inline" # FIXME
+                                       return
+                       else
+                               @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()
+               return
        clear_dom: -> # remove all the editable content (and cursor, overlays, etc)
                while @idoc.body.childNodes.length
                        @idoc.body.removeChild @idoc.body.childNodes[0]
@@ -1024,8 +1080,12 @@ class PeachHTML5Editor
                return
        load_html: (html) ->
                @tree = peach_parser.parse html, @parser_opts
+               if !@tree[0]?.parent
+                       @tree = peach_parser.parse '<p style="white-space: pre-wrap"> </p>', @parser_opts
+               @tree_parent = @tree[0]?.parent
+               @tree_parent.el = @idoc.body
                @clear_dom()
-               instantiate_tree @tree, @idoc.body
+               instantiate_tree @tree, @tree_parent.el
                tree_dedup_space @tree
                @changed()
        changed: ->
@@ -1104,15 +1164,42 @@ class PeachHTML5Editor
                                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
+       # true if n is text node with only one caracter, and the only child of a tag
+       is_only_char_in_tag: (n, i) ->
+               return false unless n.type is 'text'
+               return false unless n.text.length is 1
+               return false if n.parent is @tree_parent
+               return false unless n.parent.children.length is 1
+               return true
+       # true if n is text node with just a space in it, and the only child of a tag
+       is_lone_space: (n, i) ->
+               return false unless n.type is 'text'
+               return false unless n.text is ' '
+               return false if n.parent is @tree_parent
+               return false unless n.parent.children.length is 1
+               return true
+       # detect special case: typing before a space that's the only thing in a block/doc
+       # reason: enter key creates blocks with just a space in them
+       insert_should_replace: (n, i) ->
+               return false unless i is 0
+               return false unless n.text is ' '
+               return true if n.parent is @tree_parent
+               if n.parent.children.length is 1
+                       if n.parent.children[0] is n
+                               # n is only child
+                               return true
+               return false
        # after calling this, you MUST call changed() and adjust_whitespace_style()
        insert_character: (n, i, char) ->
+               return if @cursor.n.parent is @tree_parent # FIXME implement text nodes at top level
                parent = @cursor.n.parent
-               return unless parent
-               return unless parent.el?
                # insert the character
-               if i is 0
+               if @insert_should_replace n, i
+                       n.text = char
+               else if i is 0
                        n.text = char + n.text
                else if i is n.text.length
+                       # replace the space
                        n.text += char
                else
                        n.text =
@@ -1181,7 +1268,7 @@ class PeachHTML5Editor
                return unless n?
                prev_bounds = x: 0, y: 0, w: 0, h: 0
                alpha = 0.1
-               while n?.el?
+               while n?.el? and n isnt @tree_parent
                        if n.type is 'text'
                                n = n.parent
                                continue