Skip to content
This repository
Browse code

Introduce useLineBreaks config option (default = true) (fixes #13)

Conflicts:
	src/views/composer.js
	src/views/composer.style.js
  • Loading branch information...
commit 9e88e5808704c93f77f8cf0123fca67dbb138e90 1 parent 80b3544
Christopher Blum authored September 24, 2012
1  Makefile
@@ -40,7 +40,6 @@ JS_FILES = src/wysihtml5.js \
40 40
   src/quirks/clean_pasted_html.js \
41 41
   src/quirks/ensure_proper_clearing.js \
42 42
   src/quirks/get_correct_inner_html.js \
43  
-  src/quirks/insert_line_break_on_return.js \
44 43
   src/quirks/redraw.js \
45 44
   src/selection/selection.js \
46 45
   src/selection/html_applier.js \
10  examples/advanced.html
@@ -38,7 +38,7 @@
38 38
   
39 39
   #toolbar,
40 40
   textarea {
41  
-    width: 900px;
  41
+    width: 920px;
42 42
     padding: 5px;
43 43
     -webkit-box-sizing: border-box;
44 44
     -ms-box-sizing: border-box;
@@ -91,6 +91,7 @@
91 91
     <a data-wysihtml5-command="insertImage">insert image</a> |
92 92
     <a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h1">h1</a> |
93 93
     <a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h2">h2</a> |
  94
+    <a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="p">p</a> |
94 95
     <a data-wysihtml5-command="insertUnorderedList">insertUnorderedList</a> |
95 96
     <a data-wysihtml5-command="insertOrderedList">insertOrderedList</a> |
96 97
     <a data-wysihtml5-command="foreColor" data-wysihtml5-command-value="red">red</a> |
@@ -139,9 +140,10 @@
139 140
 <script src="../dist/wysihtml5-0.3.0.js"></script>
140 141
 <script>
141 142
   var editor = new wysihtml5.Editor("textarea", {
142  
-    toolbar:      "toolbar",
143  
-    stylesheets:  "css/stylesheet.css",
144  
-    parserRules:  wysihtml5ParserRules
  143
+    toolbar:        "toolbar",
  144
+    stylesheets:    "css/stylesheet.css",
  145
+    parserRules:    wysihtml5ParserRules,
  146
+    useLineBreaks:  false
145 147
   });
146 148
   
147 149
   var log = document.getElementById("log");
5  examples/simple.html
@@ -90,8 +90,9 @@
90 90
 <script src="../dist/wysihtml5-0.3.0.js"></script>
91 91
 <script>
92 92
   var editor = new wysihtml5.Editor("textarea", {
93  
-    toolbar:      "toolbar",
94  
-    parserRules:  wysihtml5ParserRules
  93
+    toolbar:        "toolbar",
  94
+    parserRules:    wysihtml5ParserRules,
  95
+    useLineBreaks:  false
95 96
   });
96 97
   
97 98
   var log = document.getElementById("log");
24  src/browser.js
@@ -88,7 +88,7 @@ wysihtml5.browser = (function() {
88 88
      * Firefox sometimes shows a huge caret in the beginning after focusing
89 89
      */
90 90
     displaysCaretInEmptyContentEditableCorrectly: function() {
91  
-      return !isGecko;
  91
+      return isIE;
92 92
     },
93 93
 
94 94
     /**
@@ -167,8 +167,8 @@ wysihtml5.browser = (function() {
167 167
          // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
168 168
          // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
169 169
          // IE and Opera act a bit different here as they convert the entire content of the current block element into a list
170  
-        "insertUnorderedList":  isIE || isOpera || isWebKit,
171  
-        "insertOrderedList":    isIE || isOpera || isWebKit
  170
+        "insertUnorderedList":  isIE || isWebKit,
  171
+        "insertOrderedList":    isIE || isWebKit
172 172
       };
173 173
       
174 174
       // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
@@ -241,14 +241,6 @@ wysihtml5.browser = (function() {
241 241
     },
242 242
 
243 243
     /**
244  
-     * When the caret is in an empty list (<ul><li>|</li></ul>) which is the first child in an contentEditable container
245  
-     * pressing backspace doesn't remove the entire list as done in other browsers
246  
-     */
247  
-    clearsListsInContentEditableCorrectly: function() {
248  
-      return isGecko || isIE || isWebKit;
249  
-    },
250  
-
251  
-    /**
252 244
      * All browsers except Safari and Chrome automatically scroll the range/caret position into view
253 245
      */
254 246
     autoScrollsToCaret: function() {
@@ -335,6 +327,16 @@ wysihtml5.browser = (function() {
335 327
     
336 328
     hasUndoInContextMenu: function() {
337 329
       return isGecko || isChrome || isOpera;
  330
+    },
  331
+    
  332
+    /**
  333
+     * Opera sometimes doesn't insert the node at the right position when range.insertNode(someNode)
  334
+     * is used (regardless if rangy or native)
  335
+     * This especially happens when the caret is positioned right after a <br> because then
  336
+     * insertNode() will insert the node right before the <br>
  337
+     */
  338
+    hasInsertNodeIssue: function() {
  339
+      return isOpera;
338 340
     }
339 341
   };
340 342
 })();
27  src/commands/formatBlock.js
... ...
@@ -1,10 +1,9 @@
1 1
 (function(wysihtml5) {
2 2
   var dom                     = wysihtml5.dom,
3  
-      DEFAULT_NODE_NAME       = "DIV",
4 3
       // Following elements are grouped
5 4
       // when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
6 5
       // instead of creating a H4 within a H1 which would result in semantically invalid html
7  
-      BLOCK_ELEMENTS_GROUP    = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "BLOCKQUOTE", DEFAULT_NODE_NAME];
  6
+      BLOCK_ELEMENTS_GROUP    = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "BLOCKQUOTE", "DIV"];
8 7
   
9 8
   /**
10 9
    * Remove similiar classes (based on classRegExp)
@@ -141,7 +140,7 @@
141 140
     composer.selection.surround(element);
142 141
     _removeLineBreakBeforeAndAfter(element);
143 142
     _removeLastChildIfLineBreak(element);
144  
-    composer.selection.selectNode(element);
  143
+    composer.selection.selectNode(element, wysihtml5.browser.displaysCaretInEmptyContentEditableCorrectly());
145 144
   }
146 145
 
147 146
   function _hasClasses(element) {
@@ -150,26 +149,28 @@
150 149
   
151 150
   wysihtml5.commands.formatBlock = {
152 151
     exec: function(composer, command, nodeName, className, classRegExp) {
153  
-      var doc          = composer.doc,
154  
-          blockElement = this.state(composer, command, nodeName, className, classRegExp),
  152
+      var doc             = composer.doc,
  153
+          blockElement    = this.state(composer, command, nodeName, className, classRegExp),
  154
+          useLineBreaks   = composer.config.useLineBreaks,
  155
+          defaultNodeName = useLineBreaks ? "DIV" : "P",
155 156
           selectedNode;
156 157
 
157 158
       nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;
158  
-
  159
+      
159 160
       if (blockElement) {
160 161
         composer.selection.executeAndRestoreSimple(function() {
161 162
           if (classRegExp) {
162 163
             _removeClass(blockElement, classRegExp);
163 164
           }
164 165
           var hasClasses = _hasClasses(blockElement);
165  
-          if (!hasClasses && blockElement.nodeName === (nodeName || DEFAULT_NODE_NAME)) {
  166
+          if (!hasClasses && (useLineBreaks || nodeName === "P")) {
166 167
             // Insert a line break afterwards and beforewards when there are siblings
167 168
             // that are not of type line break or block element
168 169
             _addLineBreakBeforeAndAfter(blockElement);
169 170
             dom.replaceWithChildNodes(blockElement);
170  
-          } else if (hasClasses) {
171  
-            // Make sure that styling is kept by renaming the element to <div> and copying over the class name
172  
-            dom.renameElement(blockElement, DEFAULT_NODE_NAME);
  171
+          } else {
  172
+            // Make sure that styling is kept by renaming the element to a <div> or <p> and copying over the class name
  173
+            dom.renameElement(blockElement, nodeName === "P" ? "DIV" : defaultNodeName);
173 174
           }
174 175
         });
175 176
         return;
@@ -183,7 +184,7 @@
183 184
         });
184 185
 
185 186
         if (blockElement) {
186  
-          composer.selection.executeAndRestoreSimple(function() {
  187
+          composer.selection.executeAndRestore(function() {
187 188
             // Rename current block element to new block element and add class
188 189
             if (nodeName) {
189 190
               blockElement = dom.renameElement(blockElement, nodeName);
@@ -197,11 +198,11 @@
197 198
       }
198 199
 
199 200
       if (composer.commands.support(command)) {
200  
-        _execCommand(doc, command, nodeName || DEFAULT_NODE_NAME, className);
  201
+        _execCommand(doc, command, nodeName || defaultNodeName, className);
201 202
         return;
202 203
       }
203 204
 
204  
-      blockElement = doc.createElement(nodeName || DEFAULT_NODE_NAME);
  205
+      blockElement = doc.createElement(nodeName || defaultNodeName);
205 206
       if (className) {
206 207
         blockElement.className = className;
207 208
       }
12  src/commands/insertOrderedList.js
@@ -8,7 +8,7 @@ wysihtml5.commands.insertOrderedList = {
8 8
         isEmpty,
9 9
         tempElement;
10 10
     
11  
-    if (composer.commands.support(command)) {
  11
+    if (!list && !otherList && composer.commands.support(command)) {
12 12
       doc.execCommand(command, false, null);
13 13
       return;
14 14
     }
@@ -18,15 +18,15 @@ wysihtml5.commands.insertOrderedList = {
18 18
       // <ol><li>foo</li><li>bar</li></ol>
19 19
       // becomes:
20 20
       // foo<br>bar<br>
21  
-      composer.selection.executeAndRestoreSimple(function() {
22  
-        wysihtml5.dom.resolveList(list);
  21
+      composer.selection.executeAndRestore(function() {
  22
+        wysihtml5.dom.resolveList(list, composer.config.useLineBreaks);
23 23
       });
24 24
     } else if (otherList) {
25 25
       // Turn an unordered list into an ordered list
26 26
       // <ul><li>foo</li><li>bar</li></ul>
27 27
       // becomes:
28 28
       // <ol><li>foo</li><li>bar</li></ol>
29  
-      composer.selection.executeAndRestoreSimple(function() {
  29
+      composer.selection.executeAndRestore(function() {
30 30
         wysihtml5.dom.renameElement(otherList, "ol");
31 31
       });
32 32
     } else {
@@ -34,11 +34,11 @@ wysihtml5.commands.insertOrderedList = {
34 34
       composer.commands.exec("formatBlock", "div", tempClassName);
35 35
       tempElement = doc.querySelector("." + tempClassName);
36 36
       isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE || tempElement.innerHTML === "<br>";
37  
-      composer.selection.executeAndRestoreSimple(function() {
  37
+      composer.selection.executeAndRestore(function() {
38 38
         list = wysihtml5.dom.convertToList(tempElement, "ol");
39 39
       });
40 40
       if (isEmpty) {
41  
-        composer.selection.selectNode(list.querySelector("li"));
  41
+        composer.selection.selectNode(list.querySelector("li"), true);
42 42
       }
43 43
     }
44 44
   },
12  src/commands/insertUnorderedList.js
@@ -8,7 +8,7 @@ wysihtml5.commands.insertUnorderedList = {
8 8
         isEmpty,
9 9
         tempElement;
10 10
     
11  
-    if (composer.commands.support(command)) {
  11
+    if (!list && !otherList && composer.commands.support(command)) {
12 12
       doc.execCommand(command, false, null);
13 13
       return;
14 14
     }
@@ -18,15 +18,15 @@ wysihtml5.commands.insertUnorderedList = {
18 18
       // <ul><li>foo</li><li>bar</li></ul>
19 19
       // becomes:
20 20
       // foo<br>bar<br>
21  
-      composer.selection.executeAndRestoreSimple(function() {
22  
-        wysihtml5.dom.resolveList(list);
  21
+      composer.selection.executeAndRestore(function() {
  22
+        wysihtml5.dom.resolveList(list, composer.config.useLineBreaks);
23 23
       });
24 24
     } else if (otherList) {
25 25
       // Turn an ordered list into an unordered list
26 26
       // <ol><li>foo</li><li>bar</li></ol>
27 27
       // becomes:
28 28
       // <ul><li>foo</li><li>bar</li></ul>
29  
-      composer.selection.executeAndRestoreSimple(function() {
  29
+      composer.selection.executeAndRestore(function() {
30 30
         wysihtml5.dom.renameElement(otherList, "ul");
31 31
       });
32 32
     } else {
@@ -34,11 +34,11 @@ wysihtml5.commands.insertUnorderedList = {
34 34
       composer.commands.exec("formatBlock", "div", tempClassName);
35 35
       tempElement = doc.querySelector("." + tempClassName);
36 36
       isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE || tempElement.innerHTML === "<br>";
37  
-      composer.selection.executeAndRestoreSimple(function() {
  37
+      composer.selection.executeAndRestore(function() {
38 38
         list = wysihtml5.dom.convertToList(tempElement, "ul");
39 39
       });
40 40
       if (isEmpty) {
41  
-        composer.selection.selectNode(list.querySelector("li"));
  41
+        composer.selection.selectNode(list.querySelector("li"), true);
42 42
       }
43 43
     }
44 44
   },
4  src/dom/convert_to_list.js
@@ -93,6 +93,10 @@ wysihtml5.dom.convertToList = (function() {
93 93
       currentListItem.appendChild(childNode);
94 94
     }
95 95
     
  96
+    if (childNodes.length === 0) {
  97
+      _createListItem(doc, list);
  98
+    }
  99
+    
96 100
     element.parentNode.replaceChild(list, element);
97 101
     return list;
98 102
   }
19  src/dom/insert_css.js
@@ -3,19 +3,24 @@ wysihtml5.dom.insertCSS = function(rules) {
3 3
   
4 4
   return {
5 5
     into: function(doc) {
6  
-      var head         = doc.head || doc.getElementsByTagName("head")[0],
7  
-          styleElement = doc.createElement("style");
8  
-
  6
+      var styleElement = doc.createElement("style");
9 7
       styleElement.type = "text/css";
10  
-
  8
+      
11 9
       if (styleElement.styleSheet) {
12 10
         styleElement.styleSheet.cssText = rules;
13 11
       } else {
14 12
         styleElement.appendChild(doc.createTextNode(rules));
15 13
       }
16  
-
17  
-      if (head) {
18  
-        head.appendChild(styleElement);
  14
+      
  15
+      var link = doc.querySelector("head link");
  16
+      if (link) {
  17
+        link.parentNode.insertBefore(styleElement, link);
  18
+        return;
  19
+      } else {
  20
+        var head = doc.querySelector("head");
  21
+        if (head) {
  22
+          head.appendChild(styleElement);
  23
+        }
19 24
       }
20 25
     }
21 26
   };
54  src/dom/resolve_list.js
@@ -34,8 +34,8 @@
34 34
     element.appendChild(lineBreak);
35 35
   }
36 36
   
37  
-  function resolveList(list) {
38  
-    if (list.nodeName !== "MENU" && list.nodeName !== "UL" && list.nodeName !== "OL") {
  37
+  function resolveList(list, useLineBreaks) {
  38
+    if (!list.nodeName.match(/^(MENU|UL|OL)$/)) {
39 39
       return;
40 40
     }
41 41
     
@@ -46,26 +46,46 @@
46 46
         lastChild,
47 47
         isLastChild,
48 48
         shouldAppendLineBreak,
  49
+        paragraph,
49 50
         listItem;
50 51
     
51  
-    if (previousSibling && !_isBlockElement(previousSibling)) {
52  
-      _appendLineBreak(fragment);
53  
-    }
54  
-    
55  
-    while (listItem = list.firstChild) {
56  
-      lastChild = listItem.lastChild;
57  
-      while (firstChild = listItem.firstChild) {
58  
-        isLastChild           = firstChild === lastChild;
59  
-        // This needs to be done before appending it to the fragment, as it otherwise will loose style information
60  
-        shouldAppendLineBreak = isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
61  
-        fragment.appendChild(firstChild);
62  
-        if (shouldAppendLineBreak) {
63  
-          _appendLineBreak(fragment);
  52
+    if (useLineBreaks) {
  53
+      // Insert line break if list is after a non-block element
  54
+      if (previousSibling && !_isBlockElement(previousSibling)) {
  55
+        _appendLineBreak(fragment);
  56
+      }
  57
+
  58
+      while (listItem = (list.firstElementChild || list.firstChild)) {
  59
+        lastChild = listItem.lastChild;
  60
+        while (firstChild = listItem.firstChild) {
  61
+          isLastChild           = firstChild === lastChild;
  62
+          // This needs to be done before appending it to the fragment, as it otherwise will lose style information
  63
+          shouldAppendLineBreak = isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
  64
+          fragment.appendChild(firstChild);
  65
+          if (shouldAppendLineBreak) {
  66
+            _appendLineBreak(fragment);
  67
+          }
  68
+        }
  69
+        
  70
+        listItem.parentNode.removeChild(listItem);
  71
+      }
  72
+    } else {
  73
+      while (listItem = (list.firstElementChild || list.firstChild)) {
  74
+        if (listItem.querySelector && listItem.querySelector("div, p, ul, ol, menu, blockquote, h1, h2, h3, h4, h5, h6")) {
  75
+          while (firstChild = listItem.firstChild) {
  76
+            fragment.appendChild(firstChild);
  77
+          }
  78
+        } else {
  79
+          paragraph = doc.createElement("p");
  80
+          while (firstChild = listItem.firstChild) {
  81
+            paragraph.appendChild(firstChild);
  82
+          }
  83
+          fragment.appendChild(paragraph);
64 84
         }
  85
+        listItem.parentNode.removeChild(listItem);
65 86
       }
66  
-      
67  
-      listItem.parentNode.removeChild(listItem);
68 87
     }
  88
+
69 89
     list.parentNode.replaceChild(fragment, list);
70 90
   }
71 91
   
10  src/dom/simulate_placeholder.js
@@ -31,11 +31,11 @@
31 31
         };
32 32
 
33 33
     editor
34  
-      .observe("set_placeholder", set)
35  
-      .observe("unset_placeholder", unset)
36  
-      .observe("focus:composer", unset)
37  
-      .observe("paste:composer", unset)
38  
-      .observe("blur:composer", set);
  34
+      .on("set_placeholder", set)
  35
+      .on("unset_placeholder", unset)
  36
+      .on("focus:composer", unset)
  37
+      .on("paste:composer", unset)
  38
+      .on("blur:composer", set);
39 39
 
40 40
     set();
41 41
   };
6  src/editor.js
@@ -50,6 +50,8 @@
50 50
     composerClassName:    "wysihtml5-editor",
51 51
     // Class name to add to the body when the wysihtml5 editor is supported
52 52
     bodyClassName:        "wysihtml5-supported",
  53
+    // By default wysihtml5 will insert a <br> for line breaks, set this to false to use <p>
  54
+    useLineBreaks:        true,
53 55
     // Array (or single string) of stylesheet urls to be loaded in the editor's iframe
54 56
     stylesheets:          [],
55 57
     // Placeholder text to use, defaults to the placeholder attribute on the textarea element
@@ -84,7 +86,7 @@
84 86
         this._initParser();
85 87
       }
86 88
       
87  
-      this.observe("beforeload", function() {
  89
+      this.on("beforeload", function() {
88 90
         this.synchronizer = new wysihtml5.views.Synchronizer(this, this.textarea, this.composer);
89 91
         if (this.config.toolbar) {
90 92
           this.toolbar = new wysihtml5.toolbar.Toolbar(this, this.config.toolbar);
@@ -162,7 +164,7 @@
162 164
      *  - Observes for paste and drop
163 165
      */
164 166
     _initParser: function() {
165  
-      this.observe("paste:composer", function() {
  167
+      this.on("paste:composer", function() {
166 168
         var keepScrollPosition  = true,
167 169
             that                = this;
168 170
         that.composer.selection.executeAndRestore(function() {
38  src/lang/dispatcher.js
... ...
@@ -1,27 +1,13 @@
1 1
 wysihtml5.lang.Dispatcher = Base.extend(
2 2
   /** @scope wysihtml5.lang.Dialog.prototype */ {
3  
-  observe: function(eventName, handler) {
  3
+  on: function(eventName, handler) {
4 4
     this.events = this.events || {};
5 5
     this.events[eventName] = this.events[eventName] || [];
6 6
     this.events[eventName].push(handler);
7 7
     return this;
8 8
   },
9 9
 
10  
-  on: function() {
11  
-    return this.observe.apply(this, wysihtml5.lang.array(arguments).get());
12  
-  },
13  
-
14  
-  fire: function(eventName, payload) {
15  
-    this.events = this.events || {};
16  
-    var handlers = this.events[eventName] || [],
17  
-        i        = 0;
18  
-    for (; i<handlers.length; i++) {
19  
-      handlers[i].call(this, payload);
20  
-    }
21  
-    return this;
22  
-  },
23  
-
24  
-  stopObserving: function(eventName, handler) {
  10
+  off: function(eventName, handler) {
25 11
     this.events = this.events || {};
26 12
     var i = 0,
27 13
         handlers,
@@ -40,5 +26,25 @@ wysihtml5.lang.Dispatcher = Base.extend(
40 26
       this.events = {};
41 27
     }
42 28
     return this;
  29
+  },
  30
+  
  31
+  fire: function(eventName, payload) {
  32
+    this.events = this.events || {};
  33
+    var handlers = this.events[eventName] || [],
  34
+        i        = 0;
  35
+    for (; i<handlers.length; i++) {
  36
+      handlers[i].call(this, payload);
  37
+    }
  38
+    return this;
  39
+  },
  40
+  
  41
+  // deprecated, use .on()
  42
+  observe: function() {
  43
+    return this.on.apply(this, arguments);
  44
+  },
  45
+  
  46
+  // deprecated, use .off()
  47
+  stopObserving: function() {
  48
+    return this.off.apply(this, arguments);
43 49
   }
44 50
 });
85  src/quirks/ensure_proper_clearing.js
@@ -5,76 +5,19 @@
5 5
  * @exaple
6 6
  *    wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
7 7
  */
8  
-(function(wysihtml5) {
9  
-  var dom = wysihtml5.dom;
10  
-  
11  
-  wysihtml5.quirks.ensureProperClearing = (function() {
12  
-    var clearIfNecessary = function(event) {
13  
-      var element = this;
14  
-      setTimeout(function() {
15  
-        var innerHTML = element.innerHTML.toLowerCase();
16  
-        if (innerHTML == "<p>&nbsp;</p>" ||
17  
-            innerHTML == "<p>&nbsp;</p><p>&nbsp;</p>") {
18  
-          element.innerHTML = "";
19  
-        }
20  
-      }, 0);
21  
-    };
22  
-
23  
-    return function(composer) {
24  
-      dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
25  
-    };
26  
-  })();
27  
-
28  
-
29  
-
30  
-  /**
31  
-   * In Opera when the caret is in the first and only item of a list (<ul><li>|</li></ul>) and the list is the first child of the contentEditable element, it's impossible to delete the list by hitting backspace
32  
-   *
33  
-   * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
34  
-   * @exaple
35  
-   *    wysihtml5.quirks.ensureProperClearing(myContentEditableElement);
36  
-   */
37  
-  wysihtml5.quirks.ensureProperClearingOfLists = (function() {
38  
-    var ELEMENTS_THAT_CONTAIN_LI = ["OL", "UL", "MENU"];
39  
-
40  
-    var clearIfNecessary = function(element, contentEditableElement) {
41  
-      if (!contentEditableElement.firstChild || !wysihtml5.lang.array(ELEMENTS_THAT_CONTAIN_LI).contains(contentEditableElement.firstChild.nodeName)) {
42  
-        return;
43  
-      }
44  
-
45  
-      var list = dom.getParentElement(element, { nodeName: ELEMENTS_THAT_CONTAIN_LI });
46  
-      if (!list) {
47  
-        return;
48  
-      }
49  
-
50  
-      var listIsFirstChildOfContentEditable = list == contentEditableElement.firstChild;
51  
-      if (!listIsFirstChildOfContentEditable) {
52  
-        return;
53  
-      }
54  
-
55  
-      var hasOnlyOneListItem = list.childNodes.length <= 1;
56  
-      if (!hasOnlyOneListItem) {
57  
-        return;
58  
-      }
59  
-
60  
-      var onlyListItemIsEmpty = list.firstChild ? list.firstChild.innerHTML === "" : true;
61  
-      if (!onlyListItemIsEmpty) {
62  
-        return;
  8
+wysihtml5.quirks.ensureProperClearing = (function() {
  9
+  var clearIfNecessary = function(event) {
  10
+    var element = this;
  11
+    setTimeout(function() {
  12
+      var innerHTML = element.innerHTML.toLowerCase();
  13
+      if (innerHTML == "<p>&nbsp;</p>" ||
  14
+          innerHTML == "<p>&nbsp;</p><p>&nbsp;</p>") {
  15
+        element.innerHTML = "";
63 16
       }
  17
+    }, 0);
  18
+  };
64 19
 
65  
-      list.parentNode.removeChild(list);
66  
-    };
67  
-
68  
-    return function(composer) {
69  
-      dom.observe(composer.element, "keydown", function(event) {
70  
-        if (event.keyCode !== wysihtml5.BACKSPACE_KEY) {
71  
-          return;
72  
-        }
73  
-
74  
-        var element = composer.selection.getSelectedNode();
75  
-        clearIfNecessary(element, composer.element);
76  
-      });
77  
-    };
78  
-  })();
79  
-
80  
-})(wysihtml5);
  20
+  return function(composer) {
  21
+    wysihtml5.dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
  22
+  };
  23
+})();
78  src/quirks/insert_line_break_on_return.js
... ...
@@ -1,78 +0,0 @@
1  
-/**
2  
- * Some browsers don't insert line breaks when hitting return in a contentEditable element
3  
- *    - Opera & IE insert new <p> on return
4  
- *    - Chrome & Safari insert new <div> on return
5  
- *    - Firefox inserts <br> on return (yippie!)
6  
- *
7  
- * @param {Element} element
8  
- *
9  
- * @example
10  
- *    wysihtml5.quirks.insertLineBreakOnReturn(element);
11  
- */
12  
-(function(wysihtml5) {
13  
-  var dom                                           = wysihtml5.dom,
14  
-      USE_NATIVE_LINE_BREAK_WHEN_CARET_INSIDE_TAGS  = ["LI", "P", "H1", "H2", "H3", "H4", "H5", "H6"],
15  
-      LIST_TAGS                                     = ["UL", "OL", "MENU"];
16  
-  
17  
-  wysihtml5.quirks.insertLineBreakOnReturn = function(composer) {
18  
-    function unwrap(selectedNode) {
19  
-      var parentElement = dom.getParentElement(selectedNode, { nodeName: ["P", "DIV"] }, 2);
20  
-      if (!parentElement) {
21  
-        return;
22  
-      }
23  
-
24  
-      var invisibleSpace = document.createTextNode(wysihtml5.INVISIBLE_SPACE);
25  
-      dom.insert(invisibleSpace).before(parentElement);
26  
-      dom.replaceWithChildNodes(parentElement);
27  
-      composer.selection.selectNode(invisibleSpace);
28  
-    }
29  
-
30  
-    function keyDown(event) {
31  
-      var keyCode = event.keyCode;
32  
-      if (event.shiftKey || (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY)) {
33  
-        return;
34  
-      }
35  
-
36  
-      var element         = event.target,
37  
-          selectedNode    = composer.selection.getSelectedNode(),
38  
-          blockElement    = dom.getParentElement(selectedNode, { nodeName: USE_NATIVE_LINE_BREAK_WHEN_CARET_INSIDE_TAGS }, 4);
39  
-      if (blockElement) {
40  
-        // Some browsers create <p> elements after leaving a list
41  
-        // check after keydown of backspace and return whether a <p> got inserted and unwrap it
42  
-        if (blockElement.nodeName === "LI" && (keyCode === wysihtml5.ENTER_KEY || keyCode === wysihtml5.BACKSPACE_KEY)) {
43  
-          setTimeout(function() {
44  
-            var selectedNode = composer.selection.getSelectedNode(),
45  
-                list,
46  
-                div;
47  
-            if (!selectedNode) {
48  
-              return;
49  
-            }
50  
-
51  
-            list = dom.getParentElement(selectedNode, {
52  
-              nodeName: LIST_TAGS
53  
-            }, 2);
54  
-
55  
-            if (list) {
56  
-              return;
57  
-            }
58  
-
59  
-            unwrap(selectedNode);
60  
-          }, 0);
61  
-        } else if (blockElement.nodeName.match(/H[1-6]/) && keyCode === wysihtml5.ENTER_KEY) {
62  
-          setTimeout(function() {
63  
-            unwrap(composer.selection.getSelectedNode());
64  
-          }, 0);
65  
-        } 
66  
-        return;
67  
-      }
68  
-
69  
-      if (keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
70  
-        composer.commands.exec("insertLineBreak");
71  
-        event.preventDefault();
72  
-      }
73  
-    }
74  
-    
75  
-    // keypress doesn't fire when you hit backspace
76  
-    dom.observe(composer.element.ownerDocument, "keydown", keyDown);
77  
-  };
78  
-})(wysihtml5);
37  src/selection/selection.js
@@ -87,7 +87,7 @@
87 87
      * @example
88 88
      *    selection.selectNode(document.getElementById("my-image"));
89 89
      */
90  
-    selectNode: function(node) {
  90
+    selectNode: function(node, avoidInvisibleSpace) {
91 91
       var range           = rangy.createRange(this.doc),
92 92
           isElement       = node.nodeType === wysihtml5.ELEMENT_NODE,
93 93
           canHaveHTML     = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"),
@@ -96,7 +96,7 @@
96 96
           displayStyle    = dom.getStyle("display").from(node),
97 97
           isBlockElement  = (displayStyle === "block" || displayStyle === "list-item");
98 98
 
99  
-      if (isEmpty && isElement && canHaveHTML) {
  99
+      if (isEmpty && isElement && canHaveHTML && !avoidInvisibleSpace) {
100 100
         // Make sure that caret is visible in node by inserting a zero width no breaking space
101 101
         try { node.innerHTML = wysihtml5.INVISIBLE_SPACE; } catch(e) {}
102 102
       }
@@ -150,8 +150,12 @@
150 150
           oldScrollTop          = restoreScrollPosition && body.scrollTop,
151 151
           oldScrollLeft         = restoreScrollPosition && body.scrollLeft,
152 152
           className             = "_wysihtml5-temp-placeholder",
153  
-          placeholderHTML       = '<span class="' + className + '">' + wysihtml5.INVISIBLE_SPACE + '</span>',
  153
+          placeholderHtml       = '<span class="' + className + '">' + wysihtml5.INVISIBLE_SPACE + '</span>',
154 154
           range                 = this.getRange(this.doc),
  155
+          caretPlaceholder,
  156
+          newCaretPlaceholder,
  157
+          nextSibling,
  158
+          node,
155 159
           newRange;
156 160
       
157 161
       // Nothing selected, execute and say goodbye
@@ -160,21 +164,34 @@
160 164
         return;
161 165
       }
162 166
       
163  
-      var node = range.createContextualFragment(placeholderHTML);
164  
-      range.insertNode(node);
  167
+      if (wysihtml5.browser.hasInsertNodeIssue()) {
  168
+        this.doc.execCommand("insertHTML", false, placeholderHtml);
  169
+      } else {
  170
+        node = range.createContextualFragment(placeholderHtml);
  171
+        range.insertNode(node);
  172
+      }
165 173
       
166 174
       // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder
167 175
       try {
168 176
         method(range.startContainer, range.endContainer);
169  
-      } catch(e3) {
170  
-        setTimeout(function() { throw e3; }, 0);
  177
+      } catch(e) {
  178
+        setTimeout(function() { throw e; }, 0);
171 179
       }
172 180
       
173 181
       caretPlaceholder = this.doc.querySelector("." + className);
174 182
       if (caretPlaceholder) {
175 183
         newRange = rangy.createRange(this.doc);
176  
-        newRange.selectNode(caretPlaceholder);
177  
-        newRange.deleteContents();
  184
+        nextSibling = caretPlaceholder.nextSibling;
  185
+        // Opera is so fucked up when you wanna set focus before a <br>
  186
+        if (wysihtml5.browser.hasInsertNodeIssue() && nextSibling && nextSibling.nodeName === "BR") {
  187
+          newCaretPlaceholder = this.doc.createTextNode(wysihtml5.INVISIBLE_SPACE);
  188
+          dom.insert(newCaretPlaceholder).after(caretPlaceholder);
  189
+          newRange.setStartBefore(newCaretPlaceholder);
  190
+          newRange.setEndBefore(newCaretPlaceholder);
  191
+        } else {
  192
+          newRange.selectNode(caretPlaceholder);
  193
+          newRange.deleteContents();
  194
+        }
178 195
         this.setSelection(newRange);
179 196
       } else {
180 197
         // fallback for when all hell breaks loose
@@ -189,7 +206,7 @@
189 206
       // Remove it again, just to make sure that the placeholder is definitely out of the dom tree
190 207
       try {
191 208
         caretPlaceholder.parentNode.removeChild(caretPlaceholder);
192  
-      } catch(e4) {}
  209
+      } catch(e2) {}
193 210
     },
194 211
 
195 212
     /**
14  src/toolbar/toolbar.js
@@ -79,13 +79,13 @@
79 79
       if (dialogElement) {
80 80
         dialog = new wysihtml5.toolbar.Dialog(link, dialogElement);
81 81
 
82  
-        dialog.observe("show", function() {
  82
+        dialog.on("show", function() {
83 83
           caretBookmark = that.composer.selection.getBookmark();
84 84
 
85 85
           that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
86 86
         });
87 87
 
88  
-        dialog.observe("save", function(attributes) {
  88
+        dialog.on("save", function(attributes) {
89 89
           if (caretBookmark) {
90 90
             that.composer.selection.setBookmark(caretBookmark);
91 91
           }
@@ -94,7 +94,7 @@
94 94
           that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
95 95
         });
96 96
 
97  
-        dialog.observe("cancel", function() {
  97
+        dialog.on("cancel", function() {
98 98
           that.editor.focus(false);
99 99
           that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link });
100 100
         });
@@ -178,21 +178,21 @@
178 178
         event.preventDefault();
179 179
       });
180 180
 
181  
-      editor.observe("focus:composer", function() {
  181
+      editor.on("focus:composer", function() {
182 182
         that.bookmark = null;
183 183
         clearInterval(that.interval);
184 184
         that.interval = setInterval(function() { that._updateLinkStates(); }, 500);
185 185
       });
186 186
 
187  
-      editor.observe("blur:composer", function() {
  187
+      editor.on("blur:composer", function() {
188 188
         clearInterval(that.interval);
189 189
       });
190 190
 
191  
-      editor.observe("destroy:composer", function() {
  191
+      editor.on("destroy:composer", function() {
192 192
         clearInterval(that.interval);
193 193
       });
194 194
 
195  
-      editor.observe("change_view", function(currentView) {
  195
+      editor.on("change_view", function(currentView) {
196 196
         // Set timeout needed in order to let the blur event fire first
197 197
         setTimeout(function() {
198 198
           that.commandsDisabled = (currentView !== "composer");
4  src/undo_manager.js
@@ -121,11 +121,11 @@
121 121
       }
122 122
       
123 123
       this.editor
124  
-        .observe("newword:composer", function() {
  124
+        .on("newword:composer", function() {
125 125
           that.transact();
126 126
         })
127 127
         
128  
-        .observe("beforecommand:composer", function() {
  128
+        .on("beforecommand:composer", function() {
129 129
           that.transact();
130 130
         });
131 131
     },
110  src/views/composer.js
@@ -103,12 +103,12 @@
103 103
     },
104 104
 
105 105
     isEmpty: function() {
106  
-      var innerHTML               = this.element.innerHTML,
107  
-          elementsWithVisualValue = "blockquote, ul, ol, img, embed, object, table, iframe, svg, video, audio, button, input, select, textarea";
108  
-      return innerHTML === ""              || 
109  
-             innerHTML === this.CARET_HACK ||
110  
-             this.hasPlaceholderSet()      ||
111  
-             (this.getTextContent() === "" && !this.element.querySelector(elementsWithVisualValue));
  106
+      var innerHTML = this.element.innerHTML.toLowerCase();
  107
+      return innerHTML === ""            ||
  108
+             innerHTML === "<br>"        ||
  109
+             innerHTML === "<p></p>"     ||
  110
+             innerHTML === "<p><br></p>" ||
  111
+             this.hasPlaceholderSet();
112 112
     },
113 113
 
114 114
     _initSandbox: function() {
@@ -187,6 +187,7 @@
187 187
       this._initAutoLinking();
188 188
       this._initObjectResizing();
189 189
       this._initUndoManager();
  190
+      this._initLineBreaking();
190 191
       
191 192
       // Simulate html5 autofocus on contentEditable element
192 193
       // This doesn't work on IOS (5.1.1)
@@ -194,17 +195,11 @@
194 195
         setTimeout(function() { that.focus(true); }, 100);
195 196
       }
196 197
       
197  
-      wysihtml5.quirks.insertLineBreakOnReturn(this);
198  
-      
199 198
       // IE sometimes leaves a single paragraph, which can't be removed by the user
200 199
       if (!browser.clearsContentEditableCorrectly()) {
201 200
         wysihtml5.quirks.ensureProperClearing(this);
202 201
       }
203 202
       
204  
-      if (!browser.clearsListsInContentEditableCorrectly()) {
205  
-        wysihtml5.quirks.ensureProperClearingOfLists(this);
206  
-      }
207  
-      
208 203
       // Set up a sync that makes sure that textarea and editor have the same content
209 204
       if (this.initSync && this.config.sync) {
210 205
         this.initSync();
@@ -212,7 +207,7 @@
212 207
       
213 208
       // Okay hide the textarea, we are ready to go
214 209
       this.textarea.hide();
215  
-
  210
+      
216 211
       // Fire global (before-)load event
217 212
       this.parent.fire("beforeload").fire("load");
218 213
     },
@@ -232,10 +227,12 @@
232 227
       // Only do the auto linking by ourselves when the browser doesn't support auto linking
233 228
       // OR when he supports auto linking but we were able to turn it off (IE9+)
234 229
       if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) {
235  
-        this.parent.observe("newword:composer", function() {
236  
-          that.selection.executeAndRestore(function(startContainer, endContainer) {
237  
-            dom.autoLink(endContainer.parentNode);
238  
-          });
  230
+        this.parent.on("newword:composer", function() {
  231
+          if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) {
  232
+            that.selection.executeAndRestore(function(startContainer, endContainer) {
  233
+              dom.autoLink(endContainer.parentNode);
  234
+            });
  235
+          }
239 236
         });
240 237
         
241 238
         dom.observe(this.element, "blur", function() {
@@ -326,6 +323,85 @@
326 323
     
327 324
     _initUndoManager: function() {
328 325
       this.undoManager = new wysihtml5.UndoManager(this.parent);
  326
+    },
  327
+    
  328
+    _initLineBreaking: function() {
  329
+      var that                              = this,
  330
+          USE_NATIVE_LINE_BREAK_INSIDE_TAGS = ["LI", "P", "H1", "H2", "H3", "H4", "H5", "H6"],
  331
+          LIST_TAGS                         = ["UL", "OL", "MENU"];
  332
+      
  333
+      function adjust(selectedNode) {
  334
+        var parentElement = dom.getParentElement(selectedNode, { nodeName: ["P", "DIV"] }, 2);
  335
+        if (parentElement) {
  336
+          that.selection.executeAndRestore(function() {
  337
+            if (that.config.useLineBreaks) {
  338
+              dom.replaceWithChildNodes(parentElement);
  339
+            } else if (parentElement.nodeName !== "P") {
  340
+              dom.renameElement(parentElement, "p");
  341
+            }
  342
+          });
  343
+        }
  344
+      }
  345
+      
  346
+      if (!this.config.useLineBreaks) {
  347
+        dom.observe(this.element, ["focus", "keydown"], function() {
  348
+          if (that.isEmpty()) {
  349
+            var paragraph = that.doc.createElement("P");
  350
+            that.element.innerHTML = "";
  351
+            that.element.appendChild(paragraph);
  352
+            if (!browser.displaysCaretInEmptyContentEditableCorrectly()) {
  353
+              paragraph.innerHTML = "<br>";
  354
+              that.selection.setBefore(paragraph.firstChild);
  355
+            } else {
  356
+              that.selection.selectNode(paragraph, true);
  357
+            }
  358
+          }
  359
+        });
  360
+      }
  361
+      
  362
+      dom.observe(this.doc, "keydown", function(event) {
  363
+        var keyCode = event.keyCode;
  364
+        
  365
+        if (event.shiftKey) {
  366
+          return;
  367
+        }
  368
+        
  369
+        if (keyCode !== wysihtml5.ENTER_KEY && keyCode !== wysihtml5.BACKSPACE_KEY) {
  370
+          return;
  371
+        }
  372
+        
  373
+        var blockElement = dom.getParentElement(that.selection.getSelectedNode(), { nodeName: USE_NATIVE_LINE_BREAK_INSIDE_TAGS }, 4);
  374
+        if (blockElement) {
  375
+          setTimeout(function() {
  376
+            // Unwrap paragraph after leaving a list or a H1-6
  377
+            var selectedNode = that.selection.getSelectedNode(),
  378
+                isHeadline,
  379
+                list;
  380
+            
  381
+            if (blockElement.nodeName === "LI") {
  382
+              if (!selectedNode) {
  383
+                return;
  384
+              }
  385
+
  386
+              list = dom.getParentElement(selectedNode, { nodeName: LIST_TAGS }, 2);
  387
+
  388
+              if (!list) {
  389
+                adjust(selectedNode);
  390
+              }
  391
+            }
  392
+
  393
+            if (keyCode === wysihtml5.ENTER_KEY && blockElement.nodeName.match(/^H[1-6]$/)) {
  394
+              adjust(selectedNode);
  395
+            }
  396
+          }, 0);
  397
+          return;
  398
+        }
  399
+        
  400
+        if (that.config.useLineBreaks && keyCode === wysihtml5.ENTER_KEY && !wysihtml5.browser.insertsLineBreaksOnReturn()) {
  401
+          that.commands.exec("insertLineBreak");
  402
+          event.preventDefault();
  403
+        }
  404
+      });
329 405
     }
330 406
   });
331 407
 })(wysihtml5);
2  src/views/composer.observe.js
@@ -75,7 +75,7 @@
75 75
       }
76 76
     });
77 77
 
78  
-    this.parent.observe("paste:composer", function() {
  78
+    this.parent.on("paste:composer", function() {
79 79
       setTimeout(function() { that.parent.fire("newword:composer"); }, 0);
80 80
     });
81 81
 
13  src/views/composer.style.js
@@ -46,14 +46,15 @@
46 46
         "top", "left", "right", "bottom"
47 47
       ],
48 48
       ADDITIONAL_CSS_RULES = [
49  
-        "html             { height: 100%; }",
50  
-        "body             { min-height: 100%; padding: 0; margin: 0; margin-top: -1px; padding-top: 1px; }",
51  
-        "._wysihtml5-temp { display: none; }",
  49
+        "html                 { height: 100%; }",
  50
+        "body                 { min-height: 100%; padding: 0; margin: 0; margin-top: -1px; padding-top: 1px; }",
  51
+        "body > p:first-child { margin-top: 0; }",
  52
+        "._wysihtml5-temp     { display: none; }",
52 53
         wysihtml5.browser.isGecko ?
53 54
           "body.placeholder { color: graytext !important; }" : 
54 55
           "body.placeholder { color: #a9a9a9 !important; }",
55 56
         // Ensure that user see's broken images and can delete them
56  
-        "img:-moz-broken  { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }"
  57
+        "img:-moz-broken      { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }"
57 58
       ];
58 59
   
59 60
   /**
@@ -173,12 +174,12 @@
173 174
     }
174 175
     
175 176
     // --------- Sync focus/blur styles ---------
176  
-    this.parent.observe("focus:composer", function() {
  177
+    this.parent.on("focus:composer", function() {
177 178
       dom.copyStyles(boxFormattingStyles) .from(that.focusStylesHost).to(that.iframe);
178 179
       dom.copyStyles(TEXT_FORMATTING)     .from(that.focusStylesHost).to(that.element);
179 180
     });
180 181
     
181  
-    this.parent.observe("blur:composer", function() {
  182
+    this.parent.on("blur:composer", function() {
182 183
       dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.iframe);
183 184
       dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element);
184 185
     });
4  src/views/synchronizer.js
@@ -81,7 +81,7 @@
81 81
         });
82 82
       }
83 83
 
84  
-      this.editor.observe("change_view", function(view) {
  84
+      this.editor.on("change_view", function(view) {
85 85
         if (view === "composer" && !interval) {
86 86
           that.fromTextareaToComposer(true);
87 87
           startInterval();
@@ -91,7 +91,7 @@
91 91
         }
92 92
       });
93 93
 
94  
-      this.editor.observe("destroy:composer", stopInterval);
  94
+      this.editor.on("destroy:composer", stopInterval);
95 95
     }
96 96
   });
97 97
 })(wysihtml5);
2  src/views/textarea.js
@@ -52,7 +52,7 @@ wysihtml5.views.Textarea = wysihtml5.views.View.extend(
52 52
          */
53 53
         events = wysihtml5.browser.supportsEvent("focusin") ? ["focusin", "focusout", "change"] : ["focus", "blur", "change"];
54 54
     
55  
-    parent.observe("beforeload", function() {
  55
+    parent.on("beforeload", function() {
56 56
       wysihtml5.dom.observe(element, events, function(event) {
57 57
         var eventName = eventMapping[event.type] || event.type;
58 58
         parent.fire(eventName).fire(eventName + ":textarea");
4  src/views/view.js
@@ -13,8 +13,8 @@ wysihtml5.views.View = Base.extend(
13 13
   
14 14
   _observeViewChange: function() {
15 15
     var that = this;
16  
-    this.parent.observe("beforeload", function() {
17  
-      that.parent.observe("change_view", function(view) {
  16
+    this.parent.on("beforeload", function() {
  17
+      that.parent.on("change_view", function(view) {
18 18
         if (view === that.name) {
19 19
           that.parent.currentView = that;
20 20
           that.show();
68  test/dom/insert_css_test.js
... ...
@@ -1,31 +1,51 @@
1  
-module("wysihtml5.dom.insertCSS", {
2  
-  teardown: function() {
3  
-    var iframe;
4  
-    while (iframe = document.querySelector("iframe.wysihtml5-sandbox")) {
5  
-      iframe.parentNode.removeChild(iframe);
  1
+if (wysihtml5.browser.supported()) {
  2
+
  3
+  module("wysihtml5.dom.insertCSS", {
  4
+    teardown: function() {
  5
+      var iframe;
  6
+      while (iframe = document.querySelector("iframe.wysihtml5-sandbox")) {
  7
+        iframe.parentNode.removeChild(iframe);
  8
+      }
6 9
     }
7  
-  }
8  
-});
  10
+  });
9 11
 
10  
-asyncTest("Basic Tests", function() {
11  
-  expect(3);
  12
+  asyncTest("Basic Tests", function() {
  13
+    expect(3);
12 14
   
13  
-  new wysihtml5.dom.Sandbox(function(sandbox) {
14  
-    var doc     = sandbox.getDocument(),
15  
-        body    = doc.body,
16  
-        element = doc.createElement("sub");
  15
+    new wysihtml5.dom.Sandbox(function(sandbox) {
  16
+      var doc     = sandbox.getDocument(),
  17
+          body    = doc.body,
  18
+          element = doc.createElement("sub");
17 19
     
18  
-    body.appendChild(element);
  20
+      body.appendChild(element);
19 21
     
20  
-    wysihtml5.dom.insertCSS([
21  
-      "sub  { display: block; text-align: right; }",
22  
-      "body { text-indent: 50px; }"
23  
-    ]).into(doc);
  22
+      wysihtml5.dom.insertCSS([
  23
+        "sub  { display: block; text-align: right; }",
  24
+        "body { text-indent: 50px; }"
  25
+      ]).into(doc);
24 26
     
25  
-    equal(wysihtml5.dom.getStyle("display")    .from(element), "block");
26  
-    equal(wysihtml5.dom.getStyle("text-align") .from(element), "right");
27  
-    equal(wysihtml5.dom.getStyle("text-indent").from(element), "50px");
  27
+      equal(wysihtml5.dom.getStyle("display")    .from(element), "block");
  28
+      equal(wysihtml5.dom.getStyle("text-align") .from(element), "right");
  29
+      equal(wysihtml5.dom.getStyle("text-indent").from(element), "50px");
28 30
     
29  
-    start();