JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
implement real up-arrow key
[peach-html5-editor.git] / editor.coffee
index cda74ef..9b4ad4f 100644 (file)
@@ -388,13 +388,15 @@ find_prev_cursor_position = (tree, n, i) ->
                                if prev_xyh.x < orig_xyh.x or prev_xyh.y < orig_xyh.y
                                        return [n, prev_i]
                return [n, i - 1]
-       found_prev = n?
+       found_prev = null
        found = null
        traverse_tree tree, (node) ->
                if node.type is 'text'
+                       unless n?
+                               found = node
+                               return true
                        if node is n
-                               if found_prev?
-                                       found = found_prev
+                               found = found_prev # null if n is the first text node
                                return true
                        found_prev = node
                return false
@@ -609,9 +611,9 @@ class PeachHTML5Editor
        constructor: (in_el, options) ->
                @options = options ? {}
                @in_el = in_el
-               @tree = []
+               @tree = null
                @matting = []
-               @inited = false # when iframes have loaded
+               @init_1_called = false # when iframes have loaded
                @outer_iframe # iframe to hold editor
                @outer_idoc # "document" object for @outer_iframe
                @wrap2 = null # scrollbar is on this
@@ -622,6 +624,7 @@ class PeachHTML5Editor
                @cursor_visible = false
                @poll_for_blur_timeout = null
                @iframe_offset = null
+               @iframe_height = null
                opt_fragment = @options.fragment ? true
                @parser_opts = {}
                if opt_fragment
@@ -639,8 +642,9 @@ class PeachHTML5Editor
                        @outer_idoc.head.appendChild icss
                        @iframe = domify @outer_idoc, iframe: sandbox: 'allow-same-origin allow-scripts'
                        @iframe.onload = =>
