JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
page down key: cursor to end if scrolled bot already
[peach-html5-editor.git] / editor.coffee
index 636bf19..a7c3c66 100644 (file)
@@ -16,6 +16,7 @@
 
 # SETTINGS
 overlay_padding = 10
+breathing_room = 30 # minimum pixels above/below cursor
 
 timeout = (ms, cb) -> return setTimeout cb, ms
 next_frame = (cb) ->
@@ -951,8 +952,10 @@ class PeachHTML5Editor
                        when KEY_INSERT
                                return false
                        when KEY_PAGE_UP
+                               @on_page_up_key e
                                return false
                        when KEY_PAGE_DOWN
+                               @on_page_down_key e
                                return false
                        when KEY_TAB
                                return false
@@ -1050,13 +1053,21 @@ class PeachHTML5Editor
                                        @kill_cursor
                                return
                else if @cursor.i is 0
-                       console.log 'not implemented yet'
+                       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
-                       @remove_character @cursor.n, @cursor.i - 1
+                       # 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
@@ -1064,6 +1075,84 @@ class PeachHTML5Editor
                                @move_cursor new_cursor
                        else
                                @kill_cursor()
+               return
+       on_page_up_key: (e) ->
+               if @cursor?
+                       screen_y = @cursor.y - @wrap2.scrollTop
+               scroll_amount = @wrap2_height - breathing_room
+               @wrap2.scrollTop = Math.max 0, @wrap2.scrollTop - scroll_amount
+               if @cursor?
+                       @move_cursor_into_view screen_y + @wrap2.scrollTop
+       on_page_down_key: (e) ->
+               if @cursor?
+                       screen_y = @cursor.y - @wrap2.scrollTop
+               scroll_amount = @wrap2_height - breathing_room
+               lowest_scrollpos = @wrap2.scrollHeight - @wrap2_height
+               if @wrap2.scrollTop is lowest_scrollpos # already at bottom
+                       return unless @cursor?
+                       # move cursor to bottom
+                       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
+               @wrap2.scrollTop = Math.min lowest_scrollpos, @wrap2.scrollTop + scroll_amount
+               if @cursor?
+                       @move_cursor_into_view screen_y + @wrap2.scrollTop
+               return
+       move_cursor_into_view: (y_target) ->
+               return if y_target is @cursor.y
+               was = @cursor
+               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 = (cur, target_y) ->
+                               return cur.y + cur.h <= target_y
+               else
+                       finder = find_down_cursor_position
+                       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_target
+                       was = cur
+               if was is @cursor
+                       was = null
+               if was?
+                       if was.y + was.h > y_max
+                               was = null
+                       else if was.y < y_min
+                               was = null
+               if cur?
+                       if cur.y + cur.h > y_max
+                               cur = null
+                       else if cur.y < y_min
+                               cur = null
+               if cur? and was?
+                       # both valid, pick best
+                       if cur.y < y_min
+                               new_cursor = was
+                       else if was.y + was.h > y_max
+                               new_cursor = cur
+                       else if cur.y - y_target < y_target - was.y
+                               new_cursor = cur
+                       else
+                               new_cursor = was
+               else
+                       new_cursor = was ? cur
+               if new_cursor?
+                       saved_ideal_x = @cursor_ideal_x
+                       @move_cursor new_cursor
+                       @cursor_ideal_x = saved_ideal_x
+               return
        clear_dom: -> # remove all the editable content (and cursor, overlays, etc)
                while @idoc.body.childNodes.length
                        @idoc.body.removeChild @idoc.body.childNodes[0]
@@ -1155,16 +1244,20 @@ 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
-               if n.parent.children.length is 1
-                       if n.parent.children[0] is n
-                               # n is only child
-                               return true
-               return false
+               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) ->
@@ -1221,21 +1314,20 @@ class PeachHTML5Editor
                @annotate cursor.n
                @scroll_into_view cursor.y, height
        scroll_into_view: (y, h = 0) ->
-               closest = 30 # setting: smallest pixels from top/bottom of screet that's OK
                y += overlay_padding # convert units from @idoc to @wrap2
                # very top of document
-               if y <= closest
+               if y <= breathing_room
                        @wrap2.scrollTop = 0
                        return
                # very bottom of document
-               if y + h >= @wrap2.scrollHeight - closest
+               if y + h >= @wrap2.scrollHeight - breathing_room
                        @wrap2.scrollTop = @wrap2.scrollHeight - @wrap2_height
                        return
                # The most scrolled up (lowest value for scrollTop) that would be OK
-               upmost = y + h + closest - @wrap2_height
+               upmost = y + h + breathing_room - @wrap2_height
                upmost = Math.max(upmost, 0)
                # the most scrolled down (highest value for scrollTop) that would be OK
-               downmost = y - closest
+               downmost = y - breathing_room
                downmost = Math.min(downmost, @wrap2.scrollHeight - @wrap2_height)
                if upmost > downmost # means h is too big to fit
                        # scroll so top is visible