JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
incomplete: deleting barrier between blocks
[peach-html5-editor.git] / editor.coffee
index c78151a..2ad0acc 100644 (file)
@@ -559,21 +559,32 @@ tree_dedup_space = (tree) ->
                                        throw "how is this possible?"
                                next_i -= 1
                        return 1
-       whitespace_to_space = (undo) ->
+       replace_with_space = (undo) ->
                if undo
                        cur.text = (cur.text.substr 0, cur_i) + removed_char + (cur.text.substr cur_i + 1)
                        cur.el.textContent = cur.text
                else
                        removed_char = cur.text.charAt(cur_i)
-                       cur.text = (cur.text.substr 0, cur_i) + ' ' + (cur.text.substr cur_i + 1)
-                       cur.el.textContent = cur.text
+                       if removed_char isnt ' '
+                               cur.text = (cur.text.substr 0, cur_i) + ' ' + (cur.text.substr cur_i + 1)
+                               cur.el.textContent = cur.text
                return 0
        # return true if cur was removed from the dom (ie re-use same prev)
        operate = ->
                # cur definitately set
                # prev and/or next might be null, indicating the start/end of a display:block
                return false unless is_space_code cur.text.charCodeAt cur_i
-               fixers = [remove, whitespace_to_space]
+               fixers = [remove, replace_with_space]
+               # check for common case: single whitespace surrounded by non-whitespace chars
+               if prev? and next?
+                       unless (is_space_code prev.text.charCodeAt prev_i) or (is_space_code next.text.charCodeAt next_i)
+                               dbg = cur.text.charCodeAt cur_i
+                               if cur.text.charAt(cur_i) is ' ' # perens required
+                                       # single space can't collapse, doesn't need fixin'
+                                       return false
+                               else
+                                       # tab, newline, etc, can't collapse, but maybe should be replaced
+                                       fixers = [replace_with_space]
                bounds = text_range_bounds cur.el, cur_i, cur_i + 1
                # consistent cases:
                # 1. zero rects returned by getClientRects() means collapsed space
@@ -581,7 +592,8 @@ tree_dedup_space = (tree) ->
                        return remove()
                # 2. width greater than zero means visible space
                if bounds.w > 0
-                       fixers.shift() # don't try removing
+                       # has bounds, don't try removing
+                       fixers = [replace_with_space]
                # now the weird edge cases...
                #
                # firefox and chromium both report zero width for characters at the end
@@ -590,6 +602,11 @@ tree_dedup_space = (tree) ->
                # collapsed spaces via the range/bounds api, so...
                #
                # remove it from the dom, and if prev or next moves, put it back.
+               #
+               # this block (try changing it, put it back if something moves) is also
+               # used on collapsable whitespace characters besides space. In this case
+               # the character is replaced with a normal space character instead of
+               # removed
                if prev? and not prev_px?
                        prev_px = new_cursor_position n: prev, i: prev_i
                if next? and not next_px?
@@ -1025,6 +1042,7 @@ class PeachHTML5Editor
                                else
                                        @kill_cursor()
                        else
+                               # cursor at the begining of an element that contains only a space
                                parent = @cursor.n.parent
                                new_cursor = find_prev_cursor_position @tree, @cursor
                                if new_cursor?
@@ -1052,11 +1070,41 @@ class PeachHTML5Editor
                                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 if @cursor.i is 0 # start of non-empty tag
+                       # TODO factor out function for moving children around
+                       # find containing block
+                       containing_block = @cursor.n
+                       loop
+                               containing_block = containing_block.parent
+                               return unless containing_block?
+                               return if containing_block is @tree_parent
+                               break if is_display_block containing_block
+                               # FIXME only continue if first child, else inline merge here
+                       # find contaning_block's previous sibling
+                       prev = null
+                       for n, contaning_block_i in contaning_block.parent.children
+                               break if n is containing_block
+                               prev = n
+                       containing_block.el.parent.removeChild containing_block.el
+                       containing_block.parent.children.splice contaning_block_i, 1
+                       if prev is null
+                               console.log "inimplemented: backspace at start of nested blocks" # FIXME
+                               return
+                       if is_display_block prev
+                               # insert contents of containing_block into prev
+                               ws_cleanup = false
+                               if prev.children.length > 0
+                                       prev_last = prev.children[prev.children.length - 1]
+                                       if prev_last.type is 'text' and containing_block.children[0].type is 'text'
+                                               prev_last.text = prev_last.el.textContent = prev_last.text + containing_block.children[0].text
+                                               ws_cleanup = true
+                                               containing_block.children.shift()
+                               for n in containing_block.children
+                                       # TODO insert into prev
+                               # TODO adjust whitespace property of prev_last if ws_cleanup
+                       else
+                               # TODO insert contents of containing_block into prev.parent after prev
+                       return
                else
                        # TODO handle case of removing last char
                        # CONTINUE
