JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
preserve <script> tags and whitespace in plaintext elements
[peach-html5-editor.git] / editor.js
index 0c9cf46..f3c6204 100644 (file)
--- a/editor.js
+++ b/editor.js
@@ -121,11 +121,10 @@ get_el_bounds = window.bounds = function(el) {
 }
 
 function is_display_block (el) {
-       if (el.currentStyle != null) {
-               return el.currentStyle.display === 'block'
-       } else {
+       if (el.nodeType === 1) {
                return window.getComputedStyle(el, null).getPropertyValue('display') === 'block'
        }
+       return false
 }
 
 // Pass return value from dom event handlers to this.
@@ -274,7 +273,7 @@ void_elements = {
 }
 
 // contents are not html encoded
-plaintext_elements = {
+var plaintext_elements = {
        style: 1,
        script: 1,
        xmp: 1,
@@ -286,7 +285,7 @@ plaintext_elements = {
 }
 
 // parser deletes a starting newline inside:
-newline_eating_elements = {
+var newline_eating_elements = {
        pre: 1,
        textarea: 1,
        listing: 1
@@ -428,12 +427,19 @@ function instantiate_tree (tree, parent) {
                        break
                        case 'tag':
                                if (c.name === 'script' || c.name === 'object' || c.name === 'iframe' || c.name === 'link') {
-                                       // TODO put placeholders instead
-                                       remove.unshift(i)
+                                       // TODO make some placeholders visible
+                                       // problematic to have different type than c: c.el = parent.ownerDocument.createComment(c.name + ' tag here')
+                                       c.el = parent.ownerDocument.createElement(c.name)
+                                       // correct type, but empty and no attributes
                                        continue
                                }
-                               // TODO create in correct namespace
-                               c.el = parent.ownerDocument.createElement(c.name)
+                               if (c.namespace === 'svg') {
+                                       c.el = parent.ownerDocument.createElementNS('http://www.w3.org/2000/svg', c.name)
+                               } else if (c.namespace === 'mathml') {
+                                       c.el = parent.ownerDocument.createElementNS('http://www.w3.org/1998/Math/MathML', c.name)
+                               } else {
+                                       c.el = parent.ownerDocument.createElement(c.name)
+                               }
                                ref1 = c.attrs
                                for (k in ref1) {
                                        v = ref1[k]
@@ -450,13 +456,6 @@ function instantiate_tree (tree, parent) {
                                }
                }
        }
-       results = []
-       for (i = 0; i < remove.length; i++) {
-               // FIXME this deletes the wrong node when siblings are removed
-               index = remove[i]
-               results.push(tree.splice(index, 1))
-       }
-       return results
 }
 
 function traverse_tree (tree, cb) {
@@ -805,6 +804,7 @@ function PeachHTML5Editor (in_el, options) {
        this.init_1_called = false // when iframes have loaded
        this.outer_iframe // iframe to hold editor
        this.outer_idoc // "document" object for this.outer_iframe
+       this.input_el = null
        this.wrap2 = null // scrollbar is on this
        this.wrap2_offset = null
        this.wrap2_height = null // including padding
@@ -814,7 +814,6 @@ function PeachHTML5Editor (in_el, options) {
        this.cursor_el = null
        this.cursor_visible = false
        this.cursor_ideal_x = null
-       this.poll_for_blur_timeout = null
        opt_fragment = this.options.fragment != null ? this.options.fragment : true
        this.parser_opts = {}
        if (opt_fragment) {
@@ -825,39 +824,40 @@ function PeachHTML5Editor (in_el, options) {
        if (this.options.editor_id != null) {
                this.outer_iframe.setAttribute('id', this.options.editor_id)
        }
-       this.outer_iframe.onload = (function(_this) {
-               return function() {
-                       var icss
-                       _this.outer_idoc = _this.outer_iframe.contentDocument
-                       icss = domify(_this.outer_idoc, { style: { children: [
-                               domify(_this.outer_idoc, {text: css})
-                       ]}})
-                       _this.outer_idoc.head.appendChild(icss)
-                       _this.iframe = domify(_this.outer_idoc, {iframe: {sandbox: 'allow-same-origin allow-scripts'}})
-                       _this.iframe.onload = function() {
+       this.outer_iframe.onload = (function(_this) { return function() {
+               var icss
+               _this.outer_idoc = _this.outer_iframe.contentDocument
+               icss = domify(_this.outer_idoc, { style: { children: [
+                       domify(_this.outer_idoc, {text: css})
+               ]}})
+               _this.outer_idoc.head.appendChild(icss)
+               _this.input_el = domify(_this.outer_idoc, {textarea: {style: "position: relative; left: 100px"}}) // {style: "display: block; position: absolute; top: -1000px; left: 0; height: 500px; width 50em"}})
+               _this.input_el.onblur = _this.onblur.bind(_this)
+               _this.outer_idoc.body.appendChild(_this.input_el)
+               _this.iframe = domify(_this.outer_idoc, {iframe: {sandbox: 'allow-same-origin allow-scripts'}})
+               _this.iframe.onload = function() {
+                       return _this.init_1()
+               }
+               timeout(200, function() { // firefox never fires this onload
+                       if (!_this.init_1_called) {
                                return _this.init_1()
                        }
-                       timeout(200, function() { // firefox never fires this onload
-                               if (!_this.init_1_called) {
-                                       return _this.init_1()
-                               }
-                       })
-                       _this.outer_idoc.body.appendChild(
-                               domify(_this.outer_idoc, {div: {id: 'wrap1', children: [
-                                       domify(_this.outer_idoc, {div: {
-                                               style: "position: absolute; top: 0; left: 1px; font-size: 10px",
-                                               children: [domify(_this.outer_idoc, {text: "Peach HTML5 Editor"})]
-                                       }}),
-                                       _this.wrap2 = domify(_this.outer_idoc, {div: {id: 'wrap2', children: [
-                                               domify(_this.outer_idoc, {div: {id: 'wrap3', children: [
-                                                       _this.iframe,
-                                                       _this.overlay = domify(_this.outer_idoc, { div: { id: 'overlay' }})
-                                               ]}})
+               })
+               _this.outer_idoc.body.appendChild(
+                       domify(_this.outer_idoc, {div: {id: 'wrap1', children: [
+                               domify(_this.outer_idoc, {div: {
+                                       style: "position: absolute; top: 0; left: 1px; font-size: 10px",
+                                       children: [domify(_this.outer_idoc, {text: "Peach HTML5 Editor"})]
+                               }}),
+                               _this.wrap2 = domify(_this.outer_idoc, {div: {id: 'wrap2', children: [
+                                       domify(_this.outer_idoc, {div: {id: 'wrap3', children: [
+                                               _this.iframe,
+                                               _this.overlay = domify(_this.outer_idoc, { div: { id: 'overlay' }})
                                        ]}})
                                ]}})
-                       )
-               }
-       })(this)
+                       ]}})
+               )
+       }})(this)
        outer_wrap = domify(document, {div: {"class": 'peach_html5_editor' }})
        this.in_el.parentNode.appendChild(outer_wrap)
        outer_bounds = get_el_bounds(outer_wrap)
@@ -896,36 +896,18 @@ PeachHTML5Editor.prototype.init_1 = function() { // this.iframe has loaded (but
        }
 }
 PeachHTML5Editor.prototype.init_2 = function() { // this.iframe and it's css file(s) are ready
-       this.overlay.onclick = (function(_this) {
-               return function(e) {
-                       _this.have_focus()
-                       return event_return(e, _this.onclick(e))
-               }
-       })(this)
-       this.overlay.ondoubleclick = (function(_this) {
-               return function(e) {
-                       _this.have_focus()
-                       return event_return(e, _this.ondoubleclick(e))
-               }
-       })(this)
-       this.outer_idoc.body.onkeyup = (function(_this) {
-               return function(e) {
-                       _this.have_focus()
-                       return event_return(e, _this.onkeyup(e))
-               }
-       })(this)
-       this.outer_idoc.body.onkeydown = (function(_this) {
-               return function(e) {
+       var _this = this
+       var this_event_bind = function (cb) {
+               return function (e) {
                        _this.have_focus()
-                       return event_return(e, _this.onkeydown(e))
+                       event_return(e, _this[cb](e))
                }
-       })(this)
-       this.outer_idoc.body.onkeypress = (function(_this) {
-               return function(e) {
-                       _this.have_focus()
-                       return event_return(e, _this.onkeypress(e))
-               }
-       })(this)
+       }
+       this.overlay.onmousedown = this_event_bind('onclick')
+       this.overlay.ondoubleclick = this_event_bind('ondoubleclick')
+       this.outer_idoc.body.onkeyup = this_event_bind('onkeyup')
+       this.outer_idoc.body.onkeydown = this_event_bind('onkeydown')
+       this.outer_idoc.body.onkeypress = this_event_bind('onkeypress')
        this.load_html(this.in_el.value)
        if (this.options.on_init != null) {
                return this.options.on_init()
@@ -957,6 +939,7 @@ PeachHTML5Editor.prototype.generate_outer_css = function(args) {
        ret +=     'padding: 0;'
        ret +=     'color: black;'
        ret +=     'background: white;'
+       ret +=     'font-family: sans;'
        ret += '}'
        ret += '#wrap1 {'
        ret +=     "border: " + (occupy(1)) + "px solid black;"
@@ -1690,9 +1673,9 @@ PeachHTML5Editor.prototype.clear_dom = function() {
        this.kill_cursor()
 }
 PeachHTML5Editor.prototype.load_html = function(html) {
-       this.tree = peach_parser(html, this.parser_opts)
+       this.tree = peach_parser.parse(html, this.parser_opts)
        if (this.tree[0] == null ? true : this.tree[0].parent == null) {
-               this.tree = peach_parser('<p style="white-space: pre-wrap"> </p>', this.parser_opts)
+               this.tree = peach_parser.parse('<p style="white-space: pre-wrap"> </p>', this.parser_opts)
        }
        this.tree_parent = this.tree[0].parent
        this.tree_parent.el = this.idoc.body
@@ -1833,7 +1816,7 @@ PeachHTML5Editor.prototype.preserve_space = function(n, ideal_target) {
 PeachHTML5Editor.prototype.update_style_from_el = function(n) {
        var style
        style = n.el.getAttribute('style')
-       if (style != null) {
+       if (style != null && style != '') {
                return n.attrs.style = style
        } else {
                if (n.attrs.style != null) {
@@ -1879,7 +1862,7 @@ PeachHTML5Editor.prototype.collapse_whitespace = function(tree) {
                                if (block) {
                                        cb(null)
                                }
-                               if (n.children.length > 0) {
+                               if (n.children.length > 0 && plaintext_elements[n.name] == null) {
                                        iterate(n.children, cb)
                                }
                                if (block) {
@@ -2156,7 +2139,7 @@ PeachHTML5Editor.prototype.text_cleanup = function(n) {
                        }
                        if (need_preserve) {
                                // do we have it already?
-                               ws = this.computed_style(n, 'white-space') // FIXME implement this
+                               ws = this.computed_style(n, 'white-space')
                                if (ws_props[ws] != null ? !ws_props[ws].space : true) {
                                        // 2nd arg is ideal target for css rule
                                        ws = this.preserve_space(n, block)
@@ -2164,7 +2147,7 @@ PeachHTML5Editor.prototype.text_cleanup = function(n) {
                                eats_start_sp = false
                        } else {
                                if (is_space_code(n.text.charCodeAt(n.text.length - 1))) {
-                                       ws = this.computed_style(n, 'white-space') // FIXME implement this
+                                       ws = this.computed_style(n, 'white-space')
                                        if ((ref1 = ws_props[ws]) != null ? ref1.space : void 0) {
                                                eats_start_sp = false
                                        } else {
@@ -2384,21 +2367,12 @@ PeachHTML5Editor.prototype.pretty_html = function(tree, indent, parent_flags) {
                                        is_br = true
                                }
                                is_text = false
-                               if (n.el.currentStyle != null) {
-                                       cs = n.el.currentStyle
-                                       whitespace = cs['white-space']
-                                       display = cs['display']
-                                       position = cs['position']
-                                       float = cs['float']
-                                       visibility = cs['visibility']
-                               } else {
-                                       cs = this.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')
-                               }
+                               cs = this.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 === 'textarea') {
                                        inner_flags.pre_ish = true
                                } else {
@@ -2536,27 +2510,14 @@ PeachHTML5Editor.prototype.pretty_html = function(tree, indent, parent_flags) {
        return ret
 }
 PeachHTML5Editor.prototype.onblur = function() {
+       this.editor_is_focused = false
        this.kill_cursor()
 }
 PeachHTML5Editor.prototype.have_focus = function() {
-       this.editor_is_focused = true
-       this.poll_for_blur()
-}
-PeachHTML5Editor.prototype.poll_for_blur = function() {
-       if (this.poll_for_blur_timeout != null) {
-               return
+       if (!this.editor_is_focused) {
+               this.input_el.focus()
+               this.editor_is_focused = true
        }
-       this.poll_for_blur_timeout = timeout(150, (function(_this) { return function() {
-               next_frame(function() { // pause polling when browser knows we're not active/visible/etc.
-                       _this.poll_for_blur_timeout = null
-                       if (document.activeElement === _this.outer_iframe) {
-                               _this.poll_for_blur()
-                       } else {
-                               _this.editor_is_focused = false
-                               _this.onblur()
-                       }
-               })
-       }})(this))
 }
 
 window.peach_html5_editor = function() {