JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
check for visibility:hidden/collapsed too
[peach-html5-editor.git] / editor.coffee
index b78f6a0..66d1dfe 100644 (file)
 overlay_padding = 10
 
 timeout = (ms, cb) -> return setTimeout cb, ms
+next_frame = (cb) ->
+       if (window.requestAnimationFrame?)
+               window.requestAnimationFrame cb
+       else
+               timeout 16, cb
 
-# xml 1.0 says:
+# xml 1.0 spec, chromium and firefox accept these, plus lots of unicode chars
 valid_attr_regex = new RegExp '^[a-zA-Z_:][-a-zA-Z0-9_:.]*$'
 # html5 spec is much more lax, but chromium won't let me make at attribute with the name "4"
 js_attr_regex = new RegExp '^[oO][nN].'
@@ -248,10 +253,22 @@ outer_css = (args) ->
        ret +=     'font-size: 8px;'
        ret +=     'white-space: pre;'
        ret +=     'background: rgba(255,255,255,0.4);'
+       ret +=     '-ms-user-select: none;'
+       ret +=     '-webkit-user-select: none;'
+       ret +=     '-moz-user-select: none;'
+       ret +=     'user-select: none;'
        ret += '}'
        return ret
 
-# key codes:
+
+ignore_key_codes =
+       '18': true # alt
+       '20': true # capslock
+       '17': true # ctrl
+       '144': true # numlock
+       '16': true # shift
+       '91': true # windows "start" key
+# key codes: (valid on keydown, not keypress)
 KEY_LEFT = 37
 KEY_UP = 38
 KEY_RIGHT = 39
@@ -266,14 +283,6 @@ KEY_INSERT = 45
 KEY_PAGE_UP = 33
 KEY_PAGE_DOWN = 34
 KEY_TAB = 9
-
-ignore_key_codes =
-       '18': true # alt
-       '20': true # capslock
-       '17': true # ctrl
-       '144': true # numlock
-       '16': true # shift
-       '91': true # windows "start" key
 control_key_codes = # we react to these, but they aren't typing
        '37': KEY_LEFT
        '38': KEY_UP
@@ -593,6 +602,7 @@ class PeachHTML5Editor
                @cursor = null
                @cursor_el = null
                @cursor_visible = false
+               @poll_for_blur_timeout = null
                @iframe_offset = null
                opt_fragment = @options.fragment ? true
                @parser_opts = {}
@@ -637,14 +647,19 @@ class PeachHTML5Editor
        init: -> # called by @iframe's onload (or timeout on firefox)
                @idoc = @iframe.contentDocument
                @overlay.onclick = (e) =>
+                       @have_focus()
                        return event_return e, @onclick e
                @overlay.ondoubleclick = (e) =>
+                       @have_focus()
                        return event_return e, @ondoubleclick e
                @outer_idoc.body.onkeyup = (e) =>
+                       @have_focus()
                        return event_return e, @onkeyup e
                @outer_idoc.body.onkeydown = (e) =>
+                       @have_focus()
                        return event_return e, @onkeydown e
                @outer_idoc.body.onkeypress = (e) =>
+                       @have_focus()
                        return event_return e, @onkeypress e
                if @options.stylesheet
                        # TODO test this
@@ -665,6 +680,8 @@ class PeachHTML5Editor
                new_cursor = find_loc_cursor_position @tree, xy
                if new_cursor?
                        @move_cursor new_cursor
+               else
+                       @kill_cursor()
                return false
        ondoubleclick: (e) ->
                return false
@@ -740,7 +757,7 @@ class PeachHTML5Editor
        onkeypress: (e) ->
                return if e.ctrlKey
                return false if ignore_key_codes[e.keyCode]?
-               return false if control_key_codes[e.keyCode]? # handled in keydown
+               # return false if control_key_codes[e.keyCode]? # handled in keydown
                char = e.charCode ? e.keyCode
                if char and @cursor?
                        char = String.fromCharCode char
@@ -794,7 +811,12 @@ class PeachHTML5Editor
                @overlay.appendChild @cursor_el
                @cursor_visible = true
                @cursor_el.style.left = "#{loc.x + overlay_padding - 1}px"
-               @cursor_el.style.top = "#{loc.y + overlay_padding}px"
+               if loc.h < 5
+                       height = 12
+               else
+                       height = loc.h
+               @cursor_el.style.top = "#{loc.y + overlay_padding + Math.round(height * .07)}px"
+               @cursor_el.style.height = "#{Math.round height * 0.82}px"
                @matt cursor[0]
        matt: (n) ->
                while @matting.length > 0
@@ -840,12 +862,14 @@ class PeachHTML5Editor
                                                display = cs['display']
                                                position = cs['position']
                                                float = cs['float']
+                                               visibility = cs['visibility']
                                        else
                                                cs = @iframe.contentWindow.getComputedStyle(n.el, null)
                                                whitespace = cs.getPropertyValue 'white-space'
                                                display = cs.getPropertyValue 'display'
                                                position = cs.getPropertyValue 'position'
                                                float = cs.getPropertyValue 'float'
+                                               visibility = cs.getPropertyValue 'visibility'
                                        if n.name is 'textarea'
                                                inner_flags.pre_ish = true
                                        else
@@ -861,7 +885,11 @@ class PeachHTML5Editor
                                                                        if 'display' is 'none'
                                                                                in_flow = false
                                                                        else
-                                                                               in_flow = true
+                                                                               switch visibility
+                                                                                       when 'hidden', 'collapse'
+                                                                                               in_flow = false
+                                                                                       else # visible
+                                                                                               in_flow = true
                                        switch display
                                                when 'inline', 'none'
                                                        inner_flags.block = false
@@ -931,6 +959,21 @@ class PeachHTML5Editor
                                if prev_in_flow_is_block or parent_flags.block
                                        ret += "\n#{indent.substr 4}"
                return ret
+       onblur: ->
+               @kill_cursor()
+       have_focus: ->
+               @editor_is_focused = true
+               @poll_for_blur()
+       poll_for_blur: ->
+               return if @poll_for_blur_timeout? # already polling
+               @poll_for_blur_timeout = timeout 150, =>
+                       next_frame => # pause polling when browser knows we're not active/visible/etc.
+                               @poll_for_blur_timeout = null
+                               if document.activeElement is @outer_iframe
+                                       @poll_for_blur()
+                               else
+                                       @editor_is_focused = false
+                                       @onblur()
 
 window.peach_html5_editor = (args...) ->
        return new PeachHTML5Editor args...