JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
don't leave empty style attributes
[peach-html5-editor.git] / editor.js
index 16986f4..e4ab024 100644 (file)
--- a/editor.js
+++ b/editor.js
@@ -225,7 +225,8 @@ function new_cursor_position (args) {
        return null
 }
 
-// encode text so it can be safely placed inside an html attribute
+// html encoding for attributes
+// encoding nbsp is not required, but hopefully it is useful
 enc_attr_regex = new RegExp('(&)|(")|(\u00A0)', 'g')
 function enc_attr (txt) {
        return txt.replace(enc_attr_regex, function(match, amp, quote) {
@@ -238,6 +239,8 @@ function enc_attr (txt) {
                return ' '
        })
 }
+// html encoding for text (does nothing to stop whitespace collapse)
+// encoding nbsp is not required, but hopefully it is useful
 enc_text_regex = new RegExp('(&)|(<)|(\u00A0)', 'g')
 function enc_text (txt) {
        return txt.replace(enc_text_regex, function(match, amp, lt) {
@@ -251,23 +254,94 @@ function enc_text (txt) {
        })
 }
 
+// no closing tag, cannot have children
 void_elements = {
-       area: true,
-       base: true,
-       br: true,
-       col: true,
-       embed: true,
-       hr: true,
-       img: true,
-       input: true,
-       keygen: true,
-       link: true,
-       meta: true,
-       param: true,
-       source: true,
-       track: true,
-       wbr: true
+       area: 1,
+       base: 1,
+       br: 1,
+       col: 1,
+       embed: 1,
+       hr: 1,
+       img: 1,
+       input: 1,
+       keygen: 1,
+       link: 1,
+       meta: 1,
+       param: 1,
+       source: 1,
+       track: 1,
+       wbr: 1
 }
+
+// contents are not html encoded
+plaintext_elements = {
+       style: 1,
+       script: 1,
+       xmp: 1,
+       iframe: 1,
+       noembed: 1,
+       noframes: 1,
+       plaintext: 1,
+       noscript: 1
+}
+
+// parser deletes a starting newline inside:
+newline_eating_elements = {
+       pre: 1,
+       textarea: 1,
+       listing: 1
+}
+
+// this does not pretty-print
+function nodes_to_html (tree) {
+       var attr_keys, i, k, n, ret
+       ret = ''
+       for (i = 0; i < tree.length; ++i) {
+               n = tree[i]
+               switch (n.type) {
+                       case 'tag':
+                               ret += '<' + n.name
+                               attr_keys = []
+                               for (k in n.attrs) {
+                                       ret += " " + k
+                                       if (n.attrs[k].length > 0) {
+                                               ret += "=\"" + (enc_attr(n.attrs[k])) + "\""
+                                       }
+                               }
+                               ret += '>'
+                               if (void_elements[n.name] == null) {
+                                       if (n.children.length) {
+                                               ret += nodes_to_html(n.children)
+                                       }
+                                       ret += "</" + n.name + ">"
+                               }
+                               break
+                       case 'text':
+                               if (n.parent != null ? plaintext_elements[n.parent.name] : false) {
+                                       ret += n.text
+                               } else if (n.parent != null ? newline_eating_elements[n.parent.name] && n.text.charAt(0) === "\n" : false) {
+                                       ret += enc_text("\n" + n.text)
+                               } else {
+                                       ret += enc_text(n.text)
+                               }
+                               break
+                       case 'comment':
+                               ret += "<!--" + n.text + "-->" // TODO encode?
+                               break
+                       case 'doctype':
+                               ret += "<!DOCTYPE " + n.name
+                               if ((n.public_identifier != null) && n.public_identifier.length > 0) {
+                                       ret += " \"" + n.public_identifier + "\""
+                               }
+                               if ((n.system_identifier != null) && n.system_identifier.length > 0) {
+                                       ret += " \"" + n.system_identifier + "\""
+                               }
+                               ret += ">"
+               }
+       }
+       return ret
+}
+
 // TODO make these always pretty-print (on the inside) like blocks
 // TODO careful though: whitespace might get pushed to parent, which might be rendered
 no_text_elements = { // these elements never contain text
@@ -723,6 +797,7 @@ function PeachHTML5Editor (in_el, options) {
        //   on_init: callback for when the editable content is in place
        var css, opt_fragment, outer_bounds, outer_iframe_style, outer_wrap
        this.options = options != null ? options : {}
+       this.pretty_print = options.pretty_print != null ? options.pretty_print : true
        this.in_el = in_el
        this.tree = null // array of Nodes, all editable content
        this.tree_parent = null // this.tree is this.children. .el might === this.idoc.body
@@ -1628,7 +1703,11 @@ PeachHTML5Editor.prototype.load_html = function(html) {
 }
 PeachHTML5Editor.prototype.changed = function() {
        this.in_el.onchange = null
-       this.in_el.value = this.pretty_html(this.tree)
+       if (this.pretty_print) {
+               this.in_el.value = this.pretty_html(this.tree)
+       } else {
+               this.in_el.value = nodes_to_html(this.tree)
+       }
        this.in_el.onchange = (function(_this) { return function() {
                return _this.load_html(_this.in_el.value)
        }})(this)
@@ -1754,7 +1833,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) {
@@ -1996,6 +2075,7 @@ PeachHTML5Editor.prototype.text_cleanup = function(n) {
        if (run == null) {
                return
        }
+       // merge consecutive text elements
        if (run.length > 1) {
                i = 1
                prev = run[0]
@@ -2077,7 +2157,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
-                               if (ws_props[ws] == null ? true : ws_props[ws].space == null) {
+                               if (ws_props[ws] != null ? !ws_props[ws].space : true) {
                                        // 2nd arg is ideal target for css rule
                                        ws = this.preserve_space(n, block)
                                }
@@ -2418,7 +2498,13 @@ PeachHTML5Editor.prototype.pretty_html = function(tree, indent, parent_flags) {
                                }
                                break
                        case 'text':
-                               ret += enc_text(n.text)
+                               if (n.parent != null ? plaintext_elements[n.parent.name] : false) {
+                                       ret += n.text
+                               } else if (n.parent != null ? newline_eating_elements[n.parent.name] && n.text.charAt(0) === "\n" : false) {
+                                       ret += enc_text("\n" + n.text)
+                               } else {
+                                       ret += enc_text(n.text)
+                               }
                                break
                        case 'comment':
                                ret += "<!--" + n.text + "-->" // TODO encode?