-                               @init()
-                       setTimeout (=> @init() unless @inited), 200 # firefox never fires this onload
+                               @init_1()
+                       timeout 200, => # firefox never fires this onload
+                               @init_1() unless @init_1_called
                        @outer_idoc.body.appendChild(
                                domify @outer_idoc, div: id: 'wrap1', children: [
                                        domify @outer_idoc, div: style: "position: absolute; top: 0; left: 1px; font-size: 10px", children: [ domify @outer_idoc, text: "Peach HTML5 Editor" ]
@@ -663,8 +667,23 @@ class PeachHTML5Editor
                @outer_iframe.setAttribute 'style', outer_iframe_style
                css = outer_css w: outer_bounds.w, h: outer_bounds.h
                outer_wrap.appendChild @outer_iframe
-       init: -> # called by @iframe's onload (or timeout on firefox)
+       init_1: -> # @iframe has loaded (but not it's css)
                @idoc = @iframe.contentDocument
+               @init_1_called = true
+               # chromium doesn't resolve relative urls as though they were at the same domain
+               # so add a <base> tag
+               @idoc.head.appendChild domify @idoc, base: href: this_url_sans_path()
+               # don't let @iframe have scrollbars
+               @idoc.head.appendChild domify @idoc, style: children: [domify @idoc, text: "body { overflow: hidden; }"]
+               # load css file
+               if @options.css_file
+                       istyle = domify @idoc, link: rel: 'stylesheet', href: @options.css_file
+                       istyle.onload = =>
+                               @init_2()
+                       @idoc.head.appendChild istyle
+               else
+                       @init_2()
+       init_2: -> # @iframe and it's css file(s) are ready
                @overlay.onclick = (e) =>
                        @have_focus()
                        return event_return e, @onclick e
@@ -680,15 +699,7 @@ class PeachHTML5Editor
                @outer_idoc.body.onkeypress = (e) =>
                        @have_focus()
                        return event_return e, @onkeypress e
-               # chromium doesn't resolve relative urls as though they were at the same domain
-               # so add a <base> tag
-               @idoc.head.appendChild domify @idoc, base: href: this_url_sans_path()
-               if @options.css_file
-                       # TODO test this
-                       @idoc.head.appendChild domify @idoc, link: rel: 'stylesheet', type: 'text/css', href: @options.css_file
-               @idoc.head.appendChild domify @idoc, style: children: [domify @idoc, text: "body { overflow: hidden; }"]
                @load_html @in_el.value
-               @inited = true
                if @options.on_init?
                        @options.on_init()
        overlay_event_to_inner_xy: (e) ->
@@ -730,6 +741,47 @@ class PeachHTML5Editor
                                                        break
                                return false
                        when KEY_UP
+                               if @cursor?
+                                       new_cursor = @cursor
+                                       old_loc = cursor_to_xyh new_cursor[0], new_cursor[1]
+                                       new_loc = x: old_loc.x, y: old_loc.y
+                                       while new_loc.y >= old_loc.y
+                                               new_cursor = find_prev_cursor_position @tree, new_cursor[0], new_cursor[1]
+                                               return false unless new_cursor?
+                                               new_loc = cursor_to_xyh new_cursor[0], new_cursor[1]
+                                       if new_cursor?
+                                               # now we're above
+                                               if new_loc.x <= old_loc.x
+                                                       @move_cursor new_cursor
+                                                       return false
+                                               target_y = new_loc.y
+                                               # search leftward, until we find the closest position
+                                               while new_loc.x > old_loc.x and new_loc.y is target_y
+                                                       prev_loc = new_loc
+                                                       prev_cursor = new_cursor
+                                                       new_cursor = find_prev_cursor_position @tree, new_cursor[0], new_cursor[1]
+                                                       break unless new_cursor?
+                                                       new_loc = cursor_to_xyh new_cursor[0], new_cursor[1]
+                                               # move cursor to prev_cursor or new_cursor
+                                               if new_cursor?
+                                                       if new_loc.y is target_y
+                                                               # both valid, and on the same line, use closest
+                                                               if (old_loc.x - new_loc.x) < (prev_loc.x - old_loc.x)
+                                                                       @move_cursor new_cursor
+                                                               else
+                                                                       @move_cursor prev_cursor
+                                                       else
+                                                               # new_cursor on wrong line, use prev_cursor
+                                                               @move_cursor prev_cursor
+                                               else
+                                                       # can't go any further prev, use prev_cursor
+                                                       @move_cursor prev_cursor
+                               else
+                                       # move cursor to first position in document
+                                       new_cursor = find_prev_cursor_position @tree
+                                       if new_cursor?
+                                               @move_cursor new_cursor
+                                               break
                                return false
                        when KEY_RIGHT
                                if @cursor?
@@ -737,11 +789,11 @@ class PeachHTML5Editor
                                        if new_cursor?
                                                @move_cursor new_cursor
                                else
-                                       for c in @tree
-                                               new_cursor = find_prev_cursor_position @tree, c, -1
-                                               if new_cursor?
-                                                       @move_cursor new_cursor
-                                                       break
+                                       # move cursor to first position in document
+                                       new_cursor = find_prev_cursor_position @tree
+                                       if new_cursor?
+                                               @move_cursor new_cursor
+                                               break
                                return false
                        when KEY_DOWN
                                return false
@@ -813,8 +865,15 @@ class PeachHTML5Editor
                @in_el.value = @pretty_html @tree
                @in_el.onchange = =>
                        @load_html @in_el.value
-               @iframe.style.height = "0"
-               @iframe.style.height = "#{@idoc.body.scrollHeight}px"
+               @adjust_iframe_height()
+       adjust_iframe_height: ->
+               h = parseInt(@idoc.body.scrollHeight, 10)
+               if @iframe_height isnt h
+                       @iframe_height = h
+                       s = @wrap2.scrollTop
+                       @iframe.style.height = "0"
+                       @iframe.style.height = "#{h}px"
+                       @wrap2.scrollTop = s
        kill_cursor: -> # remove it, forget where it was
                if @cursor_visible
                        @cursor_el.parentNode.removeChild @cursor_el