@@ -1077,6 +1125,13 @@ class PeachHTML5Editor
                                @kill_cursor()
                return
        on_page_up_key: (e) ->
+               if @wrap2.scrollTop is 0
+                       return unless @cursor?
+                       new_cursor = first_cursor_position @tree
+                       if new_cursor?
+                               if new_cursor.n isnt @cursor.n or new_cursor.i isnt @cursor.i
+                                       @move_cursor new_cursor
+                       return
                if @cursor?
                        screen_y = @cursor.y - @wrap2.scrollTop
                scroll_amount = @wrap2_height - breathing_room
@@ -1084,10 +1139,17 @@ class PeachHTML5Editor
                if @cursor?
                        @move_cursor_into_view screen_y + @wrap2.scrollTop
        on_page_down_key: (e) ->
+               lowest_scrollpos = @wrap2.scrollHeight - @wrap2_height
+               if @wrap2.scrollTop is lowest_scrollpos
+                       return unless @cursor?
+                       new_cursor = last_cursor_position @tree
+                       if new_cursor?
+                               if new_cursor.n isnt @cursor.n or new_cursor.i isnt @cursor.i
+                                       @move_cursor new_cursor
+                       return
                if @cursor?
                        screen_y = @cursor.y - @wrap2.scrollTop
                scroll_amount = @wrap2_height - breathing_room
-               lowest_scrollpos = @wrap2.scrollHeight - @wrap2_height
                @wrap2.scrollTop = Math.min lowest_scrollpos, @wrap2.scrollTop + scroll_amount
                if @cursor?
                        @move_cursor_into_view screen_y + @wrap2.scrollTop
@@ -1095,32 +1157,36 @@ class PeachHTML5Editor
        move_cursor_into_view: (y_target) ->
                return if y_target is @cursor.y
                was = @cursor
-               y_min = @wrap2.scrollTop + breathing_room
-               y_max = @wrap2.scrollTop + @wrap2_height - 2 * breathing_room
+               y_min = @wrap2.scrollTop
+               unless @wrap2.scrollTop is 0
+                       y_min += breathing_room
+               y_max = @wrap2.scrollTop + @wrap2_height
+               unless @wrap2.scrollTop is @wrap2.scrollHeight - @wrap2_height # downmost
+                       y_max -= breathing_room
                y_target = Math.min y_target, y_max
                y_target = Math.max y_target, y_min
                if y_target < @cursor.y
                        finder = find_up_cursor_position
-                       far_enough = (a, b) ->
-                               return a <= b
+                       far_enough = (cur, target_y) ->
+                               return cur.y + cur.h <= target_y
                else
                        finder = find_down_cursor_position
-                       far_enough = (a, b) ->
-                               return a >= b
+                       far_enough = (cur, y_target) ->
+                               return cur.y >= y_target
                loop
                        cur = finder @tree, was, @cursor_ideal_x
                        break unless cur?
-                       break if far_enough cur.y, y_target
+                       break if far_enough cur, y_target
                        was = cur
                if was is @cursor
                        was = null
                if was?
-                       if was.y > y_max
+                       if was.y + was.h > y_max
                                was = null
                        else if was.y < y_min
                                was = null
                if cur?
-                       if cur.y > y_max
+                       if cur.y + cur.h > y_max
                                cur = null
                        else if cur.y < y_min
                                cur = null
@@ -1128,7 +1194,7 @@ class PeachHTML5Editor
                        # both valid, pick best
                        if cur.y < y_min
                                new_cursor = was
-                       else if was.y > y_max
+                       else if was.y + was.h > y_max
                                new_cursor = cur
                        else if cur.y - y_target < y_target - was.y
                                new_cursor = cur