| rev | line source | 
| bsw/jbe@1309 | 1 /** | 
| bsw/jbe@1309 | 2  * @license wysihtml v0.6.0-beta1 | 
| bsw/jbe@1309 | 3  * https://github.com/Voog/wysihtml | 
| bsw/jbe@1309 | 4  * | 
| bsw/jbe@1309 | 5  * Author: Christopher Blum (https://github.com/tiff) | 
| bsw/jbe@1309 | 6  * Secondary author of extended features: Oliver Pulges (https://github.com/pulges) | 
| bsw/jbe@1309 | 7  * | 
| bsw/jbe@1309 | 8  * Copyright (C) 2012 XING AG | 
| bsw/jbe@1309 | 9  * Licensed under the MIT license (MIT) | 
| bsw/jbe@1309 | 10  * | 
| bsw/jbe@1309 | 11  */ | 
| bsw/jbe@1309 | 12 var wysihtml = { | 
| bsw/jbe@1309 | 13   version: '0.6.0-beta1', | 
| bsw/jbe@1309 | 14 | 
| bsw/jbe@1309 | 15   // namespaces | 
| bsw/jbe@1309 | 16   commands:   {}, | 
| bsw/jbe@1309 | 17   dom:        {}, | 
| bsw/jbe@1309 | 18   quirks:     {}, | 
| bsw/jbe@1309 | 19   toolbar:    {}, | 
| bsw/jbe@1309 | 20   lang:       {}, | 
| bsw/jbe@1309 | 21   selection:  {}, | 
| bsw/jbe@1309 | 22   views:      {}, | 
| bsw/jbe@1309 | 23 | 
| bsw/jbe@1309 | 24   editorExtenders: [], | 
| bsw/jbe@1309 | 25   extendEditor: function(extender) { | 
| bsw/jbe@1309 | 26     this.editorExtenders.push(extender); | 
| bsw/jbe@1309 | 27   }, | 
| bsw/jbe@1309 | 28 | 
| bsw/jbe@1309 | 29   INVISIBLE_SPACE: '\uFEFF', | 
| bsw/jbe@1309 | 30   INVISIBLE_SPACE_REG_EXP: /\uFEFF/g, | 
| bsw/jbe@1309 | 31 | 
| bsw/jbe@1309 | 32   VOID_ELEMENTS: 'area, base, br, col, embed, hr, img, input, keygen, link, meta, param, source, track, wbr', | 
| bsw/jbe@1309 | 33   PERMITTED_PHRASING_CONTENT_ONLY: 'h1, h2, h3, h4, h5, h6, p, pre', | 
| bsw/jbe@1309 | 34 | 
| bsw/jbe@1309 | 35   EMPTY_FUNCTION: function() {}, | 
| bsw/jbe@1309 | 36 | 
| bsw/jbe@1309 | 37   ELEMENT_NODE: 1, | 
| bsw/jbe@1309 | 38   TEXT_NODE:    3, | 
| bsw/jbe@1309 | 39 | 
| bsw/jbe@1309 | 40   BACKSPACE_KEY:  8, | 
| bsw/jbe@1309 | 41   ENTER_KEY:      13, | 
| bsw/jbe@1309 | 42   ESCAPE_KEY:     27, | 
| bsw/jbe@1309 | 43   SPACE_KEY:      32, | 
| bsw/jbe@1309 | 44   TAB_KEY:        9, | 
| bsw/jbe@1309 | 45   DELETE_KEY:     46 | 
| bsw/jbe@1309 | 46 }; | 
| bsw/jbe@1309 | 47 | 
| bsw/jbe@1309 | 48 wysihtml.polyfills = function(win, doc) { | 
| bsw/jbe@1309 | 49 | 
| bsw/jbe@1309 | 50   var methods = { | 
| bsw/jbe@1309 | 51 | 
| bsw/jbe@1309 | 52     // Safary has a bug of not restoring selection after node.normalize correctly. | 
| bsw/jbe@1309 | 53     // Detects the misbegaviour and patches it | 
| bsw/jbe@1309 | 54     normalizeHasCaretError: function() { | 
| bsw/jbe@1309 | 55       if ("createRange" in doc && "getSelection" in win) { | 
| bsw/jbe@1309 | 56         var originalTarget, | 
| bsw/jbe@1309 | 57             scrollTop = window.pageYOffset, | 
| bsw/jbe@1309 | 58             scrollLeft = window.pageXOffset, | 
| bsw/jbe@1309 | 59             e = doc.createElement('div'), | 
| bsw/jbe@1309 | 60             t1 = doc.createTextNode('a'), | 
| bsw/jbe@1309 | 61             t2 = doc.createTextNode('a'), | 
| bsw/jbe@1309 | 62             t3 = doc.createTextNode('a'), | 
| bsw/jbe@1309 | 63             r = doc.createRange(), | 
| bsw/jbe@1309 | 64             s, ret; | 
| bsw/jbe@1309 | 65 | 
| bsw/jbe@1309 | 66         if (document.activeElement) { | 
| bsw/jbe@1309 | 67           if (document.activeElement.nodeType === 1 && ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].indexOf(document.activeElement.nodeName) > -1) { | 
| bsw/jbe@1309 | 68             originalTarget = { | 
| bsw/jbe@1309 | 69               type: 'form', | 
| bsw/jbe@1309 | 70               node: document.activeElement, | 
| bsw/jbe@1309 | 71               start: document.activeElement.selectionStart, | 
| bsw/jbe@1309 | 72               end: document.activeElement.selectionEnd, | 
| bsw/jbe@1309 | 73             }; | 
| bsw/jbe@1309 | 74           } else { | 
| bsw/jbe@1309 | 75             s = win.getSelection(); | 
| bsw/jbe@1309 | 76             if (s && s.anchorNode) { | 
| bsw/jbe@1309 | 77               originalTarget = { | 
| bsw/jbe@1309 | 78                 type: 'range', | 
| bsw/jbe@1309 | 79                 anchorNode: s.anchorNode, | 
| bsw/jbe@1309 | 80                 anchorOffset: s.anchorOffset, | 
| bsw/jbe@1309 | 81                 focusNode: s.focusNode, | 
| bsw/jbe@1309 | 82                 focusOffset: s.focusOffset | 
| bsw/jbe@1309 | 83               }; | 
| bsw/jbe@1309 | 84             } | 
| bsw/jbe@1309 | 85           } | 
| bsw/jbe@1309 | 86         } | 
| bsw/jbe@1309 | 87 | 
| bsw/jbe@1309 | 88         e.setAttribute('contenteditable', 'true'); | 
| bsw/jbe@1309 | 89         e.appendChild(t1); | 
| bsw/jbe@1309 | 90         e.appendChild(t2); | 
| bsw/jbe@1309 | 91         e.appendChild(t3); | 
| bsw/jbe@1309 | 92         doc.body.appendChild(e); | 
| bsw/jbe@1309 | 93         r.setStart(t2, 1); | 
| bsw/jbe@1309 | 94         r.setEnd(t2, 1); | 
| bsw/jbe@1309 | 95 | 
| bsw/jbe@1309 | 96         s = win.getSelection(); | 
| bsw/jbe@1309 | 97         s.removeAllRanges(); | 
| bsw/jbe@1309 | 98         s.addRange(r); | 
| bsw/jbe@1309 | 99         e.normalize(); | 
| bsw/jbe@1309 | 100         s = win.getSelection(); | 
| bsw/jbe@1309 | 101 | 
| bsw/jbe@1309 | 102         ret = (e.childNodes.length !== 1 || s.anchorNode !== e.firstChild || s.anchorOffset !== 2); | 
| bsw/jbe@1309 | 103         e.parentNode.removeChild(e); | 
| bsw/jbe@1309 | 104         s.removeAllRanges(); | 
| bsw/jbe@1309 | 105 | 
| bsw/jbe@1309 | 106         if (originalTarget) { | 
| bsw/jbe@1309 | 107           if (originalTarget.type === 'form') { | 
| bsw/jbe@1309 | 108             // The selection parameters are not present for all form elements | 
| bsw/jbe@1309 | 109             if (typeof originalTarget.start !== 'undefined' && typeof originalTarget.end !== 'undefined') { | 
| bsw/jbe@1309 | 110               originalTarget.node.setSelectionRange(originalTarget.start, originalTarget.end); | 
| bsw/jbe@1309 | 111             } | 
| bsw/jbe@1309 | 112             originalTarget.node.focus(); | 
| bsw/jbe@1309 | 113           } else if (originalTarget.type === 'range') { | 
| bsw/jbe@1309 | 114             r = doc.createRange(); | 
| bsw/jbe@1309 | 115             r.setStart(originalTarget.anchorNode, originalTarget.anchorOffset); | 
| bsw/jbe@1309 | 116             r.setEnd(originalTarget.focusNode, originalTarget.focusOffset); | 
| bsw/jbe@1309 | 117             s.addRange(r); | 
| bsw/jbe@1309 | 118           } | 
| bsw/jbe@1309 | 119         } | 
| bsw/jbe@1309 | 120 | 
| bsw/jbe@1309 | 121         if (scrollTop !== window.pageYOffset || scrollLeft !== window.pageXOffset) { | 
| bsw/jbe@1309 | 122           win.scrollTo(scrollLeft, scrollTop); | 
| bsw/jbe@1309 | 123         } | 
| bsw/jbe@1309 | 124 | 
| bsw/jbe@1309 | 125         return ret; | 
| bsw/jbe@1309 | 126       } | 
| bsw/jbe@1309 | 127     }, | 
| bsw/jbe@1309 | 128 | 
| bsw/jbe@1309 | 129     apply: function() { | 
| bsw/jbe@1309 | 130       // closest, matches, and remove polyfill | 
| bsw/jbe@1309 | 131       // https://github.com/jonathantneal/closest | 
| bsw/jbe@1309 | 132       (function (ELEMENT) { | 
| bsw/jbe@1309 | 133         ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector || function matches(selector) { | 
| bsw/jbe@1309 | 134           var | 
| bsw/jbe@1309 | 135           element = this, | 
| bsw/jbe@1309 | 136           elements = (element.document || element.ownerDocument).querySelectorAll(selector), | 
| bsw/jbe@1309 | 137           index = 0; | 
| bsw/jbe@1309 | 138 | 
| bsw/jbe@1309 | 139           while (elements[index] && elements[index] !== element) { | 
| bsw/jbe@1309 | 140             ++index; | 
| bsw/jbe@1309 | 141           } | 
| bsw/jbe@1309 | 142 | 
| bsw/jbe@1309 | 143           return elements[index] ? true : false; | 
| bsw/jbe@1309 | 144         }; | 
| bsw/jbe@1309 | 145 | 
| bsw/jbe@1309 | 146         ELEMENT.closest = ELEMENT.closest || function closest(selector) { | 
| bsw/jbe@1309 | 147           var element = this; | 
| bsw/jbe@1309 | 148 | 
| bsw/jbe@1309 | 149           while (element) { | 
| bsw/jbe@1309 | 150             if (element.matches(selector)) { | 
| bsw/jbe@1309 | 151               break; | 
| bsw/jbe@1309 | 152             } | 
| bsw/jbe@1309 | 153 | 
| bsw/jbe@1309 | 154             element = element.parentElement; | 
| bsw/jbe@1309 | 155           } | 
| bsw/jbe@1309 | 156 | 
| bsw/jbe@1309 | 157           return element; | 
| bsw/jbe@1309 | 158         }; | 
| bsw/jbe@1309 | 159 | 
| bsw/jbe@1309 | 160         ELEMENT.remove = ELEMENT.remove || function remove() { | 
| bsw/jbe@1309 | 161           if (this.parentNode) { | 
| bsw/jbe@1309 | 162             this.parentNode.removeChild(this); | 
| bsw/jbe@1309 | 163           } | 
| bsw/jbe@1309 | 164         }; | 
| bsw/jbe@1309 | 165 | 
| bsw/jbe@1309 | 166       }(win.Element.prototype)); | 
| bsw/jbe@1309 | 167 | 
| bsw/jbe@1309 | 168       if (!('classList' in doc.documentElement) && win.Object.defineProperty && typeof win.HTMLElement !== 'undefined') { | 
| bsw/jbe@1309 | 169         win.Object.defineProperty(win.HTMLElement.prototype, 'classList', { | 
| bsw/jbe@1309 | 170           get: function() { | 
| bsw/jbe@1309 | 171             var self = this; | 
| bsw/jbe@1309 | 172             function update(fn) { | 
| bsw/jbe@1309 | 173               return function(value) { | 
| bsw/jbe@1309 | 174                 var classes = self.className.split(/\s+/), | 
| bsw/jbe@1309 | 175                     index = classes.indexOf(value); | 
| bsw/jbe@1309 | 176 | 
| bsw/jbe@1309 | 177                 fn(classes, index, value); | 
| bsw/jbe@1309 | 178                 self.className = classes.join(' '); | 
| bsw/jbe@1309 | 179               }; | 
| bsw/jbe@1309 | 180             } | 
| bsw/jbe@1309 | 181 | 
| bsw/jbe@1309 | 182             var ret = { | 
| bsw/jbe@1309 | 183                 add: update(function(classes, index, value) { | 
| bsw/jbe@1309 | 184                   ~index || classes.push(value); | 
| bsw/jbe@1309 | 185                 }), | 
| bsw/jbe@1309 | 186 | 
| bsw/jbe@1309 | 187                 remove: update(function(classes, index) { | 
| bsw/jbe@1309 | 188                   ~index && classes.splice(index, 1); | 
| bsw/jbe@1309 | 189                 }), | 
| bsw/jbe@1309 | 190 | 
| bsw/jbe@1309 | 191                 toggle: update(function(classes, index, value) { | 
| bsw/jbe@1309 | 192                   ~index ? classes.splice(index, 1) : classes.push(value); | 
| bsw/jbe@1309 | 193                 }), | 
| bsw/jbe@1309 | 194 | 
| bsw/jbe@1309 | 195                 contains: function(value) { | 
| bsw/jbe@1309 | 196                   return !!~self.className.split(/\s+/).indexOf(value); | 
| bsw/jbe@1309 | 197                 }, | 
| bsw/jbe@1309 | 198 | 
| bsw/jbe@1309 | 199                 item: function(i) { | 
| bsw/jbe@1309 | 200                   return self.className.split(/\s+/)[i] || null; | 
| bsw/jbe@1309 | 201                 } | 
| bsw/jbe@1309 | 202               }; | 
| bsw/jbe@1309 | 203 | 
| bsw/jbe@1309 | 204             win.Object.defineProperty(ret, 'length', { | 
| bsw/jbe@1309 | 205               get: function() { | 
| bsw/jbe@1309 | 206                 return self.className.split(/\s+/).length; | 
| bsw/jbe@1309 | 207               } | 
| bsw/jbe@1309 | 208             }); | 
| bsw/jbe@1309 | 209 | 
| bsw/jbe@1309 | 210             return ret; | 
| bsw/jbe@1309 | 211           } | 
| bsw/jbe@1309 | 212         }); | 
| bsw/jbe@1309 | 213       } | 
| bsw/jbe@1309 | 214 | 
| bsw/jbe@1309 | 215       var getTextNodes = function(node){ | 
| bsw/jbe@1309 | 216         var all = []; | 
| bsw/jbe@1309 | 217         for (node=node.firstChild;node;node=node.nextSibling){ | 
| bsw/jbe@1309 | 218           if (node.nodeType == 3) { | 
| bsw/jbe@1309 | 219               all.push(node); | 
| bsw/jbe@1309 | 220           } else { | 
| bsw/jbe@1309 | 221             all = all.concat(getTextNodes(node)); | 
| bsw/jbe@1309 | 222           } | 
| bsw/jbe@1309 | 223         } | 
| bsw/jbe@1309 | 224         return all; | 
| bsw/jbe@1309 | 225       }; | 
| bsw/jbe@1309 | 226 | 
| bsw/jbe@1309 | 227       var isInDom = function(node) { | 
| bsw/jbe@1309 | 228         var doc = node.ownerDocument, | 
| bsw/jbe@1309 | 229             n = node; | 
| bsw/jbe@1309 | 230 | 
| bsw/jbe@1309 | 231         do { | 
| bsw/jbe@1309 | 232           if (n === doc) { | 
| bsw/jbe@1309 | 233             return true; | 
| bsw/jbe@1309 | 234           } | 
| bsw/jbe@1309 | 235           n = n.parentNode; | 
| bsw/jbe@1309 | 236         } while(n); | 
| bsw/jbe@1309 | 237 | 
| bsw/jbe@1309 | 238         return false; | 
| bsw/jbe@1309 | 239       }; | 
| bsw/jbe@1309 | 240 | 
| bsw/jbe@1309 | 241       var normalizeFix = function() { | 
| bsw/jbe@1309 | 242         var f = win.Node.prototype.normalize; | 
| bsw/jbe@1309 | 243         var nf = function() { | 
| bsw/jbe@1309 | 244           var texts = getTextNodes(this), | 
| bsw/jbe@1309 | 245               s = this.ownerDocument.defaultView.getSelection(), | 
| bsw/jbe@1309 | 246               anode = s.anchorNode, | 
| bsw/jbe@1309 | 247               aoffset = s.anchorOffset, | 
| bsw/jbe@1309 | 248               aelement = anode && anode.nodeType === 1 && anode.childNodes.length > 0 ? anode.childNodes[aoffset] : undefined, | 
| bsw/jbe@1309 | 249               fnode = s.focusNode, | 
| bsw/jbe@1309 | 250               foffset = s.focusOffset, | 
| bsw/jbe@1309 | 251               felement = fnode && fnode.nodeType === 1 && foffset > 0 ? fnode.childNodes[foffset -1] : undefined, | 
| bsw/jbe@1309 | 252               r = this.ownerDocument.createRange(), | 
| bsw/jbe@1309 | 253               prevTxt = texts.shift(), | 
| bsw/jbe@1309 | 254               curText = prevTxt ? texts.shift() : null; | 
| bsw/jbe@1309 | 255 | 
| bsw/jbe@1309 | 256           if (felement && felement.nodeType === 3) { | 
| bsw/jbe@1309 | 257             fnode = felement; | 
| bsw/jbe@1309 | 258             foffset = felement.nodeValue.length; | 
| bsw/jbe@1309 | 259             felement = undefined; | 
| bsw/jbe@1309 | 260           } | 
| bsw/jbe@1309 | 261 | 
| bsw/jbe@1309 | 262           if (aelement && aelement.nodeType === 3) { | 
| bsw/jbe@1309 | 263             anode = aelement; | 
| bsw/jbe@1309 | 264             aoffset = 0; | 
| bsw/jbe@1309 | 265             aelement = undefined; | 
| bsw/jbe@1309 | 266           } | 
| bsw/jbe@1309 | 267 | 
| bsw/jbe@1309 | 268           if ((anode === fnode && foffset < aoffset) || (anode !== fnode && (anode.compareDocumentPosition(fnode) & win.Node.DOCUMENT_POSITION_PRECEDING) && !(anode.compareDocumentPosition(fnode) & win.Node.DOCUMENT_POSITION_CONTAINS))) { | 
| bsw/jbe@1309 | 269             fnode = [anode, anode = fnode][0]; | 
| bsw/jbe@1309 | 270             foffset = [aoffset, aoffset = foffset][0]; | 
| bsw/jbe@1309 | 271           } | 
| bsw/jbe@1309 | 272 | 
| bsw/jbe@1309 | 273           while(prevTxt && curText) { | 
| bsw/jbe@1309 | 274             if (curText.previousSibling && curText.previousSibling === prevTxt) { | 
| bsw/jbe@1309 | 275               if (anode === curText) { | 
| bsw/jbe@1309 | 276                 anode = prevTxt; | 
| bsw/jbe@1309 | 277                 aoffset = prevTxt.nodeValue.length +  aoffset; | 
| bsw/jbe@1309 | 278               } | 
| bsw/jbe@1309 | 279               if (fnode === curText) { | 
| bsw/jbe@1309 | 280                 fnode = prevTxt; | 
| bsw/jbe@1309 | 281                 foffset = prevTxt.nodeValue.length +  foffset; | 
| bsw/jbe@1309 | 282               } | 
| bsw/jbe@1309 | 283               prevTxt.nodeValue = prevTxt.nodeValue + curText.nodeValue; | 
| bsw/jbe@1309 | 284               curText.parentNode.removeChild(curText); | 
| bsw/jbe@1309 | 285               curText = texts.shift(); | 
| bsw/jbe@1309 | 286             } else { | 
| bsw/jbe@1309 | 287               prevTxt = curText; | 
| bsw/jbe@1309 | 288               curText = texts.shift(); | 
| bsw/jbe@1309 | 289             } | 
| bsw/jbe@1309 | 290           } | 
| bsw/jbe@1309 | 291 | 
| bsw/jbe@1309 | 292           if (felement) { | 
| bsw/jbe@1309 | 293             foffset = Array.prototype.indexOf.call(felement.parentNode.childNodes, felement) + 1; | 
| bsw/jbe@1309 | 294           } | 
| bsw/jbe@1309 | 295 | 
| bsw/jbe@1309 | 296           if (aelement) { | 
| bsw/jbe@1309 | 297             aoffset = Array.prototype.indexOf.call(aelement.parentNode.childNodes, aelement); | 
| bsw/jbe@1309 | 298           } | 
| bsw/jbe@1309 | 299 | 
| bsw/jbe@1309 | 300           if (isInDom(this) && anode && anode.parentNode && fnode && fnode.parentNode) { | 
| bsw/jbe@1309 | 301             r.setStart(anode, aoffset); | 
| bsw/jbe@1309 | 302             r.setEnd(fnode, foffset); | 
| bsw/jbe@1309 | 303             s.removeAllRanges(); | 
| bsw/jbe@1309 | 304             s.addRange(r); | 
| bsw/jbe@1309 | 305           } | 
| bsw/jbe@1309 | 306         }; | 
| bsw/jbe@1309 | 307         win.Node.prototype.normalize = nf; | 
| bsw/jbe@1309 | 308       }; | 
| bsw/jbe@1309 | 309 | 
| bsw/jbe@1309 | 310       var F = function() { | 
| bsw/jbe@1309 | 311         win.removeEventListener("load", F); | 
| bsw/jbe@1309 | 312         if ("Node" in win && "normalize" in win.Node.prototype && methods.normalizeHasCaretError()) { | 
| bsw/jbe@1309 | 313           normalizeFix(); | 
| bsw/jbe@1309 | 314         } | 
| bsw/jbe@1309 | 315       }; | 
| bsw/jbe@1309 | 316 | 
| bsw/jbe@1309 | 317       if (doc.readyState !== "complete") { | 
| bsw/jbe@1309 | 318         win.addEventListener("load", F); | 
| bsw/jbe@1309 | 319       } else { | 
| bsw/jbe@1309 | 320         F(); | 
| bsw/jbe@1309 | 321       } | 
| bsw/jbe@1309 | 322 | 
| bsw/jbe@1309 | 323       // CustomEvent for ie9 and up | 
| bsw/jbe@1309 | 324       function nativeCustomEventSupported() { | 
| bsw/jbe@1309 | 325         try { | 
| bsw/jbe@1309 | 326           var p = new win.CustomEvent('cat', {detail: {foo: 'bar'}}); | 
| bsw/jbe@1309 | 327           return  'cat' === p.type && 'bar' === p.detail.foo; | 
| bsw/jbe@1309 | 328         } catch (e) {} | 
| bsw/jbe@1309 | 329         return false; | 
| bsw/jbe@1309 | 330       } | 
| bsw/jbe@1309 | 331 | 
| bsw/jbe@1309 | 332       // Polyfills CustomEvent object for IE9 and up | 
| bsw/jbe@1309 | 333       (function() { | 
| bsw/jbe@1309 | 334         if (!nativeCustomEventSupported() && "CustomEvent" in win) { | 
| bsw/jbe@1309 | 335           function CustomEvent(event, params) { | 
| bsw/jbe@1309 | 336             params = params || {bubbles: false, cancelable: false, detail: undefined}; | 
| bsw/jbe@1309 | 337             var evt = doc.createEvent('CustomEvent'); | 
| bsw/jbe@1309 | 338             evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); | 
| bsw/jbe@1309 | 339             return evt; | 
| bsw/jbe@1309 | 340           } | 
| bsw/jbe@1309 | 341           CustomEvent.prototype = win.Event.prototype; | 
| bsw/jbe@1309 | 342           win.CustomEvent = CustomEvent; | 
| bsw/jbe@1309 | 343         } | 
| bsw/jbe@1309 | 344       })(); | 
| bsw/jbe@1309 | 345     } | 
| bsw/jbe@1309 | 346   } | 
| bsw/jbe@1309 | 347 | 
| bsw/jbe@1309 | 348   return methods; | 
| bsw/jbe@1309 | 349 }; | 
| bsw/jbe@1309 | 350 | 
| bsw/jbe@1309 | 351 wysihtml.polyfills(window, document).apply(); | 
| bsw/jbe@1309 | 352 | 
| bsw/jbe@1309 | 353 /* | 
| bsw/jbe@1309 | 354 	Base.js, version 1.1a | 
| bsw/jbe@1309 | 355 	Copyright 2006-2010, Dean Edwards | 
| bsw/jbe@1309 | 356 	License: http://www.opensource.org/licenses/mit-license.php | 
| bsw/jbe@1309 | 357 */ | 
| bsw/jbe@1309 | 358 | 
| bsw/jbe@1309 | 359 var Base = function() { | 
| bsw/jbe@1309 | 360 	// dummy | 
| bsw/jbe@1309 | 361 }; | 
| bsw/jbe@1309 | 362 | 
| bsw/jbe@1309 | 363 Base.extend = function(_instance, _static) { // subclass | 
| bsw/jbe@1309 | 364 	var extend = Base.prototype.extend; | 
| bsw/jbe@1309 | 365 | 
| bsw/jbe@1309 | 366 	// build the prototype | 
| bsw/jbe@1309 | 367 	Base._prototyping = true; | 
| bsw/jbe@1309 | 368 	var proto = new this; | 
| bsw/jbe@1309 | 369 	extend.call(proto, _instance); | 
| bsw/jbe@1309 | 370   proto.base = function() { | 
| bsw/jbe@1309 | 371     // call this method from any other method to invoke that method's ancestor | 
| bsw/jbe@1309 | 372   }; | 
| bsw/jbe@1309 | 373 	delete Base._prototyping; | 
| bsw/jbe@1309 | 374 | 
| bsw/jbe@1309 | 375 	// create the wrapper for the constructor function | 
| bsw/jbe@1309 | 376 	//var constructor = proto.constructor.valueOf(); //-dean | 
| bsw/jbe@1309 | 377 	var constructor = proto.constructor; | 
| bsw/jbe@1309 | 378 	var klass = proto.constructor = function() { | 
| bsw/jbe@1309 | 379 		if (!Base._prototyping) { | 
| bsw/jbe@1309 | 380 			if (this._constructing || this.constructor == klass) { // instantiation | 
| bsw/jbe@1309 | 381 				this._constructing = true; | 
| bsw/jbe@1309 | 382 				constructor.apply(this, arguments); | 
| bsw/jbe@1309 | 383 				delete this._constructing; | 
| bsw/jbe@1309 | 384 			} else if (arguments[0] != null) { // casting | 
| bsw/jbe@1309 | 385 				return (arguments[0].extend || extend).call(arguments[0], proto); | 
| bsw/jbe@1309 | 386 			} | 
| bsw/jbe@1309 | 387 		} | 
| bsw/jbe@1309 | 388 	}; | 
| bsw/jbe@1309 | 389 | 
| bsw/jbe@1309 | 390 	// build the class interface | 
| bsw/jbe@1309 | 391 	klass.ancestor = this; | 
| bsw/jbe@1309 | 392 	klass.extend = this.extend; | 
| bsw/jbe@1309 | 393 	klass.forEach = this.forEach; | 
| bsw/jbe@1309 | 394 	klass.implement = this.implement; | 
| bsw/jbe@1309 | 395 	klass.prototype = proto; | 
| bsw/jbe@1309 | 396 	klass.toString = this.toString; | 
| bsw/jbe@1309 | 397 	klass.valueOf = function(type) { | 
| bsw/jbe@1309 | 398 		//return (type == "object") ? klass : constructor; //-dean | 
| bsw/jbe@1309 | 399 		return (type == "object") ? klass : constructor.valueOf(); | 
| bsw/jbe@1309 | 400 	}; | 
| bsw/jbe@1309 | 401 	extend.call(klass, _static); | 
| bsw/jbe@1309 | 402 	// class initialisation | 
| bsw/jbe@1309 | 403 	if (typeof klass.init == "function") klass.init(); | 
| bsw/jbe@1309 | 404 	return klass; | 
| bsw/jbe@1309 | 405 }; | 
| bsw/jbe@1309 | 406 | 
| bsw/jbe@1309 | 407 Base.prototype = { | 
| bsw/jbe@1309 | 408 	extend: function(source, value) { | 
| bsw/jbe@1309 | 409 		if (arguments.length > 1) { // extending with a name/value pair | 
| bsw/jbe@1309 | 410 			var ancestor = this[source]; | 
| bsw/jbe@1309 | 411 			if (ancestor && (typeof value == "function") && // overriding a method? | 
| bsw/jbe@1309 | 412 				// the valueOf() comparison is to avoid circular references | 
| bsw/jbe@1309 | 413 				(!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) && | 
| bsw/jbe@1309 | 414 				/\bbase\b/.test(value)) { | 
| bsw/jbe@1309 | 415 				// get the underlying method | 
| bsw/jbe@1309 | 416 				var method = value.valueOf(); | 
| bsw/jbe@1309 | 417 				// override | 
| bsw/jbe@1309 | 418 				value = function() { | 
| bsw/jbe@1309 | 419 					var previous = this.base || Base.prototype.base; | 
| bsw/jbe@1309 | 420 					this.base = ancestor; | 
| bsw/jbe@1309 | 421 					var returnValue = method.apply(this, arguments); | 
| bsw/jbe@1309 | 422 					this.base = previous; | 
| bsw/jbe@1309 | 423 					return returnValue; | 
| bsw/jbe@1309 | 424 				}; | 
| bsw/jbe@1309 | 425 				// point to the underlying method | 
| bsw/jbe@1309 | 426 				value.valueOf = function(type) { | 
| bsw/jbe@1309 | 427 					return (type == "object") ? value : method; | 
| bsw/jbe@1309 | 428 				}; | 
| bsw/jbe@1309 | 429 				value.toString = Base.toString; | 
| bsw/jbe@1309 | 430 			} | 
| bsw/jbe@1309 | 431 			this[source] = value; | 
| bsw/jbe@1309 | 432 		} else if (source) { // extending with an object literal | 
| bsw/jbe@1309 | 433 			var extend = Base.prototype.extend; | 
| bsw/jbe@1309 | 434 			// if this object has a customised extend method then use it | 
| bsw/jbe@1309 | 435 			if (!Base._prototyping && typeof this != "function") { | 
| bsw/jbe@1309 | 436 				extend = this.extend || extend; | 
| bsw/jbe@1309 | 437 			} | 
| bsw/jbe@1309 | 438 			var proto = {toSource: null}; | 
| bsw/jbe@1309 | 439 			// do the "toString" and other methods manually | 
| bsw/jbe@1309 | 440 			var hidden = ["constructor", "toString", "valueOf"]; | 
| bsw/jbe@1309 | 441 			// if we are prototyping then include the constructor | 
| bsw/jbe@1309 | 442 			var i = Base._prototyping ? 0 : 1; | 
| bsw/jbe@1309 | 443 			while (key = hidden[i++]) { | 
| bsw/jbe@1309 | 444 				if (source[key] != proto[key]) { | 
| bsw/jbe@1309 | 445 					extend.call(this, key, source[key]); | 
| bsw/jbe@1309 | 446 | 
| bsw/jbe@1309 | 447 				} | 
| bsw/jbe@1309 | 448 			} | 
| bsw/jbe@1309 | 449 			// copy each of the source object's properties to this object | 
| bsw/jbe@1309 | 450 			for (var key in source) { | 
| bsw/jbe@1309 | 451 				if (!proto[key]) extend.call(this, key, source[key]); | 
| bsw/jbe@1309 | 452 			} | 
| bsw/jbe@1309 | 453 		} | 
| bsw/jbe@1309 | 454 		return this; | 
| bsw/jbe@1309 | 455 	} | 
| bsw/jbe@1309 | 456 }; | 
| bsw/jbe@1309 | 457 | 
| bsw/jbe@1309 | 458 // initialise | 
| bsw/jbe@1309 | 459 Base = Base.extend({ | 
| bsw/jbe@1309 | 460 	constructor: function() { | 
| bsw/jbe@1309 | 461 		this.extend(arguments[0]); | 
| bsw/jbe@1309 | 462 	} | 
| bsw/jbe@1309 | 463 }, { | 
| bsw/jbe@1309 | 464 	ancestor: Object, | 
| bsw/jbe@1309 | 465 	version: "1.1", | 
| bsw/jbe@1309 | 466 | 
| bsw/jbe@1309 | 467 	forEach: function(object, block, context) { | 
| bsw/jbe@1309 | 468 		for (var key in object) { | 
| bsw/jbe@1309 | 469 			if (this.prototype[key] === undefined) { | 
| bsw/jbe@1309 | 470 				block.call(context, object[key], key, object); | 
| bsw/jbe@1309 | 471 			} | 
| bsw/jbe@1309 | 472 		} | 
| bsw/jbe@1309 | 473 	}, | 
| bsw/jbe@1309 | 474 | 
| bsw/jbe@1309 | 475 	implement: function() { | 
| bsw/jbe@1309 | 476 		for (var i = 0; i < arguments.length; i++) { | 
| bsw/jbe@1309 | 477 			if (typeof arguments[i] == "function") { | 
| bsw/jbe@1309 | 478 				// if it's a function, call it | 
| bsw/jbe@1309 | 479 				arguments[i](this.prototype); | 
| bsw/jbe@1309 | 480 			} else { | 
| bsw/jbe@1309 | 481 				// add the interface using the extend method | 
| bsw/jbe@1309 | 482 				this.prototype.extend(arguments[i]); | 
| bsw/jbe@1309 | 483 			} | 
| bsw/jbe@1309 | 484 		} | 
| bsw/jbe@1309 | 485 		return this; | 
| bsw/jbe@1309 | 486 	}, | 
| bsw/jbe@1309 | 487 | 
| bsw/jbe@1309 | 488 	toString: function() { | 
| bsw/jbe@1309 | 489 		return String(this.valueOf()); | 
| bsw/jbe@1309 | 490 	} | 
| bsw/jbe@1309 | 491 }); | 
| bsw/jbe@1309 | 492 /** | 
| bsw/jbe@1309 | 493  * Rangy, a cross-browser JavaScript range and selection library | 
| bsw/jbe@1309 | 494  * https://github.com/timdown/rangy | 
| bsw/jbe@1309 | 495  * | 
| bsw/jbe@1309 | 496  * Copyright 2015, Tim Down | 
| bsw/jbe@1309 | 497  * Licensed under the MIT license. | 
| bsw/jbe@1309 | 498  * Version: 1.3.1-dev | 
| bsw/jbe@1309 | 499  * Build date: 20 May 2015 | 
| bsw/jbe@1309 | 500  * | 
| bsw/jbe@1309 | 501  * NOTE: UMD wrapper removed manually for bundling (Oliver Pulges) | 
| bsw/jbe@1309 | 502  */ | 
| bsw/jbe@1309 | 503 var rangy; | 
| bsw/jbe@1309 | 504 | 
| bsw/jbe@1309 | 505 (function() { | 
| bsw/jbe@1309 | 506     var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; | 
| bsw/jbe@1309 | 507 | 
| bsw/jbe@1309 | 508     // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START | 
| bsw/jbe@1309 | 509     // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113. | 
| bsw/jbe@1309 | 510     var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", | 
| bsw/jbe@1309 | 511         "commonAncestorContainer"]; | 
| bsw/jbe@1309 | 512 | 
| bsw/jbe@1309 | 513     // Minimal set of methods required for DOM Level 2 Range compliance | 
| bsw/jbe@1309 | 514     var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", | 
| bsw/jbe@1309 | 515         "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", | 
| bsw/jbe@1309 | 516         "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"]; | 
| bsw/jbe@1309 | 517 | 
| bsw/jbe@1309 | 518     var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"]; | 
| bsw/jbe@1309 | 519 | 
| bsw/jbe@1309 | 520     // Subset of TextRange's full set of methods that we're interested in | 
| bsw/jbe@1309 | 521     var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select", | 
| bsw/jbe@1309 | 522         "setEndPoint", "getBoundingClientRect"]; | 
| bsw/jbe@1309 | 523 | 
| bsw/jbe@1309 | 524     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 525 | 
| bsw/jbe@1309 | 526     // Trio of functions taken from Peter Michaux's article: | 
| bsw/jbe@1309 | 527     // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting | 
| bsw/jbe@1309 | 528     function isHostMethod(o, p) { | 
| bsw/jbe@1309 | 529         var t = typeof o[p]; | 
| bsw/jbe@1309 | 530         return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown"; | 
| bsw/jbe@1309 | 531     } | 
| bsw/jbe@1309 | 532 | 
| bsw/jbe@1309 | 533     function isHostObject(o, p) { | 
| bsw/jbe@1309 | 534         return !!(typeof o[p] == OBJECT && o[p]); | 
| bsw/jbe@1309 | 535     } | 
| bsw/jbe@1309 | 536 | 
| bsw/jbe@1309 | 537     function isHostProperty(o, p) { | 
| bsw/jbe@1309 | 538         return typeof o[p] != UNDEFINED; | 
| bsw/jbe@1309 | 539     } | 
| bsw/jbe@1309 | 540 | 
| bsw/jbe@1309 | 541     // Creates a convenience function to save verbose repeated calls to tests functions | 
| bsw/jbe@1309 | 542     function createMultiplePropertyTest(testFunc) { | 
| bsw/jbe@1309 | 543         return function(o, props) { | 
| bsw/jbe@1309 | 544             var i = props.length; | 
| bsw/jbe@1309 | 545             while (i--) { | 
| bsw/jbe@1309 | 546                 if (!testFunc(o, props[i])) { | 
| bsw/jbe@1309 | 547                     return false; | 
| bsw/jbe@1309 | 548                 } | 
| bsw/jbe@1309 | 549             } | 
| bsw/jbe@1309 | 550             return true; | 
| bsw/jbe@1309 | 551         }; | 
| bsw/jbe@1309 | 552     } | 
| bsw/jbe@1309 | 553 | 
| bsw/jbe@1309 | 554     // Next trio of functions are a convenience to save verbose repeated calls to previous two functions | 
| bsw/jbe@1309 | 555     var areHostMethods = createMultiplePropertyTest(isHostMethod); | 
| bsw/jbe@1309 | 556     var areHostObjects = createMultiplePropertyTest(isHostObject); | 
| bsw/jbe@1309 | 557     var areHostProperties = createMultiplePropertyTest(isHostProperty); | 
| bsw/jbe@1309 | 558 | 
| bsw/jbe@1309 | 559     function isTextRange(range) { | 
| bsw/jbe@1309 | 560         return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties); | 
| bsw/jbe@1309 | 561     } | 
| bsw/jbe@1309 | 562 | 
| bsw/jbe@1309 | 563     function getBody(doc) { | 
| bsw/jbe@1309 | 564         return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; | 
| bsw/jbe@1309 | 565     } | 
| bsw/jbe@1309 | 566 | 
| bsw/jbe@1309 | 567     var forEach = [].forEach ? | 
| bsw/jbe@1309 | 568         function(arr, func) { | 
| bsw/jbe@1309 | 569             arr.forEach(func); | 
| bsw/jbe@1309 | 570         } : | 
| bsw/jbe@1309 | 571         function(arr, func) { | 
| bsw/jbe@1309 | 572             for (var i = 0, len = arr.length; i < len; ++i) { | 
| bsw/jbe@1309 | 573                 func(arr[i], i); | 
| bsw/jbe@1309 | 574             } | 
| bsw/jbe@1309 | 575         }; | 
| bsw/jbe@1309 | 576 | 
| bsw/jbe@1309 | 577     var modules = {}; | 
| bsw/jbe@1309 | 578 | 
| bsw/jbe@1309 | 579     var isBrowser = (typeof window != UNDEFINED && typeof document != UNDEFINED); | 
| bsw/jbe@1309 | 580 | 
| bsw/jbe@1309 | 581     var util = { | 
| bsw/jbe@1309 | 582         isHostMethod: isHostMethod, | 
| bsw/jbe@1309 | 583         isHostObject: isHostObject, | 
| bsw/jbe@1309 | 584         isHostProperty: isHostProperty, | 
| bsw/jbe@1309 | 585         areHostMethods: areHostMethods, | 
| bsw/jbe@1309 | 586         areHostObjects: areHostObjects, | 
| bsw/jbe@1309 | 587         areHostProperties: areHostProperties, | 
| bsw/jbe@1309 | 588         isTextRange: isTextRange, | 
| bsw/jbe@1309 | 589         getBody: getBody, | 
| bsw/jbe@1309 | 590         forEach: forEach | 
| bsw/jbe@1309 | 591     }; | 
| bsw/jbe@1309 | 592 | 
| bsw/jbe@1309 | 593     var api = { | 
| bsw/jbe@1309 | 594         version: "1.3.1-dev", | 
| bsw/jbe@1309 | 595         initialized: false, | 
| bsw/jbe@1309 | 596         isBrowser: isBrowser, | 
| bsw/jbe@1309 | 597         supported: true, | 
| bsw/jbe@1309 | 598         util: util, | 
| bsw/jbe@1309 | 599         features: {}, | 
| bsw/jbe@1309 | 600         modules: modules, | 
| bsw/jbe@1309 | 601         config: { | 
| bsw/jbe@1309 | 602             alertOnFail: false, | 
| bsw/jbe@1309 | 603             alertOnWarn: false, | 
| bsw/jbe@1309 | 604             preferTextRange: false, | 
| bsw/jbe@1309 | 605             autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize | 
| bsw/jbe@1309 | 606         } | 
| bsw/jbe@1309 | 607     }; | 
| bsw/jbe@1309 | 608 | 
| bsw/jbe@1309 | 609     function consoleLog(msg) { | 
| bsw/jbe@1309 | 610         if (typeof console != UNDEFINED && isHostMethod(console, "log")) { | 
| bsw/jbe@1309 | 611             console.log(msg); | 
| bsw/jbe@1309 | 612         } | 
| bsw/jbe@1309 | 613     } | 
| bsw/jbe@1309 | 614 | 
| bsw/jbe@1309 | 615     function alertOrLog(msg, shouldAlert) { | 
| bsw/jbe@1309 | 616         if (isBrowser && shouldAlert) { | 
| bsw/jbe@1309 | 617             alert(msg); | 
| bsw/jbe@1309 | 618         } else  { | 
| bsw/jbe@1309 | 619             consoleLog(msg); | 
| bsw/jbe@1309 | 620         } | 
| bsw/jbe@1309 | 621     } | 
| bsw/jbe@1309 | 622 | 
| bsw/jbe@1309 | 623     function fail(reason) { | 
| bsw/jbe@1309 | 624         api.initialized = true; | 
| bsw/jbe@1309 | 625         api.supported = false; | 
| bsw/jbe@1309 | 626         alertOrLog("Rangy is not supported in this environment. Reason: " + reason, api.config.alertOnFail); | 
| bsw/jbe@1309 | 627     } | 
| bsw/jbe@1309 | 628 | 
| bsw/jbe@1309 | 629     api.fail = fail; | 
| bsw/jbe@1309 | 630 | 
| bsw/jbe@1309 | 631     function warn(msg) { | 
| bsw/jbe@1309 | 632         alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn); | 
| bsw/jbe@1309 | 633     } | 
| bsw/jbe@1309 | 634 | 
| bsw/jbe@1309 | 635     api.warn = warn; | 
| bsw/jbe@1309 | 636 | 
| bsw/jbe@1309 | 637     // Add utility extend() method | 
| bsw/jbe@1309 | 638     var extend; | 
| bsw/jbe@1309 | 639     if ({}.hasOwnProperty) { | 
| bsw/jbe@1309 | 640         util.extend = extend = function(obj, props, deep) { | 
| bsw/jbe@1309 | 641             var o, p; | 
| bsw/jbe@1309 | 642             for (var i in props) { | 
| bsw/jbe@1309 | 643                 if (props.hasOwnProperty(i)) { | 
| bsw/jbe@1309 | 644                     o = obj[i]; | 
| bsw/jbe@1309 | 645                     p = props[i]; | 
| bsw/jbe@1309 | 646                     if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") { | 
| bsw/jbe@1309 | 647                         extend(o, p, true); | 
| bsw/jbe@1309 | 648                     } | 
| bsw/jbe@1309 | 649                     obj[i] = p; | 
| bsw/jbe@1309 | 650                 } | 
| bsw/jbe@1309 | 651             } | 
| bsw/jbe@1309 | 652             // Special case for toString, which does not show up in for...in loops in IE <= 8 | 
| bsw/jbe@1309 | 653             if (props.hasOwnProperty("toString")) { | 
| bsw/jbe@1309 | 654                 obj.toString = props.toString; | 
| bsw/jbe@1309 | 655             } | 
| bsw/jbe@1309 | 656             return obj; | 
| bsw/jbe@1309 | 657         }; | 
| bsw/jbe@1309 | 658 | 
| bsw/jbe@1309 | 659         util.createOptions = function(optionsParam, defaults) { | 
| bsw/jbe@1309 | 660             var options = {}; | 
| bsw/jbe@1309 | 661             extend(options, defaults); | 
| bsw/jbe@1309 | 662             if (optionsParam) { | 
| bsw/jbe@1309 | 663                 extend(options, optionsParam); | 
| bsw/jbe@1309 | 664             } | 
| bsw/jbe@1309 | 665             return options; | 
| bsw/jbe@1309 | 666         }; | 
| bsw/jbe@1309 | 667     } else { | 
| bsw/jbe@1309 | 668         fail("hasOwnProperty not supported"); | 
| bsw/jbe@1309 | 669     } | 
| bsw/jbe@1309 | 670 | 
| bsw/jbe@1309 | 671     // Test whether we're in a browser and bail out if not | 
| bsw/jbe@1309 | 672     if (!isBrowser) { | 
| bsw/jbe@1309 | 673         fail("Rangy can only run in a browser"); | 
| bsw/jbe@1309 | 674     } | 
| bsw/jbe@1309 | 675 | 
| bsw/jbe@1309 | 676     // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not | 
| bsw/jbe@1309 | 677     (function() { | 
| bsw/jbe@1309 | 678         var toArray; | 
| bsw/jbe@1309 | 679 | 
| bsw/jbe@1309 | 680         if (isBrowser) { | 
| bsw/jbe@1309 | 681             var el = document.createElement("div"); | 
| bsw/jbe@1309 | 682             el.appendChild(document.createElement("span")); | 
| bsw/jbe@1309 | 683             var slice = [].slice; | 
| bsw/jbe@1309 | 684             try { | 
| bsw/jbe@1309 | 685                 if (slice.call(el.childNodes, 0)[0].nodeType == 1) { | 
| bsw/jbe@1309 | 686                     toArray = function(arrayLike) { | 
| bsw/jbe@1309 | 687                         return slice.call(arrayLike, 0); | 
| bsw/jbe@1309 | 688                     }; | 
| bsw/jbe@1309 | 689                 } | 
| bsw/jbe@1309 | 690             } catch (e) {} | 
| bsw/jbe@1309 | 691         } | 
| bsw/jbe@1309 | 692 | 
| bsw/jbe@1309 | 693         if (!toArray) { | 
| bsw/jbe@1309 | 694             toArray = function(arrayLike) { | 
| bsw/jbe@1309 | 695                 var arr = []; | 
| bsw/jbe@1309 | 696                 for (var i = 0, len = arrayLike.length; i < len; ++i) { | 
| bsw/jbe@1309 | 697                     arr[i] = arrayLike[i]; | 
| bsw/jbe@1309 | 698                 } | 
| bsw/jbe@1309 | 699                 return arr; | 
| bsw/jbe@1309 | 700             }; | 
| bsw/jbe@1309 | 701         } | 
| bsw/jbe@1309 | 702 | 
| bsw/jbe@1309 | 703         util.toArray = toArray; | 
| bsw/jbe@1309 | 704     })(); | 
| bsw/jbe@1309 | 705 | 
| bsw/jbe@1309 | 706     // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or | 
| bsw/jbe@1309 | 707     // normalization of event properties | 
| bsw/jbe@1309 | 708     var addListener; | 
| bsw/jbe@1309 | 709     if (isBrowser) { | 
| bsw/jbe@1309 | 710         if (isHostMethod(document, "addEventListener")) { | 
| bsw/jbe@1309 | 711             addListener = function(obj, eventType, listener) { | 
| bsw/jbe@1309 | 712                 obj.addEventListener(eventType, listener, false); | 
| bsw/jbe@1309 | 713             }; | 
| bsw/jbe@1309 | 714         } else if (isHostMethod(document, "attachEvent")) { | 
| bsw/jbe@1309 | 715             addListener = function(obj, eventType, listener) { | 
| bsw/jbe@1309 | 716                 obj.attachEvent("on" + eventType, listener); | 
| bsw/jbe@1309 | 717             }; | 
| bsw/jbe@1309 | 718         } else { | 
| bsw/jbe@1309 | 719             fail("Document does not have required addEventListener or attachEvent method"); | 
| bsw/jbe@1309 | 720         } | 
| bsw/jbe@1309 | 721 | 
| bsw/jbe@1309 | 722         util.addListener = addListener; | 
| bsw/jbe@1309 | 723     } | 
| bsw/jbe@1309 | 724 | 
| bsw/jbe@1309 | 725     var initListeners = []; | 
| bsw/jbe@1309 | 726 | 
| bsw/jbe@1309 | 727     function getErrorDesc(ex) { | 
| bsw/jbe@1309 | 728         return ex.message || ex.description || String(ex); | 
| bsw/jbe@1309 | 729     } | 
| bsw/jbe@1309 | 730 | 
| bsw/jbe@1309 | 731     // Initialization | 
| bsw/jbe@1309 | 732     function init() { | 
| bsw/jbe@1309 | 733         if (!isBrowser || api.initialized) { | 
| bsw/jbe@1309 | 734             return; | 
| bsw/jbe@1309 | 735         } | 
| bsw/jbe@1309 | 736         var testRange; | 
| bsw/jbe@1309 | 737         var implementsDomRange = false, implementsTextRange = false; | 
| bsw/jbe@1309 | 738 | 
| bsw/jbe@1309 | 739         // First, perform basic feature tests | 
| bsw/jbe@1309 | 740 | 
| bsw/jbe@1309 | 741         if (isHostMethod(document, "createRange")) { | 
| bsw/jbe@1309 | 742             testRange = document.createRange(); | 
| bsw/jbe@1309 | 743             if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) { | 
| bsw/jbe@1309 | 744                 implementsDomRange = true; | 
| bsw/jbe@1309 | 745             } | 
| bsw/jbe@1309 | 746         } | 
| bsw/jbe@1309 | 747 | 
| bsw/jbe@1309 | 748         var body = getBody(document); | 
| bsw/jbe@1309 | 749         if (!body || body.nodeName.toLowerCase() != "body") { | 
| bsw/jbe@1309 | 750             fail("No body element found"); | 
| bsw/jbe@1309 | 751             return; | 
| bsw/jbe@1309 | 752         } | 
| bsw/jbe@1309 | 753 | 
| bsw/jbe@1309 | 754         if (body && isHostMethod(body, "createTextRange")) { | 
| bsw/jbe@1309 | 755             testRange = body.createTextRange(); | 
| bsw/jbe@1309 | 756             if (isTextRange(testRange)) { | 
| bsw/jbe@1309 | 757                 implementsTextRange = true; | 
| bsw/jbe@1309 | 758             } | 
| bsw/jbe@1309 | 759         } | 
| bsw/jbe@1309 | 760 | 
| bsw/jbe@1309 | 761         if (!implementsDomRange && !implementsTextRange) { | 
| bsw/jbe@1309 | 762             fail("Neither Range nor TextRange are available"); | 
| bsw/jbe@1309 | 763             return; | 
| bsw/jbe@1309 | 764         } | 
| bsw/jbe@1309 | 765 | 
| bsw/jbe@1309 | 766         api.initialized = true; | 
| bsw/jbe@1309 | 767         api.features = { | 
| bsw/jbe@1309 | 768             implementsDomRange: implementsDomRange, | 
| bsw/jbe@1309 | 769             implementsTextRange: implementsTextRange | 
| bsw/jbe@1309 | 770         }; | 
| bsw/jbe@1309 | 771 | 
| bsw/jbe@1309 | 772         // Initialize modules | 
| bsw/jbe@1309 | 773         var module, errorMessage; | 
| bsw/jbe@1309 | 774         for (var moduleName in modules) { | 
| bsw/jbe@1309 | 775             if ( (module = modules[moduleName]) instanceof Module ) { | 
| bsw/jbe@1309 | 776                 module.init(module, api); | 
| bsw/jbe@1309 | 777             } | 
| bsw/jbe@1309 | 778         } | 
| bsw/jbe@1309 | 779 | 
| bsw/jbe@1309 | 780         // Call init listeners | 
| bsw/jbe@1309 | 781         for (var i = 0, len = initListeners.length; i < len; ++i) { | 
| bsw/jbe@1309 | 782             try { | 
| bsw/jbe@1309 | 783                 initListeners[i](api); | 
| bsw/jbe@1309 | 784             } catch (ex) { | 
| bsw/jbe@1309 | 785                 errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex); | 
| bsw/jbe@1309 | 786                 consoleLog(errorMessage); | 
| bsw/jbe@1309 | 787             } | 
| bsw/jbe@1309 | 788         } | 
| bsw/jbe@1309 | 789     } | 
| bsw/jbe@1309 | 790 | 
| bsw/jbe@1309 | 791     function deprecationNotice(deprecated, replacement, module) { | 
| bsw/jbe@1309 | 792         if (module) { | 
| bsw/jbe@1309 | 793             deprecated += " in module " + module.name; | 
| bsw/jbe@1309 | 794         } | 
| bsw/jbe@1309 | 795         api.warn("DEPRECATED: " + deprecated + " is deprecated. Please use " + | 
| bsw/jbe@1309 | 796         replacement + " instead."); | 
| bsw/jbe@1309 | 797     } | 
| bsw/jbe@1309 | 798 | 
| bsw/jbe@1309 | 799     function createAliasForDeprecatedMethod(owner, deprecated, replacement, module) { | 
| bsw/jbe@1309 | 800         owner[deprecated] = function() { | 
| bsw/jbe@1309 | 801             deprecationNotice(deprecated, replacement, module); | 
| bsw/jbe@1309 | 802             return owner[replacement].apply(owner, util.toArray(arguments)); | 
| bsw/jbe@1309 | 803         }; | 
| bsw/jbe@1309 | 804     } | 
| bsw/jbe@1309 | 805 | 
| bsw/jbe@1309 | 806     util.deprecationNotice = deprecationNotice; | 
| bsw/jbe@1309 | 807     util.createAliasForDeprecatedMethod = createAliasForDeprecatedMethod; | 
| bsw/jbe@1309 | 808 | 
| bsw/jbe@1309 | 809     // Allow external scripts to initialize this library in case it's loaded after the document has loaded | 
| bsw/jbe@1309 | 810     api.init = init; | 
| bsw/jbe@1309 | 811 | 
| bsw/jbe@1309 | 812     // Execute listener immediately if already initialized | 
| bsw/jbe@1309 | 813     api.addInitListener = function(listener) { | 
| bsw/jbe@1309 | 814         if (api.initialized) { | 
| bsw/jbe@1309 | 815             listener(api); | 
| bsw/jbe@1309 | 816         } else { | 
| bsw/jbe@1309 | 817             initListeners.push(listener); | 
| bsw/jbe@1309 | 818         } | 
| bsw/jbe@1309 | 819     }; | 
| bsw/jbe@1309 | 820 | 
| bsw/jbe@1309 | 821     var shimListeners = []; | 
| bsw/jbe@1309 | 822 | 
| bsw/jbe@1309 | 823     api.addShimListener = function(listener) { | 
| bsw/jbe@1309 | 824         shimListeners.push(listener); | 
| bsw/jbe@1309 | 825     }; | 
| bsw/jbe@1309 | 826 | 
| bsw/jbe@1309 | 827     function shim(win) { | 
| bsw/jbe@1309 | 828         win = win || window; | 
| bsw/jbe@1309 | 829         init(); | 
| bsw/jbe@1309 | 830 | 
| bsw/jbe@1309 | 831         // Notify listeners | 
| bsw/jbe@1309 | 832         for (var i = 0, len = shimListeners.length; i < len; ++i) { | 
| bsw/jbe@1309 | 833             shimListeners[i](win); | 
| bsw/jbe@1309 | 834         } | 
| bsw/jbe@1309 | 835     } | 
| bsw/jbe@1309 | 836 | 
| bsw/jbe@1309 | 837     if (isBrowser) { | 
| bsw/jbe@1309 | 838         api.shim = api.createMissingNativeApi = shim; | 
| bsw/jbe@1309 | 839         createAliasForDeprecatedMethod(api, "createMissingNativeApi", "shim"); | 
| bsw/jbe@1309 | 840     } | 
| bsw/jbe@1309 | 841 | 
| bsw/jbe@1309 | 842     function Module(name, dependencies, initializer) { | 
| bsw/jbe@1309 | 843         this.name = name; | 
| bsw/jbe@1309 | 844         this.dependencies = dependencies; | 
| bsw/jbe@1309 | 845         this.initialized = false; | 
| bsw/jbe@1309 | 846         this.supported = false; | 
| bsw/jbe@1309 | 847         this.initializer = initializer; | 
| bsw/jbe@1309 | 848     } | 
| bsw/jbe@1309 | 849 | 
| bsw/jbe@1309 | 850     Module.prototype = { | 
| bsw/jbe@1309 | 851         init: function() { | 
| bsw/jbe@1309 | 852             var requiredModuleNames = this.dependencies || []; | 
| bsw/jbe@1309 | 853             for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) { | 
| bsw/jbe@1309 | 854                 moduleName = requiredModuleNames[i]; | 
| bsw/jbe@1309 | 855 | 
| bsw/jbe@1309 | 856                 requiredModule = modules[moduleName]; | 
| bsw/jbe@1309 | 857                 if (!requiredModule || !(requiredModule instanceof Module)) { | 
| bsw/jbe@1309 | 858                     throw new Error("required module '" + moduleName + "' not found"); | 
| bsw/jbe@1309 | 859                 } | 
| bsw/jbe@1309 | 860 | 
| bsw/jbe@1309 | 861                 requiredModule.init(); | 
| bsw/jbe@1309 | 862 | 
| bsw/jbe@1309 | 863                 if (!requiredModule.supported) { | 
| bsw/jbe@1309 | 864                     throw new Error("required module '" + moduleName + "' not supported"); | 
| bsw/jbe@1309 | 865                 } | 
| bsw/jbe@1309 | 866             } | 
| bsw/jbe@1309 | 867 | 
| bsw/jbe@1309 | 868             // Now run initializer | 
| bsw/jbe@1309 | 869             this.initializer(this); | 
| bsw/jbe@1309 | 870         }, | 
| bsw/jbe@1309 | 871 | 
| bsw/jbe@1309 | 872         fail: function(reason) { | 
| bsw/jbe@1309 | 873             this.initialized = true; | 
| bsw/jbe@1309 | 874             this.supported = false; | 
| bsw/jbe@1309 | 875             throw new Error(reason); | 
| bsw/jbe@1309 | 876         }, | 
| bsw/jbe@1309 | 877 | 
| bsw/jbe@1309 | 878         warn: function(msg) { | 
| bsw/jbe@1309 | 879             api.warn("Module " + this.name + ": " + msg); | 
| bsw/jbe@1309 | 880         }, | 
| bsw/jbe@1309 | 881 | 
| bsw/jbe@1309 | 882         deprecationNotice: function(deprecated, replacement) { | 
| bsw/jbe@1309 | 883             api.warn("DEPRECATED: " + deprecated + " in module " + this.name + " is deprecated. Please use " + | 
| bsw/jbe@1309 | 884                 replacement + " instead"); | 
| bsw/jbe@1309 | 885         }, | 
| bsw/jbe@1309 | 886 | 
| bsw/jbe@1309 | 887         createError: function(msg) { | 
| bsw/jbe@1309 | 888             return new Error("Error in Rangy " + this.name + " module: " + msg); | 
| bsw/jbe@1309 | 889         } | 
| bsw/jbe@1309 | 890     }; | 
| bsw/jbe@1309 | 891 | 
| bsw/jbe@1309 | 892     function createModule(name, dependencies, initFunc) { | 
| bsw/jbe@1309 | 893         var newModule = new Module(name, dependencies, function(module) { | 
| bsw/jbe@1309 | 894             if (!module.initialized) { | 
| bsw/jbe@1309 | 895                 module.initialized = true; | 
| bsw/jbe@1309 | 896                 try { | 
| bsw/jbe@1309 | 897                     initFunc(api, module); | 
| bsw/jbe@1309 | 898                     module.supported = true; | 
| bsw/jbe@1309 | 899                 } catch (ex) { | 
| bsw/jbe@1309 | 900                     var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex); | 
| bsw/jbe@1309 | 901                     consoleLog(errorMessage); | 
| bsw/jbe@1309 | 902                     if (ex.stack) { | 
| bsw/jbe@1309 | 903                         consoleLog(ex.stack); | 
| bsw/jbe@1309 | 904                     } | 
| bsw/jbe@1309 | 905                 } | 
| bsw/jbe@1309 | 906             } | 
| bsw/jbe@1309 | 907         }); | 
| bsw/jbe@1309 | 908         modules[name] = newModule; | 
| bsw/jbe@1309 | 909         return newModule; | 
| bsw/jbe@1309 | 910     } | 
| bsw/jbe@1309 | 911 | 
| bsw/jbe@1309 | 912     api.createModule = function(name) { | 
| bsw/jbe@1309 | 913         // Allow 2 or 3 arguments (second argument is an optional array of dependencies) | 
| bsw/jbe@1309 | 914         var initFunc, dependencies; | 
| bsw/jbe@1309 | 915         if (arguments.length == 2) { | 
| bsw/jbe@1309 | 916             initFunc = arguments[1]; | 
| bsw/jbe@1309 | 917             dependencies = []; | 
| bsw/jbe@1309 | 918         } else { | 
| bsw/jbe@1309 | 919             initFunc = arguments[2]; | 
| bsw/jbe@1309 | 920             dependencies = arguments[1]; | 
| bsw/jbe@1309 | 921         } | 
| bsw/jbe@1309 | 922 | 
| bsw/jbe@1309 | 923         var module = createModule(name, dependencies, initFunc); | 
| bsw/jbe@1309 | 924 | 
| bsw/jbe@1309 | 925         // Initialize the module immediately if the core is already initialized | 
| bsw/jbe@1309 | 926         if (api.initialized && api.supported) { | 
| bsw/jbe@1309 | 927             module.init(); | 
| bsw/jbe@1309 | 928         } | 
| bsw/jbe@1309 | 929     }; | 
| bsw/jbe@1309 | 930 | 
| bsw/jbe@1309 | 931     api.createCoreModule = function(name, dependencies, initFunc) { | 
| bsw/jbe@1309 | 932         createModule(name, dependencies, initFunc); | 
| bsw/jbe@1309 | 933     }; | 
| bsw/jbe@1309 | 934 | 
| bsw/jbe@1309 | 935     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 936 | 
| bsw/jbe@1309 | 937     // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately | 
| bsw/jbe@1309 | 938 | 
| bsw/jbe@1309 | 939     function RangePrototype() {} | 
| bsw/jbe@1309 | 940     api.RangePrototype = RangePrototype; | 
| bsw/jbe@1309 | 941     api.rangePrototype = new RangePrototype(); | 
| bsw/jbe@1309 | 942 | 
| bsw/jbe@1309 | 943     function SelectionPrototype() {} | 
| bsw/jbe@1309 | 944     api.selectionPrototype = new SelectionPrototype(); | 
| bsw/jbe@1309 | 945 | 
| bsw/jbe@1309 | 946     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 947 | 
| bsw/jbe@1309 | 948     // DOM utility methods used by Rangy | 
| bsw/jbe@1309 | 949     api.createCoreModule("DomUtil", [], function(api, module) { | 
| bsw/jbe@1309 | 950         var UNDEF = "undefined"; | 
| bsw/jbe@1309 | 951         var util = api.util; | 
| bsw/jbe@1309 | 952         var getBody = util.getBody; | 
| bsw/jbe@1309 | 953 | 
| bsw/jbe@1309 | 954         // Perform feature tests | 
| bsw/jbe@1309 | 955         if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) { | 
| bsw/jbe@1309 | 956             module.fail("document missing a Node creation method"); | 
| bsw/jbe@1309 | 957         } | 
| bsw/jbe@1309 | 958 | 
| bsw/jbe@1309 | 959         if (!util.isHostMethod(document, "getElementsByTagName")) { | 
| bsw/jbe@1309 | 960             module.fail("document missing getElementsByTagName method"); | 
| bsw/jbe@1309 | 961         } | 
| bsw/jbe@1309 | 962 | 
| bsw/jbe@1309 | 963         var el = document.createElement("div"); | 
| bsw/jbe@1309 | 964         if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || | 
| bsw/jbe@1309 | 965                 !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) { | 
| bsw/jbe@1309 | 966             module.fail("Incomplete Element implementation"); | 
| bsw/jbe@1309 | 967         } | 
| bsw/jbe@1309 | 968 | 
| bsw/jbe@1309 | 969         // innerHTML is required for Range's createContextualFragment method | 
| bsw/jbe@1309 | 970         if (!util.isHostProperty(el, "innerHTML")) { | 
| bsw/jbe@1309 | 971             module.fail("Element is missing innerHTML property"); | 
| bsw/jbe@1309 | 972         } | 
| bsw/jbe@1309 | 973 | 
| bsw/jbe@1309 | 974         var textNode = document.createTextNode("test"); | 
| bsw/jbe@1309 | 975         if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || | 
| bsw/jbe@1309 | 976                 !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || | 
| bsw/jbe@1309 | 977                 !util.areHostProperties(textNode, ["data"]))) { | 
| bsw/jbe@1309 | 978             module.fail("Incomplete Text Node implementation"); | 
| bsw/jbe@1309 | 979         } | 
| bsw/jbe@1309 | 980 | 
| bsw/jbe@1309 | 981         /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 982 | 
| bsw/jbe@1309 | 983         // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been | 
| bsw/jbe@1309 | 984         // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that | 
| bsw/jbe@1309 | 985         // contains just the document as a single element and the value searched for is the document. | 
| bsw/jbe@1309 | 986         var arrayContains = /*Array.prototype.indexOf ? | 
| bsw/jbe@1309 | 987             function(arr, val) { | 
| bsw/jbe@1309 | 988                 return arr.indexOf(val) > -1; | 
| bsw/jbe@1309 | 989             }:*/ | 
| bsw/jbe@1309 | 990 | 
| bsw/jbe@1309 | 991             function(arr, val) { | 
| bsw/jbe@1309 | 992                 var i = arr.length; | 
| bsw/jbe@1309 | 993                 while (i--) { | 
| bsw/jbe@1309 | 994                     if (arr[i] === val) { | 
| bsw/jbe@1309 | 995                         return true; | 
| bsw/jbe@1309 | 996                     } | 
| bsw/jbe@1309 | 997                 } | 
| bsw/jbe@1309 | 998                 return false; | 
| bsw/jbe@1309 | 999             }; | 
| bsw/jbe@1309 | 1000 | 
| bsw/jbe@1309 | 1001         // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI | 
| bsw/jbe@1309 | 1002         function isHtmlNamespace(node) { | 
| bsw/jbe@1309 | 1003             var ns; | 
| bsw/jbe@1309 | 1004             return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml"); | 
| bsw/jbe@1309 | 1005         } | 
| bsw/jbe@1309 | 1006 | 
| bsw/jbe@1309 | 1007         function parentElement(node) { | 
| bsw/jbe@1309 | 1008             var parent = node.parentNode; | 
| bsw/jbe@1309 | 1009             return (parent.nodeType == 1) ? parent : null; | 
| bsw/jbe@1309 | 1010         } | 
| bsw/jbe@1309 | 1011 | 
| bsw/jbe@1309 | 1012         function getNodeIndex(node) { | 
| bsw/jbe@1309 | 1013             var i = 0; | 
| bsw/jbe@1309 | 1014             while( (node = node.previousSibling) ) { | 
| bsw/jbe@1309 | 1015                 ++i; | 
| bsw/jbe@1309 | 1016             } | 
| bsw/jbe@1309 | 1017             return i; | 
| bsw/jbe@1309 | 1018         } | 
| bsw/jbe@1309 | 1019 | 
| bsw/jbe@1309 | 1020         function getNodeLength(node) { | 
| bsw/jbe@1309 | 1021             switch (node.nodeType) { | 
| bsw/jbe@1309 | 1022                 case 7: | 
| bsw/jbe@1309 | 1023                 case 10: | 
| bsw/jbe@1309 | 1024                     return 0; | 
| bsw/jbe@1309 | 1025                 case 3: | 
| bsw/jbe@1309 | 1026                 case 8: | 
| bsw/jbe@1309 | 1027                     return node.length; | 
| bsw/jbe@1309 | 1028                 default: | 
| bsw/jbe@1309 | 1029                     return node.childNodes.length; | 
| bsw/jbe@1309 | 1030             } | 
| bsw/jbe@1309 | 1031         } | 
| bsw/jbe@1309 | 1032 | 
| bsw/jbe@1309 | 1033         function getCommonAncestor(node1, node2) { | 
| bsw/jbe@1309 | 1034             var ancestors = [], n; | 
| bsw/jbe@1309 | 1035             for (n = node1; n; n = n.parentNode) { | 
| bsw/jbe@1309 | 1036                 ancestors.push(n); | 
| bsw/jbe@1309 | 1037             } | 
| bsw/jbe@1309 | 1038 | 
| bsw/jbe@1309 | 1039             for (n = node2; n; n = n.parentNode) { | 
| bsw/jbe@1309 | 1040                 if (arrayContains(ancestors, n)) { | 
| bsw/jbe@1309 | 1041                     return n; | 
| bsw/jbe@1309 | 1042                 } | 
| bsw/jbe@1309 | 1043             } | 
| bsw/jbe@1309 | 1044 | 
| bsw/jbe@1309 | 1045             return null; | 
| bsw/jbe@1309 | 1046         } | 
| bsw/jbe@1309 | 1047 | 
| bsw/jbe@1309 | 1048         function isAncestorOf(ancestor, descendant, selfIsAncestor) { | 
| bsw/jbe@1309 | 1049             var n = selfIsAncestor ? descendant : descendant.parentNode; | 
| bsw/jbe@1309 | 1050             while (n) { | 
| bsw/jbe@1309 | 1051                 if (n === ancestor) { | 
| bsw/jbe@1309 | 1052                     return true; | 
| bsw/jbe@1309 | 1053                 } else { | 
| bsw/jbe@1309 | 1054                     n = n.parentNode; | 
| bsw/jbe@1309 | 1055                 } | 
| bsw/jbe@1309 | 1056             } | 
| bsw/jbe@1309 | 1057             return false; | 
| bsw/jbe@1309 | 1058         } | 
| bsw/jbe@1309 | 1059 | 
| bsw/jbe@1309 | 1060         function isOrIsAncestorOf(ancestor, descendant) { | 
| bsw/jbe@1309 | 1061             return isAncestorOf(ancestor, descendant, true); | 
| bsw/jbe@1309 | 1062         } | 
| bsw/jbe@1309 | 1063 | 
| bsw/jbe@1309 | 1064         function getClosestAncestorIn(node, ancestor, selfIsAncestor) { | 
| bsw/jbe@1309 | 1065             var p, n = selfIsAncestor ? node : node.parentNode; | 
| bsw/jbe@1309 | 1066             while (n) { | 
| bsw/jbe@1309 | 1067                 p = n.parentNode; | 
| bsw/jbe@1309 | 1068                 if (p === ancestor) { | 
| bsw/jbe@1309 | 1069                     return n; | 
| bsw/jbe@1309 | 1070                 } | 
| bsw/jbe@1309 | 1071                 n = p; | 
| bsw/jbe@1309 | 1072             } | 
| bsw/jbe@1309 | 1073             return null; | 
| bsw/jbe@1309 | 1074         } | 
| bsw/jbe@1309 | 1075 | 
| bsw/jbe@1309 | 1076         function isCharacterDataNode(node) { | 
| bsw/jbe@1309 | 1077             var t = node.nodeType; | 
| bsw/jbe@1309 | 1078             return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment | 
| bsw/jbe@1309 | 1079         } | 
| bsw/jbe@1309 | 1080 | 
| bsw/jbe@1309 | 1081         function isTextOrCommentNode(node) { | 
| bsw/jbe@1309 | 1082             if (!node) { | 
| bsw/jbe@1309 | 1083                 return false; | 
| bsw/jbe@1309 | 1084             } | 
| bsw/jbe@1309 | 1085             var t = node.nodeType; | 
| bsw/jbe@1309 | 1086             return t == 3 || t == 8 ; // Text or Comment | 
| bsw/jbe@1309 | 1087         } | 
| bsw/jbe@1309 | 1088 | 
| bsw/jbe@1309 | 1089         function insertAfter(node, precedingNode) { | 
| bsw/jbe@1309 | 1090             var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode; | 
| bsw/jbe@1309 | 1091             if (nextNode) { | 
| bsw/jbe@1309 | 1092                 parent.insertBefore(node, nextNode); | 
| bsw/jbe@1309 | 1093             } else { | 
| bsw/jbe@1309 | 1094                 parent.appendChild(node); | 
| bsw/jbe@1309 | 1095             } | 
| bsw/jbe@1309 | 1096             return node; | 
| bsw/jbe@1309 | 1097         } | 
| bsw/jbe@1309 | 1098 | 
| bsw/jbe@1309 | 1099         // Note that we cannot use splitText() because it is bugridden in IE 9. | 
| bsw/jbe@1309 | 1100         function splitDataNode(node, index, positionsToPreserve) { | 
| bsw/jbe@1309 | 1101             var newNode = node.cloneNode(false); | 
| bsw/jbe@1309 | 1102             newNode.deleteData(0, index); | 
| bsw/jbe@1309 | 1103             node.deleteData(index, node.length - index); | 
| bsw/jbe@1309 | 1104             insertAfter(newNode, node); | 
| bsw/jbe@1309 | 1105 | 
| bsw/jbe@1309 | 1106             // Preserve positions | 
| bsw/jbe@1309 | 1107             if (positionsToPreserve) { | 
| bsw/jbe@1309 | 1108                 for (var i = 0, position; position = positionsToPreserve[i++]; ) { | 
| bsw/jbe@1309 | 1109                     // Handle case where position was inside the portion of node after the split point | 
| bsw/jbe@1309 | 1110                     if (position.node == node && position.offset > index) { | 
| bsw/jbe@1309 | 1111                         position.node = newNode; | 
| bsw/jbe@1309 | 1112                         position.offset -= index; | 
| bsw/jbe@1309 | 1113                     } | 
| bsw/jbe@1309 | 1114                     // Handle the case where the position is a node offset within node's parent | 
| bsw/jbe@1309 | 1115                     else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) { | 
| bsw/jbe@1309 | 1116                         ++position.offset; | 
| bsw/jbe@1309 | 1117                     } | 
| bsw/jbe@1309 | 1118                 } | 
| bsw/jbe@1309 | 1119             } | 
| bsw/jbe@1309 | 1120             return newNode; | 
| bsw/jbe@1309 | 1121         } | 
| bsw/jbe@1309 | 1122 | 
| bsw/jbe@1309 | 1123         function getDocument(node) { | 
| bsw/jbe@1309 | 1124             if (node.nodeType == 9) { | 
| bsw/jbe@1309 | 1125                 return node; | 
| bsw/jbe@1309 | 1126             } else if (typeof node.ownerDocument != UNDEF) { | 
| bsw/jbe@1309 | 1127                 return node.ownerDocument; | 
| bsw/jbe@1309 | 1128             } else if (typeof node.document != UNDEF) { | 
| bsw/jbe@1309 | 1129                 return node.document; | 
| bsw/jbe@1309 | 1130             } else if (node.parentNode) { | 
| bsw/jbe@1309 | 1131                 return getDocument(node.parentNode); | 
| bsw/jbe@1309 | 1132             } else { | 
| bsw/jbe@1309 | 1133                 throw module.createError("getDocument: no document found for node"); | 
| bsw/jbe@1309 | 1134             } | 
| bsw/jbe@1309 | 1135         } | 
| bsw/jbe@1309 | 1136 | 
| bsw/jbe@1309 | 1137         function getWindow(node) { | 
| bsw/jbe@1309 | 1138             var doc = getDocument(node); | 
| bsw/jbe@1309 | 1139             if (typeof doc.defaultView != UNDEF) { | 
| bsw/jbe@1309 | 1140                 return doc.defaultView; | 
| bsw/jbe@1309 | 1141             } else if (typeof doc.parentWindow != UNDEF) { | 
| bsw/jbe@1309 | 1142                 return doc.parentWindow; | 
| bsw/jbe@1309 | 1143             } else { | 
| bsw/jbe@1309 | 1144                 throw module.createError("Cannot get a window object for node"); | 
| bsw/jbe@1309 | 1145             } | 
| bsw/jbe@1309 | 1146         } | 
| bsw/jbe@1309 | 1147 | 
| bsw/jbe@1309 | 1148         function getIframeDocument(iframeEl) { | 
| bsw/jbe@1309 | 1149             if (typeof iframeEl.contentDocument != UNDEF) { | 
| bsw/jbe@1309 | 1150                 return iframeEl.contentDocument; | 
| bsw/jbe@1309 | 1151             } else if (typeof iframeEl.contentWindow != UNDEF) { | 
| bsw/jbe@1309 | 1152                 return iframeEl.contentWindow.document; | 
| bsw/jbe@1309 | 1153             } else { | 
| bsw/jbe@1309 | 1154                 throw module.createError("getIframeDocument: No Document object found for iframe element"); | 
| bsw/jbe@1309 | 1155             } | 
| bsw/jbe@1309 | 1156         } | 
| bsw/jbe@1309 | 1157 | 
| bsw/jbe@1309 | 1158         function getIframeWindow(iframeEl) { | 
| bsw/jbe@1309 | 1159             if (typeof iframeEl.contentWindow != UNDEF) { | 
| bsw/jbe@1309 | 1160                 return iframeEl.contentWindow; | 
| bsw/jbe@1309 | 1161             } else if (typeof iframeEl.contentDocument != UNDEF) { | 
| bsw/jbe@1309 | 1162                 return iframeEl.contentDocument.defaultView; | 
| bsw/jbe@1309 | 1163             } else { | 
| bsw/jbe@1309 | 1164                 throw module.createError("getIframeWindow: No Window object found for iframe element"); | 
| bsw/jbe@1309 | 1165             } | 
| bsw/jbe@1309 | 1166         } | 
| bsw/jbe@1309 | 1167 | 
| bsw/jbe@1309 | 1168         // This looks bad. Is it worth it? | 
| bsw/jbe@1309 | 1169         function isWindow(obj) { | 
| bsw/jbe@1309 | 1170             return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document"); | 
| bsw/jbe@1309 | 1171         } | 
| bsw/jbe@1309 | 1172 | 
| bsw/jbe@1309 | 1173         function getContentDocument(obj, module, methodName) { | 
| bsw/jbe@1309 | 1174             var doc; | 
| bsw/jbe@1309 | 1175 | 
| bsw/jbe@1309 | 1176             if (!obj) { | 
| bsw/jbe@1309 | 1177                 doc = document; | 
| bsw/jbe@1309 | 1178             } | 
| bsw/jbe@1309 | 1179 | 
| bsw/jbe@1309 | 1180             // Test if a DOM node has been passed and obtain a document object for it if so | 
| bsw/jbe@1309 | 1181             else if (util.isHostProperty(obj, "nodeType")) { | 
| bsw/jbe@1309 | 1182                 doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") ? | 
| bsw/jbe@1309 | 1183                     getIframeDocument(obj) : getDocument(obj); | 
| bsw/jbe@1309 | 1184             } | 
| bsw/jbe@1309 | 1185 | 
| bsw/jbe@1309 | 1186             // Test if the doc parameter appears to be a Window object | 
| bsw/jbe@1309 | 1187             else if (isWindow(obj)) { | 
| bsw/jbe@1309 | 1188                 doc = obj.document; | 
| bsw/jbe@1309 | 1189             } | 
| bsw/jbe@1309 | 1190 | 
| bsw/jbe@1309 | 1191             if (!doc) { | 
| bsw/jbe@1309 | 1192                 throw module.createError(methodName + "(): Parameter must be a Window object or DOM node"); | 
| bsw/jbe@1309 | 1193             } | 
| bsw/jbe@1309 | 1194 | 
| bsw/jbe@1309 | 1195             return doc; | 
| bsw/jbe@1309 | 1196         } | 
| bsw/jbe@1309 | 1197 | 
| bsw/jbe@1309 | 1198         function getRootContainer(node) { | 
| bsw/jbe@1309 | 1199             var parent; | 
| bsw/jbe@1309 | 1200             while ( (parent = node.parentNode) ) { | 
| bsw/jbe@1309 | 1201                 node = parent; | 
| bsw/jbe@1309 | 1202             } | 
| bsw/jbe@1309 | 1203             return node; | 
| bsw/jbe@1309 | 1204         } | 
| bsw/jbe@1309 | 1205 | 
| bsw/jbe@1309 | 1206         function comparePoints(nodeA, offsetA, nodeB, offsetB) { | 
| bsw/jbe@1309 | 1207             // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing | 
| bsw/jbe@1309 | 1208             var nodeC, root, childA, childB, n; | 
| bsw/jbe@1309 | 1209             if (nodeA == nodeB) { | 
| bsw/jbe@1309 | 1210                 // Case 1: nodes are the same | 
| bsw/jbe@1309 | 1211                 return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1; | 
| bsw/jbe@1309 | 1212             } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) { | 
| bsw/jbe@1309 | 1213                 // Case 2: node C (container B or an ancestor) is a child node of A | 
| bsw/jbe@1309 | 1214                 return offsetA <= getNodeIndex(nodeC) ? -1 : 1; | 
| bsw/jbe@1309 | 1215             } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) { | 
| bsw/jbe@1309 | 1216                 // Case 3: node C (container A or an ancestor) is a child node of B | 
| bsw/jbe@1309 | 1217                 return getNodeIndex(nodeC) < offsetB  ? -1 : 1; | 
| bsw/jbe@1309 | 1218             } else { | 
| bsw/jbe@1309 | 1219                 root = getCommonAncestor(nodeA, nodeB); | 
| bsw/jbe@1309 | 1220                 if (!root) { | 
| bsw/jbe@1309 | 1221                     throw new Error("comparePoints error: nodes have no common ancestor"); | 
| bsw/jbe@1309 | 1222                 } | 
| bsw/jbe@1309 | 1223 | 
| bsw/jbe@1309 | 1224                 // Case 4: containers are siblings or descendants of siblings | 
| bsw/jbe@1309 | 1225                 childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true); | 
| bsw/jbe@1309 | 1226                 childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true); | 
| bsw/jbe@1309 | 1227 | 
| bsw/jbe@1309 | 1228                 if (childA === childB) { | 
| bsw/jbe@1309 | 1229                     // This shouldn't be possible | 
| bsw/jbe@1309 | 1230                     throw module.createError("comparePoints got to case 4 and childA and childB are the same!"); | 
| bsw/jbe@1309 | 1231                 } else { | 
| bsw/jbe@1309 | 1232                     n = root.firstChild; | 
| bsw/jbe@1309 | 1233                     while (n) { | 
| bsw/jbe@1309 | 1234                         if (n === childA) { | 
| bsw/jbe@1309 | 1235                             return -1; | 
| bsw/jbe@1309 | 1236                         } else if (n === childB) { | 
| bsw/jbe@1309 | 1237                             return 1; | 
| bsw/jbe@1309 | 1238                         } | 
| bsw/jbe@1309 | 1239                         n = n.nextSibling; | 
| bsw/jbe@1309 | 1240                     } | 
| bsw/jbe@1309 | 1241                 } | 
| bsw/jbe@1309 | 1242             } | 
| bsw/jbe@1309 | 1243         } | 
| bsw/jbe@1309 | 1244 | 
| bsw/jbe@1309 | 1245         /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 1246 | 
| bsw/jbe@1309 | 1247         // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried | 
| bsw/jbe@1309 | 1248         var crashyTextNodes = false; | 
| bsw/jbe@1309 | 1249 | 
| bsw/jbe@1309 | 1250         function isBrokenNode(node) { | 
| bsw/jbe@1309 | 1251             var n; | 
| bsw/jbe@1309 | 1252             try { | 
| bsw/jbe@1309 | 1253                 n = node.parentNode; | 
| bsw/jbe@1309 | 1254                 return false; | 
| bsw/jbe@1309 | 1255             } catch (e) { | 
| bsw/jbe@1309 | 1256                 return true; | 
| bsw/jbe@1309 | 1257             } | 
| bsw/jbe@1309 | 1258         } | 
| bsw/jbe@1309 | 1259 | 
| bsw/jbe@1309 | 1260         (function() { | 
| bsw/jbe@1309 | 1261             var el = document.createElement("b"); | 
| bsw/jbe@1309 | 1262             el.innerHTML = "1"; | 
| bsw/jbe@1309 | 1263             var textNode = el.firstChild; | 
| bsw/jbe@1309 | 1264             el.innerHTML = "<br />"; | 
| bsw/jbe@1309 | 1265             crashyTextNodes = isBrokenNode(textNode); | 
| bsw/jbe@1309 | 1266 | 
| bsw/jbe@1309 | 1267             api.features.crashyTextNodes = crashyTextNodes; | 
| bsw/jbe@1309 | 1268         })(); | 
| bsw/jbe@1309 | 1269 | 
| bsw/jbe@1309 | 1270         /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 1271 | 
| bsw/jbe@1309 | 1272         function inspectNode(node) { | 
| bsw/jbe@1309 | 1273             if (!node) { | 
| bsw/jbe@1309 | 1274                 return "[No node]"; | 
| bsw/jbe@1309 | 1275             } | 
| bsw/jbe@1309 | 1276             if (crashyTextNodes && isBrokenNode(node)) { | 
| bsw/jbe@1309 | 1277                 return "[Broken node]"; | 
| bsw/jbe@1309 | 1278             } | 
| bsw/jbe@1309 | 1279             if (isCharacterDataNode(node)) { | 
| bsw/jbe@1309 | 1280                 return '"' + node.data + '"'; | 
| bsw/jbe@1309 | 1281             } | 
| bsw/jbe@1309 | 1282             if (node.nodeType == 1) { | 
| bsw/jbe@1309 | 1283                 var idAttr = node.id ? ' id="' + node.id + '"' : ""; | 
| bsw/jbe@1309 | 1284                 return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]"; | 
| bsw/jbe@1309 | 1285             } | 
| bsw/jbe@1309 | 1286             return node.nodeName; | 
| bsw/jbe@1309 | 1287         } | 
| bsw/jbe@1309 | 1288 | 
| bsw/jbe@1309 | 1289         function fragmentFromNodeChildren(node) { | 
| bsw/jbe@1309 | 1290             var fragment = getDocument(node).createDocumentFragment(), child; | 
| bsw/jbe@1309 | 1291             while ( (child = node.firstChild) ) { | 
| bsw/jbe@1309 | 1292                 fragment.appendChild(child); | 
| bsw/jbe@1309 | 1293             } | 
| bsw/jbe@1309 | 1294             return fragment; | 
| bsw/jbe@1309 | 1295         } | 
| bsw/jbe@1309 | 1296 | 
| bsw/jbe@1309 | 1297         var getComputedStyleProperty; | 
| bsw/jbe@1309 | 1298         if (typeof window.getComputedStyle != UNDEF) { | 
| bsw/jbe@1309 | 1299             getComputedStyleProperty = function(el, propName) { | 
| bsw/jbe@1309 | 1300                 return getWindow(el).getComputedStyle(el, null)[propName]; | 
| bsw/jbe@1309 | 1301             }; | 
| bsw/jbe@1309 | 1302         } else if (typeof document.documentElement.currentStyle != UNDEF) { | 
| bsw/jbe@1309 | 1303             getComputedStyleProperty = function(el, propName) { | 
| bsw/jbe@1309 | 1304                 return el.currentStyle ? el.currentStyle[propName] : ""; | 
| bsw/jbe@1309 | 1305             }; | 
| bsw/jbe@1309 | 1306         } else { | 
| bsw/jbe@1309 | 1307             module.fail("No means of obtaining computed style properties found"); | 
| bsw/jbe@1309 | 1308         } | 
| bsw/jbe@1309 | 1309 | 
| bsw/jbe@1309 | 1310         function createTestElement(doc, html, contentEditable) { | 
| bsw/jbe@1309 | 1311             var body = getBody(doc); | 
| bsw/jbe@1309 | 1312             var el = doc.createElement("div"); | 
| bsw/jbe@1309 | 1313             el.contentEditable = "" + !!contentEditable; | 
| bsw/jbe@1309 | 1314             if (html) { | 
| bsw/jbe@1309 | 1315                 el.innerHTML = html; | 
| bsw/jbe@1309 | 1316             } | 
| bsw/jbe@1309 | 1317 | 
| bsw/jbe@1309 | 1318             // Insert the test element at the start of the body to prevent scrolling to the bottom in iOS (issue #292) | 
| bsw/jbe@1309 | 1319             var bodyFirstChild = body.firstChild; | 
| bsw/jbe@1309 | 1320             if (bodyFirstChild) { | 
| bsw/jbe@1309 | 1321                 body.insertBefore(el, bodyFirstChild); | 
| bsw/jbe@1309 | 1322             } else { | 
| bsw/jbe@1309 | 1323                 body.appendChild(el); | 
| bsw/jbe@1309 | 1324             } | 
| bsw/jbe@1309 | 1325 | 
| bsw/jbe@1309 | 1326             return el; | 
| bsw/jbe@1309 | 1327         } | 
| bsw/jbe@1309 | 1328 | 
| bsw/jbe@1309 | 1329         function removeNode(node) { | 
| bsw/jbe@1309 | 1330             return node.parentNode.removeChild(node); | 
| bsw/jbe@1309 | 1331         } | 
| bsw/jbe@1309 | 1332 | 
| bsw/jbe@1309 | 1333         function NodeIterator(root) { | 
| bsw/jbe@1309 | 1334             this.root = root; | 
| bsw/jbe@1309 | 1335             this._next = root; | 
| bsw/jbe@1309 | 1336         } | 
| bsw/jbe@1309 | 1337 | 
| bsw/jbe@1309 | 1338         NodeIterator.prototype = { | 
| bsw/jbe@1309 | 1339             _current: null, | 
| bsw/jbe@1309 | 1340 | 
| bsw/jbe@1309 | 1341             hasNext: function() { | 
| bsw/jbe@1309 | 1342                 return !!this._next; | 
| bsw/jbe@1309 | 1343             }, | 
| bsw/jbe@1309 | 1344 | 
| bsw/jbe@1309 | 1345             next: function() { | 
| bsw/jbe@1309 | 1346                 var n = this._current = this._next; | 
| bsw/jbe@1309 | 1347                 var child, next; | 
| bsw/jbe@1309 | 1348                 if (this._current) { | 
| bsw/jbe@1309 | 1349                     child = n.firstChild; | 
| bsw/jbe@1309 | 1350                     if (child) { | 
| bsw/jbe@1309 | 1351                         this._next = child; | 
| bsw/jbe@1309 | 1352                     } else { | 
| bsw/jbe@1309 | 1353                         next = null; | 
| bsw/jbe@1309 | 1354                         while ((n !== this.root) && !(next = n.nextSibling)) { | 
| bsw/jbe@1309 | 1355                             n = n.parentNode; | 
| bsw/jbe@1309 | 1356                         } | 
| bsw/jbe@1309 | 1357                         this._next = next; | 
| bsw/jbe@1309 | 1358                     } | 
| bsw/jbe@1309 | 1359                 } | 
| bsw/jbe@1309 | 1360                 return this._current; | 
| bsw/jbe@1309 | 1361             }, | 
| bsw/jbe@1309 | 1362 | 
| bsw/jbe@1309 | 1363             detach: function() { | 
| bsw/jbe@1309 | 1364                 this._current = this._next = this.root = null; | 
| bsw/jbe@1309 | 1365             } | 
| bsw/jbe@1309 | 1366         }; | 
| bsw/jbe@1309 | 1367 | 
| bsw/jbe@1309 | 1368         function createIterator(root) { | 
| bsw/jbe@1309 | 1369             return new NodeIterator(root); | 
| bsw/jbe@1309 | 1370         } | 
| bsw/jbe@1309 | 1371 | 
| bsw/jbe@1309 | 1372         function DomPosition(node, offset) { | 
| bsw/jbe@1309 | 1373             this.node = node; | 
| bsw/jbe@1309 | 1374             this.offset = offset; | 
| bsw/jbe@1309 | 1375         } | 
| bsw/jbe@1309 | 1376 | 
| bsw/jbe@1309 | 1377         DomPosition.prototype = { | 
| bsw/jbe@1309 | 1378             equals: function(pos) { | 
| bsw/jbe@1309 | 1379                 return !!pos && this.node === pos.node && this.offset == pos.offset; | 
| bsw/jbe@1309 | 1380             }, | 
| bsw/jbe@1309 | 1381 | 
| bsw/jbe@1309 | 1382             inspect: function() { | 
| bsw/jbe@1309 | 1383                 return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; | 
| bsw/jbe@1309 | 1384             }, | 
| bsw/jbe@1309 | 1385 | 
| bsw/jbe@1309 | 1386             toString: function() { | 
| bsw/jbe@1309 | 1387                 return this.inspect(); | 
| bsw/jbe@1309 | 1388             } | 
| bsw/jbe@1309 | 1389         }; | 
| bsw/jbe@1309 | 1390 | 
| bsw/jbe@1309 | 1391         function DOMException(codeName) { | 
| bsw/jbe@1309 | 1392             this.code = this[codeName]; | 
| bsw/jbe@1309 | 1393             this.codeName = codeName; | 
| bsw/jbe@1309 | 1394             this.message = "DOMException: " + this.codeName; | 
| bsw/jbe@1309 | 1395         } | 
| bsw/jbe@1309 | 1396 | 
| bsw/jbe@1309 | 1397         DOMException.prototype = { | 
| bsw/jbe@1309 | 1398             INDEX_SIZE_ERR: 1, | 
| bsw/jbe@1309 | 1399             HIERARCHY_REQUEST_ERR: 3, | 
| bsw/jbe@1309 | 1400             WRONG_DOCUMENT_ERR: 4, | 
| bsw/jbe@1309 | 1401             NO_MODIFICATION_ALLOWED_ERR: 7, | 
| bsw/jbe@1309 | 1402             NOT_FOUND_ERR: 8, | 
| bsw/jbe@1309 | 1403             NOT_SUPPORTED_ERR: 9, | 
| bsw/jbe@1309 | 1404             INVALID_STATE_ERR: 11, | 
| bsw/jbe@1309 | 1405             INVALID_NODE_TYPE_ERR: 24 | 
| bsw/jbe@1309 | 1406         }; | 
| bsw/jbe@1309 | 1407 | 
| bsw/jbe@1309 | 1408         DOMException.prototype.toString = function() { | 
| bsw/jbe@1309 | 1409             return this.message; | 
| bsw/jbe@1309 | 1410         }; | 
| bsw/jbe@1309 | 1411 | 
| bsw/jbe@1309 | 1412         api.dom = { | 
| bsw/jbe@1309 | 1413             arrayContains: arrayContains, | 
| bsw/jbe@1309 | 1414             isHtmlNamespace: isHtmlNamespace, | 
| bsw/jbe@1309 | 1415             parentElement: parentElement, | 
| bsw/jbe@1309 | 1416             getNodeIndex: getNodeIndex, | 
| bsw/jbe@1309 | 1417             getNodeLength: getNodeLength, | 
| bsw/jbe@1309 | 1418             getCommonAncestor: getCommonAncestor, | 
| bsw/jbe@1309 | 1419             isAncestorOf: isAncestorOf, | 
| bsw/jbe@1309 | 1420             isOrIsAncestorOf: isOrIsAncestorOf, | 
| bsw/jbe@1309 | 1421             getClosestAncestorIn: getClosestAncestorIn, | 
| bsw/jbe@1309 | 1422             isCharacterDataNode: isCharacterDataNode, | 
| bsw/jbe@1309 | 1423             isTextOrCommentNode: isTextOrCommentNode, | 
| bsw/jbe@1309 | 1424             insertAfter: insertAfter, | 
| bsw/jbe@1309 | 1425             splitDataNode: splitDataNode, | 
| bsw/jbe@1309 | 1426             getDocument: getDocument, | 
| bsw/jbe@1309 | 1427             getWindow: getWindow, | 
| bsw/jbe@1309 | 1428             getIframeWindow: getIframeWindow, | 
| bsw/jbe@1309 | 1429             getIframeDocument: getIframeDocument, | 
| bsw/jbe@1309 | 1430             getBody: getBody, | 
| bsw/jbe@1309 | 1431             isWindow: isWindow, | 
| bsw/jbe@1309 | 1432             getContentDocument: getContentDocument, | 
| bsw/jbe@1309 | 1433             getRootContainer: getRootContainer, | 
| bsw/jbe@1309 | 1434             comparePoints: comparePoints, | 
| bsw/jbe@1309 | 1435             isBrokenNode: isBrokenNode, | 
| bsw/jbe@1309 | 1436             inspectNode: inspectNode, | 
| bsw/jbe@1309 | 1437             getComputedStyleProperty: getComputedStyleProperty, | 
| bsw/jbe@1309 | 1438             createTestElement: createTestElement, | 
| bsw/jbe@1309 | 1439             removeNode: removeNode, | 
| bsw/jbe@1309 | 1440             fragmentFromNodeChildren: fragmentFromNodeChildren, | 
| bsw/jbe@1309 | 1441             createIterator: createIterator, | 
| bsw/jbe@1309 | 1442             DomPosition: DomPosition | 
| bsw/jbe@1309 | 1443         }; | 
| bsw/jbe@1309 | 1444 | 
| bsw/jbe@1309 | 1445         api.DOMException = DOMException; | 
| bsw/jbe@1309 | 1446     }); | 
| bsw/jbe@1309 | 1447 | 
| bsw/jbe@1309 | 1448     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 1449 | 
| bsw/jbe@1309 | 1450     // Pure JavaScript implementation of DOM Range | 
| bsw/jbe@1309 | 1451     api.createCoreModule("DomRange", ["DomUtil"], function(api, module) { | 
| bsw/jbe@1309 | 1452         var dom = api.dom; | 
| bsw/jbe@1309 | 1453         var util = api.util; | 
| bsw/jbe@1309 | 1454         var DomPosition = dom.DomPosition; | 
| bsw/jbe@1309 | 1455         var DOMException = api.DOMException; | 
| bsw/jbe@1309 | 1456 | 
| bsw/jbe@1309 | 1457         var isCharacterDataNode = dom.isCharacterDataNode; | 
| bsw/jbe@1309 | 1458         var getNodeIndex = dom.getNodeIndex; | 
| bsw/jbe@1309 | 1459         var isOrIsAncestorOf = dom.isOrIsAncestorOf; | 
| bsw/jbe@1309 | 1460         var getDocument = dom.getDocument; | 
| bsw/jbe@1309 | 1461         var comparePoints = dom.comparePoints; | 
| bsw/jbe@1309 | 1462         var splitDataNode = dom.splitDataNode; | 
| bsw/jbe@1309 | 1463         var getClosestAncestorIn = dom.getClosestAncestorIn; | 
| bsw/jbe@1309 | 1464         var getNodeLength = dom.getNodeLength; | 
| bsw/jbe@1309 | 1465         var arrayContains = dom.arrayContains; | 
| bsw/jbe@1309 | 1466         var getRootContainer = dom.getRootContainer; | 
| bsw/jbe@1309 | 1467         var crashyTextNodes = api.features.crashyTextNodes; | 
| bsw/jbe@1309 | 1468 | 
| bsw/jbe@1309 | 1469         var removeNode = dom.removeNode; | 
| bsw/jbe@1309 | 1470 | 
| bsw/jbe@1309 | 1471         /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 1472 | 
| bsw/jbe@1309 | 1473         // Utility functions | 
| bsw/jbe@1309 | 1474 | 
| bsw/jbe@1309 | 1475         function isNonTextPartiallySelected(node, range) { | 
| bsw/jbe@1309 | 1476             return (node.nodeType != 3) && | 
| bsw/jbe@1309 | 1477                    (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer)); | 
| bsw/jbe@1309 | 1478         } | 
| bsw/jbe@1309 | 1479 | 
| bsw/jbe@1309 | 1480         function getRangeDocument(range) { | 
| bsw/jbe@1309 | 1481             return range.document || getDocument(range.startContainer); | 
| bsw/jbe@1309 | 1482         } | 
| bsw/jbe@1309 | 1483 | 
| bsw/jbe@1309 | 1484         function getRangeRoot(range) { | 
| bsw/jbe@1309 | 1485             return getRootContainer(range.startContainer); | 
| bsw/jbe@1309 | 1486         } | 
| bsw/jbe@1309 | 1487 | 
| bsw/jbe@1309 | 1488         function getBoundaryBeforeNode(node) { | 
| bsw/jbe@1309 | 1489             return new DomPosition(node.parentNode, getNodeIndex(node)); | 
| bsw/jbe@1309 | 1490         } | 
| bsw/jbe@1309 | 1491 | 
| bsw/jbe@1309 | 1492         function getBoundaryAfterNode(node) { | 
| bsw/jbe@1309 | 1493             return new DomPosition(node.parentNode, getNodeIndex(node) + 1); | 
| bsw/jbe@1309 | 1494         } | 
| bsw/jbe@1309 | 1495 | 
| bsw/jbe@1309 | 1496         function insertNodeAtPosition(node, n, o) { | 
| bsw/jbe@1309 | 1497             var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; | 
| bsw/jbe@1309 | 1498             if (isCharacterDataNode(n)) { | 
| bsw/jbe@1309 | 1499                 if (o == n.length) { | 
| bsw/jbe@1309 | 1500                     dom.insertAfter(node, n); | 
| bsw/jbe@1309 | 1501                 } else { | 
| bsw/jbe@1309 | 1502                     n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o)); | 
| bsw/jbe@1309 | 1503                 } | 
| bsw/jbe@1309 | 1504             } else if (o >= n.childNodes.length) { | 
| bsw/jbe@1309 | 1505                 n.appendChild(node); | 
| bsw/jbe@1309 | 1506             } else { | 
| bsw/jbe@1309 | 1507                 n.insertBefore(node, n.childNodes[o]); | 
| bsw/jbe@1309 | 1508             } | 
| bsw/jbe@1309 | 1509             return firstNodeInserted; | 
| bsw/jbe@1309 | 1510         } | 
| bsw/jbe@1309 | 1511 | 
| bsw/jbe@1309 | 1512         function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) { | 
| bsw/jbe@1309 | 1513             assertRangeValid(rangeA); | 
| bsw/jbe@1309 | 1514             assertRangeValid(rangeB); | 
| bsw/jbe@1309 | 1515 | 
| bsw/jbe@1309 | 1516             if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) { | 
| bsw/jbe@1309 | 1517                 throw new DOMException("WRONG_DOCUMENT_ERR"); | 
| bsw/jbe@1309 | 1518             } | 
| bsw/jbe@1309 | 1519 | 
| bsw/jbe@1309 | 1520             var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset), | 
| bsw/jbe@1309 | 1521                 endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset); | 
| bsw/jbe@1309 | 1522 | 
| bsw/jbe@1309 | 1523             return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; | 
| bsw/jbe@1309 | 1524         } | 
| bsw/jbe@1309 | 1525 | 
| bsw/jbe@1309 | 1526         function cloneSubtree(iterator) { | 
| bsw/jbe@1309 | 1527             var partiallySelected; | 
| bsw/jbe@1309 | 1528             for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { | 
| bsw/jbe@1309 | 1529                 partiallySelected = iterator.isPartiallySelectedSubtree(); | 
| bsw/jbe@1309 | 1530                 node = node.cloneNode(!partiallySelected); | 
| bsw/jbe@1309 | 1531                 if (partiallySelected) { | 
| bsw/jbe@1309 | 1532                     subIterator = iterator.getSubtreeIterator(); | 
| bsw/jbe@1309 | 1533                     node.appendChild(cloneSubtree(subIterator)); | 
| bsw/jbe@1309 | 1534                     subIterator.detach(); | 
| bsw/jbe@1309 | 1535                 } | 
| bsw/jbe@1309 | 1536 | 
| bsw/jbe@1309 | 1537                 if (node.nodeType == 10) { // DocumentType | 
| bsw/jbe@1309 | 1538                     throw new DOMException("HIERARCHY_REQUEST_ERR"); | 
| bsw/jbe@1309 | 1539                 } | 
| bsw/jbe@1309 | 1540                 frag.appendChild(node); | 
| bsw/jbe@1309 | 1541             } | 
| bsw/jbe@1309 | 1542             return frag; | 
| bsw/jbe@1309 | 1543         } | 
| bsw/jbe@1309 | 1544 | 
| bsw/jbe@1309 | 1545         function iterateSubtree(rangeIterator, func, iteratorState) { | 
| bsw/jbe@1309 | 1546             var it, n; | 
| bsw/jbe@1309 | 1547             iteratorState = iteratorState || { stop: false }; | 
| bsw/jbe@1309 | 1548             for (var node, subRangeIterator; node = rangeIterator.next(); ) { | 
| bsw/jbe@1309 | 1549                 if (rangeIterator.isPartiallySelectedSubtree()) { | 
| bsw/jbe@1309 | 1550                     if (func(node) === false) { | 
| bsw/jbe@1309 | 1551                         iteratorState.stop = true; | 
| bsw/jbe@1309 | 1552                         return; | 
| bsw/jbe@1309 | 1553                     } else { | 
| bsw/jbe@1309 | 1554                         // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of | 
| bsw/jbe@1309 | 1555                         // the node selected by the Range. | 
| bsw/jbe@1309 | 1556                         subRangeIterator = rangeIterator.getSubtreeIterator(); | 
| bsw/jbe@1309 | 1557                         iterateSubtree(subRangeIterator, func, iteratorState); | 
| bsw/jbe@1309 | 1558                         subRangeIterator.detach(); | 
| bsw/jbe@1309 | 1559                         if (iteratorState.stop) { | 
| bsw/jbe@1309 | 1560                             return; | 
| bsw/jbe@1309 | 1561                         } | 
| bsw/jbe@1309 | 1562                     } | 
| bsw/jbe@1309 | 1563                 } else { | 
| bsw/jbe@1309 | 1564                     // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its | 
| bsw/jbe@1309 | 1565                     // descendants | 
| bsw/jbe@1309 | 1566                     it = dom.createIterator(node); | 
| bsw/jbe@1309 | 1567                     while ( (n = it.next()) ) { | 
| bsw/jbe@1309 | 1568                         if (func(n) === false) { | 
| bsw/jbe@1309 | 1569                             iteratorState.stop = true; | 
| bsw/jbe@1309 | 1570                             return; | 
| bsw/jbe@1309 | 1571                         } | 
| bsw/jbe@1309 | 1572                     } | 
| bsw/jbe@1309 | 1573                 } | 
| bsw/jbe@1309 | 1574             } | 
| bsw/jbe@1309 | 1575         } | 
| bsw/jbe@1309 | 1576 | 
| bsw/jbe@1309 | 1577         function deleteSubtree(iterator) { | 
| bsw/jbe@1309 | 1578             var subIterator; | 
| bsw/jbe@1309 | 1579             while (iterator.next()) { | 
| bsw/jbe@1309 | 1580                 if (iterator.isPartiallySelectedSubtree()) { | 
| bsw/jbe@1309 | 1581                     subIterator = iterator.getSubtreeIterator(); | 
| bsw/jbe@1309 | 1582                     deleteSubtree(subIterator); | 
| bsw/jbe@1309 | 1583                     subIterator.detach(); | 
| bsw/jbe@1309 | 1584                 } else { | 
| bsw/jbe@1309 | 1585                     iterator.remove(); | 
| bsw/jbe@1309 | 1586                 } | 
| bsw/jbe@1309 | 1587             } | 
| bsw/jbe@1309 | 1588         } | 
| bsw/jbe@1309 | 1589 | 
| bsw/jbe@1309 | 1590         function extractSubtree(iterator) { | 
| bsw/jbe@1309 | 1591             for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { | 
| bsw/jbe@1309 | 1592 | 
| bsw/jbe@1309 | 1593                 if (iterator.isPartiallySelectedSubtree()) { | 
| bsw/jbe@1309 | 1594                     node = node.cloneNode(false); | 
| bsw/jbe@1309 | 1595                     subIterator = iterator.getSubtreeIterator(); | 
| bsw/jbe@1309 | 1596                     node.appendChild(extractSubtree(subIterator)); | 
| bsw/jbe@1309 | 1597                     subIterator.detach(); | 
| bsw/jbe@1309 | 1598                 } else { | 
| bsw/jbe@1309 | 1599                     iterator.remove(); | 
| bsw/jbe@1309 | 1600                 } | 
| bsw/jbe@1309 | 1601                 if (node.nodeType == 10) { // DocumentType | 
| bsw/jbe@1309 | 1602                     throw new DOMException("HIERARCHY_REQUEST_ERR"); | 
| bsw/jbe@1309 | 1603                 } | 
| bsw/jbe@1309 | 1604                 frag.appendChild(node); | 
| bsw/jbe@1309 | 1605             } | 
| bsw/jbe@1309 | 1606             return frag; | 
| bsw/jbe@1309 | 1607         } | 
| bsw/jbe@1309 | 1608 | 
| bsw/jbe@1309 | 1609         function getNodesInRange(range, nodeTypes, filter) { | 
| bsw/jbe@1309 | 1610             var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex; | 
| bsw/jbe@1309 | 1611             var filterExists = !!filter; | 
| bsw/jbe@1309 | 1612             if (filterNodeTypes) { | 
| bsw/jbe@1309 | 1613                 regex = new RegExp("^(" + nodeTypes.join("|") + ")$"); | 
| bsw/jbe@1309 | 1614             } | 
| bsw/jbe@1309 | 1615 | 
| bsw/jbe@1309 | 1616             var nodes = []; | 
| bsw/jbe@1309 | 1617             iterateSubtree(new RangeIterator(range, false), function(node) { | 
| bsw/jbe@1309 | 1618                 if (filterNodeTypes && !regex.test(node.nodeType)) { | 
| bsw/jbe@1309 | 1619                     return; | 
| bsw/jbe@1309 | 1620                 } | 
| bsw/jbe@1309 | 1621                 if (filterExists && !filter(node)) { | 
| bsw/jbe@1309 | 1622                     return; | 
| bsw/jbe@1309 | 1623                 } | 
| bsw/jbe@1309 | 1624                 // Don't include a boundary container if it is a character data node and the range does not contain any | 
| bsw/jbe@1309 | 1625                 // of its character data. See issue 190. | 
| bsw/jbe@1309 | 1626                 var sc = range.startContainer; | 
| bsw/jbe@1309 | 1627                 if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) { | 
| bsw/jbe@1309 | 1628                     return; | 
| bsw/jbe@1309 | 1629                 } | 
| bsw/jbe@1309 | 1630 | 
| bsw/jbe@1309 | 1631                 var ec = range.endContainer; | 
| bsw/jbe@1309 | 1632                 if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) { | 
| bsw/jbe@1309 | 1633                     return; | 
| bsw/jbe@1309 | 1634                 } | 
| bsw/jbe@1309 | 1635 | 
| bsw/jbe@1309 | 1636                 nodes.push(node); | 
| bsw/jbe@1309 | 1637             }); | 
| bsw/jbe@1309 | 1638             return nodes; | 
| bsw/jbe@1309 | 1639         } | 
| bsw/jbe@1309 | 1640 | 
| bsw/jbe@1309 | 1641         function inspect(range) { | 
| bsw/jbe@1309 | 1642             var name = (typeof range.getName == "undefined") ? "Range" : range.getName(); | 
| bsw/jbe@1309 | 1643             return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + | 
| bsw/jbe@1309 | 1644                     dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]"; | 
| bsw/jbe@1309 | 1645         } | 
| bsw/jbe@1309 | 1646 | 
| bsw/jbe@1309 | 1647         /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 1648 | 
| bsw/jbe@1309 | 1649         // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange) | 
| bsw/jbe@1309 | 1650 | 
| bsw/jbe@1309 | 1651         function RangeIterator(range, clonePartiallySelectedTextNodes) { | 
| bsw/jbe@1309 | 1652             this.range = range; | 
| bsw/jbe@1309 | 1653             this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes; | 
| bsw/jbe@1309 | 1654 | 
| bsw/jbe@1309 | 1655 | 
| bsw/jbe@1309 | 1656             if (!range.collapsed) { | 
| bsw/jbe@1309 | 1657                 this.sc = range.startContainer; | 
| bsw/jbe@1309 | 1658                 this.so = range.startOffset; | 
| bsw/jbe@1309 | 1659                 this.ec = range.endContainer; | 
| bsw/jbe@1309 | 1660                 this.eo = range.endOffset; | 
| bsw/jbe@1309 | 1661                 var root = range.commonAncestorContainer; | 
| bsw/jbe@1309 | 1662 | 
| bsw/jbe@1309 | 1663                 if (this.sc === this.ec && isCharacterDataNode(this.sc)) { | 
| bsw/jbe@1309 | 1664                     this.isSingleCharacterDataNode = true; | 
| bsw/jbe@1309 | 1665                     this._first = this._last = this._next = this.sc; | 
| bsw/jbe@1309 | 1666                 } else { | 
| bsw/jbe@1309 | 1667                     this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ? | 
| bsw/jbe@1309 | 1668                         this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true); | 
| bsw/jbe@1309 | 1669                     this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ? | 
| bsw/jbe@1309 | 1670                         this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true); | 
| bsw/jbe@1309 | 1671                 } | 
| bsw/jbe@1309 | 1672             } | 
| bsw/jbe@1309 | 1673         } | 
| bsw/jbe@1309 | 1674 | 
| bsw/jbe@1309 | 1675         RangeIterator.prototype = { | 
| bsw/jbe@1309 | 1676             _current: null, | 
| bsw/jbe@1309 | 1677             _next: null, | 
| bsw/jbe@1309 | 1678             _first: null, | 
| bsw/jbe@1309 | 1679             _last: null, | 
| bsw/jbe@1309 | 1680             isSingleCharacterDataNode: false, | 
| bsw/jbe@1309 | 1681 | 
| bsw/jbe@1309 | 1682             reset: function() { | 
| bsw/jbe@1309 | 1683                 this._current = null; | 
| bsw/jbe@1309 | 1684                 this._next = this._first; | 
| bsw/jbe@1309 | 1685             }, | 
| bsw/jbe@1309 | 1686 | 
| bsw/jbe@1309 | 1687             hasNext: function() { | 
| bsw/jbe@1309 | 1688                 return !!this._next; | 
| bsw/jbe@1309 | 1689             }, | 
| bsw/jbe@1309 | 1690 | 
| bsw/jbe@1309 | 1691             next: function() { | 
| bsw/jbe@1309 | 1692                 // Move to next node | 
| bsw/jbe@1309 | 1693                 var current = this._current = this._next; | 
| bsw/jbe@1309 | 1694                 if (current) { | 
| bsw/jbe@1309 | 1695                     this._next = (current !== this._last) ? current.nextSibling : null; | 
| bsw/jbe@1309 | 1696 | 
| bsw/jbe@1309 | 1697                     // Check for partially selected text nodes | 
| bsw/jbe@1309 | 1698                     if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) { | 
| bsw/jbe@1309 | 1699                         if (current === this.ec) { | 
| bsw/jbe@1309 | 1700                             (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo); | 
| bsw/jbe@1309 | 1701                         } | 
| bsw/jbe@1309 | 1702                         if (this._current === this.sc) { | 
| bsw/jbe@1309 | 1703                             (current = current.cloneNode(true)).deleteData(0, this.so); | 
| bsw/jbe@1309 | 1704                         } | 
| bsw/jbe@1309 | 1705                     } | 
| bsw/jbe@1309 | 1706                 } | 
| bsw/jbe@1309 | 1707 | 
| bsw/jbe@1309 | 1708                 return current; | 
| bsw/jbe@1309 | 1709             }, | 
| bsw/jbe@1309 | 1710 | 
| bsw/jbe@1309 | 1711             remove: function() { | 
| bsw/jbe@1309 | 1712                 var current = this._current, start, end; | 
| bsw/jbe@1309 | 1713 | 
| bsw/jbe@1309 | 1714                 if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) { | 
| bsw/jbe@1309 | 1715                     start = (current === this.sc) ? this.so : 0; | 
| bsw/jbe@1309 | 1716                     end = (current === this.ec) ? this.eo : current.length; | 
| bsw/jbe@1309 | 1717                     if (start != end) { | 
| bsw/jbe@1309 | 1718                         current.deleteData(start, end - start); | 
| bsw/jbe@1309 | 1719                     } | 
| bsw/jbe@1309 | 1720                 } else { | 
| bsw/jbe@1309 | 1721                     if (current.parentNode) { | 
| bsw/jbe@1309 | 1722                         removeNode(current); | 
| bsw/jbe@1309 | 1723                     } else { | 
| bsw/jbe@1309 | 1724                     } | 
| bsw/jbe@1309 | 1725                 } | 
| bsw/jbe@1309 | 1726             }, | 
| bsw/jbe@1309 | 1727 | 
| bsw/jbe@1309 | 1728             // Checks if the current node is partially selected | 
| bsw/jbe@1309 | 1729             isPartiallySelectedSubtree: function() { | 
| bsw/jbe@1309 | 1730                 var current = this._current; | 
| bsw/jbe@1309 | 1731                 return isNonTextPartiallySelected(current, this.range); | 
| bsw/jbe@1309 | 1732             }, | 
| bsw/jbe@1309 | 1733 | 
| bsw/jbe@1309 | 1734             getSubtreeIterator: function() { | 
| bsw/jbe@1309 | 1735                 var subRange; | 
| bsw/jbe@1309 | 1736                 if (this.isSingleCharacterDataNode) { | 
| bsw/jbe@1309 | 1737                     subRange = this.range.cloneRange(); | 
| bsw/jbe@1309 | 1738                     subRange.collapse(false); | 
| bsw/jbe@1309 | 1739                 } else { | 
| bsw/jbe@1309 | 1740                     subRange = new Range(getRangeDocument(this.range)); | 
| bsw/jbe@1309 | 1741                     var current = this._current; | 
| bsw/jbe@1309 | 1742                     var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current); | 
| bsw/jbe@1309 | 1743 | 
| bsw/jbe@1309 | 1744                     if (isOrIsAncestorOf(current, this.sc)) { | 
| bsw/jbe@1309 | 1745                         startContainer = this.sc; | 
| bsw/jbe@1309 | 1746                         startOffset = this.so; | 
| bsw/jbe@1309 | 1747                     } | 
| bsw/jbe@1309 | 1748                     if (isOrIsAncestorOf(current, this.ec)) { | 
| bsw/jbe@1309 | 1749                         endContainer = this.ec; | 
| bsw/jbe@1309 | 1750                         endOffset = this.eo; | 
| bsw/jbe@1309 | 1751                     } | 
| bsw/jbe@1309 | 1752 | 
| bsw/jbe@1309 | 1753                     updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); | 
| bsw/jbe@1309 | 1754                 } | 
| bsw/jbe@1309 | 1755                 return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); | 
| bsw/jbe@1309 | 1756             }, | 
| bsw/jbe@1309 | 1757 | 
| bsw/jbe@1309 | 1758             detach: function() { | 
| bsw/jbe@1309 | 1759                 this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null; | 
| bsw/jbe@1309 | 1760             } | 
| bsw/jbe@1309 | 1761         }; | 
| bsw/jbe@1309 | 1762 | 
| bsw/jbe@1309 | 1763         /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 1764 | 
| bsw/jbe@1309 | 1765         var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10]; | 
| bsw/jbe@1309 | 1766         var rootContainerNodeTypes = [2, 9, 11]; | 
| bsw/jbe@1309 | 1767         var readonlyNodeTypes = [5, 6, 10, 12]; | 
| bsw/jbe@1309 | 1768         var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11]; | 
| bsw/jbe@1309 | 1769         var surroundNodeTypes = [1, 3, 4, 5, 7, 8]; | 
| bsw/jbe@1309 | 1770 | 
| bsw/jbe@1309 | 1771         function createAncestorFinder(nodeTypes) { | 
| bsw/jbe@1309 | 1772             return function(node, selfIsAncestor) { | 
| bsw/jbe@1309 | 1773                 var t, n = selfIsAncestor ? node : node.parentNode; | 
| bsw/jbe@1309 | 1774                 while (n) { | 
| bsw/jbe@1309 | 1775                     t = n.nodeType; | 
| bsw/jbe@1309 | 1776                     if (arrayContains(nodeTypes, t)) { | 
| bsw/jbe@1309 | 1777                         return n; | 
| bsw/jbe@1309 | 1778                     } | 
| bsw/jbe@1309 | 1779                     n = n.parentNode; | 
| bsw/jbe@1309 | 1780                 } | 
| bsw/jbe@1309 | 1781                 return null; | 
| bsw/jbe@1309 | 1782             }; | 
| bsw/jbe@1309 | 1783         } | 
| bsw/jbe@1309 | 1784 | 
| bsw/jbe@1309 | 1785         var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); | 
| bsw/jbe@1309 | 1786         var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); | 
| bsw/jbe@1309 | 1787         var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); | 
| bsw/jbe@1309 | 1788 | 
| bsw/jbe@1309 | 1789         function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { | 
| bsw/jbe@1309 | 1790             if (getDocTypeNotationEntityAncestor(node, allowSelf)) { | 
| bsw/jbe@1309 | 1791                 throw new DOMException("INVALID_NODE_TYPE_ERR"); | 
| bsw/jbe@1309 | 1792             } | 
| bsw/jbe@1309 | 1793         } | 
| bsw/jbe@1309 | 1794 | 
| bsw/jbe@1309 | 1795         function assertValidNodeType(node, invalidTypes) { | 
| bsw/jbe@1309 | 1796             if (!arrayContains(invalidTypes, node.nodeType)) { | 
| bsw/jbe@1309 | 1797                 throw new DOMException("INVALID_NODE_TYPE_ERR"); | 
| bsw/jbe@1309 | 1798             } | 
| bsw/jbe@1309 | 1799         } | 
| bsw/jbe@1309 | 1800 | 
| bsw/jbe@1309 | 1801         function assertValidOffset(node, offset) { | 
| bsw/jbe@1309 | 1802             if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) { | 
| bsw/jbe@1309 | 1803                 throw new DOMException("INDEX_SIZE_ERR"); | 
| bsw/jbe@1309 | 1804             } | 
| bsw/jbe@1309 | 1805         } | 
| bsw/jbe@1309 | 1806 | 
| bsw/jbe@1309 | 1807         function assertSameDocumentOrFragment(node1, node2) { | 
| bsw/jbe@1309 | 1808             if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) { | 
| bsw/jbe@1309 | 1809                 throw new DOMException("WRONG_DOCUMENT_ERR"); | 
| bsw/jbe@1309 | 1810             } | 
| bsw/jbe@1309 | 1811         } | 
| bsw/jbe@1309 | 1812 | 
| bsw/jbe@1309 | 1813         function assertNodeNotReadOnly(node) { | 
| bsw/jbe@1309 | 1814             if (getReadonlyAncestor(node, true)) { | 
| bsw/jbe@1309 | 1815                 throw new DOMException("NO_MODIFICATION_ALLOWED_ERR"); | 
| bsw/jbe@1309 | 1816             } | 
| bsw/jbe@1309 | 1817         } | 
| bsw/jbe@1309 | 1818 | 
| bsw/jbe@1309 | 1819         function assertNode(node, codeName) { | 
| bsw/jbe@1309 | 1820             if (!node) { | 
| bsw/jbe@1309 | 1821                 throw new DOMException(codeName); | 
| bsw/jbe@1309 | 1822             } | 
| bsw/jbe@1309 | 1823         } | 
| bsw/jbe@1309 | 1824 | 
| bsw/jbe@1309 | 1825         function isValidOffset(node, offset) { | 
| bsw/jbe@1309 | 1826             return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length); | 
| bsw/jbe@1309 | 1827         } | 
| bsw/jbe@1309 | 1828 | 
| bsw/jbe@1309 | 1829         function isRangeValid(range) { | 
| bsw/jbe@1309 | 1830             return (!!range.startContainer && !!range.endContainer && | 
| bsw/jbe@1309 | 1831                     !(crashyTextNodes && (dom.isBrokenNode(range.startContainer) || dom.isBrokenNode(range.endContainer))) && | 
| bsw/jbe@1309 | 1832                     getRootContainer(range.startContainer) == getRootContainer(range.endContainer) && | 
| bsw/jbe@1309 | 1833                     isValidOffset(range.startContainer, range.startOffset) && | 
| bsw/jbe@1309 | 1834                     isValidOffset(range.endContainer, range.endOffset)); | 
| bsw/jbe@1309 | 1835         } | 
| bsw/jbe@1309 | 1836 | 
| bsw/jbe@1309 | 1837         function assertRangeValid(range) { | 
| bsw/jbe@1309 | 1838             if (!isRangeValid(range)) { | 
| bsw/jbe@1309 | 1839                 throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: (" + range.inspect() + ")"); | 
| bsw/jbe@1309 | 1840             } | 
| bsw/jbe@1309 | 1841         } | 
| bsw/jbe@1309 | 1842 | 
| bsw/jbe@1309 | 1843         /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 1844 | 
| bsw/jbe@1309 | 1845         // Test the browser's innerHTML support to decide how to implement createContextualFragment | 
| bsw/jbe@1309 | 1846         var styleEl = document.createElement("style"); | 
| bsw/jbe@1309 | 1847         var htmlParsingConforms = false; | 
| bsw/jbe@1309 | 1848         try { | 
| bsw/jbe@1309 | 1849             styleEl.innerHTML = "<b>x</b>"; | 
| bsw/jbe@1309 | 1850             htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node | 
| bsw/jbe@1309 | 1851         } catch (e) { | 
| bsw/jbe@1309 | 1852             // IE 6 and 7 throw | 
| bsw/jbe@1309 | 1853         } | 
| bsw/jbe@1309 | 1854 | 
| bsw/jbe@1309 | 1855         api.features.htmlParsingConforms = htmlParsingConforms; | 
| bsw/jbe@1309 | 1856 | 
| bsw/jbe@1309 | 1857         var createContextualFragment = htmlParsingConforms ? | 
| bsw/jbe@1309 | 1858 | 
| bsw/jbe@1309 | 1859             // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See | 
| bsw/jbe@1309 | 1860             // discussion and base code for this implementation at issue 67. | 
| bsw/jbe@1309 | 1861             // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface | 
| bsw/jbe@1309 | 1862             // Thanks to Aleks Williams. | 
| bsw/jbe@1309 | 1863             function(fragmentStr) { | 
| bsw/jbe@1309 | 1864                 // "Let node the context object's start's node." | 
| bsw/jbe@1309 | 1865                 var node = this.startContainer; | 
| bsw/jbe@1309 | 1866                 var doc = getDocument(node); | 
| bsw/jbe@1309 | 1867 | 
| bsw/jbe@1309 | 1868                 // "If the context object's start's node is null, raise an INVALID_STATE_ERR | 
| bsw/jbe@1309 | 1869                 // exception and abort these steps." | 
| bsw/jbe@1309 | 1870                 if (!node) { | 
| bsw/jbe@1309 | 1871                     throw new DOMException("INVALID_STATE_ERR"); | 
| bsw/jbe@1309 | 1872                 } | 
| bsw/jbe@1309 | 1873 | 
| bsw/jbe@1309 | 1874                 // "Let element be as follows, depending on node's interface:" | 
| bsw/jbe@1309 | 1875                 // Document, Document Fragment: null | 
| bsw/jbe@1309 | 1876                 var el = null; | 
| bsw/jbe@1309 | 1877 | 
| bsw/jbe@1309 | 1878                 // "Element: node" | 
| bsw/jbe@1309 | 1879                 if (node.nodeType == 1) { | 
| bsw/jbe@1309 | 1880                     el = node; | 
| bsw/jbe@1309 | 1881 | 
| bsw/jbe@1309 | 1882                 // "Text, Comment: node's parentElement" | 
| bsw/jbe@1309 | 1883                 } else if (isCharacterDataNode(node)) { | 
| bsw/jbe@1309 | 1884                     el = dom.parentElement(node); | 
| bsw/jbe@1309 | 1885                 } | 
| bsw/jbe@1309 | 1886 | 
| bsw/jbe@1309 | 1887                 // "If either element is null or element's ownerDocument is an HTML document | 
| bsw/jbe@1309 | 1888                 // and element's local name is "html" and element's namespace is the HTML | 
| bsw/jbe@1309 | 1889                 // namespace" | 
| bsw/jbe@1309 | 1890                 if (el === null || ( | 
| bsw/jbe@1309 | 1891                     el.nodeName == "HTML" && | 
| bsw/jbe@1309 | 1892                     dom.isHtmlNamespace(getDocument(el).documentElement) && | 
| bsw/jbe@1309 | 1893                     dom.isHtmlNamespace(el) | 
| bsw/jbe@1309 | 1894                 )) { | 
| bsw/jbe@1309 | 1895 | 
| bsw/jbe@1309 | 1896                 // "let element be a new Element with "body" as its local name and the HTML | 
| bsw/jbe@1309 | 1897                 // namespace as its namespace."" | 
| bsw/jbe@1309 | 1898                     el = doc.createElement("body"); | 
| bsw/jbe@1309 | 1899                 } else { | 
| bsw/jbe@1309 | 1900                     el = el.cloneNode(false); | 
| bsw/jbe@1309 | 1901                 } | 
| bsw/jbe@1309 | 1902 | 
| bsw/jbe@1309 | 1903                 // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm." | 
| bsw/jbe@1309 | 1904                 // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm." | 
| bsw/jbe@1309 | 1905                 // "In either case, the algorithm must be invoked with fragment as the input | 
| bsw/jbe@1309 | 1906                 // and element as the context element." | 
| bsw/jbe@1309 | 1907                 el.innerHTML = fragmentStr; | 
| bsw/jbe@1309 | 1908 | 
| bsw/jbe@1309 | 1909                 // "If this raises an exception, then abort these steps. Otherwise, let new | 
| bsw/jbe@1309 | 1910                 // children be the nodes returned." | 
| bsw/jbe@1309 | 1911 | 
| bsw/jbe@1309 | 1912                 // "Let fragment be a new DocumentFragment." | 
| bsw/jbe@1309 | 1913                 // "Append all new children to fragment." | 
| bsw/jbe@1309 | 1914                 // "Return fragment." | 
| bsw/jbe@1309 | 1915                 return dom.fragmentFromNodeChildren(el); | 
| bsw/jbe@1309 | 1916             } : | 
| bsw/jbe@1309 | 1917 | 
| bsw/jbe@1309 | 1918             // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that | 
| bsw/jbe@1309 | 1919             // previous versions of Rangy used (with the exception of using a body element rather than a div) | 
| bsw/jbe@1309 | 1920             function(fragmentStr) { | 
| bsw/jbe@1309 | 1921                 var doc = getRangeDocument(this); | 
| bsw/jbe@1309 | 1922                 var el = doc.createElement("body"); | 
| bsw/jbe@1309 | 1923                 el.innerHTML = fragmentStr; | 
| bsw/jbe@1309 | 1924 | 
| bsw/jbe@1309 | 1925                 return dom.fragmentFromNodeChildren(el); | 
| bsw/jbe@1309 | 1926             }; | 
| bsw/jbe@1309 | 1927 | 
| bsw/jbe@1309 | 1928         function splitRangeBoundaries(range, positionsToPreserve) { | 
| bsw/jbe@1309 | 1929             assertRangeValid(range); | 
| bsw/jbe@1309 | 1930 | 
| bsw/jbe@1309 | 1931             var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset; | 
| bsw/jbe@1309 | 1932             var startEndSame = (sc === ec); | 
| bsw/jbe@1309 | 1933 | 
| bsw/jbe@1309 | 1934             if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { | 
| bsw/jbe@1309 | 1935                 splitDataNode(ec, eo, positionsToPreserve); | 
| bsw/jbe@1309 | 1936             } | 
| bsw/jbe@1309 | 1937 | 
| bsw/jbe@1309 | 1938             if (isCharacterDataNode(sc) && so > 0 && so < sc.length) { | 
| bsw/jbe@1309 | 1939                 sc = splitDataNode(sc, so, positionsToPreserve); | 
| bsw/jbe@1309 | 1940                 if (startEndSame) { | 
| bsw/jbe@1309 | 1941                     eo -= so; | 
| bsw/jbe@1309 | 1942                     ec = sc; | 
| bsw/jbe@1309 | 1943                 } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) { | 
| bsw/jbe@1309 | 1944                     eo++; | 
| bsw/jbe@1309 | 1945                 } | 
| bsw/jbe@1309 | 1946                 so = 0; | 
| bsw/jbe@1309 | 1947             } | 
| bsw/jbe@1309 | 1948             range.setStartAndEnd(sc, so, ec, eo); | 
| bsw/jbe@1309 | 1949         } | 
| bsw/jbe@1309 | 1950 | 
| bsw/jbe@1309 | 1951         function rangeToHtml(range) { | 
| bsw/jbe@1309 | 1952             assertRangeValid(range); | 
| bsw/jbe@1309 | 1953             var container = range.commonAncestorContainer.parentNode.cloneNode(false); | 
| bsw/jbe@1309 | 1954             container.appendChild( range.cloneContents() ); | 
| bsw/jbe@1309 | 1955             return container.innerHTML; | 
| bsw/jbe@1309 | 1956         } | 
| bsw/jbe@1309 | 1957 | 
| bsw/jbe@1309 | 1958         /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 1959 | 
| bsw/jbe@1309 | 1960         var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", | 
| bsw/jbe@1309 | 1961             "commonAncestorContainer"]; | 
| bsw/jbe@1309 | 1962 | 
| bsw/jbe@1309 | 1963         var s2s = 0, s2e = 1, e2e = 2, e2s = 3; | 
| bsw/jbe@1309 | 1964         var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; | 
| bsw/jbe@1309 | 1965 | 
| bsw/jbe@1309 | 1966         util.extend(api.rangePrototype, { | 
| bsw/jbe@1309 | 1967             compareBoundaryPoints: function(how, range) { | 
| bsw/jbe@1309 | 1968                 assertRangeValid(this); | 
| bsw/jbe@1309 | 1969                 assertSameDocumentOrFragment(this.startContainer, range.startContainer); | 
| bsw/jbe@1309 | 1970 | 
| bsw/jbe@1309 | 1971                 var nodeA, offsetA, nodeB, offsetB; | 
| bsw/jbe@1309 | 1972                 var prefixA = (how == e2s || how == s2s) ? "start" : "end"; | 
| bsw/jbe@1309 | 1973                 var prefixB = (how == s2e || how == s2s) ? "start" : "end"; | 
| bsw/jbe@1309 | 1974                 nodeA = this[prefixA + "Container"]; | 
| bsw/jbe@1309 | 1975                 offsetA = this[prefixA + "Offset"]; | 
| bsw/jbe@1309 | 1976                 nodeB = range[prefixB + "Container"]; | 
| bsw/jbe@1309 | 1977                 offsetB = range[prefixB + "Offset"]; | 
| bsw/jbe@1309 | 1978                 return comparePoints(nodeA, offsetA, nodeB, offsetB); | 
| bsw/jbe@1309 | 1979             }, | 
| bsw/jbe@1309 | 1980 | 
| bsw/jbe@1309 | 1981             insertNode: function(node) { | 
| bsw/jbe@1309 | 1982                 assertRangeValid(this); | 
| bsw/jbe@1309 | 1983                 assertValidNodeType(node, insertableNodeTypes); | 
| bsw/jbe@1309 | 1984                 assertNodeNotReadOnly(this.startContainer); | 
| bsw/jbe@1309 | 1985 | 
| bsw/jbe@1309 | 1986                 if (isOrIsAncestorOf(node, this.startContainer)) { | 
| bsw/jbe@1309 | 1987                     throw new DOMException("HIERARCHY_REQUEST_ERR"); | 
| bsw/jbe@1309 | 1988                 } | 
| bsw/jbe@1309 | 1989 | 
| bsw/jbe@1309 | 1990                 // No check for whether the container of the start of the Range is of a type that does not allow | 
| bsw/jbe@1309 | 1991                 // children of the type of node: the browser's DOM implementation should do this for us when we attempt | 
| bsw/jbe@1309 | 1992                 // to add the node | 
| bsw/jbe@1309 | 1993 | 
| bsw/jbe@1309 | 1994                 var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset); | 
| bsw/jbe@1309 | 1995                 this.setStartBefore(firstNodeInserted); | 
| bsw/jbe@1309 | 1996             }, | 
| bsw/jbe@1309 | 1997 | 
| bsw/jbe@1309 | 1998             cloneContents: function() { | 
| bsw/jbe@1309 | 1999                 assertRangeValid(this); | 
| bsw/jbe@1309 | 2000 | 
| bsw/jbe@1309 | 2001                 var clone, frag; | 
| bsw/jbe@1309 | 2002                 if (this.collapsed) { | 
| bsw/jbe@1309 | 2003                     return getRangeDocument(this).createDocumentFragment(); | 
| bsw/jbe@1309 | 2004                 } else { | 
| bsw/jbe@1309 | 2005                     if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) { | 
| bsw/jbe@1309 | 2006                         clone = this.startContainer.cloneNode(true); | 
| bsw/jbe@1309 | 2007                         clone.data = clone.data.slice(this.startOffset, this.endOffset); | 
| bsw/jbe@1309 | 2008                         frag = getRangeDocument(this).createDocumentFragment(); | 
| bsw/jbe@1309 | 2009                         frag.appendChild(clone); | 
| bsw/jbe@1309 | 2010                         return frag; | 
| bsw/jbe@1309 | 2011                     } else { | 
| bsw/jbe@1309 | 2012                         var iterator = new RangeIterator(this, true); | 
| bsw/jbe@1309 | 2013                         clone = cloneSubtree(iterator); | 
| bsw/jbe@1309 | 2014                         iterator.detach(); | 
| bsw/jbe@1309 | 2015                     } | 
| bsw/jbe@1309 | 2016                     return clone; | 
| bsw/jbe@1309 | 2017                 } | 
| bsw/jbe@1309 | 2018             }, | 
| bsw/jbe@1309 | 2019 | 
| bsw/jbe@1309 | 2020             canSurroundContents: function() { | 
| bsw/jbe@1309 | 2021                 assertRangeValid(this); | 
| bsw/jbe@1309 | 2022                 assertNodeNotReadOnly(this.startContainer); | 
| bsw/jbe@1309 | 2023                 assertNodeNotReadOnly(this.endContainer); | 
| bsw/jbe@1309 | 2024 | 
| bsw/jbe@1309 | 2025                 // Check if the contents can be surrounded. Specifically, this means whether the range partially selects | 
| bsw/jbe@1309 | 2026                 // no non-text nodes. | 
| bsw/jbe@1309 | 2027                 var iterator = new RangeIterator(this, true); | 
| bsw/jbe@1309 | 2028                 var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || | 
| bsw/jbe@1309 | 2029                         (iterator._last && isNonTextPartiallySelected(iterator._last, this))); | 
| bsw/jbe@1309 | 2030                 iterator.detach(); | 
| bsw/jbe@1309 | 2031                 return !boundariesInvalid; | 
| bsw/jbe@1309 | 2032             }, | 
| bsw/jbe@1309 | 2033 | 
| bsw/jbe@1309 | 2034             surroundContents: function(node) { | 
| bsw/jbe@1309 | 2035                 assertValidNodeType(node, surroundNodeTypes); | 
| bsw/jbe@1309 | 2036 | 
| bsw/jbe@1309 | 2037                 if (!this.canSurroundContents()) { | 
| bsw/jbe@1309 | 2038                     throw new DOMException("INVALID_STATE_ERR"); | 
| bsw/jbe@1309 | 2039                 } | 
| bsw/jbe@1309 | 2040 | 
| bsw/jbe@1309 | 2041                 // Extract the contents | 
| bsw/jbe@1309 | 2042                 var content = this.extractContents(); | 
| bsw/jbe@1309 | 2043 | 
| bsw/jbe@1309 | 2044                 // Clear the children of the node | 
| bsw/jbe@1309 | 2045                 if (node.hasChildNodes()) { | 
| bsw/jbe@1309 | 2046                     while (node.lastChild) { | 
| bsw/jbe@1309 | 2047                         node.removeChild(node.lastChild); | 
| bsw/jbe@1309 | 2048                     } | 
| bsw/jbe@1309 | 2049                 } | 
| bsw/jbe@1309 | 2050 | 
| bsw/jbe@1309 | 2051                 // Insert the new node and add the extracted contents | 
| bsw/jbe@1309 | 2052                 insertNodeAtPosition(node, this.startContainer, this.startOffset); | 
| bsw/jbe@1309 | 2053                 node.appendChild(content); | 
| bsw/jbe@1309 | 2054 | 
| bsw/jbe@1309 | 2055                 this.selectNode(node); | 
| bsw/jbe@1309 | 2056             }, | 
| bsw/jbe@1309 | 2057 | 
| bsw/jbe@1309 | 2058             cloneRange: function() { | 
| bsw/jbe@1309 | 2059                 assertRangeValid(this); | 
| bsw/jbe@1309 | 2060                 var range = new Range(getRangeDocument(this)); | 
| bsw/jbe@1309 | 2061                 var i = rangeProperties.length, prop; | 
| bsw/jbe@1309 | 2062                 while (i--) { | 
| bsw/jbe@1309 | 2063                     prop = rangeProperties[i]; | 
| bsw/jbe@1309 | 2064                     range[prop] = this[prop]; | 
| bsw/jbe@1309 | 2065                 } | 
| bsw/jbe@1309 | 2066                 return range; | 
| bsw/jbe@1309 | 2067             }, | 
| bsw/jbe@1309 | 2068 | 
| bsw/jbe@1309 | 2069             toString: function() { | 
| bsw/jbe@1309 | 2070                 assertRangeValid(this); | 
| bsw/jbe@1309 | 2071                 var sc = this.startContainer; | 
| bsw/jbe@1309 | 2072                 if (sc === this.endContainer && isCharacterDataNode(sc)) { | 
| bsw/jbe@1309 | 2073                     return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; | 
| bsw/jbe@1309 | 2074                 } else { | 
| bsw/jbe@1309 | 2075                     var textParts = [], iterator = new RangeIterator(this, true); | 
| bsw/jbe@1309 | 2076                     iterateSubtree(iterator, function(node) { | 
| bsw/jbe@1309 | 2077                         // Accept only text or CDATA nodes, not comments | 
| bsw/jbe@1309 | 2078                         if (node.nodeType == 3 || node.nodeType == 4) { | 
| bsw/jbe@1309 | 2079                             textParts.push(node.data); | 
| bsw/jbe@1309 | 2080                         } | 
| bsw/jbe@1309 | 2081                     }); | 
| bsw/jbe@1309 | 2082                     iterator.detach(); | 
| bsw/jbe@1309 | 2083                     return textParts.join(""); | 
| bsw/jbe@1309 | 2084                 } | 
| bsw/jbe@1309 | 2085             }, | 
| bsw/jbe@1309 | 2086 | 
| bsw/jbe@1309 | 2087             // The methods below are all non-standard. The following batch were introduced by Mozilla but have since | 
| bsw/jbe@1309 | 2088             // been removed from Mozilla. | 
| bsw/jbe@1309 | 2089 | 
| bsw/jbe@1309 | 2090             compareNode: function(node) { | 
| bsw/jbe@1309 | 2091                 assertRangeValid(this); | 
| bsw/jbe@1309 | 2092 | 
| bsw/jbe@1309 | 2093                 var parent = node.parentNode; | 
| bsw/jbe@1309 | 2094                 var nodeIndex = getNodeIndex(node); | 
| bsw/jbe@1309 | 2095 | 
| bsw/jbe@1309 | 2096                 if (!parent) { | 
| bsw/jbe@1309 | 2097                     throw new DOMException("NOT_FOUND_ERR"); | 
| bsw/jbe@1309 | 2098                 } | 
| bsw/jbe@1309 | 2099 | 
| bsw/jbe@1309 | 2100                 var startComparison = this.comparePoint(parent, nodeIndex), | 
| bsw/jbe@1309 | 2101                     endComparison = this.comparePoint(parent, nodeIndex + 1); | 
| bsw/jbe@1309 | 2102 | 
| bsw/jbe@1309 | 2103                 if (startComparison < 0) { // Node starts before | 
| bsw/jbe@1309 | 2104                     return (endComparison > 0) ? n_b_a : n_b; | 
| bsw/jbe@1309 | 2105                 } else { | 
| bsw/jbe@1309 | 2106                     return (endComparison > 0) ? n_a : n_i; | 
| bsw/jbe@1309 | 2107                 } | 
| bsw/jbe@1309 | 2108             }, | 
| bsw/jbe@1309 | 2109 | 
| bsw/jbe@1309 | 2110             comparePoint: function(node, offset) { | 
| bsw/jbe@1309 | 2111                 assertRangeValid(this); | 
| bsw/jbe@1309 | 2112                 assertNode(node, "HIERARCHY_REQUEST_ERR"); | 
| bsw/jbe@1309 | 2113                 assertSameDocumentOrFragment(node, this.startContainer); | 
| bsw/jbe@1309 | 2114 | 
| bsw/jbe@1309 | 2115                 if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { | 
| bsw/jbe@1309 | 2116                     return -1; | 
| bsw/jbe@1309 | 2117                 } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { | 
| bsw/jbe@1309 | 2118                     return 1; | 
| bsw/jbe@1309 | 2119                 } | 
| bsw/jbe@1309 | 2120                 return 0; | 
| bsw/jbe@1309 | 2121             }, | 
| bsw/jbe@1309 | 2122 | 
| bsw/jbe@1309 | 2123             createContextualFragment: createContextualFragment, | 
| bsw/jbe@1309 | 2124 | 
| bsw/jbe@1309 | 2125             toHtml: function() { | 
| bsw/jbe@1309 | 2126                 return rangeToHtml(this); | 
| bsw/jbe@1309 | 2127             }, | 
| bsw/jbe@1309 | 2128 | 
| bsw/jbe@1309 | 2129             // touchingIsIntersecting determines whether this method considers a node that borders a range intersects | 
| bsw/jbe@1309 | 2130             // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default) | 
| bsw/jbe@1309 | 2131             intersectsNode: function(node, touchingIsIntersecting) { | 
| bsw/jbe@1309 | 2132                 assertRangeValid(this); | 
| bsw/jbe@1309 | 2133                 if (getRootContainer(node) != getRangeRoot(this)) { | 
| bsw/jbe@1309 | 2134                     return false; | 
| bsw/jbe@1309 | 2135                 } | 
| bsw/jbe@1309 | 2136 | 
| bsw/jbe@1309 | 2137                 var parent = node.parentNode, offset = getNodeIndex(node); | 
| bsw/jbe@1309 | 2138                 if (!parent) { | 
| bsw/jbe@1309 | 2139                     return true; | 
| bsw/jbe@1309 | 2140                 } | 
| bsw/jbe@1309 | 2141 | 
| bsw/jbe@1309 | 2142                 var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset), | 
| bsw/jbe@1309 | 2143                     endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset); | 
| bsw/jbe@1309 | 2144 | 
| bsw/jbe@1309 | 2145                 return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; | 
| bsw/jbe@1309 | 2146             }, | 
| bsw/jbe@1309 | 2147 | 
| bsw/jbe@1309 | 2148             isPointInRange: function(node, offset) { | 
| bsw/jbe@1309 | 2149                 assertRangeValid(this); | 
| bsw/jbe@1309 | 2150                 assertNode(node, "HIERARCHY_REQUEST_ERR"); | 
| bsw/jbe@1309 | 2151                 assertSameDocumentOrFragment(node, this.startContainer); | 
| bsw/jbe@1309 | 2152 | 
| bsw/jbe@1309 | 2153                 return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && | 
| bsw/jbe@1309 | 2154                        (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0); | 
| bsw/jbe@1309 | 2155             }, | 
| bsw/jbe@1309 | 2156 | 
| bsw/jbe@1309 | 2157             // The methods below are non-standard and invented by me. | 
| bsw/jbe@1309 | 2158 | 
| bsw/jbe@1309 | 2159             // Sharing a boundary start-to-end or end-to-start does not count as intersection. | 
| bsw/jbe@1309 | 2160             intersectsRange: function(range) { | 
| bsw/jbe@1309 | 2161                 return rangesIntersect(this, range, false); | 
| bsw/jbe@1309 | 2162             }, | 
| bsw/jbe@1309 | 2163 | 
| bsw/jbe@1309 | 2164             // Sharing a boundary start-to-end or end-to-start does count as intersection. | 
| bsw/jbe@1309 | 2165             intersectsOrTouchesRange: function(range) { | 
| bsw/jbe@1309 | 2166                 return rangesIntersect(this, range, true); | 
| bsw/jbe@1309 | 2167             }, | 
| bsw/jbe@1309 | 2168 | 
| bsw/jbe@1309 | 2169             intersection: function(range) { | 
| bsw/jbe@1309 | 2170                 if (this.intersectsRange(range)) { | 
| bsw/jbe@1309 | 2171                     var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), | 
| bsw/jbe@1309 | 2172                         endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset); | 
| bsw/jbe@1309 | 2173 | 
| bsw/jbe@1309 | 2174                     var intersectionRange = this.cloneRange(); | 
| bsw/jbe@1309 | 2175                     if (startComparison == -1) { | 
| bsw/jbe@1309 | 2176                         intersectionRange.setStart(range.startContainer, range.startOffset); | 
| bsw/jbe@1309 | 2177                     } | 
| bsw/jbe@1309 | 2178                     if (endComparison == 1) { | 
| bsw/jbe@1309 | 2179                         intersectionRange.setEnd(range.endContainer, range.endOffset); | 
| bsw/jbe@1309 | 2180                     } | 
| bsw/jbe@1309 | 2181                     return intersectionRange; | 
| bsw/jbe@1309 | 2182                 } | 
| bsw/jbe@1309 | 2183                 return null; | 
| bsw/jbe@1309 | 2184             }, | 
| bsw/jbe@1309 | 2185 | 
| bsw/jbe@1309 | 2186             union: function(range) { | 
| bsw/jbe@1309 | 2187                 if (this.intersectsOrTouchesRange(range)) { | 
| bsw/jbe@1309 | 2188                     var unionRange = this.cloneRange(); | 
| bsw/jbe@1309 | 2189                     if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { | 
| bsw/jbe@1309 | 2190                         unionRange.setStart(range.startContainer, range.startOffset); | 
| bsw/jbe@1309 | 2191                     } | 
| bsw/jbe@1309 | 2192                     if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { | 
| bsw/jbe@1309 | 2193                         unionRange.setEnd(range.endContainer, range.endOffset); | 
| bsw/jbe@1309 | 2194                     } | 
| bsw/jbe@1309 | 2195                     return unionRange; | 
| bsw/jbe@1309 | 2196                 } else { | 
| bsw/jbe@1309 | 2197                     throw new DOMException("Ranges do not intersect"); | 
| bsw/jbe@1309 | 2198                 } | 
| bsw/jbe@1309 | 2199             }, | 
| bsw/jbe@1309 | 2200 | 
| bsw/jbe@1309 | 2201             containsNode: function(node, allowPartial) { | 
| bsw/jbe@1309 | 2202                 if (allowPartial) { | 
| bsw/jbe@1309 | 2203                     return this.intersectsNode(node, false); | 
| bsw/jbe@1309 | 2204                 } else { | 
| bsw/jbe@1309 | 2205                     return this.compareNode(node) == n_i; | 
| bsw/jbe@1309 | 2206                 } | 
| bsw/jbe@1309 | 2207             }, | 
| bsw/jbe@1309 | 2208 | 
| bsw/jbe@1309 | 2209             containsNodeContents: function(node) { | 
| bsw/jbe@1309 | 2210                 return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0; | 
| bsw/jbe@1309 | 2211             }, | 
| bsw/jbe@1309 | 2212 | 
| bsw/jbe@1309 | 2213             containsRange: function(range) { | 
| bsw/jbe@1309 | 2214                 var intersection = this.intersection(range); | 
| bsw/jbe@1309 | 2215                 return intersection !== null && range.equals(intersection); | 
| bsw/jbe@1309 | 2216             }, | 
| bsw/jbe@1309 | 2217 | 
| bsw/jbe@1309 | 2218             containsNodeText: function(node) { | 
| bsw/jbe@1309 | 2219                 var nodeRange = this.cloneRange(); | 
| bsw/jbe@1309 | 2220                 nodeRange.selectNode(node); | 
| bsw/jbe@1309 | 2221                 var textNodes = nodeRange.getNodes([3]); | 
| bsw/jbe@1309 | 2222                 if (textNodes.length > 0) { | 
| bsw/jbe@1309 | 2223                     nodeRange.setStart(textNodes[0], 0); | 
| bsw/jbe@1309 | 2224                     var lastTextNode = textNodes.pop(); | 
| bsw/jbe@1309 | 2225                     nodeRange.setEnd(lastTextNode, lastTextNode.length); | 
| bsw/jbe@1309 | 2226                     return this.containsRange(nodeRange); | 
| bsw/jbe@1309 | 2227                 } else { | 
| bsw/jbe@1309 | 2228                     return this.containsNodeContents(node); | 
| bsw/jbe@1309 | 2229                 } | 
| bsw/jbe@1309 | 2230             }, | 
| bsw/jbe@1309 | 2231 | 
| bsw/jbe@1309 | 2232             getNodes: function(nodeTypes, filter) { | 
| bsw/jbe@1309 | 2233                 assertRangeValid(this); | 
| bsw/jbe@1309 | 2234                 return getNodesInRange(this, nodeTypes, filter); | 
| bsw/jbe@1309 | 2235             }, | 
| bsw/jbe@1309 | 2236 | 
| bsw/jbe@1309 | 2237             getDocument: function() { | 
| bsw/jbe@1309 | 2238                 return getRangeDocument(this); | 
| bsw/jbe@1309 | 2239             }, | 
| bsw/jbe@1309 | 2240 | 
| bsw/jbe@1309 | 2241             collapseBefore: function(node) { | 
| bsw/jbe@1309 | 2242                 this.setEndBefore(node); | 
| bsw/jbe@1309 | 2243                 this.collapse(false); | 
| bsw/jbe@1309 | 2244             }, | 
| bsw/jbe@1309 | 2245 | 
| bsw/jbe@1309 | 2246             collapseAfter: function(node) { | 
| bsw/jbe@1309 | 2247                 this.setStartAfter(node); | 
| bsw/jbe@1309 | 2248                 this.collapse(true); | 
| bsw/jbe@1309 | 2249             }, | 
| bsw/jbe@1309 | 2250 | 
| bsw/jbe@1309 | 2251             getBookmark: function(containerNode) { | 
| bsw/jbe@1309 | 2252                 var doc = getRangeDocument(this); | 
| bsw/jbe@1309 | 2253                 var preSelectionRange = api.createRange(doc); | 
| bsw/jbe@1309 | 2254                 containerNode = containerNode || dom.getBody(doc); | 
| bsw/jbe@1309 | 2255                 preSelectionRange.selectNodeContents(containerNode); | 
| bsw/jbe@1309 | 2256                 var range = this.intersection(preSelectionRange); | 
| bsw/jbe@1309 | 2257                 var start = 0, end = 0; | 
| bsw/jbe@1309 | 2258                 if (range) { | 
| bsw/jbe@1309 | 2259                     preSelectionRange.setEnd(range.startContainer, range.startOffset); | 
| bsw/jbe@1309 | 2260                     start = preSelectionRange.toString().length; | 
| bsw/jbe@1309 | 2261                     end = start + range.toString().length; | 
| bsw/jbe@1309 | 2262                 } | 
| bsw/jbe@1309 | 2263 | 
| bsw/jbe@1309 | 2264                 return { | 
| bsw/jbe@1309 | 2265                     start: start, | 
| bsw/jbe@1309 | 2266                     end: end, | 
| bsw/jbe@1309 | 2267                     containerNode: containerNode | 
| bsw/jbe@1309 | 2268                 }; | 
| bsw/jbe@1309 | 2269             }, | 
| bsw/jbe@1309 | 2270 | 
| bsw/jbe@1309 | 2271             moveToBookmark: function(bookmark) { | 
| bsw/jbe@1309 | 2272                 var containerNode = bookmark.containerNode; | 
| bsw/jbe@1309 | 2273                 var charIndex = 0; | 
| bsw/jbe@1309 | 2274                 this.setStart(containerNode, 0); | 
| bsw/jbe@1309 | 2275                 this.collapse(true); | 
| bsw/jbe@1309 | 2276                 var nodeStack = [containerNode], node, foundStart = false, stop = false; | 
| bsw/jbe@1309 | 2277                 var nextCharIndex, i, childNodes; | 
| bsw/jbe@1309 | 2278 | 
| bsw/jbe@1309 | 2279                 while (!stop && (node = nodeStack.pop())) { | 
| bsw/jbe@1309 | 2280                     if (node.nodeType == 3) { | 
| bsw/jbe@1309 | 2281                         nextCharIndex = charIndex + node.length; | 
| bsw/jbe@1309 | 2282                         if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) { | 
| bsw/jbe@1309 | 2283                             this.setStart(node, bookmark.start - charIndex); | 
| bsw/jbe@1309 | 2284                             foundStart = true; | 
| bsw/jbe@1309 | 2285                         } | 
| bsw/jbe@1309 | 2286                         if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) { | 
| bsw/jbe@1309 | 2287                             this.setEnd(node, bookmark.end - charIndex); | 
| bsw/jbe@1309 | 2288                             stop = true; | 
| bsw/jbe@1309 | 2289                         } | 
| bsw/jbe@1309 | 2290                         charIndex = nextCharIndex; | 
| bsw/jbe@1309 | 2291                     } else { | 
| bsw/jbe@1309 | 2292                         childNodes = node.childNodes; | 
| bsw/jbe@1309 | 2293                         i = childNodes.length; | 
| bsw/jbe@1309 | 2294                         while (i--) { | 
| bsw/jbe@1309 | 2295                             nodeStack.push(childNodes[i]); | 
| bsw/jbe@1309 | 2296                         } | 
| bsw/jbe@1309 | 2297                     } | 
| bsw/jbe@1309 | 2298                 } | 
| bsw/jbe@1309 | 2299             }, | 
| bsw/jbe@1309 | 2300 | 
| bsw/jbe@1309 | 2301             getName: function() { | 
| bsw/jbe@1309 | 2302                 return "DomRange"; | 
| bsw/jbe@1309 | 2303             }, | 
| bsw/jbe@1309 | 2304 | 
| bsw/jbe@1309 | 2305             equals: function(range) { | 
| bsw/jbe@1309 | 2306                 return Range.rangesEqual(this, range); | 
| bsw/jbe@1309 | 2307             }, | 
| bsw/jbe@1309 | 2308 | 
| bsw/jbe@1309 | 2309             isValid: function() { | 
| bsw/jbe@1309 | 2310                 return isRangeValid(this); | 
| bsw/jbe@1309 | 2311             }, | 
| bsw/jbe@1309 | 2312 | 
| bsw/jbe@1309 | 2313             inspect: function() { | 
| bsw/jbe@1309 | 2314                 return inspect(this); | 
| bsw/jbe@1309 | 2315             }, | 
| bsw/jbe@1309 | 2316 | 
| bsw/jbe@1309 | 2317             detach: function() { | 
| bsw/jbe@1309 | 2318                 // In DOM4, detach() is now a no-op. | 
| bsw/jbe@1309 | 2319             } | 
| bsw/jbe@1309 | 2320         }); | 
| bsw/jbe@1309 | 2321 | 
| bsw/jbe@1309 | 2322         function copyComparisonConstantsToObject(obj) { | 
| bsw/jbe@1309 | 2323             obj.START_TO_START = s2s; | 
| bsw/jbe@1309 | 2324             obj.START_TO_END = s2e; | 
| bsw/jbe@1309 | 2325             obj.END_TO_END = e2e; | 
| bsw/jbe@1309 | 2326             obj.END_TO_START = e2s; | 
| bsw/jbe@1309 | 2327 | 
| bsw/jbe@1309 | 2328             obj.NODE_BEFORE = n_b; | 
| bsw/jbe@1309 | 2329             obj.NODE_AFTER = n_a; | 
| bsw/jbe@1309 | 2330             obj.NODE_BEFORE_AND_AFTER = n_b_a; | 
| bsw/jbe@1309 | 2331             obj.NODE_INSIDE = n_i; | 
| bsw/jbe@1309 | 2332         } | 
| bsw/jbe@1309 | 2333 | 
| bsw/jbe@1309 | 2334         function copyComparisonConstants(constructor) { | 
| bsw/jbe@1309 | 2335             copyComparisonConstantsToObject(constructor); | 
| bsw/jbe@1309 | 2336             copyComparisonConstantsToObject(constructor.prototype); | 
| bsw/jbe@1309 | 2337         } | 
| bsw/jbe@1309 | 2338 | 
| bsw/jbe@1309 | 2339         function createRangeContentRemover(remover, boundaryUpdater) { | 
| bsw/jbe@1309 | 2340             return function() { | 
| bsw/jbe@1309 | 2341                 assertRangeValid(this); | 
| bsw/jbe@1309 | 2342 | 
| bsw/jbe@1309 | 2343                 var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer; | 
| bsw/jbe@1309 | 2344 | 
| bsw/jbe@1309 | 2345                 var iterator = new RangeIterator(this, true); | 
| bsw/jbe@1309 | 2346 | 
| bsw/jbe@1309 | 2347                 // Work out where to position the range after content removal | 
| bsw/jbe@1309 | 2348                 var node, boundary; | 
| bsw/jbe@1309 | 2349                 if (sc !== root) { | 
| bsw/jbe@1309 | 2350                     node = getClosestAncestorIn(sc, root, true); | 
| bsw/jbe@1309 | 2351                     boundary = getBoundaryAfterNode(node); | 
| bsw/jbe@1309 | 2352                     sc = boundary.node; | 
| bsw/jbe@1309 | 2353                     so = boundary.offset; | 
| bsw/jbe@1309 | 2354                 } | 
| bsw/jbe@1309 | 2355 | 
| bsw/jbe@1309 | 2356                 // Check none of the range is read-only | 
| bsw/jbe@1309 | 2357                 iterateSubtree(iterator, assertNodeNotReadOnly); | 
| bsw/jbe@1309 | 2358 | 
| bsw/jbe@1309 | 2359                 iterator.reset(); | 
| bsw/jbe@1309 | 2360 | 
| bsw/jbe@1309 | 2361                 // Remove the content | 
| bsw/jbe@1309 | 2362                 var returnValue = remover(iterator); | 
| bsw/jbe@1309 | 2363                 iterator.detach(); | 
| bsw/jbe@1309 | 2364 | 
| bsw/jbe@1309 | 2365                 // Move to the new position | 
| bsw/jbe@1309 | 2366                 boundaryUpdater(this, sc, so, sc, so); | 
| bsw/jbe@1309 | 2367 | 
| bsw/jbe@1309 | 2368                 return returnValue; | 
| bsw/jbe@1309 | 2369             }; | 
| bsw/jbe@1309 | 2370         } | 
| bsw/jbe@1309 | 2371 | 
| bsw/jbe@1309 | 2372         function createPrototypeRange(constructor, boundaryUpdater) { | 
| bsw/jbe@1309 | 2373             function createBeforeAfterNodeSetter(isBefore, isStart) { | 
| bsw/jbe@1309 | 2374                 return function(node) { | 
| bsw/jbe@1309 | 2375                     assertValidNodeType(node, beforeAfterNodeTypes); | 
| bsw/jbe@1309 | 2376                     assertValidNodeType(getRootContainer(node), rootContainerNodeTypes); | 
| bsw/jbe@1309 | 2377 | 
| bsw/jbe@1309 | 2378                     var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node); | 
| bsw/jbe@1309 | 2379                     (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset); | 
| bsw/jbe@1309 | 2380                 }; | 
| bsw/jbe@1309 | 2381             } | 
| bsw/jbe@1309 | 2382 | 
| bsw/jbe@1309 | 2383             function setRangeStart(range, node, offset) { | 
| bsw/jbe@1309 | 2384                 var ec = range.endContainer, eo = range.endOffset; | 
| bsw/jbe@1309 | 2385                 if (node !== range.startContainer || offset !== range.startOffset) { | 
| bsw/jbe@1309 | 2386                     // Check the root containers of the range and the new boundary, and also check whether the new boundary | 
| bsw/jbe@1309 | 2387                     // is after the current end. In either case, collapse the range to the new position | 
| bsw/jbe@1309 | 2388                     if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) { | 
| bsw/jbe@1309 | 2389                         ec = node; | 
| bsw/jbe@1309 | 2390                         eo = offset; | 
| bsw/jbe@1309 | 2391                     } | 
| bsw/jbe@1309 | 2392                     boundaryUpdater(range, node, offset, ec, eo); | 
| bsw/jbe@1309 | 2393                 } | 
| bsw/jbe@1309 | 2394             } | 
| bsw/jbe@1309 | 2395 | 
| bsw/jbe@1309 | 2396             function setRangeEnd(range, node, offset) { | 
| bsw/jbe@1309 | 2397                 var sc = range.startContainer, so = range.startOffset; | 
| bsw/jbe@1309 | 2398                 if (node !== range.endContainer || offset !== range.endOffset) { | 
| bsw/jbe@1309 | 2399                     // Check the root containers of the range and the new boundary, and also check whether the new boundary | 
| bsw/jbe@1309 | 2400                     // is after the current end. In either case, collapse the range to the new position | 
| bsw/jbe@1309 | 2401                     if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) { | 
| bsw/jbe@1309 | 2402                         sc = node; | 
| bsw/jbe@1309 | 2403                         so = offset; | 
| bsw/jbe@1309 | 2404                     } | 
| bsw/jbe@1309 | 2405                     boundaryUpdater(range, sc, so, node, offset); | 
| bsw/jbe@1309 | 2406                 } | 
| bsw/jbe@1309 | 2407             } | 
| bsw/jbe@1309 | 2408 | 
| bsw/jbe@1309 | 2409             // Set up inheritance | 
| bsw/jbe@1309 | 2410             var F = function() {}; | 
| bsw/jbe@1309 | 2411             F.prototype = api.rangePrototype; | 
| bsw/jbe@1309 | 2412             constructor.prototype = new F(); | 
| bsw/jbe@1309 | 2413 | 
| bsw/jbe@1309 | 2414             util.extend(constructor.prototype, { | 
| bsw/jbe@1309 | 2415                 setStart: function(node, offset) { | 
| bsw/jbe@1309 | 2416                     assertNoDocTypeNotationEntityAncestor(node, true); | 
| bsw/jbe@1309 | 2417                     assertValidOffset(node, offset); | 
| bsw/jbe@1309 | 2418 | 
| bsw/jbe@1309 | 2419                     setRangeStart(this, node, offset); | 
| bsw/jbe@1309 | 2420                 }, | 
| bsw/jbe@1309 | 2421 | 
| bsw/jbe@1309 | 2422                 setEnd: function(node, offset) { | 
| bsw/jbe@1309 | 2423                     assertNoDocTypeNotationEntityAncestor(node, true); | 
| bsw/jbe@1309 | 2424                     assertValidOffset(node, offset); | 
| bsw/jbe@1309 | 2425 | 
| bsw/jbe@1309 | 2426                     setRangeEnd(this, node, offset); | 
| bsw/jbe@1309 | 2427                 }, | 
| bsw/jbe@1309 | 2428 | 
| bsw/jbe@1309 | 2429                 /** | 
| bsw/jbe@1309 | 2430                  * Convenience method to set a range's start and end boundaries. Overloaded as follows: | 
| bsw/jbe@1309 | 2431                  * - Two parameters (node, offset) creates a collapsed range at that position | 
| bsw/jbe@1309 | 2432                  * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at | 
| bsw/jbe@1309 | 2433                  *   startOffset and ending at endOffset | 
| bsw/jbe@1309 | 2434                  * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in | 
| bsw/jbe@1309 | 2435                  *   startNode and ending at endOffset in endNode | 
| bsw/jbe@1309 | 2436                  */ | 
| bsw/jbe@1309 | 2437                 setStartAndEnd: function() { | 
| bsw/jbe@1309 | 2438                     var args = arguments; | 
| bsw/jbe@1309 | 2439                     var sc = args[0], so = args[1], ec = sc, eo = so; | 
| bsw/jbe@1309 | 2440 | 
| bsw/jbe@1309 | 2441                     switch (args.length) { | 
| bsw/jbe@1309 | 2442                         case 3: | 
| bsw/jbe@1309 | 2443                             eo = args[2]; | 
| bsw/jbe@1309 | 2444                             break; | 
| bsw/jbe@1309 | 2445                         case 4: | 
| bsw/jbe@1309 | 2446                             ec = args[2]; | 
| bsw/jbe@1309 | 2447                             eo = args[3]; | 
| bsw/jbe@1309 | 2448                             break; | 
| bsw/jbe@1309 | 2449                     } | 
| bsw/jbe@1309 | 2450 | 
| bsw/jbe@1309 | 2451                     boundaryUpdater(this, sc, so, ec, eo); | 
| bsw/jbe@1309 | 2452                 }, | 
| bsw/jbe@1309 | 2453 | 
| bsw/jbe@1309 | 2454                 setBoundary: function(node, offset, isStart) { | 
| bsw/jbe@1309 | 2455                     this["set" + (isStart ? "Start" : "End")](node, offset); | 
| bsw/jbe@1309 | 2456                 }, | 
| bsw/jbe@1309 | 2457 | 
| bsw/jbe@1309 | 2458                 setStartBefore: createBeforeAfterNodeSetter(true, true), | 
| bsw/jbe@1309 | 2459                 setStartAfter: createBeforeAfterNodeSetter(false, true), | 
| bsw/jbe@1309 | 2460                 setEndBefore: createBeforeAfterNodeSetter(true, false), | 
| bsw/jbe@1309 | 2461                 setEndAfter: createBeforeAfterNodeSetter(false, false), | 
| bsw/jbe@1309 | 2462 | 
| bsw/jbe@1309 | 2463                 collapse: function(isStart) { | 
| bsw/jbe@1309 | 2464                     assertRangeValid(this); | 
| bsw/jbe@1309 | 2465                     if (isStart) { | 
| bsw/jbe@1309 | 2466                         boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset); | 
| bsw/jbe@1309 | 2467                     } else { | 
| bsw/jbe@1309 | 2468                         boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset); | 
| bsw/jbe@1309 | 2469                     } | 
| bsw/jbe@1309 | 2470                 }, | 
| bsw/jbe@1309 | 2471 | 
| bsw/jbe@1309 | 2472                 selectNodeContents: function(node) { | 
| bsw/jbe@1309 | 2473                     assertNoDocTypeNotationEntityAncestor(node, true); | 
| bsw/jbe@1309 | 2474 | 
| bsw/jbe@1309 | 2475                     boundaryUpdater(this, node, 0, node, getNodeLength(node)); | 
| bsw/jbe@1309 | 2476                 }, | 
| bsw/jbe@1309 | 2477 | 
| bsw/jbe@1309 | 2478                 selectNode: function(node) { | 
| bsw/jbe@1309 | 2479                     assertNoDocTypeNotationEntityAncestor(node, false); | 
| bsw/jbe@1309 | 2480                     assertValidNodeType(node, beforeAfterNodeTypes); | 
| bsw/jbe@1309 | 2481 | 
| bsw/jbe@1309 | 2482                     var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node); | 
| bsw/jbe@1309 | 2483                     boundaryUpdater(this, start.node, start.offset, end.node, end.offset); | 
| bsw/jbe@1309 | 2484                 }, | 
| bsw/jbe@1309 | 2485 | 
| bsw/jbe@1309 | 2486                 extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater), | 
| bsw/jbe@1309 | 2487 | 
| bsw/jbe@1309 | 2488                 deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater), | 
| bsw/jbe@1309 | 2489 | 
| bsw/jbe@1309 | 2490                 canSurroundContents: function() { | 
| bsw/jbe@1309 | 2491                     assertRangeValid(this); | 
| bsw/jbe@1309 | 2492                     assertNodeNotReadOnly(this.startContainer); | 
| bsw/jbe@1309 | 2493                     assertNodeNotReadOnly(this.endContainer); | 
| bsw/jbe@1309 | 2494 | 
| bsw/jbe@1309 | 2495                     // Check if the contents can be surrounded. Specifically, this means whether the range partially selects | 
| bsw/jbe@1309 | 2496                     // no non-text nodes. | 
| bsw/jbe@1309 | 2497                     var iterator = new RangeIterator(this, true); | 
| bsw/jbe@1309 | 2498                     var boundariesInvalid = (iterator._first && isNonTextPartiallySelected(iterator._first, this) || | 
| bsw/jbe@1309 | 2499                             (iterator._last && isNonTextPartiallySelected(iterator._last, this))); | 
| bsw/jbe@1309 | 2500                     iterator.detach(); | 
| bsw/jbe@1309 | 2501                     return !boundariesInvalid; | 
| bsw/jbe@1309 | 2502                 }, | 
| bsw/jbe@1309 | 2503 | 
| bsw/jbe@1309 | 2504                 splitBoundaries: function() { | 
| bsw/jbe@1309 | 2505                     splitRangeBoundaries(this); | 
| bsw/jbe@1309 | 2506                 }, | 
| bsw/jbe@1309 | 2507 | 
| bsw/jbe@1309 | 2508                 splitBoundariesPreservingPositions: function(positionsToPreserve) { | 
| bsw/jbe@1309 | 2509                     splitRangeBoundaries(this, positionsToPreserve); | 
| bsw/jbe@1309 | 2510                 }, | 
| bsw/jbe@1309 | 2511 | 
| bsw/jbe@1309 | 2512                 normalizeBoundaries: function() { | 
| bsw/jbe@1309 | 2513                     assertRangeValid(this); | 
| bsw/jbe@1309 | 2514 | 
| bsw/jbe@1309 | 2515                     var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; | 
| bsw/jbe@1309 | 2516 | 
| bsw/jbe@1309 | 2517                     var mergeForward = function(node) { | 
| bsw/jbe@1309 | 2518                         var sibling = node.nextSibling; | 
| bsw/jbe@1309 | 2519                         if (sibling && sibling.nodeType == node.nodeType) { | 
| bsw/jbe@1309 | 2520                             ec = node; | 
| bsw/jbe@1309 | 2521                             eo = node.length; | 
| bsw/jbe@1309 | 2522                             node.appendData(sibling.data); | 
| bsw/jbe@1309 | 2523                             removeNode(sibling); | 
| bsw/jbe@1309 | 2524                         } | 
| bsw/jbe@1309 | 2525                     }; | 
| bsw/jbe@1309 | 2526 | 
| bsw/jbe@1309 | 2527                     var mergeBackward = function(node) { | 
| bsw/jbe@1309 | 2528                         var sibling = node.previousSibling; | 
| bsw/jbe@1309 | 2529                         if (sibling && sibling.nodeType == node.nodeType) { | 
| bsw/jbe@1309 | 2530                             sc = node; | 
| bsw/jbe@1309 | 2531                             var nodeLength = node.length; | 
| bsw/jbe@1309 | 2532                             so = sibling.length; | 
| bsw/jbe@1309 | 2533                             node.insertData(0, sibling.data); | 
| bsw/jbe@1309 | 2534                             removeNode(sibling); | 
| bsw/jbe@1309 | 2535                             if (sc == ec) { | 
| bsw/jbe@1309 | 2536                                 eo += so; | 
| bsw/jbe@1309 | 2537                                 ec = sc; | 
| bsw/jbe@1309 | 2538                             } else if (ec == node.parentNode) { | 
| bsw/jbe@1309 | 2539                                 var nodeIndex = getNodeIndex(node); | 
| bsw/jbe@1309 | 2540                                 if (eo == nodeIndex) { | 
| bsw/jbe@1309 | 2541                                     ec = node; | 
| bsw/jbe@1309 | 2542                                     eo = nodeLength; | 
| bsw/jbe@1309 | 2543                                 } else if (eo > nodeIndex) { | 
| bsw/jbe@1309 | 2544                                     eo--; | 
| bsw/jbe@1309 | 2545                                 } | 
| bsw/jbe@1309 | 2546                             } | 
| bsw/jbe@1309 | 2547                         } | 
| bsw/jbe@1309 | 2548                     }; | 
| bsw/jbe@1309 | 2549 | 
| bsw/jbe@1309 | 2550                     var normalizeStart = true; | 
| bsw/jbe@1309 | 2551                     var sibling; | 
| bsw/jbe@1309 | 2552 | 
| bsw/jbe@1309 | 2553                     if (isCharacterDataNode(ec)) { | 
| bsw/jbe@1309 | 2554                         if (eo == ec.length) { | 
| bsw/jbe@1309 | 2555                             mergeForward(ec); | 
| bsw/jbe@1309 | 2556                         } else if (eo == 0) { | 
| bsw/jbe@1309 | 2557                             sibling = ec.previousSibling; | 
| bsw/jbe@1309 | 2558                             if (sibling && sibling.nodeType == ec.nodeType) { | 
| bsw/jbe@1309 | 2559                                 eo = sibling.length; | 
| bsw/jbe@1309 | 2560                                 if (sc == ec) { | 
| bsw/jbe@1309 | 2561                                     normalizeStart = false; | 
| bsw/jbe@1309 | 2562                                 } | 
| bsw/jbe@1309 | 2563                                 sibling.appendData(ec.data); | 
| bsw/jbe@1309 | 2564                                 removeNode(ec); | 
| bsw/jbe@1309 | 2565                                 ec = sibling; | 
| bsw/jbe@1309 | 2566                             } | 
| bsw/jbe@1309 | 2567                         } | 
| bsw/jbe@1309 | 2568                     } else { | 
| bsw/jbe@1309 | 2569                         if (eo > 0) { | 
| bsw/jbe@1309 | 2570                             var endNode = ec.childNodes[eo - 1]; | 
| bsw/jbe@1309 | 2571                             if (endNode && isCharacterDataNode(endNode)) { | 
| bsw/jbe@1309 | 2572                                 mergeForward(endNode); | 
| bsw/jbe@1309 | 2573                             } | 
| bsw/jbe@1309 | 2574                         } | 
| bsw/jbe@1309 | 2575                         normalizeStart = !this.collapsed; | 
| bsw/jbe@1309 | 2576                     } | 
| bsw/jbe@1309 | 2577 | 
| bsw/jbe@1309 | 2578                     if (normalizeStart) { | 
| bsw/jbe@1309 | 2579                         if (isCharacterDataNode(sc)) { | 
| bsw/jbe@1309 | 2580                             if (so == 0) { | 
| bsw/jbe@1309 | 2581                                 mergeBackward(sc); | 
| bsw/jbe@1309 | 2582                             } else if (so == sc.length) { | 
| bsw/jbe@1309 | 2583                                 sibling = sc.nextSibling; | 
| bsw/jbe@1309 | 2584                                 if (sibling && sibling.nodeType == sc.nodeType) { | 
| bsw/jbe@1309 | 2585                                     if (ec == sibling) { | 
| bsw/jbe@1309 | 2586                                         ec = sc; | 
| bsw/jbe@1309 | 2587                                         eo += sc.length; | 
| bsw/jbe@1309 | 2588                                     } | 
| bsw/jbe@1309 | 2589                                     sc.appendData(sibling.data); | 
| bsw/jbe@1309 | 2590                                     removeNode(sibling); | 
| bsw/jbe@1309 | 2591                                 } | 
| bsw/jbe@1309 | 2592                             } | 
| bsw/jbe@1309 | 2593                         } else { | 
| bsw/jbe@1309 | 2594                             if (so < sc.childNodes.length) { | 
| bsw/jbe@1309 | 2595                                 var startNode = sc.childNodes[so]; | 
| bsw/jbe@1309 | 2596                                 if (startNode && isCharacterDataNode(startNode)) { | 
| bsw/jbe@1309 | 2597                                     mergeBackward(startNode); | 
| bsw/jbe@1309 | 2598                                 } | 
| bsw/jbe@1309 | 2599                             } | 
| bsw/jbe@1309 | 2600                         } | 
| bsw/jbe@1309 | 2601                     } else { | 
| bsw/jbe@1309 | 2602                         sc = ec; | 
| bsw/jbe@1309 | 2603                         so = eo; | 
| bsw/jbe@1309 | 2604                     } | 
| bsw/jbe@1309 | 2605 | 
| bsw/jbe@1309 | 2606                     boundaryUpdater(this, sc, so, ec, eo); | 
| bsw/jbe@1309 | 2607                 }, | 
| bsw/jbe@1309 | 2608 | 
| bsw/jbe@1309 | 2609                 collapseToPoint: function(node, offset) { | 
| bsw/jbe@1309 | 2610                     assertNoDocTypeNotationEntityAncestor(node, true); | 
| bsw/jbe@1309 | 2611                     assertValidOffset(node, offset); | 
| bsw/jbe@1309 | 2612                     this.setStartAndEnd(node, offset); | 
| bsw/jbe@1309 | 2613                 } | 
| bsw/jbe@1309 | 2614             }); | 
| bsw/jbe@1309 | 2615 | 
| bsw/jbe@1309 | 2616             copyComparisonConstants(constructor); | 
| bsw/jbe@1309 | 2617         } | 
| bsw/jbe@1309 | 2618 | 
| bsw/jbe@1309 | 2619         /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 2620 | 
| bsw/jbe@1309 | 2621         // Updates commonAncestorContainer and collapsed after boundary change | 
| bsw/jbe@1309 | 2622         function updateCollapsedAndCommonAncestor(range) { | 
| bsw/jbe@1309 | 2623             range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); | 
| bsw/jbe@1309 | 2624             range.commonAncestorContainer = range.collapsed ? | 
| bsw/jbe@1309 | 2625                 range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer); | 
| bsw/jbe@1309 | 2626         } | 
| bsw/jbe@1309 | 2627 | 
| bsw/jbe@1309 | 2628         function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) { | 
| bsw/jbe@1309 | 2629             range.startContainer = startContainer; | 
| bsw/jbe@1309 | 2630             range.startOffset = startOffset; | 
| bsw/jbe@1309 | 2631             range.endContainer = endContainer; | 
| bsw/jbe@1309 | 2632             range.endOffset = endOffset; | 
| bsw/jbe@1309 | 2633             range.document = dom.getDocument(startContainer); | 
| bsw/jbe@1309 | 2634 | 
| bsw/jbe@1309 | 2635             updateCollapsedAndCommonAncestor(range); | 
| bsw/jbe@1309 | 2636         } | 
| bsw/jbe@1309 | 2637 | 
| bsw/jbe@1309 | 2638         function Range(doc) { | 
| bsw/jbe@1309 | 2639             this.startContainer = doc; | 
| bsw/jbe@1309 | 2640             this.startOffset = 0; | 
| bsw/jbe@1309 | 2641             this.endContainer = doc; | 
| bsw/jbe@1309 | 2642             this.endOffset = 0; | 
| bsw/jbe@1309 | 2643             this.document = doc; | 
| bsw/jbe@1309 | 2644             updateCollapsedAndCommonAncestor(this); | 
| bsw/jbe@1309 | 2645         } | 
| bsw/jbe@1309 | 2646 | 
| bsw/jbe@1309 | 2647         createPrototypeRange(Range, updateBoundaries); | 
| bsw/jbe@1309 | 2648 | 
| bsw/jbe@1309 | 2649         util.extend(Range, { | 
| bsw/jbe@1309 | 2650             rangeProperties: rangeProperties, | 
| bsw/jbe@1309 | 2651             RangeIterator: RangeIterator, | 
| bsw/jbe@1309 | 2652             copyComparisonConstants: copyComparisonConstants, | 
| bsw/jbe@1309 | 2653             createPrototypeRange: createPrototypeRange, | 
| bsw/jbe@1309 | 2654             inspect: inspect, | 
| bsw/jbe@1309 | 2655             toHtml: rangeToHtml, | 
| bsw/jbe@1309 | 2656             getRangeDocument: getRangeDocument, | 
| bsw/jbe@1309 | 2657             rangesEqual: function(r1, r2) { | 
| bsw/jbe@1309 | 2658                 return r1.startContainer === r2.startContainer && | 
| bsw/jbe@1309 | 2659                     r1.startOffset === r2.startOffset && | 
| bsw/jbe@1309 | 2660                     r1.endContainer === r2.endContainer && | 
| bsw/jbe@1309 | 2661                     r1.endOffset === r2.endOffset; | 
| bsw/jbe@1309 | 2662             } | 
| bsw/jbe@1309 | 2663         }); | 
| bsw/jbe@1309 | 2664 | 
| bsw/jbe@1309 | 2665         api.DomRange = Range; | 
| bsw/jbe@1309 | 2666     }); | 
| bsw/jbe@1309 | 2667 | 
| bsw/jbe@1309 | 2668     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 2669 | 
| bsw/jbe@1309 | 2670     // Wrappers for the browser's native DOM Range and/or TextRange implementation | 
| bsw/jbe@1309 | 2671     api.createCoreModule("WrappedRange", ["DomRange"], function(api, module) { | 
| bsw/jbe@1309 | 2672         var WrappedRange, WrappedTextRange; | 
| bsw/jbe@1309 | 2673         var dom = api.dom; | 
| bsw/jbe@1309 | 2674         var util = api.util; | 
| bsw/jbe@1309 | 2675         var DomPosition = dom.DomPosition; | 
| bsw/jbe@1309 | 2676         var DomRange = api.DomRange; | 
| bsw/jbe@1309 | 2677         var getBody = dom.getBody; | 
| bsw/jbe@1309 | 2678         var getContentDocument = dom.getContentDocument; | 
| bsw/jbe@1309 | 2679         var isCharacterDataNode = dom.isCharacterDataNode; | 
| bsw/jbe@1309 | 2680 | 
| bsw/jbe@1309 | 2681 | 
| bsw/jbe@1309 | 2682         /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 2683 | 
| bsw/jbe@1309 | 2684         if (api.features.implementsDomRange) { | 
| bsw/jbe@1309 | 2685             // This is a wrapper around the browser's native DOM Range. It has two aims: | 
| bsw/jbe@1309 | 2686             // - Provide workarounds for specific browser bugs | 
| bsw/jbe@1309 | 2687             // - provide convenient extensions, which are inherited from Rangy's DomRange | 
| bsw/jbe@1309 | 2688 | 
| bsw/jbe@1309 | 2689             (function() { | 
| bsw/jbe@1309 | 2690                 var rangeProto; | 
| bsw/jbe@1309 | 2691                 var rangeProperties = DomRange.rangeProperties; | 
| bsw/jbe@1309 | 2692 | 
| bsw/jbe@1309 | 2693                 function updateRangeProperties(range) { | 
| bsw/jbe@1309 | 2694                     var i = rangeProperties.length, prop; | 
| bsw/jbe@1309 | 2695                     while (i--) { | 
| bsw/jbe@1309 | 2696                         prop = rangeProperties[i]; | 
| bsw/jbe@1309 | 2697                         range[prop] = range.nativeRange[prop]; | 
| bsw/jbe@1309 | 2698                     } | 
| bsw/jbe@1309 | 2699                     // Fix for broken collapsed property in IE 9. | 
| bsw/jbe@1309 | 2700                     range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); | 
| bsw/jbe@1309 | 2701                 } | 
| bsw/jbe@1309 | 2702 | 
| bsw/jbe@1309 | 2703                 function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) { | 
| bsw/jbe@1309 | 2704                     var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset); | 
| bsw/jbe@1309 | 2705                     var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset); | 
| bsw/jbe@1309 | 2706                     var nativeRangeDifferent = !range.equals(range.nativeRange); | 
| bsw/jbe@1309 | 2707 | 
| bsw/jbe@1309 | 2708                     // Always set both boundaries for the benefit of IE9 (see issue 35) | 
| bsw/jbe@1309 | 2709                     if (startMoved || endMoved || nativeRangeDifferent) { | 
| bsw/jbe@1309 | 2710                         range.setEnd(endContainer, endOffset); | 
| bsw/jbe@1309 | 2711                         range.setStart(startContainer, startOffset); | 
| bsw/jbe@1309 | 2712                     } | 
| bsw/jbe@1309 | 2713                 } | 
| bsw/jbe@1309 | 2714 | 
| bsw/jbe@1309 | 2715                 var createBeforeAfterNodeSetter; | 
| bsw/jbe@1309 | 2716 | 
| bsw/jbe@1309 | 2717                 WrappedRange = function(range) { | 
| bsw/jbe@1309 | 2718                     if (!range) { | 
| bsw/jbe@1309 | 2719                         throw module.createError("WrappedRange: Range must be specified"); | 
| bsw/jbe@1309 | 2720                     } | 
| bsw/jbe@1309 | 2721                     this.nativeRange = range; | 
| bsw/jbe@1309 | 2722                     updateRangeProperties(this); | 
| bsw/jbe@1309 | 2723                 }; | 
| bsw/jbe@1309 | 2724 | 
| bsw/jbe@1309 | 2725                 DomRange.createPrototypeRange(WrappedRange, updateNativeRange); | 
| bsw/jbe@1309 | 2726 | 
| bsw/jbe@1309 | 2727                 rangeProto = WrappedRange.prototype; | 
| bsw/jbe@1309 | 2728 | 
| bsw/jbe@1309 | 2729                 rangeProto.selectNode = function(node) { | 
| bsw/jbe@1309 | 2730                     this.nativeRange.selectNode(node); | 
| bsw/jbe@1309 | 2731                     updateRangeProperties(this); | 
| bsw/jbe@1309 | 2732                 }; | 
| bsw/jbe@1309 | 2733 | 
| bsw/jbe@1309 | 2734                 rangeProto.cloneContents = function() { | 
| bsw/jbe@1309 | 2735                     return this.nativeRange.cloneContents(); | 
| bsw/jbe@1309 | 2736                 }; | 
| bsw/jbe@1309 | 2737 | 
| bsw/jbe@1309 | 2738                 // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect, | 
| bsw/jbe@1309 | 2739                 // insertNode() is never delegated to the native range. | 
| bsw/jbe@1309 | 2740 | 
| bsw/jbe@1309 | 2741                 rangeProto.surroundContents = function(node) { | 
| bsw/jbe@1309 | 2742                     this.nativeRange.surroundContents(node); | 
| bsw/jbe@1309 | 2743                     updateRangeProperties(this); | 
| bsw/jbe@1309 | 2744                 }; | 
| bsw/jbe@1309 | 2745 | 
| bsw/jbe@1309 | 2746                 rangeProto.collapse = function(isStart) { | 
| bsw/jbe@1309 | 2747                     this.nativeRange.collapse(isStart); | 
| bsw/jbe@1309 | 2748                     updateRangeProperties(this); | 
| bsw/jbe@1309 | 2749                 }; | 
| bsw/jbe@1309 | 2750 | 
| bsw/jbe@1309 | 2751                 rangeProto.cloneRange = function() { | 
| bsw/jbe@1309 | 2752                     return new WrappedRange(this.nativeRange.cloneRange()); | 
| bsw/jbe@1309 | 2753                 }; | 
| bsw/jbe@1309 | 2754 | 
| bsw/jbe@1309 | 2755                 rangeProto.refresh = function() { | 
| bsw/jbe@1309 | 2756                     updateRangeProperties(this); | 
| bsw/jbe@1309 | 2757                 }; | 
| bsw/jbe@1309 | 2758 | 
| bsw/jbe@1309 | 2759                 rangeProto.toString = function() { | 
| bsw/jbe@1309 | 2760                     return this.nativeRange.toString(); | 
| bsw/jbe@1309 | 2761                 }; | 
| bsw/jbe@1309 | 2762 | 
| bsw/jbe@1309 | 2763                 // Create test range and node for feature detection | 
| bsw/jbe@1309 | 2764 | 
| bsw/jbe@1309 | 2765                 var testTextNode = document.createTextNode("test"); | 
| bsw/jbe@1309 | 2766                 getBody(document).appendChild(testTextNode); | 
| bsw/jbe@1309 | 2767                 var range = document.createRange(); | 
| bsw/jbe@1309 | 2768 | 
| bsw/jbe@1309 | 2769                 /*--------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 2770 | 
| bsw/jbe@1309 | 2771                 // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and | 
| bsw/jbe@1309 | 2772                 // correct for it | 
| bsw/jbe@1309 | 2773 | 
| bsw/jbe@1309 | 2774                 range.setStart(testTextNode, 0); | 
| bsw/jbe@1309 | 2775                 range.setEnd(testTextNode, 0); | 
| bsw/jbe@1309 | 2776 | 
| bsw/jbe@1309 | 2777                 try { | 
| bsw/jbe@1309 | 2778                     range.setStart(testTextNode, 1); | 
| bsw/jbe@1309 | 2779 | 
| bsw/jbe@1309 | 2780                     rangeProto.setStart = function(node, offset) { | 
| bsw/jbe@1309 | 2781                         this.nativeRange.setStart(node, offset); | 
| bsw/jbe@1309 | 2782                         updateRangeProperties(this); | 
| bsw/jbe@1309 | 2783                     }; | 
| bsw/jbe@1309 | 2784 | 
| bsw/jbe@1309 | 2785                     rangeProto.setEnd = function(node, offset) { | 
| bsw/jbe@1309 | 2786                         this.nativeRange.setEnd(node, offset); | 
| bsw/jbe@1309 | 2787                         updateRangeProperties(this); | 
| bsw/jbe@1309 | 2788                     }; | 
| bsw/jbe@1309 | 2789 | 
| bsw/jbe@1309 | 2790                     createBeforeAfterNodeSetter = function(name) { | 
| bsw/jbe@1309 | 2791                         return function(node) { | 
| bsw/jbe@1309 | 2792                             this.nativeRange[name](node); | 
| bsw/jbe@1309 | 2793                             updateRangeProperties(this); | 
| bsw/jbe@1309 | 2794                         }; | 
| bsw/jbe@1309 | 2795                     }; | 
| bsw/jbe@1309 | 2796 | 
| bsw/jbe@1309 | 2797                 } catch(ex) { | 
| bsw/jbe@1309 | 2798 | 
| bsw/jbe@1309 | 2799                     rangeProto.setStart = function(node, offset) { | 
| bsw/jbe@1309 | 2800                         try { | 
| bsw/jbe@1309 | 2801                             this.nativeRange.setStart(node, offset); | 
| bsw/jbe@1309 | 2802                         } catch (ex) { | 
| bsw/jbe@1309 | 2803                             this.nativeRange.setEnd(node, offset); | 
| bsw/jbe@1309 | 2804                             this.nativeRange.setStart(node, offset); | 
| bsw/jbe@1309 | 2805                         } | 
| bsw/jbe@1309 | 2806                         updateRangeProperties(this); | 
| bsw/jbe@1309 | 2807                     }; | 
| bsw/jbe@1309 | 2808 | 
| bsw/jbe@1309 | 2809                     rangeProto.setEnd = function(node, offset) { | 
| bsw/jbe@1309 | 2810                         try { | 
| bsw/jbe@1309 | 2811                             this.nativeRange.setEnd(node, offset); | 
| bsw/jbe@1309 | 2812                         } catch (ex) { | 
| bsw/jbe@1309 | 2813                             this.nativeRange.setStart(node, offset); | 
| bsw/jbe@1309 | 2814                             this.nativeRange.setEnd(node, offset); | 
| bsw/jbe@1309 | 2815                         } | 
| bsw/jbe@1309 | 2816                         updateRangeProperties(this); | 
| bsw/jbe@1309 | 2817                     }; | 
| bsw/jbe@1309 | 2818 | 
| bsw/jbe@1309 | 2819                     createBeforeAfterNodeSetter = function(name, oppositeName) { | 
| bsw/jbe@1309 | 2820                         return function(node) { | 
| bsw/jbe@1309 | 2821                             try { | 
| bsw/jbe@1309 | 2822                                 this.nativeRange[name](node); | 
| bsw/jbe@1309 | 2823                             } catch (ex) { | 
| bsw/jbe@1309 | 2824                                 this.nativeRange[oppositeName](node); | 
| bsw/jbe@1309 | 2825                                 this.nativeRange[name](node); | 
| bsw/jbe@1309 | 2826                             } | 
| bsw/jbe@1309 | 2827                             updateRangeProperties(this); | 
| bsw/jbe@1309 | 2828                         }; | 
| bsw/jbe@1309 | 2829                     }; | 
| bsw/jbe@1309 | 2830                 } | 
| bsw/jbe@1309 | 2831 | 
| bsw/jbe@1309 | 2832                 rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore"); | 
| bsw/jbe@1309 | 2833                 rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter"); | 
| bsw/jbe@1309 | 2834                 rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore"); | 
| bsw/jbe@1309 | 2835                 rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter"); | 
| bsw/jbe@1309 | 2836 | 
| bsw/jbe@1309 | 2837                 /*--------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 2838 | 
| bsw/jbe@1309 | 2839                 // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing | 
| bsw/jbe@1309 | 2840                 // whether the native implementation can be trusted | 
| bsw/jbe@1309 | 2841                 rangeProto.selectNodeContents = function(node) { | 
| bsw/jbe@1309 | 2842                     this.setStartAndEnd(node, 0, dom.getNodeLength(node)); | 
| bsw/jbe@1309 | 2843                 }; | 
| bsw/jbe@1309 | 2844 | 
| bsw/jbe@1309 | 2845                 /*--------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 2846 | 
| bsw/jbe@1309 | 2847                 // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for | 
| bsw/jbe@1309 | 2848                 // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738 | 
| bsw/jbe@1309 | 2849 | 
| bsw/jbe@1309 | 2850                 range.selectNodeContents(testTextNode); | 
| bsw/jbe@1309 | 2851                 range.setEnd(testTextNode, 3); | 
| bsw/jbe@1309 | 2852 | 
| bsw/jbe@1309 | 2853                 var range2 = document.createRange(); | 
| bsw/jbe@1309 | 2854                 range2.selectNodeContents(testTextNode); | 
| bsw/jbe@1309 | 2855                 range2.setEnd(testTextNode, 4); | 
| bsw/jbe@1309 | 2856                 range2.setStart(testTextNode, 2); | 
| bsw/jbe@1309 | 2857 | 
| bsw/jbe@1309 | 2858                 if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 && | 
| bsw/jbe@1309 | 2859                         range.compareBoundaryPoints(range.END_TO_START, range2) == 1) { | 
| bsw/jbe@1309 | 2860                     // This is the wrong way round, so correct for it | 
| bsw/jbe@1309 | 2861 | 
| bsw/jbe@1309 | 2862                     rangeProto.compareBoundaryPoints = function(type, range) { | 
| bsw/jbe@1309 | 2863                         range = range.nativeRange || range; | 
| bsw/jbe@1309 | 2864                         if (type == range.START_TO_END) { | 
| bsw/jbe@1309 | 2865                             type = range.END_TO_START; | 
| bsw/jbe@1309 | 2866                         } else if (type == range.END_TO_START) { | 
| bsw/jbe@1309 | 2867                             type = range.START_TO_END; | 
| bsw/jbe@1309 | 2868                         } | 
| bsw/jbe@1309 | 2869                         return this.nativeRange.compareBoundaryPoints(type, range); | 
| bsw/jbe@1309 | 2870                     }; | 
| bsw/jbe@1309 | 2871                 } else { | 
| bsw/jbe@1309 | 2872                     rangeProto.compareBoundaryPoints = function(type, range) { | 
| bsw/jbe@1309 | 2873                         return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range); | 
| bsw/jbe@1309 | 2874                     }; | 
| bsw/jbe@1309 | 2875                 } | 
| bsw/jbe@1309 | 2876 | 
| bsw/jbe@1309 | 2877                 /*--------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 2878 | 
| bsw/jbe@1309 | 2879                 // Test for IE deleteContents() and extractContents() bug and correct it. See issue 107. | 
| bsw/jbe@1309 | 2880 | 
| bsw/jbe@1309 | 2881                 var el = document.createElement("div"); | 
| bsw/jbe@1309 | 2882                 el.innerHTML = "123"; | 
| bsw/jbe@1309 | 2883                 var textNode = el.firstChild; | 
| bsw/jbe@1309 | 2884                 var body = getBody(document); | 
| bsw/jbe@1309 | 2885                 body.appendChild(el); | 
| bsw/jbe@1309 | 2886 | 
| bsw/jbe@1309 | 2887                 range.setStart(textNode, 1); | 
| bsw/jbe@1309 | 2888                 range.setEnd(textNode, 2); | 
| bsw/jbe@1309 | 2889                 range.deleteContents(); | 
| bsw/jbe@1309 | 2890 | 
| bsw/jbe@1309 | 2891                 if (textNode.data == "13") { | 
| bsw/jbe@1309 | 2892                     // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and | 
| bsw/jbe@1309 | 2893                     // extractContents() | 
| bsw/jbe@1309 | 2894                     rangeProto.deleteContents = function() { | 
| bsw/jbe@1309 | 2895                         this.nativeRange.deleteContents(); | 
| bsw/jbe@1309 | 2896                         updateRangeProperties(this); | 
| bsw/jbe@1309 | 2897                     }; | 
| bsw/jbe@1309 | 2898 | 
| bsw/jbe@1309 | 2899                     rangeProto.extractContents = function() { | 
| bsw/jbe@1309 | 2900                         var frag = this.nativeRange.extractContents(); | 
| bsw/jbe@1309 | 2901                         updateRangeProperties(this); | 
| bsw/jbe@1309 | 2902                         return frag; | 
| bsw/jbe@1309 | 2903                     }; | 
| bsw/jbe@1309 | 2904                 } else { | 
| bsw/jbe@1309 | 2905                 } | 
| bsw/jbe@1309 | 2906 | 
| bsw/jbe@1309 | 2907                 body.removeChild(el); | 
| bsw/jbe@1309 | 2908                 body = null; | 
| bsw/jbe@1309 | 2909 | 
| bsw/jbe@1309 | 2910                 /*--------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 2911 | 
| bsw/jbe@1309 | 2912                 // Test for existence of createContextualFragment and delegate to it if it exists | 
| bsw/jbe@1309 | 2913                 if (util.isHostMethod(range, "createContextualFragment")) { | 
| bsw/jbe@1309 | 2914                     rangeProto.createContextualFragment = function(fragmentStr) { | 
| bsw/jbe@1309 | 2915                         return this.nativeRange.createContextualFragment(fragmentStr); | 
| bsw/jbe@1309 | 2916                     }; | 
| bsw/jbe@1309 | 2917                 } | 
| bsw/jbe@1309 | 2918 | 
| bsw/jbe@1309 | 2919                 /*--------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 2920 | 
| bsw/jbe@1309 | 2921                 // Clean up | 
| bsw/jbe@1309 | 2922                 getBody(document).removeChild(testTextNode); | 
| bsw/jbe@1309 | 2923 | 
| bsw/jbe@1309 | 2924                 rangeProto.getName = function() { | 
| bsw/jbe@1309 | 2925                     return "WrappedRange"; | 
| bsw/jbe@1309 | 2926                 }; | 
| bsw/jbe@1309 | 2927 | 
| bsw/jbe@1309 | 2928                 api.WrappedRange = WrappedRange; | 
| bsw/jbe@1309 | 2929 | 
| bsw/jbe@1309 | 2930                 api.createNativeRange = function(doc) { | 
| bsw/jbe@1309 | 2931                     doc = getContentDocument(doc, module, "createNativeRange"); | 
| bsw/jbe@1309 | 2932                     return doc.createRange(); | 
| bsw/jbe@1309 | 2933                 }; | 
| bsw/jbe@1309 | 2934             })(); | 
| bsw/jbe@1309 | 2935         } | 
| bsw/jbe@1309 | 2936 | 
| bsw/jbe@1309 | 2937         if (api.features.implementsTextRange) { | 
| bsw/jbe@1309 | 2938             /* | 
| bsw/jbe@1309 | 2939             This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement() | 
| bsw/jbe@1309 | 2940             method. For example, in the following (where pipes denote the selection boundaries): | 
| bsw/jbe@1309 | 2941 | 
| bsw/jbe@1309 | 2942             <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul> | 
| bsw/jbe@1309 | 2943 | 
| bsw/jbe@1309 | 2944             var range = document.selection.createRange(); | 
| bsw/jbe@1309 | 2945             alert(range.parentElement().id); // Should alert "ul" but alerts "b" | 
| bsw/jbe@1309 | 2946 | 
| bsw/jbe@1309 | 2947             This method returns the common ancestor node of the following: | 
| bsw/jbe@1309 | 2948             - the parentElement() of the textRange | 
| bsw/jbe@1309 | 2949             - the parentElement() of the textRange after calling collapse(true) | 
| bsw/jbe@1309 | 2950             - the parentElement() of the textRange after calling collapse(false) | 
| bsw/jbe@1309 | 2951             */ | 
| bsw/jbe@1309 | 2952             var getTextRangeContainerElement = function(textRange) { | 
| bsw/jbe@1309 | 2953                 var parentEl = textRange.parentElement(); | 
| bsw/jbe@1309 | 2954                 var range = textRange.duplicate(); | 
| bsw/jbe@1309 | 2955                 range.collapse(true); | 
| bsw/jbe@1309 | 2956                 var startEl = range.parentElement(); | 
| bsw/jbe@1309 | 2957                 range = textRange.duplicate(); | 
| bsw/jbe@1309 | 2958                 range.collapse(false); | 
| bsw/jbe@1309 | 2959                 var endEl = range.parentElement(); | 
| bsw/jbe@1309 | 2960                 var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl); | 
| bsw/jbe@1309 | 2961 | 
| bsw/jbe@1309 | 2962                 return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer); | 
| bsw/jbe@1309 | 2963             }; | 
| bsw/jbe@1309 | 2964 | 
| bsw/jbe@1309 | 2965             var textRangeIsCollapsed = function(textRange) { | 
| bsw/jbe@1309 | 2966                 return textRange.compareEndPoints("StartToEnd", textRange) == 0; | 
| bsw/jbe@1309 | 2967             }; | 
| bsw/jbe@1309 | 2968 | 
| bsw/jbe@1309 | 2969             // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started | 
| bsw/jbe@1309 | 2970             // out as an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) | 
| bsw/jbe@1309 | 2971             // but has grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange | 
| bsw/jbe@1309 | 2972             // bugs, handling for inputs and images, plus optimizations. | 
| bsw/jbe@1309 | 2973             var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) { | 
| bsw/jbe@1309 | 2974                 var workingRange = textRange.duplicate(); | 
| bsw/jbe@1309 | 2975                 workingRange.collapse(isStart); | 
| bsw/jbe@1309 | 2976                 var containerElement = workingRange.parentElement(); | 
| bsw/jbe@1309 | 2977 | 
| bsw/jbe@1309 | 2978                 // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so | 
| bsw/jbe@1309 | 2979                 // check for that | 
| bsw/jbe@1309 | 2980                 if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) { | 
| bsw/jbe@1309 | 2981                     containerElement = wholeRangeContainerElement; | 
| bsw/jbe@1309 | 2982                 } | 
| bsw/jbe@1309 | 2983 | 
| bsw/jbe@1309 | 2984 | 
| bsw/jbe@1309 | 2985                 // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and | 
| bsw/jbe@1309 | 2986                 // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx | 
| bsw/jbe@1309 | 2987                 if (!containerElement.canHaveHTML) { | 
| bsw/jbe@1309 | 2988                     var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); | 
| bsw/jbe@1309 | 2989                     return { | 
| bsw/jbe@1309 | 2990                         boundaryPosition: pos, | 
| bsw/jbe@1309 | 2991                         nodeInfo: { | 
| bsw/jbe@1309 | 2992                             nodeIndex: pos.offset, | 
| bsw/jbe@1309 | 2993                             containerElement: pos.node | 
| bsw/jbe@1309 | 2994                         } | 
| bsw/jbe@1309 | 2995                     }; | 
| bsw/jbe@1309 | 2996                 } | 
| bsw/jbe@1309 | 2997 | 
| bsw/jbe@1309 | 2998                 var workingNode = dom.getDocument(containerElement).createElement("span"); | 
| bsw/jbe@1309 | 2999 | 
| bsw/jbe@1309 | 3000                 // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5 | 
| bsw/jbe@1309 | 3001                 // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64 | 
| bsw/jbe@1309 | 3002                 if (workingNode.parentNode) { | 
| bsw/jbe@1309 | 3003                     dom.removeNode(workingNode); | 
| bsw/jbe@1309 | 3004                 } | 
| bsw/jbe@1309 | 3005 | 
| bsw/jbe@1309 | 3006                 var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; | 
| bsw/jbe@1309 | 3007                 var previousNode, nextNode, boundaryPosition, boundaryNode; | 
| bsw/jbe@1309 | 3008                 var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0; | 
| bsw/jbe@1309 | 3009                 var childNodeCount = containerElement.childNodes.length; | 
| bsw/jbe@1309 | 3010                 var end = childNodeCount; | 
| bsw/jbe@1309 | 3011 | 
| bsw/jbe@1309 | 3012                 // Check end first. Code within the loop assumes that the endth child node of the container is definitely | 
| bsw/jbe@1309 | 3013                 // after the range boundary. | 
| bsw/jbe@1309 | 3014                 var nodeIndex = end; | 
| bsw/jbe@1309 | 3015 | 
| bsw/jbe@1309 | 3016                 while (true) { | 
| bsw/jbe@1309 | 3017                     if (nodeIndex == childNodeCount) { | 
| bsw/jbe@1309 | 3018                         containerElement.appendChild(workingNode); | 
| bsw/jbe@1309 | 3019                     } else { | 
| bsw/jbe@1309 | 3020                         containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]); | 
| bsw/jbe@1309 | 3021                     } | 
| bsw/jbe@1309 | 3022                     workingRange.moveToElementText(workingNode); | 
| bsw/jbe@1309 | 3023                     comparison = workingRange.compareEndPoints(workingComparisonType, textRange); | 
| bsw/jbe@1309 | 3024                     if (comparison == 0 || start == end) { | 
| bsw/jbe@1309 | 3025                         break; | 
| bsw/jbe@1309 | 3026                     } else if (comparison == -1) { | 
| bsw/jbe@1309 | 3027                         if (end == start + 1) { | 
| bsw/jbe@1309 | 3028                             // We know the endth child node is after the range boundary, so we must be done. | 
| bsw/jbe@1309 | 3029                             break; | 
| bsw/jbe@1309 | 3030                         } else { | 
| bsw/jbe@1309 | 3031                             start = nodeIndex; | 
| bsw/jbe@1309 | 3032                         } | 
| bsw/jbe@1309 | 3033                     } else { | 
| bsw/jbe@1309 | 3034                         end = (end == start + 1) ? start : nodeIndex; | 
| bsw/jbe@1309 | 3035                     } | 
| bsw/jbe@1309 | 3036                     nodeIndex = Math.floor((start + end) / 2); | 
| bsw/jbe@1309 | 3037                     containerElement.removeChild(workingNode); | 
| bsw/jbe@1309 | 3038                 } | 
| bsw/jbe@1309 | 3039 | 
| bsw/jbe@1309 | 3040 | 
| bsw/jbe@1309 | 3041                 // We've now reached or gone past the boundary of the text range we're interested in | 
| bsw/jbe@1309 | 3042                 // so have identified the node we want | 
| bsw/jbe@1309 | 3043                 boundaryNode = workingNode.nextSibling; | 
| bsw/jbe@1309 | 3044 | 
| bsw/jbe@1309 | 3045                 if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) { | 
| bsw/jbe@1309 | 3046                     // This is a character data node (text, comment, cdata). The working range is collapsed at the start of | 
| bsw/jbe@1309 | 3047                     // the node containing the text range's boundary, so we move the end of the working range to the | 
| bsw/jbe@1309 | 3048                     // boundary point and measure the length of its text to get the boundary's offset within the node. | 
| bsw/jbe@1309 | 3049                     workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); | 
| bsw/jbe@1309 | 3050 | 
| bsw/jbe@1309 | 3051                     var offset; | 
| bsw/jbe@1309 | 3052 | 
| bsw/jbe@1309 | 3053                     if (/[\r\n]/.test(boundaryNode.data)) { | 
| bsw/jbe@1309 | 3054                         /* | 
| bsw/jbe@1309 | 3055                         For the particular case of a boundary within a text node containing rendered line breaks (within a | 
| bsw/jbe@1309 | 3056                         <pre> element, for example), we need a slightly complicated approach to get the boundary's offset in | 
| bsw/jbe@1309 | 3057                         IE. The facts: | 
| bsw/jbe@1309 | 3058 | 
| bsw/jbe@1309 | 3059                         - Each line break is represented as \r in the text node's data/nodeValue properties | 
| bsw/jbe@1309 | 3060                         - Each line break is represented as \r\n in the TextRange's 'text' property | 
| bsw/jbe@1309 | 3061                         - The 'text' property of the TextRange does not contain trailing line breaks | 
| bsw/jbe@1309 | 3062 | 
| bsw/jbe@1309 | 3063                         To get round the problem presented by the final fact above, we can use the fact that TextRange's | 
| bsw/jbe@1309 | 3064                         moveStart() and moveEnd() methods return the actual number of characters moved, which is not | 
| bsw/jbe@1309 | 3065                         necessarily the same as the number of characters it was instructed to move. The simplest approach is | 
| bsw/jbe@1309 | 3066                         to use this to store the characters moved when moving both the start and end of the range to the | 
| bsw/jbe@1309 | 3067                         start of the document body and subtracting the start offset from the end offset (the | 
| bsw/jbe@1309 | 3068                         "move-negative-gazillion" method). However, this is extremely slow when the document is large and | 
| bsw/jbe@1309 | 3069                         the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to | 
| bsw/jbe@1309 | 3070                         the end of the document) has the same problem. | 
| bsw/jbe@1309 | 3071 | 
| bsw/jbe@1309 | 3072                         Another approach that works is to use moveStart() to move the start boundary of the range up to the | 
| bsw/jbe@1309 | 3073                         end boundary one character at a time and incrementing a counter with the value returned by the | 
| bsw/jbe@1309 | 3074                         moveStart() call. However, the check for whether the start boundary has reached the end boundary is | 
| bsw/jbe@1309 | 3075                         expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected | 
| bsw/jbe@1309 | 3076                         by the location of the range within the document). | 
| bsw/jbe@1309 | 3077 | 
| bsw/jbe@1309 | 3078                         The approach used below is a hybrid of the two methods above. It uses the fact that a string | 
| bsw/jbe@1309 | 3079                         containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot | 
| bsw/jbe@1309 | 3080                         be longer than the text of the TextRange, so the start of the range is moved that length initially | 
| bsw/jbe@1309 | 3081                         and then a character at a time to make up for any trailing line breaks not contained in the 'text' | 
| bsw/jbe@1309 | 3082                         property. This has good performance in most situations compared to the previous two methods. | 
| bsw/jbe@1309 | 3083                         */ | 
| bsw/jbe@1309 | 3084                         var tempRange = workingRange.duplicate(); | 
| bsw/jbe@1309 | 3085                         var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length; | 
| bsw/jbe@1309 | 3086 | 
| bsw/jbe@1309 | 3087                         offset = tempRange.moveStart("character", rangeLength); | 
| bsw/jbe@1309 | 3088                         while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) { | 
| bsw/jbe@1309 | 3089                             offset++; | 
| bsw/jbe@1309 | 3090                             tempRange.moveStart("character", 1); | 
| bsw/jbe@1309 | 3091                         } | 
| bsw/jbe@1309 | 3092                     } else { | 
| bsw/jbe@1309 | 3093                         offset = workingRange.text.length; | 
| bsw/jbe@1309 | 3094                     } | 
| bsw/jbe@1309 | 3095                     boundaryPosition = new DomPosition(boundaryNode, offset); | 
| bsw/jbe@1309 | 3096                 } else { | 
| bsw/jbe@1309 | 3097 | 
| bsw/jbe@1309 | 3098                     // If the boundary immediately follows a character data node and this is the end boundary, we should favour | 
| bsw/jbe@1309 | 3099                     // a position within that, and likewise for a start boundary preceding a character data node | 
| bsw/jbe@1309 | 3100                     previousNode = (isCollapsed || !isStart) && workingNode.previousSibling; | 
| bsw/jbe@1309 | 3101                     nextNode = (isCollapsed || isStart) && workingNode.nextSibling; | 
| bsw/jbe@1309 | 3102                     if (nextNode && isCharacterDataNode(nextNode)) { | 
| bsw/jbe@1309 | 3103                         boundaryPosition = new DomPosition(nextNode, 0); | 
| bsw/jbe@1309 | 3104                     } else if (previousNode && isCharacterDataNode(previousNode)) { | 
| bsw/jbe@1309 | 3105                         boundaryPosition = new DomPosition(previousNode, previousNode.data.length); | 
| bsw/jbe@1309 | 3106                     } else { | 
| bsw/jbe@1309 | 3107                         boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode)); | 
| bsw/jbe@1309 | 3108                     } | 
| bsw/jbe@1309 | 3109                 } | 
| bsw/jbe@1309 | 3110 | 
| bsw/jbe@1309 | 3111                 // Clean up | 
| bsw/jbe@1309 | 3112                 dom.removeNode(workingNode); | 
| bsw/jbe@1309 | 3113 | 
| bsw/jbe@1309 | 3114                 return { | 
| bsw/jbe@1309 | 3115                     boundaryPosition: boundaryPosition, | 
| bsw/jbe@1309 | 3116                     nodeInfo: { | 
| bsw/jbe@1309 | 3117                         nodeIndex: nodeIndex, | 
| bsw/jbe@1309 | 3118                         containerElement: containerElement | 
| bsw/jbe@1309 | 3119                     } | 
| bsw/jbe@1309 | 3120                 }; | 
| bsw/jbe@1309 | 3121             }; | 
| bsw/jbe@1309 | 3122 | 
| bsw/jbe@1309 | 3123             // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that | 
| bsw/jbe@1309 | 3124             // node. This function started out as an optimized version of code found in Tim Cameron Ryan's IERange | 
| bsw/jbe@1309 | 3125             // (http://code.google.com/p/ierange/) | 
| bsw/jbe@1309 | 3126             var createBoundaryTextRange = function(boundaryPosition, isStart) { | 
| bsw/jbe@1309 | 3127                 var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset; | 
| bsw/jbe@1309 | 3128                 var doc = dom.getDocument(boundaryPosition.node); | 
| bsw/jbe@1309 | 3129                 var workingNode, childNodes, workingRange = getBody(doc).createTextRange(); | 
| bsw/jbe@1309 | 3130                 var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node); | 
| bsw/jbe@1309 | 3131 | 
| bsw/jbe@1309 | 3132                 if (nodeIsDataNode) { | 
| bsw/jbe@1309 | 3133                     boundaryNode = boundaryPosition.node; | 
| bsw/jbe@1309 | 3134                     boundaryParent = boundaryNode.parentNode; | 
| bsw/jbe@1309 | 3135                 } else { | 
| bsw/jbe@1309 | 3136                     childNodes = boundaryPosition.node.childNodes; | 
| bsw/jbe@1309 | 3137                     boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null; | 
| bsw/jbe@1309 | 3138                     boundaryParent = boundaryPosition.node; | 
| bsw/jbe@1309 | 3139                 } | 
| bsw/jbe@1309 | 3140 | 
| bsw/jbe@1309 | 3141                 // Position the range immediately before the node containing the boundary | 
| bsw/jbe@1309 | 3142                 workingNode = doc.createElement("span"); | 
| bsw/jbe@1309 | 3143 | 
| bsw/jbe@1309 | 3144                 // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within | 
| bsw/jbe@1309 | 3145                 // the element rather than immediately before or after it | 
| bsw/jbe@1309 | 3146                 workingNode.innerHTML = "&#feff;"; | 
| bsw/jbe@1309 | 3147 | 
| bsw/jbe@1309 | 3148                 // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report | 
| bsw/jbe@1309 | 3149                 // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12 | 
| bsw/jbe@1309 | 3150                 if (boundaryNode) { | 
| bsw/jbe@1309 | 3151                     boundaryParent.insertBefore(workingNode, boundaryNode); | 
| bsw/jbe@1309 | 3152                 } else { | 
| bsw/jbe@1309 | 3153                     boundaryParent.appendChild(workingNode); | 
| bsw/jbe@1309 | 3154                 } | 
| bsw/jbe@1309 | 3155 | 
| bsw/jbe@1309 | 3156                 workingRange.moveToElementText(workingNode); | 
| bsw/jbe@1309 | 3157                 workingRange.collapse(!isStart); | 
| bsw/jbe@1309 | 3158 | 
| bsw/jbe@1309 | 3159                 // Clean up | 
| bsw/jbe@1309 | 3160                 boundaryParent.removeChild(workingNode); | 
| bsw/jbe@1309 | 3161 | 
| bsw/jbe@1309 | 3162                 // Move the working range to the text offset, if required | 
| bsw/jbe@1309 | 3163                 if (nodeIsDataNode) { | 
| bsw/jbe@1309 | 3164                     workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset); | 
| bsw/jbe@1309 | 3165                 } | 
| bsw/jbe@1309 | 3166 | 
| bsw/jbe@1309 | 3167                 return workingRange; | 
| bsw/jbe@1309 | 3168             }; | 
| bsw/jbe@1309 | 3169 | 
| bsw/jbe@1309 | 3170             /*------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 3171 | 
| bsw/jbe@1309 | 3172             // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a | 
| bsw/jbe@1309 | 3173             // prototype | 
| bsw/jbe@1309 | 3174 | 
| bsw/jbe@1309 | 3175             WrappedTextRange = function(textRange) { | 
| bsw/jbe@1309 | 3176                 this.textRange = textRange; | 
| bsw/jbe@1309 | 3177                 this.refresh(); | 
| bsw/jbe@1309 | 3178             }; | 
| bsw/jbe@1309 | 3179 | 
| bsw/jbe@1309 | 3180             WrappedTextRange.prototype = new DomRange(document); | 
| bsw/jbe@1309 | 3181 | 
| bsw/jbe@1309 | 3182             WrappedTextRange.prototype.refresh = function() { | 
| bsw/jbe@1309 | 3183                 var start, end, startBoundary; | 
| bsw/jbe@1309 | 3184 | 
| bsw/jbe@1309 | 3185                 // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that. | 
| bsw/jbe@1309 | 3186                 var rangeContainerElement = getTextRangeContainerElement(this.textRange); | 
| bsw/jbe@1309 | 3187 | 
| bsw/jbe@1309 | 3188                 if (textRangeIsCollapsed(this.textRange)) { | 
| bsw/jbe@1309 | 3189                     end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, | 
| bsw/jbe@1309 | 3190                         true).boundaryPosition; | 
| bsw/jbe@1309 | 3191                 } else { | 
| bsw/jbe@1309 | 3192                     startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false); | 
| bsw/jbe@1309 | 3193                     start = startBoundary.boundaryPosition; | 
| bsw/jbe@1309 | 3194 | 
| bsw/jbe@1309 | 3195                     // An optimization used here is that if the start and end boundaries have the same parent element, the | 
| bsw/jbe@1309 | 3196                     // search scope for the end boundary can be limited to exclude the portion of the element that precedes | 
| bsw/jbe@1309 | 3197                     // the start boundary | 
| bsw/jbe@1309 | 3198                     end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false, | 
| bsw/jbe@1309 | 3199                         startBoundary.nodeInfo).boundaryPosition; | 
| bsw/jbe@1309 | 3200                 } | 
| bsw/jbe@1309 | 3201 | 
| bsw/jbe@1309 | 3202                 this.setStart(start.node, start.offset); | 
| bsw/jbe@1309 | 3203                 this.setEnd(end.node, end.offset); | 
| bsw/jbe@1309 | 3204             }; | 
| bsw/jbe@1309 | 3205 | 
| bsw/jbe@1309 | 3206             WrappedTextRange.prototype.getName = function() { | 
| bsw/jbe@1309 | 3207                 return "WrappedTextRange"; | 
| bsw/jbe@1309 | 3208             }; | 
| bsw/jbe@1309 | 3209 | 
| bsw/jbe@1309 | 3210             DomRange.copyComparisonConstants(WrappedTextRange); | 
| bsw/jbe@1309 | 3211 | 
| bsw/jbe@1309 | 3212             var rangeToTextRange = function(range) { | 
| bsw/jbe@1309 | 3213                 if (range.collapsed) { | 
| bsw/jbe@1309 | 3214                     return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true); | 
| bsw/jbe@1309 | 3215                 } else { | 
| bsw/jbe@1309 | 3216                     var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true); | 
| bsw/jbe@1309 | 3217                     var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false); | 
| bsw/jbe@1309 | 3218                     var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange(); | 
| bsw/jbe@1309 | 3219                     textRange.setEndPoint("StartToStart", startRange); | 
| bsw/jbe@1309 | 3220                     textRange.setEndPoint("EndToEnd", endRange); | 
| bsw/jbe@1309 | 3221                     return textRange; | 
| bsw/jbe@1309 | 3222                 } | 
| bsw/jbe@1309 | 3223             }; | 
| bsw/jbe@1309 | 3224 | 
| bsw/jbe@1309 | 3225             WrappedTextRange.rangeToTextRange = rangeToTextRange; | 
| bsw/jbe@1309 | 3226 | 
| bsw/jbe@1309 | 3227             WrappedTextRange.prototype.toTextRange = function() { | 
| bsw/jbe@1309 | 3228                 return rangeToTextRange(this); | 
| bsw/jbe@1309 | 3229             }; | 
| bsw/jbe@1309 | 3230 | 
| bsw/jbe@1309 | 3231             api.WrappedTextRange = WrappedTextRange; | 
| bsw/jbe@1309 | 3232 | 
| bsw/jbe@1309 | 3233             // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which | 
| bsw/jbe@1309 | 3234             // implementation to use by default. | 
| bsw/jbe@1309 | 3235             if (!api.features.implementsDomRange || api.config.preferTextRange) { | 
| bsw/jbe@1309 | 3236                 // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work | 
| bsw/jbe@1309 | 3237                 var globalObj = (function(f) { return f("return this;")(); })(Function); | 
| bsw/jbe@1309 | 3238                 if (typeof globalObj.Range == "undefined") { | 
| bsw/jbe@1309 | 3239                     globalObj.Range = WrappedTextRange; | 
| bsw/jbe@1309 | 3240                 } | 
| bsw/jbe@1309 | 3241 | 
| bsw/jbe@1309 | 3242                 api.createNativeRange = function(doc) { | 
| bsw/jbe@1309 | 3243                     doc = getContentDocument(doc, module, "createNativeRange"); | 
| bsw/jbe@1309 | 3244                     return getBody(doc).createTextRange(); | 
| bsw/jbe@1309 | 3245                 }; | 
| bsw/jbe@1309 | 3246 | 
| bsw/jbe@1309 | 3247                 api.WrappedRange = WrappedTextRange; | 
| bsw/jbe@1309 | 3248             } | 
| bsw/jbe@1309 | 3249         } | 
| bsw/jbe@1309 | 3250 | 
| bsw/jbe@1309 | 3251         api.createRange = function(doc) { | 
| bsw/jbe@1309 | 3252             doc = getContentDocument(doc, module, "createRange"); | 
| bsw/jbe@1309 | 3253             return new api.WrappedRange(api.createNativeRange(doc)); | 
| bsw/jbe@1309 | 3254         }; | 
| bsw/jbe@1309 | 3255 | 
| bsw/jbe@1309 | 3256         api.createRangyRange = function(doc) { | 
| bsw/jbe@1309 | 3257             doc = getContentDocument(doc, module, "createRangyRange"); | 
| bsw/jbe@1309 | 3258             return new DomRange(doc); | 
| bsw/jbe@1309 | 3259         }; | 
| bsw/jbe@1309 | 3260 | 
| bsw/jbe@1309 | 3261         util.createAliasForDeprecatedMethod(api, "createIframeRange", "createRange"); | 
| bsw/jbe@1309 | 3262         util.createAliasForDeprecatedMethod(api, "createIframeRangyRange", "createRangyRange"); | 
| bsw/jbe@1309 | 3263 | 
| bsw/jbe@1309 | 3264         api.addShimListener(function(win) { | 
| bsw/jbe@1309 | 3265             var doc = win.document; | 
| bsw/jbe@1309 | 3266             if (typeof doc.createRange == "undefined") { | 
| bsw/jbe@1309 | 3267                 doc.createRange = function() { | 
| bsw/jbe@1309 | 3268                     return api.createRange(doc); | 
| bsw/jbe@1309 | 3269                 }; | 
| bsw/jbe@1309 | 3270             } | 
| bsw/jbe@1309 | 3271             doc = win = null; | 
| bsw/jbe@1309 | 3272         }); | 
| bsw/jbe@1309 | 3273     }); | 
| bsw/jbe@1309 | 3274 | 
| bsw/jbe@1309 | 3275     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 3276 | 
| bsw/jbe@1309 | 3277     // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification | 
| bsw/jbe@1309 | 3278     // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections) | 
| bsw/jbe@1309 | 3279     api.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) { | 
| bsw/jbe@1309 | 3280         api.config.checkSelectionRanges = true; | 
| bsw/jbe@1309 | 3281 | 
| bsw/jbe@1309 | 3282         var BOOLEAN = "boolean"; | 
| bsw/jbe@1309 | 3283         var NUMBER = "number"; | 
| bsw/jbe@1309 | 3284         var dom = api.dom; | 
| bsw/jbe@1309 | 3285         var util = api.util; | 
| bsw/jbe@1309 | 3286         var isHostMethod = util.isHostMethod; | 
| bsw/jbe@1309 | 3287         var DomRange = api.DomRange; | 
| bsw/jbe@1309 | 3288         var WrappedRange = api.WrappedRange; | 
| bsw/jbe@1309 | 3289         var DOMException = api.DOMException; | 
| bsw/jbe@1309 | 3290         var DomPosition = dom.DomPosition; | 
| bsw/jbe@1309 | 3291         var getNativeSelection; | 
| bsw/jbe@1309 | 3292         var selectionIsCollapsed; | 
| bsw/jbe@1309 | 3293         var features = api.features; | 
| bsw/jbe@1309 | 3294         var CONTROL = "Control"; | 
| bsw/jbe@1309 | 3295         var getDocument = dom.getDocument; | 
| bsw/jbe@1309 | 3296         var getBody = dom.getBody; | 
| bsw/jbe@1309 | 3297         var rangesEqual = DomRange.rangesEqual; | 
| bsw/jbe@1309 | 3298 | 
| bsw/jbe@1309 | 3299 | 
| bsw/jbe@1309 | 3300         // Utility function to support direction parameters in the API that may be a string ("backward", "backwards", | 
| bsw/jbe@1309 | 3301         // "forward" or "forwards") or a Boolean (true for backwards). | 
| bsw/jbe@1309 | 3302         function isDirectionBackward(dir) { | 
| bsw/jbe@1309 | 3303             return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir; | 
| bsw/jbe@1309 | 3304         } | 
| bsw/jbe@1309 | 3305 | 
| bsw/jbe@1309 | 3306         function getWindow(win, methodName) { | 
| bsw/jbe@1309 | 3307             if (!win) { | 
| bsw/jbe@1309 | 3308                 return window; | 
| bsw/jbe@1309 | 3309             } else if (dom.isWindow(win)) { | 
| bsw/jbe@1309 | 3310                 return win; | 
| bsw/jbe@1309 | 3311             } else if (win instanceof WrappedSelection) { | 
| bsw/jbe@1309 | 3312                 return win.win; | 
| bsw/jbe@1309 | 3313             } else { | 
| bsw/jbe@1309 | 3314                 var doc = dom.getContentDocument(win, module, methodName); | 
| bsw/jbe@1309 | 3315                 return dom.getWindow(doc); | 
| bsw/jbe@1309 | 3316             } | 
| bsw/jbe@1309 | 3317         } | 
| bsw/jbe@1309 | 3318 | 
| bsw/jbe@1309 | 3319         function getWinSelection(winParam) { | 
| bsw/jbe@1309 | 3320             return getWindow(winParam, "getWinSelection").getSelection(); | 
| bsw/jbe@1309 | 3321         } | 
| bsw/jbe@1309 | 3322 | 
| bsw/jbe@1309 | 3323         function getDocSelection(winParam) { | 
| bsw/jbe@1309 | 3324             return getWindow(winParam, "getDocSelection").document.selection; | 
| bsw/jbe@1309 | 3325         } | 
| bsw/jbe@1309 | 3326 | 
| bsw/jbe@1309 | 3327         function winSelectionIsBackward(sel) { | 
| bsw/jbe@1309 | 3328             var backward = false; | 
| bsw/jbe@1309 | 3329             if (sel.anchorNode) { | 
| bsw/jbe@1309 | 3330                 backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1); | 
| bsw/jbe@1309 | 3331             } | 
| bsw/jbe@1309 | 3332             return backward; | 
| bsw/jbe@1309 | 3333         } | 
| bsw/jbe@1309 | 3334 | 
| bsw/jbe@1309 | 3335         // Test for the Range/TextRange and Selection features required | 
| bsw/jbe@1309 | 3336         // Test for ability to retrieve selection | 
| bsw/jbe@1309 | 3337         var implementsWinGetSelection = isHostMethod(window, "getSelection"), | 
| bsw/jbe@1309 | 3338             implementsDocSelection = util.isHostObject(document, "selection"); | 
| bsw/jbe@1309 | 3339 | 
| bsw/jbe@1309 | 3340         features.implementsWinGetSelection = implementsWinGetSelection; | 
| bsw/jbe@1309 | 3341         features.implementsDocSelection = implementsDocSelection; | 
| bsw/jbe@1309 | 3342 | 
| bsw/jbe@1309 | 3343         var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange); | 
| bsw/jbe@1309 | 3344 | 
| bsw/jbe@1309 | 3345         if (useDocumentSelection) { | 
| bsw/jbe@1309 | 3346             getNativeSelection = getDocSelection; | 
| bsw/jbe@1309 | 3347             api.isSelectionValid = function(winParam) { | 
| bsw/jbe@1309 | 3348                 var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection; | 
| bsw/jbe@1309 | 3349 | 
| bsw/jbe@1309 | 3350                 // Check whether the selection TextRange is actually contained within the correct document | 
| bsw/jbe@1309 | 3351                 return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc); | 
| bsw/jbe@1309 | 3352             }; | 
| bsw/jbe@1309 | 3353         } else if (implementsWinGetSelection) { | 
| bsw/jbe@1309 | 3354             getNativeSelection = getWinSelection; | 
| bsw/jbe@1309 | 3355             api.isSelectionValid = function() { | 
| bsw/jbe@1309 | 3356                 return true; | 
| bsw/jbe@1309 | 3357             }; | 
| bsw/jbe@1309 | 3358         } else { | 
| bsw/jbe@1309 | 3359             module.fail("Neither document.selection or window.getSelection() detected."); | 
| bsw/jbe@1309 | 3360             return false; | 
| bsw/jbe@1309 | 3361         } | 
| bsw/jbe@1309 | 3362 | 
| bsw/jbe@1309 | 3363         api.getNativeSelection = getNativeSelection; | 
| bsw/jbe@1309 | 3364 | 
| bsw/jbe@1309 | 3365         var testSelection = getNativeSelection(); | 
| bsw/jbe@1309 | 3366 | 
| bsw/jbe@1309 | 3367         // In Firefox, the selection is null in an iframe with display: none. See issue #138. | 
| bsw/jbe@1309 | 3368         if (!testSelection) { | 
| bsw/jbe@1309 | 3369             module.fail("Native selection was null (possibly issue 138?)"); | 
| bsw/jbe@1309 | 3370             return false; | 
| bsw/jbe@1309 | 3371         } | 
| bsw/jbe@1309 | 3372 | 
| bsw/jbe@1309 | 3373         var testRange = api.createNativeRange(document); | 
| bsw/jbe@1309 | 3374         var body = getBody(document); | 
| bsw/jbe@1309 | 3375 | 
| bsw/jbe@1309 | 3376         // Obtaining a range from a selection | 
| bsw/jbe@1309 | 3377         var selectionHasAnchorAndFocus = util.areHostProperties(testSelection, | 
| bsw/jbe@1309 | 3378             ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]); | 
| bsw/jbe@1309 | 3379 | 
| bsw/jbe@1309 | 3380         features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus; | 
| bsw/jbe@1309 | 3381 | 
| bsw/jbe@1309 | 3382         // Test for existence of native selection extend() method | 
| bsw/jbe@1309 | 3383         var selectionHasExtend = isHostMethod(testSelection, "extend"); | 
| bsw/jbe@1309 | 3384         features.selectionHasExtend = selectionHasExtend; | 
| bsw/jbe@1309 | 3385 | 
| bsw/jbe@1309 | 3386         // Test if rangeCount exists | 
| bsw/jbe@1309 | 3387         var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER); | 
| bsw/jbe@1309 | 3388         features.selectionHasRangeCount = selectionHasRangeCount; | 
| bsw/jbe@1309 | 3389 | 
| bsw/jbe@1309 | 3390         var selectionSupportsMultipleRanges = false; | 
| bsw/jbe@1309 | 3391         var collapsedNonEditableSelectionsSupported = true; | 
| bsw/jbe@1309 | 3392 | 
| bsw/jbe@1309 | 3393         var addRangeBackwardToNative = selectionHasExtend ? | 
| bsw/jbe@1309 | 3394             function(nativeSelection, range) { | 
| bsw/jbe@1309 | 3395                 var doc = DomRange.getRangeDocument(range); | 
| bsw/jbe@1309 | 3396                 var endRange = api.createRange(doc); | 
| bsw/jbe@1309 | 3397                 endRange.collapseToPoint(range.endContainer, range.endOffset); | 
| bsw/jbe@1309 | 3398                 nativeSelection.addRange(getNativeRange(endRange)); | 
| bsw/jbe@1309 | 3399                 nativeSelection.extend(range.startContainer, range.startOffset); | 
| bsw/jbe@1309 | 3400             } : null; | 
| bsw/jbe@1309 | 3401 | 
| bsw/jbe@1309 | 3402         if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) && | 
| bsw/jbe@1309 | 3403                 typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) { | 
| bsw/jbe@1309 | 3404 | 
| bsw/jbe@1309 | 3405             (function() { | 
| bsw/jbe@1309 | 3406                 // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are | 
| bsw/jbe@1309 | 3407                 // performed on the current document's selection. See issue 109. | 
| bsw/jbe@1309 | 3408 | 
| bsw/jbe@1309 | 3409                 // Note also that if a selection previously existed, it is wiped and later restored by these tests. This | 
| bsw/jbe@1309 | 3410                 // will result in the selection direction begin reversed if the original selection was backwards and the | 
| bsw/jbe@1309 | 3411                 // browser does not support setting backwards selections (Internet Explorer, I'm looking at you). | 
| bsw/jbe@1309 | 3412                 var sel = window.getSelection(); | 
| bsw/jbe@1309 | 3413                 if (sel) { | 
| bsw/jbe@1309 | 3414                     // Store the current selection | 
| bsw/jbe@1309 | 3415                     var originalSelectionRangeCount = sel.rangeCount; | 
| bsw/jbe@1309 | 3416                     var selectionHasMultipleRanges = (originalSelectionRangeCount > 1); | 
| bsw/jbe@1309 | 3417                     var originalSelectionRanges = []; | 
| bsw/jbe@1309 | 3418                     var originalSelectionBackward = winSelectionIsBackward(sel); | 
| bsw/jbe@1309 | 3419                     for (var i = 0; i < originalSelectionRangeCount; ++i) { | 
| bsw/jbe@1309 | 3420                         originalSelectionRanges[i] = sel.getRangeAt(i); | 
| bsw/jbe@1309 | 3421                     } | 
| bsw/jbe@1309 | 3422 | 
| bsw/jbe@1309 | 3423                     // Create some test elements | 
| bsw/jbe@1309 | 3424                     var testEl = dom.createTestElement(document, "", false); | 
| bsw/jbe@1309 | 3425                     var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") ); | 
| bsw/jbe@1309 | 3426 | 
| bsw/jbe@1309 | 3427                     // Test whether the native selection will allow a collapsed selection within a non-editable element | 
| bsw/jbe@1309 | 3428                     var r1 = document.createRange(); | 
| bsw/jbe@1309 | 3429 | 
| bsw/jbe@1309 | 3430                     r1.setStart(textNode, 1); | 
| bsw/jbe@1309 | 3431                     r1.collapse(true); | 
| bsw/jbe@1309 | 3432                     sel.removeAllRanges(); | 
| bsw/jbe@1309 | 3433                     sel.addRange(r1); | 
| bsw/jbe@1309 | 3434                     collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1); | 
| bsw/jbe@1309 | 3435                     sel.removeAllRanges(); | 
| bsw/jbe@1309 | 3436 | 
| bsw/jbe@1309 | 3437                     // Test whether the native selection is capable of supporting multiple ranges. | 
| bsw/jbe@1309 | 3438                     if (!selectionHasMultipleRanges) { | 
| bsw/jbe@1309 | 3439                         // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a | 
| bsw/jbe@1309 | 3440                         // console error of "Discontiguous selection is not supported." that cannot be suppressed. There's | 
| bsw/jbe@1309 | 3441                         // nothing we can do about this while retaining the feature test so we have to resort to a browser | 
| bsw/jbe@1309 | 3442                         // sniff. I'm not happy about it. See | 
| bsw/jbe@1309 | 3443                         // https://code.google.com/p/chromium/issues/detail?id=399791 | 
| bsw/jbe@1309 | 3444                         var chromeMatch = window.navigator.appVersion.match(/Chrome\/(.*?) /); | 
| bsw/jbe@1309 | 3445                         if (chromeMatch && parseInt(chromeMatch[1]) >= 36) { | 
| bsw/jbe@1309 | 3446                             selectionSupportsMultipleRanges = false; | 
| bsw/jbe@1309 | 3447                         } else { | 
| bsw/jbe@1309 | 3448                             var r2 = r1.cloneRange(); | 
| bsw/jbe@1309 | 3449                             r1.setStart(textNode, 0); | 
| bsw/jbe@1309 | 3450                             r2.setEnd(textNode, 3); | 
| bsw/jbe@1309 | 3451                             r2.setStart(textNode, 2); | 
| bsw/jbe@1309 | 3452                             sel.addRange(r1); | 
| bsw/jbe@1309 | 3453                             sel.addRange(r2); | 
| bsw/jbe@1309 | 3454                             selectionSupportsMultipleRanges = (sel.rangeCount == 2); | 
| bsw/jbe@1309 | 3455                         } | 
| bsw/jbe@1309 | 3456                     } | 
| bsw/jbe@1309 | 3457 | 
| bsw/jbe@1309 | 3458                     // Clean up | 
| bsw/jbe@1309 | 3459                     dom.removeNode(testEl); | 
| bsw/jbe@1309 | 3460                     sel.removeAllRanges(); | 
| bsw/jbe@1309 | 3461 | 
| bsw/jbe@1309 | 3462                     for (i = 0; i < originalSelectionRangeCount; ++i) { | 
| bsw/jbe@1309 | 3463                         if (i == 0 && originalSelectionBackward) { | 
| bsw/jbe@1309 | 3464                             if (addRangeBackwardToNative) { | 
| bsw/jbe@1309 | 3465                                 addRangeBackwardToNative(sel, originalSelectionRanges[i]); | 
| bsw/jbe@1309 | 3466                             } else { | 
| bsw/jbe@1309 | 3467                                 api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend"); | 
| bsw/jbe@1309 | 3468                                 sel.addRange(originalSelectionRanges[i]); | 
| bsw/jbe@1309 | 3469                             } | 
| bsw/jbe@1309 | 3470                         } else { | 
| bsw/jbe@1309 | 3471                             sel.addRange(originalSelectionRanges[i]); | 
| bsw/jbe@1309 | 3472                         } | 
| bsw/jbe@1309 | 3473                     } | 
| bsw/jbe@1309 | 3474                 } | 
| bsw/jbe@1309 | 3475             })(); | 
| bsw/jbe@1309 | 3476         } | 
| bsw/jbe@1309 | 3477 | 
| bsw/jbe@1309 | 3478         features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges; | 
| bsw/jbe@1309 | 3479         features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported; | 
| bsw/jbe@1309 | 3480 | 
| bsw/jbe@1309 | 3481         // ControlRanges | 
| bsw/jbe@1309 | 3482         var implementsControlRange = false, testControlRange; | 
| bsw/jbe@1309 | 3483 | 
| bsw/jbe@1309 | 3484         if (body && isHostMethod(body, "createControlRange")) { | 
| bsw/jbe@1309 | 3485             testControlRange = body.createControlRange(); | 
| bsw/jbe@1309 | 3486             if (util.areHostProperties(testControlRange, ["item", "add"])) { | 
| bsw/jbe@1309 | 3487                 implementsControlRange = true; | 
| bsw/jbe@1309 | 3488             } | 
| bsw/jbe@1309 | 3489         } | 
| bsw/jbe@1309 | 3490         features.implementsControlRange = implementsControlRange; | 
| bsw/jbe@1309 | 3491 | 
| bsw/jbe@1309 | 3492         // Selection collapsedness | 
| bsw/jbe@1309 | 3493         if (selectionHasAnchorAndFocus) { | 
| bsw/jbe@1309 | 3494             selectionIsCollapsed = function(sel) { | 
| bsw/jbe@1309 | 3495                 return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset; | 
| bsw/jbe@1309 | 3496             }; | 
| bsw/jbe@1309 | 3497         } else { | 
| bsw/jbe@1309 | 3498             selectionIsCollapsed = function(sel) { | 
| bsw/jbe@1309 | 3499                 return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false; | 
| bsw/jbe@1309 | 3500             }; | 
| bsw/jbe@1309 | 3501         } | 
| bsw/jbe@1309 | 3502 | 
| bsw/jbe@1309 | 3503         function updateAnchorAndFocusFromRange(sel, range, backward) { | 
| bsw/jbe@1309 | 3504             var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end"; | 
| bsw/jbe@1309 | 3505             sel.anchorNode = range[anchorPrefix + "Container"]; | 
| bsw/jbe@1309 | 3506             sel.anchorOffset = range[anchorPrefix + "Offset"]; | 
| bsw/jbe@1309 | 3507             sel.focusNode = range[focusPrefix + "Container"]; | 
| bsw/jbe@1309 | 3508             sel.focusOffset = range[focusPrefix + "Offset"]; | 
| bsw/jbe@1309 | 3509         } | 
| bsw/jbe@1309 | 3510 | 
| bsw/jbe@1309 | 3511         function updateAnchorAndFocusFromNativeSelection(sel) { | 
| bsw/jbe@1309 | 3512             var nativeSel = sel.nativeSelection; | 
| bsw/jbe@1309 | 3513             sel.anchorNode = nativeSel.anchorNode; | 
| bsw/jbe@1309 | 3514             sel.anchorOffset = nativeSel.anchorOffset; | 
| bsw/jbe@1309 | 3515             sel.focusNode = nativeSel.focusNode; | 
| bsw/jbe@1309 | 3516             sel.focusOffset = nativeSel.focusOffset; | 
| bsw/jbe@1309 | 3517         } | 
| bsw/jbe@1309 | 3518 | 
| bsw/jbe@1309 | 3519         function updateEmptySelection(sel) { | 
| bsw/jbe@1309 | 3520             sel.anchorNode = sel.focusNode = null; | 
| bsw/jbe@1309 | 3521             sel.anchorOffset = sel.focusOffset = 0; | 
| bsw/jbe@1309 | 3522             sel.rangeCount = 0; | 
| bsw/jbe@1309 | 3523             sel.isCollapsed = true; | 
| bsw/jbe@1309 | 3524             sel._ranges.length = 0; | 
| bsw/jbe@1309 | 3525         } | 
| bsw/jbe@1309 | 3526 | 
| bsw/jbe@1309 | 3527         function getNativeRange(range) { | 
| bsw/jbe@1309 | 3528             var nativeRange; | 
| bsw/jbe@1309 | 3529             if (range instanceof DomRange) { | 
| bsw/jbe@1309 | 3530                 nativeRange = api.createNativeRange(range.getDocument()); | 
| bsw/jbe@1309 | 3531                 nativeRange.setEnd(range.endContainer, range.endOffset); | 
| bsw/jbe@1309 | 3532                 nativeRange.setStart(range.startContainer, range.startOffset); | 
| bsw/jbe@1309 | 3533             } else if (range instanceof WrappedRange) { | 
| bsw/jbe@1309 | 3534                 nativeRange = range.nativeRange; | 
| bsw/jbe@1309 | 3535             } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) { | 
| bsw/jbe@1309 | 3536                 nativeRange = range; | 
| bsw/jbe@1309 | 3537             } | 
| bsw/jbe@1309 | 3538             return nativeRange; | 
| bsw/jbe@1309 | 3539         } | 
| bsw/jbe@1309 | 3540 | 
| bsw/jbe@1309 | 3541         function rangeContainsSingleElement(rangeNodes) { | 
| bsw/jbe@1309 | 3542             if (!rangeNodes.length || rangeNodes[0].nodeType != 1) { | 
| bsw/jbe@1309 | 3543                 return false; | 
| bsw/jbe@1309 | 3544             } | 
| bsw/jbe@1309 | 3545             for (var i = 1, len = rangeNodes.length; i < len; ++i) { | 
| bsw/jbe@1309 | 3546                 if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) { | 
| bsw/jbe@1309 | 3547                     return false; | 
| bsw/jbe@1309 | 3548                 } | 
| bsw/jbe@1309 | 3549             } | 
| bsw/jbe@1309 | 3550             return true; | 
| bsw/jbe@1309 | 3551         } | 
| bsw/jbe@1309 | 3552 | 
| bsw/jbe@1309 | 3553         function getSingleElementFromRange(range) { | 
| bsw/jbe@1309 | 3554             var nodes = range.getNodes(); | 
| bsw/jbe@1309 | 3555             if (!rangeContainsSingleElement(nodes)) { | 
| bsw/jbe@1309 | 3556                 throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element"); | 
| bsw/jbe@1309 | 3557             } | 
| bsw/jbe@1309 | 3558             return nodes[0]; | 
| bsw/jbe@1309 | 3559         } | 
| bsw/jbe@1309 | 3560 | 
| bsw/jbe@1309 | 3561         // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange | 
| bsw/jbe@1309 | 3562         function isTextRange(range) { | 
| bsw/jbe@1309 | 3563             return !!range && typeof range.text != "undefined"; | 
| bsw/jbe@1309 | 3564         } | 
| bsw/jbe@1309 | 3565 | 
| bsw/jbe@1309 | 3566         function updateFromTextRange(sel, range) { | 
| bsw/jbe@1309 | 3567             // Create a Range from the selected TextRange | 
| bsw/jbe@1309 | 3568             var wrappedRange = new WrappedRange(range); | 
| bsw/jbe@1309 | 3569             sel._ranges = [wrappedRange]; | 
| bsw/jbe@1309 | 3570 | 
| bsw/jbe@1309 | 3571             updateAnchorAndFocusFromRange(sel, wrappedRange, false); | 
| bsw/jbe@1309 | 3572             sel.rangeCount = 1; | 
| bsw/jbe@1309 | 3573             sel.isCollapsed = wrappedRange.collapsed; | 
| bsw/jbe@1309 | 3574         } | 
| bsw/jbe@1309 | 3575 | 
| bsw/jbe@1309 | 3576         function updateControlSelection(sel) { | 
| bsw/jbe@1309 | 3577             // Update the wrapped selection based on what's now in the native selection | 
| bsw/jbe@1309 | 3578             sel._ranges.length = 0; | 
| bsw/jbe@1309 | 3579             if (sel.docSelection.type == "None") { | 
| bsw/jbe@1309 | 3580                 updateEmptySelection(sel); | 
| bsw/jbe@1309 | 3581             } else { | 
| bsw/jbe@1309 | 3582                 var controlRange = sel.docSelection.createRange(); | 
| bsw/jbe@1309 | 3583                 if (isTextRange(controlRange)) { | 
| bsw/jbe@1309 | 3584                     // This case (where the selection type is "Control" and calling createRange() on the selection returns | 
| bsw/jbe@1309 | 3585                     // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected | 
| bsw/jbe@1309 | 3586                     // ControlRange have been removed from the ControlRange and removed from the document. | 
| bsw/jbe@1309 | 3587                     updateFromTextRange(sel, controlRange); | 
| bsw/jbe@1309 | 3588                 } else { | 
| bsw/jbe@1309 | 3589                     sel.rangeCount = controlRange.length; | 
| bsw/jbe@1309 | 3590                     var range, doc = getDocument(controlRange.item(0)); | 
| bsw/jbe@1309 | 3591                     for (var i = 0; i < sel.rangeCount; ++i) { | 
| bsw/jbe@1309 | 3592                         range = api.createRange(doc); | 
| bsw/jbe@1309 | 3593                         range.selectNode(controlRange.item(i)); | 
| bsw/jbe@1309 | 3594                         sel._ranges.push(range); | 
| bsw/jbe@1309 | 3595                     } | 
| bsw/jbe@1309 | 3596                     sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed; | 
| bsw/jbe@1309 | 3597                     updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false); | 
| bsw/jbe@1309 | 3598                 } | 
| bsw/jbe@1309 | 3599             } | 
| bsw/jbe@1309 | 3600         } | 
| bsw/jbe@1309 | 3601 | 
| bsw/jbe@1309 | 3602         function addRangeToControlSelection(sel, range) { | 
| bsw/jbe@1309 | 3603             var controlRange = sel.docSelection.createRange(); | 
| bsw/jbe@1309 | 3604             var rangeElement = getSingleElementFromRange(range); | 
| bsw/jbe@1309 | 3605 | 
| bsw/jbe@1309 | 3606             // Create a new ControlRange containing all the elements in the selected ControlRange plus the element | 
| bsw/jbe@1309 | 3607             // contained by the supplied range | 
| bsw/jbe@1309 | 3608             var doc = getDocument(controlRange.item(0)); | 
| bsw/jbe@1309 | 3609             var newControlRange = getBody(doc).createControlRange(); | 
| bsw/jbe@1309 | 3610             for (var i = 0, len = controlRange.length; i < len; ++i) { | 
| bsw/jbe@1309 | 3611                 newControlRange.add(controlRange.item(i)); | 
| bsw/jbe@1309 | 3612             } | 
| bsw/jbe@1309 | 3613             try { | 
| bsw/jbe@1309 | 3614                 newControlRange.add(rangeElement); | 
| bsw/jbe@1309 | 3615             } catch (ex) { | 
| bsw/jbe@1309 | 3616                 throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)"); | 
| bsw/jbe@1309 | 3617             } | 
| bsw/jbe@1309 | 3618             newControlRange.select(); | 
| bsw/jbe@1309 | 3619 | 
| bsw/jbe@1309 | 3620             // Update the wrapped selection based on what's now in the native selection | 
| bsw/jbe@1309 | 3621             updateControlSelection(sel); | 
| bsw/jbe@1309 | 3622         } | 
| bsw/jbe@1309 | 3623 | 
| bsw/jbe@1309 | 3624         var getSelectionRangeAt; | 
| bsw/jbe@1309 | 3625 | 
| bsw/jbe@1309 | 3626         if (isHostMethod(testSelection, "getRangeAt")) { | 
| bsw/jbe@1309 | 3627             // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation. | 
| bsw/jbe@1309 | 3628             // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a | 
| bsw/jbe@1309 | 3629             // lesson to us all, especially me. | 
| bsw/jbe@1309 | 3630             getSelectionRangeAt = function(sel, index) { | 
| bsw/jbe@1309 | 3631                 try { | 
| bsw/jbe@1309 | 3632                     return sel.getRangeAt(index); | 
| bsw/jbe@1309 | 3633                 } catch (ex) { | 
| bsw/jbe@1309 | 3634                     return null; | 
| bsw/jbe@1309 | 3635                 } | 
| bsw/jbe@1309 | 3636             }; | 
| bsw/jbe@1309 | 3637         } else if (selectionHasAnchorAndFocus) { | 
| bsw/jbe@1309 | 3638             getSelectionRangeAt = function(sel) { | 
| bsw/jbe@1309 | 3639                 var doc = getDocument(sel.anchorNode); | 
| bsw/jbe@1309 | 3640                 var range = api.createRange(doc); | 
| bsw/jbe@1309 | 3641                 range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset); | 
| bsw/jbe@1309 | 3642 | 
| bsw/jbe@1309 | 3643                 // Handle the case when the selection was selected backwards (from the end to the start in the | 
| bsw/jbe@1309 | 3644                 // document) | 
| bsw/jbe@1309 | 3645                 if (range.collapsed !== this.isCollapsed) { | 
| bsw/jbe@1309 | 3646                     range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset); | 
| bsw/jbe@1309 | 3647                 } | 
| bsw/jbe@1309 | 3648 | 
| bsw/jbe@1309 | 3649                 return range; | 
| bsw/jbe@1309 | 3650             }; | 
| bsw/jbe@1309 | 3651         } | 
| bsw/jbe@1309 | 3652 | 
| bsw/jbe@1309 | 3653         function WrappedSelection(selection, docSelection, win) { | 
| bsw/jbe@1309 | 3654             this.nativeSelection = selection; | 
| bsw/jbe@1309 | 3655             this.docSelection = docSelection; | 
| bsw/jbe@1309 | 3656             this._ranges = []; | 
| bsw/jbe@1309 | 3657             this.win = win; | 
| bsw/jbe@1309 | 3658             this.refresh(); | 
| bsw/jbe@1309 | 3659         } | 
| bsw/jbe@1309 | 3660 | 
| bsw/jbe@1309 | 3661         WrappedSelection.prototype = api.selectionPrototype; | 
| bsw/jbe@1309 | 3662 | 
| bsw/jbe@1309 | 3663         function deleteProperties(sel) { | 
| bsw/jbe@1309 | 3664             sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null; | 
| bsw/jbe@1309 | 3665             sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0; | 
| bsw/jbe@1309 | 3666             sel.detached = true; | 
| bsw/jbe@1309 | 3667         } | 
| bsw/jbe@1309 | 3668 | 
| bsw/jbe@1309 | 3669         var cachedRangySelections = []; | 
| bsw/jbe@1309 | 3670 | 
| bsw/jbe@1309 | 3671         function actOnCachedSelection(win, action) { | 
| bsw/jbe@1309 | 3672             var i = cachedRangySelections.length, cached, sel; | 
| bsw/jbe@1309 | 3673             while (i--) { | 
| bsw/jbe@1309 | 3674                 cached = cachedRangySelections[i]; | 
| bsw/jbe@1309 | 3675                 sel = cached.selection; | 
| bsw/jbe@1309 | 3676                 if (action == "deleteAll") { | 
| bsw/jbe@1309 | 3677                     deleteProperties(sel); | 
| bsw/jbe@1309 | 3678                 } else if (cached.win == win) { | 
| bsw/jbe@1309 | 3679                     if (action == "delete") { | 
| bsw/jbe@1309 | 3680                         cachedRangySelections.splice(i, 1); | 
| bsw/jbe@1309 | 3681                         return true; | 
| bsw/jbe@1309 | 3682                     } else { | 
| bsw/jbe@1309 | 3683                         return sel; | 
| bsw/jbe@1309 | 3684                     } | 
| bsw/jbe@1309 | 3685                 } | 
| bsw/jbe@1309 | 3686             } | 
| bsw/jbe@1309 | 3687             if (action == "deleteAll") { | 
| bsw/jbe@1309 | 3688                 cachedRangySelections.length = 0; | 
| bsw/jbe@1309 | 3689             } | 
| bsw/jbe@1309 | 3690             return null; | 
| bsw/jbe@1309 | 3691         } | 
| bsw/jbe@1309 | 3692 | 
| bsw/jbe@1309 | 3693         var getSelection = function(win) { | 
| bsw/jbe@1309 | 3694             // Check if the parameter is a Rangy Selection object | 
| bsw/jbe@1309 | 3695             if (win && win instanceof WrappedSelection) { | 
| bsw/jbe@1309 | 3696                 win.refresh(); | 
| bsw/jbe@1309 | 3697                 return win; | 
| bsw/jbe@1309 | 3698             } | 
| bsw/jbe@1309 | 3699 | 
| bsw/jbe@1309 | 3700             win = getWindow(win, "getNativeSelection"); | 
| bsw/jbe@1309 | 3701 | 
| bsw/jbe@1309 | 3702             var sel = actOnCachedSelection(win); | 
| bsw/jbe@1309 | 3703             var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null; | 
| bsw/jbe@1309 | 3704             if (sel) { | 
| bsw/jbe@1309 | 3705                 sel.nativeSelection = nativeSel; | 
| bsw/jbe@1309 | 3706                 sel.docSelection = docSel; | 
| bsw/jbe@1309 | 3707                 sel.refresh(); | 
| bsw/jbe@1309 | 3708             } else { | 
| bsw/jbe@1309 | 3709                 sel = new WrappedSelection(nativeSel, docSel, win); | 
| bsw/jbe@1309 | 3710                 cachedRangySelections.push( { win: win, selection: sel } ); | 
| bsw/jbe@1309 | 3711             } | 
| bsw/jbe@1309 | 3712             return sel; | 
| bsw/jbe@1309 | 3713         }; | 
| bsw/jbe@1309 | 3714 | 
| bsw/jbe@1309 | 3715         api.getSelection = getSelection; | 
| bsw/jbe@1309 | 3716 | 
| bsw/jbe@1309 | 3717         util.createAliasForDeprecatedMethod(api, "getIframeSelection", "getSelection"); | 
| bsw/jbe@1309 | 3718 | 
| bsw/jbe@1309 | 3719         var selProto = WrappedSelection.prototype; | 
| bsw/jbe@1309 | 3720 | 
| bsw/jbe@1309 | 3721         function createControlSelection(sel, ranges) { | 
| bsw/jbe@1309 | 3722             // Ensure that the selection becomes of type "Control" | 
| bsw/jbe@1309 | 3723             var doc = getDocument(ranges[0].startContainer); | 
| bsw/jbe@1309 | 3724             var controlRange = getBody(doc).createControlRange(); | 
| bsw/jbe@1309 | 3725             for (var i = 0, el, len = ranges.length; i < len; ++i) { | 
| bsw/jbe@1309 | 3726                 el = getSingleElementFromRange(ranges[i]); | 
| bsw/jbe@1309 | 3727                 try { | 
| bsw/jbe@1309 | 3728                     controlRange.add(el); | 
| bsw/jbe@1309 | 3729                 } catch (ex) { | 
| bsw/jbe@1309 | 3730                     throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)"); | 
| bsw/jbe@1309 | 3731                 } | 
| bsw/jbe@1309 | 3732             } | 
| bsw/jbe@1309 | 3733             controlRange.select(); | 
| bsw/jbe@1309 | 3734 | 
| bsw/jbe@1309 | 3735             // Update the wrapped selection based on what's now in the native selection | 
| bsw/jbe@1309 | 3736             updateControlSelection(sel); | 
| bsw/jbe@1309 | 3737         } | 
| bsw/jbe@1309 | 3738 | 
| bsw/jbe@1309 | 3739         // Selecting a range | 
| bsw/jbe@1309 | 3740         if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) { | 
| bsw/jbe@1309 | 3741             selProto.removeAllRanges = function() { | 
| bsw/jbe@1309 | 3742                 this.nativeSelection.removeAllRanges(); | 
| bsw/jbe@1309 | 3743                 updateEmptySelection(this); | 
| bsw/jbe@1309 | 3744             }; | 
| bsw/jbe@1309 | 3745 | 
| bsw/jbe@1309 | 3746             var addRangeBackward = function(sel, range) { | 
| bsw/jbe@1309 | 3747                 addRangeBackwardToNative(sel.nativeSelection, range); | 
| bsw/jbe@1309 | 3748                 sel.refresh(); | 
| bsw/jbe@1309 | 3749             }; | 
| bsw/jbe@1309 | 3750 | 
| bsw/jbe@1309 | 3751             if (selectionHasRangeCount) { | 
| bsw/jbe@1309 | 3752                 selProto.addRange = function(range, direction) { | 
| bsw/jbe@1309 | 3753                     if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) { | 
| bsw/jbe@1309 | 3754                         addRangeToControlSelection(this, range); | 
| bsw/jbe@1309 | 3755                     } else { | 
| bsw/jbe@1309 | 3756                         if (isDirectionBackward(direction) && selectionHasExtend) { | 
| bsw/jbe@1309 | 3757                             addRangeBackward(this, range); | 
| bsw/jbe@1309 | 3758                         } else { | 
| bsw/jbe@1309 | 3759                             var previousRangeCount; | 
| bsw/jbe@1309 | 3760                             if (selectionSupportsMultipleRanges) { | 
| bsw/jbe@1309 | 3761                                 previousRangeCount = this.rangeCount; | 
| bsw/jbe@1309 | 3762                             } else { | 
| bsw/jbe@1309 | 3763                                 this.removeAllRanges(); | 
| bsw/jbe@1309 | 3764                                 previousRangeCount = 0; | 
| bsw/jbe@1309 | 3765                             } | 
| bsw/jbe@1309 | 3766                             // Clone the native range so that changing the selected range does not affect the selection. | 
| bsw/jbe@1309 | 3767                             // This is contrary to the spec but is the only way to achieve consistency between browsers. See | 
| bsw/jbe@1309 | 3768                             // issue 80. | 
| bsw/jbe@1309 | 3769                             var clonedNativeRange = getNativeRange(range).cloneRange(); | 
| bsw/jbe@1309 | 3770                             try { | 
| bsw/jbe@1309 | 3771                                 this.nativeSelection.addRange(clonedNativeRange); | 
| bsw/jbe@1309 | 3772                             } catch (ex) { | 
| bsw/jbe@1309 | 3773                             } | 
| bsw/jbe@1309 | 3774 | 
| bsw/jbe@1309 | 3775                             // Check whether adding the range was successful | 
| bsw/jbe@1309 | 3776                             this.rangeCount = this.nativeSelection.rangeCount; | 
| bsw/jbe@1309 | 3777 | 
| bsw/jbe@1309 | 3778                             if (this.rangeCount == previousRangeCount + 1) { | 
| bsw/jbe@1309 | 3779                                 // The range was added successfully | 
| bsw/jbe@1309 | 3780 | 
| bsw/jbe@1309 | 3781                                 // Check whether the range that we added to the selection is reflected in the last range extracted from | 
| bsw/jbe@1309 | 3782                                 // the selection | 
| bsw/jbe@1309 | 3783                                 if (api.config.checkSelectionRanges) { | 
| bsw/jbe@1309 | 3784                                     var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1); | 
| bsw/jbe@1309 | 3785                                     if (nativeRange && !rangesEqual(nativeRange, range)) { | 
| bsw/jbe@1309 | 3786                                         // Happens in WebKit with, for example, a selection placed at the start of a text node | 
| bsw/jbe@1309 | 3787                                         range = new WrappedRange(nativeRange); | 
| bsw/jbe@1309 | 3788                                     } | 
| bsw/jbe@1309 | 3789                                 } | 
| bsw/jbe@1309 | 3790                                 this._ranges[this.rangeCount - 1] = range; | 
| bsw/jbe@1309 | 3791                                 updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection)); | 
| bsw/jbe@1309 | 3792                                 this.isCollapsed = selectionIsCollapsed(this); | 
| bsw/jbe@1309 | 3793                             } else { | 
| bsw/jbe@1309 | 3794                                 // The range was not added successfully. The simplest thing is to refresh | 
| bsw/jbe@1309 | 3795                                 this.refresh(); | 
| bsw/jbe@1309 | 3796                             } | 
| bsw/jbe@1309 | 3797                         } | 
| bsw/jbe@1309 | 3798                     } | 
| bsw/jbe@1309 | 3799                 }; | 
| bsw/jbe@1309 | 3800             } else { | 
| bsw/jbe@1309 | 3801                 selProto.addRange = function(range, direction) { | 
| bsw/jbe@1309 | 3802                     if (isDirectionBackward(direction) && selectionHasExtend) { | 
| bsw/jbe@1309 | 3803                         addRangeBackward(this, range); | 
| bsw/jbe@1309 | 3804                     } else { | 
| bsw/jbe@1309 | 3805                         this.nativeSelection.addRange(getNativeRange(range)); | 
| bsw/jbe@1309 | 3806                         this.refresh(); | 
| bsw/jbe@1309 | 3807                     } | 
| bsw/jbe@1309 | 3808                 }; | 
| bsw/jbe@1309 | 3809             } | 
| bsw/jbe@1309 | 3810 | 
| bsw/jbe@1309 | 3811             selProto.setRanges = function(ranges) { | 
| bsw/jbe@1309 | 3812                 if (implementsControlRange && implementsDocSelection && ranges.length > 1) { | 
| bsw/jbe@1309 | 3813                     createControlSelection(this, ranges); | 
| bsw/jbe@1309 | 3814                 } else { | 
| bsw/jbe@1309 | 3815                     this.removeAllRanges(); | 
| bsw/jbe@1309 | 3816                     for (var i = 0, len = ranges.length; i < len; ++i) { | 
| bsw/jbe@1309 | 3817                         this.addRange(ranges[i]); | 
| bsw/jbe@1309 | 3818                     } | 
| bsw/jbe@1309 | 3819                 } | 
| bsw/jbe@1309 | 3820             }; | 
| bsw/jbe@1309 | 3821         } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") && | 
| bsw/jbe@1309 | 3822                    implementsControlRange && useDocumentSelection) { | 
| bsw/jbe@1309 | 3823 | 
| bsw/jbe@1309 | 3824             selProto.removeAllRanges = function() { | 
| bsw/jbe@1309 | 3825                 // Added try/catch as fix for issue #21 | 
| bsw/jbe@1309 | 3826                 try { | 
| bsw/jbe@1309 | 3827                     this.docSelection.empty(); | 
| bsw/jbe@1309 | 3828 | 
| bsw/jbe@1309 | 3829                     // Check for empty() not working (issue #24) | 
| bsw/jbe@1309 | 3830                     if (this.docSelection.type != "None") { | 
| bsw/jbe@1309 | 3831                         // Work around failure to empty a control selection by instead selecting a TextRange and then | 
| bsw/jbe@1309 | 3832                         // calling empty() | 
| bsw/jbe@1309 | 3833                         var doc; | 
| bsw/jbe@1309 | 3834                         if (this.anchorNode) { | 
| bsw/jbe@1309 | 3835                             doc = getDocument(this.anchorNode); | 
| bsw/jbe@1309 | 3836                         } else if (this.docSelection.type == CONTROL) { | 
| bsw/jbe@1309 | 3837                             var controlRange = this.docSelection.createRange(); | 
| bsw/jbe@1309 | 3838                             if (controlRange.length) { | 
| bsw/jbe@1309 | 3839                                 doc = getDocument( controlRange.item(0) ); | 
| bsw/jbe@1309 | 3840                             } | 
| bsw/jbe@1309 | 3841                         } | 
| bsw/jbe@1309 | 3842                         if (doc) { | 
| bsw/jbe@1309 | 3843                             var textRange = getBody(doc).createTextRange(); | 
| bsw/jbe@1309 | 3844                             textRange.select(); | 
| bsw/jbe@1309 | 3845                             this.docSelection.empty(); | 
| bsw/jbe@1309 | 3846                         } | 
| bsw/jbe@1309 | 3847                     } | 
| bsw/jbe@1309 | 3848                 } catch(ex) {} | 
| bsw/jbe@1309 | 3849                 updateEmptySelection(this); | 
| bsw/jbe@1309 | 3850             }; | 
| bsw/jbe@1309 | 3851 | 
| bsw/jbe@1309 | 3852             selProto.addRange = function(range) { | 
| bsw/jbe@1309 | 3853                 if (this.docSelection.type == CONTROL) { | 
| bsw/jbe@1309 | 3854                     addRangeToControlSelection(this, range); | 
| bsw/jbe@1309 | 3855                 } else { | 
| bsw/jbe@1309 | 3856                     api.WrappedTextRange.rangeToTextRange(range).select(); | 
| bsw/jbe@1309 | 3857                     this._ranges[0] = range; | 
| bsw/jbe@1309 | 3858                     this.rangeCount = 1; | 
| bsw/jbe@1309 | 3859                     this.isCollapsed = this._ranges[0].collapsed; | 
| bsw/jbe@1309 | 3860                     updateAnchorAndFocusFromRange(this, range, false); | 
| bsw/jbe@1309 | 3861                 } | 
| bsw/jbe@1309 | 3862             }; | 
| bsw/jbe@1309 | 3863 | 
| bsw/jbe@1309 | 3864             selProto.setRanges = function(ranges) { | 
| bsw/jbe@1309 | 3865                 this.removeAllRanges(); | 
| bsw/jbe@1309 | 3866                 var rangeCount = ranges.length; | 
| bsw/jbe@1309 | 3867                 if (rangeCount > 1) { | 
| bsw/jbe@1309 | 3868                     createControlSelection(this, ranges); | 
| bsw/jbe@1309 | 3869                 } else if (rangeCount) { | 
| bsw/jbe@1309 | 3870                     this.addRange(ranges[0]); | 
| bsw/jbe@1309 | 3871                 } | 
| bsw/jbe@1309 | 3872             }; | 
| bsw/jbe@1309 | 3873         } else { | 
| bsw/jbe@1309 | 3874             module.fail("No means of selecting a Range or TextRange was found"); | 
| bsw/jbe@1309 | 3875             return false; | 
| bsw/jbe@1309 | 3876         } | 
| bsw/jbe@1309 | 3877 | 
| bsw/jbe@1309 | 3878         selProto.getRangeAt = function(index) { | 
| bsw/jbe@1309 | 3879             if (index < 0 || index >= this.rangeCount) { | 
| bsw/jbe@1309 | 3880                 throw new DOMException("INDEX_SIZE_ERR"); | 
| bsw/jbe@1309 | 3881             } else { | 
| bsw/jbe@1309 | 3882                 // Clone the range to preserve selection-range independence. See issue 80. | 
| bsw/jbe@1309 | 3883                 return this._ranges[index].cloneRange(); | 
| bsw/jbe@1309 | 3884             } | 
| bsw/jbe@1309 | 3885         }; | 
| bsw/jbe@1309 | 3886 | 
| bsw/jbe@1309 | 3887         var refreshSelection; | 
| bsw/jbe@1309 | 3888 | 
| bsw/jbe@1309 | 3889         if (useDocumentSelection) { | 
| bsw/jbe@1309 | 3890             refreshSelection = function(sel) { | 
| bsw/jbe@1309 | 3891                 var range; | 
| bsw/jbe@1309 | 3892                 if (api.isSelectionValid(sel.win)) { | 
| bsw/jbe@1309 | 3893                     range = sel.docSelection.createRange(); | 
| bsw/jbe@1309 | 3894                 } else { | 
| bsw/jbe@1309 | 3895                     range = getBody(sel.win.document).createTextRange(); | 
| bsw/jbe@1309 | 3896                     range.collapse(true); | 
| bsw/jbe@1309 | 3897                 } | 
| bsw/jbe@1309 | 3898 | 
| bsw/jbe@1309 | 3899                 if (sel.docSelection.type == CONTROL) { | 
| bsw/jbe@1309 | 3900                     updateControlSelection(sel); | 
| bsw/jbe@1309 | 3901                 } else if (isTextRange(range)) { | 
| bsw/jbe@1309 | 3902                     updateFromTextRange(sel, range); | 
| bsw/jbe@1309 | 3903                 } else { | 
| bsw/jbe@1309 | 3904                     updateEmptySelection(sel); | 
| bsw/jbe@1309 | 3905                 } | 
| bsw/jbe@1309 | 3906             }; | 
| bsw/jbe@1309 | 3907         } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) { | 
| bsw/jbe@1309 | 3908             refreshSelection = function(sel) { | 
| bsw/jbe@1309 | 3909                 if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) { | 
| bsw/jbe@1309 | 3910                     updateControlSelection(sel); | 
| bsw/jbe@1309 | 3911                 } else { | 
| bsw/jbe@1309 | 3912                     sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount; | 
| bsw/jbe@1309 | 3913                     if (sel.rangeCount) { | 
| bsw/jbe@1309 | 3914                         for (var i = 0, len = sel.rangeCount; i < len; ++i) { | 
| bsw/jbe@1309 | 3915                             sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i)); | 
| bsw/jbe@1309 | 3916                         } | 
| bsw/jbe@1309 | 3917                         updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection)); | 
| bsw/jbe@1309 | 3918                         sel.isCollapsed = selectionIsCollapsed(sel); | 
| bsw/jbe@1309 | 3919                     } else { | 
| bsw/jbe@1309 | 3920                         updateEmptySelection(sel); | 
| bsw/jbe@1309 | 3921                     } | 
| bsw/jbe@1309 | 3922                 } | 
| bsw/jbe@1309 | 3923             }; | 
| bsw/jbe@1309 | 3924         } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) { | 
| bsw/jbe@1309 | 3925             refreshSelection = function(sel) { | 
| bsw/jbe@1309 | 3926                 var range, nativeSel = sel.nativeSelection; | 
| bsw/jbe@1309 | 3927                 if (nativeSel.anchorNode) { | 
| bsw/jbe@1309 | 3928                     range = getSelectionRangeAt(nativeSel, 0); | 
| bsw/jbe@1309 | 3929                     sel._ranges = [range]; | 
| bsw/jbe@1309 | 3930                     sel.rangeCount = 1; | 
| bsw/jbe@1309 | 3931                     updateAnchorAndFocusFromNativeSelection(sel); | 
| bsw/jbe@1309 | 3932                     sel.isCollapsed = selectionIsCollapsed(sel); | 
| bsw/jbe@1309 | 3933                 } else { | 
| bsw/jbe@1309 | 3934                     updateEmptySelection(sel); | 
| bsw/jbe@1309 | 3935                 } | 
| bsw/jbe@1309 | 3936             }; | 
| bsw/jbe@1309 | 3937         } else { | 
| bsw/jbe@1309 | 3938             module.fail("No means of obtaining a Range or TextRange from the user's selection was found"); | 
| bsw/jbe@1309 | 3939             return false; | 
| bsw/jbe@1309 | 3940         } | 
| bsw/jbe@1309 | 3941 | 
| bsw/jbe@1309 | 3942         selProto.refresh = function(checkForChanges) { | 
| bsw/jbe@1309 | 3943             var oldRanges = checkForChanges ? this._ranges.slice(0) : null; | 
| bsw/jbe@1309 | 3944             var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset; | 
| bsw/jbe@1309 | 3945 | 
| bsw/jbe@1309 | 3946             refreshSelection(this); | 
| bsw/jbe@1309 | 3947             if (checkForChanges) { | 
| bsw/jbe@1309 | 3948                 // Check the range count first | 
| bsw/jbe@1309 | 3949                 var i = oldRanges.length; | 
| bsw/jbe@1309 | 3950                 if (i != this._ranges.length) { | 
| bsw/jbe@1309 | 3951                     return true; | 
| bsw/jbe@1309 | 3952                 } | 
| bsw/jbe@1309 | 3953 | 
| bsw/jbe@1309 | 3954                 // Now check the direction. Checking the anchor position is the same is enough since we're checking all the | 
| bsw/jbe@1309 | 3955                 // ranges after this | 
| bsw/jbe@1309 | 3956                 if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) { | 
| bsw/jbe@1309 | 3957                     return true; | 
| bsw/jbe@1309 | 3958                 } | 
| bsw/jbe@1309 | 3959 | 
| bsw/jbe@1309 | 3960                 // Finally, compare each range in turn | 
| bsw/jbe@1309 | 3961                 while (i--) { | 
| bsw/jbe@1309 | 3962                     if (!rangesEqual(oldRanges[i], this._ranges[i])) { | 
| bsw/jbe@1309 | 3963                         return true; | 
| bsw/jbe@1309 | 3964                     } | 
| bsw/jbe@1309 | 3965                 } | 
| bsw/jbe@1309 | 3966                 return false; | 
| bsw/jbe@1309 | 3967             } | 
| bsw/jbe@1309 | 3968         }; | 
| bsw/jbe@1309 | 3969 | 
| bsw/jbe@1309 | 3970         // Removal of a single range | 
| bsw/jbe@1309 | 3971         var removeRangeManually = function(sel, range) { | 
| bsw/jbe@1309 | 3972             var ranges = sel.getAllRanges(); | 
| bsw/jbe@1309 | 3973             sel.removeAllRanges(); | 
| bsw/jbe@1309 | 3974             for (var i = 0, len = ranges.length; i < len; ++i) { | 
| bsw/jbe@1309 | 3975                 if (!rangesEqual(range, ranges[i])) { | 
| bsw/jbe@1309 | 3976                     sel.addRange(ranges[i]); | 
| bsw/jbe@1309 | 3977                 } | 
| bsw/jbe@1309 | 3978             } | 
| bsw/jbe@1309 | 3979             if (!sel.rangeCount) { | 
| bsw/jbe@1309 | 3980                 updateEmptySelection(sel); | 
| bsw/jbe@1309 | 3981             } | 
| bsw/jbe@1309 | 3982         }; | 
| bsw/jbe@1309 | 3983 | 
| bsw/jbe@1309 | 3984         if (implementsControlRange && implementsDocSelection) { | 
| bsw/jbe@1309 | 3985             selProto.removeRange = function(range) { | 
| bsw/jbe@1309 | 3986                 if (this.docSelection.type == CONTROL) { | 
| bsw/jbe@1309 | 3987                     var controlRange = this.docSelection.createRange(); | 
| bsw/jbe@1309 | 3988                     var rangeElement = getSingleElementFromRange(range); | 
| bsw/jbe@1309 | 3989 | 
| bsw/jbe@1309 | 3990                     // Create a new ControlRange containing all the elements in the selected ControlRange minus the | 
| bsw/jbe@1309 | 3991                     // element contained by the supplied range | 
| bsw/jbe@1309 | 3992                     var doc = getDocument(controlRange.item(0)); | 
| bsw/jbe@1309 | 3993                     var newControlRange = getBody(doc).createControlRange(); | 
| bsw/jbe@1309 | 3994                     var el, removed = false; | 
| bsw/jbe@1309 | 3995                     for (var i = 0, len = controlRange.length; i < len; ++i) { | 
| bsw/jbe@1309 | 3996                         el = controlRange.item(i); | 
| bsw/jbe@1309 | 3997                         if (el !== rangeElement || removed) { | 
| bsw/jbe@1309 | 3998                             newControlRange.add(controlRange.item(i)); | 
| bsw/jbe@1309 | 3999                         } else { | 
| bsw/jbe@1309 | 4000                             removed = true; | 
| bsw/jbe@1309 | 4001                         } | 
| bsw/jbe@1309 | 4002                     } | 
| bsw/jbe@1309 | 4003                     newControlRange.select(); | 
| bsw/jbe@1309 | 4004 | 
| bsw/jbe@1309 | 4005                     // Update the wrapped selection based on what's now in the native selection | 
| bsw/jbe@1309 | 4006                     updateControlSelection(this); | 
| bsw/jbe@1309 | 4007                 } else { | 
| bsw/jbe@1309 | 4008                     removeRangeManually(this, range); | 
| bsw/jbe@1309 | 4009                 } | 
| bsw/jbe@1309 | 4010             }; | 
| bsw/jbe@1309 | 4011         } else { | 
| bsw/jbe@1309 | 4012             selProto.removeRange = function(range) { | 
| bsw/jbe@1309 | 4013                 removeRangeManually(this, range); | 
| bsw/jbe@1309 | 4014             }; | 
| bsw/jbe@1309 | 4015         } | 
| bsw/jbe@1309 | 4016 | 
| bsw/jbe@1309 | 4017         // Detecting if a selection is backward | 
| bsw/jbe@1309 | 4018         var selectionIsBackward; | 
| bsw/jbe@1309 | 4019         if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) { | 
| bsw/jbe@1309 | 4020             selectionIsBackward = winSelectionIsBackward; | 
| bsw/jbe@1309 | 4021 | 
| bsw/jbe@1309 | 4022             selProto.isBackward = function() { | 
| bsw/jbe@1309 | 4023                 return selectionIsBackward(this); | 
| bsw/jbe@1309 | 4024             }; | 
| bsw/jbe@1309 | 4025         } else { | 
| bsw/jbe@1309 | 4026             selectionIsBackward = selProto.isBackward = function() { | 
| bsw/jbe@1309 | 4027                 return false; | 
| bsw/jbe@1309 | 4028             }; | 
| bsw/jbe@1309 | 4029         } | 
| bsw/jbe@1309 | 4030 | 
| bsw/jbe@1309 | 4031         // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards" | 
| bsw/jbe@1309 | 4032         selProto.isBackwards = selProto.isBackward; | 
| bsw/jbe@1309 | 4033 | 
| bsw/jbe@1309 | 4034         // Selection stringifier | 
| bsw/jbe@1309 | 4035         // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation. | 
| bsw/jbe@1309 | 4036         // The current spec does not yet define this method. | 
| bsw/jbe@1309 | 4037         selProto.toString = function() { | 
| bsw/jbe@1309 | 4038             var rangeTexts = []; | 
| bsw/jbe@1309 | 4039             for (var i = 0, len = this.rangeCount; i < len; ++i) { | 
| bsw/jbe@1309 | 4040                 rangeTexts[i] = "" + this._ranges[i]; | 
| bsw/jbe@1309 | 4041             } | 
| bsw/jbe@1309 | 4042             return rangeTexts.join(""); | 
| bsw/jbe@1309 | 4043         }; | 
| bsw/jbe@1309 | 4044 | 
| bsw/jbe@1309 | 4045         function assertNodeInSameDocument(sel, node) { | 
| bsw/jbe@1309 | 4046             if (sel.win.document != getDocument(node)) { | 
| bsw/jbe@1309 | 4047                 throw new DOMException("WRONG_DOCUMENT_ERR"); | 
| bsw/jbe@1309 | 4048             } | 
| bsw/jbe@1309 | 4049         } | 
| bsw/jbe@1309 | 4050 | 
| bsw/jbe@1309 | 4051         // No current browser conforms fully to the spec for this method, so Rangy's own method is always used | 
| bsw/jbe@1309 | 4052         selProto.collapse = function(node, offset) { | 
| bsw/jbe@1309 | 4053             assertNodeInSameDocument(this, node); | 
| bsw/jbe@1309 | 4054             var range = api.createRange(node); | 
| bsw/jbe@1309 | 4055             range.collapseToPoint(node, offset); | 
| bsw/jbe@1309 | 4056             this.setSingleRange(range); | 
| bsw/jbe@1309 | 4057             this.isCollapsed = true; | 
| bsw/jbe@1309 | 4058         }; | 
| bsw/jbe@1309 | 4059 | 
| bsw/jbe@1309 | 4060         selProto.collapseToStart = function() { | 
| bsw/jbe@1309 | 4061             if (this.rangeCount) { | 
| bsw/jbe@1309 | 4062                 var range = this._ranges[0]; | 
| bsw/jbe@1309 | 4063                 this.collapse(range.startContainer, range.startOffset); | 
| bsw/jbe@1309 | 4064             } else { | 
| bsw/jbe@1309 | 4065                 throw new DOMException("INVALID_STATE_ERR"); | 
| bsw/jbe@1309 | 4066             } | 
| bsw/jbe@1309 | 4067         }; | 
| bsw/jbe@1309 | 4068 | 
| bsw/jbe@1309 | 4069         selProto.collapseToEnd = function() { | 
| bsw/jbe@1309 | 4070             if (this.rangeCount) { | 
| bsw/jbe@1309 | 4071                 var range = this._ranges[this.rangeCount - 1]; | 
| bsw/jbe@1309 | 4072                 this.collapse(range.endContainer, range.endOffset); | 
| bsw/jbe@1309 | 4073             } else { | 
| bsw/jbe@1309 | 4074                 throw new DOMException("INVALID_STATE_ERR"); | 
| bsw/jbe@1309 | 4075             } | 
| bsw/jbe@1309 | 4076         }; | 
| bsw/jbe@1309 | 4077 | 
| bsw/jbe@1309 | 4078         // The spec is very specific on how selectAllChildren should be implemented and not all browsers implement it as | 
| bsw/jbe@1309 | 4079         // specified so the native implementation is never used by Rangy. | 
| bsw/jbe@1309 | 4080         selProto.selectAllChildren = function(node) { | 
| bsw/jbe@1309 | 4081             assertNodeInSameDocument(this, node); | 
| bsw/jbe@1309 | 4082             var range = api.createRange(node); | 
| bsw/jbe@1309 | 4083             range.selectNodeContents(node); | 
| bsw/jbe@1309 | 4084             this.setSingleRange(range); | 
| bsw/jbe@1309 | 4085         }; | 
| bsw/jbe@1309 | 4086 | 
| bsw/jbe@1309 | 4087         selProto.deleteFromDocument = function() { | 
| bsw/jbe@1309 | 4088             // Sepcial behaviour required for IE's control selections | 
| bsw/jbe@1309 | 4089             if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) { | 
| bsw/jbe@1309 | 4090                 var controlRange = this.docSelection.createRange(); | 
| bsw/jbe@1309 | 4091                 var element; | 
| bsw/jbe@1309 | 4092                 while (controlRange.length) { | 
| bsw/jbe@1309 | 4093                     element = controlRange.item(0); | 
| bsw/jbe@1309 | 4094                     controlRange.remove(element); | 
| bsw/jbe@1309 | 4095                     dom.removeNode(element); | 
| bsw/jbe@1309 | 4096                 } | 
| bsw/jbe@1309 | 4097                 this.refresh(); | 
| bsw/jbe@1309 | 4098             } else if (this.rangeCount) { | 
| bsw/jbe@1309 | 4099                 var ranges = this.getAllRanges(); | 
| bsw/jbe@1309 | 4100                 if (ranges.length) { | 
| bsw/jbe@1309 | 4101                     this.removeAllRanges(); | 
| bsw/jbe@1309 | 4102                     for (var i = 0, len = ranges.length; i < len; ++i) { | 
| bsw/jbe@1309 | 4103                         ranges[i].deleteContents(); | 
| bsw/jbe@1309 | 4104                     } | 
| bsw/jbe@1309 | 4105                     // The spec says nothing about what the selection should contain after calling deleteContents on each | 
| bsw/jbe@1309 | 4106                     // range. Firefox moves the selection to where the final selected range was, so we emulate that | 
| bsw/jbe@1309 | 4107                     this.addRange(ranges[len - 1]); | 
| bsw/jbe@1309 | 4108                 } | 
| bsw/jbe@1309 | 4109             } | 
| bsw/jbe@1309 | 4110         }; | 
| bsw/jbe@1309 | 4111 | 
| bsw/jbe@1309 | 4112         // The following are non-standard extensions | 
| bsw/jbe@1309 | 4113         selProto.eachRange = function(func, returnValue) { | 
| bsw/jbe@1309 | 4114             for (var i = 0, len = this._ranges.length; i < len; ++i) { | 
| bsw/jbe@1309 | 4115                 if ( func( this.getRangeAt(i) ) ) { | 
| bsw/jbe@1309 | 4116                     return returnValue; | 
| bsw/jbe@1309 | 4117                 } | 
| bsw/jbe@1309 | 4118             } | 
| bsw/jbe@1309 | 4119         }; | 
| bsw/jbe@1309 | 4120 | 
| bsw/jbe@1309 | 4121         selProto.getAllRanges = function() { | 
| bsw/jbe@1309 | 4122             var ranges = []; | 
| bsw/jbe@1309 | 4123             this.eachRange(function(range) { | 
| bsw/jbe@1309 | 4124                 ranges.push(range); | 
| bsw/jbe@1309 | 4125             }); | 
| bsw/jbe@1309 | 4126             return ranges; | 
| bsw/jbe@1309 | 4127         }; | 
| bsw/jbe@1309 | 4128 | 
| bsw/jbe@1309 | 4129         selProto.setSingleRange = function(range, direction) { | 
| bsw/jbe@1309 | 4130             this.removeAllRanges(); | 
| bsw/jbe@1309 | 4131             this.addRange(range, direction); | 
| bsw/jbe@1309 | 4132         }; | 
| bsw/jbe@1309 | 4133 | 
| bsw/jbe@1309 | 4134         selProto.callMethodOnEachRange = function(methodName, params) { | 
| bsw/jbe@1309 | 4135             var results = []; | 
| bsw/jbe@1309 | 4136             this.eachRange( function(range) { | 
| bsw/jbe@1309 | 4137                 results.push( range[methodName].apply(range, params || []) ); | 
| bsw/jbe@1309 | 4138             } ); | 
| bsw/jbe@1309 | 4139             return results; | 
| bsw/jbe@1309 | 4140         }; | 
| bsw/jbe@1309 | 4141 | 
| bsw/jbe@1309 | 4142         function createStartOrEndSetter(isStart) { | 
| bsw/jbe@1309 | 4143             return function(node, offset) { | 
| bsw/jbe@1309 | 4144                 var range; | 
| bsw/jbe@1309 | 4145                 if (this.rangeCount) { | 
| bsw/jbe@1309 | 4146                     range = this.getRangeAt(0); | 
| bsw/jbe@1309 | 4147                     range["set" + (isStart ? "Start" : "End")](node, offset); | 
| bsw/jbe@1309 | 4148                 } else { | 
| bsw/jbe@1309 | 4149                     range = api.createRange(this.win.document); | 
| bsw/jbe@1309 | 4150                     range.setStartAndEnd(node, offset); | 
| bsw/jbe@1309 | 4151                 } | 
| bsw/jbe@1309 | 4152                 this.setSingleRange(range, this.isBackward()); | 
| bsw/jbe@1309 | 4153             }; | 
| bsw/jbe@1309 | 4154         } | 
| bsw/jbe@1309 | 4155 | 
| bsw/jbe@1309 | 4156         selProto.setStart = createStartOrEndSetter(true); | 
| bsw/jbe@1309 | 4157         selProto.setEnd = createStartOrEndSetter(false); | 
| bsw/jbe@1309 | 4158 | 
| bsw/jbe@1309 | 4159         // Add select() method to Range prototype. Any existing selection will be removed. | 
| bsw/jbe@1309 | 4160         api.rangePrototype.select = function(direction) { | 
| bsw/jbe@1309 | 4161             getSelection( this.getDocument() ).setSingleRange(this, direction); | 
| bsw/jbe@1309 | 4162         }; | 
| bsw/jbe@1309 | 4163 | 
| bsw/jbe@1309 | 4164         selProto.changeEachRange = function(func) { | 
| bsw/jbe@1309 | 4165             var ranges = []; | 
| bsw/jbe@1309 | 4166             var backward = this.isBackward(); | 
| bsw/jbe@1309 | 4167 | 
| bsw/jbe@1309 | 4168             this.eachRange(function(range) { | 
| bsw/jbe@1309 | 4169                 func(range); | 
| bsw/jbe@1309 | 4170                 ranges.push(range); | 
| bsw/jbe@1309 | 4171             }); | 
| bsw/jbe@1309 | 4172 | 
| bsw/jbe@1309 | 4173             this.removeAllRanges(); | 
| bsw/jbe@1309 | 4174             if (backward && ranges.length == 1) { | 
| bsw/jbe@1309 | 4175                 this.addRange(ranges[0], "backward"); | 
| bsw/jbe@1309 | 4176             } else { | 
| bsw/jbe@1309 | 4177                 this.setRanges(ranges); | 
| bsw/jbe@1309 | 4178             } | 
| bsw/jbe@1309 | 4179         }; | 
| bsw/jbe@1309 | 4180 | 
| bsw/jbe@1309 | 4181         selProto.containsNode = function(node, allowPartial) { | 
| bsw/jbe@1309 | 4182             return this.eachRange( function(range) { | 
| bsw/jbe@1309 | 4183                 return range.containsNode(node, allowPartial); | 
| bsw/jbe@1309 | 4184             }, true ) || false; | 
| bsw/jbe@1309 | 4185         }; | 
| bsw/jbe@1309 | 4186 | 
| bsw/jbe@1309 | 4187         selProto.getBookmark = function(containerNode) { | 
| bsw/jbe@1309 | 4188             return { | 
| bsw/jbe@1309 | 4189                 backward: this.isBackward(), | 
| bsw/jbe@1309 | 4190                 rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode]) | 
| bsw/jbe@1309 | 4191             }; | 
| bsw/jbe@1309 | 4192         }; | 
| bsw/jbe@1309 | 4193 | 
| bsw/jbe@1309 | 4194         selProto.moveToBookmark = function(bookmark) { | 
| bsw/jbe@1309 | 4195             var selRanges = []; | 
| bsw/jbe@1309 | 4196             for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) { | 
| bsw/jbe@1309 | 4197                 range = api.createRange(this.win); | 
| bsw/jbe@1309 | 4198                 range.moveToBookmark(rangeBookmark); | 
| bsw/jbe@1309 | 4199                 selRanges.push(range); | 
| bsw/jbe@1309 | 4200             } | 
| bsw/jbe@1309 | 4201             if (bookmark.backward) { | 
| bsw/jbe@1309 | 4202                 this.setSingleRange(selRanges[0], "backward"); | 
| bsw/jbe@1309 | 4203             } else { | 
| bsw/jbe@1309 | 4204                 this.setRanges(selRanges); | 
| bsw/jbe@1309 | 4205             } | 
| bsw/jbe@1309 | 4206         }; | 
| bsw/jbe@1309 | 4207 | 
| bsw/jbe@1309 | 4208         selProto.saveRanges = function() { | 
| bsw/jbe@1309 | 4209             return { | 
| bsw/jbe@1309 | 4210                 backward: this.isBackward(), | 
| bsw/jbe@1309 | 4211                 ranges: this.callMethodOnEachRange("cloneRange") | 
| bsw/jbe@1309 | 4212             }; | 
| bsw/jbe@1309 | 4213         }; | 
| bsw/jbe@1309 | 4214 | 
| bsw/jbe@1309 | 4215         selProto.restoreRanges = function(selRanges) { | 
| bsw/jbe@1309 | 4216             this.removeAllRanges(); | 
| bsw/jbe@1309 | 4217             for (var i = 0, range; range = selRanges.ranges[i]; ++i) { | 
| bsw/jbe@1309 | 4218                 this.addRange(range, (selRanges.backward && i == 0)); | 
| bsw/jbe@1309 | 4219             } | 
| bsw/jbe@1309 | 4220         }; | 
| bsw/jbe@1309 | 4221 | 
| bsw/jbe@1309 | 4222         selProto.toHtml = function() { | 
| bsw/jbe@1309 | 4223             var rangeHtmls = []; | 
| bsw/jbe@1309 | 4224             this.eachRange(function(range) { | 
| bsw/jbe@1309 | 4225                 rangeHtmls.push( DomRange.toHtml(range) ); | 
| bsw/jbe@1309 | 4226             }); | 
| bsw/jbe@1309 | 4227             return rangeHtmls.join(""); | 
| bsw/jbe@1309 | 4228         }; | 
| bsw/jbe@1309 | 4229 | 
| bsw/jbe@1309 | 4230         if (features.implementsTextRange) { | 
| bsw/jbe@1309 | 4231             selProto.getNativeTextRange = function() { | 
| bsw/jbe@1309 | 4232                 var sel, textRange; | 
| bsw/jbe@1309 | 4233                 if ( (sel = this.docSelection) ) { | 
| bsw/jbe@1309 | 4234                     var range = sel.createRange(); | 
| bsw/jbe@1309 | 4235                     if (isTextRange(range)) { | 
| bsw/jbe@1309 | 4236                         return range; | 
| bsw/jbe@1309 | 4237                     } else { | 
| bsw/jbe@1309 | 4238                         throw module.createError("getNativeTextRange: selection is a control selection"); | 
| bsw/jbe@1309 | 4239                     } | 
| bsw/jbe@1309 | 4240                 } else if (this.rangeCount > 0) { | 
| bsw/jbe@1309 | 4241                     return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) ); | 
| bsw/jbe@1309 | 4242                 } else { | 
| bsw/jbe@1309 | 4243                     throw module.createError("getNativeTextRange: selection contains no range"); | 
| bsw/jbe@1309 | 4244                 } | 
| bsw/jbe@1309 | 4245             }; | 
| bsw/jbe@1309 | 4246         } | 
| bsw/jbe@1309 | 4247 | 
| bsw/jbe@1309 | 4248         function inspect(sel) { | 
| bsw/jbe@1309 | 4249             var rangeInspects = []; | 
| bsw/jbe@1309 | 4250             var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset); | 
| bsw/jbe@1309 | 4251             var focus = new DomPosition(sel.focusNode, sel.focusOffset); | 
| bsw/jbe@1309 | 4252             var name = (typeof sel.getName == "function") ? sel.getName() : "Selection"; | 
| bsw/jbe@1309 | 4253 | 
| bsw/jbe@1309 | 4254             if (typeof sel.rangeCount != "undefined") { | 
| bsw/jbe@1309 | 4255                 for (var i = 0, len = sel.rangeCount; i < len; ++i) { | 
| bsw/jbe@1309 | 4256                     rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i)); | 
| bsw/jbe@1309 | 4257                 } | 
| bsw/jbe@1309 | 4258             } | 
| bsw/jbe@1309 | 4259             return "[" + name + "(Ranges: " + rangeInspects.join(", ") + | 
| bsw/jbe@1309 | 4260                     ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]"; | 
| bsw/jbe@1309 | 4261         } | 
| bsw/jbe@1309 | 4262 | 
| bsw/jbe@1309 | 4263         selProto.getName = function() { | 
| bsw/jbe@1309 | 4264             return "WrappedSelection"; | 
| bsw/jbe@1309 | 4265         }; | 
| bsw/jbe@1309 | 4266 | 
| bsw/jbe@1309 | 4267         selProto.inspect = function() { | 
| bsw/jbe@1309 | 4268             return inspect(this); | 
| bsw/jbe@1309 | 4269         }; | 
| bsw/jbe@1309 | 4270 | 
| bsw/jbe@1309 | 4271         selProto.detach = function() { | 
| bsw/jbe@1309 | 4272             actOnCachedSelection(this.win, "delete"); | 
| bsw/jbe@1309 | 4273             deleteProperties(this); | 
| bsw/jbe@1309 | 4274         }; | 
| bsw/jbe@1309 | 4275 | 
| bsw/jbe@1309 | 4276         WrappedSelection.detachAll = function() { | 
| bsw/jbe@1309 | 4277             actOnCachedSelection(null, "deleteAll"); | 
| bsw/jbe@1309 | 4278         }; | 
| bsw/jbe@1309 | 4279 | 
| bsw/jbe@1309 | 4280         WrappedSelection.inspect = inspect; | 
| bsw/jbe@1309 | 4281         WrappedSelection.isDirectionBackward = isDirectionBackward; | 
| bsw/jbe@1309 | 4282 | 
| bsw/jbe@1309 | 4283         api.Selection = WrappedSelection; | 
| bsw/jbe@1309 | 4284 | 
| bsw/jbe@1309 | 4285         api.selectionPrototype = selProto; | 
| bsw/jbe@1309 | 4286 | 
| bsw/jbe@1309 | 4287         api.addShimListener(function(win) { | 
| bsw/jbe@1309 | 4288             if (typeof win.getSelection == "undefined") { | 
| bsw/jbe@1309 | 4289                 win.getSelection = function() { | 
| bsw/jbe@1309 | 4290                     return getSelection(win); | 
| bsw/jbe@1309 | 4291                 }; | 
| bsw/jbe@1309 | 4292             } | 
| bsw/jbe@1309 | 4293             win = null; | 
| bsw/jbe@1309 | 4294         }); | 
| bsw/jbe@1309 | 4295     }); | 
| bsw/jbe@1309 | 4296 | 
| bsw/jbe@1309 | 4297 | 
| bsw/jbe@1309 | 4298     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 4299 | 
| bsw/jbe@1309 | 4300     // Wait for document to load before initializing | 
| bsw/jbe@1309 | 4301     var docReady = false; | 
| bsw/jbe@1309 | 4302 | 
| bsw/jbe@1309 | 4303     var loadHandler = function(e) { | 
| bsw/jbe@1309 | 4304         if (!docReady) { | 
| bsw/jbe@1309 | 4305             docReady = true; | 
| bsw/jbe@1309 | 4306             if (!api.initialized && api.config.autoInitialize) { | 
| bsw/jbe@1309 | 4307                 init(); | 
| bsw/jbe@1309 | 4308             } | 
| bsw/jbe@1309 | 4309         } | 
| bsw/jbe@1309 | 4310     }; | 
| bsw/jbe@1309 | 4311 | 
| bsw/jbe@1309 | 4312     if (isBrowser) { | 
| bsw/jbe@1309 | 4313         // Test whether the document has already been loaded and initialize immediately if so | 
| bsw/jbe@1309 | 4314         if (document.readyState == "complete") { | 
| bsw/jbe@1309 | 4315             loadHandler(); | 
| bsw/jbe@1309 | 4316         } else { | 
| bsw/jbe@1309 | 4317             if (isHostMethod(document, "addEventListener")) { | 
| bsw/jbe@1309 | 4318                 document.addEventListener("DOMContentLoaded", loadHandler, false); | 
| bsw/jbe@1309 | 4319             } | 
| bsw/jbe@1309 | 4320 | 
| bsw/jbe@1309 | 4321             // Add a fallback in case the DOMContentLoaded event isn't supported | 
| bsw/jbe@1309 | 4322             addListener(window, "load", loadHandler); | 
| bsw/jbe@1309 | 4323         } | 
| bsw/jbe@1309 | 4324     } | 
| bsw/jbe@1309 | 4325 | 
| bsw/jbe@1309 | 4326     rangy = api; | 
| bsw/jbe@1309 | 4327 })(); | 
| bsw/jbe@1309 | 4328 | 
| bsw/jbe@1309 | 4329 /** | 
| bsw/jbe@1309 | 4330  * Selection save and restore module for Rangy. | 
| bsw/jbe@1309 | 4331  * Saves and restores user selections using marker invisible elements in the DOM. | 
| bsw/jbe@1309 | 4332  * | 
| bsw/jbe@1309 | 4333  * Part of Rangy, a cross-browser JavaScript range and selection library | 
| bsw/jbe@1309 | 4334  * https://github.com/timdown/rangy | 
| bsw/jbe@1309 | 4335  * | 
| bsw/jbe@1309 | 4336  * Depends on Rangy core. | 
| bsw/jbe@1309 | 4337  * | 
| bsw/jbe@1309 | 4338  * Copyright 2015, Tim Down | 
| bsw/jbe@1309 | 4339  * Licensed under the MIT license. | 
| bsw/jbe@1309 | 4340  * Version: 1.3.1-dev | 
| bsw/jbe@1309 | 4341  * Build date: 20 May 2015 | 
| bsw/jbe@1309 | 4342  * | 
| bsw/jbe@1309 | 4343 * NOTE: UMD wrapper removed manually for bundling (Oliver Pulges) | 
| bsw/jbe@1309 | 4344 */ | 
| bsw/jbe@1309 | 4345 rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) { | 
| bsw/jbe@1309 | 4346     var dom = api.dom; | 
| bsw/jbe@1309 | 4347     var removeNode = dom.removeNode; | 
| bsw/jbe@1309 | 4348     var isDirectionBackward = api.Selection.isDirectionBackward; | 
| bsw/jbe@1309 | 4349     var markerTextChar = "\ufeff"; | 
| bsw/jbe@1309 | 4350 | 
| bsw/jbe@1309 | 4351     function gEBI(id, doc) { | 
| bsw/jbe@1309 | 4352         return (doc || document).getElementById(id); | 
| bsw/jbe@1309 | 4353     } | 
| bsw/jbe@1309 | 4354 | 
| bsw/jbe@1309 | 4355     function insertRangeBoundaryMarker(range, atStart) { | 
| bsw/jbe@1309 | 4356         var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2); | 
| bsw/jbe@1309 | 4357         var markerEl; | 
| bsw/jbe@1309 | 4358         var doc = dom.getDocument(range.startContainer); | 
| bsw/jbe@1309 | 4359 | 
| bsw/jbe@1309 | 4360         // Clone the Range and collapse to the appropriate boundary point | 
| bsw/jbe@1309 | 4361         var boundaryRange = range.cloneRange(); | 
| bsw/jbe@1309 | 4362         boundaryRange.collapse(atStart); | 
| bsw/jbe@1309 | 4363 | 
| bsw/jbe@1309 | 4364         // Create the marker element containing a single invisible character using DOM methods and insert it | 
| bsw/jbe@1309 | 4365         markerEl = doc.createElement("span"); | 
| bsw/jbe@1309 | 4366         markerEl.id = markerId; | 
| bsw/jbe@1309 | 4367         markerEl.style.lineHeight = "0"; | 
| bsw/jbe@1309 | 4368         markerEl.style.display = "none"; | 
| bsw/jbe@1309 | 4369         markerEl.className = "rangySelectionBoundary"; | 
| bsw/jbe@1309 | 4370         markerEl.appendChild(doc.createTextNode(markerTextChar)); | 
| bsw/jbe@1309 | 4371 | 
| bsw/jbe@1309 | 4372         boundaryRange.insertNode(markerEl); | 
| bsw/jbe@1309 | 4373         return markerEl; | 
| bsw/jbe@1309 | 4374     } | 
| bsw/jbe@1309 | 4375 | 
| bsw/jbe@1309 | 4376     function setRangeBoundary(doc, range, markerId, atStart) { | 
| bsw/jbe@1309 | 4377         var markerEl = gEBI(markerId, doc); | 
| bsw/jbe@1309 | 4378         if (markerEl) { | 
| bsw/jbe@1309 | 4379             range[atStart ? "setStartBefore" : "setEndBefore"](markerEl); | 
| bsw/jbe@1309 | 4380             removeNode(markerEl); | 
| bsw/jbe@1309 | 4381         } else { | 
| bsw/jbe@1309 | 4382             module.warn("Marker element has been removed. Cannot restore selection."); | 
| bsw/jbe@1309 | 4383         } | 
| bsw/jbe@1309 | 4384     } | 
| bsw/jbe@1309 | 4385 | 
| bsw/jbe@1309 | 4386     function compareRanges(r1, r2) { | 
| bsw/jbe@1309 | 4387         return r2.compareBoundaryPoints(r1.START_TO_START, r1); | 
| bsw/jbe@1309 | 4388     } | 
| bsw/jbe@1309 | 4389 | 
| bsw/jbe@1309 | 4390     function saveRange(range, direction) { | 
| bsw/jbe@1309 | 4391         var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString(); | 
| bsw/jbe@1309 | 4392         var backward = isDirectionBackward(direction); | 
| bsw/jbe@1309 | 4393 | 
| bsw/jbe@1309 | 4394         if (range.collapsed) { | 
| bsw/jbe@1309 | 4395             endEl = insertRangeBoundaryMarker(range, false); | 
| bsw/jbe@1309 | 4396             return { | 
| bsw/jbe@1309 | 4397                 document: doc, | 
| bsw/jbe@1309 | 4398                 markerId: endEl.id, | 
| bsw/jbe@1309 | 4399                 collapsed: true | 
| bsw/jbe@1309 | 4400             }; | 
| bsw/jbe@1309 | 4401         } else { | 
| bsw/jbe@1309 | 4402             endEl = insertRangeBoundaryMarker(range, false); | 
| bsw/jbe@1309 | 4403             startEl = insertRangeBoundaryMarker(range, true); | 
| bsw/jbe@1309 | 4404 | 
| bsw/jbe@1309 | 4405             return { | 
| bsw/jbe@1309 | 4406                 document: doc, | 
| bsw/jbe@1309 | 4407                 startMarkerId: startEl.id, | 
| bsw/jbe@1309 | 4408                 endMarkerId: endEl.id, | 
| bsw/jbe@1309 | 4409                 collapsed: false, | 
| bsw/jbe@1309 | 4410                 backward: backward, | 
| bsw/jbe@1309 | 4411                 toString: function() { | 
| bsw/jbe@1309 | 4412                     return "original text: '" + text + "', new text: '" + range.toString() + "'"; | 
| bsw/jbe@1309 | 4413                 } | 
| bsw/jbe@1309 | 4414             }; | 
| bsw/jbe@1309 | 4415         } | 
| bsw/jbe@1309 | 4416     } | 
| bsw/jbe@1309 | 4417 | 
| bsw/jbe@1309 | 4418     function restoreRange(rangeInfo, normalize) { | 
| bsw/jbe@1309 | 4419         var doc = rangeInfo.document; | 
| bsw/jbe@1309 | 4420         if (typeof normalize == "undefined") { | 
| bsw/jbe@1309 | 4421             normalize = true; | 
| bsw/jbe@1309 | 4422         } | 
| bsw/jbe@1309 | 4423         var range = api.createRange(doc); | 
| bsw/jbe@1309 | 4424         if (rangeInfo.collapsed) { | 
| bsw/jbe@1309 | 4425             var markerEl = gEBI(rangeInfo.markerId, doc); | 
| bsw/jbe@1309 | 4426             if (markerEl) { | 
| bsw/jbe@1309 | 4427                 markerEl.style.display = "inline"; | 
| bsw/jbe@1309 | 4428                 var previousNode = markerEl.previousSibling; | 
| bsw/jbe@1309 | 4429 | 
| bsw/jbe@1309 | 4430                 // Workaround for issue 17 | 
| bsw/jbe@1309 | 4431                 if (previousNode && previousNode.nodeType == 3) { | 
| bsw/jbe@1309 | 4432                     removeNode(markerEl); | 
| bsw/jbe@1309 | 4433                     range.collapseToPoint(previousNode, previousNode.length); | 
| bsw/jbe@1309 | 4434                 } else { | 
| bsw/jbe@1309 | 4435                     range.collapseBefore(markerEl); | 
| bsw/jbe@1309 | 4436                     removeNode(markerEl); | 
| bsw/jbe@1309 | 4437                 } | 
| bsw/jbe@1309 | 4438             } else { | 
| bsw/jbe@1309 | 4439                 module.warn("Marker element has been removed. Cannot restore selection."); | 
| bsw/jbe@1309 | 4440             } | 
| bsw/jbe@1309 | 4441         } else { | 
| bsw/jbe@1309 | 4442             setRangeBoundary(doc, range, rangeInfo.startMarkerId, true); | 
| bsw/jbe@1309 | 4443             setRangeBoundary(doc, range, rangeInfo.endMarkerId, false); | 
| bsw/jbe@1309 | 4444         } | 
| bsw/jbe@1309 | 4445 | 
| bsw/jbe@1309 | 4446         if (normalize) { | 
| bsw/jbe@1309 | 4447             range.normalizeBoundaries(); | 
| bsw/jbe@1309 | 4448         } | 
| bsw/jbe@1309 | 4449 | 
| bsw/jbe@1309 | 4450         return range; | 
| bsw/jbe@1309 | 4451     } | 
| bsw/jbe@1309 | 4452 | 
| bsw/jbe@1309 | 4453     function saveRanges(ranges, direction) { | 
| bsw/jbe@1309 | 4454         var rangeInfos = [], range, doc; | 
| bsw/jbe@1309 | 4455         var backward = isDirectionBackward(direction); | 
| bsw/jbe@1309 | 4456 | 
| bsw/jbe@1309 | 4457         // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched | 
| bsw/jbe@1309 | 4458         ranges = ranges.slice(0); | 
| bsw/jbe@1309 | 4459         ranges.sort(compareRanges); | 
| bsw/jbe@1309 | 4460 | 
| bsw/jbe@1309 | 4461         for (var i = 0, len = ranges.length; i < len; ++i) { | 
| bsw/jbe@1309 | 4462             rangeInfos[i] = saveRange(ranges[i], backward); | 
| bsw/jbe@1309 | 4463         } | 
| bsw/jbe@1309 | 4464 | 
| bsw/jbe@1309 | 4465         // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie | 
| bsw/jbe@1309 | 4466         // between its markers | 
| bsw/jbe@1309 | 4467         for (i = len - 1; i >= 0; --i) { | 
| bsw/jbe@1309 | 4468             range = ranges[i]; | 
| bsw/jbe@1309 | 4469             doc = api.DomRange.getRangeDocument(range); | 
| bsw/jbe@1309 | 4470             if (range.collapsed) { | 
| bsw/jbe@1309 | 4471                 range.collapseAfter(gEBI(rangeInfos[i].markerId, doc)); | 
| bsw/jbe@1309 | 4472             } else { | 
| bsw/jbe@1309 | 4473                 range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc)); | 
| bsw/jbe@1309 | 4474                 range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc)); | 
| bsw/jbe@1309 | 4475             } | 
| bsw/jbe@1309 | 4476         } | 
| bsw/jbe@1309 | 4477 | 
| bsw/jbe@1309 | 4478         return rangeInfos; | 
| bsw/jbe@1309 | 4479     } | 
| bsw/jbe@1309 | 4480 | 
| bsw/jbe@1309 | 4481     function saveSelection(win) { | 
| bsw/jbe@1309 | 4482         if (!api.isSelectionValid(win)) { | 
| bsw/jbe@1309 | 4483             module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus."); | 
| bsw/jbe@1309 | 4484             return null; | 
| bsw/jbe@1309 | 4485         } | 
| bsw/jbe@1309 | 4486         var sel = api.getSelection(win); | 
| bsw/jbe@1309 | 4487         var ranges = sel.getAllRanges(); | 
| bsw/jbe@1309 | 4488         var backward = (ranges.length == 1 && sel.isBackward()); | 
| bsw/jbe@1309 | 4489 | 
| bsw/jbe@1309 | 4490         var rangeInfos = saveRanges(ranges, backward); | 
| bsw/jbe@1309 | 4491 | 
| bsw/jbe@1309 | 4492         // Ensure current selection is unaffected | 
| bsw/jbe@1309 | 4493         if (backward) { | 
| bsw/jbe@1309 | 4494             sel.setSingleRange(ranges[0], backward); | 
| bsw/jbe@1309 | 4495         } else { | 
| bsw/jbe@1309 | 4496             sel.setRanges(ranges); | 
| bsw/jbe@1309 | 4497         } | 
| bsw/jbe@1309 | 4498 | 
| bsw/jbe@1309 | 4499         return { | 
| bsw/jbe@1309 | 4500             win: win, | 
| bsw/jbe@1309 | 4501             rangeInfos: rangeInfos, | 
| bsw/jbe@1309 | 4502             restored: false | 
| bsw/jbe@1309 | 4503         }; | 
| bsw/jbe@1309 | 4504     } | 
| bsw/jbe@1309 | 4505 | 
| bsw/jbe@1309 | 4506     function restoreRanges(rangeInfos) { | 
| bsw/jbe@1309 | 4507         var ranges = []; | 
| bsw/jbe@1309 | 4508 | 
| bsw/jbe@1309 | 4509         // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid | 
| bsw/jbe@1309 | 4510         // normalization affecting previously restored ranges. | 
| bsw/jbe@1309 | 4511         var rangeCount = rangeInfos.length; | 
| bsw/jbe@1309 | 4512 | 
| bsw/jbe@1309 | 4513         for (var i = rangeCount - 1; i >= 0; i--) { | 
| bsw/jbe@1309 | 4514             ranges[i] = restoreRange(rangeInfos[i], true); | 
| bsw/jbe@1309 | 4515         } | 
| bsw/jbe@1309 | 4516 | 
| bsw/jbe@1309 | 4517         return ranges; | 
| bsw/jbe@1309 | 4518     } | 
| bsw/jbe@1309 | 4519 | 
| bsw/jbe@1309 | 4520     function restoreSelection(savedSelection, preserveDirection) { | 
| bsw/jbe@1309 | 4521         if (!savedSelection.restored) { | 
| bsw/jbe@1309 | 4522             var rangeInfos = savedSelection.rangeInfos; | 
| bsw/jbe@1309 | 4523             var sel = api.getSelection(savedSelection.win); | 
| bsw/jbe@1309 | 4524             var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length; | 
| bsw/jbe@1309 | 4525 | 
| bsw/jbe@1309 | 4526             if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) { | 
| bsw/jbe@1309 | 4527                 sel.removeAllRanges(); | 
| bsw/jbe@1309 | 4528                 sel.addRange(ranges[0], true); | 
| bsw/jbe@1309 | 4529             } else { | 
| bsw/jbe@1309 | 4530                 sel.setRanges(ranges); | 
| bsw/jbe@1309 | 4531             } | 
| bsw/jbe@1309 | 4532 | 
| bsw/jbe@1309 | 4533             savedSelection.restored = true; | 
| bsw/jbe@1309 | 4534         } | 
| bsw/jbe@1309 | 4535     } | 
| bsw/jbe@1309 | 4536 | 
| bsw/jbe@1309 | 4537     function removeMarkerElement(doc, markerId) { | 
| bsw/jbe@1309 | 4538         var markerEl = gEBI(markerId, doc); | 
| bsw/jbe@1309 | 4539         if (markerEl) { | 
| bsw/jbe@1309 | 4540             removeNode(markerEl); | 
| bsw/jbe@1309 | 4541         } | 
| bsw/jbe@1309 | 4542     } | 
| bsw/jbe@1309 | 4543 | 
| bsw/jbe@1309 | 4544     function removeMarkers(savedSelection) { | 
| bsw/jbe@1309 | 4545         var rangeInfos = savedSelection.rangeInfos; | 
| bsw/jbe@1309 | 4546         for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) { | 
| bsw/jbe@1309 | 4547             rangeInfo = rangeInfos[i]; | 
| bsw/jbe@1309 | 4548             if (rangeInfo.collapsed) { | 
| bsw/jbe@1309 | 4549                 removeMarkerElement(savedSelection.doc, rangeInfo.markerId); | 
| bsw/jbe@1309 | 4550             } else { | 
| bsw/jbe@1309 | 4551                 removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId); | 
| bsw/jbe@1309 | 4552                 removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId); | 
| bsw/jbe@1309 | 4553             } | 
| bsw/jbe@1309 | 4554         } | 
| bsw/jbe@1309 | 4555     } | 
| bsw/jbe@1309 | 4556 | 
| bsw/jbe@1309 | 4557     api.util.extend(api, { | 
| bsw/jbe@1309 | 4558         saveRange: saveRange, | 
| bsw/jbe@1309 | 4559         restoreRange: restoreRange, | 
| bsw/jbe@1309 | 4560         saveRanges: saveRanges, | 
| bsw/jbe@1309 | 4561         restoreRanges: restoreRanges, | 
| bsw/jbe@1309 | 4562         saveSelection: saveSelection, | 
| bsw/jbe@1309 | 4563         restoreSelection: restoreSelection, | 
| bsw/jbe@1309 | 4564         removeMarkerElement: removeMarkerElement, | 
| bsw/jbe@1309 | 4565         removeMarkers: removeMarkers | 
| bsw/jbe@1309 | 4566     }); | 
| bsw/jbe@1309 | 4567 }); | 
| bsw/jbe@1309 | 4568 | 
| bsw/jbe@1309 | 4569 /** | 
| bsw/jbe@1309 | 4570  * Text range module for Rangy. | 
| bsw/jbe@1309 | 4571  * Text-based manipulation and searching of ranges and selections. | 
| bsw/jbe@1309 | 4572  * | 
| bsw/jbe@1309 | 4573  * Features | 
| bsw/jbe@1309 | 4574  * | 
| bsw/jbe@1309 | 4575  * - Ability to move range boundaries by character or word offsets | 
| bsw/jbe@1309 | 4576  * - Customizable word tokenizer | 
| bsw/jbe@1309 | 4577  * - Ignores text nodes inside <script> or <style> elements or those hidden by CSS display and visibility properties | 
| bsw/jbe@1309 | 4578  * - Range findText method to search for text or regex within the page or within a range. Flags for whole words and case | 
| bsw/jbe@1309 | 4579  *   sensitivity | 
| bsw/jbe@1309 | 4580  * - Selection and range save/restore as text offsets within a node | 
| bsw/jbe@1309 | 4581  * - Methods to return visible text within a range or selection | 
| bsw/jbe@1309 | 4582  * - innerText method for elements | 
| bsw/jbe@1309 | 4583  * | 
| bsw/jbe@1309 | 4584  * References | 
| bsw/jbe@1309 | 4585  * | 
| bsw/jbe@1309 | 4586  * https://www.w3.org/Bugs/Public/show_bug.cgi?id=13145 | 
| bsw/jbe@1309 | 4587  * http://aryeh.name/spec/innertext/innertext.html | 
| bsw/jbe@1309 | 4588  * http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html | 
| bsw/jbe@1309 | 4589  * | 
| bsw/jbe@1309 | 4590  * Part of Rangy, a cross-browser JavaScript range and selection library | 
| bsw/jbe@1309 | 4591  * https://github.com/timdown/rangy | 
| bsw/jbe@1309 | 4592  * | 
| bsw/jbe@1309 | 4593  * Depends on Rangy core. | 
| bsw/jbe@1309 | 4594  * | 
| bsw/jbe@1309 | 4595  * Copyright 2015, Tim Down | 
| bsw/jbe@1309 | 4596  * Licensed under the MIT license. | 
| bsw/jbe@1309 | 4597  * Version: 1.3.1-dev | 
| bsw/jbe@1309 | 4598  * Build date: 20 May 2015 | 
| bsw/jbe@1309 | 4599  */ | 
| bsw/jbe@1309 | 4600 | 
| bsw/jbe@1309 | 4601 /** | 
| bsw/jbe@1309 | 4602  * Problem: handling of trailing spaces before line breaks is handled inconsistently between browsers. | 
| bsw/jbe@1309 | 4603  * | 
| bsw/jbe@1309 | 4604  * First, a <br>: this is relatively simple. For the following HTML: | 
| bsw/jbe@1309 | 4605  * | 
| bsw/jbe@1309 | 4606  * 1 <br>2 | 
| bsw/jbe@1309 | 4607  * | 
| bsw/jbe@1309 | 4608  * - IE and WebKit render the space, include it in the selection (i.e. when the content is selected and pasted into a | 
| bsw/jbe@1309 | 4609  *   textarea, the space is present) and allow the caret to be placed after it. | 
| bsw/jbe@1309 | 4610  * - Firefox does not acknowledge the space in the selection but it is possible to place the caret after it. | 
| bsw/jbe@1309 | 4611  * - Opera does not render the space but has two separate caret positions on either side of the space (left and right | 
| bsw/jbe@1309 | 4612  *   arrow keys show this) and includes the space in the selection. | 
| bsw/jbe@1309 | 4613  * | 
| bsw/jbe@1309 | 4614  * The other case is the line break or breaks implied by block elements. For the following HTML: | 
| bsw/jbe@1309 | 4615  * | 
| bsw/jbe@1309 | 4616  * <p>1 </p><p>2<p> | 
| bsw/jbe@1309 | 4617  * | 
| bsw/jbe@1309 | 4618  * - WebKit does not acknowledge the space in any way | 
| bsw/jbe@1309 | 4619  * - Firefox, IE and Opera as per <br> | 
| bsw/jbe@1309 | 4620  * | 
| bsw/jbe@1309 | 4621  * One more case is trailing spaces before line breaks in elements with white-space: pre-line. For the following HTML: | 
| bsw/jbe@1309 | 4622  * | 
| bsw/jbe@1309 | 4623  * <p style="white-space: pre-line">1 | 
| bsw/jbe@1309 | 4624  * 2</p> | 
| bsw/jbe@1309 | 4625  * | 
| bsw/jbe@1309 | 4626  * - Firefox and WebKit include the space in caret positions | 
| bsw/jbe@1309 | 4627  * - IE does not support pre-line up to and including version 9 | 
| bsw/jbe@1309 | 4628  * - Opera ignores the space | 
| bsw/jbe@1309 | 4629  * - Trailing space only renders if there is a non-collapsed character in the line | 
| bsw/jbe@1309 | 4630  * | 
| bsw/jbe@1309 | 4631  * Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be | 
| bsw/jbe@1309 | 4632  * feature-tested | 
| bsw/jbe@1309 | 4633  * | 
| bsw/jbe@1309 | 4634  * NOTE: UMD wrapper removed manually for bundling (Oliver Pulges) | 
| bsw/jbe@1309 | 4635 */ | 
| bsw/jbe@1309 | 4636 rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { | 
| bsw/jbe@1309 | 4637     var UNDEF = "undefined"; | 
| bsw/jbe@1309 | 4638     var CHARACTER = "character", WORD = "word"; | 
| bsw/jbe@1309 | 4639     var dom = api.dom, util = api.util; | 
| bsw/jbe@1309 | 4640     var extend = util.extend; | 
| bsw/jbe@1309 | 4641     var createOptions = util.createOptions; | 
| bsw/jbe@1309 | 4642     var getBody = dom.getBody; | 
| bsw/jbe@1309 | 4643 | 
| bsw/jbe@1309 | 4644 | 
| bsw/jbe@1309 | 4645     var spacesRegex = /^[ \t\f\r\n]+$/; | 
| bsw/jbe@1309 | 4646     var spacesMinusLineBreaksRegex = /^[ \t\f\r]+$/; | 
| bsw/jbe@1309 | 4647     var allWhiteSpaceRegex = /^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/; | 
| bsw/jbe@1309 | 4648     var nonLineBreakWhiteSpaceRegex = /^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/; | 
| bsw/jbe@1309 | 4649     var lineBreakRegex = /^[\n-\r\u0085\u2028\u2029]$/; | 
| bsw/jbe@1309 | 4650 | 
| bsw/jbe@1309 | 4651     var defaultLanguage = "en"; | 
| bsw/jbe@1309 | 4652 | 
| bsw/jbe@1309 | 4653     var isDirectionBackward = api.Selection.isDirectionBackward; | 
| bsw/jbe@1309 | 4654 | 
| bsw/jbe@1309 | 4655     // Properties representing whether trailing spaces inside blocks are completely collapsed (as they are in WebKit, | 
| bsw/jbe@1309 | 4656     // but not other browsers). Also test whether trailing spaces before <br> elements are collapsed. | 
| bsw/jbe@1309 | 4657     var trailingSpaceInBlockCollapses = false; | 
| bsw/jbe@1309 | 4658     var trailingSpaceBeforeBrCollapses = false; | 
| bsw/jbe@1309 | 4659     var trailingSpaceBeforeBlockCollapses = false; | 
| bsw/jbe@1309 | 4660     var trailingSpaceBeforeLineBreakInPreLineCollapses = true; | 
| bsw/jbe@1309 | 4661 | 
| bsw/jbe@1309 | 4662     (function() { | 
| bsw/jbe@1309 | 4663         var el = dom.createTestElement(document, "<p>1 </p><p></p>", true); | 
| bsw/jbe@1309 | 4664         var p = el.firstChild; | 
| bsw/jbe@1309 | 4665         var sel = api.getSelection(); | 
| bsw/jbe@1309 | 4666         sel.collapse(p.lastChild, 2); | 
| bsw/jbe@1309 | 4667         sel.setStart(p.firstChild, 0); | 
| bsw/jbe@1309 | 4668         trailingSpaceInBlockCollapses = ("" + sel).length == 1; | 
| bsw/jbe@1309 | 4669 | 
| bsw/jbe@1309 | 4670         el.innerHTML = "1 <br />"; | 
| bsw/jbe@1309 | 4671         sel.collapse(el, 2); | 
| bsw/jbe@1309 | 4672         sel.setStart(el.firstChild, 0); | 
| bsw/jbe@1309 | 4673         trailingSpaceBeforeBrCollapses = ("" + sel).length == 1; | 
| bsw/jbe@1309 | 4674 | 
| bsw/jbe@1309 | 4675         el.innerHTML = "1 <p>1</p>"; | 
| bsw/jbe@1309 | 4676         sel.collapse(el, 2); | 
| bsw/jbe@1309 | 4677         sel.setStart(el.firstChild, 0); | 
| bsw/jbe@1309 | 4678         trailingSpaceBeforeBlockCollapses = ("" + sel).length == 1; | 
| bsw/jbe@1309 | 4679 | 
| bsw/jbe@1309 | 4680         dom.removeNode(el); | 
| bsw/jbe@1309 | 4681         sel.removeAllRanges(); | 
| bsw/jbe@1309 | 4682     })(); | 
| bsw/jbe@1309 | 4683 | 
| bsw/jbe@1309 | 4684     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 4685 | 
| bsw/jbe@1309 | 4686     // This function must create word and non-word tokens for the whole of the text supplied to it | 
| bsw/jbe@1309 | 4687     function defaultTokenizer(chars, wordOptions) { | 
| bsw/jbe@1309 | 4688         var word = chars.join(""), result, tokenRanges = []; | 
| bsw/jbe@1309 | 4689 | 
| bsw/jbe@1309 | 4690         function createTokenRange(start, end, isWord) { | 
| bsw/jbe@1309 | 4691             tokenRanges.push( { start: start, end: end, isWord: isWord } ); | 
| bsw/jbe@1309 | 4692         } | 
| bsw/jbe@1309 | 4693 | 
| bsw/jbe@1309 | 4694         // Match words and mark characters | 
| bsw/jbe@1309 | 4695         var lastWordEnd = 0, wordStart, wordEnd; | 
| bsw/jbe@1309 | 4696         while ( (result = wordOptions.wordRegex.exec(word)) ) { | 
| bsw/jbe@1309 | 4697             wordStart = result.index; | 
| bsw/jbe@1309 | 4698             wordEnd = wordStart + result[0].length; | 
| bsw/jbe@1309 | 4699 | 
| bsw/jbe@1309 | 4700             // Create token for non-word characters preceding this word | 
| bsw/jbe@1309 | 4701             if (wordStart > lastWordEnd) { | 
| bsw/jbe@1309 | 4702                 createTokenRange(lastWordEnd, wordStart, false); | 
| bsw/jbe@1309 | 4703             } | 
| bsw/jbe@1309 | 4704 | 
| bsw/jbe@1309 | 4705             // Get trailing space characters for word | 
| bsw/jbe@1309 | 4706             if (wordOptions.includeTrailingSpace) { | 
| bsw/jbe@1309 | 4707                 while ( nonLineBreakWhiteSpaceRegex.test(chars[wordEnd]) ) { | 
| bsw/jbe@1309 | 4708                     ++wordEnd; | 
| bsw/jbe@1309 | 4709                 } | 
| bsw/jbe@1309 | 4710             } | 
| bsw/jbe@1309 | 4711             createTokenRange(wordStart, wordEnd, true); | 
| bsw/jbe@1309 | 4712             lastWordEnd = wordEnd; | 
| bsw/jbe@1309 | 4713         } | 
| bsw/jbe@1309 | 4714 | 
| bsw/jbe@1309 | 4715         // Create token for trailing non-word characters, if any exist | 
| bsw/jbe@1309 | 4716         if (lastWordEnd < chars.length) { | 
| bsw/jbe@1309 | 4717             createTokenRange(lastWordEnd, chars.length, false); | 
| bsw/jbe@1309 | 4718         } | 
| bsw/jbe@1309 | 4719 | 
| bsw/jbe@1309 | 4720         return tokenRanges; | 
| bsw/jbe@1309 | 4721     } | 
| bsw/jbe@1309 | 4722 | 
| bsw/jbe@1309 | 4723     function convertCharRangeToToken(chars, tokenRange) { | 
| bsw/jbe@1309 | 4724         var tokenChars = chars.slice(tokenRange.start, tokenRange.end); | 
| bsw/jbe@1309 | 4725         var token = { | 
| bsw/jbe@1309 | 4726             isWord: tokenRange.isWord, | 
| bsw/jbe@1309 | 4727             chars: tokenChars, | 
| bsw/jbe@1309 | 4728             toString: function() { | 
| bsw/jbe@1309 | 4729                 return tokenChars.join(""); | 
| bsw/jbe@1309 | 4730             } | 
| bsw/jbe@1309 | 4731         }; | 
| bsw/jbe@1309 | 4732         for (var i = 0, len = tokenChars.length; i < len; ++i) { | 
| bsw/jbe@1309 | 4733             tokenChars[i].token = token; | 
| bsw/jbe@1309 | 4734         } | 
| bsw/jbe@1309 | 4735         return token; | 
| bsw/jbe@1309 | 4736     } | 
| bsw/jbe@1309 | 4737 | 
| bsw/jbe@1309 | 4738     function tokenize(chars, wordOptions, tokenizer) { | 
| bsw/jbe@1309 | 4739         var tokenRanges = tokenizer(chars, wordOptions); | 
| bsw/jbe@1309 | 4740         var tokens = []; | 
| bsw/jbe@1309 | 4741         for (var i = 0, tokenRange; tokenRange = tokenRanges[i++]; ) { | 
| bsw/jbe@1309 | 4742             tokens.push( convertCharRangeToToken(chars, tokenRange) ); | 
| bsw/jbe@1309 | 4743         } | 
| bsw/jbe@1309 | 4744         return tokens; | 
| bsw/jbe@1309 | 4745     } | 
| bsw/jbe@1309 | 4746 | 
| bsw/jbe@1309 | 4747     var defaultCharacterOptions = { | 
| bsw/jbe@1309 | 4748         includeBlockContentTrailingSpace: true, | 
| bsw/jbe@1309 | 4749         includeSpaceBeforeBr: true, | 
| bsw/jbe@1309 | 4750         includeSpaceBeforeBlock: true, | 
| bsw/jbe@1309 | 4751         includePreLineTrailingSpace: true, | 
| bsw/jbe@1309 | 4752         ignoreCharacters: "" | 
| bsw/jbe@1309 | 4753     }; | 
| bsw/jbe@1309 | 4754 | 
| bsw/jbe@1309 | 4755     function normalizeIgnoredCharacters(ignoredCharacters) { | 
| bsw/jbe@1309 | 4756         // Check if character is ignored | 
| bsw/jbe@1309 | 4757         var ignoredChars = ignoredCharacters || ""; | 
| bsw/jbe@1309 | 4758 | 
| bsw/jbe@1309 | 4759         // Normalize ignored characters into a string consisting of characters in ascending order of character code | 
| bsw/jbe@1309 | 4760         var ignoredCharsArray = (typeof ignoredChars == "string") ? ignoredChars.split("") : ignoredChars; | 
| bsw/jbe@1309 | 4761         ignoredCharsArray.sort(function(char1, char2) { | 
| bsw/jbe@1309 | 4762             return char1.charCodeAt(0) - char2.charCodeAt(0); | 
| bsw/jbe@1309 | 4763         }); | 
| bsw/jbe@1309 | 4764 | 
| bsw/jbe@1309 | 4765         /// Convert back to a string and remove duplicates | 
| bsw/jbe@1309 | 4766         return ignoredCharsArray.join("").replace(/(.)\1+/g, "$1"); | 
| bsw/jbe@1309 | 4767     } | 
| bsw/jbe@1309 | 4768 | 
| bsw/jbe@1309 | 4769     var defaultCaretCharacterOptions = { | 
| bsw/jbe@1309 | 4770         includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses, | 
| bsw/jbe@1309 | 4771         includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses, | 
| bsw/jbe@1309 | 4772         includeSpaceBeforeBlock: !trailingSpaceBeforeBlockCollapses, | 
| bsw/jbe@1309 | 4773         includePreLineTrailingSpace: true | 
| bsw/jbe@1309 | 4774     }; | 
| bsw/jbe@1309 | 4775 | 
| bsw/jbe@1309 | 4776     var defaultWordOptions = { | 
| bsw/jbe@1309 | 4777         "en": { | 
| bsw/jbe@1309 | 4778             wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi, | 
| bsw/jbe@1309 | 4779             includeTrailingSpace: false, | 
| bsw/jbe@1309 | 4780             tokenizer: defaultTokenizer | 
| bsw/jbe@1309 | 4781         } | 
| bsw/jbe@1309 | 4782     }; | 
| bsw/jbe@1309 | 4783 | 
| bsw/jbe@1309 | 4784     var defaultFindOptions = { | 
| bsw/jbe@1309 | 4785         caseSensitive: false, | 
| bsw/jbe@1309 | 4786         withinRange: null, | 
| bsw/jbe@1309 | 4787         wholeWordsOnly: false, | 
| bsw/jbe@1309 | 4788         wrap: false, | 
| bsw/jbe@1309 | 4789         direction: "forward", | 
| bsw/jbe@1309 | 4790         wordOptions: null, | 
| bsw/jbe@1309 | 4791         characterOptions: null | 
| bsw/jbe@1309 | 4792     }; | 
| bsw/jbe@1309 | 4793 | 
| bsw/jbe@1309 | 4794     var defaultMoveOptions = { | 
| bsw/jbe@1309 | 4795         wordOptions: null, | 
| bsw/jbe@1309 | 4796         characterOptions: null | 
| bsw/jbe@1309 | 4797     }; | 
| bsw/jbe@1309 | 4798 | 
| bsw/jbe@1309 | 4799     var defaultExpandOptions = { | 
| bsw/jbe@1309 | 4800         wordOptions: null, | 
| bsw/jbe@1309 | 4801         characterOptions: null, | 
| bsw/jbe@1309 | 4802         trim: false, | 
| bsw/jbe@1309 | 4803         trimStart: true, | 
| bsw/jbe@1309 | 4804         trimEnd: true | 
| bsw/jbe@1309 | 4805     }; | 
| bsw/jbe@1309 | 4806 | 
| bsw/jbe@1309 | 4807     var defaultWordIteratorOptions = { | 
| bsw/jbe@1309 | 4808         wordOptions: null, | 
| bsw/jbe@1309 | 4809         characterOptions: null, | 
| bsw/jbe@1309 | 4810         direction: "forward" | 
| bsw/jbe@1309 | 4811     }; | 
| bsw/jbe@1309 | 4812 | 
| bsw/jbe@1309 | 4813     function createWordOptions(options) { | 
| bsw/jbe@1309 | 4814         var lang, defaults; | 
| bsw/jbe@1309 | 4815         if (!options) { | 
| bsw/jbe@1309 | 4816             return defaultWordOptions[defaultLanguage]; | 
| bsw/jbe@1309 | 4817         } else { | 
| bsw/jbe@1309 | 4818             lang = options.language || defaultLanguage; | 
| bsw/jbe@1309 | 4819             defaults = {}; | 
| bsw/jbe@1309 | 4820             extend(defaults, defaultWordOptions[lang] || defaultWordOptions[defaultLanguage]); | 
| bsw/jbe@1309 | 4821             extend(defaults, options); | 
| bsw/jbe@1309 | 4822             return defaults; | 
| bsw/jbe@1309 | 4823         } | 
| bsw/jbe@1309 | 4824     } | 
| bsw/jbe@1309 | 4825 | 
| bsw/jbe@1309 | 4826     function createNestedOptions(optionsParam, defaults) { | 
| bsw/jbe@1309 | 4827         var options = createOptions(optionsParam, defaults); | 
| bsw/jbe@1309 | 4828         if (defaults.hasOwnProperty("wordOptions")) { | 
| bsw/jbe@1309 | 4829             options.wordOptions = createWordOptions(options.wordOptions); | 
| bsw/jbe@1309 | 4830         } | 
| bsw/jbe@1309 | 4831         if (defaults.hasOwnProperty("characterOptions")) { | 
| bsw/jbe@1309 | 4832             options.characterOptions = createOptions(options.characterOptions, defaultCharacterOptions); | 
| bsw/jbe@1309 | 4833         } | 
| bsw/jbe@1309 | 4834         return options; | 
| bsw/jbe@1309 | 4835     } | 
| bsw/jbe@1309 | 4836 | 
| bsw/jbe@1309 | 4837     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 4838 | 
| bsw/jbe@1309 | 4839     /* DOM utility functions */ | 
| bsw/jbe@1309 | 4840     var getComputedStyleProperty = dom.getComputedStyleProperty; | 
| bsw/jbe@1309 | 4841 | 
| bsw/jbe@1309 | 4842     // Create cachable versions of DOM functions | 
| bsw/jbe@1309 | 4843 | 
| bsw/jbe@1309 | 4844     // Test for old IE's incorrect display properties | 
| bsw/jbe@1309 | 4845     var tableCssDisplayBlock; | 
| bsw/jbe@1309 | 4846     (function() { | 
| bsw/jbe@1309 | 4847         var table = document.createElement("table"); | 
| bsw/jbe@1309 | 4848         var body = getBody(document); | 
| bsw/jbe@1309 | 4849         body.appendChild(table); | 
| bsw/jbe@1309 | 4850         tableCssDisplayBlock = (getComputedStyleProperty(table, "display") == "block"); | 
| bsw/jbe@1309 | 4851         body.removeChild(table); | 
| bsw/jbe@1309 | 4852     })(); | 
| bsw/jbe@1309 | 4853 | 
| bsw/jbe@1309 | 4854     var defaultDisplayValueForTag = { | 
| bsw/jbe@1309 | 4855         table: "table", | 
| bsw/jbe@1309 | 4856         caption: "table-caption", | 
| bsw/jbe@1309 | 4857         colgroup: "table-column-group", | 
| bsw/jbe@1309 | 4858         col: "table-column", | 
| bsw/jbe@1309 | 4859         thead: "table-header-group", | 
| bsw/jbe@1309 | 4860         tbody: "table-row-group", | 
| bsw/jbe@1309 | 4861         tfoot: "table-footer-group", | 
| bsw/jbe@1309 | 4862         tr: "table-row", | 
| bsw/jbe@1309 | 4863         td: "table-cell", | 
| bsw/jbe@1309 | 4864         th: "table-cell" | 
| bsw/jbe@1309 | 4865     }; | 
| bsw/jbe@1309 | 4866 | 
| bsw/jbe@1309 | 4867     // Corrects IE's "block" value for table-related elements | 
| bsw/jbe@1309 | 4868     function getComputedDisplay(el, win) { | 
| bsw/jbe@1309 | 4869         var display = getComputedStyleProperty(el, "display", win); | 
| bsw/jbe@1309 | 4870         var tagName = el.tagName.toLowerCase(); | 
| bsw/jbe@1309 | 4871         return (display == "block" && | 
| bsw/jbe@1309 | 4872                 tableCssDisplayBlock && | 
| bsw/jbe@1309 | 4873                 defaultDisplayValueForTag.hasOwnProperty(tagName)) ? | 
| bsw/jbe@1309 | 4874             defaultDisplayValueForTag[tagName] : display; | 
| bsw/jbe@1309 | 4875     } | 
| bsw/jbe@1309 | 4876 | 
| bsw/jbe@1309 | 4877     function isHidden(node) { | 
| bsw/jbe@1309 | 4878         var ancestors = getAncestorsAndSelf(node); | 
| bsw/jbe@1309 | 4879         for (var i = 0, len = ancestors.length; i < len; ++i) { | 
| bsw/jbe@1309 | 4880             if (ancestors[i].nodeType == 1 && getComputedDisplay(ancestors[i]) == "none") { | 
| bsw/jbe@1309 | 4881                 return true; | 
| bsw/jbe@1309 | 4882             } | 
| bsw/jbe@1309 | 4883         } | 
| bsw/jbe@1309 | 4884 | 
| bsw/jbe@1309 | 4885         return false; | 
| bsw/jbe@1309 | 4886     } | 
| bsw/jbe@1309 | 4887 | 
| bsw/jbe@1309 | 4888     function isVisibilityHiddenTextNode(textNode) { | 
| bsw/jbe@1309 | 4889         var el; | 
| bsw/jbe@1309 | 4890         return textNode.nodeType == 3 && | 
| bsw/jbe@1309 | 4891             (el = textNode.parentNode) && | 
| bsw/jbe@1309 | 4892             getComputedStyleProperty(el, "visibility") == "hidden"; | 
| bsw/jbe@1309 | 4893     } | 
| bsw/jbe@1309 | 4894 | 
| bsw/jbe@1309 | 4895     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 4896 | 
| bsw/jbe@1309 | 4897 | 
| bsw/jbe@1309 | 4898     // "A block node is either an Element whose "display" property does not have | 
| bsw/jbe@1309 | 4899     // resolved value "inline" or "inline-block" or "inline-table" or "none", or a | 
| bsw/jbe@1309 | 4900     // Document, or a DocumentFragment." | 
| bsw/jbe@1309 | 4901     function isBlockNode(node) { | 
| bsw/jbe@1309 | 4902         return node && | 
| bsw/jbe@1309 | 4903             ((node.nodeType == 1 && !/^(inline(-block|-table)?|none)$/.test(getComputedDisplay(node))) || | 
| bsw/jbe@1309 | 4904             node.nodeType == 9 || node.nodeType == 11); | 
| bsw/jbe@1309 | 4905     } | 
| bsw/jbe@1309 | 4906 | 
| bsw/jbe@1309 | 4907     function getLastDescendantOrSelf(node) { | 
| bsw/jbe@1309 | 4908         var lastChild = node.lastChild; | 
| bsw/jbe@1309 | 4909         return lastChild ? getLastDescendantOrSelf(lastChild) : node; | 
| bsw/jbe@1309 | 4910     } | 
| bsw/jbe@1309 | 4911 | 
| bsw/jbe@1309 | 4912     function containsPositions(node) { | 
| bsw/jbe@1309 | 4913         return dom.isCharacterDataNode(node) || | 
| bsw/jbe@1309 | 4914             !/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i.test(node.nodeName); | 
| bsw/jbe@1309 | 4915     } | 
| bsw/jbe@1309 | 4916 | 
| bsw/jbe@1309 | 4917     function getAncestors(node) { | 
| bsw/jbe@1309 | 4918         var ancestors = []; | 
| bsw/jbe@1309 | 4919         while (node.parentNode) { | 
| bsw/jbe@1309 | 4920             ancestors.unshift(node.parentNode); | 
| bsw/jbe@1309 | 4921             node = node.parentNode; | 
| bsw/jbe@1309 | 4922         } | 
| bsw/jbe@1309 | 4923         return ancestors; | 
| bsw/jbe@1309 | 4924     } | 
| bsw/jbe@1309 | 4925 | 
| bsw/jbe@1309 | 4926     function getAncestorsAndSelf(node) { | 
| bsw/jbe@1309 | 4927         return getAncestors(node).concat([node]); | 
| bsw/jbe@1309 | 4928     } | 
| bsw/jbe@1309 | 4929 | 
| bsw/jbe@1309 | 4930     function nextNodeDescendants(node) { | 
| bsw/jbe@1309 | 4931         while (node && !node.nextSibling) { | 
| bsw/jbe@1309 | 4932             node = node.parentNode; | 
| bsw/jbe@1309 | 4933         } | 
| bsw/jbe@1309 | 4934         if (!node) { | 
| bsw/jbe@1309 | 4935             return null; | 
| bsw/jbe@1309 | 4936         } | 
| bsw/jbe@1309 | 4937         return node.nextSibling; | 
| bsw/jbe@1309 | 4938     } | 
| bsw/jbe@1309 | 4939 | 
| bsw/jbe@1309 | 4940     function nextNode(node, excludeChildren) { | 
| bsw/jbe@1309 | 4941         if (!excludeChildren && node.hasChildNodes()) { | 
| bsw/jbe@1309 | 4942             return node.firstChild; | 
| bsw/jbe@1309 | 4943         } | 
| bsw/jbe@1309 | 4944         return nextNodeDescendants(node); | 
| bsw/jbe@1309 | 4945     } | 
| bsw/jbe@1309 | 4946 | 
| bsw/jbe@1309 | 4947     function previousNode(node) { | 
| bsw/jbe@1309 | 4948         var previous = node.previousSibling; | 
| bsw/jbe@1309 | 4949         if (previous) { | 
| bsw/jbe@1309 | 4950             node = previous; | 
| bsw/jbe@1309 | 4951             while (node.hasChildNodes()) { | 
| bsw/jbe@1309 | 4952                 node = node.lastChild; | 
| bsw/jbe@1309 | 4953             } | 
| bsw/jbe@1309 | 4954             return node; | 
| bsw/jbe@1309 | 4955         } | 
| bsw/jbe@1309 | 4956         var parent = node.parentNode; | 
| bsw/jbe@1309 | 4957         if (parent && parent.nodeType == 1) { | 
| bsw/jbe@1309 | 4958             return parent; | 
| bsw/jbe@1309 | 4959         } | 
| bsw/jbe@1309 | 4960         return null; | 
| bsw/jbe@1309 | 4961     } | 
| bsw/jbe@1309 | 4962 | 
| bsw/jbe@1309 | 4963     // Adpated from Aryeh's code. | 
| bsw/jbe@1309 | 4964     // "A whitespace node is either a Text node whose data is the empty string; or | 
| bsw/jbe@1309 | 4965     // a Text node whose data consists only of one or more tabs (0x0009), line | 
| bsw/jbe@1309 | 4966     // feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose | 
| bsw/jbe@1309 | 4967     // parent is an Element whose resolved value for "white-space" is "normal" or | 
| bsw/jbe@1309 | 4968     // "nowrap"; or a Text node whose data consists only of one or more tabs | 
| bsw/jbe@1309 | 4969     // (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose | 
| bsw/jbe@1309 | 4970     // parent is an Element whose resolved value for "white-space" is "pre-line"." | 
| bsw/jbe@1309 | 4971     function isWhitespaceNode(node) { | 
| bsw/jbe@1309 | 4972         if (!node || node.nodeType != 3) { | 
| bsw/jbe@1309 | 4973             return false; | 
| bsw/jbe@1309 | 4974         } | 
| bsw/jbe@1309 | 4975         var text = node.data; | 
| bsw/jbe@1309 | 4976         if (text === "") { | 
| bsw/jbe@1309 | 4977             return true; | 
| bsw/jbe@1309 | 4978         } | 
| bsw/jbe@1309 | 4979         var parent = node.parentNode; | 
| bsw/jbe@1309 | 4980         if (!parent || parent.nodeType != 1) { | 
| bsw/jbe@1309 | 4981             return false; | 
| bsw/jbe@1309 | 4982         } | 
| bsw/jbe@1309 | 4983         var computedWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace"); | 
| bsw/jbe@1309 | 4984 | 
| bsw/jbe@1309 | 4985         return (/^[\t\n\r ]+$/.test(text) && /^(normal|nowrap)$/.test(computedWhiteSpace)) || | 
| bsw/jbe@1309 | 4986             (/^[\t\r ]+$/.test(text) && computedWhiteSpace == "pre-line"); | 
| bsw/jbe@1309 | 4987     } | 
| bsw/jbe@1309 | 4988 | 
| bsw/jbe@1309 | 4989     // Adpated from Aryeh's code. | 
| bsw/jbe@1309 | 4990     // "node is a collapsed whitespace node if the following algorithm returns | 
| bsw/jbe@1309 | 4991     // true:" | 
| bsw/jbe@1309 | 4992     function isCollapsedWhitespaceNode(node) { | 
| bsw/jbe@1309 | 4993         // "If node's data is the empty string, return true." | 
| bsw/jbe@1309 | 4994         if (node.data === "") { | 
| bsw/jbe@1309 | 4995             return true; | 
| bsw/jbe@1309 | 4996         } | 
| bsw/jbe@1309 | 4997 | 
| bsw/jbe@1309 | 4998         // "If node is not a whitespace node, return false." | 
| bsw/jbe@1309 | 4999         if (!isWhitespaceNode(node)) { | 
| bsw/jbe@1309 | 5000             return false; | 
| bsw/jbe@1309 | 5001         } | 
| bsw/jbe@1309 | 5002 | 
| bsw/jbe@1309 | 5003         // "Let ancestor be node's parent." | 
| bsw/jbe@1309 | 5004         var ancestor = node.parentNode; | 
| bsw/jbe@1309 | 5005 | 
| bsw/jbe@1309 | 5006         // "If ancestor is null, return true." | 
| bsw/jbe@1309 | 5007         if (!ancestor) { | 
| bsw/jbe@1309 | 5008             return true; | 
| bsw/jbe@1309 | 5009         } | 
| bsw/jbe@1309 | 5010 | 
| bsw/jbe@1309 | 5011         // "If the "display" property of some ancestor of node has resolved value "none", return true." | 
| bsw/jbe@1309 | 5012         if (isHidden(node)) { | 
| bsw/jbe@1309 | 5013             return true; | 
| bsw/jbe@1309 | 5014         } | 
| bsw/jbe@1309 | 5015 | 
| bsw/jbe@1309 | 5016         return false; | 
| bsw/jbe@1309 | 5017     } | 
| bsw/jbe@1309 | 5018 | 
| bsw/jbe@1309 | 5019     function isCollapsedNode(node) { | 
| bsw/jbe@1309 | 5020         var type = node.nodeType; | 
| bsw/jbe@1309 | 5021         return type == 7 /* PROCESSING_INSTRUCTION */ || | 
| bsw/jbe@1309 | 5022             type == 8 /* COMMENT */ || | 
| bsw/jbe@1309 | 5023             isHidden(node) || | 
| bsw/jbe@1309 | 5024             /^(script|style)$/i.test(node.nodeName) || | 
| bsw/jbe@1309 | 5025             isVisibilityHiddenTextNode(node) || | 
| bsw/jbe@1309 | 5026             isCollapsedWhitespaceNode(node); | 
| bsw/jbe@1309 | 5027     } | 
| bsw/jbe@1309 | 5028 | 
| bsw/jbe@1309 | 5029     function isIgnoredNode(node, win) { | 
| bsw/jbe@1309 | 5030         var type = node.nodeType; | 
| bsw/jbe@1309 | 5031         return type == 7 /* PROCESSING_INSTRUCTION */ || | 
| bsw/jbe@1309 | 5032             type == 8 /* COMMENT */ || | 
| bsw/jbe@1309 | 5033             (type == 1 && getComputedDisplay(node, win) == "none"); | 
| bsw/jbe@1309 | 5034     } | 
| bsw/jbe@1309 | 5035 | 
| bsw/jbe@1309 | 5036     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 5037 | 
| bsw/jbe@1309 | 5038     // Possibly overengineered caching system to prevent repeated DOM calls slowing everything down | 
| bsw/jbe@1309 | 5039 | 
| bsw/jbe@1309 | 5040     function Cache() { | 
| bsw/jbe@1309 | 5041         this.store = {}; | 
| bsw/jbe@1309 | 5042     } | 
| bsw/jbe@1309 | 5043 | 
| bsw/jbe@1309 | 5044     Cache.prototype = { | 
| bsw/jbe@1309 | 5045         get: function(key) { | 
| bsw/jbe@1309 | 5046             return this.store.hasOwnProperty(key) ? this.store[key] : null; | 
| bsw/jbe@1309 | 5047         }, | 
| bsw/jbe@1309 | 5048 | 
| bsw/jbe@1309 | 5049         set: function(key, value) { | 
| bsw/jbe@1309 | 5050             return this.store[key] = value; | 
| bsw/jbe@1309 | 5051         } | 
| bsw/jbe@1309 | 5052     }; | 
| bsw/jbe@1309 | 5053 | 
| bsw/jbe@1309 | 5054     var cachedCount = 0, uncachedCount = 0; | 
| bsw/jbe@1309 | 5055 | 
| bsw/jbe@1309 | 5056     function createCachingGetter(methodName, func, objProperty) { | 
| bsw/jbe@1309 | 5057         return function(args) { | 
| bsw/jbe@1309 | 5058             var cache = this.cache; | 
| bsw/jbe@1309 | 5059             if (cache.hasOwnProperty(methodName)) { | 
| bsw/jbe@1309 | 5060                 cachedCount++; | 
| bsw/jbe@1309 | 5061                 return cache[methodName]; | 
| bsw/jbe@1309 | 5062             } else { | 
| bsw/jbe@1309 | 5063                 uncachedCount++; | 
| bsw/jbe@1309 | 5064                 var value = func.call(this, objProperty ? this[objProperty] : this, args); | 
| bsw/jbe@1309 | 5065                 cache[methodName] = value; | 
| bsw/jbe@1309 | 5066                 return value; | 
| bsw/jbe@1309 | 5067             } | 
| bsw/jbe@1309 | 5068         }; | 
| bsw/jbe@1309 | 5069     } | 
| bsw/jbe@1309 | 5070 | 
| bsw/jbe@1309 | 5071     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 5072 | 
| bsw/jbe@1309 | 5073     function NodeWrapper(node, session) { | 
| bsw/jbe@1309 | 5074         this.node = node; | 
| bsw/jbe@1309 | 5075         this.session = session; | 
| bsw/jbe@1309 | 5076         this.cache = new Cache(); | 
| bsw/jbe@1309 | 5077         this.positions = new Cache(); | 
| bsw/jbe@1309 | 5078     } | 
| bsw/jbe@1309 | 5079 | 
| bsw/jbe@1309 | 5080     var nodeProto = { | 
| bsw/jbe@1309 | 5081         getPosition: function(offset) { | 
| bsw/jbe@1309 | 5082             var positions = this.positions; | 
| bsw/jbe@1309 | 5083             return positions.get(offset) || positions.set(offset, new Position(this, offset)); | 
| bsw/jbe@1309 | 5084         }, | 
| bsw/jbe@1309 | 5085 | 
| bsw/jbe@1309 | 5086         toString: function() { | 
| bsw/jbe@1309 | 5087             return "[NodeWrapper(" + dom.inspectNode(this.node) + ")]"; | 
| bsw/jbe@1309 | 5088         } | 
| bsw/jbe@1309 | 5089     }; | 
| bsw/jbe@1309 | 5090 | 
| bsw/jbe@1309 | 5091     NodeWrapper.prototype = nodeProto; | 
| bsw/jbe@1309 | 5092 | 
| bsw/jbe@1309 | 5093     var EMPTY = "EMPTY", | 
| bsw/jbe@1309 | 5094         NON_SPACE = "NON_SPACE", | 
| bsw/jbe@1309 | 5095         UNCOLLAPSIBLE_SPACE = "UNCOLLAPSIBLE_SPACE", | 
| bsw/jbe@1309 | 5096         COLLAPSIBLE_SPACE = "COLLAPSIBLE_SPACE", | 
| bsw/jbe@1309 | 5097         TRAILING_SPACE_BEFORE_BLOCK = "TRAILING_SPACE_BEFORE_BLOCK", | 
| bsw/jbe@1309 | 5098         TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK", | 
| bsw/jbe@1309 | 5099         TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR", | 
| bsw/jbe@1309 | 5100         PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK", | 
| bsw/jbe@1309 | 5101         TRAILING_LINE_BREAK_AFTER_BR = "TRAILING_LINE_BREAK_AFTER_BR", | 
| bsw/jbe@1309 | 5102         INCLUDED_TRAILING_LINE_BREAK_AFTER_BR = "INCLUDED_TRAILING_LINE_BREAK_AFTER_BR"; | 
| bsw/jbe@1309 | 5103 | 
| bsw/jbe@1309 | 5104     extend(nodeProto, { | 
| bsw/jbe@1309 | 5105         isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"), | 
| bsw/jbe@1309 | 5106         getNodeIndex: createCachingGetter("nodeIndex", dom.getNodeIndex, "node"), | 
| bsw/jbe@1309 | 5107         getLength: createCachingGetter("nodeLength", dom.getNodeLength, "node"), | 
| bsw/jbe@1309 | 5108         containsPositions: createCachingGetter("containsPositions", containsPositions, "node"), | 
| bsw/jbe@1309 | 5109         isWhitespace: createCachingGetter("isWhitespace", isWhitespaceNode, "node"), | 
| bsw/jbe@1309 | 5110         isCollapsedWhitespace: createCachingGetter("isCollapsedWhitespace", isCollapsedWhitespaceNode, "node"), | 
| bsw/jbe@1309 | 5111         getComputedDisplay: createCachingGetter("computedDisplay", getComputedDisplay, "node"), | 
| bsw/jbe@1309 | 5112         isCollapsed: createCachingGetter("collapsed", isCollapsedNode, "node"), | 
| bsw/jbe@1309 | 5113         isIgnored: createCachingGetter("ignored", isIgnoredNode, "node"), | 
| bsw/jbe@1309 | 5114         next: createCachingGetter("nextPos", nextNode, "node"), | 
| bsw/jbe@1309 | 5115         previous: createCachingGetter("previous", previousNode, "node"), | 
| bsw/jbe@1309 | 5116 | 
| bsw/jbe@1309 | 5117         getTextNodeInfo: createCachingGetter("textNodeInfo", function(textNode) { | 
| bsw/jbe@1309 | 5118             var spaceRegex = null, collapseSpaces = false; | 
| bsw/jbe@1309 | 5119             var cssWhitespace = getComputedStyleProperty(textNode.parentNode, "whiteSpace"); | 
| bsw/jbe@1309 | 5120             var preLine = (cssWhitespace == "pre-line"); | 
| bsw/jbe@1309 | 5121             if (preLine) { | 
| bsw/jbe@1309 | 5122                 spaceRegex = spacesMinusLineBreaksRegex; | 
| bsw/jbe@1309 | 5123                 collapseSpaces = true; | 
| bsw/jbe@1309 | 5124             } else if (cssWhitespace == "normal" || cssWhitespace == "nowrap") { | 
| bsw/jbe@1309 | 5125                 spaceRegex = spacesRegex; | 
| bsw/jbe@1309 | 5126                 collapseSpaces = true; | 
| bsw/jbe@1309 | 5127             } | 
| bsw/jbe@1309 | 5128 | 
| bsw/jbe@1309 | 5129             return { | 
| bsw/jbe@1309 | 5130                 node: textNode, | 
| bsw/jbe@1309 | 5131                 text: textNode.data, | 
| bsw/jbe@1309 | 5132                 spaceRegex: spaceRegex, | 
| bsw/jbe@1309 | 5133                 collapseSpaces: collapseSpaces, | 
| bsw/jbe@1309 | 5134                 preLine: preLine | 
| bsw/jbe@1309 | 5135             }; | 
| bsw/jbe@1309 | 5136         }, "node"), | 
| bsw/jbe@1309 | 5137 | 
| bsw/jbe@1309 | 5138         hasInnerText: createCachingGetter("hasInnerText", function(el, backward) { | 
| bsw/jbe@1309 | 5139             var session = this.session; | 
| bsw/jbe@1309 | 5140             var posAfterEl = session.getPosition(el.parentNode, this.getNodeIndex() + 1); | 
| bsw/jbe@1309 | 5141             var firstPosInEl = session.getPosition(el, 0); | 
| bsw/jbe@1309 | 5142 | 
| bsw/jbe@1309 | 5143             var pos = backward ? posAfterEl : firstPosInEl; | 
| bsw/jbe@1309 | 5144             var endPos = backward ? firstPosInEl : posAfterEl; | 
| bsw/jbe@1309 | 5145 | 
| bsw/jbe@1309 | 5146             /* | 
| bsw/jbe@1309 | 5147              <body><p>X  </p><p>Y</p></body> | 
| bsw/jbe@1309 | 5148 | 
| bsw/jbe@1309 | 5149              Positions: | 
| bsw/jbe@1309 | 5150 | 
| bsw/jbe@1309 | 5151              body:0:"" | 
| bsw/jbe@1309 | 5152              p:0:"" | 
| bsw/jbe@1309 | 5153              text:0:"" | 
| bsw/jbe@1309 | 5154              text:1:"X" | 
| bsw/jbe@1309 | 5155              text:2:TRAILING_SPACE_IN_BLOCK | 
| bsw/jbe@1309 | 5156              text:3:COLLAPSED_SPACE | 
| bsw/jbe@1309 | 5157              p:1:"" | 
| bsw/jbe@1309 | 5158              body:1:"\n" | 
| bsw/jbe@1309 | 5159              p:0:"" | 
| bsw/jbe@1309 | 5160              text:0:"" | 
| bsw/jbe@1309 | 5161              text:1:"Y" | 
| bsw/jbe@1309 | 5162 | 
| bsw/jbe@1309 | 5163              A character is a TRAILING_SPACE_IN_BLOCK iff: | 
| bsw/jbe@1309 | 5164 | 
| bsw/jbe@1309 | 5165              - There is no uncollapsed character after it within the visible containing block element | 
| bsw/jbe@1309 | 5166 | 
| bsw/jbe@1309 | 5167              A character is a TRAILING_SPACE_BEFORE_BR iff: | 
| bsw/jbe@1309 | 5168 | 
| bsw/jbe@1309 | 5169              - There is no uncollapsed character after it preceding a <br> element | 
| bsw/jbe@1309 | 5170 | 
| bsw/jbe@1309 | 5171              An element has inner text iff | 
| bsw/jbe@1309 | 5172 | 
| bsw/jbe@1309 | 5173              - It is not hidden | 
| bsw/jbe@1309 | 5174              - It contains an uncollapsed character | 
| bsw/jbe@1309 | 5175 | 
| bsw/jbe@1309 | 5176              All trailing spaces (pre-line, before <br>, end of block) require definite non-empty characters to render. | 
| bsw/jbe@1309 | 5177              */ | 
| bsw/jbe@1309 | 5178 | 
| bsw/jbe@1309 | 5179             while (pos !== endPos) { | 
| bsw/jbe@1309 | 5180                 pos.prepopulateChar(); | 
| bsw/jbe@1309 | 5181                 if (pos.isDefinitelyNonEmpty()) { | 
| bsw/jbe@1309 | 5182                     return true; | 
| bsw/jbe@1309 | 5183                 } | 
| bsw/jbe@1309 | 5184                 pos = backward ? pos.previousVisible() : pos.nextVisible(); | 
| bsw/jbe@1309 | 5185             } | 
| bsw/jbe@1309 | 5186 | 
| bsw/jbe@1309 | 5187             return false; | 
| bsw/jbe@1309 | 5188         }, "node"), | 
| bsw/jbe@1309 | 5189 | 
| bsw/jbe@1309 | 5190         isRenderedBlock: createCachingGetter("isRenderedBlock", function(el) { | 
| bsw/jbe@1309 | 5191             // Ensure that a block element containing a <br> is considered to have inner text | 
| bsw/jbe@1309 | 5192             var brs = el.getElementsByTagName("br"); | 
| bsw/jbe@1309 | 5193             for (var i = 0, len = brs.length; i < len; ++i) { | 
| bsw/jbe@1309 | 5194                 if (!isCollapsedNode(brs[i])) { | 
| bsw/jbe@1309 | 5195                     return true; | 
| bsw/jbe@1309 | 5196                 } | 
| bsw/jbe@1309 | 5197             } | 
| bsw/jbe@1309 | 5198             return this.hasInnerText(); | 
| bsw/jbe@1309 | 5199         }, "node"), | 
| bsw/jbe@1309 | 5200 | 
| bsw/jbe@1309 | 5201         getTrailingSpace: createCachingGetter("trailingSpace", function(el) { | 
| bsw/jbe@1309 | 5202             if (el.tagName.toLowerCase() == "br") { | 
| bsw/jbe@1309 | 5203                 return ""; | 
| bsw/jbe@1309 | 5204             } else { | 
| bsw/jbe@1309 | 5205                 switch (this.getComputedDisplay()) { | 
| bsw/jbe@1309 | 5206                     case "inline": | 
| bsw/jbe@1309 | 5207                         var child = el.lastChild; | 
| bsw/jbe@1309 | 5208                         while (child) { | 
| bsw/jbe@1309 | 5209                             if (!isIgnoredNode(child)) { | 
| bsw/jbe@1309 | 5210                                 return (child.nodeType == 1) ? this.session.getNodeWrapper(child).getTrailingSpace() : ""; | 
| bsw/jbe@1309 | 5211                             } | 
| bsw/jbe@1309 | 5212                             child = child.previousSibling; | 
| bsw/jbe@1309 | 5213                         } | 
| bsw/jbe@1309 | 5214                         break; | 
| bsw/jbe@1309 | 5215                     case "inline-block": | 
| bsw/jbe@1309 | 5216                     case "inline-table": | 
| bsw/jbe@1309 | 5217                     case "none": | 
| bsw/jbe@1309 | 5218                     case "table-column": | 
| bsw/jbe@1309 | 5219                     case "table-column-group": | 
| bsw/jbe@1309 | 5220                         break; | 
| bsw/jbe@1309 | 5221                     case "table-cell": | 
| bsw/jbe@1309 | 5222                         return "\t"; | 
| bsw/jbe@1309 | 5223                     default: | 
| bsw/jbe@1309 | 5224                         return this.isRenderedBlock(true) ? "\n" : ""; | 
| bsw/jbe@1309 | 5225                 } | 
| bsw/jbe@1309 | 5226             } | 
| bsw/jbe@1309 | 5227             return ""; | 
| bsw/jbe@1309 | 5228         }, "node"), | 
| bsw/jbe@1309 | 5229 | 
| bsw/jbe@1309 | 5230         getLeadingSpace: createCachingGetter("leadingSpace", function(el) { | 
| bsw/jbe@1309 | 5231             switch (this.getComputedDisplay()) { | 
| bsw/jbe@1309 | 5232                 case "inline": | 
| bsw/jbe@1309 | 5233                 case "inline-block": | 
| bsw/jbe@1309 | 5234                 case "inline-table": | 
| bsw/jbe@1309 | 5235                 case "none": | 
| bsw/jbe@1309 | 5236                 case "table-column": | 
| bsw/jbe@1309 | 5237                 case "table-column-group": | 
| bsw/jbe@1309 | 5238                 case "table-cell": | 
| bsw/jbe@1309 | 5239                     break; | 
| bsw/jbe@1309 | 5240                 default: | 
| bsw/jbe@1309 | 5241                     return this.isRenderedBlock(false) ? "\n" : ""; | 
| bsw/jbe@1309 | 5242             } | 
| bsw/jbe@1309 | 5243             return ""; | 
| bsw/jbe@1309 | 5244         }, "node") | 
| bsw/jbe@1309 | 5245     }); | 
| bsw/jbe@1309 | 5246 | 
| bsw/jbe@1309 | 5247     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 5248 | 
| bsw/jbe@1309 | 5249     function Position(nodeWrapper, offset) { | 
| bsw/jbe@1309 | 5250         this.offset = offset; | 
| bsw/jbe@1309 | 5251         this.nodeWrapper = nodeWrapper; | 
| bsw/jbe@1309 | 5252         this.node = nodeWrapper.node; | 
| bsw/jbe@1309 | 5253         this.session = nodeWrapper.session; | 
| bsw/jbe@1309 | 5254         this.cache = new Cache(); | 
| bsw/jbe@1309 | 5255     } | 
| bsw/jbe@1309 | 5256 | 
| bsw/jbe@1309 | 5257     function inspectPosition() { | 
| bsw/jbe@1309 | 5258         return "[Position(" + dom.inspectNode(this.node) + ":" + this.offset + ")]"; | 
| bsw/jbe@1309 | 5259     } | 
| bsw/jbe@1309 | 5260 | 
| bsw/jbe@1309 | 5261     var positionProto = { | 
| bsw/jbe@1309 | 5262         character: "", | 
| bsw/jbe@1309 | 5263         characterType: EMPTY, | 
| bsw/jbe@1309 | 5264         isBr: false, | 
| bsw/jbe@1309 | 5265 | 
| bsw/jbe@1309 | 5266         /* | 
| bsw/jbe@1309 | 5267         This method: | 
| bsw/jbe@1309 | 5268         - Fully populates positions that have characters that can be determined independently of any other characters. | 
| bsw/jbe@1309 | 5269         - Populates most types of space positions with a provisional character. The character is finalized later. | 
| bsw/jbe@1309 | 5270          */ | 
| bsw/jbe@1309 | 5271         prepopulateChar: function() { | 
| bsw/jbe@1309 | 5272             var pos = this; | 
| bsw/jbe@1309 | 5273             if (!pos.prepopulatedChar) { | 
| bsw/jbe@1309 | 5274                 var node = pos.node, offset = pos.offset; | 
| bsw/jbe@1309 | 5275                 var visibleChar = "", charType = EMPTY; | 
| bsw/jbe@1309 | 5276                 var finalizedChar = false; | 
| bsw/jbe@1309 | 5277                 if (offset > 0) { | 
| bsw/jbe@1309 | 5278                     if (node.nodeType == 3) { | 
| bsw/jbe@1309 | 5279                         var text = node.data; | 
| bsw/jbe@1309 | 5280                         var textChar = text.charAt(offset - 1); | 
| bsw/jbe@1309 | 5281 | 
| bsw/jbe@1309 | 5282                         var nodeInfo = pos.nodeWrapper.getTextNodeInfo(); | 
| bsw/jbe@1309 | 5283                         var spaceRegex = nodeInfo.spaceRegex; | 
| bsw/jbe@1309 | 5284                         if (nodeInfo.collapseSpaces) { | 
| bsw/jbe@1309 | 5285                             if (spaceRegex.test(textChar)) { | 
| bsw/jbe@1309 | 5286                                 // "If the character at position is from set, append a single space (U+0020) to newdata and advance | 
| bsw/jbe@1309 | 5287                                 // position until the character at position is not from set." | 
| bsw/jbe@1309 | 5288 | 
| bsw/jbe@1309 | 5289                                 // We also need to check for the case where we're in a pre-line and we have a space preceding a | 
| bsw/jbe@1309 | 5290                                 // line break, because such spaces are collapsed in some browsers | 
| bsw/jbe@1309 | 5291                                 if (offset > 1 && spaceRegex.test(text.charAt(offset - 2))) { | 
| bsw/jbe@1309 | 5292                                 } else if (nodeInfo.preLine && text.charAt(offset) === "\n") { | 
| bsw/jbe@1309 | 5293                                     visibleChar = " "; | 
| bsw/jbe@1309 | 5294                                     charType = PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK; | 
| bsw/jbe@1309 | 5295                                 } else { | 
| bsw/jbe@1309 | 5296                                     visibleChar = " "; | 
| bsw/jbe@1309 | 5297                                     //pos.checkForFollowingLineBreak = true; | 
| bsw/jbe@1309 | 5298                                     charType = COLLAPSIBLE_SPACE; | 
| bsw/jbe@1309 | 5299                                 } | 
| bsw/jbe@1309 | 5300                             } else { | 
| bsw/jbe@1309 | 5301                                 visibleChar = textChar; | 
| bsw/jbe@1309 | 5302                                 charType = NON_SPACE; | 
| bsw/jbe@1309 | 5303                                 finalizedChar = true; | 
| bsw/jbe@1309 | 5304                             } | 
| bsw/jbe@1309 | 5305                         } else { | 
| bsw/jbe@1309 | 5306                             visibleChar = textChar; | 
| bsw/jbe@1309 | 5307                             charType = UNCOLLAPSIBLE_SPACE; | 
| bsw/jbe@1309 | 5308                             finalizedChar = true; | 
| bsw/jbe@1309 | 5309                         } | 
| bsw/jbe@1309 | 5310                     } else { | 
| bsw/jbe@1309 | 5311                         var nodePassed = node.childNodes[offset - 1]; | 
| bsw/jbe@1309 | 5312                         if (nodePassed && nodePassed.nodeType == 1 && !isCollapsedNode(nodePassed)) { | 
| bsw/jbe@1309 | 5313                             if (nodePassed.tagName.toLowerCase() == "br") { | 
| bsw/jbe@1309 | 5314                                 visibleChar = "\n"; | 
| bsw/jbe@1309 | 5315                                 pos.isBr = true; | 
| bsw/jbe@1309 | 5316                                 charType = COLLAPSIBLE_SPACE; | 
| bsw/jbe@1309 | 5317                                 finalizedChar = false; | 
| bsw/jbe@1309 | 5318                             } else { | 
| bsw/jbe@1309 | 5319                                 pos.checkForTrailingSpace = true; | 
| bsw/jbe@1309 | 5320                             } | 
| bsw/jbe@1309 | 5321                         } | 
| bsw/jbe@1309 | 5322 | 
| bsw/jbe@1309 | 5323                         // Check the leading space of the next node for the case when a block element follows an inline | 
| bsw/jbe@1309 | 5324                         // element or text node. In that case, there is an implied line break between the two nodes. | 
| bsw/jbe@1309 | 5325                         if (!visibleChar) { | 
| bsw/jbe@1309 | 5326                             var nextNode = node.childNodes[offset]; | 
| bsw/jbe@1309 | 5327                             if (nextNode && nextNode.nodeType == 1 && !isCollapsedNode(nextNode)) { | 
| bsw/jbe@1309 | 5328                                 pos.checkForLeadingSpace = true; | 
| bsw/jbe@1309 | 5329                             } | 
| bsw/jbe@1309 | 5330                         } | 
| bsw/jbe@1309 | 5331                     } | 
| bsw/jbe@1309 | 5332                 } | 
| bsw/jbe@1309 | 5333 | 
| bsw/jbe@1309 | 5334                 pos.prepopulatedChar = true; | 
| bsw/jbe@1309 | 5335                 pos.character = visibleChar; | 
| bsw/jbe@1309 | 5336                 pos.characterType = charType; | 
| bsw/jbe@1309 | 5337                 pos.isCharInvariant = finalizedChar; | 
| bsw/jbe@1309 | 5338             } | 
| bsw/jbe@1309 | 5339         }, | 
| bsw/jbe@1309 | 5340 | 
| bsw/jbe@1309 | 5341         isDefinitelyNonEmpty: function() { | 
| bsw/jbe@1309 | 5342             var charType = this.characterType; | 
| bsw/jbe@1309 | 5343             return charType == NON_SPACE || charType == UNCOLLAPSIBLE_SPACE; | 
| bsw/jbe@1309 | 5344         }, | 
| bsw/jbe@1309 | 5345 | 
| bsw/jbe@1309 | 5346         // Resolve leading and trailing spaces, which may involve prepopulating other positions | 
| bsw/jbe@1309 | 5347         resolveLeadingAndTrailingSpaces: function() { | 
| bsw/jbe@1309 | 5348             if (!this.prepopulatedChar) { | 
| bsw/jbe@1309 | 5349                 this.prepopulateChar(); | 
| bsw/jbe@1309 | 5350             } | 
| bsw/jbe@1309 | 5351             if (this.checkForTrailingSpace) { | 
| bsw/jbe@1309 | 5352                 var trailingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset - 1]).getTrailingSpace(); | 
| bsw/jbe@1309 | 5353                 if (trailingSpace) { | 
| bsw/jbe@1309 | 5354                     this.isTrailingSpace = true; | 
| bsw/jbe@1309 | 5355                     this.character = trailingSpace; | 
| bsw/jbe@1309 | 5356                     this.characterType = COLLAPSIBLE_SPACE; | 
| bsw/jbe@1309 | 5357                 } | 
| bsw/jbe@1309 | 5358                 this.checkForTrailingSpace = false; | 
| bsw/jbe@1309 | 5359             } | 
| bsw/jbe@1309 | 5360             if (this.checkForLeadingSpace) { | 
| bsw/jbe@1309 | 5361                 var leadingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace(); | 
| bsw/jbe@1309 | 5362                 if (leadingSpace) { | 
| bsw/jbe@1309 | 5363                     this.isLeadingSpace = true; | 
| bsw/jbe@1309 | 5364                     this.character = leadingSpace; | 
| bsw/jbe@1309 | 5365                     this.characterType = COLLAPSIBLE_SPACE; | 
| bsw/jbe@1309 | 5366                 } | 
| bsw/jbe@1309 | 5367                 this.checkForLeadingSpace = false; | 
| bsw/jbe@1309 | 5368             } | 
| bsw/jbe@1309 | 5369         }, | 
| bsw/jbe@1309 | 5370 | 
| bsw/jbe@1309 | 5371         getPrecedingUncollapsedPosition: function(characterOptions) { | 
| bsw/jbe@1309 | 5372             var pos = this, character; | 
| bsw/jbe@1309 | 5373             while ( (pos = pos.previousVisible()) ) { | 
| bsw/jbe@1309 | 5374                 character = pos.getCharacter(characterOptions); | 
| bsw/jbe@1309 | 5375                 if (character !== "") { | 
| bsw/jbe@1309 | 5376                     return pos; | 
| bsw/jbe@1309 | 5377                 } | 
| bsw/jbe@1309 | 5378             } | 
| bsw/jbe@1309 | 5379 | 
| bsw/jbe@1309 | 5380             return null; | 
| bsw/jbe@1309 | 5381         }, | 
| bsw/jbe@1309 | 5382 | 
| bsw/jbe@1309 | 5383         getCharacter: function(characterOptions) { | 
| bsw/jbe@1309 | 5384             this.resolveLeadingAndTrailingSpaces(); | 
| bsw/jbe@1309 | 5385 | 
| bsw/jbe@1309 | 5386             var thisChar = this.character, returnChar; | 
| bsw/jbe@1309 | 5387 | 
| bsw/jbe@1309 | 5388             // Check if character is ignored | 
| bsw/jbe@1309 | 5389             var ignoredChars = normalizeIgnoredCharacters(characterOptions.ignoreCharacters); | 
| bsw/jbe@1309 | 5390             var isIgnoredCharacter = (thisChar !== "" && ignoredChars.indexOf(thisChar) > -1); | 
| bsw/jbe@1309 | 5391 | 
| bsw/jbe@1309 | 5392             // Check if this position's  character is invariant (i.e. not dependent on character options) and return it | 
| bsw/jbe@1309 | 5393             // if so | 
| bsw/jbe@1309 | 5394             if (this.isCharInvariant) { | 
| bsw/jbe@1309 | 5395                 returnChar = isIgnoredCharacter ? "" : thisChar; | 
| bsw/jbe@1309 | 5396                 return returnChar; | 
| bsw/jbe@1309 | 5397             } | 
| bsw/jbe@1309 | 5398 | 
| bsw/jbe@1309 | 5399             var cacheKey = ["character", characterOptions.includeSpaceBeforeBr, characterOptions.includeBlockContentTrailingSpace, characterOptions.includePreLineTrailingSpace, ignoredChars].join("_"); | 
| bsw/jbe@1309 | 5400             var cachedChar = this.cache.get(cacheKey); | 
| bsw/jbe@1309 | 5401             if (cachedChar !== null) { | 
| bsw/jbe@1309 | 5402                 return cachedChar; | 
| bsw/jbe@1309 | 5403             } | 
| bsw/jbe@1309 | 5404 | 
| bsw/jbe@1309 | 5405             // We need to actually get the character now | 
| bsw/jbe@1309 | 5406             var character = ""; | 
| bsw/jbe@1309 | 5407             var collapsible = (this.characterType == COLLAPSIBLE_SPACE); | 
| bsw/jbe@1309 | 5408 | 
| bsw/jbe@1309 | 5409             var nextPos, previousPos; | 
| bsw/jbe@1309 | 5410             var gotPreviousPos = false; | 
| bsw/jbe@1309 | 5411             var pos = this; | 
| bsw/jbe@1309 | 5412 | 
| bsw/jbe@1309 | 5413             function getPreviousPos() { | 
| bsw/jbe@1309 | 5414                 if (!gotPreviousPos) { | 
| bsw/jbe@1309 | 5415                     previousPos = pos.getPrecedingUncollapsedPosition(characterOptions); | 
| bsw/jbe@1309 | 5416                     gotPreviousPos = true; | 
| bsw/jbe@1309 | 5417                 } | 
| bsw/jbe@1309 | 5418                 return previousPos; | 
| bsw/jbe@1309 | 5419             } | 
| bsw/jbe@1309 | 5420 | 
| bsw/jbe@1309 | 5421             // Disallow a collapsible space that is followed by a line break or is the last character | 
| bsw/jbe@1309 | 5422             if (collapsible) { | 
| bsw/jbe@1309 | 5423                 // Allow a trailing space that we've previously determined should be included | 
| bsw/jbe@1309 | 5424                 if (this.type == INCLUDED_TRAILING_LINE_BREAK_AFTER_BR) { | 
| bsw/jbe@1309 | 5425                     character = "\n"; | 
| bsw/jbe@1309 | 5426                 } | 
| bsw/jbe@1309 | 5427                 // Disallow a collapsible space that follows a trailing space or line break, or is the first character, | 
| bsw/jbe@1309 | 5428                 // or follows a collapsible included space | 
| bsw/jbe@1309 | 5429                 else if (thisChar == " " && | 
| bsw/jbe@1309 | 5430                         (!getPreviousPos() || previousPos.isTrailingSpace || previousPos.character == "\n" || (previousPos.character == " " && previousPos.characterType == COLLAPSIBLE_SPACE))) { | 
| bsw/jbe@1309 | 5431                 } | 
| bsw/jbe@1309 | 5432                 // Allow a leading line break unless it follows a line break | 
| bsw/jbe@1309 | 5433                 else if (thisChar == "\n" && this.isLeadingSpace) { | 
| bsw/jbe@1309 | 5434                     if (getPreviousPos() && previousPos.character != "\n") { | 
| bsw/jbe@1309 | 5435                         character = "\n"; | 
| bsw/jbe@1309 | 5436                     } else { | 
| bsw/jbe@1309 | 5437                     } | 
| bsw/jbe@1309 | 5438                 } else { | 
| bsw/jbe@1309 | 5439                     nextPos = this.nextUncollapsed(); | 
| bsw/jbe@1309 | 5440                     if (nextPos) { | 
| bsw/jbe@1309 | 5441                         if (nextPos.isBr) { | 
| bsw/jbe@1309 | 5442                             this.type = TRAILING_SPACE_BEFORE_BR; | 
| bsw/jbe@1309 | 5443                         } else if (nextPos.isTrailingSpace && nextPos.character == "\n") { | 
| bsw/jbe@1309 | 5444                             this.type = TRAILING_SPACE_IN_BLOCK; | 
| bsw/jbe@1309 | 5445                         } else if (nextPos.isLeadingSpace && nextPos.character == "\n") { | 
| bsw/jbe@1309 | 5446                             this.type = TRAILING_SPACE_BEFORE_BLOCK; | 
| bsw/jbe@1309 | 5447                         } | 
| bsw/jbe@1309 | 5448 | 
| bsw/jbe@1309 | 5449                         if (nextPos.character == "\n") { | 
| bsw/jbe@1309 | 5450                             if (this.type == TRAILING_SPACE_BEFORE_BR && !characterOptions.includeSpaceBeforeBr) { | 
| bsw/jbe@1309 | 5451                             } else if (this.type == TRAILING_SPACE_BEFORE_BLOCK && !characterOptions.includeSpaceBeforeBlock) { | 
| bsw/jbe@1309 | 5452                             } else if (this.type == TRAILING_SPACE_IN_BLOCK && nextPos.isTrailingSpace && !characterOptions.includeBlockContentTrailingSpace) { | 
| bsw/jbe@1309 | 5453                             } else if (this.type == PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK && nextPos.type == NON_SPACE && !characterOptions.includePreLineTrailingSpace) { | 
| bsw/jbe@1309 | 5454                             } else if (thisChar == "\n") { | 
| bsw/jbe@1309 | 5455                                 if (nextPos.isTrailingSpace) { | 
| bsw/jbe@1309 | 5456                                     if (this.isTrailingSpace) { | 
| bsw/jbe@1309 | 5457                                     } else if (this.isBr) { | 
| bsw/jbe@1309 | 5458                                         nextPos.type = TRAILING_LINE_BREAK_AFTER_BR; | 
| bsw/jbe@1309 | 5459 | 
| bsw/jbe@1309 | 5460                                         if (getPreviousPos() && previousPos.isLeadingSpace && !previousPos.isTrailingSpace && previousPos.character == "\n") { | 
| bsw/jbe@1309 | 5461                                             nextPos.character = ""; | 
| bsw/jbe@1309 | 5462                                         } else { | 
| bsw/jbe@1309 | 5463                                             nextPos.type = INCLUDED_TRAILING_LINE_BREAK_AFTER_BR; | 
| bsw/jbe@1309 | 5464                                         } | 
| bsw/jbe@1309 | 5465                                     } | 
| bsw/jbe@1309 | 5466                                 } else { | 
| bsw/jbe@1309 | 5467                                     character = "\n"; | 
| bsw/jbe@1309 | 5468                                 } | 
| bsw/jbe@1309 | 5469                             } else if (thisChar == " ") { | 
| bsw/jbe@1309 | 5470                                 character = " "; | 
| bsw/jbe@1309 | 5471                             } else { | 
| bsw/jbe@1309 | 5472                             } | 
| bsw/jbe@1309 | 5473                         } else { | 
| bsw/jbe@1309 | 5474                             character = thisChar; | 
| bsw/jbe@1309 | 5475                         } | 
| bsw/jbe@1309 | 5476                     } else { | 
| bsw/jbe@1309 | 5477                     } | 
| bsw/jbe@1309 | 5478                 } | 
| bsw/jbe@1309 | 5479             } | 
| bsw/jbe@1309 | 5480 | 
| bsw/jbe@1309 | 5481             if (ignoredChars.indexOf(character) > -1) { | 
| bsw/jbe@1309 | 5482                 character = ""; | 
| bsw/jbe@1309 | 5483             } | 
| bsw/jbe@1309 | 5484 | 
| bsw/jbe@1309 | 5485 | 
| bsw/jbe@1309 | 5486             this.cache.set(cacheKey, character); | 
| bsw/jbe@1309 | 5487 | 
| bsw/jbe@1309 | 5488             return character; | 
| bsw/jbe@1309 | 5489         }, | 
| bsw/jbe@1309 | 5490 | 
| bsw/jbe@1309 | 5491         equals: function(pos) { | 
| bsw/jbe@1309 | 5492             return !!pos && this.node === pos.node && this.offset === pos.offset; | 
| bsw/jbe@1309 | 5493         }, | 
| bsw/jbe@1309 | 5494 | 
| bsw/jbe@1309 | 5495         inspect: inspectPosition, | 
| bsw/jbe@1309 | 5496 | 
| bsw/jbe@1309 | 5497         toString: function() { | 
| bsw/jbe@1309 | 5498             return this.character; | 
| bsw/jbe@1309 | 5499         } | 
| bsw/jbe@1309 | 5500     }; | 
| bsw/jbe@1309 | 5501 | 
| bsw/jbe@1309 | 5502     Position.prototype = positionProto; | 
| bsw/jbe@1309 | 5503 | 
| bsw/jbe@1309 | 5504     extend(positionProto, { | 
| bsw/jbe@1309 | 5505         next: createCachingGetter("nextPos", function(pos) { | 
| bsw/jbe@1309 | 5506             var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session; | 
| bsw/jbe@1309 | 5507             if (!node) { | 
| bsw/jbe@1309 | 5508                 return null; | 
| bsw/jbe@1309 | 5509             } | 
| bsw/jbe@1309 | 5510             var nextNode, nextOffset, child; | 
| bsw/jbe@1309 | 5511             if (offset == nodeWrapper.getLength()) { | 
| bsw/jbe@1309 | 5512                 // Move onto the next node | 
| bsw/jbe@1309 | 5513                 nextNode = node.parentNode; | 
| bsw/jbe@1309 | 5514                 nextOffset = nextNode ? nodeWrapper.getNodeIndex() + 1 : 0; | 
| bsw/jbe@1309 | 5515             } else { | 
| bsw/jbe@1309 | 5516                 if (nodeWrapper.isCharacterDataNode()) { | 
| bsw/jbe@1309 | 5517                     nextNode = node; | 
| bsw/jbe@1309 | 5518                     nextOffset = offset + 1; | 
| bsw/jbe@1309 | 5519                 } else { | 
| bsw/jbe@1309 | 5520                     child = node.childNodes[offset]; | 
| bsw/jbe@1309 | 5521                     // Go into the children next, if children there are | 
| bsw/jbe@1309 | 5522                     if (session.getNodeWrapper(child).containsPositions()) { | 
| bsw/jbe@1309 | 5523                         nextNode = child; | 
| bsw/jbe@1309 | 5524                         nextOffset = 0; | 
| bsw/jbe@1309 | 5525                     } else { | 
| bsw/jbe@1309 | 5526                         nextNode = node; | 
| bsw/jbe@1309 | 5527                         nextOffset = offset + 1; | 
| bsw/jbe@1309 | 5528                     } | 
| bsw/jbe@1309 | 5529                 } | 
| bsw/jbe@1309 | 5530             } | 
| bsw/jbe@1309 | 5531 | 
| bsw/jbe@1309 | 5532             return nextNode ? session.getPosition(nextNode, nextOffset) : null; | 
| bsw/jbe@1309 | 5533         }), | 
| bsw/jbe@1309 | 5534 | 
| bsw/jbe@1309 | 5535         previous: createCachingGetter("previous", function(pos) { | 
| bsw/jbe@1309 | 5536             var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session; | 
| bsw/jbe@1309 | 5537             var previousNode, previousOffset, child; | 
| bsw/jbe@1309 | 5538             if (offset == 0) { | 
| bsw/jbe@1309 | 5539                 previousNode = node.parentNode; | 
| bsw/jbe@1309 | 5540                 previousOffset = previousNode ? nodeWrapper.getNodeIndex() : 0; | 
| bsw/jbe@1309 | 5541             } else { | 
| bsw/jbe@1309 | 5542                 if (nodeWrapper.isCharacterDataNode()) { | 
| bsw/jbe@1309 | 5543                     previousNode = node; | 
| bsw/jbe@1309 | 5544                     previousOffset = offset - 1; | 
| bsw/jbe@1309 | 5545                 } else { | 
| bsw/jbe@1309 | 5546                     child = node.childNodes[offset - 1]; | 
| bsw/jbe@1309 | 5547                     // Go into the children next, if children there are | 
| bsw/jbe@1309 | 5548                     if (session.getNodeWrapper(child).containsPositions()) { | 
| bsw/jbe@1309 | 5549                         previousNode = child; | 
| bsw/jbe@1309 | 5550                         previousOffset = dom.getNodeLength(child); | 
| bsw/jbe@1309 | 5551                     } else { | 
| bsw/jbe@1309 | 5552                         previousNode = node; | 
| bsw/jbe@1309 | 5553                         previousOffset = offset - 1; | 
| bsw/jbe@1309 | 5554                     } | 
| bsw/jbe@1309 | 5555                 } | 
| bsw/jbe@1309 | 5556             } | 
| bsw/jbe@1309 | 5557             return previousNode ? session.getPosition(previousNode, previousOffset) : null; | 
| bsw/jbe@1309 | 5558         }), | 
| bsw/jbe@1309 | 5559 | 
| bsw/jbe@1309 | 5560         /* | 
| bsw/jbe@1309 | 5561          Next and previous position moving functions that filter out | 
| bsw/jbe@1309 | 5562 | 
| bsw/jbe@1309 | 5563          - Hidden (CSS visibility/display) elements | 
| bsw/jbe@1309 | 5564          - Script and style elements | 
| bsw/jbe@1309 | 5565          */ | 
| bsw/jbe@1309 | 5566         nextVisible: createCachingGetter("nextVisible", function(pos) { | 
| bsw/jbe@1309 | 5567             var next = pos.next(); | 
| bsw/jbe@1309 | 5568             if (!next) { | 
| bsw/jbe@1309 | 5569                 return null; | 
| bsw/jbe@1309 | 5570             } | 
| bsw/jbe@1309 | 5571             var nodeWrapper = next.nodeWrapper, node = next.node; | 
| bsw/jbe@1309 | 5572             var newPos = next; | 
| bsw/jbe@1309 | 5573             if (nodeWrapper.isCollapsed()) { | 
| bsw/jbe@1309 | 5574                 // We're skipping this node and all its descendants | 
| bsw/jbe@1309 | 5575                 newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex() + 1); | 
| bsw/jbe@1309 | 5576             } | 
| bsw/jbe@1309 | 5577             return newPos; | 
| bsw/jbe@1309 | 5578         }), | 
| bsw/jbe@1309 | 5579 | 
| bsw/jbe@1309 | 5580         nextUncollapsed: createCachingGetter("nextUncollapsed", function(pos) { | 
| bsw/jbe@1309 | 5581             var nextPos = pos; | 
| bsw/jbe@1309 | 5582             while ( (nextPos = nextPos.nextVisible()) ) { | 
| bsw/jbe@1309 | 5583                 nextPos.resolveLeadingAndTrailingSpaces(); | 
| bsw/jbe@1309 | 5584                 if (nextPos.character !== "") { | 
| bsw/jbe@1309 | 5585                     return nextPos; | 
| bsw/jbe@1309 | 5586                 } | 
| bsw/jbe@1309 | 5587             } | 
| bsw/jbe@1309 | 5588             return null; | 
| bsw/jbe@1309 | 5589         }), | 
| bsw/jbe@1309 | 5590 | 
| bsw/jbe@1309 | 5591         previousVisible: createCachingGetter("previousVisible", function(pos) { | 
| bsw/jbe@1309 | 5592             var previous = pos.previous(); | 
| bsw/jbe@1309 | 5593             if (!previous) { | 
| bsw/jbe@1309 | 5594                 return null; | 
| bsw/jbe@1309 | 5595             } | 
| bsw/jbe@1309 | 5596             var nodeWrapper = previous.nodeWrapper, node = previous.node; | 
| bsw/jbe@1309 | 5597             var newPos = previous; | 
| bsw/jbe@1309 | 5598             if (nodeWrapper.isCollapsed()) { | 
| bsw/jbe@1309 | 5599                 // We're skipping this node and all its descendants | 
| bsw/jbe@1309 | 5600                 newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex()); | 
| bsw/jbe@1309 | 5601             } | 
| bsw/jbe@1309 | 5602             return newPos; | 
| bsw/jbe@1309 | 5603         }) | 
| bsw/jbe@1309 | 5604     }); | 
| bsw/jbe@1309 | 5605 | 
| bsw/jbe@1309 | 5606     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 5607 | 
| bsw/jbe@1309 | 5608     var currentSession = null; | 
| bsw/jbe@1309 | 5609 | 
| bsw/jbe@1309 | 5610     var Session = (function() { | 
| bsw/jbe@1309 | 5611         function createWrapperCache(nodeProperty) { | 
| bsw/jbe@1309 | 5612             var cache = new Cache(); | 
| bsw/jbe@1309 | 5613 | 
| bsw/jbe@1309 | 5614             return { | 
| bsw/jbe@1309 | 5615                 get: function(node) { | 
| bsw/jbe@1309 | 5616                     var wrappersByProperty = cache.get(node[nodeProperty]); | 
| bsw/jbe@1309 | 5617                     if (wrappersByProperty) { | 
| bsw/jbe@1309 | 5618                         for (var i = 0, wrapper; wrapper = wrappersByProperty[i++]; ) { | 
| bsw/jbe@1309 | 5619                             if (wrapper.node === node) { | 
| bsw/jbe@1309 | 5620                                 return wrapper; | 
| bsw/jbe@1309 | 5621                             } | 
| bsw/jbe@1309 | 5622                         } | 
| bsw/jbe@1309 | 5623                     } | 
| bsw/jbe@1309 | 5624                     return null; | 
| bsw/jbe@1309 | 5625                 }, | 
| bsw/jbe@1309 | 5626 | 
| bsw/jbe@1309 | 5627                 set: function(nodeWrapper) { | 
| bsw/jbe@1309 | 5628                     var property = nodeWrapper.node[nodeProperty]; | 
| bsw/jbe@1309 | 5629                     var wrappersByProperty = cache.get(property) || cache.set(property, []); | 
| bsw/jbe@1309 | 5630                     wrappersByProperty.push(nodeWrapper); | 
| bsw/jbe@1309 | 5631                 } | 
| bsw/jbe@1309 | 5632             }; | 
| bsw/jbe@1309 | 5633         } | 
| bsw/jbe@1309 | 5634 | 
| bsw/jbe@1309 | 5635         var uniqueIDSupported = util.isHostProperty(document.documentElement, "uniqueID"); | 
| bsw/jbe@1309 | 5636 | 
| bsw/jbe@1309 | 5637         function Session() { | 
| bsw/jbe@1309 | 5638             this.initCaches(); | 
| bsw/jbe@1309 | 5639         } | 
| bsw/jbe@1309 | 5640 | 
| bsw/jbe@1309 | 5641         Session.prototype = { | 
| bsw/jbe@1309 | 5642             initCaches: function() { | 
| bsw/jbe@1309 | 5643                 this.elementCache = uniqueIDSupported ? (function() { | 
| bsw/jbe@1309 | 5644                     var elementsCache = new Cache(); | 
| bsw/jbe@1309 | 5645 | 
| bsw/jbe@1309 | 5646                     return { | 
| bsw/jbe@1309 | 5647                         get: function(el) { | 
| bsw/jbe@1309 | 5648                             return elementsCache.get(el.uniqueID); | 
| bsw/jbe@1309 | 5649                         }, | 
| bsw/jbe@1309 | 5650 | 
| bsw/jbe@1309 | 5651                         set: function(elWrapper) { | 
| bsw/jbe@1309 | 5652                             elementsCache.set(elWrapper.node.uniqueID, elWrapper); | 
| bsw/jbe@1309 | 5653                         } | 
| bsw/jbe@1309 | 5654                     }; | 
| bsw/jbe@1309 | 5655                 })() : createWrapperCache("tagName"); | 
| bsw/jbe@1309 | 5656 | 
| bsw/jbe@1309 | 5657                 // Store text nodes keyed by data, although we may need to truncate this | 
| bsw/jbe@1309 | 5658                 this.textNodeCache = createWrapperCache("data"); | 
| bsw/jbe@1309 | 5659                 this.otherNodeCache = createWrapperCache("nodeName"); | 
| bsw/jbe@1309 | 5660             }, | 
| bsw/jbe@1309 | 5661 | 
| bsw/jbe@1309 | 5662             getNodeWrapper: function(node) { | 
| bsw/jbe@1309 | 5663                 var wrapperCache; | 
| bsw/jbe@1309 | 5664                 switch (node.nodeType) { | 
| bsw/jbe@1309 | 5665                     case 1: | 
| bsw/jbe@1309 | 5666                         wrapperCache = this.elementCache; | 
| bsw/jbe@1309 | 5667                         break; | 
| bsw/jbe@1309 | 5668                     case 3: | 
| bsw/jbe@1309 | 5669                         wrapperCache = this.textNodeCache; | 
| bsw/jbe@1309 | 5670                         break; | 
| bsw/jbe@1309 | 5671                     default: | 
| bsw/jbe@1309 | 5672                         wrapperCache = this.otherNodeCache; | 
| bsw/jbe@1309 | 5673                         break; | 
| bsw/jbe@1309 | 5674                 } | 
| bsw/jbe@1309 | 5675 | 
| bsw/jbe@1309 | 5676                 var wrapper = wrapperCache.get(node); | 
| bsw/jbe@1309 | 5677                 if (!wrapper) { | 
| bsw/jbe@1309 | 5678                     wrapper = new NodeWrapper(node, this); | 
| bsw/jbe@1309 | 5679                     wrapperCache.set(wrapper); | 
| bsw/jbe@1309 | 5680                 } | 
| bsw/jbe@1309 | 5681                 return wrapper; | 
| bsw/jbe@1309 | 5682             }, | 
| bsw/jbe@1309 | 5683 | 
| bsw/jbe@1309 | 5684             getPosition: function(node, offset) { | 
| bsw/jbe@1309 | 5685                 return this.getNodeWrapper(node).getPosition(offset); | 
| bsw/jbe@1309 | 5686             }, | 
| bsw/jbe@1309 | 5687 | 
| bsw/jbe@1309 | 5688             getRangeBoundaryPosition: function(range, isStart) { | 
| bsw/jbe@1309 | 5689                 var prefix = isStart ? "start" : "end"; | 
| bsw/jbe@1309 | 5690                 return this.getPosition(range[prefix + "Container"], range[prefix + "Offset"]); | 
| bsw/jbe@1309 | 5691             }, | 
| bsw/jbe@1309 | 5692 | 
| bsw/jbe@1309 | 5693             detach: function() { | 
| bsw/jbe@1309 | 5694                 this.elementCache = this.textNodeCache = this.otherNodeCache = null; | 
| bsw/jbe@1309 | 5695             } | 
| bsw/jbe@1309 | 5696         }; | 
| bsw/jbe@1309 | 5697 | 
| bsw/jbe@1309 | 5698         return Session; | 
| bsw/jbe@1309 | 5699     })(); | 
| bsw/jbe@1309 | 5700 | 
| bsw/jbe@1309 | 5701     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 5702 | 
| bsw/jbe@1309 | 5703     function startSession() { | 
| bsw/jbe@1309 | 5704         endSession(); | 
| bsw/jbe@1309 | 5705         return (currentSession = new Session()); | 
| bsw/jbe@1309 | 5706     } | 
| bsw/jbe@1309 | 5707 | 
| bsw/jbe@1309 | 5708     function getSession() { | 
| bsw/jbe@1309 | 5709         return currentSession || startSession(); | 
| bsw/jbe@1309 | 5710     } | 
| bsw/jbe@1309 | 5711 | 
| bsw/jbe@1309 | 5712     function endSession() { | 
| bsw/jbe@1309 | 5713         if (currentSession) { | 
| bsw/jbe@1309 | 5714             currentSession.detach(); | 
| bsw/jbe@1309 | 5715         } | 
| bsw/jbe@1309 | 5716         currentSession = null; | 
| bsw/jbe@1309 | 5717     } | 
| bsw/jbe@1309 | 5718 | 
| bsw/jbe@1309 | 5719     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 5720 | 
| bsw/jbe@1309 | 5721     // Extensions to the rangy.dom utility object | 
| bsw/jbe@1309 | 5722 | 
| bsw/jbe@1309 | 5723     extend(dom, { | 
| bsw/jbe@1309 | 5724         nextNode: nextNode, | 
| bsw/jbe@1309 | 5725         previousNode: previousNode | 
| bsw/jbe@1309 | 5726     }); | 
| bsw/jbe@1309 | 5727 | 
| bsw/jbe@1309 | 5728     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 5729 | 
| bsw/jbe@1309 | 5730     function createCharacterIterator(startPos, backward, endPos, characterOptions) { | 
| bsw/jbe@1309 | 5731 | 
| bsw/jbe@1309 | 5732         // Adjust the end position to ensure that it is actually reached | 
| bsw/jbe@1309 | 5733         if (endPos) { | 
| bsw/jbe@1309 | 5734             if (backward) { | 
| bsw/jbe@1309 | 5735                 if (isCollapsedNode(endPos.node)) { | 
| bsw/jbe@1309 | 5736                     endPos = startPos.previousVisible(); | 
| bsw/jbe@1309 | 5737                 } | 
| bsw/jbe@1309 | 5738             } else { | 
| bsw/jbe@1309 | 5739                 if (isCollapsedNode(endPos.node)) { | 
| bsw/jbe@1309 | 5740                     endPos = endPos.nextVisible(); | 
| bsw/jbe@1309 | 5741                 } | 
| bsw/jbe@1309 | 5742             } | 
| bsw/jbe@1309 | 5743         } | 
| bsw/jbe@1309 | 5744 | 
| bsw/jbe@1309 | 5745         var pos = startPos, finished = false; | 
| bsw/jbe@1309 | 5746 | 
| bsw/jbe@1309 | 5747         function next() { | 
| bsw/jbe@1309 | 5748             var charPos = null; | 
| bsw/jbe@1309 | 5749             if (backward) { | 
| bsw/jbe@1309 | 5750                 charPos = pos; | 
| bsw/jbe@1309 | 5751                 if (!finished) { | 
| bsw/jbe@1309 | 5752                     pos = pos.previousVisible(); | 
| bsw/jbe@1309 | 5753                     finished = !pos || (endPos && pos.equals(endPos)); | 
| bsw/jbe@1309 | 5754                 } | 
| bsw/jbe@1309 | 5755             } else { | 
| bsw/jbe@1309 | 5756                 if (!finished) { | 
| bsw/jbe@1309 | 5757                     charPos = pos = pos.nextVisible(); | 
| bsw/jbe@1309 | 5758                     finished = !pos || (endPos && pos.equals(endPos)); | 
| bsw/jbe@1309 | 5759                 } | 
| bsw/jbe@1309 | 5760             } | 
| bsw/jbe@1309 | 5761             if (finished) { | 
| bsw/jbe@1309 | 5762                 pos = null; | 
| bsw/jbe@1309 | 5763             } | 
| bsw/jbe@1309 | 5764             return charPos; | 
| bsw/jbe@1309 | 5765         } | 
| bsw/jbe@1309 | 5766 | 
| bsw/jbe@1309 | 5767         var previousTextPos, returnPreviousTextPos = false; | 
| bsw/jbe@1309 | 5768 | 
| bsw/jbe@1309 | 5769         return { | 
| bsw/jbe@1309 | 5770             next: function() { | 
| bsw/jbe@1309 | 5771                 if (returnPreviousTextPos) { | 
| bsw/jbe@1309 | 5772                     returnPreviousTextPos = false; | 
| bsw/jbe@1309 | 5773                     return previousTextPos; | 
| bsw/jbe@1309 | 5774                 } else { | 
| bsw/jbe@1309 | 5775                     var pos, character; | 
| bsw/jbe@1309 | 5776                     while ( (pos = next()) ) { | 
| bsw/jbe@1309 | 5777                         character = pos.getCharacter(characterOptions); | 
| bsw/jbe@1309 | 5778                         if (character) { | 
| bsw/jbe@1309 | 5779                             previousTextPos = pos; | 
| bsw/jbe@1309 | 5780                             return pos; | 
| bsw/jbe@1309 | 5781                         } | 
| bsw/jbe@1309 | 5782                     } | 
| bsw/jbe@1309 | 5783                     return null; | 
| bsw/jbe@1309 | 5784                 } | 
| bsw/jbe@1309 | 5785             }, | 
| bsw/jbe@1309 | 5786 | 
| bsw/jbe@1309 | 5787             rewind: function() { | 
| bsw/jbe@1309 | 5788                 if (previousTextPos) { | 
| bsw/jbe@1309 | 5789                     returnPreviousTextPos = true; | 
| bsw/jbe@1309 | 5790                 } else { | 
| bsw/jbe@1309 | 5791                     throw module.createError("createCharacterIterator: cannot rewind. Only one position can be rewound."); | 
| bsw/jbe@1309 | 5792                 } | 
| bsw/jbe@1309 | 5793             }, | 
| bsw/jbe@1309 | 5794 | 
| bsw/jbe@1309 | 5795             dispose: function() { | 
| bsw/jbe@1309 | 5796                 startPos = endPos = null; | 
| bsw/jbe@1309 | 5797             } | 
| bsw/jbe@1309 | 5798         }; | 
| bsw/jbe@1309 | 5799     } | 
| bsw/jbe@1309 | 5800 | 
| bsw/jbe@1309 | 5801     var arrayIndexOf = Array.prototype.indexOf ? | 
| bsw/jbe@1309 | 5802         function(arr, val) { | 
| bsw/jbe@1309 | 5803             return arr.indexOf(val); | 
| bsw/jbe@1309 | 5804         } : | 
| bsw/jbe@1309 | 5805         function(arr, val) { | 
| bsw/jbe@1309 | 5806             for (var i = 0, len = arr.length; i < len; ++i) { | 
| bsw/jbe@1309 | 5807                 if (arr[i] === val) { | 
| bsw/jbe@1309 | 5808                     return i; | 
| bsw/jbe@1309 | 5809                 } | 
| bsw/jbe@1309 | 5810             } | 
| bsw/jbe@1309 | 5811             return -1; | 
| bsw/jbe@1309 | 5812         }; | 
| bsw/jbe@1309 | 5813 | 
| bsw/jbe@1309 | 5814     // Provides a pair of iterators over text positions, tokenized. Transparently requests more text when next() | 
| bsw/jbe@1309 | 5815     // is called and there is no more tokenized text | 
| bsw/jbe@1309 | 5816     function createTokenizedTextProvider(pos, characterOptions, wordOptions) { | 
| bsw/jbe@1309 | 5817         var forwardIterator = createCharacterIterator(pos, false, null, characterOptions); | 
| bsw/jbe@1309 | 5818         var backwardIterator = createCharacterIterator(pos, true, null, characterOptions); | 
| bsw/jbe@1309 | 5819         var tokenizer = wordOptions.tokenizer; | 
| bsw/jbe@1309 | 5820 | 
| bsw/jbe@1309 | 5821         // Consumes a word and the whitespace beyond it | 
| bsw/jbe@1309 | 5822         function consumeWord(forward) { | 
| bsw/jbe@1309 | 5823             var pos, textChar; | 
| bsw/jbe@1309 | 5824             var newChars = [], it = forward ? forwardIterator : backwardIterator; | 
| bsw/jbe@1309 | 5825 | 
| bsw/jbe@1309 | 5826             var passedWordBoundary = false, insideWord = false; | 
| bsw/jbe@1309 | 5827 | 
| bsw/jbe@1309 | 5828             while ( (pos = it.next()) ) { | 
| bsw/jbe@1309 | 5829                 textChar = pos.character; | 
| bsw/jbe@1309 | 5830 | 
| bsw/jbe@1309 | 5831 | 
| bsw/jbe@1309 | 5832                 if (allWhiteSpaceRegex.test(textChar)) { | 
| bsw/jbe@1309 | 5833                     if (insideWord) { | 
| bsw/jbe@1309 | 5834                         insideWord = false; | 
| bsw/jbe@1309 | 5835                         passedWordBoundary = true; | 
| bsw/jbe@1309 | 5836                     } | 
| bsw/jbe@1309 | 5837                 } else { | 
| bsw/jbe@1309 | 5838                     if (passedWordBoundary) { | 
| bsw/jbe@1309 | 5839                         it.rewind(); | 
| bsw/jbe@1309 | 5840                         break; | 
| bsw/jbe@1309 | 5841                     } else { | 
| bsw/jbe@1309 | 5842                         insideWord = true; | 
| bsw/jbe@1309 | 5843                     } | 
| bsw/jbe@1309 | 5844                 } | 
| bsw/jbe@1309 | 5845                 newChars.push(pos); | 
| bsw/jbe@1309 | 5846             } | 
| bsw/jbe@1309 | 5847 | 
| bsw/jbe@1309 | 5848 | 
| bsw/jbe@1309 | 5849             return newChars; | 
| bsw/jbe@1309 | 5850         } | 
| bsw/jbe@1309 | 5851 | 
| bsw/jbe@1309 | 5852         // Get initial word surrounding initial position and tokenize it | 
| bsw/jbe@1309 | 5853         var forwardChars = consumeWord(true); | 
| bsw/jbe@1309 | 5854         var backwardChars = consumeWord(false).reverse(); | 
| bsw/jbe@1309 | 5855         var tokens = tokenize(backwardChars.concat(forwardChars), wordOptions, tokenizer); | 
| bsw/jbe@1309 | 5856 | 
| bsw/jbe@1309 | 5857         // Create initial token buffers | 
| bsw/jbe@1309 | 5858         var forwardTokensBuffer = forwardChars.length ? | 
| bsw/jbe@1309 | 5859             tokens.slice(arrayIndexOf(tokens, forwardChars[0].token)) : []; | 
| bsw/jbe@1309 | 5860 | 
| bsw/jbe@1309 | 5861         var backwardTokensBuffer = backwardChars.length ? | 
| bsw/jbe@1309 | 5862             tokens.slice(0, arrayIndexOf(tokens, backwardChars.pop().token) + 1) : []; | 
| bsw/jbe@1309 | 5863 | 
| bsw/jbe@1309 | 5864         function inspectBuffer(buffer) { | 
| bsw/jbe@1309 | 5865             var textPositions = ["[" + buffer.length + "]"]; | 
| bsw/jbe@1309 | 5866             for (var i = 0; i < buffer.length; ++i) { | 
| bsw/jbe@1309 | 5867                 textPositions.push("(word: " + buffer[i] + ", is word: " + buffer[i].isWord + ")"); | 
| bsw/jbe@1309 | 5868             } | 
| bsw/jbe@1309 | 5869             return textPositions; | 
| bsw/jbe@1309 | 5870         } | 
| bsw/jbe@1309 | 5871 | 
| bsw/jbe@1309 | 5872 | 
| bsw/jbe@1309 | 5873         return { | 
| bsw/jbe@1309 | 5874             nextEndToken: function() { | 
| bsw/jbe@1309 | 5875                 var lastToken, forwardChars; | 
| bsw/jbe@1309 | 5876 | 
| bsw/jbe@1309 | 5877                 // If we're down to the last token, consume character chunks until we have a word or run out of | 
| bsw/jbe@1309 | 5878                 // characters to consume | 
| bsw/jbe@1309 | 5879                 while ( forwardTokensBuffer.length == 1 && | 
| bsw/jbe@1309 | 5880                     !(lastToken = forwardTokensBuffer[0]).isWord && | 
| bsw/jbe@1309 | 5881                     (forwardChars = consumeWord(true)).length > 0) { | 
| bsw/jbe@1309 | 5882 | 
| bsw/jbe@1309 | 5883                     // Merge trailing non-word into next word and tokenize | 
| bsw/jbe@1309 | 5884                     forwardTokensBuffer = tokenize(lastToken.chars.concat(forwardChars), wordOptions, tokenizer); | 
| bsw/jbe@1309 | 5885                 } | 
| bsw/jbe@1309 | 5886 | 
| bsw/jbe@1309 | 5887                 return forwardTokensBuffer.shift(); | 
| bsw/jbe@1309 | 5888             }, | 
| bsw/jbe@1309 | 5889 | 
| bsw/jbe@1309 | 5890             previousStartToken: function() { | 
| bsw/jbe@1309 | 5891                 var lastToken, backwardChars; | 
| bsw/jbe@1309 | 5892 | 
| bsw/jbe@1309 | 5893                 // If we're down to the last token, consume character chunks until we have a word or run out of | 
| bsw/jbe@1309 | 5894                 // characters to consume | 
| bsw/jbe@1309 | 5895                 while ( backwardTokensBuffer.length == 1 && | 
| bsw/jbe@1309 | 5896                     !(lastToken = backwardTokensBuffer[0]).isWord && | 
| bsw/jbe@1309 | 5897                     (backwardChars = consumeWord(false)).length > 0) { | 
| bsw/jbe@1309 | 5898 | 
| bsw/jbe@1309 | 5899                     // Merge leading non-word into next word and tokenize | 
| bsw/jbe@1309 | 5900                     backwardTokensBuffer = tokenize(backwardChars.reverse().concat(lastToken.chars), wordOptions, tokenizer); | 
| bsw/jbe@1309 | 5901                 } | 
| bsw/jbe@1309 | 5902 | 
| bsw/jbe@1309 | 5903                 return backwardTokensBuffer.pop(); | 
| bsw/jbe@1309 | 5904             }, | 
| bsw/jbe@1309 | 5905 | 
| bsw/jbe@1309 | 5906             dispose: function() { | 
| bsw/jbe@1309 | 5907                 forwardIterator.dispose(); | 
| bsw/jbe@1309 | 5908                 backwardIterator.dispose(); | 
| bsw/jbe@1309 | 5909                 forwardTokensBuffer = backwardTokensBuffer = null; | 
| bsw/jbe@1309 | 5910             } | 
| bsw/jbe@1309 | 5911         }; | 
| bsw/jbe@1309 | 5912     } | 
| bsw/jbe@1309 | 5913 | 
| bsw/jbe@1309 | 5914     function movePositionBy(pos, unit, count, characterOptions, wordOptions) { | 
| bsw/jbe@1309 | 5915         var unitsMoved = 0, currentPos, newPos = pos, charIterator, nextPos, absCount = Math.abs(count), token; | 
| bsw/jbe@1309 | 5916         if (count !== 0) { | 
| bsw/jbe@1309 | 5917             var backward = (count < 0); | 
| bsw/jbe@1309 | 5918 | 
| bsw/jbe@1309 | 5919             switch (unit) { | 
| bsw/jbe@1309 | 5920                 case CHARACTER: | 
| bsw/jbe@1309 | 5921                     charIterator = createCharacterIterator(pos, backward, null, characterOptions); | 
| bsw/jbe@1309 | 5922                     while ( (currentPos = charIterator.next()) && unitsMoved < absCount ) { | 
| bsw/jbe@1309 | 5923                         ++unitsMoved; | 
| bsw/jbe@1309 | 5924                         newPos = currentPos; | 
| bsw/jbe@1309 | 5925                     } | 
| bsw/jbe@1309 | 5926                     nextPos = currentPos; | 
| bsw/jbe@1309 | 5927                     charIterator.dispose(); | 
| bsw/jbe@1309 | 5928                     break; | 
| bsw/jbe@1309 | 5929                 case WORD: | 
| bsw/jbe@1309 | 5930                     var tokenizedTextProvider = createTokenizedTextProvider(pos, characterOptions, wordOptions); | 
| bsw/jbe@1309 | 5931                     var next = backward ? tokenizedTextProvider.previousStartToken : tokenizedTextProvider.nextEndToken; | 
| bsw/jbe@1309 | 5932 | 
| bsw/jbe@1309 | 5933                     while ( (token = next()) && unitsMoved < absCount ) { | 
| bsw/jbe@1309 | 5934                         if (token.isWord) { | 
| bsw/jbe@1309 | 5935                             ++unitsMoved; | 
| bsw/jbe@1309 | 5936                             newPos = backward ? token.chars[0] : token.chars[token.chars.length - 1]; | 
| bsw/jbe@1309 | 5937                         } | 
| bsw/jbe@1309 | 5938                     } | 
| bsw/jbe@1309 | 5939                     break; | 
| bsw/jbe@1309 | 5940                 default: | 
| bsw/jbe@1309 | 5941                     throw new Error("movePositionBy: unit '" + unit + "' not implemented"); | 
| bsw/jbe@1309 | 5942             } | 
| bsw/jbe@1309 | 5943 | 
| bsw/jbe@1309 | 5944             // Perform any necessary position tweaks | 
| bsw/jbe@1309 | 5945             if (backward) { | 
| bsw/jbe@1309 | 5946                 newPos = newPos.previousVisible(); | 
| bsw/jbe@1309 | 5947                 unitsMoved = -unitsMoved; | 
| bsw/jbe@1309 | 5948             } else if (newPos && newPos.isLeadingSpace && !newPos.isTrailingSpace) { | 
| bsw/jbe@1309 | 5949                 // Tweak the position for the case of a leading space. The problem is that an uncollapsed leading space | 
| bsw/jbe@1309 | 5950                 // before a block element (for example, the line break between "1" and "2" in the following HTML: | 
| bsw/jbe@1309 | 5951                 // "1<p>2</p>") is considered to be attached to the position immediately before the block element, which | 
| bsw/jbe@1309 | 5952                 // corresponds with a different selection position in most browsers from the one we want (i.e. at the | 
| bsw/jbe@1309 | 5953                 // start of the contents of the block element). We get round this by advancing the position returned to | 
| bsw/jbe@1309 | 5954                 // the last possible equivalent visible position. | 
| bsw/jbe@1309 | 5955                 if (unit == WORD) { | 
| bsw/jbe@1309 | 5956                     charIterator = createCharacterIterator(pos, false, null, characterOptions); | 
| bsw/jbe@1309 | 5957                     nextPos = charIterator.next(); | 
| bsw/jbe@1309 | 5958                     charIterator.dispose(); | 
| bsw/jbe@1309 | 5959                 } | 
| bsw/jbe@1309 | 5960                 if (nextPos) { | 
| bsw/jbe@1309 | 5961                     newPos = nextPos.previousVisible(); | 
| bsw/jbe@1309 | 5962                 } | 
| bsw/jbe@1309 | 5963             } | 
| bsw/jbe@1309 | 5964         } | 
| bsw/jbe@1309 | 5965 | 
| bsw/jbe@1309 | 5966 | 
| bsw/jbe@1309 | 5967         return { | 
| bsw/jbe@1309 | 5968             position: newPos, | 
| bsw/jbe@1309 | 5969             unitsMoved: unitsMoved | 
| bsw/jbe@1309 | 5970         }; | 
| bsw/jbe@1309 | 5971     } | 
| bsw/jbe@1309 | 5972 | 
| bsw/jbe@1309 | 5973     function createRangeCharacterIterator(session, range, characterOptions, backward) { | 
| bsw/jbe@1309 | 5974         var rangeStart = session.getRangeBoundaryPosition(range, true); | 
| bsw/jbe@1309 | 5975         var rangeEnd = session.getRangeBoundaryPosition(range, false); | 
| bsw/jbe@1309 | 5976         var itStart = backward ? rangeEnd : rangeStart; | 
| bsw/jbe@1309 | 5977         var itEnd = backward ? rangeStart : rangeEnd; | 
| bsw/jbe@1309 | 5978 | 
| bsw/jbe@1309 | 5979         return createCharacterIterator(itStart, !!backward, itEnd, characterOptions); | 
| bsw/jbe@1309 | 5980     } | 
| bsw/jbe@1309 | 5981 | 
| bsw/jbe@1309 | 5982     function getRangeCharacters(session, range, characterOptions) { | 
| bsw/jbe@1309 | 5983 | 
| bsw/jbe@1309 | 5984         var chars = [], it = createRangeCharacterIterator(session, range, characterOptions), pos; | 
| bsw/jbe@1309 | 5985         while ( (pos = it.next()) ) { | 
| bsw/jbe@1309 | 5986             chars.push(pos); | 
| bsw/jbe@1309 | 5987         } | 
| bsw/jbe@1309 | 5988 | 
| bsw/jbe@1309 | 5989         it.dispose(); | 
| bsw/jbe@1309 | 5990         return chars; | 
| bsw/jbe@1309 | 5991     } | 
| bsw/jbe@1309 | 5992 | 
| bsw/jbe@1309 | 5993     function isWholeWord(startPos, endPos, wordOptions) { | 
| bsw/jbe@1309 | 5994         var range = api.createRange(startPos.node); | 
| bsw/jbe@1309 | 5995         range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset); | 
| bsw/jbe@1309 | 5996         return !range.expand("word", { wordOptions: wordOptions }); | 
| bsw/jbe@1309 | 5997     } | 
| bsw/jbe@1309 | 5998 | 
| bsw/jbe@1309 | 5999     function findTextFromPosition(initialPos, searchTerm, isRegex, searchScopeRange, findOptions) { | 
| bsw/jbe@1309 | 6000         var backward = isDirectionBackward(findOptions.direction); | 
| bsw/jbe@1309 | 6001         var it = createCharacterIterator( | 
| bsw/jbe@1309 | 6002             initialPos, | 
| bsw/jbe@1309 | 6003             backward, | 
| bsw/jbe@1309 | 6004             initialPos.session.getRangeBoundaryPosition(searchScopeRange, backward), | 
| bsw/jbe@1309 | 6005             findOptions.characterOptions | 
| bsw/jbe@1309 | 6006         ); | 
| bsw/jbe@1309 | 6007         var text = "", chars = [], pos, currentChar, matchStartIndex, matchEndIndex; | 
| bsw/jbe@1309 | 6008         var result, insideRegexMatch; | 
| bsw/jbe@1309 | 6009         var returnValue = null; | 
| bsw/jbe@1309 | 6010 | 
| bsw/jbe@1309 | 6011         function handleMatch(startIndex, endIndex) { | 
| bsw/jbe@1309 | 6012             var startPos = chars[startIndex].previousVisible(); | 
| bsw/jbe@1309 | 6013             var endPos = chars[endIndex - 1]; | 
| bsw/jbe@1309 | 6014             var valid = (!findOptions.wholeWordsOnly || isWholeWord(startPos, endPos, findOptions.wordOptions)); | 
| bsw/jbe@1309 | 6015 | 
| bsw/jbe@1309 | 6016             return { | 
| bsw/jbe@1309 | 6017                 startPos: startPos, | 
| bsw/jbe@1309 | 6018                 endPos: endPos, | 
| bsw/jbe@1309 | 6019                 valid: valid | 
| bsw/jbe@1309 | 6020             }; | 
| bsw/jbe@1309 | 6021         } | 
| bsw/jbe@1309 | 6022 | 
| bsw/jbe@1309 | 6023         while ( (pos = it.next()) ) { | 
| bsw/jbe@1309 | 6024             currentChar = pos.character; | 
| bsw/jbe@1309 | 6025             if (!isRegex && !findOptions.caseSensitive) { | 
| bsw/jbe@1309 | 6026                 currentChar = currentChar.toLowerCase(); | 
| bsw/jbe@1309 | 6027             } | 
| bsw/jbe@1309 | 6028 | 
| bsw/jbe@1309 | 6029             if (backward) { | 
| bsw/jbe@1309 | 6030                 chars.unshift(pos); | 
| bsw/jbe@1309 | 6031                 text = currentChar + text; | 
| bsw/jbe@1309 | 6032             } else { | 
| bsw/jbe@1309 | 6033                 chars.push(pos); | 
| bsw/jbe@1309 | 6034                 text += currentChar; | 
| bsw/jbe@1309 | 6035             } | 
| bsw/jbe@1309 | 6036 | 
| bsw/jbe@1309 | 6037             if (isRegex) { | 
| bsw/jbe@1309 | 6038                 result = searchTerm.exec(text); | 
| bsw/jbe@1309 | 6039                 if (result) { | 
| bsw/jbe@1309 | 6040                     matchStartIndex = result.index; | 
| bsw/jbe@1309 | 6041                     matchEndIndex = matchStartIndex + result[0].length; | 
| bsw/jbe@1309 | 6042                     if (insideRegexMatch) { | 
| bsw/jbe@1309 | 6043                         // Check whether the match is now over | 
| bsw/jbe@1309 | 6044                         if ((!backward && matchEndIndex < text.length) || (backward && matchStartIndex > 0)) { | 
| bsw/jbe@1309 | 6045                             returnValue = handleMatch(matchStartIndex, matchEndIndex); | 
| bsw/jbe@1309 | 6046                             break; | 
| bsw/jbe@1309 | 6047                         } | 
| bsw/jbe@1309 | 6048                     } else { | 
| bsw/jbe@1309 | 6049                         insideRegexMatch = true; | 
| bsw/jbe@1309 | 6050                     } | 
| bsw/jbe@1309 | 6051                 } | 
| bsw/jbe@1309 | 6052             } else if ( (matchStartIndex = text.indexOf(searchTerm)) != -1 ) { | 
| bsw/jbe@1309 | 6053                 returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length); | 
| bsw/jbe@1309 | 6054                 break; | 
| bsw/jbe@1309 | 6055             } | 
| bsw/jbe@1309 | 6056         } | 
| bsw/jbe@1309 | 6057 | 
| bsw/jbe@1309 | 6058         // Check whether regex match extends to the end of the range | 
| bsw/jbe@1309 | 6059         if (insideRegexMatch) { | 
| bsw/jbe@1309 | 6060             returnValue = handleMatch(matchStartIndex, matchEndIndex); | 
| bsw/jbe@1309 | 6061         } | 
| bsw/jbe@1309 | 6062         it.dispose(); | 
| bsw/jbe@1309 | 6063 | 
| bsw/jbe@1309 | 6064         return returnValue; | 
| bsw/jbe@1309 | 6065     } | 
| bsw/jbe@1309 | 6066 | 
| bsw/jbe@1309 | 6067     function createEntryPointFunction(func) { | 
| bsw/jbe@1309 | 6068         return function() { | 
| bsw/jbe@1309 | 6069             var sessionRunning = !!currentSession; | 
| bsw/jbe@1309 | 6070             var session = getSession(); | 
| bsw/jbe@1309 | 6071             var args = [session].concat( util.toArray(arguments) ); | 
| bsw/jbe@1309 | 6072             var returnValue = func.apply(this, args); | 
| bsw/jbe@1309 | 6073             if (!sessionRunning) { | 
| bsw/jbe@1309 | 6074                 endSession(); | 
| bsw/jbe@1309 | 6075             } | 
| bsw/jbe@1309 | 6076             return returnValue; | 
| bsw/jbe@1309 | 6077         }; | 
| bsw/jbe@1309 | 6078     } | 
| bsw/jbe@1309 | 6079 | 
| bsw/jbe@1309 | 6080     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 6081 | 
| bsw/jbe@1309 | 6082     // Extensions to the Rangy Range object | 
| bsw/jbe@1309 | 6083 | 
| bsw/jbe@1309 | 6084     function createRangeBoundaryMover(isStart, collapse) { | 
| bsw/jbe@1309 | 6085         /* | 
| bsw/jbe@1309 | 6086          Unit can be "character" or "word" | 
| bsw/jbe@1309 | 6087          Options: | 
| bsw/jbe@1309 | 6088 | 
| bsw/jbe@1309 | 6089          - includeTrailingSpace | 
| bsw/jbe@1309 | 6090          - wordRegex | 
| bsw/jbe@1309 | 6091          - tokenizer | 
| bsw/jbe@1309 | 6092          - collapseSpaceBeforeLineBreak | 
| bsw/jbe@1309 | 6093          */ | 
| bsw/jbe@1309 | 6094         return createEntryPointFunction( | 
| bsw/jbe@1309 | 6095             function(session, unit, count, moveOptions) { | 
| bsw/jbe@1309 | 6096                 if (typeof count == UNDEF) { | 
| bsw/jbe@1309 | 6097                     count = unit; | 
| bsw/jbe@1309 | 6098                     unit = CHARACTER; | 
| bsw/jbe@1309 | 6099                 } | 
| bsw/jbe@1309 | 6100                 moveOptions = createNestedOptions(moveOptions, defaultMoveOptions); | 
| bsw/jbe@1309 | 6101 | 
| bsw/jbe@1309 | 6102                 var boundaryIsStart = isStart; | 
| bsw/jbe@1309 | 6103                 if (collapse) { | 
| bsw/jbe@1309 | 6104                     boundaryIsStart = (count >= 0); | 
| bsw/jbe@1309 | 6105                     this.collapse(!boundaryIsStart); | 
| bsw/jbe@1309 | 6106                 } | 
| bsw/jbe@1309 | 6107                 var moveResult = movePositionBy(session.getRangeBoundaryPosition(this, boundaryIsStart), unit, count, moveOptions.characterOptions, moveOptions.wordOptions); | 
| bsw/jbe@1309 | 6108                 var newPos = moveResult.position; | 
| bsw/jbe@1309 | 6109                 this[boundaryIsStart ? "setStart" : "setEnd"](newPos.node, newPos.offset); | 
| bsw/jbe@1309 | 6110                 return moveResult.unitsMoved; | 
| bsw/jbe@1309 | 6111             } | 
| bsw/jbe@1309 | 6112         ); | 
| bsw/jbe@1309 | 6113     } | 
| bsw/jbe@1309 | 6114 | 
| bsw/jbe@1309 | 6115     function createRangeTrimmer(isStart) { | 
| bsw/jbe@1309 | 6116         return createEntryPointFunction( | 
| bsw/jbe@1309 | 6117             function(session, characterOptions) { | 
| bsw/jbe@1309 | 6118                 characterOptions = createOptions(characterOptions, defaultCharacterOptions); | 
| bsw/jbe@1309 | 6119                 var pos; | 
| bsw/jbe@1309 | 6120                 var it = createRangeCharacterIterator(session, this, characterOptions, !isStart); | 
| bsw/jbe@1309 | 6121                 var trimCharCount = 0; | 
| bsw/jbe@1309 | 6122                 while ( (pos = it.next()) && allWhiteSpaceRegex.test(pos.character) ) { | 
| bsw/jbe@1309 | 6123                     ++trimCharCount; | 
| bsw/jbe@1309 | 6124                 } | 
| bsw/jbe@1309 | 6125                 it.dispose(); | 
| bsw/jbe@1309 | 6126                 var trimmed = (trimCharCount > 0); | 
| bsw/jbe@1309 | 6127                 if (trimmed) { | 
| bsw/jbe@1309 | 6128                     this[isStart ? "moveStart" : "moveEnd"]( | 
| bsw/jbe@1309 | 6129                         "character", | 
| bsw/jbe@1309 | 6130                         isStart ? trimCharCount : -trimCharCount, | 
| bsw/jbe@1309 | 6131                         { characterOptions: characterOptions } | 
| bsw/jbe@1309 | 6132                     ); | 
| bsw/jbe@1309 | 6133                 } | 
| bsw/jbe@1309 | 6134                 return trimmed; | 
| bsw/jbe@1309 | 6135             } | 
| bsw/jbe@1309 | 6136         ); | 
| bsw/jbe@1309 | 6137     } | 
| bsw/jbe@1309 | 6138 | 
| bsw/jbe@1309 | 6139     extend(api.rangePrototype, { | 
| bsw/jbe@1309 | 6140         moveStart: createRangeBoundaryMover(true, false), | 
| bsw/jbe@1309 | 6141 | 
| bsw/jbe@1309 | 6142         moveEnd: createRangeBoundaryMover(false, false), | 
| bsw/jbe@1309 | 6143 | 
| bsw/jbe@1309 | 6144         move: createRangeBoundaryMover(true, true), | 
| bsw/jbe@1309 | 6145 | 
| bsw/jbe@1309 | 6146         trimStart: createRangeTrimmer(true), | 
| bsw/jbe@1309 | 6147 | 
| bsw/jbe@1309 | 6148         trimEnd: createRangeTrimmer(false), | 
| bsw/jbe@1309 | 6149 | 
| bsw/jbe@1309 | 6150         trim: createEntryPointFunction( | 
| bsw/jbe@1309 | 6151             function(session, characterOptions) { | 
| bsw/jbe@1309 | 6152                 var startTrimmed = this.trimStart(characterOptions), endTrimmed = this.trimEnd(characterOptions); | 
| bsw/jbe@1309 | 6153                 return startTrimmed || endTrimmed; | 
| bsw/jbe@1309 | 6154             } | 
| bsw/jbe@1309 | 6155         ), | 
| bsw/jbe@1309 | 6156 | 
| bsw/jbe@1309 | 6157         expand: createEntryPointFunction( | 
| bsw/jbe@1309 | 6158             function(session, unit, expandOptions) { | 
| bsw/jbe@1309 | 6159                 var moved = false; | 
| bsw/jbe@1309 | 6160                 expandOptions = createNestedOptions(expandOptions, defaultExpandOptions); | 
| bsw/jbe@1309 | 6161                 var characterOptions = expandOptions.characterOptions; | 
| bsw/jbe@1309 | 6162                 if (!unit) { | 
| bsw/jbe@1309 | 6163                     unit = CHARACTER; | 
| bsw/jbe@1309 | 6164                 } | 
| bsw/jbe@1309 | 6165                 if (unit == WORD) { | 
| bsw/jbe@1309 | 6166                     var wordOptions = expandOptions.wordOptions; | 
| bsw/jbe@1309 | 6167                     var startPos = session.getRangeBoundaryPosition(this, true); | 
| bsw/jbe@1309 | 6168                     var endPos = session.getRangeBoundaryPosition(this, false); | 
| bsw/jbe@1309 | 6169 | 
| bsw/jbe@1309 | 6170                     var startTokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions); | 
| bsw/jbe@1309 | 6171                     var startToken = startTokenizedTextProvider.nextEndToken(); | 
| bsw/jbe@1309 | 6172                     var newStartPos = startToken.chars[0].previousVisible(); | 
| bsw/jbe@1309 | 6173                     var endToken, newEndPos; | 
| bsw/jbe@1309 | 6174 | 
| bsw/jbe@1309 | 6175                     if (this.collapsed) { | 
| bsw/jbe@1309 | 6176                         endToken = startToken; | 
| bsw/jbe@1309 | 6177                     } else { | 
| bsw/jbe@1309 | 6178                         var endTokenizedTextProvider = createTokenizedTextProvider(endPos, characterOptions, wordOptions); | 
| bsw/jbe@1309 | 6179                         endToken = endTokenizedTextProvider.previousStartToken(); | 
| bsw/jbe@1309 | 6180                     } | 
| bsw/jbe@1309 | 6181                     newEndPos = endToken.chars[endToken.chars.length - 1]; | 
| bsw/jbe@1309 | 6182 | 
| bsw/jbe@1309 | 6183                     if (!newStartPos.equals(startPos)) { | 
| bsw/jbe@1309 | 6184                         this.setStart(newStartPos.node, newStartPos.offset); | 
| bsw/jbe@1309 | 6185                         moved = true; | 
| bsw/jbe@1309 | 6186                     } | 
| bsw/jbe@1309 | 6187                     if (newEndPos && !newEndPos.equals(endPos)) { | 
| bsw/jbe@1309 | 6188                         this.setEnd(newEndPos.node, newEndPos.offset); | 
| bsw/jbe@1309 | 6189                         moved = true; | 
| bsw/jbe@1309 | 6190                     } | 
| bsw/jbe@1309 | 6191 | 
| bsw/jbe@1309 | 6192                     if (expandOptions.trim) { | 
| bsw/jbe@1309 | 6193                         if (expandOptions.trimStart) { | 
| bsw/jbe@1309 | 6194                             moved = this.trimStart(characterOptions) || moved; | 
| bsw/jbe@1309 | 6195                         } | 
| bsw/jbe@1309 | 6196                         if (expandOptions.trimEnd) { | 
| bsw/jbe@1309 | 6197                             moved = this.trimEnd(characterOptions) || moved; | 
| bsw/jbe@1309 | 6198                         } | 
| bsw/jbe@1309 | 6199                     } | 
| bsw/jbe@1309 | 6200 | 
| bsw/jbe@1309 | 6201                     return moved; | 
| bsw/jbe@1309 | 6202                 } else { | 
| bsw/jbe@1309 | 6203                     return this.moveEnd(CHARACTER, 1, expandOptions); | 
| bsw/jbe@1309 | 6204                 } | 
| bsw/jbe@1309 | 6205             } | 
| bsw/jbe@1309 | 6206         ), | 
| bsw/jbe@1309 | 6207 | 
| bsw/jbe@1309 | 6208         text: createEntryPointFunction( | 
| bsw/jbe@1309 | 6209             function(session, characterOptions) { | 
| bsw/jbe@1309 | 6210                 return this.collapsed ? | 
| bsw/jbe@1309 | 6211                     "" : getRangeCharacters(session, this, createOptions(characterOptions, defaultCharacterOptions)).join(""); | 
| bsw/jbe@1309 | 6212             } | 
| bsw/jbe@1309 | 6213         ), | 
| bsw/jbe@1309 | 6214 | 
| bsw/jbe@1309 | 6215         selectCharacters: createEntryPointFunction( | 
| bsw/jbe@1309 | 6216             function(session, containerNode, startIndex, endIndex, characterOptions) { | 
| bsw/jbe@1309 | 6217                 var moveOptions = { characterOptions: characterOptions }; | 
| bsw/jbe@1309 | 6218                 if (!containerNode) { | 
| bsw/jbe@1309 | 6219                     containerNode = getBody( this.getDocument() ); | 
| bsw/jbe@1309 | 6220                 } | 
| bsw/jbe@1309 | 6221                 this.selectNodeContents(containerNode); | 
| bsw/jbe@1309 | 6222                 this.collapse(true); | 
| bsw/jbe@1309 | 6223                 this.moveStart("character", startIndex, moveOptions); | 
| bsw/jbe@1309 | 6224                 this.collapse(true); | 
| bsw/jbe@1309 | 6225                 this.moveEnd("character", endIndex - startIndex, moveOptions); | 
| bsw/jbe@1309 | 6226             } | 
| bsw/jbe@1309 | 6227         ), | 
| bsw/jbe@1309 | 6228 | 
| bsw/jbe@1309 | 6229         // Character indexes are relative to the start of node | 
| bsw/jbe@1309 | 6230         toCharacterRange: createEntryPointFunction( | 
| bsw/jbe@1309 | 6231             function(session, containerNode, characterOptions) { | 
| bsw/jbe@1309 | 6232                 if (!containerNode) { | 
| bsw/jbe@1309 | 6233                     containerNode = getBody( this.getDocument() ); | 
| bsw/jbe@1309 | 6234                 } | 
| bsw/jbe@1309 | 6235                 var parent = containerNode.parentNode, nodeIndex = dom.getNodeIndex(containerNode); | 
| bsw/jbe@1309 | 6236                 var rangeStartsBeforeNode = (dom.comparePoints(this.startContainer, this.endContainer, parent, nodeIndex) == -1); | 
| bsw/jbe@1309 | 6237                 var rangeBetween = this.cloneRange(); | 
| bsw/jbe@1309 | 6238                 var startIndex, endIndex; | 
| bsw/jbe@1309 | 6239                 if (rangeStartsBeforeNode) { | 
| bsw/jbe@1309 | 6240                     rangeBetween.setStartAndEnd(this.startContainer, this.startOffset, parent, nodeIndex); | 
| bsw/jbe@1309 | 6241                     startIndex = -rangeBetween.text(characterOptions).length; | 
| bsw/jbe@1309 | 6242                 } else { | 
| bsw/jbe@1309 | 6243                     rangeBetween.setStartAndEnd(parent, nodeIndex, this.startContainer, this.startOffset); | 
| bsw/jbe@1309 | 6244                     startIndex = rangeBetween.text(characterOptions).length; | 
| bsw/jbe@1309 | 6245                 } | 
| bsw/jbe@1309 | 6246                 endIndex = startIndex + this.text(characterOptions).length; | 
| bsw/jbe@1309 | 6247 | 
| bsw/jbe@1309 | 6248                 return { | 
| bsw/jbe@1309 | 6249                     start: startIndex, | 
| bsw/jbe@1309 | 6250                     end: endIndex | 
| bsw/jbe@1309 | 6251                 }; | 
| bsw/jbe@1309 | 6252             } | 
| bsw/jbe@1309 | 6253         ), | 
| bsw/jbe@1309 | 6254 | 
| bsw/jbe@1309 | 6255         findText: createEntryPointFunction( | 
| bsw/jbe@1309 | 6256             function(session, searchTermParam, findOptions) { | 
| bsw/jbe@1309 | 6257                 // Set up options | 
| bsw/jbe@1309 | 6258                 findOptions = createNestedOptions(findOptions, defaultFindOptions); | 
| bsw/jbe@1309 | 6259 | 
| bsw/jbe@1309 | 6260                 // Create word options if we're matching whole words only | 
| bsw/jbe@1309 | 6261                 if (findOptions.wholeWordsOnly) { | 
| bsw/jbe@1309 | 6262                     // We don't ever want trailing spaces for search results | 
| bsw/jbe@1309 | 6263                     findOptions.wordOptions.includeTrailingSpace = false; | 
| bsw/jbe@1309 | 6264                 } | 
| bsw/jbe@1309 | 6265 | 
| bsw/jbe@1309 | 6266                 var backward = isDirectionBackward(findOptions.direction); | 
| bsw/jbe@1309 | 6267 | 
| bsw/jbe@1309 | 6268                 // Create a range representing the search scope if none was provided | 
| bsw/jbe@1309 | 6269                 var searchScopeRange = findOptions.withinRange; | 
| bsw/jbe@1309 | 6270                 if (!searchScopeRange) { | 
| bsw/jbe@1309 | 6271                     searchScopeRange = api.createRange(); | 
| bsw/jbe@1309 | 6272                     searchScopeRange.selectNodeContents(this.getDocument()); | 
| bsw/jbe@1309 | 6273                 } | 
| bsw/jbe@1309 | 6274 | 
| bsw/jbe@1309 | 6275                 // Examine and prepare the search term | 
| bsw/jbe@1309 | 6276                 var searchTerm = searchTermParam, isRegex = false; | 
| bsw/jbe@1309 | 6277                 if (typeof searchTerm == "string") { | 
| bsw/jbe@1309 | 6278                     if (!findOptions.caseSensitive) { | 
| bsw/jbe@1309 | 6279                         searchTerm = searchTerm.toLowerCase(); | 
| bsw/jbe@1309 | 6280                     } | 
| bsw/jbe@1309 | 6281                 } else { | 
| bsw/jbe@1309 | 6282                     isRegex = true; | 
| bsw/jbe@1309 | 6283                 } | 
| bsw/jbe@1309 | 6284 | 
| bsw/jbe@1309 | 6285                 var initialPos = session.getRangeBoundaryPosition(this, !backward); | 
| bsw/jbe@1309 | 6286 | 
| bsw/jbe@1309 | 6287                 // Adjust initial position if it lies outside the search scope | 
| bsw/jbe@1309 | 6288                 var comparison = searchScopeRange.comparePoint(initialPos.node, initialPos.offset); | 
| bsw/jbe@1309 | 6289 | 
| bsw/jbe@1309 | 6290                 if (comparison === -1) { | 
| bsw/jbe@1309 | 6291                     initialPos = session.getRangeBoundaryPosition(searchScopeRange, true); | 
| bsw/jbe@1309 | 6292                 } else if (comparison === 1) { | 
| bsw/jbe@1309 | 6293                     initialPos = session.getRangeBoundaryPosition(searchScopeRange, false); | 
| bsw/jbe@1309 | 6294                 } | 
| bsw/jbe@1309 | 6295 | 
| bsw/jbe@1309 | 6296                 var pos = initialPos; | 
| bsw/jbe@1309 | 6297                 var wrappedAround = false; | 
| bsw/jbe@1309 | 6298 | 
| bsw/jbe@1309 | 6299                 // Try to find a match and ignore invalid ones | 
| bsw/jbe@1309 | 6300                 var findResult; | 
| bsw/jbe@1309 | 6301                 while (true) { | 
| bsw/jbe@1309 | 6302                     findResult = findTextFromPosition(pos, searchTerm, isRegex, searchScopeRange, findOptions); | 
| bsw/jbe@1309 | 6303 | 
| bsw/jbe@1309 | 6304                     if (findResult) { | 
| bsw/jbe@1309 | 6305                         if (findResult.valid) { | 
| bsw/jbe@1309 | 6306                             this.setStartAndEnd(findResult.startPos.node, findResult.startPos.offset, findResult.endPos.node, findResult.endPos.offset); | 
| bsw/jbe@1309 | 6307                             return true; | 
| bsw/jbe@1309 | 6308                         } else { | 
| bsw/jbe@1309 | 6309                             // We've found a match that is not a whole word, so we carry on searching from the point immediately | 
| bsw/jbe@1309 | 6310                             // after the match | 
| bsw/jbe@1309 | 6311                             pos = backward ? findResult.startPos : findResult.endPos; | 
| bsw/jbe@1309 | 6312                         } | 
| bsw/jbe@1309 | 6313                     } else if (findOptions.wrap && !wrappedAround) { | 
| bsw/jbe@1309 | 6314                         // No result found but we're wrapping around and limiting the scope to the unsearched part of the range | 
| bsw/jbe@1309 | 6315                         searchScopeRange = searchScopeRange.cloneRange(); | 
| bsw/jbe@1309 | 6316                         pos = session.getRangeBoundaryPosition(searchScopeRange, !backward); | 
| bsw/jbe@1309 | 6317                         searchScopeRange.setBoundary(initialPos.node, initialPos.offset, backward); | 
| bsw/jbe@1309 | 6318                         wrappedAround = true; | 
| bsw/jbe@1309 | 6319                     } else { | 
| bsw/jbe@1309 | 6320                         // Nothing found and we can't wrap around, so we're done | 
| bsw/jbe@1309 | 6321                         return false; | 
| bsw/jbe@1309 | 6322                     } | 
| bsw/jbe@1309 | 6323                 } | 
| bsw/jbe@1309 | 6324             } | 
| bsw/jbe@1309 | 6325         ), | 
| bsw/jbe@1309 | 6326 | 
| bsw/jbe@1309 | 6327         pasteHtml: function(html) { | 
| bsw/jbe@1309 | 6328             this.deleteContents(); | 
| bsw/jbe@1309 | 6329             if (html) { | 
| bsw/jbe@1309 | 6330                 var frag = this.createContextualFragment(html); | 
| bsw/jbe@1309 | 6331                 var lastChild = frag.lastChild; | 
| bsw/jbe@1309 | 6332                 this.insertNode(frag); | 
| bsw/jbe@1309 | 6333                 this.collapseAfter(lastChild); | 
| bsw/jbe@1309 | 6334             } | 
| bsw/jbe@1309 | 6335         } | 
| bsw/jbe@1309 | 6336     }); | 
| bsw/jbe@1309 | 6337 | 
| bsw/jbe@1309 | 6338     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 6339 | 
| bsw/jbe@1309 | 6340     // Extensions to the Rangy Selection object | 
| bsw/jbe@1309 | 6341 | 
| bsw/jbe@1309 | 6342     function createSelectionTrimmer(methodName) { | 
| bsw/jbe@1309 | 6343         return createEntryPointFunction( | 
| bsw/jbe@1309 | 6344             function(session, characterOptions) { | 
| bsw/jbe@1309 | 6345                 var trimmed = false; | 
| bsw/jbe@1309 | 6346                 this.changeEachRange(function(range) { | 
| bsw/jbe@1309 | 6347                     trimmed = range[methodName](characterOptions) || trimmed; | 
| bsw/jbe@1309 | 6348                 }); | 
| bsw/jbe@1309 | 6349                 return trimmed; | 
| bsw/jbe@1309 | 6350             } | 
| bsw/jbe@1309 | 6351         ); | 
| bsw/jbe@1309 | 6352     } | 
| bsw/jbe@1309 | 6353 | 
| bsw/jbe@1309 | 6354     extend(api.selectionPrototype, { | 
| bsw/jbe@1309 | 6355         expand: createEntryPointFunction( | 
| bsw/jbe@1309 | 6356             function(session, unit, expandOptions) { | 
| bsw/jbe@1309 | 6357                 this.changeEachRange(function(range) { | 
| bsw/jbe@1309 | 6358                     range.expand(unit, expandOptions); | 
| bsw/jbe@1309 | 6359                 }); | 
| bsw/jbe@1309 | 6360             } | 
| bsw/jbe@1309 | 6361         ), | 
| bsw/jbe@1309 | 6362 | 
| bsw/jbe@1309 | 6363         move: createEntryPointFunction( | 
| bsw/jbe@1309 | 6364             function(session, unit, count, options) { | 
| bsw/jbe@1309 | 6365                 var unitsMoved = 0; | 
| bsw/jbe@1309 | 6366                 if (this.focusNode) { | 
| bsw/jbe@1309 | 6367                     this.collapse(this.focusNode, this.focusOffset); | 
| bsw/jbe@1309 | 6368                     var range = this.getRangeAt(0); | 
| bsw/jbe@1309 | 6369                     if (!options) { | 
| bsw/jbe@1309 | 6370                         options = {}; | 
| bsw/jbe@1309 | 6371                     } | 
| bsw/jbe@1309 | 6372                     options.characterOptions = createOptions(options.characterOptions, defaultCaretCharacterOptions); | 
| bsw/jbe@1309 | 6373                     unitsMoved = range.move(unit, count, options); | 
| bsw/jbe@1309 | 6374                     this.setSingleRange(range); | 
| bsw/jbe@1309 | 6375                 } | 
| bsw/jbe@1309 | 6376                 return unitsMoved; | 
| bsw/jbe@1309 | 6377             } | 
| bsw/jbe@1309 | 6378         ), | 
| bsw/jbe@1309 | 6379 | 
| bsw/jbe@1309 | 6380         trimStart: createSelectionTrimmer("trimStart"), | 
| bsw/jbe@1309 | 6381         trimEnd: createSelectionTrimmer("trimEnd"), | 
| bsw/jbe@1309 | 6382         trim: createSelectionTrimmer("trim"), | 
| bsw/jbe@1309 | 6383 | 
| bsw/jbe@1309 | 6384         selectCharacters: createEntryPointFunction( | 
| bsw/jbe@1309 | 6385             function(session, containerNode, startIndex, endIndex, direction, characterOptions) { | 
| bsw/jbe@1309 | 6386                 var range = api.createRange(containerNode); | 
| bsw/jbe@1309 | 6387                 range.selectCharacters(containerNode, startIndex, endIndex, characterOptions); | 
| bsw/jbe@1309 | 6388                 this.setSingleRange(range, direction); | 
| bsw/jbe@1309 | 6389             } | 
| bsw/jbe@1309 | 6390         ), | 
| bsw/jbe@1309 | 6391 | 
| bsw/jbe@1309 | 6392         saveCharacterRanges: createEntryPointFunction( | 
| bsw/jbe@1309 | 6393             function(session, containerNode, characterOptions) { | 
| bsw/jbe@1309 | 6394                 var ranges = this.getAllRanges(), rangeCount = ranges.length; | 
| bsw/jbe@1309 | 6395                 var rangeInfos = []; | 
| bsw/jbe@1309 | 6396 | 
| bsw/jbe@1309 | 6397                 var backward = rangeCount == 1 && this.isBackward(); | 
| bsw/jbe@1309 | 6398 | 
| bsw/jbe@1309 | 6399                 for (var i = 0, len = ranges.length; i < len; ++i) { | 
| bsw/jbe@1309 | 6400                     rangeInfos[i] = { | 
| bsw/jbe@1309 | 6401                         characterRange: ranges[i].toCharacterRange(containerNode, characterOptions), | 
| bsw/jbe@1309 | 6402                         backward: backward, | 
| bsw/jbe@1309 | 6403                         characterOptions: characterOptions | 
| bsw/jbe@1309 | 6404                     }; | 
| bsw/jbe@1309 | 6405                 } | 
| bsw/jbe@1309 | 6406 | 
| bsw/jbe@1309 | 6407                 return rangeInfos; | 
| bsw/jbe@1309 | 6408             } | 
| bsw/jbe@1309 | 6409         ), | 
| bsw/jbe@1309 | 6410 | 
| bsw/jbe@1309 | 6411         restoreCharacterRanges: createEntryPointFunction( | 
| bsw/jbe@1309 | 6412             function(session, containerNode, saved) { | 
| bsw/jbe@1309 | 6413                 this.removeAllRanges(); | 
| bsw/jbe@1309 | 6414                 for (var i = 0, len = saved.length, range, rangeInfo, characterRange; i < len; ++i) { | 
| bsw/jbe@1309 | 6415                     rangeInfo = saved[i]; | 
| bsw/jbe@1309 | 6416                     characterRange = rangeInfo.characterRange; | 
| bsw/jbe@1309 | 6417                     range = api.createRange(containerNode); | 
| bsw/jbe@1309 | 6418                     range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions); | 
| bsw/jbe@1309 | 6419                     this.addRange(range, rangeInfo.backward); | 
| bsw/jbe@1309 | 6420                 } | 
| bsw/jbe@1309 | 6421             } | 
| bsw/jbe@1309 | 6422         ), | 
| bsw/jbe@1309 | 6423 | 
| bsw/jbe@1309 | 6424         text: createEntryPointFunction( | 
| bsw/jbe@1309 | 6425             function(session, characterOptions) { | 
| bsw/jbe@1309 | 6426                 var rangeTexts = []; | 
| bsw/jbe@1309 | 6427                 for (var i = 0, len = this.rangeCount; i < len; ++i) { | 
| bsw/jbe@1309 | 6428                     rangeTexts[i] = this.getRangeAt(i).text(characterOptions); | 
| bsw/jbe@1309 | 6429                 } | 
| bsw/jbe@1309 | 6430                 return rangeTexts.join(""); | 
| bsw/jbe@1309 | 6431             } | 
| bsw/jbe@1309 | 6432         ) | 
| bsw/jbe@1309 | 6433     }); | 
| bsw/jbe@1309 | 6434 | 
| bsw/jbe@1309 | 6435     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 6436 | 
| bsw/jbe@1309 | 6437     // Extensions to the core rangy object | 
| bsw/jbe@1309 | 6438 | 
| bsw/jbe@1309 | 6439     api.innerText = function(el, characterOptions) { | 
| bsw/jbe@1309 | 6440         var range = api.createRange(el); | 
| bsw/jbe@1309 | 6441         range.selectNodeContents(el); | 
| bsw/jbe@1309 | 6442         var text = range.text(characterOptions); | 
| bsw/jbe@1309 | 6443         return text; | 
| bsw/jbe@1309 | 6444     }; | 
| bsw/jbe@1309 | 6445 | 
| bsw/jbe@1309 | 6446     api.createWordIterator = function(startNode, startOffset, iteratorOptions) { | 
| bsw/jbe@1309 | 6447         var session = getSession(); | 
| bsw/jbe@1309 | 6448         iteratorOptions = createNestedOptions(iteratorOptions, defaultWordIteratorOptions); | 
| bsw/jbe@1309 | 6449         var startPos = session.getPosition(startNode, startOffset); | 
| bsw/jbe@1309 | 6450         var tokenizedTextProvider = createTokenizedTextProvider(startPos, iteratorOptions.characterOptions, iteratorOptions.wordOptions); | 
| bsw/jbe@1309 | 6451         var backward = isDirectionBackward(iteratorOptions.direction); | 
| bsw/jbe@1309 | 6452 | 
| bsw/jbe@1309 | 6453         return { | 
| bsw/jbe@1309 | 6454             next: function() { | 
| bsw/jbe@1309 | 6455                 return backward ? tokenizedTextProvider.previousStartToken() : tokenizedTextProvider.nextEndToken(); | 
| bsw/jbe@1309 | 6456             }, | 
| bsw/jbe@1309 | 6457 | 
| bsw/jbe@1309 | 6458             dispose: function() { | 
| bsw/jbe@1309 | 6459                 tokenizedTextProvider.dispose(); | 
| bsw/jbe@1309 | 6460                 this.next = function() {}; | 
| bsw/jbe@1309 | 6461             } | 
| bsw/jbe@1309 | 6462         }; | 
| bsw/jbe@1309 | 6463     }; | 
| bsw/jbe@1309 | 6464 | 
| bsw/jbe@1309 | 6465     /*----------------------------------------------------------------------------------------------------------------*/ | 
| bsw/jbe@1309 | 6466 | 
| bsw/jbe@1309 | 6467     api.noMutation = function(func) { | 
| bsw/jbe@1309 | 6468         var session = getSession(); | 
| bsw/jbe@1309 | 6469         func(session); | 
| bsw/jbe@1309 | 6470         endSession(); | 
| bsw/jbe@1309 | 6471     }; | 
| bsw/jbe@1309 | 6472 | 
| bsw/jbe@1309 | 6473     api.noMutation.createEntryPointFunction = createEntryPointFunction; | 
| bsw/jbe@1309 | 6474 | 
| bsw/jbe@1309 | 6475     api.textRange = { | 
| bsw/jbe@1309 | 6476         isBlockNode: isBlockNode, | 
| bsw/jbe@1309 | 6477         isCollapsedWhitespaceNode: isCollapsedWhitespaceNode, | 
| bsw/jbe@1309 | 6478 | 
| bsw/jbe@1309 | 6479         createPosition: createEntryPointFunction( | 
| bsw/jbe@1309 | 6480             function(session, node, offset) { | 
| bsw/jbe@1309 | 6481                 return session.getPosition(node, offset); | 
| bsw/jbe@1309 | 6482             } | 
| bsw/jbe@1309 | 6483         ) | 
| bsw/jbe@1309 | 6484     }; | 
| bsw/jbe@1309 | 6485 }); | 
| bsw/jbe@1309 | 6486 | 
| bsw/jbe@1309 | 6487 /** | 
| bsw/jbe@1309 | 6488  * Detect browser support for specific features | 
| bsw/jbe@1309 | 6489  */ | 
| bsw/jbe@1309 | 6490 wysihtml.browser = (function() { | 
| bsw/jbe@1309 | 6491   var userAgent   = navigator.userAgent, | 
| bsw/jbe@1309 | 6492       testElement = document.createElement("div"), | 
| bsw/jbe@1309 | 6493       // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect | 
| bsw/jbe@1309 | 6494       // We need to be extra careful about Microsoft as it shows increasing tendency of tainting its userAgent strings with false feathers | 
| bsw/jbe@1309 | 6495       isGecko     = userAgent.indexOf("Gecko")        !== -1 && userAgent.indexOf("KHTML") === -1 && !isIE(), | 
| bsw/jbe@1309 | 6496       isWebKit    = userAgent.indexOf("AppleWebKit/") !== -1 && !isIE(), | 
| bsw/jbe@1309 | 6497       isChrome    = userAgent.indexOf("Chrome/")      !== -1 && !isIE(), | 
| bsw/jbe@1309 | 6498       isOpera     = userAgent.indexOf("Opera/")       !== -1 && !isIE(); | 
| bsw/jbe@1309 | 6499 | 
| bsw/jbe@1309 | 6500   function iosVersion(userAgent) { | 
| bsw/jbe@1309 | 6501     return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [undefined, 0])[1]; | 
| bsw/jbe@1309 | 6502   } | 
| bsw/jbe@1309 | 6503 | 
| bsw/jbe@1309 | 6504   function androidVersion(userAgent) { | 
| bsw/jbe@1309 | 6505     return +(userAgent.match(/android (\d+)/) || [undefined, 0])[1]; | 
| bsw/jbe@1309 | 6506   } | 
| bsw/jbe@1309 | 6507 | 
| bsw/jbe@1309 | 6508   function isIE(version, equation) { | 
| bsw/jbe@1309 | 6509     var rv = -1, | 
| bsw/jbe@1309 | 6510         re; | 
| bsw/jbe@1309 | 6511 | 
| bsw/jbe@1309 | 6512     if (navigator.appName == 'Microsoft Internet Explorer') { | 
| bsw/jbe@1309 | 6513       re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); | 
| bsw/jbe@1309 | 6514     } else if (navigator.appName == 'Netscape') { | 
| bsw/jbe@1309 | 6515       if (navigator.userAgent.indexOf("Trident") > -1) { | 
| bsw/jbe@1309 | 6516         re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})"); | 
| bsw/jbe@1309 | 6517       } else if ((/Edge\/(\d+)./i).test(navigator.userAgent)) { | 
| bsw/jbe@1309 | 6518         re = /Edge\/(\d+)./i; | 
| bsw/jbe@1309 | 6519       } | 
| bsw/jbe@1309 | 6520     } | 
| bsw/jbe@1309 | 6521 | 
| bsw/jbe@1309 | 6522     if (re && re.exec(navigator.userAgent) != null) { | 
| bsw/jbe@1309 | 6523       rv = parseFloat(RegExp.$1); | 
| bsw/jbe@1309 | 6524     } | 
| bsw/jbe@1309 | 6525 | 
| bsw/jbe@1309 | 6526     if (rv === -1) { return false; } | 
| bsw/jbe@1309 | 6527     if (!version) { return true; } | 
| bsw/jbe@1309 | 6528     if (!equation) { return version === rv; } | 
| bsw/jbe@1309 | 6529     if (equation === "<") { return version < rv; } | 
| bsw/jbe@1309 | 6530     if (equation === ">") { return version > rv; } | 
| bsw/jbe@1309 | 6531     if (equation === "<=") { return version <= rv; } | 
| bsw/jbe@1309 | 6532     if (equation === ">=") { return version >= rv; } | 
| bsw/jbe@1309 | 6533   } | 
| bsw/jbe@1309 | 6534 | 
| bsw/jbe@1309 | 6535   return { | 
| bsw/jbe@1309 | 6536     // Static variable needed, publicly accessible, to be able override it in unit tests | 
| bsw/jbe@1309 | 6537     USER_AGENT: userAgent, | 
| bsw/jbe@1309 | 6538 | 
| bsw/jbe@1309 | 6539     /** | 
| bsw/jbe@1309 | 6540      * Exclude browsers that are not capable of displaying and handling | 
| bsw/jbe@1309 | 6541      * contentEditable as desired: | 
| bsw/jbe@1309 | 6542      *    - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable | 
| bsw/jbe@1309 | 6543      *    - IE < 8 create invalid markup and crash randomly from time to time | 
| bsw/jbe@1309 | 6544      * | 
| bsw/jbe@1309 | 6545      * @return {Boolean} | 
| bsw/jbe@1309 | 6546      */ | 
| bsw/jbe@1309 | 6547     supported: function() { | 
| bsw/jbe@1309 | 6548       var userAgent                   = this.USER_AGENT.toLowerCase(), | 
| bsw/jbe@1309 | 6549           // Essential for making html elements editable | 
| bsw/jbe@1309 | 6550           hasContentEditableSupport   = "contentEditable" in testElement, | 
| bsw/jbe@1309 | 6551           // Following methods are needed in order to interact with the contentEditable area | 
| bsw/jbe@1309 | 6552           hasEditingApiSupport        = document.execCommand && document.queryCommandSupported && document.queryCommandState, | 
| bsw/jbe@1309 | 6553           // document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+ | 
| bsw/jbe@1309 | 6554           hasQuerySelectorSupport     = document.querySelector && document.querySelectorAll, | 
| bsw/jbe@1309 | 6555           // contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05) | 
| bsw/jbe@1309 | 6556           isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || (this.isAndroid() && androidVersion(userAgent) < 4) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1; | 
| bsw/jbe@1309 | 6557       return hasContentEditableSupport | 
| bsw/jbe@1309 | 6558         && hasEditingApiSupport | 
| bsw/jbe@1309 | 6559         && hasQuerySelectorSupport | 
| bsw/jbe@1309 | 6560         && !isIncompatibleMobileBrowser; | 
| bsw/jbe@1309 | 6561     }, | 
| bsw/jbe@1309 | 6562 | 
| bsw/jbe@1309 | 6563     isTouchDevice: function() { | 
| bsw/jbe@1309 | 6564       return this.supportsEvent("touchmove"); | 
| bsw/jbe@1309 | 6565     }, | 
| bsw/jbe@1309 | 6566 | 
| bsw/jbe@1309 | 6567     isIos: function() { | 
| bsw/jbe@1309 | 6568       return (/ipad|iphone|ipod/i).test(this.USER_AGENT); | 
| bsw/jbe@1309 | 6569     }, | 
| bsw/jbe@1309 | 6570 | 
| bsw/jbe@1309 | 6571     isAndroid: function() { | 
| bsw/jbe@1309 | 6572       return this.USER_AGENT.indexOf("Android") !== -1; | 
| bsw/jbe@1309 | 6573     }, | 
| bsw/jbe@1309 | 6574 | 
| bsw/jbe@1309 | 6575     /** | 
| bsw/jbe@1309 | 6576      * Whether the browser supports sandboxed iframes | 
| bsw/jbe@1309 | 6577      * Currently only IE 6+ offers such feature <iframe security="restricted"> | 
| bsw/jbe@1309 | 6578      * | 
| bsw/jbe@1309 | 6579      * http://msdn.microsoft.com/en-us/library/ms534622(v=vs.85).aspx | 
| bsw/jbe@1309 | 6580      * http://blogs.msdn.com/b/ie/archive/2008/01/18/using-frames-more-securely.aspx | 
| bsw/jbe@1309 | 6581      * | 
| bsw/jbe@1309 | 6582      * HTML5 sandboxed iframes are still buggy and their DOM is not reachable from the outside (except when using postMessage) | 
| bsw/jbe@1309 | 6583      */ | 
| bsw/jbe@1309 | 6584     supportsSandboxedIframes: function() { | 
| bsw/jbe@1309 | 6585       return isIE(); | 
| bsw/jbe@1309 | 6586     }, | 
| bsw/jbe@1309 | 6587 | 
| bsw/jbe@1309 | 6588     /** | 
| bsw/jbe@1309 | 6589      * IE6+7 throw a mixed content warning when the src of an iframe | 
| bsw/jbe@1309 | 6590      * is empty/unset or about:blank | 
| bsw/jbe@1309 | 6591      * window.querySelector is implemented as of IE8 | 
| bsw/jbe@1309 | 6592      */ | 
| bsw/jbe@1309 | 6593     throwsMixedContentWarningWhenIframeSrcIsEmpty: function() { | 
| bsw/jbe@1309 | 6594       return !("querySelector" in document); | 
| bsw/jbe@1309 | 6595     }, | 
| bsw/jbe@1309 | 6596 | 
| bsw/jbe@1309 | 6597     /** | 
| bsw/jbe@1309 | 6598      * Whether the caret is correctly displayed in contentEditable elements | 
| bsw/jbe@1309 | 6599      * Firefox sometimes shows a huge caret in the beginning after focusing | 
| bsw/jbe@1309 | 6600      */ | 
| bsw/jbe@1309 | 6601     displaysCaretInEmptyContentEditableCorrectly: function() { | 
| bsw/jbe@1309 | 6602       return isIE(12, ">"); | 
| bsw/jbe@1309 | 6603     }, | 
| bsw/jbe@1309 | 6604 | 
| bsw/jbe@1309 | 6605     /** | 
| bsw/jbe@1309 | 6606      * Opera and IE are the only browsers who offer the css value | 
| bsw/jbe@1309 | 6607      * in the original unit, thx to the currentStyle object | 
| bsw/jbe@1309 | 6608      * All other browsers provide the computed style in px via window.getComputedStyle | 
| bsw/jbe@1309 | 6609      */ | 
| bsw/jbe@1309 | 6610     hasCurrentStyleProperty: function() { | 
| bsw/jbe@1309 | 6611       return "currentStyle" in testElement; | 
| bsw/jbe@1309 | 6612     }, | 
| bsw/jbe@1309 | 6613 | 
| bsw/jbe@1309 | 6614     /** | 
| bsw/jbe@1309 | 6615      * Whether the browser inserts a <br> when pressing enter in a contentEditable element | 
| bsw/jbe@1309 | 6616      */ | 
| bsw/jbe@1309 | 6617     insertsLineBreaksOnReturn: function() { | 
| bsw/jbe@1309 | 6618       return isGecko; | 
| bsw/jbe@1309 | 6619     }, | 
| bsw/jbe@1309 | 6620 | 
| bsw/jbe@1309 | 6621     supportsPlaceholderAttributeOn: function(element) { | 
| bsw/jbe@1309 | 6622       return "placeholder" in element; | 
| bsw/jbe@1309 | 6623     }, | 
| bsw/jbe@1309 | 6624 | 
| bsw/jbe@1309 | 6625     supportsEvent: function(eventName) { | 
| bsw/jbe@1309 | 6626       return "on" + eventName in testElement || (function() { | 
| bsw/jbe@1309 | 6627         testElement.setAttribute("on" + eventName, "return;"); | 
| bsw/jbe@1309 | 6628         return typeof(testElement["on" + eventName]) === "function"; | 
| bsw/jbe@1309 | 6629       })(); | 
| bsw/jbe@1309 | 6630     }, | 
| bsw/jbe@1309 | 6631 | 
| bsw/jbe@1309 | 6632     /** | 
| bsw/jbe@1309 | 6633      * Opera doesn't correctly fire focus/blur events when clicking in- and outside of iframe | 
| bsw/jbe@1309 | 6634      */ | 
| bsw/jbe@1309 | 6635     supportsEventsInIframeCorrectly: function() { | 
| bsw/jbe@1309 | 6636       return !isOpera; | 
| bsw/jbe@1309 | 6637     }, | 
| bsw/jbe@1309 | 6638 | 
| bsw/jbe@1309 | 6639     /** | 
| bsw/jbe@1309 | 6640      * Everything below IE9 doesn't know how to treat HTML5 tags | 
| bsw/jbe@1309 | 6641      * | 
| bsw/jbe@1309 | 6642      * @param {Object} context The document object on which to check HTML5 support | 
| bsw/jbe@1309 | 6643      * | 
| bsw/jbe@1309 | 6644      * @example | 
| bsw/jbe@1309 | 6645      *    wysihtml.browser.supportsHTML5Tags(document); | 
| bsw/jbe@1309 | 6646      */ | 
| bsw/jbe@1309 | 6647     supportsHTML5Tags: function(context) { | 
| bsw/jbe@1309 | 6648       var element = context.createElement("div"), | 
| bsw/jbe@1309 | 6649           html5   = "<article>foo</article>"; | 
| bsw/jbe@1309 | 6650       element.innerHTML = html5; | 
| bsw/jbe@1309 | 6651       return element.innerHTML.toLowerCase() === html5; | 
| bsw/jbe@1309 | 6652     }, | 
| bsw/jbe@1309 | 6653 | 
| bsw/jbe@1309 | 6654     /** | 
| bsw/jbe@1309 | 6655      * Checks whether a document supports a certain queryCommand | 
| bsw/jbe@1309 | 6656      * In particular, Opera needs a reference to a document that has a contentEditable in it's dom tree | 
| bsw/jbe@1309 | 6657      * in oder to report correct results | 
| bsw/jbe@1309 | 6658      * | 
| bsw/jbe@1309 | 6659      * @param {Object} doc Document object on which to check for a query command | 
| bsw/jbe@1309 | 6660      * @param {String} command The query command to check for | 
| bsw/jbe@1309 | 6661      * @return {Boolean} | 
| bsw/jbe@1309 | 6662      * | 
| bsw/jbe@1309 | 6663      * @example | 
| bsw/jbe@1309 | 6664      *    wysihtml.browser.supportsCommand(document, "bold"); | 
| bsw/jbe@1309 | 6665      */ | 
| bsw/jbe@1309 | 6666     supportsCommand: (function() { | 
| bsw/jbe@1309 | 6667       // Following commands are supported but contain bugs in some browsers | 
| bsw/jbe@1309 | 6668       // TODO: investigate if some of these bugs can be tested without altering selection on page, instead of targeting browsers and versions directly | 
| bsw/jbe@1309 | 6669       var buggyCommands = { | 
| bsw/jbe@1309 | 6670         // formatBlock fails with some tags (eg. <blockquote>) | 
| bsw/jbe@1309 | 6671         "formatBlock":          isIE(10, "<="), | 
| bsw/jbe@1309 | 6672          // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets | 
| bsw/jbe@1309 | 6673          // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>) | 
| bsw/jbe@1309 | 6674          // IE and Opera act a bit different here as they convert the entire content of the current block element into a list | 
| bsw/jbe@1309 | 6675         "insertUnorderedList":  isIE(), | 
| bsw/jbe@1309 | 6676         "insertOrderedList":    isIE() | 
| bsw/jbe@1309 | 6677       }; | 
| bsw/jbe@1309 | 6678 | 
| bsw/jbe@1309 | 6679       // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands | 
| bsw/jbe@1309 | 6680       var supported = { | 
| bsw/jbe@1309 | 6681         "insertHTML": isGecko | 
| bsw/jbe@1309 | 6682       }; | 
| bsw/jbe@1309 | 6683 | 
| bsw/jbe@1309 | 6684       return function(doc, command) { | 
| bsw/jbe@1309 | 6685         var isBuggy = buggyCommands[command]; | 
| bsw/jbe@1309 | 6686         if (!isBuggy) { | 
| bsw/jbe@1309 | 6687           // Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled | 
| bsw/jbe@1309 | 6688           try { | 
| bsw/jbe@1309 | 6689             return doc.queryCommandSupported(command); | 
| bsw/jbe@1309 | 6690           } catch(e1) {} | 
| bsw/jbe@1309 | 6691 | 
| bsw/jbe@1309 | 6692           try { | 
| bsw/jbe@1309 | 6693             return doc.queryCommandEnabled(command); | 
| bsw/jbe@1309 | 6694           } catch(e2) { | 
| bsw/jbe@1309 | 6695             return !!supported[command]; | 
| bsw/jbe@1309 | 6696           } | 
| bsw/jbe@1309 | 6697         } | 
| bsw/jbe@1309 | 6698         return false; | 
| bsw/jbe@1309 | 6699       }; | 
| bsw/jbe@1309 | 6700     })(), | 
| bsw/jbe@1309 | 6701 | 
| bsw/jbe@1309 | 6702     /** | 
| bsw/jbe@1309 | 6703      * IE: URLs starting with: | 
| bsw/jbe@1309 | 6704      *    www., http://, https://, ftp://, gopher://, mailto:, new:, snews:, telnet:, wasis:, file://, | 
| bsw/jbe@1309 | 6705      *    nntp://, newsrc:, ldap://, ldaps://, outlook:, mic:// and url: | 
| bsw/jbe@1309 | 6706      * will automatically be auto-linked when either the user inserts them via copy&paste or presses the | 
| bsw/jbe@1309 | 6707      * space bar when the caret is directly after such an url. | 
| bsw/jbe@1309 | 6708      * This behavior cannot easily be avoided in IE < 9 since the logic is hardcoded in the mshtml.dll | 
| bsw/jbe@1309 | 6709      * (related blog post on msdn | 
| bsw/jbe@1309 | 6710      * http://blogs.msdn.com/b/ieinternals/archive/2009/09/17/prevent-automatic-hyperlinking-in-contenteditable-html.aspx). | 
| bsw/jbe@1309 | 6711      */ | 
| bsw/jbe@1309 | 6712     doesAutoLinkingInContentEditable: function() { | 
| bsw/jbe@1309 | 6713       return isIE(); | 
| bsw/jbe@1309 | 6714     }, | 
| bsw/jbe@1309 | 6715 | 
| bsw/jbe@1309 | 6716     /** | 
| bsw/jbe@1309 | 6717      * As stated above, IE auto links urls typed into contentEditable elements | 
| bsw/jbe@1309 | 6718      * Since IE9 it's possible to prevent this behavior | 
| bsw/jbe@1309 | 6719      */ | 
| bsw/jbe@1309 | 6720     canDisableAutoLinking: function() { | 
| bsw/jbe@1309 | 6721       return this.supportsCommand(document, "AutoUrlDetect"); | 
| bsw/jbe@1309 | 6722     }, | 
| bsw/jbe@1309 | 6723 | 
| bsw/jbe@1309 | 6724     /** | 
| bsw/jbe@1309 | 6725      * IE leaves an empty paragraph in the contentEditable element after clearing it | 
| bsw/jbe@1309 | 6726      * Chrome/Safari sometimes an empty <div> | 
| bsw/jbe@1309 | 6727      */ | 
| bsw/jbe@1309 | 6728     clearsContentEditableCorrectly: function() { | 
| bsw/jbe@1309 | 6729       return isGecko || isOpera || isWebKit; | 
| bsw/jbe@1309 | 6730     }, | 
| bsw/jbe@1309 | 6731 | 
| bsw/jbe@1309 | 6732     /** | 
| bsw/jbe@1309 | 6733      * IE gives wrong results for getAttribute | 
| bsw/jbe@1309 | 6734      */ | 
| bsw/jbe@1309 | 6735     supportsGetAttributeCorrectly: function() { | 
| bsw/jbe@1309 | 6736       var td = document.createElement("td"); | 
| bsw/jbe@1309 | 6737       return td.getAttribute("rowspan") != "1"; | 
| bsw/jbe@1309 | 6738     }, | 
| bsw/jbe@1309 | 6739 | 
| bsw/jbe@1309 | 6740     /** | 
| bsw/jbe@1309 | 6741      * When clicking on images in IE, Opera and Firefox, they are selected, which makes it easy to interact with them. | 
| bsw/jbe@1309 | 6742      * Chrome and Safari both don't support this | 
| bsw/jbe@1309 | 6743      */ | 
| bsw/jbe@1309 | 6744     canSelectImagesInContentEditable: function() { | 
| bsw/jbe@1309 | 6745       return isGecko || isIE() || isOpera; | 
| bsw/jbe@1309 | 6746     }, | 
| bsw/jbe@1309 | 6747 | 
| bsw/jbe@1309 | 6748     /** | 
| bsw/jbe@1309 | 6749      * All browsers except Safari and Chrome automatically scroll the range/caret position into view | 
| bsw/jbe@1309 | 6750      */ | 
| bsw/jbe@1309 | 6751     autoScrollsToCaret: function() { | 
| bsw/jbe@1309 | 6752       return !isWebKit; | 
| bsw/jbe@1309 | 6753     }, | 
| bsw/jbe@1309 | 6754 | 
| bsw/jbe@1309 | 6755     /** | 
| bsw/jbe@1309 | 6756      * Check whether the browser automatically closes tags that don't need to be opened | 
| bsw/jbe@1309 | 6757      */ | 
| bsw/jbe@1309 | 6758     autoClosesUnclosedTags: function() { | 
| bsw/jbe@1309 | 6759       var clonedTestElement = testElement.cloneNode(false), | 
| bsw/jbe@1309 | 6760           returnValue, | 
| bsw/jbe@1309 | 6761           innerHTML; | 
| bsw/jbe@1309 | 6762 | 
| bsw/jbe@1309 | 6763       clonedTestElement.innerHTML = "<p><div></div>"; | 
| bsw/jbe@1309 | 6764       innerHTML                   = clonedTestElement.innerHTML.toLowerCase(); | 
| bsw/jbe@1309 | 6765       returnValue                 = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>"; | 
| bsw/jbe@1309 | 6766 | 
| bsw/jbe@1309 | 6767       // Cache result by overwriting current function | 
| bsw/jbe@1309 | 6768       this.autoClosesUnclosedTags = function() { return returnValue; }; | 
| bsw/jbe@1309 | 6769 | 
| bsw/jbe@1309 | 6770       return returnValue; | 
| bsw/jbe@1309 | 6771     }, | 
| bsw/jbe@1309 | 6772 | 
| bsw/jbe@1309 | 6773     /** | 
| bsw/jbe@1309 | 6774      * Whether the browser supports the native document.getElementsByClassName which returns live NodeLists | 
| bsw/jbe@1309 | 6775      */ | 
| bsw/jbe@1309 | 6776     supportsNativeGetElementsByClassName: function() { | 
| bsw/jbe@1309 | 6777       return String(document.getElementsByClassName).indexOf("[native code]") !== -1; | 
| bsw/jbe@1309 | 6778     }, | 
| bsw/jbe@1309 | 6779 | 
| bsw/jbe@1309 | 6780     /** | 
| bsw/jbe@1309 | 6781      * As of now (19.04.2011) only supported by Firefox 4 and Chrome | 
| bsw/jbe@1309 | 6782      * See https://developer.mozilla.org/en/DOM/Selection/modify | 
| bsw/jbe@1309 | 6783      */ | 
| bsw/jbe@1309 | 6784     supportsSelectionModify: function() { | 
| bsw/jbe@1309 | 6785       return "getSelection" in window && "modify" in window.getSelection(); | 
| bsw/jbe@1309 | 6786     }, | 
| bsw/jbe@1309 | 6787 | 
| bsw/jbe@1309 | 6788     /** | 
| bsw/jbe@1309 | 6789      * Opera needs a white space after a <br> in order to position the caret correctly | 
| bsw/jbe@1309 | 6790      */ | 
| bsw/jbe@1309 | 6791     needsSpaceAfterLineBreak: function() { | 
| bsw/jbe@1309 | 6792       return isOpera; | 
| bsw/jbe@1309 | 6793     }, | 
| bsw/jbe@1309 | 6794 | 
| bsw/jbe@1309 | 6795     /** | 
| bsw/jbe@1309 | 6796      * Whether the browser supports the speech api on the given element | 
| bsw/jbe@1309 | 6797      * See http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/ | 
| bsw/jbe@1309 | 6798      * | 
| bsw/jbe@1309 | 6799      * @example | 
| bsw/jbe@1309 | 6800      *    var input = document.createElement("input"); | 
| bsw/jbe@1309 | 6801      *    if (wysihtml.browser.supportsSpeechApiOn(input)) { | 
| bsw/jbe@1309 | 6802      *      // ... | 
| bsw/jbe@1309 | 6803      *    } | 
| bsw/jbe@1309 | 6804      */ | 
| bsw/jbe@1309 | 6805     supportsSpeechApiOn: function(input) { | 
| bsw/jbe@1309 | 6806       var chromeVersion = userAgent.match(/Chrome\/(\d+)/) || [undefined, 0]; | 
| bsw/jbe@1309 | 6807       return chromeVersion[1] >= 11 && ("onwebkitspeechchange" in input || "speech" in input); | 
| bsw/jbe@1309 | 6808     }, | 
| bsw/jbe@1309 | 6809 | 
| bsw/jbe@1309 | 6810     /** | 
| bsw/jbe@1309 | 6811      * IE9 crashes when setting a getter via Object.defineProperty on XMLHttpRequest or XDomainRequest | 
| bsw/jbe@1309 | 6812      * See https://connect.microsoft.com/ie/feedback/details/650112 | 
| bsw/jbe@1309 | 6813      * or try the POC http://tifftiff.de/ie9_crash/ | 
| bsw/jbe@1309 | 6814      */ | 
| bsw/jbe@1309 | 6815     crashesWhenDefineProperty: function(property) { | 
| bsw/jbe@1309 | 6816       return isIE(9) && (property === "XMLHttpRequest" || property === "XDomainRequest"); | 
| bsw/jbe@1309 | 6817     }, | 
| bsw/jbe@1309 | 6818 | 
| bsw/jbe@1309 | 6819     /** | 
| bsw/jbe@1309 | 6820      * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element | 
| bsw/jbe@1309 | 6821      */ | 
| bsw/jbe@1309 | 6822     doesAsyncFocus: function() { | 
| bsw/jbe@1309 | 6823       return isIE(12, ">"); | 
| bsw/jbe@1309 | 6824     }, | 
| bsw/jbe@1309 | 6825 | 
| bsw/jbe@1309 | 6826     /** | 
| bsw/jbe@1309 | 6827      * In IE it's impssible for the user and for the selection library to set the caret after an <img> when it's the lastChild in the document | 
| bsw/jbe@1309 | 6828      */ | 
| bsw/jbe@1309 | 6829     hasProblemsSettingCaretAfterImg: function() { | 
| bsw/jbe@1309 | 6830       return isIE(); | 
| bsw/jbe@1309 | 6831     }, | 
| bsw/jbe@1309 | 6832 | 
| bsw/jbe@1309 | 6833     /* In IE when deleting with caret at the begining of LI, List get broken into half instead of merging the LI with previous */ | 
| bsw/jbe@1309 | 6834     hasLiDeletingProblem: function() { | 
| bsw/jbe@1309 | 6835       return isIE(); | 
| bsw/jbe@1309 | 6836     }, | 
| bsw/jbe@1309 | 6837 | 
| bsw/jbe@1309 | 6838     hasUndoInContextMenu: function() { | 
| bsw/jbe@1309 | 6839       return isGecko || isChrome || isOpera; | 
| bsw/jbe@1309 | 6840     }, | 
| bsw/jbe@1309 | 6841 | 
| bsw/jbe@1309 | 6842     /** | 
| bsw/jbe@1309 | 6843      * Opera sometimes doesn't insert the node at the right position when range.insertNode(someNode) | 
| bsw/jbe@1309 | 6844      * is used (regardless if rangy or native) | 
| bsw/jbe@1309 | 6845      * This especially happens when the caret is positioned right after a <br> because then | 
| bsw/jbe@1309 | 6846      * insertNode() will insert the node right before the <br> | 
| bsw/jbe@1309 | 6847      */ | 
| bsw/jbe@1309 | 6848     hasInsertNodeIssue: function() { | 
| bsw/jbe@1309 | 6849       return isOpera; | 
| bsw/jbe@1309 | 6850     }, | 
| bsw/jbe@1309 | 6851 | 
| bsw/jbe@1309 | 6852     /** | 
| bsw/jbe@1309 | 6853      * IE 8+9 don't fire the focus event of the <body> when the iframe gets focused (even though the caret gets set into the <body>) | 
| bsw/jbe@1309 | 6854      */ | 
| bsw/jbe@1309 | 6855     hasIframeFocusIssue: function() { | 
| bsw/jbe@1309 | 6856       return isIE(); | 
| bsw/jbe@1309 | 6857     }, | 
| bsw/jbe@1309 | 6858 | 
| bsw/jbe@1309 | 6859     /** | 
| bsw/jbe@1309 | 6860      * Chrome + Safari create invalid nested markup after paste | 
| bsw/jbe@1309 | 6861      * | 
| bsw/jbe@1309 | 6862      *  <p> | 
| bsw/jbe@1309 | 6863      *    foo | 
| bsw/jbe@1309 | 6864      *    <p>bar</p> <!-- BOO! --> | 
| bsw/jbe@1309 | 6865      *  </p> | 
| bsw/jbe@1309 | 6866      */ | 
| bsw/jbe@1309 | 6867     createsNestedInvalidMarkupAfterPaste: function() { | 
| bsw/jbe@1309 | 6868       return isWebKit; | 
| bsw/jbe@1309 | 6869     }, | 
| bsw/jbe@1309 | 6870 | 
| bsw/jbe@1309 | 6871     // In all webkit browsers there are some places where caret can not be placed at the end of blocks and directly before block level element | 
| bsw/jbe@1309 | 6872     //   when startContainer is element. | 
| bsw/jbe@1309 | 6873     hasCaretBlockElementIssue: function() { | 
| bsw/jbe@1309 | 6874       return isWebKit; | 
| bsw/jbe@1309 | 6875     }, | 
| bsw/jbe@1309 | 6876 | 
| bsw/jbe@1309 | 6877     supportsMutationEvents: function() { | 
| bsw/jbe@1309 | 6878       return ("MutationEvent" in window); | 
| bsw/jbe@1309 | 6879     }, | 
| bsw/jbe@1309 | 6880 | 
| bsw/jbe@1309 | 6881     /** | 
| bsw/jbe@1309 | 6882       IE (at least up to 11) does not support clipboardData on event. | 
| bsw/jbe@1309 | 6883       It is on window but cannot return text/html | 
| bsw/jbe@1309 | 6884       Should actually check for clipboardData on paste event, but cannot in firefox | 
| bsw/jbe@1309 | 6885     */ | 
| bsw/jbe@1309 | 6886     supportsModernPaste: function () { | 
| bsw/jbe@1309 | 6887       return !isIE(); | 
| bsw/jbe@1309 | 6888     }, | 
| bsw/jbe@1309 | 6889 | 
| bsw/jbe@1309 | 6890     // Unifies the property names of element.style by returning the suitable property name for current browser | 
| bsw/jbe@1309 | 6891     // Input property key must be the standard | 
| bsw/jbe@1309 | 6892     fixStyleKey: function(key) { | 
| bsw/jbe@1309 | 6893       if (key === "cssFloat") { | 
| bsw/jbe@1309 | 6894         return ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat"; | 
| bsw/jbe@1309 | 6895       } | 
| bsw/jbe@1309 | 6896       return key; | 
| bsw/jbe@1309 | 6897     }, | 
| bsw/jbe@1309 | 6898 | 
| bsw/jbe@1309 | 6899     usesControlRanges: function() { | 
| bsw/jbe@1309 | 6900       return document.body && "createControlRange" in document.body; | 
| bsw/jbe@1309 | 6901     }, | 
| bsw/jbe@1309 | 6902 | 
| bsw/jbe@1309 | 6903     // Webkit browsers have an issue that when caret is at the end of link it is moved outside of link while inserting new characters, | 
| bsw/jbe@1309 | 6904     // so all inserted content will be after link. Selection before inserion is reported to be in link though. | 
| bsw/jbe@1309 | 6905     // This makes changing link texts from problematic to impossible (if link is just 1 characer long) for the user. | 
| bsw/jbe@1309 | 6906     // TODO: needs to be tested better than just browser as it some day might get fixed | 
| bsw/jbe@1309 | 6907     hasCaretAtLinkEndInsertionProblems: function() { | 
| bsw/jbe@1309 | 6908       return isWebKit; | 
| bsw/jbe@1309 | 6909     } | 
| bsw/jbe@1309 | 6910   }; | 
| bsw/jbe@1309 | 6911 })(); | 
| bsw/jbe@1309 | 6912 | 
| bsw/jbe@1309 | 6913 wysihtml.lang.array = function(arr) { | 
| bsw/jbe@1309 | 6914   return { | 
| bsw/jbe@1309 | 6915     /** | 
| bsw/jbe@1309 | 6916      * Check whether a given object exists in an array | 
| bsw/jbe@1309 | 6917      * | 
| bsw/jbe@1309 | 6918      * @example | 
| bsw/jbe@1309 | 6919      *    wysihtml.lang.array([1, 2]).contains(1); | 
| bsw/jbe@1309 | 6920      *    // => true | 
| bsw/jbe@1309 | 6921      * | 
| bsw/jbe@1309 | 6922      * Can be used to match array with array. If intersection is found true is returned | 
| bsw/jbe@1309 | 6923      */ | 
| bsw/jbe@1309 | 6924     contains: function(needle) { | 
| bsw/jbe@1309 | 6925       if (Array.isArray(needle)) { | 
| bsw/jbe@1309 | 6926         for (var i = needle.length; i--;) { | 
| bsw/jbe@1309 | 6927           if (wysihtml.lang.array(arr).indexOf(needle[i]) !== -1) { | 
| bsw/jbe@1309 | 6928             return true; | 
| bsw/jbe@1309 | 6929           } | 
| bsw/jbe@1309 | 6930         } | 
| bsw/jbe@1309 | 6931         return false; | 
| bsw/jbe@1309 | 6932       } else { | 
| bsw/jbe@1309 | 6933         return wysihtml.lang.array(arr).indexOf(needle) !== -1; | 
| bsw/jbe@1309 | 6934       } | 
| bsw/jbe@1309 | 6935     }, | 
| bsw/jbe@1309 | 6936 | 
| bsw/jbe@1309 | 6937     /** | 
| bsw/jbe@1309 | 6938      * Check whether a given object exists in an array and return index | 
| bsw/jbe@1309 | 6939      * If no elelemt found returns -1 | 
| bsw/jbe@1309 | 6940      * | 
| bsw/jbe@1309 | 6941      * @example | 
| bsw/jbe@1309 | 6942      *    wysihtml.lang.array([1, 2]).indexOf(2); | 
| bsw/jbe@1309 | 6943      *    // => 1 | 
| bsw/jbe@1309 | 6944      */ | 
| bsw/jbe@1309 | 6945     indexOf: function(needle) { | 
| bsw/jbe@1309 | 6946         if (arr.indexOf) { | 
| bsw/jbe@1309 | 6947           return arr.indexOf(needle); | 
| bsw/jbe@1309 | 6948         } else { | 
| bsw/jbe@1309 | 6949           for (var i=0, length=arr.length; i<length; i++) { | 
| bsw/jbe@1309 | 6950             if (arr[i] === needle) { return i; } | 
| bsw/jbe@1309 | 6951           } | 
| bsw/jbe@1309 | 6952           return -1; | 
| bsw/jbe@1309 | 6953         } | 
| bsw/jbe@1309 | 6954     }, | 
| bsw/jbe@1309 | 6955 | 
| bsw/jbe@1309 | 6956     /** | 
| bsw/jbe@1309 | 6957      * Substract one array from another | 
| bsw/jbe@1309 | 6958      * | 
| bsw/jbe@1309 | 6959      * @example | 
| bsw/jbe@1309 | 6960      *    wysihtml.lang.array([1, 2, 3, 4]).without([3, 4]); | 
| bsw/jbe@1309 | 6961      *    // => [1, 2] | 
| bsw/jbe@1309 | 6962      */ | 
| bsw/jbe@1309 | 6963     without: function(arrayToSubstract) { | 
| bsw/jbe@1309 | 6964       arrayToSubstract = wysihtml.lang.array(arrayToSubstract); | 
| bsw/jbe@1309 | 6965       var newArr  = [], | 
| bsw/jbe@1309 | 6966           i       = 0, | 
| bsw/jbe@1309 | 6967           length  = arr.length; | 
| bsw/jbe@1309 | 6968       for (; i<length; i++) { | 
| bsw/jbe@1309 | 6969         if (!arrayToSubstract.contains(arr[i])) { | 
| bsw/jbe@1309 | 6970           newArr.push(arr[i]); | 
| bsw/jbe@1309 | 6971         } | 
| bsw/jbe@1309 | 6972       } | 
| bsw/jbe@1309 | 6973       return newArr; | 
| bsw/jbe@1309 | 6974     }, | 
| bsw/jbe@1309 | 6975 | 
| bsw/jbe@1309 | 6976     /** | 
| bsw/jbe@1309 | 6977      * Return a clean native array | 
| bsw/jbe@1309 | 6978      * | 
| bsw/jbe@1309 | 6979      * Following will convert a Live NodeList to a proper Array | 
| bsw/jbe@1309 | 6980      * @example | 
| bsw/jbe@1309 | 6981      *    var childNodes = wysihtml.lang.array(document.body.childNodes).get(); | 
| bsw/jbe@1309 | 6982      */ | 
| bsw/jbe@1309 | 6983     get: function() { | 
| bsw/jbe@1309 | 6984       var i        = 0, | 
| bsw/jbe@1309 | 6985           length   = arr.length, | 
| bsw/jbe@1309 | 6986           newArray = []; | 
| bsw/jbe@1309 | 6987       for (; i<length; i++) { | 
| bsw/jbe@1309 | 6988         newArray.push(arr[i]); | 
| bsw/jbe@1309 | 6989       } | 
| bsw/jbe@1309 | 6990       return newArray; | 
| bsw/jbe@1309 | 6991     }, | 
| bsw/jbe@1309 | 6992 | 
| bsw/jbe@1309 | 6993     /** | 
| bsw/jbe@1309 | 6994      * Creates a new array with the results of calling a provided function on every element in this array. | 
| bsw/jbe@1309 | 6995      * optionally this can be provided as second argument | 
| bsw/jbe@1309 | 6996      * | 
| bsw/jbe@1309 | 6997      * @example | 
| bsw/jbe@1309 | 6998      *    var childNodes = wysihtml.lang.array([1,2,3,4]).map(function (value, index, array) { | 
| bsw/jbe@1309 | 6999             return value * 2; | 
| bsw/jbe@1309 | 7000      *    }); | 
| bsw/jbe@1309 | 7001      *    // => [2,4,6,8] | 
| bsw/jbe@1309 | 7002      */ | 
| bsw/jbe@1309 | 7003     map: function(callback, thisArg) { | 
| bsw/jbe@1309 | 7004       if (Array.prototype.map) { | 
| bsw/jbe@1309 | 7005         return arr.map(callback, thisArg); | 
| bsw/jbe@1309 | 7006       } else { | 
| bsw/jbe@1309 | 7007         var len = arr.length >>> 0, | 
| bsw/jbe@1309 | 7008             A = new Array(len), | 
| bsw/jbe@1309 | 7009             i = 0; | 
| bsw/jbe@1309 | 7010         for (; i < len; i++) { | 
| bsw/jbe@1309 | 7011            A[i] = callback.call(thisArg, arr[i], i, arr); | 
| bsw/jbe@1309 | 7012         } | 
| bsw/jbe@1309 | 7013         return A; | 
| bsw/jbe@1309 | 7014       } | 
| bsw/jbe@1309 | 7015     }, | 
| bsw/jbe@1309 | 7016 | 
| bsw/jbe@1309 | 7017     /* ReturnS new array without duplicate entries | 
| bsw/jbe@1309 | 7018      * | 
| bsw/jbe@1309 | 7019      * @example | 
| bsw/jbe@1309 | 7020      *    var uniq = wysihtml.lang.array([1,2,3,2,1,4]).unique(); | 
| bsw/jbe@1309 | 7021      *    // => [1,2,3,4] | 
| bsw/jbe@1309 | 7022      */ | 
| bsw/jbe@1309 | 7023     unique: function() { | 
| bsw/jbe@1309 | 7024       var vals = [], | 
| bsw/jbe@1309 | 7025           max = arr.length, | 
| bsw/jbe@1309 | 7026           idx = 0; | 
| bsw/jbe@1309 | 7027 | 
| bsw/jbe@1309 | 7028       while (idx < max) { | 
| bsw/jbe@1309 | 7029         if (!wysihtml.lang.array(vals).contains(arr[idx])) { | 
| bsw/jbe@1309 | 7030           vals.push(arr[idx]); | 
| bsw/jbe@1309 | 7031         } | 
| bsw/jbe@1309 | 7032         idx++; | 
| bsw/jbe@1309 | 7033       } | 
| bsw/jbe@1309 | 7034       return vals; | 
| bsw/jbe@1309 | 7035     } | 
| bsw/jbe@1309 | 7036 | 
| bsw/jbe@1309 | 7037   }; | 
| bsw/jbe@1309 | 7038 }; | 
| bsw/jbe@1309 | 7039 | 
| bsw/jbe@1309 | 7040 wysihtml.lang.Dispatcher = Base.extend( | 
| bsw/jbe@1309 | 7041   /** @scope wysihtml.lang.Dialog.prototype */ { | 
| bsw/jbe@1309 | 7042   on: function(eventName, handler) { | 
| bsw/jbe@1309 | 7043     this.events = this.events || {}; | 
| bsw/jbe@1309 | 7044     this.events[eventName] = this.events[eventName] || []; | 
| bsw/jbe@1309 | 7045     this.events[eventName].push(handler); | 
| bsw/jbe@1309 | 7046     return this; | 
| bsw/jbe@1309 | 7047   }, | 
| bsw/jbe@1309 | 7048 | 
| bsw/jbe@1309 | 7049   off: function(eventName, handler) { | 
| bsw/jbe@1309 | 7050     this.events = this.events || {}; | 
| bsw/jbe@1309 | 7051     var i = 0, | 
| bsw/jbe@1309 | 7052         handlers, | 
| bsw/jbe@1309 | 7053         newHandlers; | 
| bsw/jbe@1309 | 7054     if (eventName) { | 
| bsw/jbe@1309 | 7055       handlers    = this.events[eventName] || [], | 
| bsw/jbe@1309 | 7056       newHandlers = []; | 
| bsw/jbe@1309 | 7057       for (; i<handlers.length; i++) { | 
| bsw/jbe@1309 | 7058         if (handlers[i] !== handler && handler) { | 
| bsw/jbe@1309 | 7059           newHandlers.push(handlers[i]); | 
| bsw/jbe@1309 | 7060         } | 
| bsw/jbe@1309 | 7061       } | 
| bsw/jbe@1309 | 7062       this.events[eventName] = newHandlers; | 
| bsw/jbe@1309 | 7063     } else { | 
| bsw/jbe@1309 | 7064       // Clean up all events | 
| bsw/jbe@1309 | 7065       this.events = {}; | 
| bsw/jbe@1309 | 7066     } | 
| bsw/jbe@1309 | 7067     return this; | 
| bsw/jbe@1309 | 7068   }, | 
| bsw/jbe@1309 | 7069 | 
| bsw/jbe@1309 | 7070   fire: function(eventName, payload) { | 
| bsw/jbe@1309 | 7071     this.events = this.events || {}; | 
| bsw/jbe@1309 | 7072     var handlers = this.events[eventName] || [], | 
| bsw/jbe@1309 | 7073         i        = 0; | 
| bsw/jbe@1309 | 7074     for (; i<handlers.length; i++) { | 
| bsw/jbe@1309 | 7075       handlers[i].call(this, payload); | 
| bsw/jbe@1309 | 7076     } | 
| bsw/jbe@1309 | 7077     return this; | 
| bsw/jbe@1309 | 7078   }, | 
| bsw/jbe@1309 | 7079 | 
| bsw/jbe@1309 | 7080   // deprecated, use .on() | 
| bsw/jbe@1309 | 7081   observe: function() { | 
| bsw/jbe@1309 | 7082     return this.on.apply(this, arguments); | 
| bsw/jbe@1309 | 7083   }, | 
| bsw/jbe@1309 | 7084 | 
| bsw/jbe@1309 | 7085   // deprecated, use .off() | 
| bsw/jbe@1309 | 7086   stopObserving: function() { | 
| bsw/jbe@1309 | 7087     return this.off.apply(this, arguments); | 
| bsw/jbe@1309 | 7088   } | 
| bsw/jbe@1309 | 7089 }); | 
| bsw/jbe@1309 | 7090 | 
| bsw/jbe@1309 | 7091 wysihtml.lang.object = function(obj) { | 
| bsw/jbe@1309 | 7092   return { | 
| bsw/jbe@1309 | 7093     /** | 
| bsw/jbe@1309 | 7094      * @example | 
| bsw/jbe@1309 | 7095      *    wysihtml.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get(); | 
| bsw/jbe@1309 | 7096      *    // => { foo: 1, bar: 2, baz: 3 } | 
| bsw/jbe@1309 | 7097      */ | 
| bsw/jbe@1309 | 7098     merge: function(otherObj, deep) { | 
| bsw/jbe@1309 | 7099       for (var i in otherObj) { | 
| bsw/jbe@1309 | 7100         if (deep && wysihtml.lang.object(otherObj[i]).isPlainObject() && (typeof obj[i] === "undefined" || wysihtml.lang.object(obj[i]).isPlainObject())) { | 
| bsw/jbe@1309 | 7101           if (typeof obj[i] === "undefined") { | 
| bsw/jbe@1309 | 7102             obj[i] = wysihtml.lang.object(otherObj[i]).clone(true); | 
| bsw/jbe@1309 | 7103           } else { | 
| bsw/jbe@1309 | 7104             wysihtml.lang.object(obj[i]).merge(wysihtml.lang.object(otherObj[i]).clone(true)); | 
| bsw/jbe@1309 | 7105           } | 
| bsw/jbe@1309 | 7106         } else { | 
| bsw/jbe@1309 | 7107           obj[i] = wysihtml.lang.object(otherObj[i]).isPlainObject() ? wysihtml.lang.object(otherObj[i]).clone(true) : otherObj[i]; | 
| bsw/jbe@1309 | 7108         } | 
| bsw/jbe@1309 | 7109       } | 
| bsw/jbe@1309 | 7110       return this; | 
| bsw/jbe@1309 | 7111     }, | 
| bsw/jbe@1309 | 7112 | 
| bsw/jbe@1309 | 7113     difference: function (otherObj) { | 
| bsw/jbe@1309 | 7114       var diffObj = {}; | 
| bsw/jbe@1309 | 7115 | 
| bsw/jbe@1309 | 7116       // Get old values not in comparing object | 
| bsw/jbe@1309 | 7117       for (var i in obj) { | 
| bsw/jbe@1309 | 7118         if (obj.hasOwnProperty(i)) { | 
| bsw/jbe@1309 | 7119           if (!otherObj.hasOwnProperty(i)) { | 
| bsw/jbe@1309 | 7120             diffObj[i] = obj[i]; | 
| bsw/jbe@1309 | 7121           } | 
| bsw/jbe@1309 | 7122         } | 
| bsw/jbe@1309 | 7123       } | 
| bsw/jbe@1309 | 7124 | 
| bsw/jbe@1309 | 7125       // Get new and different values in comparing object | 
| bsw/jbe@1309 | 7126       for (var o in otherObj) { | 
| bsw/jbe@1309 | 7127         if (otherObj.hasOwnProperty(o)) { | 
| bsw/jbe@1309 | 7128           if (!obj.hasOwnProperty(o) || obj[o] !== otherObj[o]) { | 
| bsw/jbe@1309 | 7129             diffObj[0] = obj[0]; | 
| bsw/jbe@1309 | 7130           } | 
| bsw/jbe@1309 | 7131         } | 
| bsw/jbe@1309 | 7132       } | 
| bsw/jbe@1309 | 7133       return diffObj; | 
| bsw/jbe@1309 | 7134     }, | 
| bsw/jbe@1309 | 7135 | 
| bsw/jbe@1309 | 7136     get: function() { | 
| bsw/jbe@1309 | 7137       return obj; | 
| bsw/jbe@1309 | 7138     }, | 
| bsw/jbe@1309 | 7139 | 
| bsw/jbe@1309 | 7140     /** | 
| bsw/jbe@1309 | 7141      * @example | 
| bsw/jbe@1309 | 7142      *    wysihtml.lang.object({ foo: 1 }).clone(); | 
| bsw/jbe@1309 | 7143      *    // => { foo: 1 } | 
| bsw/jbe@1309 | 7144      * | 
| bsw/jbe@1309 | 7145      *    v0.4.14 adds options for deep clone : wysihtml.lang.object({ foo: 1 }).clone(true); | 
| bsw/jbe@1309 | 7146      */ | 
| bsw/jbe@1309 | 7147     clone: function(deep) { | 
| bsw/jbe@1309 | 7148       var newObj = {}, | 
| bsw/jbe@1309 | 7149           i; | 
| bsw/jbe@1309 | 7150 | 
| bsw/jbe@1309 | 7151       if (obj === null || !wysihtml.lang.object(obj).isPlainObject()) { | 
| bsw/jbe@1309 | 7152         return obj; | 
| bsw/jbe@1309 | 7153       } | 
| bsw/jbe@1309 | 7154 | 
| bsw/jbe@1309 | 7155       for (i in obj) { | 
| bsw/jbe@1309 | 7156         if(obj.hasOwnProperty(i)) { | 
| bsw/jbe@1309 | 7157           if (deep) { | 
| bsw/jbe@1309 | 7158             newObj[i] = wysihtml.lang.object(obj[i]).clone(deep); | 
| bsw/jbe@1309 | 7159           } else { | 
| bsw/jbe@1309 | 7160             newObj[i] = obj[i]; | 
| bsw/jbe@1309 | 7161           } | 
| bsw/jbe@1309 | 7162         } | 
| bsw/jbe@1309 | 7163       } | 
| bsw/jbe@1309 | 7164       return newObj; | 
| bsw/jbe@1309 | 7165     }, | 
| bsw/jbe@1309 | 7166 | 
| bsw/jbe@1309 | 7167     /** | 
| bsw/jbe@1309 | 7168      * @example | 
| bsw/jbe@1309 | 7169      *    wysihtml.lang.object([]).isArray(); | 
| bsw/jbe@1309 | 7170      *    // => true | 
| bsw/jbe@1309 | 7171      */ | 
| bsw/jbe@1309 | 7172     isArray: function() { | 
| bsw/jbe@1309 | 7173       return Object.prototype.toString.call(obj) === "[object Array]"; | 
| bsw/jbe@1309 | 7174     }, | 
| bsw/jbe@1309 | 7175 | 
| bsw/jbe@1309 | 7176     /** | 
| bsw/jbe@1309 | 7177      * @example | 
| bsw/jbe@1309 | 7178      *    wysihtml.lang.object(function() {}).isFunction(); | 
| bsw/jbe@1309 | 7179      *    // => true | 
| bsw/jbe@1309 | 7180      */ | 
| bsw/jbe@1309 | 7181     isFunction: function() { | 
| bsw/jbe@1309 | 7182       return Object.prototype.toString.call(obj) === '[object Function]'; | 
| bsw/jbe@1309 | 7183     }, | 
| bsw/jbe@1309 | 7184 | 
| bsw/jbe@1309 | 7185     isPlainObject: function () { | 
| bsw/jbe@1309 | 7186       return obj && Object.prototype.toString.call(obj) === '[object Object]' && !(("Node" in window) ? obj instanceof Node : obj instanceof Element || obj instanceof Text); | 
| bsw/jbe@1309 | 7187     }, | 
| bsw/jbe@1309 | 7188 | 
| bsw/jbe@1309 | 7189     /** | 
| bsw/jbe@1309 | 7190      * @example | 
| bsw/jbe@1309 | 7191      *    wysihtml.lang.object({}).isEmpty(); | 
| bsw/jbe@1309 | 7192      *    // => true | 
| bsw/jbe@1309 | 7193      */ | 
| bsw/jbe@1309 | 7194     isEmpty: function() { | 
| bsw/jbe@1309 | 7195       for (var i in obj) { | 
| bsw/jbe@1309 | 7196         if (obj.hasOwnProperty(i)) { | 
| bsw/jbe@1309 | 7197           return false; | 
| bsw/jbe@1309 | 7198         } | 
| bsw/jbe@1309 | 7199       } | 
| bsw/jbe@1309 | 7200       return true; | 
| bsw/jbe@1309 | 7201     } | 
| bsw/jbe@1309 | 7202   }; | 
| bsw/jbe@1309 | 7203 }; | 
| bsw/jbe@1309 | 7204 | 
| bsw/jbe@1309 | 7205 (function() { | 
| bsw/jbe@1309 | 7206   var WHITE_SPACE_START = /^\s+/, | 
| bsw/jbe@1309 | 7207       WHITE_SPACE_END   = /\s+$/, | 
| bsw/jbe@1309 | 7208       ENTITY_REG_EXP    = /[&<>\t"]/g, | 
| bsw/jbe@1309 | 7209       ENTITY_MAP = { | 
| bsw/jbe@1309 | 7210         '&': '&', | 
| bsw/jbe@1309 | 7211         '<': '<', | 
| bsw/jbe@1309 | 7212         '>': '>', | 
| bsw/jbe@1309 | 7213         '"': """, | 
| bsw/jbe@1309 | 7214         '\t':"  " | 
| bsw/jbe@1309 | 7215       }; | 
| bsw/jbe@1309 | 7216   wysihtml.lang.string = function(str) { | 
| bsw/jbe@1309 | 7217     str = String(str); | 
| bsw/jbe@1309 | 7218     return { | 
| bsw/jbe@1309 | 7219       /** | 
| bsw/jbe@1309 | 7220        * @example | 
| bsw/jbe@1309 | 7221        *    wysihtml.lang.string("   foo   ").trim(); | 
| bsw/jbe@1309 | 7222        *    // => "foo" | 
| bsw/jbe@1309 | 7223        */ | 
| bsw/jbe@1309 | 7224       trim: function() { | 
| bsw/jbe@1309 | 7225         return str.replace(WHITE_SPACE_START, "").replace(WHITE_SPACE_END, ""); | 
| bsw/jbe@1309 | 7226       }, | 
| bsw/jbe@1309 | 7227 | 
| bsw/jbe@1309 | 7228       /** | 
| bsw/jbe@1309 | 7229        * @example | 
| bsw/jbe@1309 | 7230        *    wysihtml.lang.string("Hello #{name}").interpolate({ name: "Christopher" }); | 
| bsw/jbe@1309 | 7231        *    // => "Hello Christopher" | 
| bsw/jbe@1309 | 7232        */ | 
| bsw/jbe@1309 | 7233       interpolate: function(vars) { | 
| bsw/jbe@1309 | 7234         for (var i in vars) { | 
| bsw/jbe@1309 | 7235           str = this.replace("#{" + i + "}").by(vars[i]); | 
| bsw/jbe@1309 | 7236         } | 
| bsw/jbe@1309 | 7237         return str; | 
| bsw/jbe@1309 | 7238       }, | 
| bsw/jbe@1309 | 7239 | 
| bsw/jbe@1309 | 7240       /** | 
| bsw/jbe@1309 | 7241        * @example | 
| bsw/jbe@1309 | 7242        *    wysihtml.lang.string("Hello Tom").replace("Tom").with("Hans"); | 
| bsw/jbe@1309 | 7243        *    // => "Hello Hans" | 
| bsw/jbe@1309 | 7244        */ | 
| bsw/jbe@1309 | 7245       replace: function(search) { | 
| bsw/jbe@1309 | 7246         return { | 
| bsw/jbe@1309 | 7247           by: function(replace) { | 
| bsw/jbe@1309 | 7248             return str.split(search).join(replace); | 
| bsw/jbe@1309 | 7249           } | 
| bsw/jbe@1309 | 7250         }; | 
| bsw/jbe@1309 | 7251       }, | 
| bsw/jbe@1309 | 7252 | 
| bsw/jbe@1309 | 7253       /** | 
| bsw/jbe@1309 | 7254        * @example | 
| bsw/jbe@1309 | 7255        *    wysihtml.lang.string("hello<br>").escapeHTML(); | 
| bsw/jbe@1309 | 7256        *    // => "hello<br>" | 
| bsw/jbe@1309 | 7257        */ | 
| bsw/jbe@1309 | 7258       escapeHTML: function(linebreaks, convertSpaces) { | 
| bsw/jbe@1309 | 7259         var html = str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; }); | 
| bsw/jbe@1309 | 7260         if (linebreaks) { | 
| bsw/jbe@1309 | 7261           html = html.replace(/(?:\r\n|\r|\n)/g, '<br />'); | 
| bsw/jbe@1309 | 7262         } | 
| bsw/jbe@1309 | 7263         if (convertSpaces) { | 
| bsw/jbe@1309 | 7264           html = html.replace(/  /gi, "  "); | 
| bsw/jbe@1309 | 7265         } | 
| bsw/jbe@1309 | 7266         return html; | 
| bsw/jbe@1309 | 7267       } | 
| bsw/jbe@1309 | 7268     }; | 
| bsw/jbe@1309 | 7269   }; | 
| bsw/jbe@1309 | 7270 })(); | 
| bsw/jbe@1309 | 7271 | 
| bsw/jbe@1309 | 7272 /** | 
| bsw/jbe@1309 | 7273  * Find urls in descendant text nodes of an element and auto-links them | 
| bsw/jbe@1309 | 7274  * Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/ | 
| bsw/jbe@1309 | 7275  * | 
| bsw/jbe@1309 | 7276  * @param {Element} element Container element in which to search for urls | 
| bsw/jbe@1309 | 7277  * | 
| bsw/jbe@1309 | 7278  * @example | 
| bsw/jbe@1309 | 7279  *    <div id="text-container">Please click here: www.google.com</div> | 
| bsw/jbe@1309 | 7280  *    <script>wysihtml.dom.autoLink(document.getElementById("text-container"));</script> | 
| bsw/jbe@1309 | 7281  */ | 
| bsw/jbe@1309 | 7282 (function(wysihtml) { | 
| bsw/jbe@1309 | 7283   var /** | 
| bsw/jbe@1309 | 7284        * Don't auto-link urls that are contained in the following elements: | 
| bsw/jbe@1309 | 7285        */ | 
| bsw/jbe@1309 | 7286       IGNORE_URLS_IN        = wysihtml.lang.array(["CODE", "PRE", "A", "SCRIPT", "HEAD", "TITLE", "STYLE"]), | 
| bsw/jbe@1309 | 7287       /** | 
| bsw/jbe@1309 | 7288        * revision 1: | 
| bsw/jbe@1309 | 7289        *    /(\S+\.{1}[^\s\,\.\!]+)/g | 
| bsw/jbe@1309 | 7290        * | 
| bsw/jbe@1309 | 7291        * revision 2: | 
| bsw/jbe@1309 | 7292        *    /(\b(((https?|ftp):\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;\[\]]*[-A-Z0-9+&@#\/%=~_|])/gim | 
| bsw/jbe@1309 | 7293        * | 
| bsw/jbe@1309 | 7294        * put this in the beginning if you don't wan't to match within a word | 
| bsw/jbe@1309 | 7295        *    (^|[\>\(\{\[\s\>]) | 
| bsw/jbe@1309 | 7296        */ | 
| bsw/jbe@1309 | 7297       URL_REG_EXP           = /((https?:\/\/|www\.)[^\s<]{3,})/gi, | 
| bsw/jbe@1309 | 7298       TRAILING_CHAR_REG_EXP = /([^\w\/\-](,?))$/i, | 
| bsw/jbe@1309 | 7299       MAX_DISPLAY_LENGTH    = 100, | 
| bsw/jbe@1309 | 7300       BRACKETS              = { ")": "(", "]": "[", "}": "{" }; | 
| bsw/jbe@1309 | 7301 | 
| bsw/jbe@1309 | 7302   function autoLink(element, ignoreInClasses) { | 
| bsw/jbe@1309 | 7303     if (_hasParentThatShouldBeIgnored(element, ignoreInClasses)) { | 
| bsw/jbe@1309 | 7304       return element; | 
| bsw/jbe@1309 | 7305     } | 
| bsw/jbe@1309 | 7306 | 
| bsw/jbe@1309 | 7307     if (element === element.ownerDocument.documentElement) { | 
| bsw/jbe@1309 | 7308       element = element.ownerDocument.body; | 
| bsw/jbe@1309 | 7309     } | 
| bsw/jbe@1309 | 7310 | 
| bsw/jbe@1309 | 7311     return _parseNode(element, ignoreInClasses); | 
| bsw/jbe@1309 | 7312   } | 
| bsw/jbe@1309 | 7313 | 
| bsw/jbe@1309 | 7314   /** | 
| bsw/jbe@1309 | 7315    * This is basically a rebuild of | 
| bsw/jbe@1309 | 7316    * the rails auto_link_urls text helper | 
| bsw/jbe@1309 | 7317    */ | 
| bsw/jbe@1309 | 7318   function _convertUrlsToLinks(str) { | 
| bsw/jbe@1309 | 7319     return str.replace(URL_REG_EXP, function(match, url) { | 
| bsw/jbe@1309 | 7320       var punctuation = (url.match(TRAILING_CHAR_REG_EXP) || [])[1] || "", | 
| bsw/jbe@1309 | 7321           opening     = BRACKETS[punctuation]; | 
| bsw/jbe@1309 | 7322       url = url.replace(TRAILING_CHAR_REG_EXP, ""); | 
| bsw/jbe@1309 | 7323 | 
| bsw/jbe@1309 | 7324       if (url.split(opening).length > url.split(punctuation).length) { | 
| bsw/jbe@1309 | 7325         url = url + punctuation; | 
| bsw/jbe@1309 | 7326         punctuation = ""; | 
| bsw/jbe@1309 | 7327       } | 
| bsw/jbe@1309 | 7328       var realUrl    = url, | 
| bsw/jbe@1309 | 7329           displayUrl = url; | 
| bsw/jbe@1309 | 7330       if (url.length > MAX_DISPLAY_LENGTH) { | 
| bsw/jbe@1309 | 7331         displayUrl = displayUrl.substr(0, MAX_DISPLAY_LENGTH) + "..."; | 
| bsw/jbe@1309 | 7332       } | 
| bsw/jbe@1309 | 7333       // Add http prefix if necessary | 
| bsw/jbe@1309 | 7334       if (realUrl.substr(0, 4) === "www.") { | 
| bsw/jbe@1309 | 7335         realUrl = "http://" + realUrl; | 
| bsw/jbe@1309 | 7336       } | 
| bsw/jbe@1309 | 7337 | 
| bsw/jbe@1309 | 7338       return '<a href="' + realUrl + '">' + displayUrl + '</a>' + punctuation; | 
| bsw/jbe@1309 | 7339     }); | 
| bsw/jbe@1309 | 7340   } | 
| bsw/jbe@1309 | 7341 | 
| bsw/jbe@1309 | 7342   /** | 
| bsw/jbe@1309 | 7343    * Creates or (if already cached) returns a temp element | 
| bsw/jbe@1309 | 7344    * for the given document object | 
| bsw/jbe@1309 | 7345    */ | 
| bsw/jbe@1309 | 7346   function _getTempElement(context) { | 
| bsw/jbe@1309 | 7347     var tempElement = context._wysihtml_tempElement; | 
| bsw/jbe@1309 | 7348     if (!tempElement) { | 
| bsw/jbe@1309 | 7349       tempElement = context._wysihtml_tempElement = context.createElement("div"); | 
| bsw/jbe@1309 | 7350     } | 
| bsw/jbe@1309 | 7351     return tempElement; | 
| bsw/jbe@1309 | 7352   } | 
| bsw/jbe@1309 | 7353 | 
| bsw/jbe@1309 | 7354   /** | 
| bsw/jbe@1309 | 7355    * Replaces the original text nodes with the newly auto-linked dom tree | 
| bsw/jbe@1309 | 7356    */ | 
| bsw/jbe@1309 | 7357   function _wrapMatchesInNode(textNode) { | 
| bsw/jbe@1309 | 7358     var parentNode  = textNode.parentNode, | 
| bsw/jbe@1309 | 7359         nodeValue   = wysihtml.lang.string(textNode.data).escapeHTML(), | 
| bsw/jbe@1309 | 7360         tempElement = _getTempElement(parentNode.ownerDocument); | 
| bsw/jbe@1309 | 7361 | 
| bsw/jbe@1309 | 7362     // We need to insert an empty/temporary <span /> to fix IE quirks | 
| bsw/jbe@1309 | 7363     // Elsewise IE would strip white space in the beginning | 
| bsw/jbe@1309 | 7364     tempElement.innerHTML = "<span></span>" + _convertUrlsToLinks(nodeValue); | 
| bsw/jbe@1309 | 7365     tempElement.removeChild(tempElement.firstChild); | 
| bsw/jbe@1309 | 7366 | 
| bsw/jbe@1309 | 7367     while (tempElement.firstChild) { | 
| bsw/jbe@1309 | 7368       // inserts tempElement.firstChild before textNode | 
| bsw/jbe@1309 | 7369       parentNode.insertBefore(tempElement.firstChild, textNode); | 
| bsw/jbe@1309 | 7370     } | 
| bsw/jbe@1309 | 7371     parentNode.removeChild(textNode); | 
| bsw/jbe@1309 | 7372   } | 
| bsw/jbe@1309 | 7373 | 
| bsw/jbe@1309 | 7374   function _hasParentThatShouldBeIgnored(node, ignoreInClasses) { | 
| bsw/jbe@1309 | 7375     var nodeName; | 
| bsw/jbe@1309 | 7376     while (node.parentNode) { | 
| bsw/jbe@1309 | 7377       node = node.parentNode; | 
| bsw/jbe@1309 | 7378       nodeName = node.nodeName; | 
| bsw/jbe@1309 | 7379       if (node.className && wysihtml.lang.array(node.className.split(' ')).contains(ignoreInClasses)) { | 
| bsw/jbe@1309 | 7380         return true; | 
| bsw/jbe@1309 | 7381       } | 
| bsw/jbe@1309 | 7382       if (IGNORE_URLS_IN.contains(nodeName)) { | 
| bsw/jbe@1309 | 7383         return true; | 
| bsw/jbe@1309 | 7384       } else if (nodeName === "body") { | 
| bsw/jbe@1309 | 7385         return false; | 
| bsw/jbe@1309 | 7386       } | 
| bsw/jbe@1309 | 7387     } | 
| bsw/jbe@1309 | 7388     return false; | 
| bsw/jbe@1309 | 7389   } | 
| bsw/jbe@1309 | 7390 | 
| bsw/jbe@1309 | 7391   function _parseNode(element, ignoreInClasses) { | 
| bsw/jbe@1309 | 7392     if (IGNORE_URLS_IN.contains(element.nodeName)) { | 
| bsw/jbe@1309 | 7393       return; | 
| bsw/jbe@1309 | 7394     } | 
| bsw/jbe@1309 | 7395 | 
| bsw/jbe@1309 | 7396     if (element.className && wysihtml.lang.array(element.className.split(' ')).contains(ignoreInClasses)) { | 
| bsw/jbe@1309 | 7397       return; | 
| bsw/jbe@1309 | 7398     } | 
| bsw/jbe@1309 | 7399 | 
| bsw/jbe@1309 | 7400     if (element.nodeType === wysihtml.TEXT_NODE && element.data.match(URL_REG_EXP)) { | 
| bsw/jbe@1309 | 7401       _wrapMatchesInNode(element); | 
| bsw/jbe@1309 | 7402       return; | 
| bsw/jbe@1309 | 7403     } | 
| bsw/jbe@1309 | 7404 | 
| bsw/jbe@1309 | 7405     var childNodes        = wysihtml.lang.array(element.childNodes).get(), | 
| bsw/jbe@1309 | 7406         childNodesLength  = childNodes.length, | 
| bsw/jbe@1309 | 7407         i                 = 0; | 
| bsw/jbe@1309 | 7408 | 
| bsw/jbe@1309 | 7409     for (; i<childNodesLength; i++) { | 
| bsw/jbe@1309 | 7410       _parseNode(childNodes[i], ignoreInClasses); | 
| bsw/jbe@1309 | 7411     } | 
| bsw/jbe@1309 | 7412 | 
| bsw/jbe@1309 | 7413     return element; | 
| bsw/jbe@1309 | 7414   } | 
| bsw/jbe@1309 | 7415 | 
| bsw/jbe@1309 | 7416   wysihtml.dom.autoLink = autoLink; | 
| bsw/jbe@1309 | 7417 | 
| bsw/jbe@1309 | 7418   // Reveal url reg exp to the outside | 
| bsw/jbe@1309 | 7419   wysihtml.dom.autoLink.URL_REG_EXP = URL_REG_EXP; | 
| bsw/jbe@1309 | 7420 })(wysihtml); | 
| bsw/jbe@1309 | 7421 | 
| bsw/jbe@1309 | 7422 (function(wysihtml) { | 
| bsw/jbe@1309 | 7423   var api = wysihtml.dom; | 
| bsw/jbe@1309 | 7424 | 
| bsw/jbe@1309 | 7425   api.addClass = function(element, className) { | 
| bsw/jbe@1309 | 7426     var classList = element.classList; | 
| bsw/jbe@1309 | 7427     if (classList) { | 
| bsw/jbe@1309 | 7428       return classList.add(className); | 
| bsw/jbe@1309 | 7429     } | 
| bsw/jbe@1309 | 7430     if (api.hasClass(element, className)) { | 
| bsw/jbe@1309 | 7431       return; | 
| bsw/jbe@1309 | 7432     } | 
| bsw/jbe@1309 | 7433     element.className += " " + className; | 
| bsw/jbe@1309 | 7434   }; | 
| bsw/jbe@1309 | 7435 | 
| bsw/jbe@1309 | 7436   api.removeClass = function(element, className) { | 
| bsw/jbe@1309 | 7437     var classList = element.classList; | 
| bsw/jbe@1309 | 7438     if (classList) { | 
| bsw/jbe@1309 | 7439       return classList.remove(className); | 
| bsw/jbe@1309 | 7440     } | 
| bsw/jbe@1309 | 7441 | 
| bsw/jbe@1309 | 7442     element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " "); | 
| bsw/jbe@1309 | 7443   }; | 
| bsw/jbe@1309 | 7444 | 
| bsw/jbe@1309 | 7445   api.hasClass = function(element, className) { | 
| bsw/jbe@1309 | 7446     var classList = element.classList; | 
| bsw/jbe@1309 | 7447     if (classList) { | 
| bsw/jbe@1309 | 7448       return classList.contains(className); | 
| bsw/jbe@1309 | 7449     } | 
| bsw/jbe@1309 | 7450 | 
| bsw/jbe@1309 | 7451     var elementClassName = element.className; | 
| bsw/jbe@1309 | 7452     return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); | 
| bsw/jbe@1309 | 7453   }; | 
| bsw/jbe@1309 | 7454 })(wysihtml); | 
| bsw/jbe@1309 | 7455 | 
| bsw/jbe@1309 | 7456 wysihtml.dom.compareDocumentPosition = (function() { | 
| bsw/jbe@1309 | 7457   var documentElement = document.documentElement; | 
| bsw/jbe@1309 | 7458   if (documentElement.compareDocumentPosition) { | 
| bsw/jbe@1309 | 7459     return function(container, element) { | 
| bsw/jbe@1309 | 7460       return container.compareDocumentPosition(element); | 
| bsw/jbe@1309 | 7461     }; | 
| bsw/jbe@1309 | 7462   } else { | 
| bsw/jbe@1309 | 7463     return function( container, element ) { | 
| bsw/jbe@1309 | 7464       // implementation borrowed from https://github.com/tmpvar/jsdom/blob/681a8524b663281a0f58348c6129c8c184efc62c/lib/jsdom/level3/core.js // MIT license | 
| bsw/jbe@1309 | 7465       var thisOwner, otherOwner; | 
| bsw/jbe@1309 | 7466 | 
| bsw/jbe@1309 | 7467       if( container.nodeType === 9) // Node.DOCUMENT_NODE | 
| bsw/jbe@1309 | 7468         thisOwner = container; | 
| bsw/jbe@1309 | 7469       else | 
| bsw/jbe@1309 | 7470         thisOwner = container.ownerDocument; | 
| bsw/jbe@1309 | 7471 | 
| bsw/jbe@1309 | 7472       if( element.nodeType === 9) // Node.DOCUMENT_NODE | 
| bsw/jbe@1309 | 7473         otherOwner = element; | 
| bsw/jbe@1309 | 7474       else | 
| bsw/jbe@1309 | 7475         otherOwner = element.ownerDocument; | 
| bsw/jbe@1309 | 7476 | 
| bsw/jbe@1309 | 7477       if( container === element ) return 0; | 
| bsw/jbe@1309 | 7478       if( container === element.ownerDocument ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY; | 
| bsw/jbe@1309 | 7479       if( container.ownerDocument === element ) return 2 + 8;  //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS; | 
| bsw/jbe@1309 | 7480       if( thisOwner !== otherOwner ) return 1; // Node.DOCUMENT_POSITION_DISCONNECTED; | 
| bsw/jbe@1309 | 7481 | 
| bsw/jbe@1309 | 7482       // Text nodes for attributes does not have a _parentNode. So we need to find them as attribute child. | 
| bsw/jbe@1309 | 7483       if( container.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && container.childNodes && wysihtml.lang.array(container.childNodes).indexOf( element ) !== -1) | 
| bsw/jbe@1309 | 7484         return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY; | 
| bsw/jbe@1309 | 7485 | 
| bsw/jbe@1309 | 7486       if( element.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && element.childNodes && wysihtml.lang.array(element.childNodes).indexOf( container ) !== -1) | 
| bsw/jbe@1309 | 7487         return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS; | 
| bsw/jbe@1309 | 7488 | 
| bsw/jbe@1309 | 7489       var point = container; | 
| bsw/jbe@1309 | 7490       var parents = [ ]; | 
| bsw/jbe@1309 | 7491       var previous = null; | 
| bsw/jbe@1309 | 7492       while( point ) { | 
| bsw/jbe@1309 | 7493         if( point == element ) return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS; | 
| bsw/jbe@1309 | 7494         parents.push( point ); | 
| bsw/jbe@1309 | 7495         point = point.parentNode; | 
| bsw/jbe@1309 | 7496       } | 
| bsw/jbe@1309 | 7497       point = element; | 
| bsw/jbe@1309 | 7498       previous = null; | 
| bsw/jbe@1309 | 7499       while( point ) { | 
| bsw/jbe@1309 | 7500         if( point == container ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY; | 
| bsw/jbe@1309 | 7501         var location_index = wysihtml.lang.array(parents).indexOf( point ); | 
| bsw/jbe@1309 | 7502         if( location_index !== -1) { | 
| bsw/jbe@1309 | 7503          var smallest_common_ancestor = parents[ location_index ]; | 
| bsw/jbe@1309 | 7504          var this_index = wysihtml.lang.array(smallest_common_ancestor.childNodes).indexOf( parents[location_index - 1]);//smallest_common_ancestor.childNodes.toArray().indexOf( parents[location_index - 1] ); | 
| bsw/jbe@1309 | 7505          var other_index = wysihtml.lang.array(smallest_common_ancestor.childNodes).indexOf( previous ); //smallest_common_ancestor.childNodes.toArray().indexOf( previous ); | 
| bsw/jbe@1309 | 7506          if( this_index > other_index ) { | 
| bsw/jbe@1309 | 7507                return 2; //Node.DOCUMENT_POSITION_PRECEDING; | 
| bsw/jbe@1309 | 7508          } | 
| bsw/jbe@1309 | 7509          else { | 
| bsw/jbe@1309 | 7510            return 4; //Node.DOCUMENT_POSITION_FOLLOWING; | 
| bsw/jbe@1309 | 7511          } | 
| bsw/jbe@1309 | 7512         } | 
| bsw/jbe@1309 | 7513         previous = point; | 
| bsw/jbe@1309 | 7514         point = point.parentNode; | 
| bsw/jbe@1309 | 7515       } | 
| bsw/jbe@1309 | 7516       return 1; //Node.DOCUMENT_POSITION_DISCONNECTED; | 
| bsw/jbe@1309 | 7517     }; | 
| bsw/jbe@1309 | 7518   } | 
| bsw/jbe@1309 | 7519 })(); | 
| bsw/jbe@1309 | 7520 | 
| bsw/jbe@1309 | 7521 wysihtml.dom.contains = (function() { | 
| bsw/jbe@1309 | 7522   var documentElement = document.documentElement; | 
| bsw/jbe@1309 | 7523   if (documentElement.contains) { | 
| bsw/jbe@1309 | 7524     return function(container, element) { | 
| bsw/jbe@1309 | 7525       if (element.nodeType !== wysihtml.ELEMENT_NODE) { | 
| bsw/jbe@1309 | 7526         if (element.parentNode === container) { | 
| bsw/jbe@1309 | 7527           return true; | 
| bsw/jbe@1309 | 7528         } | 
| bsw/jbe@1309 | 7529         element = element.parentNode; | 
| bsw/jbe@1309 | 7530       } | 
| bsw/jbe@1309 | 7531       return container !== element && container.contains(element); | 
| bsw/jbe@1309 | 7532     }; | 
| bsw/jbe@1309 | 7533   } else if (documentElement.compareDocumentPosition) { | 
| bsw/jbe@1309 | 7534     return function(container, element) { | 
| bsw/jbe@1309 | 7535       // https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition | 
| bsw/jbe@1309 | 7536       return !!(container.compareDocumentPosition(element) & 16); | 
| bsw/jbe@1309 | 7537     }; | 
| bsw/jbe@1309 | 7538   } | 
| bsw/jbe@1309 | 7539 })(); | 
| bsw/jbe@1309 | 7540 | 
| bsw/jbe@1309 | 7541 (function(wysihtml) { | 
| bsw/jbe@1309 | 7542   var doc = document; | 
| bsw/jbe@1309 | 7543   wysihtml.dom.ContentEditableArea = Base.extend({ | 
| bsw/jbe@1309 | 7544       getContentEditable: function() { | 
| bsw/jbe@1309 | 7545         return this.element; | 
| bsw/jbe@1309 | 7546       }, | 
| bsw/jbe@1309 | 7547 | 
| bsw/jbe@1309 | 7548       getWindow: function() { | 
| bsw/jbe@1309 | 7549         return this.element.ownerDocument.defaultView || this.element.ownerDocument.parentWindow; | 
| bsw/jbe@1309 | 7550       }, | 
| bsw/jbe@1309 | 7551 | 
| bsw/jbe@1309 | 7552       getDocument: function() { | 
| bsw/jbe@1309 | 7553         return this.element.ownerDocument; | 
| bsw/jbe@1309 | 7554       }, | 
| bsw/jbe@1309 | 7555 | 
| bsw/jbe@1309 | 7556       constructor: function(readyCallback, config, contentEditable) { | 
| bsw/jbe@1309 | 7557         this.callback = readyCallback || wysihtml.EMPTY_FUNCTION; | 
| bsw/jbe@1309 | 7558         this.config   = wysihtml.lang.object({}).merge(config).get(); | 
| bsw/jbe@1309 | 7559         if (!this.config.className) { | 
| bsw/jbe@1309 | 7560           this.config.className = "wysihtml-sandbox"; | 
| bsw/jbe@1309 | 7561         } | 
| bsw/jbe@1309 | 7562         if (contentEditable) { | 
| bsw/jbe@1309 | 7563             this.element = this._bindElement(contentEditable); | 
| bsw/jbe@1309 | 7564         } else { | 
| bsw/jbe@1309 | 7565             this.element = this._createElement(); | 
| bsw/jbe@1309 | 7566         } | 
| bsw/jbe@1309 | 7567       }, | 
| bsw/jbe@1309 | 7568 | 
| bsw/jbe@1309 | 7569       destroy: function() { | 
| bsw/jbe@1309 | 7570 | 
| bsw/jbe@1309 | 7571       }, | 
| bsw/jbe@1309 | 7572 | 
| bsw/jbe@1309 | 7573       // creates a new contenteditable and initiates it | 
| bsw/jbe@1309 | 7574       _createElement: function() { | 
| bsw/jbe@1309 | 7575         var element = doc.createElement("div"); | 
| bsw/jbe@1309 | 7576         element.className = this.config.className; | 
| bsw/jbe@1309 | 7577         this._loadElement(element); | 
| bsw/jbe@1309 | 7578         return element; | 
| bsw/jbe@1309 | 7579       }, | 
| bsw/jbe@1309 | 7580 | 
| bsw/jbe@1309 | 7581       // initiates an allready existent contenteditable | 
| bsw/jbe@1309 | 7582       _bindElement: function(contentEditable) { | 
| bsw/jbe@1309 | 7583         contentEditable.className = contentEditable.className ? contentEditable.className + " wysihtml-sandbox" : "wysihtml-sandbox"; | 
| bsw/jbe@1309 | 7584         this._loadElement(contentEditable, true); | 
| bsw/jbe@1309 | 7585         return contentEditable; | 
| bsw/jbe@1309 | 7586       }, | 
| bsw/jbe@1309 | 7587 | 
| bsw/jbe@1309 | 7588       _loadElement: function(element, contentExists) { | 
| bsw/jbe@1309 | 7589         var that = this; | 
| bsw/jbe@1309 | 7590 | 
| bsw/jbe@1309 | 7591         if (!contentExists) { | 
| bsw/jbe@1309 | 7592             var innerHtml = this._getHtml(); | 
| bsw/jbe@1309 | 7593             element.innerHTML = innerHtml; | 
| bsw/jbe@1309 | 7594         } | 
| bsw/jbe@1309 | 7595 | 
| bsw/jbe@1309 | 7596         this.loaded = true; | 
| bsw/jbe@1309 | 7597         // Trigger the callback | 
| bsw/jbe@1309 | 7598         setTimeout(function() { that.callback(that); }, 0); | 
| bsw/jbe@1309 | 7599       }, | 
| bsw/jbe@1309 | 7600 | 
| bsw/jbe@1309 | 7601       _getHtml: function(templateVars) { | 
| bsw/jbe@1309 | 7602         return ''; | 
| bsw/jbe@1309 | 7603       } | 
| bsw/jbe@1309 | 7604 | 
| bsw/jbe@1309 | 7605   }); | 
| bsw/jbe@1309 | 7606 })(wysihtml); | 
| bsw/jbe@1309 | 7607 | 
| bsw/jbe@1309 | 7608 /** | 
| bsw/jbe@1309 | 7609  * Converts an HTML fragment/element into a unordered/ordered list | 
| bsw/jbe@1309 | 7610  * | 
| bsw/jbe@1309 | 7611  * @param {Element} element The element which should be turned into a list | 
| bsw/jbe@1309 | 7612  * @param {String} listType The list type in which to convert the tree (either "ul" or "ol") | 
| bsw/jbe@1309 | 7613  * @return {Element} The created list | 
| bsw/jbe@1309 | 7614  * | 
| bsw/jbe@1309 | 7615  * @example | 
| bsw/jbe@1309 | 7616  *    <!-- Assume the following dom: --> | 
| bsw/jbe@1309 | 7617  *    <span id="pseudo-list"> | 
| bsw/jbe@1309 | 7618  *      eminem<br> | 
| bsw/jbe@1309 | 7619  *      dr. dre | 
| bsw/jbe@1309 | 7620  *      <div>50 Cent</div> | 
| bsw/jbe@1309 | 7621  *    </span> | 
| bsw/jbe@1309 | 7622  * | 
| bsw/jbe@1309 | 7623  *    <script> | 
| bsw/jbe@1309 | 7624  *      wysihtml.dom.convertToList(document.getElementById("pseudo-list"), "ul"); | 
| bsw/jbe@1309 | 7625  *    </script> | 
| bsw/jbe@1309 | 7626  * | 
| bsw/jbe@1309 | 7627  *    <!-- Will result in: --> | 
| bsw/jbe@1309 | 7628  *    <ul> | 
| bsw/jbe@1309 | 7629  *      <li>eminem</li> | 
| bsw/jbe@1309 | 7630  *      <li>dr. dre</li> | 
| bsw/jbe@1309 | 7631  *      <li>50 Cent</li> | 
| bsw/jbe@1309 | 7632  *    </ul> | 
| bsw/jbe@1309 | 7633  */ | 
| bsw/jbe@1309 | 7634 wysihtml.dom.convertToList = (function() { | 
| bsw/jbe@1309 | 7635   function _createListItem(doc, list) { | 
| bsw/jbe@1309 | 7636     var listItem = doc.createElement("li"); | 
| bsw/jbe@1309 | 7637     list.appendChild(listItem); | 
| bsw/jbe@1309 | 7638     return listItem; | 
| bsw/jbe@1309 | 7639   } | 
| bsw/jbe@1309 | 7640 | 
| bsw/jbe@1309 | 7641   function _createList(doc, type) { | 
| bsw/jbe@1309 | 7642     return doc.createElement(type); | 
| bsw/jbe@1309 | 7643   } | 
| bsw/jbe@1309 | 7644 | 
| bsw/jbe@1309 | 7645   function convertToList(element, listType, uneditableClass) { | 
| bsw/jbe@1309 | 7646     if (element.nodeName === "UL" || element.nodeName === "OL" || element.nodeName === "MENU") { | 
| bsw/jbe@1309 | 7647       // Already a list | 
| bsw/jbe@1309 | 7648       return element; | 
| bsw/jbe@1309 | 7649     } | 
| bsw/jbe@1309 | 7650 | 
| bsw/jbe@1309 | 7651     var doc               = element.ownerDocument, | 
| bsw/jbe@1309 | 7652         list              = _createList(doc, listType), | 
| bsw/jbe@1309 | 7653         lineBreaks        = element.querySelectorAll("br"), | 
| bsw/jbe@1309 | 7654         lineBreaksLength  = lineBreaks.length, | 
| bsw/jbe@1309 | 7655         childNodes, | 
| bsw/jbe@1309 | 7656         childNodesLength, | 
| bsw/jbe@1309 | 7657         childNode, | 
| bsw/jbe@1309 | 7658         lineBreak, | 
| bsw/jbe@1309 | 7659         parentNode, | 
| bsw/jbe@1309 | 7660         isBlockElement, | 
| bsw/jbe@1309 | 7661         isLineBreak, | 
| bsw/jbe@1309 | 7662         currentListItem, | 
| bsw/jbe@1309 | 7663         i; | 
| bsw/jbe@1309 | 7664 | 
| bsw/jbe@1309 | 7665     // First find <br> at the end of inline elements and move them behind them | 
| bsw/jbe@1309 | 7666     for (i=0; i<lineBreaksLength; i++) { | 
| bsw/jbe@1309 | 7667       lineBreak = lineBreaks[i]; | 
| bsw/jbe@1309 | 7668       while ((parentNode = lineBreak.parentNode) && parentNode !== element && parentNode.lastChild === lineBreak) { | 
| bsw/jbe@1309 | 7669         if (wysihtml.dom.getStyle("display").from(parentNode) === "block") { | 
| bsw/jbe@1309 | 7670           parentNode.removeChild(lineBreak); | 
| bsw/jbe@1309 | 7671           break; | 
| bsw/jbe@1309 | 7672         } | 
| bsw/jbe@1309 | 7673         wysihtml.dom.insert(lineBreak).after(lineBreak.parentNode); | 
| bsw/jbe@1309 | 7674       } | 
| bsw/jbe@1309 | 7675     } | 
| bsw/jbe@1309 | 7676 | 
| bsw/jbe@1309 | 7677     childNodes        = wysihtml.lang.array(element.childNodes).get(); | 
| bsw/jbe@1309 | 7678     childNodesLength  = childNodes.length; | 
| bsw/jbe@1309 | 7679 | 
| bsw/jbe@1309 | 7680     for (i=0; i<childNodesLength; i++) { | 
| bsw/jbe@1309 | 7681       currentListItem   = currentListItem || _createListItem(doc, list); | 
| bsw/jbe@1309 | 7682       childNode         = childNodes[i]; | 
| bsw/jbe@1309 | 7683       isBlockElement    = wysihtml.dom.getStyle("display").from(childNode) === "block"; | 
| bsw/jbe@1309 | 7684       isLineBreak       = childNode.nodeName === "BR"; | 
| bsw/jbe@1309 | 7685 | 
| bsw/jbe@1309 | 7686       // consider uneditable as an inline element | 
| bsw/jbe@1309 | 7687       if (isBlockElement && (!uneditableClass || !wysihtml.dom.hasClass(childNode, uneditableClass))) { | 
| bsw/jbe@1309 | 7688         // Append blockElement to current <li> if empty, otherwise create a new one | 
| bsw/jbe@1309 | 7689         currentListItem = currentListItem.firstChild ? _createListItem(doc, list) : currentListItem; | 
| bsw/jbe@1309 | 7690         currentListItem.appendChild(childNode); | 
| bsw/jbe@1309 | 7691         currentListItem = null; | 
| bsw/jbe@1309 | 7692         continue; | 
| bsw/jbe@1309 | 7693       } | 
| bsw/jbe@1309 | 7694 | 
| bsw/jbe@1309 | 7695       if (isLineBreak) { | 
| bsw/jbe@1309 | 7696         // Only create a new list item in the next iteration when the current one has already content | 
| bsw/jbe@1309 | 7697         currentListItem = currentListItem.firstChild ? null : currentListItem; | 
| bsw/jbe@1309 | 7698         continue; | 
| bsw/jbe@1309 | 7699       } | 
| bsw/jbe@1309 | 7700 | 
| bsw/jbe@1309 | 7701       currentListItem.appendChild(childNode); | 
| bsw/jbe@1309 | 7702     } | 
| bsw/jbe@1309 | 7703 | 
| bsw/jbe@1309 | 7704     if (childNodes.length === 0) { | 
| bsw/jbe@1309 | 7705       _createListItem(doc, list); | 
| bsw/jbe@1309 | 7706     } | 
| bsw/jbe@1309 | 7707 | 
| bsw/jbe@1309 | 7708     element.parentNode.replaceChild(list, element); | 
| bsw/jbe@1309 | 7709     return list; | 
| bsw/jbe@1309 | 7710   } | 
| bsw/jbe@1309 | 7711 | 
| bsw/jbe@1309 | 7712   return convertToList; | 
| bsw/jbe@1309 | 7713 })(); | 
| bsw/jbe@1309 | 7714 | 
| bsw/jbe@1309 | 7715 /** | 
| bsw/jbe@1309 | 7716  * Copy a set of attributes from one element to another | 
| bsw/jbe@1309 | 7717  * | 
| bsw/jbe@1309 | 7718  * @param {Array} attributesToCopy List of attributes which should be copied | 
| bsw/jbe@1309 | 7719  * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to | 
| bsw/jbe@1309 | 7720  *    copy the attributes from., this again returns an object which provides a method named "to" which can be invoked | 
| bsw/jbe@1309 | 7721  *    with the element where to copy the attributes to (see example) | 
| bsw/jbe@1309 | 7722  * | 
| bsw/jbe@1309 | 7723  * @example | 
| bsw/jbe@1309 | 7724  *    var textarea    = document.querySelector("textarea"), | 
| bsw/jbe@1309 | 7725  *        div         = document.querySelector("div[contenteditable=true]"), | 
| bsw/jbe@1309 | 7726  *        anotherDiv  = document.querySelector("div.preview"); | 
| bsw/jbe@1309 | 7727  *    wysihtml.dom.copyAttributes(["spellcheck", "value", "placeholder"]).from(textarea).to(div).andTo(anotherDiv); | 
| bsw/jbe@1309 | 7728  * | 
| bsw/jbe@1309 | 7729  */ | 
| bsw/jbe@1309 | 7730 wysihtml.dom.copyAttributes = function(attributesToCopy) { | 
| bsw/jbe@1309 | 7731   return { | 
| bsw/jbe@1309 | 7732     from: function(elementToCopyFrom) { | 
| bsw/jbe@1309 | 7733       return { | 
| bsw/jbe@1309 | 7734         to: function pasteElementAttributesTo(elementToCopyTo) { | 
| bsw/jbe@1309 | 7735           var attribute, | 
| bsw/jbe@1309 | 7736               i         = 0, | 
| bsw/jbe@1309 | 7737               length    = attributesToCopy.length; | 
| bsw/jbe@1309 | 7738           for (; i<length; i++) { | 
| bsw/jbe@1309 | 7739             attribute = attributesToCopy[i]; | 
| bsw/jbe@1309 | 7740             if (typeof(elementToCopyFrom[attribute]) !== "undefined" && elementToCopyFrom[attribute] !== "") { | 
| bsw/jbe@1309 | 7741               elementToCopyTo[attribute] = elementToCopyFrom[attribute]; | 
| bsw/jbe@1309 | 7742             } | 
| bsw/jbe@1309 | 7743           } | 
| bsw/jbe@1309 | 7744           return { andTo: pasteElementAttributesTo }; | 
| bsw/jbe@1309 | 7745         } | 
| bsw/jbe@1309 | 7746       }; | 
| bsw/jbe@1309 | 7747     } | 
| bsw/jbe@1309 | 7748   }; | 
| bsw/jbe@1309 | 7749 }; | 
| bsw/jbe@1309 | 7750 | 
| bsw/jbe@1309 | 7751 /** | 
| bsw/jbe@1309 | 7752  * Copy a set of styles from one element to another | 
| bsw/jbe@1309 | 7753  * Please note that this only works properly across browsers when the element from which to copy the styles | 
| bsw/jbe@1309 | 7754  * is in the dom | 
| bsw/jbe@1309 | 7755  * | 
| bsw/jbe@1309 | 7756  * Interesting article on how to copy styles | 
| bsw/jbe@1309 | 7757  * | 
| bsw/jbe@1309 | 7758  * @param {Array} stylesToCopy List of styles which should be copied | 
| bsw/jbe@1309 | 7759  * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to | 
| bsw/jbe@1309 | 7760  *    copy the styles from., this again returns an object which provides a method named "to" which can be invoked | 
| bsw/jbe@1309 | 7761  *    with the element where to copy the styles to (see example) | 
| bsw/jbe@1309 | 7762  * | 
| bsw/jbe@1309 | 7763  * @example | 
| bsw/jbe@1309 | 7764  *    var textarea    = document.querySelector("textarea"), | 
| bsw/jbe@1309 | 7765  *        div         = document.querySelector("div[contenteditable=true]"), | 
| bsw/jbe@1309 | 7766  *        anotherDiv  = document.querySelector("div.preview"); | 
| bsw/jbe@1309 | 7767  *    wysihtml.dom.copyStyles(["overflow-y", "width", "height"]).from(textarea).to(div).andTo(anotherDiv); | 
| bsw/jbe@1309 | 7768  * | 
| bsw/jbe@1309 | 7769  */ | 
| bsw/jbe@1309 | 7770 (function(dom) { | 
| bsw/jbe@1309 | 7771 | 
| bsw/jbe@1309 | 7772   /** | 
| bsw/jbe@1309 | 7773    * Mozilla, WebKit and Opera recalculate the computed width when box-sizing: boder-box; is set | 
| bsw/jbe@1309 | 7774    * So if an element has "width: 200px; -moz-box-sizing: border-box; border: 1px;" then | 
| bsw/jbe@1309 | 7775    * its computed css width will be 198px | 
| bsw/jbe@1309 | 7776    * | 
| bsw/jbe@1309 | 7777    * See https://bugzilla.mozilla.org/show_bug.cgi?id=520992 | 
| bsw/jbe@1309 | 7778    */ | 
| bsw/jbe@1309 | 7779   var BOX_SIZING_PROPERTIES = ["-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing"]; | 
| bsw/jbe@1309 | 7780 | 
| bsw/jbe@1309 | 7781   var shouldIgnoreBoxSizingBorderBox = function(element) { | 
| bsw/jbe@1309 | 7782     if (hasBoxSizingBorderBox(element)) { | 
| bsw/jbe@1309 | 7783        return parseInt(dom.getStyle("width").from(element), 10) < element.offsetWidth; | 
| bsw/jbe@1309 | 7784     } | 
| bsw/jbe@1309 | 7785     return false; | 
| bsw/jbe@1309 | 7786   }; | 
| bsw/jbe@1309 | 7787 | 
| bsw/jbe@1309 | 7788   var hasBoxSizingBorderBox = function(element) { | 
| bsw/jbe@1309 | 7789     var i       = 0, | 
| bsw/jbe@1309 | 7790         length  = BOX_SIZING_PROPERTIES.length; | 
| bsw/jbe@1309 | 7791     for (; i<length; i++) { | 
| bsw/jbe@1309 | 7792       if (dom.getStyle(BOX_SIZING_PROPERTIES[i]).from(element) === "border-box") { | 
| bsw/jbe@1309 | 7793         return BOX_SIZING_PROPERTIES[i]; | 
| bsw/jbe@1309 | 7794       } | 
| bsw/jbe@1309 | 7795     } | 
| bsw/jbe@1309 | 7796   }; | 
| bsw/jbe@1309 | 7797 | 
| bsw/jbe@1309 | 7798   dom.copyStyles = function(stylesToCopy) { | 
| bsw/jbe@1309 | 7799     return { | 
| bsw/jbe@1309 | 7800       from: function(element) { | 
| bsw/jbe@1309 | 7801         if (shouldIgnoreBoxSizingBorderBox(element)) { | 
| bsw/jbe@1309 | 7802           stylesToCopy = wysihtml.lang.array(stylesToCopy).without(BOX_SIZING_PROPERTIES); | 
| bsw/jbe@1309 | 7803         } | 
| bsw/jbe@1309 | 7804 | 
| bsw/jbe@1309 | 7805         var cssText = "", | 
| bsw/jbe@1309 | 7806             length  = stylesToCopy.length, | 
| bsw/jbe@1309 | 7807             i       = 0, | 
| bsw/jbe@1309 | 7808             property; | 
| bsw/jbe@1309 | 7809         for (; i<length; i++) { | 
| bsw/jbe@1309 | 7810           property = stylesToCopy[i]; | 
| bsw/jbe@1309 | 7811           cssText += property + ":" + dom.getStyle(property).from(element) + ";"; | 
| bsw/jbe@1309 | 7812         } | 
| bsw/jbe@1309 | 7813 | 
| bsw/jbe@1309 | 7814         return { | 
| bsw/jbe@1309 | 7815           to: function pasteStylesTo(element) { | 
| bsw/jbe@1309 | 7816             dom.setStyles(cssText).on(element); | 
| bsw/jbe@1309 | 7817             return { andTo: pasteStylesTo }; | 
| bsw/jbe@1309 | 7818           } | 
| bsw/jbe@1309 | 7819         }; | 
| bsw/jbe@1309 | 7820       } | 
| bsw/jbe@1309 | 7821     }; | 
| bsw/jbe@1309 | 7822   }; | 
| bsw/jbe@1309 | 7823 })(wysihtml.dom); | 
| bsw/jbe@1309 | 7824 | 
| bsw/jbe@1309 | 7825 /** | 
| bsw/jbe@1309 | 7826  * Event Delegation | 
| bsw/jbe@1309 | 7827  * | 
| bsw/jbe@1309 | 7828  * @example | 
| bsw/jbe@1309 | 7829  *    wysihtml.dom.delegate(document.body, "a", "click", function() { | 
| bsw/jbe@1309 | 7830  *      // foo | 
| bsw/jbe@1309 | 7831  *    }); | 
| bsw/jbe@1309 | 7832  */ | 
| bsw/jbe@1309 | 7833 (function(wysihtml) { | 
| bsw/jbe@1309 | 7834   wysihtml.dom.delegate = function(container, selector, eventName, handler) { | 
| bsw/jbe@1309 | 7835     var callback = function(event) { | 
| bsw/jbe@1309 | 7836       var target = event.target, | 
| bsw/jbe@1309 | 7837           element = (target.nodeType === 3) ? target.parentNode : target, // IE has .contains only seeing elements not textnodes | 
| bsw/jbe@1309 | 7838           matches  = container.querySelectorAll(selector); | 
| bsw/jbe@1309 | 7839 | 
| bsw/jbe@1309 | 7840       for (var i = 0, max = matches.length; i < max; i++) { | 
| bsw/jbe@1309 | 7841         if (matches[i].contains(element)) { | 
| bsw/jbe@1309 | 7842           handler.call(matches[i], event); | 
| bsw/jbe@1309 | 7843         } | 
| bsw/jbe@1309 | 7844       } | 
| bsw/jbe@1309 | 7845     }; | 
| bsw/jbe@1309 | 7846 | 
| bsw/jbe@1309 | 7847     container.addEventListener(eventName, callback, false); | 
| bsw/jbe@1309 | 7848     return { | 
| bsw/jbe@1309 | 7849       stop: function() { | 
| bsw/jbe@1309 | 7850         container.removeEventListener(eventName, callback, false); | 
| bsw/jbe@1309 | 7851       } | 
| bsw/jbe@1309 | 7852     }; | 
| bsw/jbe@1309 | 7853   }; | 
| bsw/jbe@1309 | 7854 })(wysihtml); | 
| bsw/jbe@1309 | 7855 | 
| bsw/jbe@1309 | 7856 // TODO: Refactor dom tree traversing here | 
| bsw/jbe@1309 | 7857 (function(wysihtml) { | 
| bsw/jbe@1309 | 7858 | 
| bsw/jbe@1309 | 7859   // Finds parents of a node, returning the outermost node first in Array | 
| bsw/jbe@1309 | 7860   // if contain node is given parents search is stopped at the container | 
| bsw/jbe@1309 | 7861   function parents(node, container) { | 
| bsw/jbe@1309 | 7862     var nodes = [node], n = node; | 
| bsw/jbe@1309 | 7863 | 
| bsw/jbe@1309 | 7864     // iterate parents while parent exists and it is not container element | 
| bsw/jbe@1309 | 7865     while((container && n && n !== container) || (!container && n)) { | 
| bsw/jbe@1309 | 7866       nodes.unshift(n); | 
| bsw/jbe@1309 | 7867       n = n.parentNode; | 
| bsw/jbe@1309 | 7868     } | 
| bsw/jbe@1309 | 7869     return nodes; | 
| bsw/jbe@1309 | 7870   } | 
| bsw/jbe@1309 | 7871 | 
| bsw/jbe@1309 | 7872   wysihtml.dom.domNode = function(node) { | 
| bsw/jbe@1309 | 7873     var defaultNodeTypes = [wysihtml.ELEMENT_NODE, wysihtml.TEXT_NODE]; | 
| bsw/jbe@1309 | 7874 | 
| bsw/jbe@1309 | 7875     return { | 
| bsw/jbe@1309 | 7876 | 
| bsw/jbe@1309 | 7877       is: { | 
| bsw/jbe@1309 | 7878         emptyTextNode: function(ignoreWhitespace) { | 
| bsw/jbe@1309 | 7879           var regx = ignoreWhitespace ? (/^\s*$/g) : (/^[\r\n]*$/g); | 
| bsw/jbe@1309 | 7880           return node && node.nodeType === wysihtml.TEXT_NODE && (regx).test(node.data); | 
| bsw/jbe@1309 | 7881         }, | 
| bsw/jbe@1309 | 7882 | 
| bsw/jbe@1309 | 7883         // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring) | 
| bsw/jbe@1309 | 7884         rangyBookmark: function() { | 
| bsw/jbe@1309 | 7885           return node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary'); | 
| bsw/jbe@1309 | 7886         }, | 
| bsw/jbe@1309 | 7887 | 
| bsw/jbe@1309 | 7888         visible: function() { | 
| bsw/jbe@1309 | 7889           var isVisible = !(/^\s*$/g).test(wysihtml.dom.getTextContent(node)); | 
| bsw/jbe@1309 | 7890 | 
| bsw/jbe@1309 | 7891           if (!isVisible) { | 
| bsw/jbe@1309 | 7892             if (node.nodeType === 1 && node.querySelector('img, br, hr, object, embed, canvas, input, textarea')) { | 
| bsw/jbe@1309 | 7893               isVisible = true; | 
| bsw/jbe@1309 | 7894             } | 
| bsw/jbe@1309 | 7895           } | 
| bsw/jbe@1309 | 7896           return isVisible; | 
| bsw/jbe@1309 | 7897         }, | 
| bsw/jbe@1309 | 7898         lineBreak: function() { | 
| bsw/jbe@1309 | 7899           return node && node.nodeType === 1 && node.nodeName === "BR"; | 
| bsw/jbe@1309 | 7900         }, | 
| bsw/jbe@1309 | 7901         block: function() { | 
| bsw/jbe@1309 | 7902           return node && node.nodeType === 1 && node.ownerDocument.defaultView.getComputedStyle(node).display === "block"; | 
| bsw/jbe@1309 | 7903         }, | 
| bsw/jbe@1309 | 7904         // Void elements are elemens that can not have content | 
| bsw/jbe@1309 | 7905         // In most cases browsers should solve the cases for you when you try to insert content into those, | 
| bsw/jbe@1309 | 7906         //    but IE does not and it is not nice to do so anyway. | 
| bsw/jbe@1309 | 7907         voidElement: function() { | 
| bsw/jbe@1309 | 7908           return wysihtml.dom.domNode(node).test({ | 
| bsw/jbe@1309 | 7909             query: wysihtml.VOID_ELEMENTS | 
| bsw/jbe@1309 | 7910           }); | 
| bsw/jbe@1309 | 7911         } | 
| bsw/jbe@1309 | 7912       }, | 
| bsw/jbe@1309 | 7913 | 
| bsw/jbe@1309 | 7914       // var node = wysihtml.dom.domNode(element).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 7915       prev: function(options) { | 
| bsw/jbe@1309 | 7916         var prevNode = node.previousSibling, | 
| bsw/jbe@1309 | 7917             types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes; | 
| bsw/jbe@1309 | 7918 | 
| bsw/jbe@1309 | 7919         if (!prevNode) { | 
| bsw/jbe@1309 | 7920           return null; | 
| bsw/jbe@1309 | 7921         } | 
| bsw/jbe@1309 | 7922 | 
| bsw/jbe@1309 | 7923         if ( | 
| bsw/jbe@1309 | 7924           wysihtml.dom.domNode(prevNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass) | 
| bsw/jbe@1309 | 7925           (!wysihtml.lang.array(types).contains(prevNode.nodeType)) || // nodeTypes check. | 
| bsw/jbe@1309 | 7926           (options && options.ignoreBlankTexts && wysihtml.dom.domNode(prevNode).is.emptyTextNode(true)) // Blank text nodes bypassed if set | 
| bsw/jbe@1309 | 7927         ) { | 
| bsw/jbe@1309 | 7928           return wysihtml.dom.domNode(prevNode).prev(options); | 
| bsw/jbe@1309 | 7929         } | 
| bsw/jbe@1309 | 7930 | 
| bsw/jbe@1309 | 7931         return prevNode; | 
| bsw/jbe@1309 | 7932       }, | 
| bsw/jbe@1309 | 7933 | 
| bsw/jbe@1309 | 7934       // var node = wysihtml.dom.domNode(element).next({nodeTypes: [1,3], ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 7935       next: function(options) { | 
| bsw/jbe@1309 | 7936         var nextNode = node.nextSibling, | 
| bsw/jbe@1309 | 7937             types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes; | 
| bsw/jbe@1309 | 7938 | 
| bsw/jbe@1309 | 7939         if (!nextNode) { | 
| bsw/jbe@1309 | 7940           return null; | 
| bsw/jbe@1309 | 7941         } | 
| bsw/jbe@1309 | 7942 | 
| bsw/jbe@1309 | 7943         if ( | 
| bsw/jbe@1309 | 7944           wysihtml.dom.domNode(nextNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass) | 
| bsw/jbe@1309 | 7945           (!wysihtml.lang.array(types).contains(nextNode.nodeType)) || // nodeTypes check. | 
| bsw/jbe@1309 | 7946           (options && options.ignoreBlankTexts && wysihtml.dom.domNode(nextNode).is.emptyTextNode(true)) // blank text nodes bypassed if set | 
| bsw/jbe@1309 | 7947         ) { | 
| bsw/jbe@1309 | 7948           return wysihtml.dom.domNode(nextNode).next(options); | 
| bsw/jbe@1309 | 7949         } | 
| bsw/jbe@1309 | 7950 | 
| bsw/jbe@1309 | 7951         return nextNode; | 
| bsw/jbe@1309 | 7952       }, | 
| bsw/jbe@1309 | 7953 | 
| bsw/jbe@1309 | 7954       // Finds the common acnestor container of two nodes | 
| bsw/jbe@1309 | 7955       // If container given stops search at the container | 
| bsw/jbe@1309 | 7956       // If no common ancestor found returns null | 
| bsw/jbe@1309 | 7957       // var node = wysihtml.dom.domNode(element).commonAncestor(node2, container); | 
| bsw/jbe@1309 | 7958       commonAncestor: function(node2, container) { | 
| bsw/jbe@1309 | 7959         var parents1 = parents(node, container), | 
| bsw/jbe@1309 | 7960             parents2 = parents(node2, container); | 
| bsw/jbe@1309 | 7961 | 
| bsw/jbe@1309 | 7962         // Ensure we have found a common ancestor, which will be the first one if anything | 
| bsw/jbe@1309 | 7963         if (parents1[0] != parents2[0]) { | 
| bsw/jbe@1309 | 7964           return null; | 
| bsw/jbe@1309 | 7965         } | 
| bsw/jbe@1309 | 7966 | 
| bsw/jbe@1309 | 7967         // Traverse up the hierarchy of parents until we reach where they're no longer | 
| bsw/jbe@1309 | 7968         // the same. Then return previous which was the common ancestor. | 
| bsw/jbe@1309 | 7969         for (var i = 0; i < parents1.length; i++) { | 
| bsw/jbe@1309 | 7970           if (parents1[i] != parents2[i]) { | 
| bsw/jbe@1309 | 7971             return parents1[i - 1]; | 
| bsw/jbe@1309 | 7972           } | 
| bsw/jbe@1309 | 7973         } | 
| bsw/jbe@1309 | 7974 | 
| bsw/jbe@1309 | 7975         return null; | 
| bsw/jbe@1309 | 7976       }, | 
| bsw/jbe@1309 | 7977 | 
| bsw/jbe@1309 | 7978       // Traverses a node for last children and their chidren (including itself), and finds the last node that has no children. | 
| bsw/jbe@1309 | 7979       // Array of classes for forced last-leaves (ex: uneditable-container) can be defined (options = {leafClasses: [...]}) | 
| bsw/jbe@1309 | 7980       // Useful for finding the actually visible element before cursor | 
| bsw/jbe@1309 | 7981       lastLeafNode: function(options) { | 
| bsw/jbe@1309 | 7982         var lastChild; | 
| bsw/jbe@1309 | 7983 | 
| bsw/jbe@1309 | 7984         // Returns non-element nodes | 
| bsw/jbe@1309 | 7985         if (node.nodeType !== 1) { | 
| bsw/jbe@1309 | 7986           return node; | 
| bsw/jbe@1309 | 7987         } | 
| bsw/jbe@1309 | 7988 | 
| bsw/jbe@1309 | 7989         // Returns if element is leaf | 
| bsw/jbe@1309 | 7990         lastChild = node.lastChild; | 
| bsw/jbe@1309 | 7991         if (!lastChild) { | 
| bsw/jbe@1309 | 7992           return node; | 
| bsw/jbe@1309 | 7993         } | 
| bsw/jbe@1309 | 7994 | 
| bsw/jbe@1309 | 7995         // Returns if element is of of options.leafClasses leaf | 
| bsw/jbe@1309 | 7996         if (options && options.leafClasses) { | 
| bsw/jbe@1309 | 7997           for (var i = options.leafClasses.length; i--;) { | 
| bsw/jbe@1309 | 7998             if (wysihtml.dom.hasClass(node, options.leafClasses[i])) { | 
| bsw/jbe@1309 | 7999               return node; | 
| bsw/jbe@1309 | 8000             } | 
| bsw/jbe@1309 | 8001           } | 
| bsw/jbe@1309 | 8002         } | 
| bsw/jbe@1309 | 8003 | 
| bsw/jbe@1309 | 8004         return wysihtml.dom.domNode(lastChild).lastLeafNode(options); | 
| bsw/jbe@1309 | 8005       }, | 
| bsw/jbe@1309 | 8006 | 
| bsw/jbe@1309 | 8007       // Splits element at childnode and extracts the childNode out of the element context | 
| bsw/jbe@1309 | 8008       // Example: | 
| bsw/jbe@1309 | 8009       //   var node = wysihtml.dom.domNode(node).escapeParent(parentNode); | 
| bsw/jbe@1309 | 8010       escapeParent: function(element, newWrapper) { | 
| bsw/jbe@1309 | 8011         var parent, split2, nodeWrap, | 
| bsw/jbe@1309 | 8012             curNode = node; | 
| bsw/jbe@1309 | 8013 | 
| bsw/jbe@1309 | 8014         // Stop if node is not a descendant of element | 
| bsw/jbe@1309 | 8015         if (!wysihtml.dom.contains(element, node)) { | 
| bsw/jbe@1309 | 8016           throw new Error("Child is not a descendant of node."); | 
| bsw/jbe@1309 | 8017         } | 
| bsw/jbe@1309 | 8018 | 
| bsw/jbe@1309 | 8019         // Climb up the node tree untill node is reached | 
| bsw/jbe@1309 | 8020         do { | 
| bsw/jbe@1309 | 8021           // Get current parent of node | 
| bsw/jbe@1309 | 8022           parent = curNode.parentNode; | 
| bsw/jbe@1309 | 8023 | 
| bsw/jbe@1309 | 8024           // Move after nodes to new clone wrapper | 
| bsw/jbe@1309 | 8025           split2 = parent.cloneNode(false); | 
| bsw/jbe@1309 | 8026           while (parent.lastChild && parent.lastChild !== curNode) { | 
| bsw/jbe@1309 | 8027             split2.insertBefore(parent.lastChild, split2.firstChild); | 
| bsw/jbe@1309 | 8028           } | 
| bsw/jbe@1309 | 8029 | 
| bsw/jbe@1309 | 8030           // Move node up a level. If parent is not yet the container to escape, clone the parent around node, so inner nodes are escaped out too | 
| bsw/jbe@1309 | 8031           if (parent !== element) { | 
| bsw/jbe@1309 | 8032             nodeWrap = parent.cloneNode(false); | 
| bsw/jbe@1309 | 8033             nodeWrap.appendChild(curNode); | 
| bsw/jbe@1309 | 8034             curNode = nodeWrap; | 
| bsw/jbe@1309 | 8035           } | 
| bsw/jbe@1309 | 8036           parent.parentNode.insertBefore(curNode, parent.nextSibling); | 
| bsw/jbe@1309 | 8037 | 
| bsw/jbe@1309 | 8038           // Add after nodes (unless empty) | 
| bsw/jbe@1309 | 8039           if (split2.innerHTML !== '') { | 
| bsw/jbe@1309 | 8040             // if contents are empty insert without wrap | 
| bsw/jbe@1309 | 8041             if ((/^\s+$/).test(split2.innerHTML)) { | 
| bsw/jbe@1309 | 8042               while (split2.lastChild) { | 
| bsw/jbe@1309 | 8043                 parent.parentNode.insertBefore(split2.lastChild, curNode.nextSibling); | 
| bsw/jbe@1309 | 8044               } | 
| bsw/jbe@1309 | 8045             } else { | 
| bsw/jbe@1309 | 8046               parent.parentNode.insertBefore(split2, curNode.nextSibling); | 
| bsw/jbe@1309 | 8047             } | 
| bsw/jbe@1309 | 8048           } | 
| bsw/jbe@1309 | 8049 | 
| bsw/jbe@1309 | 8050           // If the node left behind before the split (parent) is now empty then remove | 
| bsw/jbe@1309 | 8051           if (parent.innerHTML === '') { | 
| bsw/jbe@1309 | 8052             parent.parentNode.removeChild(parent); | 
| bsw/jbe@1309 | 8053           } else if ((/^\s+$/).test(parent.innerHTML)) { | 
| bsw/jbe@1309 | 8054             while (parent.firstChild) { | 
| bsw/jbe@1309 | 8055               parent.parentNode.insertBefore(parent.firstChild, parent); | 
| bsw/jbe@1309 | 8056             } | 
| bsw/jbe@1309 | 8057             parent.parentNode.removeChild(parent); | 
| bsw/jbe@1309 | 8058           } | 
| bsw/jbe@1309 | 8059 | 
| bsw/jbe@1309 | 8060         } while (parent && parent !== element); | 
| bsw/jbe@1309 | 8061 | 
| bsw/jbe@1309 | 8062         if (newWrapper && curNode) { | 
| bsw/jbe@1309 | 8063           curNode.parentNode.insertBefore(newWrapper, curNode); | 
| bsw/jbe@1309 | 8064           newWrapper.appendChild(curNode); | 
| bsw/jbe@1309 | 8065         } | 
| bsw/jbe@1309 | 8066       }, | 
| bsw/jbe@1309 | 8067 | 
| bsw/jbe@1309 | 8068       transferContentTo: function(targetNode, removeOldWrapper) { | 
| bsw/jbe@1309 | 8069         if (node.nodeType === 1) { | 
| bsw/jbe@1309 | 8070           if (wysihtml.dom.domNode(targetNode).is.voidElement() || targetNode.nodeType === 3) { | 
| bsw/jbe@1309 | 8071             while (node.lastChild) { | 
| bsw/jbe@1309 | 8072               targetNode.parentNode.insertBefore(node.lastChild, targetNode.nextSibling); | 
| bsw/jbe@1309 | 8073             } | 
| bsw/jbe@1309 | 8074           } else { | 
| bsw/jbe@1309 | 8075             while (node.firstChild) { | 
| bsw/jbe@1309 | 8076               targetNode.appendChild(node.firstChild); | 
| bsw/jbe@1309 | 8077             } | 
| bsw/jbe@1309 | 8078           } | 
| bsw/jbe@1309 | 8079           if (removeOldWrapper) { | 
| bsw/jbe@1309 | 8080             node.parentNode.removeChild(node); | 
| bsw/jbe@1309 | 8081           } | 
| bsw/jbe@1309 | 8082         } else if (node.nodeType === 3 || node.nodeType === 8){ | 
| bsw/jbe@1309 | 8083           if (wysihtml.dom.domNode(targetNode).is.voidElement()) { | 
| bsw/jbe@1309 | 8084             targetNode.parentNode.insertBefore(node, targetNode.nextSibling); | 
| bsw/jbe@1309 | 8085           } else { | 
| bsw/jbe@1309 | 8086             targetNode.appendChild(node); | 
| bsw/jbe@1309 | 8087           } | 
| bsw/jbe@1309 | 8088         } | 
| bsw/jbe@1309 | 8089       }, | 
| bsw/jbe@1309 | 8090 | 
| bsw/jbe@1309 | 8091       /* | 
| bsw/jbe@1309 | 8092         Tests a node against properties, and returns true if matches. | 
| bsw/jbe@1309 | 8093         Tests on principle that all properties defined must have at least one match. | 
| bsw/jbe@1309 | 8094         styleValue parameter works in context of styleProperty and has no effect otherwise. | 
| bsw/jbe@1309 | 8095         Returns true if element matches and false if it does not. | 
| bsw/jbe@1309 | 8096 | 
| bsw/jbe@1309 | 8097         Properties for filtering element: | 
| bsw/jbe@1309 | 8098         { | 
| bsw/jbe@1309 | 8099           query: selector string, | 
| bsw/jbe@1309 | 8100           nodeName: string (uppercase), | 
| bsw/jbe@1309 | 8101           className: string, | 
| bsw/jbe@1309 | 8102           classRegExp: regex, | 
| bsw/jbe@1309 | 8103           styleProperty: string or [], | 
| bsw/jbe@1309 | 8104           styleValue: string, [] or regex | 
| bsw/jbe@1309 | 8105         } | 
| bsw/jbe@1309 | 8106 | 
| bsw/jbe@1309 | 8107         Example: | 
| bsw/jbe@1309 | 8108         var node = wysihtml.dom.domNode(element).test({}) | 
| bsw/jbe@1309 | 8109       */ | 
| bsw/jbe@1309 | 8110       test: function(properties) { | 
| bsw/jbe@1309 | 8111         var prop; | 
| bsw/jbe@1309 | 8112 | 
| bsw/jbe@1309 | 8113         // return false if properties object is not defined | 
| bsw/jbe@1309 | 8114         if (!properties) { | 
| bsw/jbe@1309 | 8115           return false; | 
| bsw/jbe@1309 | 8116         } | 
| bsw/jbe@1309 | 8117 | 
| bsw/jbe@1309 | 8118         // Only element nodes can be tested for these properties | 
| bsw/jbe@1309 | 8119         if (node.nodeType !== 1) { | 
| bsw/jbe@1309 | 8120           return false; | 
| bsw/jbe@1309 | 8121         } | 
| bsw/jbe@1309 | 8122 | 
| bsw/jbe@1309 | 8123         if (properties.query) { | 
| bsw/jbe@1309 | 8124           if (!node.matches(properties.query)) { | 
| bsw/jbe@1309 | 8125             return false; | 
| bsw/jbe@1309 | 8126           } | 
| bsw/jbe@1309 | 8127         } | 
| bsw/jbe@1309 | 8128 | 
| bsw/jbe@1309 | 8129         if (properties.nodeName && node.nodeName.toLowerCase() !== properties.nodeName.toLowerCase()) { | 
| bsw/jbe@1309 | 8130           return false; | 
| bsw/jbe@1309 | 8131         } | 
| bsw/jbe@1309 | 8132 | 
| bsw/jbe@1309 | 8133         if (properties.className && !node.classList.contains(properties.className)) { | 
| bsw/jbe@1309 | 8134           return false; | 
| bsw/jbe@1309 | 8135         } | 
| bsw/jbe@1309 | 8136 | 
| bsw/jbe@1309 | 8137         // classRegExp check (useful for classname begins with logic) | 
| bsw/jbe@1309 | 8138         if (properties.classRegExp) { | 
| bsw/jbe@1309 | 8139           var matches = (node.className || "").match(properties.classRegExp) || []; | 
| bsw/jbe@1309 | 8140           if (matches.length === 0) { | 
| bsw/jbe@1309 | 8141             return false; | 
| bsw/jbe@1309 | 8142           } | 
| bsw/jbe@1309 | 8143         } | 
| bsw/jbe@1309 | 8144 | 
| bsw/jbe@1309 | 8145         // styleProperty check | 
| bsw/jbe@1309 | 8146         if (properties.styleProperty && properties.styleProperty.length > 0) { | 
| bsw/jbe@1309 | 8147           var hasOneStyle = false, | 
| bsw/jbe@1309 | 8148               styles = (Array.isArray(properties.styleProperty)) ? properties.styleProperty : [properties.styleProperty]; | 
| bsw/jbe@1309 | 8149           for (var j = 0, maxStyleP = styles.length; j < maxStyleP; j++) { | 
| bsw/jbe@1309 | 8150             // Some old IE-s have different property name for cssFloat | 
| bsw/jbe@1309 | 8151             prop = wysihtml.browser.fixStyleKey(styles[j]); | 
| bsw/jbe@1309 | 8152             if (node.style[prop]) { | 
| bsw/jbe@1309 | 8153               if (properties.styleValue) { | 
| bsw/jbe@1309 | 8154                 // Style value as additional parameter | 
| bsw/jbe@1309 | 8155                 if (properties.styleValue instanceof RegExp) { | 
| bsw/jbe@1309 | 8156                   // style value as Regexp | 
| bsw/jbe@1309 | 8157                   if (node.style[prop].trim().match(properties.styleValue).length > 0) { | 
| bsw/jbe@1309 | 8158                     hasOneStyle = true; | 
| bsw/jbe@1309 | 8159                     break; | 
| bsw/jbe@1309 | 8160                   } | 
| bsw/jbe@1309 | 8161                 } else if (Array.isArray(properties.styleValue)) { | 
| bsw/jbe@1309 | 8162                   // style value as array | 
| bsw/jbe@1309 | 8163                   if (properties.styleValue.indexOf(node.style[prop].trim())) { | 
| bsw/jbe@1309 | 8164                     hasOneStyle = true; | 
| bsw/jbe@1309 | 8165                     break; | 
| bsw/jbe@1309 | 8166                   } | 
| bsw/jbe@1309 | 8167                 } else { | 
| bsw/jbe@1309 | 8168                   // style value as string | 
| bsw/jbe@1309 | 8169                   if (properties.styleValue === node.style[prop].trim().replace(/, /g, ",")) { | 
| bsw/jbe@1309 | 8170                     hasOneStyle = true; | 
| bsw/jbe@1309 | 8171                     break; | 
| bsw/jbe@1309 | 8172                   } | 
| bsw/jbe@1309 | 8173                 } | 
| bsw/jbe@1309 | 8174               } else { | 
| bsw/jbe@1309 | 8175                 hasOneStyle = true; | 
| bsw/jbe@1309 | 8176                 break; | 
| bsw/jbe@1309 | 8177               } | 
| bsw/jbe@1309 | 8178             } | 
| bsw/jbe@1309 | 8179             if (!hasOneStyle) { | 
| bsw/jbe@1309 | 8180               return false; | 
| bsw/jbe@1309 | 8181             } | 
| bsw/jbe@1309 | 8182           } | 
| bsw/jbe@1309 | 8183         } | 
| bsw/jbe@1309 | 8184 | 
| bsw/jbe@1309 | 8185         if (properties.attribute) { | 
| bsw/jbe@1309 | 8186           var attr = wysihtml.dom.getAttributes(node), | 
| bsw/jbe@1309 | 8187               attrList = [], | 
| bsw/jbe@1309 | 8188               hasOneAttribute = false; | 
| bsw/jbe@1309 | 8189 | 
| bsw/jbe@1309 | 8190           if (Array.isArray(properties.attribute)) { | 
| bsw/jbe@1309 | 8191             attrList = properties.attribute; | 
| bsw/jbe@1309 | 8192           } else { | 
| bsw/jbe@1309 | 8193             attrList[properties.attribute] = properties.attributeValue; | 
| bsw/jbe@1309 | 8194           } | 
| bsw/jbe@1309 | 8195 | 
| bsw/jbe@1309 | 8196           for (var a in attrList) { | 
| bsw/jbe@1309 | 8197             if (attrList.hasOwnProperty(a)) { | 
| bsw/jbe@1309 | 8198               if (typeof attrList[a] === "undefined") { | 
| bsw/jbe@1309 | 8199                 if (typeof attr[a] !== "undefined") { | 
| bsw/jbe@1309 | 8200                   hasOneAttribute = true; | 
| bsw/jbe@1309 | 8201                   break; | 
| bsw/jbe@1309 | 8202                 } | 
| bsw/jbe@1309 | 8203               } else if (attr[a] === attrList[a]) { | 
| bsw/jbe@1309 | 8204                 hasOneAttribute = true; | 
| bsw/jbe@1309 | 8205                 break; | 
| bsw/jbe@1309 | 8206               } | 
| bsw/jbe@1309 | 8207             } | 
| bsw/jbe@1309 | 8208           } | 
| bsw/jbe@1309 | 8209 | 
| bsw/jbe@1309 | 8210           if (!hasOneAttribute) { | 
| bsw/jbe@1309 | 8211             return false; | 
| bsw/jbe@1309 | 8212           } | 
| bsw/jbe@1309 | 8213 | 
| bsw/jbe@1309 | 8214         } | 
| bsw/jbe@1309 | 8215 | 
| bsw/jbe@1309 | 8216         return true; | 
| bsw/jbe@1309 | 8217       } | 
| bsw/jbe@1309 | 8218 | 
| bsw/jbe@1309 | 8219     }; | 
| bsw/jbe@1309 | 8220   }; | 
| bsw/jbe@1309 | 8221 })(wysihtml); | 
| bsw/jbe@1309 | 8222 | 
| bsw/jbe@1309 | 8223 /** | 
| bsw/jbe@1309 | 8224  * Returns the given html wrapped in a div element | 
| bsw/jbe@1309 | 8225  * | 
| bsw/jbe@1309 | 8226  * Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly | 
| bsw/jbe@1309 | 8227  * when inserted via innerHTML | 
| bsw/jbe@1309 | 8228  * | 
| bsw/jbe@1309 | 8229  * @param {String} html The html which should be wrapped in a dom element | 
| bsw/jbe@1309 | 8230  * @param {Obejct} [context] Document object of the context the html belongs to | 
| bsw/jbe@1309 | 8231  * | 
| bsw/jbe@1309 | 8232  * @example | 
| bsw/jbe@1309 | 8233  *    wysihtml.dom.getAsDom("<article>foo</article>"); | 
| bsw/jbe@1309 | 8234  */ | 
| bsw/jbe@1309 | 8235 wysihtml.dom.getAsDom = (function() { | 
| bsw/jbe@1309 | 8236 | 
| bsw/jbe@1309 | 8237   var _innerHTMLShiv = function(html, context) { | 
| bsw/jbe@1309 | 8238     var tempElement = context.createElement("div"); | 
| bsw/jbe@1309 | 8239     tempElement.style.display = "none"; | 
| bsw/jbe@1309 | 8240     context.body.appendChild(tempElement); | 
| bsw/jbe@1309 | 8241     // IE throws an exception when trying to insert <frameset></frameset> via innerHTML | 
| bsw/jbe@1309 | 8242     try { tempElement.innerHTML = html; } catch(e) {} | 
| bsw/jbe@1309 | 8243     context.body.removeChild(tempElement); | 
| bsw/jbe@1309 | 8244     return tempElement; | 
| bsw/jbe@1309 | 8245   }; | 
| bsw/jbe@1309 | 8246 | 
| bsw/jbe@1309 | 8247   /** | 
| bsw/jbe@1309 | 8248    * Make sure IE supports HTML5 tags, which is accomplished by simply creating one instance of each element | 
| bsw/jbe@1309 | 8249    */ | 
| bsw/jbe@1309 | 8250   var _ensureHTML5Compatibility = function(context) { | 
| bsw/jbe@1309 | 8251     if (context._wysihtml_supportsHTML5Tags) { | 
| bsw/jbe@1309 | 8252       return; | 
| bsw/jbe@1309 | 8253     } | 
| bsw/jbe@1309 | 8254     for (var i=0, length=HTML5_ELEMENTS.length; i<length; i++) { | 
| bsw/jbe@1309 | 8255       context.createElement(HTML5_ELEMENTS[i]); | 
| bsw/jbe@1309 | 8256     } | 
| bsw/jbe@1309 | 8257     context._wysihtml_supportsHTML5Tags = true; | 
| bsw/jbe@1309 | 8258   }; | 
| bsw/jbe@1309 | 8259 | 
| bsw/jbe@1309 | 8260 | 
| bsw/jbe@1309 | 8261   /** | 
| bsw/jbe@1309 | 8262    * List of html5 tags | 
| bsw/jbe@1309 | 8263    * taken from http://simon.html5.org/html5-elements | 
| bsw/jbe@1309 | 8264    */ | 
| bsw/jbe@1309 | 8265   var HTML5_ELEMENTS = [ | 
| bsw/jbe@1309 | 8266     "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption", | 
| bsw/jbe@1309 | 8267     "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress", | 
| bsw/jbe@1309 | 8268     "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr" | 
| bsw/jbe@1309 | 8269   ]; | 
| bsw/jbe@1309 | 8270 | 
| bsw/jbe@1309 | 8271   return function(html, context) { | 
| bsw/jbe@1309 | 8272     context = context || document; | 
| bsw/jbe@1309 | 8273     var tempElement; | 
| bsw/jbe@1309 | 8274     if (typeof(html) === "object" && html.nodeType) { | 
| bsw/jbe@1309 | 8275       tempElement = context.createElement("div"); | 
| bsw/jbe@1309 | 8276       tempElement.appendChild(html); | 
| bsw/jbe@1309 | 8277     } else if (wysihtml.browser.supportsHTML5Tags(context)) { | 
| bsw/jbe@1309 | 8278       tempElement = context.createElement("div"); | 
| bsw/jbe@1309 | 8279       tempElement.innerHTML = html; | 
| bsw/jbe@1309 | 8280     } else { | 
| bsw/jbe@1309 | 8281       _ensureHTML5Compatibility(context); | 
| bsw/jbe@1309 | 8282       tempElement = _innerHTMLShiv(html, context); | 
| bsw/jbe@1309 | 8283     } | 
| bsw/jbe@1309 | 8284     return tempElement; | 
| bsw/jbe@1309 | 8285   }; | 
| bsw/jbe@1309 | 8286 })(); | 
| bsw/jbe@1309 | 8287 | 
| bsw/jbe@1309 | 8288 /** | 
| bsw/jbe@1309 | 8289  * Get a set of attribute from one element | 
| bsw/jbe@1309 | 8290  * | 
| bsw/jbe@1309 | 8291  * IE gives wrong results for hasAttribute/getAttribute, for example: | 
| bsw/jbe@1309 | 8292  *    var td = document.createElement("td"); | 
| bsw/jbe@1309 | 8293  *    td.getAttribute("rowspan"); // => "1" in IE | 
| bsw/jbe@1309 | 8294  * | 
| bsw/jbe@1309 | 8295  * Therefore we have to check the element's outerHTML for the attribute | 
| bsw/jbe@1309 | 8296 */ | 
| bsw/jbe@1309 | 8297 | 
| bsw/jbe@1309 | 8298 wysihtml.dom.getAttribute = function(node, attributeName) { | 
| bsw/jbe@1309 | 8299   var HAS_GET_ATTRIBUTE_BUG = !wysihtml.browser.supportsGetAttributeCorrectly(); | 
| bsw/jbe@1309 | 8300   attributeName = attributeName.toLowerCase(); | 
| bsw/jbe@1309 | 8301   var nodeName = node.nodeName; | 
| bsw/jbe@1309 | 8302   if (nodeName == "IMG" && attributeName == "src" && wysihtml.dom.isLoadedImage(node) === true) { | 
| bsw/jbe@1309 | 8303     // Get 'src' attribute value via object property since this will always contain the | 
| bsw/jbe@1309 | 8304     // full absolute url (http://...) | 
| bsw/jbe@1309 | 8305     // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host | 
| bsw/jbe@1309 | 8306     // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url) | 
| bsw/jbe@1309 | 8307     return node.src; | 
| bsw/jbe@1309 | 8308   } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) { | 
| bsw/jbe@1309 | 8309     // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML | 
| bsw/jbe@1309 | 8310     var outerHTML      = node.outerHTML.toLowerCase(), | 
| bsw/jbe@1309 | 8311         // TODO: This might not work for attributes without value: <input disabled> | 
| bsw/jbe@1309 | 8312         hasAttribute   = outerHTML.indexOf(" " + attributeName +  "=") != -1; | 
| bsw/jbe@1309 | 8313 | 
| bsw/jbe@1309 | 8314     return hasAttribute ? node.getAttribute(attributeName) : null; | 
| bsw/jbe@1309 | 8315   } else{ | 
| bsw/jbe@1309 | 8316     return node.getAttribute(attributeName); | 
| bsw/jbe@1309 | 8317   } | 
| bsw/jbe@1309 | 8318 }; | 
| bsw/jbe@1309 | 8319 | 
| bsw/jbe@1309 | 8320 /** | 
| bsw/jbe@1309 | 8321  * Get all attributes of an element | 
| bsw/jbe@1309 | 8322  * | 
| bsw/jbe@1309 | 8323  * IE gives wrong results for hasAttribute/getAttribute, for example: | 
| bsw/jbe@1309 | 8324  *    var td = document.createElement("td"); | 
| bsw/jbe@1309 | 8325  *    td.getAttribute("rowspan"); // => "1" in IE | 
| bsw/jbe@1309 | 8326  * | 
| bsw/jbe@1309 | 8327  * Therefore we have to check the element's outerHTML for the attribute | 
| bsw/jbe@1309 | 8328 */ | 
| bsw/jbe@1309 | 8329 | 
| bsw/jbe@1309 | 8330 wysihtml.dom.getAttributes = function(node) { | 
| bsw/jbe@1309 | 8331   var HAS_GET_ATTRIBUTE_BUG = !wysihtml.browser.supportsGetAttributeCorrectly(), | 
| bsw/jbe@1309 | 8332       nodeName = node.nodeName, | 
| bsw/jbe@1309 | 8333       attributes = [], | 
| bsw/jbe@1309 | 8334       attr; | 
| bsw/jbe@1309 | 8335 | 
| bsw/jbe@1309 | 8336   for (attr in node.attributes) { | 
| bsw/jbe@1309 | 8337     if ((node.attributes.hasOwnProperty && node.attributes.hasOwnProperty(attr)) || (!node.attributes.hasOwnProperty && Object.prototype.hasOwnProperty.call(node.attributes, attr)))  { | 
| bsw/jbe@1309 | 8338       if (node.attributes[attr].specified) { | 
| bsw/jbe@1309 | 8339         if (nodeName == "IMG" && node.attributes[attr].name.toLowerCase() == "src" && wysihtml.dom.isLoadedImage(node) === true) { | 
| bsw/jbe@1309 | 8340           attributes['src'] = node.src; | 
| bsw/jbe@1309 | 8341         } else if (wysihtml.lang.array(['rowspan', 'colspan']).contains(node.attributes[attr].name.toLowerCase()) && HAS_GET_ATTRIBUTE_BUG) { | 
| bsw/jbe@1309 | 8342           if (node.attributes[attr].value !== 1) { | 
| bsw/jbe@1309 | 8343             attributes[node.attributes[attr].name] = node.attributes[attr].value; | 
| bsw/jbe@1309 | 8344           } | 
| bsw/jbe@1309 | 8345         } else { | 
| bsw/jbe@1309 | 8346           attributes[node.attributes[attr].name] = node.attributes[attr].value; | 
| bsw/jbe@1309 | 8347         } | 
| bsw/jbe@1309 | 8348       } | 
| bsw/jbe@1309 | 8349     } | 
| bsw/jbe@1309 | 8350   } | 
| bsw/jbe@1309 | 8351   return attributes; | 
| bsw/jbe@1309 | 8352 }; | 
| bsw/jbe@1309 | 8353 | 
| bsw/jbe@1309 | 8354 /** | 
| bsw/jbe@1309 | 8355  * Walks the dom tree from the given node up until it finds a match | 
| bsw/jbe@1309 | 8356  * | 
| bsw/jbe@1309 | 8357  * @param {Element} node The from which to check the parent nodes | 
| bsw/jbe@1309 | 8358  * @param {Object} matchingSet Object to match against, Properties for filtering element: | 
| bsw/jbe@1309 | 8359  *   { | 
| bsw/jbe@1309 | 8360  *     query: selector string, | 
| bsw/jbe@1309 | 8361  *     classRegExp: regex, | 
| bsw/jbe@1309 | 8362  *     styleProperty: string or [], | 
| bsw/jbe@1309 | 8363  *     styleValue: string, [] or regex | 
| bsw/jbe@1309 | 8364  *   } | 
| bsw/jbe@1309 | 8365  * @param {Number} [levels] How many parents should the function check up from the current node (defaults to 50) | 
| bsw/jbe@1309 | 8366  * @param {Element} Optional, defines the container that limits the search | 
| bsw/jbe@1309 | 8367  * | 
| bsw/jbe@1309 | 8368  * @return {null|Element} Returns the first element that matched the desiredNodeName(s) | 
| bsw/jbe@1309 | 8369 */ | 
| bsw/jbe@1309 | 8370 | 
| bsw/jbe@1309 | 8371 wysihtml.dom.getParentElement = (function() { | 
| bsw/jbe@1309 | 8372 | 
| bsw/jbe@1309 | 8373   return function(node, properties, levels, container) { | 
| bsw/jbe@1309 | 8374     levels = levels || 50; | 
| bsw/jbe@1309 | 8375     while (levels-- && node && node.nodeName !== "BODY" && (!container || node !== container)) { | 
| bsw/jbe@1309 | 8376       if (wysihtml.dom.domNode(node).test(properties)) { | 
| bsw/jbe@1309 | 8377         return node; | 
| bsw/jbe@1309 | 8378       } | 
| bsw/jbe@1309 | 8379       node = node.parentNode; | 
| bsw/jbe@1309 | 8380     } | 
| bsw/jbe@1309 | 8381     return null; | 
| bsw/jbe@1309 | 8382   }; | 
| bsw/jbe@1309 | 8383 | 
| bsw/jbe@1309 | 8384 })(); | 
| bsw/jbe@1309 | 8385 | 
| bsw/jbe@1309 | 8386 /* | 
| bsw/jbe@1309 | 8387  * Methods for fetching pasted html before it gets inserted into content | 
| bsw/jbe@1309 | 8388 **/ | 
| bsw/jbe@1309 | 8389 | 
| bsw/jbe@1309 | 8390 /* Modern event.clipboardData driven approach. | 
| bsw/jbe@1309 | 8391  * Advantage is that it does not have to loose selection or modify dom to catch the data. | 
| bsw/jbe@1309 | 8392  * IE does not support though. | 
| bsw/jbe@1309 | 8393 **/ | 
| bsw/jbe@1309 | 8394 wysihtml.dom.getPastedHtml = function(event) { | 
| bsw/jbe@1309 | 8395   var html; | 
| bsw/jbe@1309 | 8396   if (wysihtml.browser.supportsModernPaste() && event.clipboardData) { | 
| bsw/jbe@1309 | 8397     if (wysihtml.lang.array(event.clipboardData.types).contains('text/html')) { | 
| bsw/jbe@1309 | 8398       html = event.clipboardData.getData('text/html'); | 
| bsw/jbe@1309 | 8399     } else if (wysihtml.lang.array(event.clipboardData.types).contains('text/plain')) { | 
| bsw/jbe@1309 | 8400       html = wysihtml.lang.string(event.clipboardData.getData('text/plain')).escapeHTML(true, true); | 
| bsw/jbe@1309 | 8401     } | 
| bsw/jbe@1309 | 8402   } | 
| bsw/jbe@1309 | 8403   return html; | 
| bsw/jbe@1309 | 8404 }; | 
| bsw/jbe@1309 | 8405 | 
| bsw/jbe@1309 | 8406 /* Older temprorary contenteditable as paste source catcher method for fallbacks */ | 
| bsw/jbe@1309 | 8407 wysihtml.dom.getPastedHtmlWithDiv = function (composer, f) { | 
| bsw/jbe@1309 | 8408   var selBookmark = composer.selection.getBookmark(), | 
| bsw/jbe@1309 | 8409       doc = composer.element.ownerDocument, | 
| bsw/jbe@1309 | 8410       cleanerDiv = doc.createElement('DIV'), | 
| bsw/jbe@1309 | 8411       scrollPos = composer.getScrollPos(); | 
| bsw/jbe@1309 | 8412 | 
| bsw/jbe@1309 | 8413   doc.body.appendChild(cleanerDiv); | 
| bsw/jbe@1309 | 8414 | 
| bsw/jbe@1309 | 8415   cleanerDiv.style.width = "1px"; | 
| bsw/jbe@1309 | 8416   cleanerDiv.style.height = "1px"; | 
| bsw/jbe@1309 | 8417   cleanerDiv.style.overflow = "hidden"; | 
| bsw/jbe@1309 | 8418   cleanerDiv.style.position = "absolute"; | 
| bsw/jbe@1309 | 8419   cleanerDiv.style.top = scrollPos.y + "px"; | 
| bsw/jbe@1309 | 8420   cleanerDiv.style.left = scrollPos.x + "px"; | 
| bsw/jbe@1309 | 8421 | 
| bsw/jbe@1309 | 8422   cleanerDiv.setAttribute('contenteditable', 'true'); | 
| bsw/jbe@1309 | 8423   cleanerDiv.focus(); | 
| bsw/jbe@1309 | 8424 | 
| bsw/jbe@1309 | 8425   setTimeout(function () { | 
| bsw/jbe@1309 | 8426     var html; | 
| bsw/jbe@1309 | 8427 | 
| bsw/jbe@1309 | 8428     composer.selection.setBookmark(selBookmark); | 
| bsw/jbe@1309 | 8429     html = cleanerDiv.innerHTML; | 
| bsw/jbe@1309 | 8430     if (html && (/^<br\/?>$/i).test(html.trim())) { | 
| bsw/jbe@1309 | 8431       html = false; | 
| bsw/jbe@1309 | 8432     } | 
| bsw/jbe@1309 | 8433     f(html); | 
| bsw/jbe@1309 | 8434     cleanerDiv.parentNode.removeChild(cleanerDiv); | 
| bsw/jbe@1309 | 8435   }, 0); | 
| bsw/jbe@1309 | 8436 }; | 
| bsw/jbe@1309 | 8437 | 
| bsw/jbe@1309 | 8438 /** | 
| bsw/jbe@1309 | 8439  * Get element's style for a specific css property | 
| bsw/jbe@1309 | 8440  * | 
| bsw/jbe@1309 | 8441  * @param {Element} element The element on which to retrieve the style | 
| bsw/jbe@1309 | 8442  * @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...) | 
| bsw/jbe@1309 | 8443  * | 
| bsw/jbe@1309 | 8444  * @example | 
| bsw/jbe@1309 | 8445  *    wysihtml.dom.getStyle("display").from(document.body); | 
| bsw/jbe@1309 | 8446  *    // => "block" | 
| bsw/jbe@1309 | 8447  */ | 
| bsw/jbe@1309 | 8448 wysihtml.dom.getStyle = (function() { | 
| bsw/jbe@1309 | 8449   var stylePropertyMapping = { | 
| bsw/jbe@1309 | 8450         "float": ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat" | 
| bsw/jbe@1309 | 8451       }, | 
| bsw/jbe@1309 | 8452       REG_EXP_CAMELIZE = /\-[a-z]/g; | 
| bsw/jbe@1309 | 8453 | 
| bsw/jbe@1309 | 8454   function camelize(str) { | 
| bsw/jbe@1309 | 8455     return str.replace(REG_EXP_CAMELIZE, function(match) { | 
| bsw/jbe@1309 | 8456       return match.charAt(1).toUpperCase(); | 
| bsw/jbe@1309 | 8457     }); | 
| bsw/jbe@1309 | 8458   } | 
| bsw/jbe@1309 | 8459 | 
| bsw/jbe@1309 | 8460   return function(property) { | 
| bsw/jbe@1309 | 8461     return { | 
| bsw/jbe@1309 | 8462       from: function(element) { | 
| bsw/jbe@1309 | 8463         if (element.nodeType !== wysihtml.ELEMENT_NODE) { | 
| bsw/jbe@1309 | 8464           return; | 
| bsw/jbe@1309 | 8465         } | 
| bsw/jbe@1309 | 8466 | 
| bsw/jbe@1309 | 8467         var doc               = element.ownerDocument, | 
| bsw/jbe@1309 | 8468             camelizedProperty = stylePropertyMapping[property] || camelize(property), | 
| bsw/jbe@1309 | 8469             style             = element.style, | 
| bsw/jbe@1309 | 8470             currentStyle      = element.currentStyle, | 
| bsw/jbe@1309 | 8471             styleValue        = style[camelizedProperty]; | 
| bsw/jbe@1309 | 8472         if (styleValue) { | 
| bsw/jbe@1309 | 8473           return styleValue; | 
| bsw/jbe@1309 | 8474         } | 
| bsw/jbe@1309 | 8475 | 
| bsw/jbe@1309 | 8476         // currentStyle is no standard and only supported by Opera and IE but it has one important advantage over the standard-compliant | 
| bsw/jbe@1309 | 8477         // window.getComputedStyle, since it returns css property values in their original unit: | 
| bsw/jbe@1309 | 8478         // If you set an elements width to "50%", window.getComputedStyle will give you it's current width in px while currentStyle | 
| bsw/jbe@1309 | 8479         // gives you the original "50%". | 
| bsw/jbe@1309 | 8480         // Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio | 
| bsw/jbe@1309 | 8481         if (currentStyle) { | 
| bsw/jbe@1309 | 8482           try { | 
| bsw/jbe@1309 | 8483             return currentStyle[camelizedProperty]; | 
| bsw/jbe@1309 | 8484           } catch(e) { | 
| bsw/jbe@1309 | 8485             //ie will occasionally fail for unknown reasons. swallowing exception | 
| bsw/jbe@1309 | 8486           } | 
| bsw/jbe@1309 | 8487         } | 
| bsw/jbe@1309 | 8488 | 
| bsw/jbe@1309 | 8489         var win                 = doc.defaultView || doc.parentWindow, | 
| bsw/jbe@1309 | 8490             needsOverflowReset  = (property === "height" || property === "width") && element.nodeName === "TEXTAREA", | 
| bsw/jbe@1309 | 8491             originalOverflow, | 
| bsw/jbe@1309 | 8492             returnValue; | 
| bsw/jbe@1309 | 8493 | 
| bsw/jbe@1309 | 8494         if (win.getComputedStyle) { | 
| bsw/jbe@1309 | 8495           // Chrome and Safari both calculate a wrong width and height for textareas when they have scroll bars | 
| bsw/jbe@1309 | 8496           // therfore we remove and restore the scrollbar and calculate the value in between | 
| bsw/jbe@1309 | 8497           if (needsOverflowReset) { | 
| bsw/jbe@1309 | 8498             originalOverflow = style.overflow; | 
| bsw/jbe@1309 | 8499             style.overflow = "hidden"; | 
| bsw/jbe@1309 | 8500           } | 
| bsw/jbe@1309 | 8501           returnValue = win.getComputedStyle(element, null).getPropertyValue(property); | 
| bsw/jbe@1309 | 8502           if (needsOverflowReset) { | 
| bsw/jbe@1309 | 8503             style.overflow = originalOverflow || ""; | 
| bsw/jbe@1309 | 8504           } | 
| bsw/jbe@1309 | 8505           return returnValue; | 
| bsw/jbe@1309 | 8506         } | 
| bsw/jbe@1309 | 8507       } | 
| bsw/jbe@1309 | 8508     }; | 
| bsw/jbe@1309 | 8509   }; | 
| bsw/jbe@1309 | 8510 })(); | 
| bsw/jbe@1309 | 8511 | 
| bsw/jbe@1309 | 8512 wysihtml.dom.getTextNodes = function(node, ingoreEmpty){ | 
| bsw/jbe@1309 | 8513   var all = []; | 
| bsw/jbe@1309 | 8514   for (node=node.firstChild;node;node=node.nextSibling){ | 
| bsw/jbe@1309 | 8515     if (node.nodeType == 3) { | 
| bsw/jbe@1309 | 8516       if (!ingoreEmpty || !(/^\s*$/).test(node.innerText || node.textContent)) { | 
| bsw/jbe@1309 | 8517         all.push(node); | 
| bsw/jbe@1309 | 8518       } | 
| bsw/jbe@1309 | 8519     } else { | 
| bsw/jbe@1309 | 8520       all = all.concat(wysihtml.dom.getTextNodes(node, ingoreEmpty)); | 
| bsw/jbe@1309 | 8521     } | 
| bsw/jbe@1309 | 8522   } | 
| bsw/jbe@1309 | 8523   return all; | 
| bsw/jbe@1309 | 8524 }; | 
| bsw/jbe@1309 | 8525 | 
| bsw/jbe@1309 | 8526 /** | 
| bsw/jbe@1309 | 8527  * High performant way to check whether an element with a specific class name is in the given document | 
| bsw/jbe@1309 | 8528  * Optimized for being heavily executed | 
| bsw/jbe@1309 | 8529  * Unleashes the power of live node lists | 
| bsw/jbe@1309 | 8530  * | 
| bsw/jbe@1309 | 8531  * @param {Object} doc The document object of the context where to check | 
| bsw/jbe@1309 | 8532  * @param {String} tagName Upper cased tag name | 
| bsw/jbe@1309 | 8533  * @example | 
| bsw/jbe@1309 | 8534  *    wysihtml.dom.hasElementWithClassName(document, "foobar"); | 
| bsw/jbe@1309 | 8535  */ | 
| bsw/jbe@1309 | 8536 (function(wysihtml) { | 
| bsw/jbe@1309 | 8537   var LIVE_CACHE          = {}, | 
| bsw/jbe@1309 | 8538       DOCUMENT_IDENTIFIER = 1; | 
| bsw/jbe@1309 | 8539 | 
| bsw/jbe@1309 | 8540   function _getDocumentIdentifier(doc) { | 
| bsw/jbe@1309 | 8541     return doc._wysihtml_identifier || (doc._wysihtml_identifier = DOCUMENT_IDENTIFIER++); | 
| bsw/jbe@1309 | 8542   } | 
| bsw/jbe@1309 | 8543 | 
| bsw/jbe@1309 | 8544   wysihtml.dom.hasElementWithClassName = function(doc, className) { | 
| bsw/jbe@1309 | 8545     // getElementsByClassName is not supported by IE<9 | 
| bsw/jbe@1309 | 8546     // but is sometimes mocked via library code (which then doesn't return live node lists) | 
| bsw/jbe@1309 | 8547     if (!wysihtml.browser.supportsNativeGetElementsByClassName()) { | 
| bsw/jbe@1309 | 8548       return !!doc.querySelector("." + className); | 
| bsw/jbe@1309 | 8549     } | 
| bsw/jbe@1309 | 8550 | 
| bsw/jbe@1309 | 8551     var key         = _getDocumentIdentifier(doc) + ":" + className, | 
| bsw/jbe@1309 | 8552         cacheEntry  = LIVE_CACHE[key]; | 
| bsw/jbe@1309 | 8553     if (!cacheEntry) { | 
| bsw/jbe@1309 | 8554       cacheEntry = LIVE_CACHE[key] = doc.getElementsByClassName(className); | 
| bsw/jbe@1309 | 8555     } | 
| bsw/jbe@1309 | 8556 | 
| bsw/jbe@1309 | 8557     return cacheEntry.length > 0; | 
| bsw/jbe@1309 | 8558   }; | 
| bsw/jbe@1309 | 8559 })(wysihtml); | 
| bsw/jbe@1309 | 8560 | 
| bsw/jbe@1309 | 8561 /** | 
| bsw/jbe@1309 | 8562  * High performant way to check whether an element with a specific tag name is in the given document | 
| bsw/jbe@1309 | 8563  * Optimized for being heavily executed | 
| bsw/jbe@1309 | 8564  * Unleashes the power of live node lists | 
| bsw/jbe@1309 | 8565  * | 
| bsw/jbe@1309 | 8566  * @param {Object} doc The document object of the context where to check | 
| bsw/jbe@1309 | 8567  * @param {String} tagName Upper cased tag name | 
| bsw/jbe@1309 | 8568  * @example | 
| bsw/jbe@1309 | 8569  *    wysihtml.dom.hasElementWithTagName(document, "IMG"); | 
| bsw/jbe@1309 | 8570  */ | 
| bsw/jbe@1309 | 8571 wysihtml.dom.hasElementWithTagName = (function() { | 
| bsw/jbe@1309 | 8572   var LIVE_CACHE          = {}, | 
| bsw/jbe@1309 | 8573       DOCUMENT_IDENTIFIER = 1; | 
| bsw/jbe@1309 | 8574 | 
| bsw/jbe@1309 | 8575   function _getDocumentIdentifier(doc) { | 
| bsw/jbe@1309 | 8576     return doc._wysihtml_identifier || (doc._wysihtml_identifier = DOCUMENT_IDENTIFIER++); | 
| bsw/jbe@1309 | 8577   } | 
| bsw/jbe@1309 | 8578 | 
| bsw/jbe@1309 | 8579   return function(doc, tagName) { | 
| bsw/jbe@1309 | 8580     var key         = _getDocumentIdentifier(doc) + ":" + tagName, | 
| bsw/jbe@1309 | 8581         cacheEntry  = LIVE_CACHE[key]; | 
| bsw/jbe@1309 | 8582     if (!cacheEntry) { | 
| bsw/jbe@1309 | 8583       cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tagName); | 
| bsw/jbe@1309 | 8584     } | 
| bsw/jbe@1309 | 8585 | 
| bsw/jbe@1309 | 8586     return cacheEntry.length > 0; | 
| bsw/jbe@1309 | 8587   }; | 
| bsw/jbe@1309 | 8588 })(); | 
| bsw/jbe@1309 | 8589 | 
| bsw/jbe@1309 | 8590 wysihtml.dom.insert = function(elementToInsert) { | 
| bsw/jbe@1309 | 8591   return { | 
| bsw/jbe@1309 | 8592     after: function(element) { | 
| bsw/jbe@1309 | 8593       element.parentNode.insertBefore(elementToInsert, element.nextSibling); | 
| bsw/jbe@1309 | 8594     }, | 
| bsw/jbe@1309 | 8595 | 
| bsw/jbe@1309 | 8596     before: function(element) { | 
| bsw/jbe@1309 | 8597       element.parentNode.insertBefore(elementToInsert, element); | 
| bsw/jbe@1309 | 8598     }, | 
| bsw/jbe@1309 | 8599 | 
| bsw/jbe@1309 | 8600     into: function(element) { | 
| bsw/jbe@1309 | 8601       element.appendChild(elementToInsert); | 
| bsw/jbe@1309 | 8602     } | 
| bsw/jbe@1309 | 8603   }; | 
| bsw/jbe@1309 | 8604 }; | 
| bsw/jbe@1309 | 8605 | 
| bsw/jbe@1309 | 8606 wysihtml.dom.insertCSS = function(rules) { | 
| bsw/jbe@1309 | 8607   rules = rules.join("\n"); | 
| bsw/jbe@1309 | 8608 | 
| bsw/jbe@1309 | 8609   return { | 
| bsw/jbe@1309 | 8610     into: function(doc) { | 
| bsw/jbe@1309 | 8611       var styleElement = doc.createElement("style"); | 
| bsw/jbe@1309 | 8612       styleElement.type = "text/css"; | 
| bsw/jbe@1309 | 8613 | 
| bsw/jbe@1309 | 8614       if (styleElement.styleSheet) { | 
| bsw/jbe@1309 | 8615         styleElement.styleSheet.cssText = rules; | 
| bsw/jbe@1309 | 8616       } else { | 
| bsw/jbe@1309 | 8617         styleElement.appendChild(doc.createTextNode(rules)); | 
| bsw/jbe@1309 | 8618       } | 
| bsw/jbe@1309 | 8619 | 
| bsw/jbe@1309 | 8620       var link = doc.querySelector("head link"); | 
| bsw/jbe@1309 | 8621       if (link) { | 
| bsw/jbe@1309 | 8622         link.parentNode.insertBefore(styleElement, link); | 
| bsw/jbe@1309 | 8623         return; | 
| bsw/jbe@1309 | 8624       } else { | 
| bsw/jbe@1309 | 8625         var head = doc.querySelector("head"); | 
| bsw/jbe@1309 | 8626         if (head) { | 
| bsw/jbe@1309 | 8627           head.appendChild(styleElement); | 
| bsw/jbe@1309 | 8628         } | 
| bsw/jbe@1309 | 8629       } | 
| bsw/jbe@1309 | 8630     } | 
| bsw/jbe@1309 | 8631   }; | 
| bsw/jbe@1309 | 8632 }; | 
| bsw/jbe@1309 | 8633 | 
| bsw/jbe@1309 | 8634 /** | 
| bsw/jbe@1309 | 8635    * Check whether the given node is a proper loaded image | 
| bsw/jbe@1309 | 8636    * FIXME: Returns undefined when unknown (Chrome, Safari) | 
| bsw/jbe@1309 | 8637 */ | 
| bsw/jbe@1309 | 8638 | 
| bsw/jbe@1309 | 8639 wysihtml.dom.isLoadedImage = function (node) { | 
| bsw/jbe@1309 | 8640   try { | 
| bsw/jbe@1309 | 8641     return node.complete && !node.mozMatchesSelector(":-moz-broken"); | 
| bsw/jbe@1309 | 8642   } catch(e) { | 
| bsw/jbe@1309 | 8643     if (node.complete && node.readyState === "complete") { | 
| bsw/jbe@1309 | 8644       return true; | 
| bsw/jbe@1309 | 8645     } | 
| bsw/jbe@1309 | 8646   } | 
| bsw/jbe@1309 | 8647 }; | 
| bsw/jbe@1309 | 8648 | 
| bsw/jbe@1309 | 8649 // TODO: Refactor dom tree traversing here | 
| bsw/jbe@1309 | 8650 (function(wysihtml) { | 
| bsw/jbe@1309 | 8651   wysihtml.dom.lineBreaks = function(node) { | 
| bsw/jbe@1309 | 8652 | 
| bsw/jbe@1309 | 8653     function _isLineBreak(n) { | 
| bsw/jbe@1309 | 8654       return n.nodeName === "BR"; | 
| bsw/jbe@1309 | 8655     } | 
| bsw/jbe@1309 | 8656 | 
| bsw/jbe@1309 | 8657     /** | 
| bsw/jbe@1309 | 8658      * Checks whether the elment causes a visual line break | 
| bsw/jbe@1309 | 8659      * (<br> or block elements) | 
| bsw/jbe@1309 | 8660      */ | 
| bsw/jbe@1309 | 8661     function _isLineBreakOrBlockElement(element) { | 
| bsw/jbe@1309 | 8662       if (_isLineBreak(element)) { | 
| bsw/jbe@1309 | 8663         return true; | 
| bsw/jbe@1309 | 8664       } | 
| bsw/jbe@1309 | 8665 | 
| bsw/jbe@1309 | 8666       if (wysihtml.dom.getStyle("display").from(element) === "block") { | 
| bsw/jbe@1309 | 8667         return true; | 
| bsw/jbe@1309 | 8668       } | 
| bsw/jbe@1309 | 8669 | 
| bsw/jbe@1309 | 8670       return false; | 
| bsw/jbe@1309 | 8671     } | 
| bsw/jbe@1309 | 8672 | 
| bsw/jbe@1309 | 8673     return { | 
| bsw/jbe@1309 | 8674 | 
| bsw/jbe@1309 | 8675       /* wysihtml.dom.lineBreaks(element).add(); | 
| bsw/jbe@1309 | 8676        * | 
| bsw/jbe@1309 | 8677        * Adds line breaks before and after the given node if the previous and next siblings | 
| bsw/jbe@1309 | 8678        * aren't already causing a visual line break (block element or <br>) | 
| bsw/jbe@1309 | 8679        */ | 
| bsw/jbe@1309 | 8680       add: function(options) { | 
| bsw/jbe@1309 | 8681         var doc             = node.ownerDocument, | 
| bsw/jbe@1309 | 8682           nextSibling     = wysihtml.dom.domNode(node).next({ignoreBlankTexts: true}), | 
| bsw/jbe@1309 | 8683           previousSibling = wysihtml.dom.domNode(node).prev({ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 8684 | 
| bsw/jbe@1309 | 8685         if (nextSibling && !_isLineBreakOrBlockElement(nextSibling)) { | 
| bsw/jbe@1309 | 8686           wysihtml.dom.insert(doc.createElement("br")).after(node); | 
| bsw/jbe@1309 | 8687         } | 
| bsw/jbe@1309 | 8688         if (previousSibling && !_isLineBreakOrBlockElement(previousSibling)) { | 
| bsw/jbe@1309 | 8689           wysihtml.dom.insert(doc.createElement("br")).before(node); | 
| bsw/jbe@1309 | 8690         } | 
| bsw/jbe@1309 | 8691       }, | 
| bsw/jbe@1309 | 8692 | 
| bsw/jbe@1309 | 8693       /* wysihtml.dom.lineBreaks(element).remove(); | 
| bsw/jbe@1309 | 8694        * | 
| bsw/jbe@1309 | 8695        * Removes line breaks before and after the given node | 
| bsw/jbe@1309 | 8696        */ | 
| bsw/jbe@1309 | 8697       remove: function(options) { | 
| bsw/jbe@1309 | 8698         var nextSibling     = wysihtml.dom.domNode(node).next({ignoreBlankTexts: true}), | 
| bsw/jbe@1309 | 8699             previousSibling = wysihtml.dom.domNode(node).prev({ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 8700 | 
| bsw/jbe@1309 | 8701         if (nextSibling && _isLineBreak(nextSibling)) { | 
| bsw/jbe@1309 | 8702           nextSibling.parentNode.removeChild(nextSibling); | 
| bsw/jbe@1309 | 8703         } | 
| bsw/jbe@1309 | 8704         if (previousSibling && _isLineBreak(previousSibling)) { | 
| bsw/jbe@1309 | 8705           previousSibling.parentNode.removeChild(previousSibling); | 
| bsw/jbe@1309 | 8706         } | 
| bsw/jbe@1309 | 8707       } | 
| bsw/jbe@1309 | 8708     }; | 
| bsw/jbe@1309 | 8709   }; | 
| bsw/jbe@1309 | 8710 })(wysihtml); | 
| bsw/jbe@1309 | 8711 /** | 
| bsw/jbe@1309 | 8712  * Method to set dom events | 
| bsw/jbe@1309 | 8713  * | 
| bsw/jbe@1309 | 8714  * @example | 
| bsw/jbe@1309 | 8715  *    wysihtml.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... }); | 
| bsw/jbe@1309 | 8716  */ | 
| bsw/jbe@1309 | 8717 wysihtml.dom.observe = function(element, eventNames, handler) { | 
| bsw/jbe@1309 | 8718   eventNames = typeof(eventNames) === "string" ? [eventNames] : eventNames; | 
| bsw/jbe@1309 | 8719 | 
| bsw/jbe@1309 | 8720   var handlerWrapper, | 
| bsw/jbe@1309 | 8721       eventName, | 
| bsw/jbe@1309 | 8722       i       = 0, | 
| bsw/jbe@1309 | 8723       length  = eventNames.length; | 
| bsw/jbe@1309 | 8724 | 
| bsw/jbe@1309 | 8725   for (; i<length; i++) { | 
| bsw/jbe@1309 | 8726     eventName = eventNames[i]; | 
| bsw/jbe@1309 | 8727     if (element.addEventListener) { | 
| bsw/jbe@1309 | 8728       element.addEventListener(eventName, handler, false); | 
| bsw/jbe@1309 | 8729     } else { | 
| bsw/jbe@1309 | 8730       handlerWrapper = function(event) { | 
| bsw/jbe@1309 | 8731         if (!("target" in event)) { | 
| bsw/jbe@1309 | 8732           event.target = event.srcElement; | 
| bsw/jbe@1309 | 8733         } | 
| bsw/jbe@1309 | 8734         event.preventDefault = event.preventDefault || function() { | 
| bsw/jbe@1309 | 8735           this.returnValue = false; | 
| bsw/jbe@1309 | 8736         }; | 
| bsw/jbe@1309 | 8737         event.stopPropagation = event.stopPropagation || function() { | 
| bsw/jbe@1309 | 8738           this.cancelBubble = true; | 
| bsw/jbe@1309 | 8739         }; | 
| bsw/jbe@1309 | 8740         handler.call(element, event); | 
| bsw/jbe@1309 | 8741       }; | 
| bsw/jbe@1309 | 8742       element.attachEvent("on" + eventName, handlerWrapper); | 
| bsw/jbe@1309 | 8743     } | 
| bsw/jbe@1309 | 8744   } | 
| bsw/jbe@1309 | 8745 | 
| bsw/jbe@1309 | 8746   return { | 
| bsw/jbe@1309 | 8747     stop: function() { | 
| bsw/jbe@1309 | 8748       var eventName, | 
| bsw/jbe@1309 | 8749           i       = 0, | 
| bsw/jbe@1309 | 8750           length  = eventNames.length; | 
| bsw/jbe@1309 | 8751       for (; i<length; i++) { | 
| bsw/jbe@1309 | 8752         eventName = eventNames[i]; | 
| bsw/jbe@1309 | 8753         if (element.removeEventListener) { | 
| bsw/jbe@1309 | 8754           element.removeEventListener(eventName, handler, false); | 
| bsw/jbe@1309 | 8755         } else { | 
| bsw/jbe@1309 | 8756           element.detachEvent("on" + eventName, handlerWrapper); | 
| bsw/jbe@1309 | 8757         } | 
| bsw/jbe@1309 | 8758       } | 
| bsw/jbe@1309 | 8759     } | 
| bsw/jbe@1309 | 8760   }; | 
| bsw/jbe@1309 | 8761 }; | 
| bsw/jbe@1309 | 8762 | 
| bsw/jbe@1309 | 8763 /** | 
| bsw/jbe@1309 | 8764  * HTML Sanitizer | 
| bsw/jbe@1309 | 8765  * Rewrites the HTML based on given rules | 
| bsw/jbe@1309 | 8766  * | 
| bsw/jbe@1309 | 8767  * @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized | 
| bsw/jbe@1309 | 8768  * @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will | 
| bsw/jbe@1309 | 8769  *    be converted to a "span". Each rule is a key/value pair where key is the tag to convert, and value the | 
| bsw/jbe@1309 | 8770  *    desired substitution. | 
| bsw/jbe@1309 | 8771  * @param {Object} context Document object in which to parse the html, needed to sandbox the parsing | 
| bsw/jbe@1309 | 8772  * | 
| bsw/jbe@1309 | 8773  * @return {Element|String} Depends on the elementOrHtml parameter. When html then the sanitized html as string elsewise the element. | 
| bsw/jbe@1309 | 8774  * | 
| bsw/jbe@1309 | 8775  * @example | 
| bsw/jbe@1309 | 8776  *    var userHTML = '<div id="foo" onclick="alert(1);"><p><font color="red">foo</font><script>alert(1);</script></p></div>'; | 
| bsw/jbe@1309 | 8777  *    wysihtml.dom.parse(userHTML, { | 
| bsw/jbe@1309 | 8778  *      tags { | 
| bsw/jbe@1309 | 8779  *        p:      "div",      // Rename p tags to div tags | 
| bsw/jbe@1309 | 8780  *        font:   "span"      // Rename font tags to span tags | 
| bsw/jbe@1309 | 8781  *        div:    true,       // Keep them, also possible (same result when passing: "div" or true) | 
| bsw/jbe@1309 | 8782  *        script: undefined   // Remove script elements | 
| bsw/jbe@1309 | 8783  *      } | 
| bsw/jbe@1309 | 8784  *    }); | 
| bsw/jbe@1309 | 8785  *    // => <div><div><span>foo bar</span></div></div> | 
| bsw/jbe@1309 | 8786  * | 
| bsw/jbe@1309 | 8787  *    var userHTML = '<table><tbody><tr><td>I'm a table!</td></tr></tbody></table>'; | 
| bsw/jbe@1309 | 8788  *    wysihtml.dom.parse(userHTML); | 
| bsw/jbe@1309 | 8789  *    // => '<span><span><span><span>I'm a table!</span></span></span></span>' | 
| bsw/jbe@1309 | 8790  * | 
| bsw/jbe@1309 | 8791  *    var userHTML = '<div>foobar<br>foobar</div>'; | 
| bsw/jbe@1309 | 8792  *    wysihtml.dom.parse(userHTML, { | 
| bsw/jbe@1309 | 8793  *      tags: { | 
| bsw/jbe@1309 | 8794  *        div: undefined, | 
| bsw/jbe@1309 | 8795  *        br:  true | 
| bsw/jbe@1309 | 8796  *      } | 
| bsw/jbe@1309 | 8797  *    }); | 
| bsw/jbe@1309 | 8798  *    // => '' | 
| bsw/jbe@1309 | 8799  * | 
| bsw/jbe@1309 | 8800  *    var userHTML = '<div class="red">foo</div><div class="pink">bar</div>'; | 
| bsw/jbe@1309 | 8801  *    wysihtml.dom.parse(userHTML, { | 
| bsw/jbe@1309 | 8802  *      classes: { | 
| bsw/jbe@1309 | 8803  *        red:    1, | 
| bsw/jbe@1309 | 8804  *        green:  1 | 
| bsw/jbe@1309 | 8805  *      }, | 
| bsw/jbe@1309 | 8806  *      tags: { | 
| bsw/jbe@1309 | 8807  *        div: { | 
| bsw/jbe@1309 | 8808  *          rename_tag:     "p" | 
| bsw/jbe@1309 | 8809  *        } | 
| bsw/jbe@1309 | 8810  *      } | 
| bsw/jbe@1309 | 8811  *    }); | 
| bsw/jbe@1309 | 8812  *    // => '<p class="red">foo</p><p>bar</p>' | 
| bsw/jbe@1309 | 8813  */ | 
| bsw/jbe@1309 | 8814 | 
| bsw/jbe@1309 | 8815 wysihtml.dom.parse = function(elementOrHtml_current, config_current) { | 
| bsw/jbe@1309 | 8816   /* TODO: Currently escaped module pattern as otherwise folloowing default swill be shared among multiple editors. | 
| bsw/jbe@1309 | 8817    * Refactor whole code as this method while workind is kind of awkward too */ | 
| bsw/jbe@1309 | 8818 | 
| bsw/jbe@1309 | 8819   /** | 
| bsw/jbe@1309 | 8820    * It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML | 
| bsw/jbe@1309 | 8821    * new DOMParser().parseFromString('<img src="foo.gif">') will cause a parseError since the | 
| bsw/jbe@1309 | 8822    * node isn't closed | 
| bsw/jbe@1309 | 8823    * | 
| bsw/jbe@1309 | 8824    * Therefore we've to use the browser's ordinary HTML parser invoked by setting innerHTML. | 
| bsw/jbe@1309 | 8825    */ | 
| bsw/jbe@1309 | 8826   var NODE_TYPE_MAPPING = { | 
| bsw/jbe@1309 | 8827         "1": _handleElement, | 
| bsw/jbe@1309 | 8828         "3": _handleText, | 
| bsw/jbe@1309 | 8829         "8": _handleComment | 
| bsw/jbe@1309 | 8830       }, | 
| bsw/jbe@1309 | 8831       // Rename unknown tags to this | 
| bsw/jbe@1309 | 8832       DEFAULT_NODE_NAME   = "span", | 
| bsw/jbe@1309 | 8833       WHITE_SPACE_REG_EXP = /\s+/, | 
| bsw/jbe@1309 | 8834       defaultRules        = { tags: {}, classes: {} }, | 
| bsw/jbe@1309 | 8835       currentRules        = {}, | 
| bsw/jbe@1309 | 8836       blockElements       = ["ADDRESS" ,"BLOCKQUOTE" ,"CENTER" ,"DIR" ,"DIV" ,"DL" ,"FIELDSET" , | 
| bsw/jbe@1309 | 8837                              "FORM", "H1" ,"H2" ,"H3" ,"H4" ,"H5" ,"H6" ,"ISINDEX" ,"MENU", | 
| bsw/jbe@1309 | 8838                              "NOFRAMES", "NOSCRIPT" ,"OL" ,"P" ,"PRE","TABLE", "UL"]; | 
| bsw/jbe@1309 | 8839 | 
| bsw/jbe@1309 | 8840   /** | 
| bsw/jbe@1309 | 8841    * Iterates over all childs of the element, recreates them, appends them into a document fragment | 
| bsw/jbe@1309 | 8842    * which later replaces the entire body content | 
| bsw/jbe@1309 | 8843    */ | 
| bsw/jbe@1309 | 8844    function parse(elementOrHtml, config) { | 
| bsw/jbe@1309 | 8845     wysihtml.lang.object(currentRules).merge(defaultRules).merge(config.rules).get(); | 
| bsw/jbe@1309 | 8846 | 
| bsw/jbe@1309 | 8847     var context       = config.context || elementOrHtml.ownerDocument || document, | 
| bsw/jbe@1309 | 8848         fragment      = context.createDocumentFragment(), | 
| bsw/jbe@1309 | 8849         isString      = typeof(elementOrHtml) === "string", | 
| bsw/jbe@1309 | 8850         clearInternals = false, | 
| bsw/jbe@1309 | 8851         element, | 
| bsw/jbe@1309 | 8852         newNode, | 
| bsw/jbe@1309 | 8853         firstChild; | 
| bsw/jbe@1309 | 8854 | 
| bsw/jbe@1309 | 8855     if (config.clearInternals === true) { | 
| bsw/jbe@1309 | 8856       clearInternals = true; | 
| bsw/jbe@1309 | 8857     } | 
| bsw/jbe@1309 | 8858 | 
| bsw/jbe@1309 | 8859     if (isString) { | 
| bsw/jbe@1309 | 8860       element = wysihtml.dom.getAsDom(elementOrHtml, context); | 
| bsw/jbe@1309 | 8861     } else { | 
| bsw/jbe@1309 | 8862       element = elementOrHtml; | 
| bsw/jbe@1309 | 8863     } | 
| bsw/jbe@1309 | 8864 | 
| bsw/jbe@1309 | 8865     if (currentRules.selectors) { | 
| bsw/jbe@1309 | 8866       _applySelectorRules(element, currentRules.selectors); | 
| bsw/jbe@1309 | 8867     } | 
| bsw/jbe@1309 | 8868 | 
| bsw/jbe@1309 | 8869     while (element.firstChild) { | 
| bsw/jbe@1309 | 8870       firstChild = element.firstChild; | 
| bsw/jbe@1309 | 8871       newNode = _convert(firstChild, config.cleanUp, clearInternals, config.uneditableClass); | 
| bsw/jbe@1309 | 8872       if (newNode) { | 
| bsw/jbe@1309 | 8873         fragment.appendChild(newNode); | 
| bsw/jbe@1309 | 8874       } | 
| bsw/jbe@1309 | 8875       if (firstChild !== newNode) { | 
| bsw/jbe@1309 | 8876         element.removeChild(firstChild); | 
| bsw/jbe@1309 | 8877       } | 
| bsw/jbe@1309 | 8878     } | 
| bsw/jbe@1309 | 8879 | 
| bsw/jbe@1309 | 8880     if (config.unjoinNbsps) { | 
| bsw/jbe@1309 | 8881       // replace joined non-breakable spaces with unjoined | 
| bsw/jbe@1309 | 8882       var txtnodes = wysihtml.dom.getTextNodes(fragment); | 
| bsw/jbe@1309 | 8883       for (var n = txtnodes.length; n--;) { | 
| bsw/jbe@1309 | 8884         txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 "); | 
| bsw/jbe@1309 | 8885       } | 
| bsw/jbe@1309 | 8886     } | 
| bsw/jbe@1309 | 8887 | 
| bsw/jbe@1309 | 8888     // Clear element contents | 
| bsw/jbe@1309 | 8889     element.innerHTML = ""; | 
| bsw/jbe@1309 | 8890 | 
| bsw/jbe@1309 | 8891     // Insert new DOM tree | 
| bsw/jbe@1309 | 8892     element.appendChild(fragment); | 
| bsw/jbe@1309 | 8893 | 
| bsw/jbe@1309 | 8894     return isString ? wysihtml.quirks.getCorrectInnerHTML(element) : element; | 
| bsw/jbe@1309 | 8895   } | 
| bsw/jbe@1309 | 8896 | 
| bsw/jbe@1309 | 8897   function _convert(oldNode, cleanUp, clearInternals, uneditableClass) { | 
| bsw/jbe@1309 | 8898     var oldNodeType     = oldNode.nodeType, | 
| bsw/jbe@1309 | 8899         oldChilds       = oldNode.childNodes, | 
| bsw/jbe@1309 | 8900         oldChildsLength = oldChilds.length, | 
| bsw/jbe@1309 | 8901         method          = NODE_TYPE_MAPPING[oldNodeType], | 
| bsw/jbe@1309 | 8902         i               = 0, | 
| bsw/jbe@1309 | 8903         fragment, | 
| bsw/jbe@1309 | 8904         newNode, | 
| bsw/jbe@1309 | 8905         newChild, | 
| bsw/jbe@1309 | 8906         nodeDisplay; | 
| bsw/jbe@1309 | 8907 | 
| bsw/jbe@1309 | 8908     // Passes directly elemets with uneditable class | 
| bsw/jbe@1309 | 8909     if (uneditableClass && oldNodeType === 1 && wysihtml.dom.hasClass(oldNode, uneditableClass)) { | 
| bsw/jbe@1309 | 8910         return oldNode; | 
| bsw/jbe@1309 | 8911     } | 
| bsw/jbe@1309 | 8912 | 
| bsw/jbe@1309 | 8913     newNode = method && method(oldNode, clearInternals); | 
| bsw/jbe@1309 | 8914 | 
| bsw/jbe@1309 | 8915     // Remove or unwrap node in case of return value null or false | 
| bsw/jbe@1309 | 8916     if (!newNode) { | 
| bsw/jbe@1309 | 8917         if (newNode === false) { | 
| bsw/jbe@1309 | 8918             // false defines that tag should be removed but contents should remain (unwrap) | 
| bsw/jbe@1309 | 8919             fragment = oldNode.ownerDocument.createDocumentFragment(); | 
| bsw/jbe@1309 | 8920 | 
| bsw/jbe@1309 | 8921             for (i = oldChildsLength; i--;) { | 
| bsw/jbe@1309 | 8922               if (oldChilds[i]) { | 
| bsw/jbe@1309 | 8923                 newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass); | 
| bsw/jbe@1309 | 8924                 if (newChild) { | 
| bsw/jbe@1309 | 8925                   if (oldChilds[i] === newChild) { | 
| bsw/jbe@1309 | 8926                     i--; | 
| bsw/jbe@1309 | 8927                   } | 
| bsw/jbe@1309 | 8928                   fragment.insertBefore(newChild, fragment.firstChild); | 
| bsw/jbe@1309 | 8929                 } | 
| bsw/jbe@1309 | 8930               } | 
| bsw/jbe@1309 | 8931             } | 
| bsw/jbe@1309 | 8932 | 
| bsw/jbe@1309 | 8933             nodeDisplay = wysihtml.dom.getStyle("display").from(oldNode); | 
| bsw/jbe@1309 | 8934 | 
| bsw/jbe@1309 | 8935             if (nodeDisplay === '') { | 
| bsw/jbe@1309 | 8936               // Handle display style when element not in dom | 
| bsw/jbe@1309 | 8937               nodeDisplay = wysihtml.lang.array(blockElements).contains(oldNode.tagName) ? "block" : ""; | 
| bsw/jbe@1309 | 8938             } | 
| bsw/jbe@1309 | 8939             if (wysihtml.lang.array(["block", "flex", "table"]).contains(nodeDisplay)) { | 
| bsw/jbe@1309 | 8940               fragment.appendChild(oldNode.ownerDocument.createElement("br")); | 
| bsw/jbe@1309 | 8941             } | 
| bsw/jbe@1309 | 8942 | 
| bsw/jbe@1309 | 8943             // TODO: try to minimize surplus spaces | 
| bsw/jbe@1309 | 8944             if (wysihtml.lang.array([ | 
| bsw/jbe@1309 | 8945                 "div", "pre", "p", | 
| bsw/jbe@1309 | 8946                 "table", "td", "th", | 
| bsw/jbe@1309 | 8947                 "ul", "ol", "li", | 
| bsw/jbe@1309 | 8948                 "dd", "dl", | 
| bsw/jbe@1309 | 8949                 "footer", "header", "section", | 
| bsw/jbe@1309 | 8950                 "h1", "h2", "h3", "h4", "h5", "h6" | 
| bsw/jbe@1309 | 8951             ]).contains(oldNode.nodeName.toLowerCase()) && oldNode.parentNode.lastChild !== oldNode) { | 
| bsw/jbe@1309 | 8952                 // add space at first when unwraping non-textflow elements | 
| bsw/jbe@1309 | 8953                 if (!oldNode.nextSibling || oldNode.nextSibling.nodeType !== 3 || !(/^\s/).test(oldNode.nextSibling.nodeValue)) { | 
| bsw/jbe@1309 | 8954                   fragment.appendChild(oldNode.ownerDocument.createTextNode(" ")); | 
| bsw/jbe@1309 | 8955                 } | 
| bsw/jbe@1309 | 8956             } | 
| bsw/jbe@1309 | 8957 | 
| bsw/jbe@1309 | 8958             if (fragment.normalize) { | 
| bsw/jbe@1309 | 8959               fragment.normalize(); | 
| bsw/jbe@1309 | 8960             } | 
| bsw/jbe@1309 | 8961             return fragment; | 
| bsw/jbe@1309 | 8962         } else { | 
| bsw/jbe@1309 | 8963           // Remove | 
| bsw/jbe@1309 | 8964           return null; | 
| bsw/jbe@1309 | 8965         } | 
| bsw/jbe@1309 | 8966     } | 
| bsw/jbe@1309 | 8967 | 
| bsw/jbe@1309 | 8968     // Converts all childnodes | 
| bsw/jbe@1309 | 8969     for (i=0; i<oldChildsLength; i++) { | 
| bsw/jbe@1309 | 8970       if (oldChilds[i]) { | 
| bsw/jbe@1309 | 8971         newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass); | 
| bsw/jbe@1309 | 8972         if (newChild) { | 
| bsw/jbe@1309 | 8973           if (oldChilds[i] === newChild) { | 
| bsw/jbe@1309 | 8974             i--; | 
| bsw/jbe@1309 | 8975           } | 
| bsw/jbe@1309 | 8976           newNode.appendChild(newChild); | 
| bsw/jbe@1309 | 8977         } | 
| bsw/jbe@1309 | 8978       } | 
| bsw/jbe@1309 | 8979     } | 
| bsw/jbe@1309 | 8980 | 
| bsw/jbe@1309 | 8981     // Cleanup senseless <span> elements | 
| bsw/jbe@1309 | 8982     if (cleanUp && | 
| bsw/jbe@1309 | 8983         newNode.nodeName.toLowerCase() === DEFAULT_NODE_NAME && | 
| bsw/jbe@1309 | 8984         (!newNode.childNodes.length || | 
| bsw/jbe@1309 | 8985          ((/^\s*$/gi).test(newNode.innerHTML) && (clearInternals || (oldNode.className !== "_wysihtml-temp-placeholder" && oldNode.className !== "rangySelectionBoundary"))) || | 
| bsw/jbe@1309 | 8986          !newNode.attributes.length) | 
| bsw/jbe@1309 | 8987         ) { | 
| bsw/jbe@1309 | 8988       fragment = newNode.ownerDocument.createDocumentFragment(); | 
| bsw/jbe@1309 | 8989       while (newNode.firstChild) { | 
| bsw/jbe@1309 | 8990         fragment.appendChild(newNode.firstChild); | 
| bsw/jbe@1309 | 8991       } | 
| bsw/jbe@1309 | 8992       if (fragment.normalize) { | 
| bsw/jbe@1309 | 8993         fragment.normalize(); | 
| bsw/jbe@1309 | 8994       } | 
| bsw/jbe@1309 | 8995       return fragment; | 
| bsw/jbe@1309 | 8996     } | 
| bsw/jbe@1309 | 8997 | 
| bsw/jbe@1309 | 8998     if (newNode.normalize) { | 
| bsw/jbe@1309 | 8999       newNode.normalize(); | 
| bsw/jbe@1309 | 9000     } | 
| bsw/jbe@1309 | 9001     return newNode; | 
| bsw/jbe@1309 | 9002   } | 
| bsw/jbe@1309 | 9003 | 
| bsw/jbe@1309 | 9004   function _applySelectorRules (element, selectorRules) { | 
| bsw/jbe@1309 | 9005     var sel, method, els; | 
| bsw/jbe@1309 | 9006 | 
| bsw/jbe@1309 | 9007     for (sel in selectorRules) { | 
| bsw/jbe@1309 | 9008       if (selectorRules.hasOwnProperty(sel)) { | 
| bsw/jbe@1309 | 9009         if (wysihtml.lang.object(selectorRules[sel]).isFunction()) { | 
| bsw/jbe@1309 | 9010           method = selectorRules[sel]; | 
| bsw/jbe@1309 | 9011         } else if (typeof(selectorRules[sel]) === "string" && elementHandlingMethods[selectorRules[sel]]) { | 
| bsw/jbe@1309 | 9012           method = elementHandlingMethods[selectorRules[sel]]; | 
| bsw/jbe@1309 | 9013         } | 
| bsw/jbe@1309 | 9014         els = element.querySelectorAll(sel); | 
| bsw/jbe@1309 | 9015         for (var i = els.length; i--;) { | 
| bsw/jbe@1309 | 9016           method(els[i]); | 
| bsw/jbe@1309 | 9017         } | 
| bsw/jbe@1309 | 9018       } | 
| bsw/jbe@1309 | 9019     } | 
| bsw/jbe@1309 | 9020   } | 
| bsw/jbe@1309 | 9021 | 
| bsw/jbe@1309 | 9022   function _handleElement(oldNode, clearInternals) { | 
| bsw/jbe@1309 | 9023     var rule, | 
| bsw/jbe@1309 | 9024         newNode, | 
| bsw/jbe@1309 | 9025         tagRules    = currentRules.tags, | 
| bsw/jbe@1309 | 9026         nodeName    = oldNode.nodeName.toLowerCase(), | 
| bsw/jbe@1309 | 9027         scopeName   = oldNode.scopeName, | 
| bsw/jbe@1309 | 9028         renameTag; | 
| bsw/jbe@1309 | 9029 | 
| bsw/jbe@1309 | 9030     /** | 
| bsw/jbe@1309 | 9031      * We already parsed that element | 
| bsw/jbe@1309 | 9032      * ignore it! (yes, this sometimes happens in IE8 when the html is invalid) | 
| bsw/jbe@1309 | 9033      */ | 
| bsw/jbe@1309 | 9034     if (oldNode._wysihtml) { | 
| bsw/jbe@1309 | 9035       return null; | 
| bsw/jbe@1309 | 9036     } | 
| bsw/jbe@1309 | 9037     oldNode._wysihtml = 1; | 
| bsw/jbe@1309 | 9038 | 
| bsw/jbe@1309 | 9039     if (oldNode.className === "wysihtml-temp") { | 
| bsw/jbe@1309 | 9040       return null; | 
| bsw/jbe@1309 | 9041     } | 
| bsw/jbe@1309 | 9042 | 
| bsw/jbe@1309 | 9043     /** | 
| bsw/jbe@1309 | 9044      * IE is the only browser who doesn't include the namespace in the | 
| bsw/jbe@1309 | 9045      * nodeName, that's why we have to prepend it by ourselves | 
| bsw/jbe@1309 | 9046      * scopeName is a proprietary IE feature | 
| bsw/jbe@1309 | 9047      * read more here http://msdn.microsoft.com/en-us/library/ms534388(v=vs.85).aspx | 
| bsw/jbe@1309 | 9048      */ | 
| bsw/jbe@1309 | 9049     if (scopeName && scopeName != "HTML") { | 
| bsw/jbe@1309 | 9050       nodeName = scopeName + ":" + nodeName; | 
| bsw/jbe@1309 | 9051     } | 
| bsw/jbe@1309 | 9052     /** | 
| bsw/jbe@1309 | 9053      * Repair node | 
| bsw/jbe@1309 | 9054      * IE is a bit bitchy when it comes to invalid nested markup which includes unclosed tags | 
| bsw/jbe@1309 | 9055      * A <p> doesn't need to be closed according HTML4-5 spec, we simply replace it with a <div> to preserve its content and layout | 
| bsw/jbe@1309 | 9056      */ | 
| bsw/jbe@1309 | 9057     if ("outerHTML" in oldNode) { | 
| bsw/jbe@1309 | 9058       if (!wysihtml.browser.autoClosesUnclosedTags() && | 
| bsw/jbe@1309 | 9059           oldNode.nodeName === "P" && | 
| bsw/jbe@1309 | 9060           oldNode.outerHTML.slice(-4).toLowerCase() !== "</p>") { | 
| bsw/jbe@1309 | 9061         nodeName = "div"; | 
| bsw/jbe@1309 | 9062       } | 
| bsw/jbe@1309 | 9063     } | 
| bsw/jbe@1309 | 9064 | 
| bsw/jbe@1309 | 9065     if (nodeName in tagRules) { | 
| bsw/jbe@1309 | 9066       rule = tagRules[nodeName]; | 
| bsw/jbe@1309 | 9067       if (!rule || rule.remove) { | 
| bsw/jbe@1309 | 9068         return null; | 
| bsw/jbe@1309 | 9069       } else if (rule.unwrap) { | 
| bsw/jbe@1309 | 9070         return false; | 
| bsw/jbe@1309 | 9071       } | 
| bsw/jbe@1309 | 9072       rule = typeof(rule) === "string" ? { rename_tag: rule } : rule; | 
| bsw/jbe@1309 | 9073     } else if (oldNode.firstChild) { | 
| bsw/jbe@1309 | 9074       rule = { rename_tag: DEFAULT_NODE_NAME }; | 
| bsw/jbe@1309 | 9075     } else { | 
| bsw/jbe@1309 | 9076       // Remove empty unknown elements | 
| bsw/jbe@1309 | 9077       return null; | 
| bsw/jbe@1309 | 9078     } | 
| bsw/jbe@1309 | 9079 | 
| bsw/jbe@1309 | 9080     // tests if type condition is met or node should be removed/unwrapped/renamed | 
| bsw/jbe@1309 | 9081     if (rule.one_of_type && !_testTypes(oldNode, currentRules, rule.one_of_type, clearInternals)) { | 
| bsw/jbe@1309 | 9082       if (rule.remove_action) { | 
| bsw/jbe@1309 | 9083         if (rule.remove_action === "unwrap") { | 
| bsw/jbe@1309 | 9084           return false; | 
| bsw/jbe@1309 | 9085         } else if (rule.remove_action === "rename") { | 
| bsw/jbe@1309 | 9086           renameTag = rule.remove_action_rename_to || DEFAULT_NODE_NAME; | 
| bsw/jbe@1309 | 9087         } else { | 
| bsw/jbe@1309 | 9088           return null; | 
| bsw/jbe@1309 | 9089         } | 
| bsw/jbe@1309 | 9090       } else { | 
| bsw/jbe@1309 | 9091         return null; | 
| bsw/jbe@1309 | 9092       } | 
| bsw/jbe@1309 | 9093     } | 
| bsw/jbe@1309 | 9094 | 
| bsw/jbe@1309 | 9095     newNode = oldNode.ownerDocument.createElement(renameTag || rule.rename_tag || nodeName); | 
| bsw/jbe@1309 | 9096     _handleAttributes(oldNode, newNode, rule, clearInternals); | 
| bsw/jbe@1309 | 9097     _handleStyles(oldNode, newNode, rule); | 
| bsw/jbe@1309 | 9098 | 
| bsw/jbe@1309 | 9099     oldNode = null; | 
| bsw/jbe@1309 | 9100 | 
| bsw/jbe@1309 | 9101     if (newNode.normalize) { newNode.normalize(); } | 
| bsw/jbe@1309 | 9102     return newNode; | 
| bsw/jbe@1309 | 9103   } | 
| bsw/jbe@1309 | 9104 | 
| bsw/jbe@1309 | 9105   function _testTypes(oldNode, rules, types, clearInternals) { | 
| bsw/jbe@1309 | 9106     var definition, type; | 
| bsw/jbe@1309 | 9107 | 
| bsw/jbe@1309 | 9108     // do not interfere with placeholder span or pasting caret position is not maintained | 
| bsw/jbe@1309 | 9109     if (oldNode.nodeName === "SPAN" && !clearInternals && (oldNode.className === "_wysihtml-temp-placeholder" || oldNode.className === "rangySelectionBoundary")) { | 
| bsw/jbe@1309 | 9110       return true; | 
| bsw/jbe@1309 | 9111     } | 
| bsw/jbe@1309 | 9112 | 
| bsw/jbe@1309 | 9113     for (type in types) { | 
| bsw/jbe@1309 | 9114       if (types.hasOwnProperty(type) && rules.type_definitions && rules.type_definitions[type]) { | 
| bsw/jbe@1309 | 9115         definition = rules.type_definitions[type]; | 
| bsw/jbe@1309 | 9116         if (_testType(oldNode, definition)) { | 
| bsw/jbe@1309 | 9117           return true; | 
| bsw/jbe@1309 | 9118         } | 
| bsw/jbe@1309 | 9119       } | 
| bsw/jbe@1309 | 9120     } | 
| bsw/jbe@1309 | 9121     return false; | 
| bsw/jbe@1309 | 9122   } | 
| bsw/jbe@1309 | 9123 | 
| bsw/jbe@1309 | 9124   function array_contains(a, obj) { | 
| bsw/jbe@1309 | 9125       var i = a.length; | 
| bsw/jbe@1309 | 9126       while (i--) { | 
| bsw/jbe@1309 | 9127          if (a[i] === obj) { | 
| bsw/jbe@1309 | 9128              return true; | 
| bsw/jbe@1309 | 9129          } | 
| bsw/jbe@1309 | 9130       } | 
| bsw/jbe@1309 | 9131       return false; | 
| bsw/jbe@1309 | 9132   } | 
| bsw/jbe@1309 | 9133 | 
| bsw/jbe@1309 | 9134   function _testType(oldNode, definition) { | 
| bsw/jbe@1309 | 9135 | 
| bsw/jbe@1309 | 9136     var nodeClasses = oldNode.getAttribute("class"), | 
| bsw/jbe@1309 | 9137         nodeStyles =  oldNode.getAttribute("style"), | 
| bsw/jbe@1309 | 9138         classesLength, s, s_corrected, a, attr, currentClass, styleProp; | 
| bsw/jbe@1309 | 9139 | 
| bsw/jbe@1309 | 9140     // test for methods | 
| bsw/jbe@1309 | 9141     if (definition.methods) { | 
| bsw/jbe@1309 | 9142       for (var m in definition.methods) { | 
| bsw/jbe@1309 | 9143         if (definition.methods.hasOwnProperty(m) && typeCeckMethods[m]) { | 
| bsw/jbe@1309 | 9144 | 
| bsw/jbe@1309 | 9145           if (typeCeckMethods[m](oldNode)) { | 
| bsw/jbe@1309 | 9146             return true; | 
| bsw/jbe@1309 | 9147           } | 
| bsw/jbe@1309 | 9148         } | 
| bsw/jbe@1309 | 9149       } | 
| bsw/jbe@1309 | 9150     } | 
| bsw/jbe@1309 | 9151 | 
| bsw/jbe@1309 | 9152     // test for classes, if one found return true | 
| bsw/jbe@1309 | 9153     if (nodeClasses && definition.classes) { | 
| bsw/jbe@1309 | 9154       nodeClasses = nodeClasses.replace(/^\s+/g, '').replace(/\s+$/g, '').split(WHITE_SPACE_REG_EXP); | 
| bsw/jbe@1309 | 9155       classesLength = nodeClasses.length; | 
| bsw/jbe@1309 | 9156       for (var i = 0; i < classesLength; i++) { | 
| bsw/jbe@1309 | 9157         if (definition.classes[nodeClasses[i]]) { | 
| bsw/jbe@1309 | 9158           return true; | 
| bsw/jbe@1309 | 9159         } | 
| bsw/jbe@1309 | 9160       } | 
| bsw/jbe@1309 | 9161     } | 
| bsw/jbe@1309 | 9162 | 
| bsw/jbe@1309 | 9163     // test for styles, if one found return true | 
| bsw/jbe@1309 | 9164     if (nodeStyles && definition.styles) { | 
| bsw/jbe@1309 | 9165 | 
| bsw/jbe@1309 | 9166       nodeStyles = nodeStyles.split(';'); | 
| bsw/jbe@1309 | 9167       for (s in definition.styles) { | 
| bsw/jbe@1309 | 9168         if (definition.styles.hasOwnProperty(s)) { | 
| bsw/jbe@1309 | 9169           for (var sp = nodeStyles.length; sp--;) { | 
| bsw/jbe@1309 | 9170             styleProp = nodeStyles[sp].split(':'); | 
| bsw/jbe@1309 | 9171 | 
| bsw/jbe@1309 | 9172             if (styleProp[0].replace(/\s/g, '').toLowerCase() === s) { | 
| bsw/jbe@1309 | 9173               if (definition.styles[s] === true || definition.styles[s] === 1 || wysihtml.lang.array(definition.styles[s]).contains(styleProp[1].replace(/\s/g, '').toLowerCase()) ) { | 
| bsw/jbe@1309 | 9174                 return true; | 
| bsw/jbe@1309 | 9175               } | 
| bsw/jbe@1309 | 9176             } | 
| bsw/jbe@1309 | 9177           } | 
| bsw/jbe@1309 | 9178         } | 
| bsw/jbe@1309 | 9179       } | 
| bsw/jbe@1309 | 9180     } | 
| bsw/jbe@1309 | 9181 | 
| bsw/jbe@1309 | 9182     // test for attributes in general against regex match | 
| bsw/jbe@1309 | 9183     if (definition.attrs) { | 
| bsw/jbe@1309 | 9184         for (a in definition.attrs) { | 
| bsw/jbe@1309 | 9185             if (definition.attrs.hasOwnProperty(a)) { | 
| bsw/jbe@1309 | 9186                 attr = wysihtml.dom.getAttribute(oldNode, a); | 
| bsw/jbe@1309 | 9187                 if (typeof(attr) === "string") { | 
| bsw/jbe@1309 | 9188                     if (attr.search(definition.attrs[a]) > -1) { | 
| bsw/jbe@1309 | 9189                         return true; | 
| bsw/jbe@1309 | 9190                     } | 
| bsw/jbe@1309 | 9191                 } | 
| bsw/jbe@1309 | 9192             } | 
| bsw/jbe@1309 | 9193         } | 
| bsw/jbe@1309 | 9194     } | 
| bsw/jbe@1309 | 9195     return false; | 
| bsw/jbe@1309 | 9196   } | 
| bsw/jbe@1309 | 9197 | 
| bsw/jbe@1309 | 9198   function _handleStyles(oldNode, newNode, rule) { | 
| bsw/jbe@1309 | 9199     var s, v; | 
| bsw/jbe@1309 | 9200     if(rule && rule.keep_styles) { | 
| bsw/jbe@1309 | 9201       for (s in rule.keep_styles) { | 
| bsw/jbe@1309 | 9202         if (rule.keep_styles.hasOwnProperty(s)) { | 
| bsw/jbe@1309 | 9203           v = (s === "float") ? oldNode.style.styleFloat || oldNode.style.cssFloat : oldNode.style[s]; | 
| bsw/jbe@1309 | 9204           // value can be regex and if so should match or style skipped | 
| bsw/jbe@1309 | 9205           if (rule.keep_styles[s] instanceof RegExp && !(rule.keep_styles[s].test(v))) { | 
| bsw/jbe@1309 | 9206             continue; | 
| bsw/jbe@1309 | 9207           } | 
| bsw/jbe@1309 | 9208           if (s === "float") { | 
| bsw/jbe@1309 | 9209             // IE compability | 
| bsw/jbe@1309 | 9210             newNode.style[(oldNode.style.styleFloat) ? 'styleFloat': 'cssFloat'] = v; | 
| bsw/jbe@1309 | 9211            } else if (oldNode.style[s]) { | 
| bsw/jbe@1309 | 9212              newNode.style[s] = v; | 
| bsw/jbe@1309 | 9213            } | 
| bsw/jbe@1309 | 9214         } | 
| bsw/jbe@1309 | 9215       } | 
| bsw/jbe@1309 | 9216     } | 
| bsw/jbe@1309 | 9217   }; | 
| bsw/jbe@1309 | 9218 | 
| bsw/jbe@1309 | 9219   function _getAttributesBeginningWith(beginning, attributes) { | 
| bsw/jbe@1309 | 9220     var returnAttributes = []; | 
| bsw/jbe@1309 | 9221     for (var attr in attributes) { | 
| bsw/jbe@1309 | 9222       if (attributes.hasOwnProperty(attr) && attr.indexOf(beginning) === 0) { | 
| bsw/jbe@1309 | 9223         returnAttributes.push(attr); | 
| bsw/jbe@1309 | 9224       } | 
| bsw/jbe@1309 | 9225     } | 
| bsw/jbe@1309 | 9226     return returnAttributes; | 
| bsw/jbe@1309 | 9227   } | 
| bsw/jbe@1309 | 9228 | 
| bsw/jbe@1309 | 9229   function _checkAttribute(attributeName, attributeValue, methodName, nodeName) { | 
| bsw/jbe@1309 | 9230     var method = wysihtml.lang.object(methodName).isFunction() ? methodName : attributeCheckMethods[methodName], | 
| bsw/jbe@1309 | 9231         newAttributeValue; | 
| bsw/jbe@1309 | 9232 | 
| bsw/jbe@1309 | 9233     if (method) { | 
| bsw/jbe@1309 | 9234       newAttributeValue = method(attributeValue, nodeName); | 
| bsw/jbe@1309 | 9235       if (typeof(newAttributeValue) === "string") { | 
| bsw/jbe@1309 | 9236         return newAttributeValue; | 
| bsw/jbe@1309 | 9237       } | 
| bsw/jbe@1309 | 9238     } | 
| bsw/jbe@1309 | 9239 | 
| bsw/jbe@1309 | 9240     return false; | 
| bsw/jbe@1309 | 9241   } | 
| bsw/jbe@1309 | 9242 | 
| bsw/jbe@1309 | 9243   function _checkAttributes(oldNode, local_attributes) { | 
| bsw/jbe@1309 | 9244     var globalAttributes  = wysihtml.lang.object(currentRules.attributes || {}).clone(), // global values for check/convert values of attributes | 
| bsw/jbe@1309 | 9245         checkAttributes   = wysihtml.lang.object(globalAttributes).merge( wysihtml.lang.object(local_attributes || {}).clone()).get(), | 
| bsw/jbe@1309 | 9246         attributes        = {}, | 
| bsw/jbe@1309 | 9247         oldAttributes     = wysihtml.dom.getAttributes(oldNode), | 
| bsw/jbe@1309 | 9248         attributeName, newValue, matchingAttributes; | 
| bsw/jbe@1309 | 9249 | 
| bsw/jbe@1309 | 9250     for (attributeName in checkAttributes) { | 
| bsw/jbe@1309 | 9251       if ((/\*$/).test(attributeName)) { | 
| bsw/jbe@1309 | 9252 | 
| bsw/jbe@1309 | 9253         matchingAttributes = _getAttributesBeginningWith(attributeName.slice(0,-1), oldAttributes); | 
| bsw/jbe@1309 | 9254         for (var i = 0, imax = matchingAttributes.length; i < imax; i++) { | 
| bsw/jbe@1309 | 9255 | 
| bsw/jbe@1309 | 9256           newValue = _checkAttribute(matchingAttributes[i], oldAttributes[matchingAttributes[i]], checkAttributes[attributeName], oldNode.nodeName); | 
| bsw/jbe@1309 | 9257           if (newValue !== false) { | 
| bsw/jbe@1309 | 9258             attributes[matchingAttributes[i]] = newValue; | 
| bsw/jbe@1309 | 9259           } | 
| bsw/jbe@1309 | 9260         } | 
| bsw/jbe@1309 | 9261       } else { | 
| bsw/jbe@1309 | 9262         newValue = _checkAttribute(attributeName, oldAttributes[attributeName], checkAttributes[attributeName], oldNode.nodeName); | 
| bsw/jbe@1309 | 9263         if (newValue !== false) { | 
| bsw/jbe@1309 | 9264           attributes[attributeName] = newValue; | 
| bsw/jbe@1309 | 9265         } | 
| bsw/jbe@1309 | 9266       } | 
| bsw/jbe@1309 | 9267     } | 
| bsw/jbe@1309 | 9268 | 
| bsw/jbe@1309 | 9269     return attributes; | 
| bsw/jbe@1309 | 9270   } | 
| bsw/jbe@1309 | 9271 | 
| bsw/jbe@1309 | 9272   // TODO: refactor. Too long to read | 
| bsw/jbe@1309 | 9273   function _handleAttributes(oldNode, newNode, rule, clearInternals) { | 
| bsw/jbe@1309 | 9274     var attributes          = {},                         // fresh new set of attributes to set on newNode | 
| bsw/jbe@1309 | 9275         setClass            = rule.set_class,             // classes to set | 
| bsw/jbe@1309 | 9276         addClass            = rule.add_class,             // add classes based on existing attributes | 
| bsw/jbe@1309 | 9277         addStyle            = rule.add_style,             // add styles based on existing attributes | 
| bsw/jbe@1309 | 9278         setAttributes       = rule.set_attributes,        // attributes to set on the current node | 
| bsw/jbe@1309 | 9279         allowedClasses      = currentRules.classes, | 
| bsw/jbe@1309 | 9280         i                   = 0, | 
| bsw/jbe@1309 | 9281         classes             = [], | 
| bsw/jbe@1309 | 9282         styles              = [], | 
| bsw/jbe@1309 | 9283         newClasses          = [], | 
| bsw/jbe@1309 | 9284         oldClasses          = [], | 
| bsw/jbe@1309 | 9285         classesLength, | 
| bsw/jbe@1309 | 9286         newClassesLength, | 
| bsw/jbe@1309 | 9287         currentClass, | 
| bsw/jbe@1309 | 9288         newClass, | 
| bsw/jbe@1309 | 9289         attributeName, | 
| bsw/jbe@1309 | 9290         method; | 
| bsw/jbe@1309 | 9291 | 
| bsw/jbe@1309 | 9292     if (setAttributes) { | 
| bsw/jbe@1309 | 9293       attributes = wysihtml.lang.object(setAttributes).clone(); | 
| bsw/jbe@1309 | 9294     } | 
| bsw/jbe@1309 | 9295 | 
| bsw/jbe@1309 | 9296     // check/convert values of attributes | 
| bsw/jbe@1309 | 9297     attributes = wysihtml.lang.object(attributes).merge(_checkAttributes(oldNode,  rule.check_attributes)).get(); | 
| bsw/jbe@1309 | 9298 | 
| bsw/jbe@1309 | 9299     if (setClass) { | 
| bsw/jbe@1309 | 9300       classes.push(setClass); | 
| bsw/jbe@1309 | 9301     } | 
| bsw/jbe@1309 | 9302 | 
| bsw/jbe@1309 | 9303     if (addClass) { | 
| bsw/jbe@1309 | 9304       for (attributeName in addClass) { | 
| bsw/jbe@1309 | 9305         method = addClassMethods[addClass[attributeName]]; | 
| bsw/jbe@1309 | 9306         if (!method) { | 
| bsw/jbe@1309 | 9307           continue; | 
| bsw/jbe@1309 | 9308         } | 
| bsw/jbe@1309 | 9309         newClass = method(wysihtml.dom.getAttribute(oldNode, attributeName)); | 
| bsw/jbe@1309 | 9310         if (typeof(newClass) === "string") { | 
| bsw/jbe@1309 | 9311           classes.push(newClass); | 
| bsw/jbe@1309 | 9312         } | 
| bsw/jbe@1309 | 9313       } | 
| bsw/jbe@1309 | 9314     } | 
| bsw/jbe@1309 | 9315 | 
| bsw/jbe@1309 | 9316     if (addStyle) { | 
| bsw/jbe@1309 | 9317       for (attributeName in addStyle) { | 
| bsw/jbe@1309 | 9318         method = addStyleMethods[addStyle[attributeName]]; | 
| bsw/jbe@1309 | 9319         if (!method) { | 
| bsw/jbe@1309 | 9320           continue; | 
| bsw/jbe@1309 | 9321         } | 
| bsw/jbe@1309 | 9322 | 
| bsw/jbe@1309 | 9323         newStyle = method(wysihtml.dom.getAttribute(oldNode, attributeName)); | 
| bsw/jbe@1309 | 9324         if (typeof(newStyle) === "string") { | 
| bsw/jbe@1309 | 9325           styles.push(newStyle); | 
| bsw/jbe@1309 | 9326         } | 
| bsw/jbe@1309 | 9327       } | 
| bsw/jbe@1309 | 9328     } | 
| bsw/jbe@1309 | 9329 | 
| bsw/jbe@1309 | 9330 | 
| bsw/jbe@1309 | 9331     if (typeof(allowedClasses) === "string" && allowedClasses === "any") { | 
| bsw/jbe@1309 | 9332       if (oldNode.getAttribute("class")) { | 
| bsw/jbe@1309 | 9333         if (currentRules.classes_blacklist) { | 
| bsw/jbe@1309 | 9334           oldClasses = oldNode.getAttribute("class"); | 
| bsw/jbe@1309 | 9335           if (oldClasses) { | 
| bsw/jbe@1309 | 9336             classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP)); | 
| bsw/jbe@1309 | 9337           } | 
| bsw/jbe@1309 | 9338 | 
| bsw/jbe@1309 | 9339           classesLength = classes.length; | 
| bsw/jbe@1309 | 9340           for (; i<classesLength; i++) { | 
| bsw/jbe@1309 | 9341             currentClass = classes[i]; | 
| bsw/jbe@1309 | 9342             if (!currentRules.classes_blacklist[currentClass]) { | 
| bsw/jbe@1309 | 9343               newClasses.push(currentClass); | 
| bsw/jbe@1309 | 9344             } | 
| bsw/jbe@1309 | 9345           } | 
| bsw/jbe@1309 | 9346 | 
| bsw/jbe@1309 | 9347           if (newClasses.length) { | 
| bsw/jbe@1309 | 9348             attributes["class"] = wysihtml.lang.array(newClasses).unique().join(" "); | 
| bsw/jbe@1309 | 9349           } | 
| bsw/jbe@1309 | 9350 | 
| bsw/jbe@1309 | 9351         } else { | 
| bsw/jbe@1309 | 9352           attributes["class"] = oldNode.getAttribute("class"); | 
| bsw/jbe@1309 | 9353         } | 
| bsw/jbe@1309 | 9354       } else { | 
| bsw/jbe@1309 | 9355         if(classes && classes.length > 0) { | 
| bsw/jbe@1309 | 9356           attributes["class"] = wysihtml.lang.array(classes).unique().join(" "); | 
| bsw/jbe@1309 | 9357         } | 
| bsw/jbe@1309 | 9358       } | 
| bsw/jbe@1309 | 9359     } else { | 
| bsw/jbe@1309 | 9360       // make sure that wysihtml temp class doesn't get stripped out | 
| bsw/jbe@1309 | 9361       if (!clearInternals) { | 
| bsw/jbe@1309 | 9362         allowedClasses["_wysihtml-temp-placeholder"] = 1; | 
| bsw/jbe@1309 | 9363         allowedClasses["_rangySelectionBoundary"] = 1; | 
| bsw/jbe@1309 | 9364         allowedClasses["wysiwyg-tmp-selected-cell"] = 1; | 
| bsw/jbe@1309 | 9365       } | 
| bsw/jbe@1309 | 9366 | 
| bsw/jbe@1309 | 9367       // add old classes last | 
| bsw/jbe@1309 | 9368       oldClasses = oldNode.getAttribute("class"); | 
| bsw/jbe@1309 | 9369       if (oldClasses) { | 
| bsw/jbe@1309 | 9370         classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP)); | 
| bsw/jbe@1309 | 9371       } | 
| bsw/jbe@1309 | 9372       classesLength = classes.length; | 
| bsw/jbe@1309 | 9373       for (; i<classesLength; i++) { | 
| bsw/jbe@1309 | 9374         currentClass = classes[i]; | 
| bsw/jbe@1309 | 9375         if (allowedClasses[currentClass]) { | 
| bsw/jbe@1309 | 9376           newClasses.push(currentClass); | 
| bsw/jbe@1309 | 9377         } | 
| bsw/jbe@1309 | 9378       } | 
| bsw/jbe@1309 | 9379 | 
| bsw/jbe@1309 | 9380       if (newClasses.length) { | 
| bsw/jbe@1309 | 9381         attributes["class"] = wysihtml.lang.array(newClasses).unique().join(" "); | 
| bsw/jbe@1309 | 9382       } | 
| bsw/jbe@1309 | 9383     } | 
| bsw/jbe@1309 | 9384 | 
| bsw/jbe@1309 | 9385     // remove table selection class if present | 
| bsw/jbe@1309 | 9386     if (attributes["class"] && clearInternals) { | 
| bsw/jbe@1309 | 9387       attributes["class"] = attributes["class"].replace("wysiwyg-tmp-selected-cell", ""); | 
| bsw/jbe@1309 | 9388       if ((/^\s*$/g).test(attributes["class"])) { | 
| bsw/jbe@1309 | 9389         delete attributes["class"]; | 
| bsw/jbe@1309 | 9390       } | 
| bsw/jbe@1309 | 9391     } | 
| bsw/jbe@1309 | 9392 | 
| bsw/jbe@1309 | 9393     if (styles.length) { | 
| bsw/jbe@1309 | 9394       attributes["style"] = wysihtml.lang.array(styles).unique().join(" "); | 
| bsw/jbe@1309 | 9395     } | 
| bsw/jbe@1309 | 9396 | 
| bsw/jbe@1309 | 9397     // set attributes on newNode | 
| bsw/jbe@1309 | 9398     for (attributeName in attributes) { | 
| bsw/jbe@1309 | 9399       // Setting attributes can cause a js error in IE under certain circumstances | 
| bsw/jbe@1309 | 9400       // eg. on a <img> under https when it's new attribute value is non-https | 
| bsw/jbe@1309 | 9401       // TODO: Investigate this further and check for smarter handling | 
| bsw/jbe@1309 | 9402       try { | 
| bsw/jbe@1309 | 9403         newNode.setAttribute(attributeName, attributes[attributeName]); | 
| bsw/jbe@1309 | 9404       } catch(e) {} | 
| bsw/jbe@1309 | 9405     } | 
| bsw/jbe@1309 | 9406 | 
| bsw/jbe@1309 | 9407     // IE8 sometimes loses the width/height attributes when those are set before the "src" | 
| bsw/jbe@1309 | 9408     // so we make sure to set them again | 
| bsw/jbe@1309 | 9409     if (attributes.src) { | 
| bsw/jbe@1309 | 9410       if (typeof(attributes.width) !== "undefined") { | 
| bsw/jbe@1309 | 9411         newNode.setAttribute("width", attributes.width); | 
| bsw/jbe@1309 | 9412       } | 
| bsw/jbe@1309 | 9413       if (typeof(attributes.height) !== "undefined") { | 
| bsw/jbe@1309 | 9414         newNode.setAttribute("height", attributes.height); | 
| bsw/jbe@1309 | 9415       } | 
| bsw/jbe@1309 | 9416     } | 
| bsw/jbe@1309 | 9417   } | 
| bsw/jbe@1309 | 9418 | 
| bsw/jbe@1309 | 9419   function _handleText(oldNode) { | 
| bsw/jbe@1309 | 9420     var nextSibling = oldNode.nextSibling; | 
| bsw/jbe@1309 | 9421     if (nextSibling && nextSibling.nodeType === wysihtml.TEXT_NODE) { | 
| bsw/jbe@1309 | 9422       // Concatenate text nodes | 
| bsw/jbe@1309 | 9423       nextSibling.data = oldNode.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "") + nextSibling.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ""); | 
| bsw/jbe@1309 | 9424     } else { | 
| bsw/jbe@1309 | 9425       // \uFEFF = wysihtml.INVISIBLE_SPACE (used as a hack in certain rich text editing situations) | 
| bsw/jbe@1309 | 9426       var data = oldNode.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ""); | 
| bsw/jbe@1309 | 9427       return oldNode.ownerDocument.createTextNode(data); | 
| bsw/jbe@1309 | 9428     } | 
| bsw/jbe@1309 | 9429   } | 
| bsw/jbe@1309 | 9430 | 
| bsw/jbe@1309 | 9431   function _handleComment(oldNode) { | 
| bsw/jbe@1309 | 9432     if (currentRules.comments) { | 
| bsw/jbe@1309 | 9433       return oldNode.ownerDocument.createComment(oldNode.nodeValue); | 
| bsw/jbe@1309 | 9434     } | 
| bsw/jbe@1309 | 9435   } | 
| bsw/jbe@1309 | 9436 | 
| bsw/jbe@1309 | 9437   // ------------ attribute checks ------------ \\ | 
| bsw/jbe@1309 | 9438   var attributeCheckMethods = { | 
| bsw/jbe@1309 | 9439     url: (function() { | 
| bsw/jbe@1309 | 9440       var REG_EXP = /^https?:\/\//i; | 
| bsw/jbe@1309 | 9441       return function(attributeValue) { | 
| bsw/jbe@1309 | 9442         if (!attributeValue || !attributeValue.match(REG_EXP)) { | 
| bsw/jbe@1309 | 9443           return null; | 
| bsw/jbe@1309 | 9444         } | 
| bsw/jbe@1309 | 9445         return attributeValue.replace(REG_EXP, function(match) { | 
| bsw/jbe@1309 | 9446           return match.toLowerCase(); | 
| bsw/jbe@1309 | 9447         }); | 
| bsw/jbe@1309 | 9448       }; | 
| bsw/jbe@1309 | 9449     })(), | 
| bsw/jbe@1309 | 9450 | 
| bsw/jbe@1309 | 9451     src: (function() { | 
| bsw/jbe@1309 | 9452       var REG_EXP = /^(\/|https?:\/\/)/i; | 
| bsw/jbe@1309 | 9453       return function(attributeValue) { | 
| bsw/jbe@1309 | 9454         if (!attributeValue || !attributeValue.match(REG_EXP)) { | 
| bsw/jbe@1309 | 9455           return null; | 
| bsw/jbe@1309 | 9456         } | 
| bsw/jbe@1309 | 9457         return attributeValue.replace(REG_EXP, function(match) { | 
| bsw/jbe@1309 | 9458           return match.toLowerCase(); | 
| bsw/jbe@1309 | 9459         }); | 
| bsw/jbe@1309 | 9460       }; | 
| bsw/jbe@1309 | 9461     })(), | 
| bsw/jbe@1309 | 9462 | 
| bsw/jbe@1309 | 9463     href: (function() { | 
| bsw/jbe@1309 | 9464       var REG_EXP = /^(#|\/|https?:\/\/|mailto:|tel:)/i; | 
| bsw/jbe@1309 | 9465       return function(attributeValue) { | 
| bsw/jbe@1309 | 9466         if (!attributeValue || !attributeValue.match(REG_EXP)) { | 
| bsw/jbe@1309 | 9467           return null; | 
| bsw/jbe@1309 | 9468         } | 
| bsw/jbe@1309 | 9469         return attributeValue.replace(REG_EXP, function(match) { | 
| bsw/jbe@1309 | 9470           return match.toLowerCase(); | 
| bsw/jbe@1309 | 9471         }); | 
| bsw/jbe@1309 | 9472       }; | 
| bsw/jbe@1309 | 9473     })(), | 
| bsw/jbe@1309 | 9474 | 
| bsw/jbe@1309 | 9475     alt: (function() { | 
| bsw/jbe@1309 | 9476       var REG_EXP = /[^ a-z0-9_\-]/gi; | 
| bsw/jbe@1309 | 9477       return function(attributeValue, nodeName) { | 
| bsw/jbe@1309 | 9478         if (!attributeValue) { | 
| bsw/jbe@1309 | 9479           if (nodeName === "IMG") { | 
| bsw/jbe@1309 | 9480             return ""; | 
| bsw/jbe@1309 | 9481           } else { | 
| bsw/jbe@1309 | 9482             return null; | 
| bsw/jbe@1309 | 9483           } | 
| bsw/jbe@1309 | 9484         } | 
| bsw/jbe@1309 | 9485         return attributeValue.replace(REG_EXP, ""); | 
| bsw/jbe@1309 | 9486       }; | 
| bsw/jbe@1309 | 9487     })(), | 
| bsw/jbe@1309 | 9488 | 
| bsw/jbe@1309 | 9489     // Integers. Does not work with floating point numbers and units | 
| bsw/jbe@1309 | 9490     numbers: (function() { | 
| bsw/jbe@1309 | 9491       var REG_EXP = /\D/g; | 
| bsw/jbe@1309 | 9492       return function(attributeValue) { | 
| bsw/jbe@1309 | 9493         attributeValue = (attributeValue || "").replace(REG_EXP, ""); | 
| bsw/jbe@1309 | 9494         return attributeValue || null; | 
| bsw/jbe@1309 | 9495       }; | 
| bsw/jbe@1309 | 9496     })(), | 
| bsw/jbe@1309 | 9497 | 
| bsw/jbe@1309 | 9498     // Useful for with/height attributes where floating points and percentages are allowed | 
| bsw/jbe@1309 | 9499     dimension: (function() { | 
| bsw/jbe@1309 | 9500       var REG_EXP = /\D*(\d+)(\.\d+)?\s?(%)?\D*/; | 
| bsw/jbe@1309 | 9501       return function(attributeValue) { | 
| bsw/jbe@1309 | 9502         attributeValue = (attributeValue || "").replace(REG_EXP, "$1$2$3"); | 
| bsw/jbe@1309 | 9503         return attributeValue || null; | 
| bsw/jbe@1309 | 9504       }; | 
| bsw/jbe@1309 | 9505     })(), | 
| bsw/jbe@1309 | 9506 | 
| bsw/jbe@1309 | 9507     any: (function() { | 
| bsw/jbe@1309 | 9508       return function(attributeValue) { | 
| bsw/jbe@1309 | 9509         if (!attributeValue) { | 
| bsw/jbe@1309 | 9510           return null; | 
| bsw/jbe@1309 | 9511         } | 
| bsw/jbe@1309 | 9512         return attributeValue; | 
| bsw/jbe@1309 | 9513       }; | 
| bsw/jbe@1309 | 9514     })() | 
| bsw/jbe@1309 | 9515   }; | 
| bsw/jbe@1309 | 9516 | 
| bsw/jbe@1309 | 9517   // ------------ style converter (converts an html attribute to a style) ------------ \\ | 
| bsw/jbe@1309 | 9518   var addStyleMethods = { | 
| bsw/jbe@1309 | 9519     align_text: (function() { | 
| bsw/jbe@1309 | 9520       var mapping = { | 
| bsw/jbe@1309 | 9521         left:     "text-align: left;", | 
| bsw/jbe@1309 | 9522         right:    "text-align: right;", | 
| bsw/jbe@1309 | 9523         center:   "text-align: center;" | 
| bsw/jbe@1309 | 9524       }; | 
| bsw/jbe@1309 | 9525       return function(attributeValue) { | 
| bsw/jbe@1309 | 9526         return mapping[String(attributeValue).toLowerCase()]; | 
| bsw/jbe@1309 | 9527       }; | 
| bsw/jbe@1309 | 9528     })(), | 
| bsw/jbe@1309 | 9529   }; | 
| bsw/jbe@1309 | 9530 | 
| bsw/jbe@1309 | 9531   // ------------ class converter (converts an html attribute to a class name) ------------ \\ | 
| bsw/jbe@1309 | 9532   var addClassMethods = { | 
| bsw/jbe@1309 | 9533     align_img: (function() { | 
| bsw/jbe@1309 | 9534       var mapping = { | 
| bsw/jbe@1309 | 9535         left:   "wysiwyg-float-left", | 
| bsw/jbe@1309 | 9536         right:  "wysiwyg-float-right" | 
| bsw/jbe@1309 | 9537       }; | 
| bsw/jbe@1309 | 9538       return function(attributeValue) { | 
| bsw/jbe@1309 | 9539         return mapping[String(attributeValue).toLowerCase()]; | 
| bsw/jbe@1309 | 9540       }; | 
| bsw/jbe@1309 | 9541     })(), | 
| bsw/jbe@1309 | 9542 | 
| bsw/jbe@1309 | 9543     align_text: (function() { | 
| bsw/jbe@1309 | 9544       var mapping = { | 
| bsw/jbe@1309 | 9545         left:     "wysiwyg-text-align-left", | 
| bsw/jbe@1309 | 9546         right:    "wysiwyg-text-align-right", | 
| bsw/jbe@1309 | 9547         center:   "wysiwyg-text-align-center", | 
| bsw/jbe@1309 | 9548         justify:  "wysiwyg-text-align-justify" | 
| bsw/jbe@1309 | 9549       }; | 
| bsw/jbe@1309 | 9550       return function(attributeValue) { | 
| bsw/jbe@1309 | 9551         return mapping[String(attributeValue).toLowerCase()]; | 
| bsw/jbe@1309 | 9552       }; | 
| bsw/jbe@1309 | 9553     })(), | 
| bsw/jbe@1309 | 9554 | 
| bsw/jbe@1309 | 9555     clear_br: (function() { | 
| bsw/jbe@1309 | 9556       var mapping = { | 
| bsw/jbe@1309 | 9557         left:   "wysiwyg-clear-left", | 
| bsw/jbe@1309 | 9558         right:  "wysiwyg-clear-right", | 
| bsw/jbe@1309 | 9559         both:   "wysiwyg-clear-both", | 
| bsw/jbe@1309 | 9560         all:    "wysiwyg-clear-both" | 
| bsw/jbe@1309 | 9561       }; | 
| bsw/jbe@1309 | 9562       return function(attributeValue) { | 
| bsw/jbe@1309 | 9563         return mapping[String(attributeValue).toLowerCase()]; | 
| bsw/jbe@1309 | 9564       }; | 
| bsw/jbe@1309 | 9565     })(), | 
| bsw/jbe@1309 | 9566 | 
| bsw/jbe@1309 | 9567     size_font: (function() { | 
| bsw/jbe@1309 | 9568       var mapping = { | 
| bsw/jbe@1309 | 9569         "1": "wysiwyg-font-size-xx-small", | 
| bsw/jbe@1309 | 9570         "2": "wysiwyg-font-size-small", | 
| bsw/jbe@1309 | 9571         "3": "wysiwyg-font-size-medium", | 
| bsw/jbe@1309 | 9572         "4": "wysiwyg-font-size-large", | 
| bsw/jbe@1309 | 9573         "5": "wysiwyg-font-size-x-large", | 
| bsw/jbe@1309 | 9574         "6": "wysiwyg-font-size-xx-large", | 
| bsw/jbe@1309 | 9575         "7": "wysiwyg-font-size-xx-large", | 
| bsw/jbe@1309 | 9576         "-": "wysiwyg-font-size-smaller", | 
| bsw/jbe@1309 | 9577         "+": "wysiwyg-font-size-larger" | 
| bsw/jbe@1309 | 9578       }; | 
| bsw/jbe@1309 | 9579       return function(attributeValue) { | 
| bsw/jbe@1309 | 9580         return mapping[String(attributeValue).charAt(0)]; | 
| bsw/jbe@1309 | 9581       }; | 
| bsw/jbe@1309 | 9582     })() | 
| bsw/jbe@1309 | 9583   }; | 
| bsw/jbe@1309 | 9584 | 
| bsw/jbe@1309 | 9585   // checks if element is possibly visible | 
| bsw/jbe@1309 | 9586   var typeCeckMethods = { | 
| bsw/jbe@1309 | 9587     has_visible_contet: (function() { | 
| bsw/jbe@1309 | 9588       var txt, | 
| bsw/jbe@1309 | 9589           isVisible = false, | 
| bsw/jbe@1309 | 9590           visibleElements = ['img', 'video', 'picture', 'br', 'script', 'noscript', | 
| bsw/jbe@1309 | 9591                              'style', 'table', 'iframe', 'object', 'embed', 'audio', | 
| bsw/jbe@1309 | 9592                              'svg', 'input', 'button', 'select','textarea', 'canvas']; | 
| bsw/jbe@1309 | 9593 | 
| bsw/jbe@1309 | 9594       return function(el) { | 
| bsw/jbe@1309 | 9595 | 
| bsw/jbe@1309 | 9596         // has visible innertext. so is visible | 
| bsw/jbe@1309 | 9597         txt = (el.innerText || el.textContent).replace(/\s/g, ''); | 
| bsw/jbe@1309 | 9598         if (txt && txt.length > 0) { | 
| bsw/jbe@1309 | 9599           return true; | 
| bsw/jbe@1309 | 9600         } | 
| bsw/jbe@1309 | 9601 | 
| bsw/jbe@1309 | 9602         // matches list of visible dimensioned elements | 
| bsw/jbe@1309 | 9603         for (var i = visibleElements.length; i--;) { | 
| bsw/jbe@1309 | 9604           if (el.querySelector(visibleElements[i])) { | 
| bsw/jbe@1309 | 9605             return true; | 
| bsw/jbe@1309 | 9606           } | 
| bsw/jbe@1309 | 9607         } | 
| bsw/jbe@1309 | 9608 | 
| bsw/jbe@1309 | 9609         // try to measure dimesions in last resort. (can find only of elements in dom) | 
| bsw/jbe@1309 | 9610         if (el.offsetWidth && el.offsetWidth > 0 && el.offsetHeight && el.offsetHeight > 0) { | 
| bsw/jbe@1309 | 9611           return true; | 
| bsw/jbe@1309 | 9612         } | 
| bsw/jbe@1309 | 9613 | 
| bsw/jbe@1309 | 9614         return false; | 
| bsw/jbe@1309 | 9615       }; | 
| bsw/jbe@1309 | 9616     })() | 
| bsw/jbe@1309 | 9617   }; | 
| bsw/jbe@1309 | 9618 | 
| bsw/jbe@1309 | 9619   var elementHandlingMethods = { | 
| bsw/jbe@1309 | 9620     unwrap: function (element) { | 
| bsw/jbe@1309 | 9621       wysihtml.dom.unwrap(element); | 
| bsw/jbe@1309 | 9622     }, | 
| bsw/jbe@1309 | 9623 | 
| bsw/jbe@1309 | 9624     remove: function (element) { | 
| bsw/jbe@1309 | 9625       element.parentNode.removeChild(element); | 
| bsw/jbe@1309 | 9626     } | 
| bsw/jbe@1309 | 9627   }; | 
| bsw/jbe@1309 | 9628 | 
| bsw/jbe@1309 | 9629   return parse(elementOrHtml_current, config_current); | 
| bsw/jbe@1309 | 9630 }; | 
| bsw/jbe@1309 | 9631 | 
| bsw/jbe@1309 | 9632 // does a selector query on element or array of elements | 
| bsw/jbe@1309 | 9633 wysihtml.dom.query = function(elements, query) { | 
| bsw/jbe@1309 | 9634     var ret = [], | 
| bsw/jbe@1309 | 9635         q; | 
| bsw/jbe@1309 | 9636 | 
| bsw/jbe@1309 | 9637     if (elements.nodeType) { | 
| bsw/jbe@1309 | 9638         elements = [elements]; | 
| bsw/jbe@1309 | 9639     } | 
| bsw/jbe@1309 | 9640 | 
| bsw/jbe@1309 | 9641     for (var e = 0, len = elements.length; e < len; e++) { | 
| bsw/jbe@1309 | 9642         q = elements[e].querySelectorAll(query); | 
| bsw/jbe@1309 | 9643         if (q) { | 
| bsw/jbe@1309 | 9644             for(var i = q.length; i--; ret.unshift(q[i])); | 
| bsw/jbe@1309 | 9645         } | 
| bsw/jbe@1309 | 9646     } | 
| bsw/jbe@1309 | 9647     return ret; | 
| bsw/jbe@1309 | 9648 }; | 
| bsw/jbe@1309 | 9649 | 
| bsw/jbe@1309 | 9650 /** | 
| bsw/jbe@1309 | 9651  * Checks for empty text node childs and removes them | 
| bsw/jbe@1309 | 9652  * | 
| bsw/jbe@1309 | 9653  * @param {Element} node The element in which to cleanup | 
| bsw/jbe@1309 | 9654  * @example | 
| bsw/jbe@1309 | 9655  *    wysihtml.dom.removeEmptyTextNodes(element); | 
| bsw/jbe@1309 | 9656  */ | 
| bsw/jbe@1309 | 9657 wysihtml.dom.removeEmptyTextNodes = function(node) { | 
| bsw/jbe@1309 | 9658   var childNode, | 
| bsw/jbe@1309 | 9659       childNodes        = wysihtml.lang.array(node.childNodes).get(), | 
| bsw/jbe@1309 | 9660       childNodesLength  = childNodes.length, | 
| bsw/jbe@1309 | 9661       i                 = 0; | 
| bsw/jbe@1309 | 9662 | 
| bsw/jbe@1309 | 9663   for (; i<childNodesLength; i++) { | 
| bsw/jbe@1309 | 9664     childNode = childNodes[i]; | 
| bsw/jbe@1309 | 9665     if (childNode.nodeType === wysihtml.TEXT_NODE && (/^[\n\r]*$/).test(childNode.data)) { | 
| bsw/jbe@1309 | 9666       childNode.parentNode.removeChild(childNode); | 
| bsw/jbe@1309 | 9667     } | 
| bsw/jbe@1309 | 9668   } | 
| bsw/jbe@1309 | 9669 }; | 
| bsw/jbe@1309 | 9670 | 
| bsw/jbe@1309 | 9671 wysihtml.dom.removeInvisibleSpaces = function(node) { | 
| bsw/jbe@1309 | 9672   var textNodes = wysihtml.dom.getTextNodes(node); | 
| bsw/jbe@1309 | 9673   for (var n = textNodes.length; n--;) { | 
| bsw/jbe@1309 | 9674     textNodes[n].nodeValue = textNodes[n].nodeValue.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ""); | 
| bsw/jbe@1309 | 9675   } | 
| bsw/jbe@1309 | 9676 }; | 
| bsw/jbe@1309 | 9677 | 
| bsw/jbe@1309 | 9678 /** | 
| bsw/jbe@1309 | 9679  * Renames an element (eg. a <div> to a <p>) and keeps its childs | 
| bsw/jbe@1309 | 9680  * | 
| bsw/jbe@1309 | 9681  * @param {Element} element The list element which should be renamed | 
| bsw/jbe@1309 | 9682  * @param {Element} newNodeName The desired tag name | 
| bsw/jbe@1309 | 9683  * | 
| bsw/jbe@1309 | 9684  * @example | 
| bsw/jbe@1309 | 9685  *    <!-- Assume the following dom: --> | 
| bsw/jbe@1309 | 9686  *    <ul id="list"> | 
| bsw/jbe@1309 | 9687  *      <li>eminem</li> | 
| bsw/jbe@1309 | 9688  *      <li>dr. dre</li> | 
| bsw/jbe@1309 | 9689  *      <li>50 Cent</li> | 
| bsw/jbe@1309 | 9690  *    </ul> | 
| bsw/jbe@1309 | 9691  * | 
| bsw/jbe@1309 | 9692  *    <script> | 
| bsw/jbe@1309 | 9693  *      wysihtml.dom.renameElement(document.getElementById("list"), "ol"); | 
| bsw/jbe@1309 | 9694  *    </script> | 
| bsw/jbe@1309 | 9695  * | 
| bsw/jbe@1309 | 9696  *    <!-- Will result in: --> | 
| bsw/jbe@1309 | 9697  *    <ol> | 
| bsw/jbe@1309 | 9698  *      <li>eminem</li> | 
| bsw/jbe@1309 | 9699  *      <li>dr. dre</li> | 
| bsw/jbe@1309 | 9700  *      <li>50 Cent</li> | 
| bsw/jbe@1309 | 9701  *    </ol> | 
| bsw/jbe@1309 | 9702  */ | 
| bsw/jbe@1309 | 9703 wysihtml.dom.renameElement = function(element, newNodeName) { | 
| bsw/jbe@1309 | 9704   var newElement = element.ownerDocument.createElement(newNodeName), | 
| bsw/jbe@1309 | 9705       firstChild; | 
| bsw/jbe@1309 | 9706   while (firstChild = element.firstChild) { | 
| bsw/jbe@1309 | 9707     newElement.appendChild(firstChild); | 
| bsw/jbe@1309 | 9708   } | 
| bsw/jbe@1309 | 9709   wysihtml.dom.copyAttributes(["align", "className"]).from(element).to(newElement); | 
| bsw/jbe@1309 | 9710 | 
| bsw/jbe@1309 | 9711   if (element.parentNode) { | 
| bsw/jbe@1309 | 9712     element.parentNode.replaceChild(newElement, element); | 
| bsw/jbe@1309 | 9713   } | 
| bsw/jbe@1309 | 9714 | 
| bsw/jbe@1309 | 9715   return newElement; | 
| bsw/jbe@1309 | 9716 }; | 
| bsw/jbe@1309 | 9717 | 
| bsw/jbe@1309 | 9718 /** | 
| bsw/jbe@1309 | 9719  * Takes an element, removes it and replaces it with it's childs | 
| bsw/jbe@1309 | 9720  * | 
| bsw/jbe@1309 | 9721  * @param {Object} node The node which to replace with it's child nodes | 
| bsw/jbe@1309 | 9722  * @example | 
| bsw/jbe@1309 | 9723  *    <div id="foo"> | 
| bsw/jbe@1309 | 9724  *      <span>hello</span> | 
| bsw/jbe@1309 | 9725  *    </div> | 
| bsw/jbe@1309 | 9726  *    <script> | 
| bsw/jbe@1309 | 9727  *      // Remove #foo and replace with it's children | 
| bsw/jbe@1309 | 9728  *      wysihtml.dom.replaceWithChildNodes(document.getElementById("foo")); | 
| bsw/jbe@1309 | 9729  *    </script> | 
| bsw/jbe@1309 | 9730  */ | 
| bsw/jbe@1309 | 9731 wysihtml.dom.replaceWithChildNodes = function(node) { | 
| bsw/jbe@1309 | 9732   if (!node.parentNode) { | 
| bsw/jbe@1309 | 9733     return; | 
| bsw/jbe@1309 | 9734   } | 
| bsw/jbe@1309 | 9735 | 
| bsw/jbe@1309 | 9736   while (node.firstChild) { | 
| bsw/jbe@1309 | 9737     node.parentNode.insertBefore(node.firstChild, node); | 
| bsw/jbe@1309 | 9738   } | 
| bsw/jbe@1309 | 9739   node.parentNode.removeChild(node); | 
| bsw/jbe@1309 | 9740 }; | 
| bsw/jbe@1309 | 9741 | 
| bsw/jbe@1309 | 9742 /** | 
| bsw/jbe@1309 | 9743  * Unwraps an unordered/ordered list | 
| bsw/jbe@1309 | 9744  * | 
| bsw/jbe@1309 | 9745  * @param {Element} element The list element which should be unwrapped | 
| bsw/jbe@1309 | 9746  * | 
| bsw/jbe@1309 | 9747  * @example | 
| bsw/jbe@1309 | 9748  *    <!-- Assume the following dom: --> | 
| bsw/jbe@1309 | 9749  *    <ul id="list"> | 
| bsw/jbe@1309 | 9750  *      <li>eminem</li> | 
| bsw/jbe@1309 | 9751  *      <li>dr. dre</li> | 
| bsw/jbe@1309 | 9752  *      <li>50 Cent</li> | 
| bsw/jbe@1309 | 9753  *    </ul> | 
| bsw/jbe@1309 | 9754  * | 
| bsw/jbe@1309 | 9755  *    <script> | 
| bsw/jbe@1309 | 9756  *      wysihtml.dom.resolveList(document.getElementById("list")); | 
| bsw/jbe@1309 | 9757  *    </script> | 
| bsw/jbe@1309 | 9758  * | 
| bsw/jbe@1309 | 9759  *    <!-- Will result in: --> | 
| bsw/jbe@1309 | 9760  *    eminem<br> | 
| bsw/jbe@1309 | 9761  *    dr. dre<br> | 
| bsw/jbe@1309 | 9762  *    50 Cent<br> | 
| bsw/jbe@1309 | 9763  */ | 
| bsw/jbe@1309 | 9764 (function(dom) { | 
| bsw/jbe@1309 | 9765   function _isBlockElement(node) { | 
| bsw/jbe@1309 | 9766     return dom.getStyle("display").from(node) === "block"; | 
| bsw/jbe@1309 | 9767   } | 
| bsw/jbe@1309 | 9768 | 
| bsw/jbe@1309 | 9769   function _isLineBreak(node) { | 
| bsw/jbe@1309 | 9770     return node.nodeName === "BR"; | 
| bsw/jbe@1309 | 9771   } | 
| bsw/jbe@1309 | 9772 | 
| bsw/jbe@1309 | 9773   function _appendLineBreak(element) { | 
| bsw/jbe@1309 | 9774     var lineBreak = element.ownerDocument.createElement("br"); | 
| bsw/jbe@1309 | 9775     element.appendChild(lineBreak); | 
| bsw/jbe@1309 | 9776   } | 
| bsw/jbe@1309 | 9777 | 
| bsw/jbe@1309 | 9778   function resolveList(list, useLineBreaks) { | 
| bsw/jbe@1309 | 9779     if (!list.nodeName.match(/^(MENU|UL|OL)$/)) { | 
| bsw/jbe@1309 | 9780       return; | 
| bsw/jbe@1309 | 9781     } | 
| bsw/jbe@1309 | 9782 | 
| bsw/jbe@1309 | 9783     var doc             = list.ownerDocument, | 
| bsw/jbe@1309 | 9784         fragment        = doc.createDocumentFragment(), | 
| bsw/jbe@1309 | 9785         previousSibling = wysihtml.dom.domNode(list).prev({ignoreBlankTexts: true}), | 
| bsw/jbe@1309 | 9786         nextSibling = wysihtml.dom.domNode(list).next({ignoreBlankTexts: true}), | 
| bsw/jbe@1309 | 9787         firstChild, | 
| bsw/jbe@1309 | 9788         lastChild, | 
| bsw/jbe@1309 | 9789         isLastChild, | 
| bsw/jbe@1309 | 9790         shouldAppendLineBreak, | 
| bsw/jbe@1309 | 9791         paragraph, | 
| bsw/jbe@1309 | 9792         listItem, | 
| bsw/jbe@1309 | 9793         lastListItem = list.lastElementChild || list.lastChild, | 
| bsw/jbe@1309 | 9794         isLastItem; | 
| bsw/jbe@1309 | 9795 | 
| bsw/jbe@1309 | 9796     if (useLineBreaks) { | 
| bsw/jbe@1309 | 9797       // Insert line break if list is after a non-block element | 
| bsw/jbe@1309 | 9798       if (previousSibling && !_isBlockElement(previousSibling) && !_isLineBreak(previousSibling)) { | 
| bsw/jbe@1309 | 9799         _appendLineBreak(fragment); | 
| bsw/jbe@1309 | 9800       } | 
| bsw/jbe@1309 | 9801 | 
| bsw/jbe@1309 | 9802       while (listItem = (list.firstElementChild || list.firstChild)) { | 
| bsw/jbe@1309 | 9803         lastChild = listItem.lastChild; | 
| bsw/jbe@1309 | 9804         isLastItem = listItem === lastListItem; | 
| bsw/jbe@1309 | 9805         while (firstChild = listItem.firstChild) { | 
| bsw/jbe@1309 | 9806           isLastChild           = firstChild === lastChild; | 
| bsw/jbe@1309 | 9807           // This needs to be done before appending it to the fragment, as it otherwise will lose style information | 
| bsw/jbe@1309 | 9808           shouldAppendLineBreak = (!isLastItem || (nextSibling && !_isBlockElement(nextSibling))) && isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild); | 
| bsw/jbe@1309 | 9809           fragment.appendChild(firstChild); | 
| bsw/jbe@1309 | 9810           if (shouldAppendLineBreak) { | 
| bsw/jbe@1309 | 9811             _appendLineBreak(fragment); | 
| bsw/jbe@1309 | 9812           } | 
| bsw/jbe@1309 | 9813         } | 
| bsw/jbe@1309 | 9814 | 
| bsw/jbe@1309 | 9815         listItem.parentNode.removeChild(listItem); | 
| bsw/jbe@1309 | 9816       } | 
| bsw/jbe@1309 | 9817     } else { | 
| bsw/jbe@1309 | 9818       while (listItem = (list.firstElementChild || list.firstChild)) { | 
| bsw/jbe@1309 | 9819         if (listItem.querySelector && listItem.querySelector("div, p, ul, ol, menu, blockquote, h1, h2, h3, h4, h5, h6")) { | 
| bsw/jbe@1309 | 9820           while (firstChild = listItem.firstChild) { | 
| bsw/jbe@1309 | 9821             fragment.appendChild(firstChild); | 
| bsw/jbe@1309 | 9822           } | 
| bsw/jbe@1309 | 9823         } else { | 
| bsw/jbe@1309 | 9824           paragraph = doc.createElement("p"); | 
| bsw/jbe@1309 | 9825           while (firstChild = listItem.firstChild) { | 
| bsw/jbe@1309 | 9826             paragraph.appendChild(firstChild); | 
| bsw/jbe@1309 | 9827           } | 
| bsw/jbe@1309 | 9828           fragment.appendChild(paragraph); | 
| bsw/jbe@1309 | 9829         } | 
| bsw/jbe@1309 | 9830         listItem.parentNode.removeChild(listItem); | 
| bsw/jbe@1309 | 9831       } | 
| bsw/jbe@1309 | 9832     } | 
| bsw/jbe@1309 | 9833 | 
| bsw/jbe@1309 | 9834     list.parentNode.replaceChild(fragment, list); | 
| bsw/jbe@1309 | 9835   } | 
| bsw/jbe@1309 | 9836 | 
| bsw/jbe@1309 | 9837   dom.resolveList = resolveList; | 
| bsw/jbe@1309 | 9838 })(wysihtml.dom); | 
| bsw/jbe@1309 | 9839 | 
| bsw/jbe@1309 | 9840 /** | 
| bsw/jbe@1309 | 9841  * Sandbox for executing javascript, parsing css styles and doing dom operations in a secure way | 
| bsw/jbe@1309 | 9842  * | 
| bsw/jbe@1309 | 9843  * Browser Compatibility: | 
| bsw/jbe@1309 | 9844  *  - Secure in MSIE 6+, but only when the user hasn't made changes to his security level "restricted" | 
| bsw/jbe@1309 | 9845  *  - Partially secure in other browsers (Firefox, Opera, Safari, Chrome, ...) | 
| bsw/jbe@1309 | 9846  * | 
| bsw/jbe@1309 | 9847  * Please note that this class can't benefit from the HTML5 sandbox attribute for the following reasons: | 
| bsw/jbe@1309 | 9848  *    - sandboxing doesn't work correctly with inlined content (src="javascript:'<html>...</html>'") | 
| bsw/jbe@1309 | 9849  *    - sandboxing of physical documents causes that the dom isn't accessible anymore from the outside (iframe.contentWindow, ...) | 
| bsw/jbe@1309 | 9850  *    - setting the "allow-same-origin" flag would fix that, but then still javascript and dom events refuse to fire | 
| bsw/jbe@1309 | 9851  *    - therefore the "allow-scripts" flag is needed, which then would deactivate any security, as the js executed inside the iframe | 
| bsw/jbe@1309 | 9852  *      can do anything as if the sandbox attribute wasn't set | 
| bsw/jbe@1309 | 9853  * | 
| bsw/jbe@1309 | 9854  * @param {Function} [readyCallback] Method that gets invoked when the sandbox is ready | 
| bsw/jbe@1309 | 9855  * @param {Object} [config] Optional parameters | 
| bsw/jbe@1309 | 9856  * | 
| bsw/jbe@1309 | 9857  * @example | 
| bsw/jbe@1309 | 9858  *    new wysihtml.dom.Sandbox(function(sandbox) { | 
| bsw/jbe@1309 | 9859  *      sandbox.getWindow().document.body.innerHTML = '<img src=foo.gif onerror="alert(document.cookie)">'; | 
| bsw/jbe@1309 | 9860  *    }); | 
| bsw/jbe@1309 | 9861  */ | 
| bsw/jbe@1309 | 9862 (function(wysihtml) { | 
| bsw/jbe@1309 | 9863   var /** | 
| bsw/jbe@1309 | 9864        * Default configuration | 
| bsw/jbe@1309 | 9865        */ | 
| bsw/jbe@1309 | 9866       doc                 = document, | 
| bsw/jbe@1309 | 9867       /** | 
| bsw/jbe@1309 | 9868        * Properties to unset/protect on the window object | 
| bsw/jbe@1309 | 9869        */ | 
| bsw/jbe@1309 | 9870       windowProperties    = [ | 
| bsw/jbe@1309 | 9871         "parent", "top", "opener", "frameElement", "frames", | 
| bsw/jbe@1309 | 9872         "localStorage", "globalStorage", "sessionStorage", "indexedDB" | 
| bsw/jbe@1309 | 9873       ], | 
| bsw/jbe@1309 | 9874       /** | 
| bsw/jbe@1309 | 9875        * Properties on the window object which are set to an empty function | 
| bsw/jbe@1309 | 9876        */ | 
| bsw/jbe@1309 | 9877       windowProperties2   = [ | 
| bsw/jbe@1309 | 9878         "open", "close", "openDialog", "showModalDialog", | 
| bsw/jbe@1309 | 9879         "alert", "confirm", "prompt", | 
| bsw/jbe@1309 | 9880         "openDatabase", "postMessage", | 
| bsw/jbe@1309 | 9881         "XMLHttpRequest", "XDomainRequest" | 
| bsw/jbe@1309 | 9882       ], | 
| bsw/jbe@1309 | 9883       /** | 
| bsw/jbe@1309 | 9884        * Properties to unset/protect on the document object | 
| bsw/jbe@1309 | 9885        */ | 
| bsw/jbe@1309 | 9886       documentProperties  = [ | 
| bsw/jbe@1309 | 9887         "referrer", | 
| bsw/jbe@1309 | 9888         "write", "open", "close" | 
| bsw/jbe@1309 | 9889       ]; | 
| bsw/jbe@1309 | 9890 | 
| bsw/jbe@1309 | 9891   wysihtml.dom.Sandbox = Base.extend( | 
| bsw/jbe@1309 | 9892     /** @scope wysihtml.dom.Sandbox.prototype */ { | 
| bsw/jbe@1309 | 9893 | 
| bsw/jbe@1309 | 9894     constructor: function(readyCallback, config) { | 
| bsw/jbe@1309 | 9895       this.callback = readyCallback || wysihtml.EMPTY_FUNCTION; | 
| bsw/jbe@1309 | 9896       this.config   = wysihtml.lang.object({}).merge(config).get(); | 
| bsw/jbe@1309 | 9897       if (!this.config.className) { | 
| bsw/jbe@1309 | 9898         this.config.className = "wysihtml-sandbox"; | 
| bsw/jbe@1309 | 9899       } | 
| bsw/jbe@1309 | 9900       this.editableArea   = this._createIframe(); | 
| bsw/jbe@1309 | 9901     }, | 
| bsw/jbe@1309 | 9902 | 
| bsw/jbe@1309 | 9903     insertInto: function(element) { | 
| bsw/jbe@1309 | 9904       if (typeof(element) === "string") { | 
| bsw/jbe@1309 | 9905         element = doc.getElementById(element); | 
| bsw/jbe@1309 | 9906       } | 
| bsw/jbe@1309 | 9907 | 
| bsw/jbe@1309 | 9908       element.appendChild(this.editableArea); | 
| bsw/jbe@1309 | 9909     }, | 
| bsw/jbe@1309 | 9910 | 
| bsw/jbe@1309 | 9911     getIframe: function() { | 
| bsw/jbe@1309 | 9912       return this.editableArea; | 
| bsw/jbe@1309 | 9913     }, | 
| bsw/jbe@1309 | 9914 | 
| bsw/jbe@1309 | 9915     getWindow: function() { | 
| bsw/jbe@1309 | 9916       this._readyError(); | 
| bsw/jbe@1309 | 9917     }, | 
| bsw/jbe@1309 | 9918 | 
| bsw/jbe@1309 | 9919     getDocument: function() { | 
| bsw/jbe@1309 | 9920       this._readyError(); | 
| bsw/jbe@1309 | 9921     }, | 
| bsw/jbe@1309 | 9922 | 
| bsw/jbe@1309 | 9923     destroy: function() { | 
| bsw/jbe@1309 | 9924       var iframe = this.getIframe(); | 
| bsw/jbe@1309 | 9925       iframe.parentNode.removeChild(iframe); | 
| bsw/jbe@1309 | 9926     }, | 
| bsw/jbe@1309 | 9927 | 
| bsw/jbe@1309 | 9928     _readyError: function() { | 
| bsw/jbe@1309 | 9929       throw new Error("wysihtml.Sandbox: Sandbox iframe isn't loaded yet"); | 
| bsw/jbe@1309 | 9930     }, | 
| bsw/jbe@1309 | 9931 | 
| bsw/jbe@1309 | 9932     /** | 
| bsw/jbe@1309 | 9933      * Creates the sandbox iframe | 
| bsw/jbe@1309 | 9934      * | 
| bsw/jbe@1309 | 9935      * Some important notes: | 
| bsw/jbe@1309 | 9936      *  - We can't use HTML5 sandbox for now: | 
| bsw/jbe@1309 | 9937      *    setting it causes that the iframe's dom can't be accessed from the outside | 
| bsw/jbe@1309 | 9938      *    Therefore we need to set the "allow-same-origin" flag which enables accessing the iframe's dom | 
| bsw/jbe@1309 | 9939      *    But then there's another problem, DOM events (focus, blur, change, keypress, ...) aren't fired. | 
| bsw/jbe@1309 | 9940      *    In order to make this happen we need to set the "allow-scripts" flag. | 
| bsw/jbe@1309 | 9941      *    A combination of allow-scripts and allow-same-origin is almost the same as setting no sandbox attribute at all. | 
| bsw/jbe@1309 | 9942      *  - Chrome & Safari, doesn't seem to support sandboxing correctly when the iframe's html is inlined (no physical document) | 
| bsw/jbe@1309 | 9943      *  - IE needs to have the security="restricted" attribute set before the iframe is | 
| bsw/jbe@1309 | 9944      *    inserted into the dom tree | 
| bsw/jbe@1309 | 9945      *  - Believe it or not but in IE "security" in document.createElement("iframe") is false, even | 
| bsw/jbe@1309 | 9946      *    though it supports it | 
| bsw/jbe@1309 | 9947      *  - When an iframe has security="restricted", in IE eval() & execScript() don't work anymore | 
| bsw/jbe@1309 | 9948      *  - IE doesn't fire the onload event when the content is inlined in the src attribute, therefore we rely | 
| bsw/jbe@1309 | 9949      *    on the onreadystatechange event | 
| bsw/jbe@1309 | 9950      */ | 
| bsw/jbe@1309 | 9951     _createIframe: function() { | 
| bsw/jbe@1309 | 9952       var that   = this, | 
| bsw/jbe@1309 | 9953           iframe = doc.createElement("iframe"); | 
| bsw/jbe@1309 | 9954       iframe.className = this.config.className; | 
| bsw/jbe@1309 | 9955       wysihtml.dom.setAttributes({ | 
| bsw/jbe@1309 | 9956         "security":           "restricted", | 
| bsw/jbe@1309 | 9957         "allowtransparency":  "true", | 
| bsw/jbe@1309 | 9958         "frameborder":        0, | 
| bsw/jbe@1309 | 9959         "width":              0, | 
| bsw/jbe@1309 | 9960         "height":             0, | 
| bsw/jbe@1309 | 9961         "marginwidth":        0, | 
| bsw/jbe@1309 | 9962         "marginheight":       0 | 
| bsw/jbe@1309 | 9963       }).on(iframe); | 
| bsw/jbe@1309 | 9964 | 
| bsw/jbe@1309 | 9965       // Setting the src like this prevents ssl warnings in IE6 | 
| bsw/jbe@1309 | 9966       if (wysihtml.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()) { | 
| bsw/jbe@1309 | 9967         iframe.src = "javascript:'<html></html>'"; | 
| bsw/jbe@1309 | 9968       } | 
| bsw/jbe@1309 | 9969 | 
| bsw/jbe@1309 | 9970       iframe.onload = function() { | 
| bsw/jbe@1309 | 9971         iframe.onreadystatechange = iframe.onload = null; | 
| bsw/jbe@1309 | 9972         that._onLoadIframe(iframe); | 
| bsw/jbe@1309 | 9973       }; | 
| bsw/jbe@1309 | 9974 | 
| bsw/jbe@1309 | 9975       iframe.onreadystatechange = function() { | 
| bsw/jbe@1309 | 9976         if (/loaded|complete/.test(iframe.readyState)) { | 
| bsw/jbe@1309 | 9977           iframe.onreadystatechange = iframe.onload = null; | 
| bsw/jbe@1309 | 9978           that._onLoadIframe(iframe); | 
| bsw/jbe@1309 | 9979         } | 
| bsw/jbe@1309 | 9980       }; | 
| bsw/jbe@1309 | 9981 | 
| bsw/jbe@1309 | 9982       return iframe; | 
| bsw/jbe@1309 | 9983     }, | 
| bsw/jbe@1309 | 9984 | 
| bsw/jbe@1309 | 9985     /** | 
| bsw/jbe@1309 | 9986      * Callback for when the iframe has finished loading | 
| bsw/jbe@1309 | 9987      */ | 
| bsw/jbe@1309 | 9988     _onLoadIframe: function(iframe) { | 
| bsw/jbe@1309 | 9989       // don't resume when the iframe got unloaded (eg. by removing it from the dom) | 
| bsw/jbe@1309 | 9990       if (!wysihtml.dom.contains(doc.documentElement, iframe)) { | 
| bsw/jbe@1309 | 9991         return; | 
| bsw/jbe@1309 | 9992       } | 
| bsw/jbe@1309 | 9993 | 
| bsw/jbe@1309 | 9994       var that           = this, | 
| bsw/jbe@1309 | 9995           iframeWindow   = iframe.contentWindow, | 
| bsw/jbe@1309 | 9996           iframeDocument = iframe.contentWindow.document, | 
| bsw/jbe@1309 | 9997           charset        = doc.characterSet || doc.charset || "utf-8", | 
| bsw/jbe@1309 | 9998           sandboxHtml    = this._getHtml({ | 
| bsw/jbe@1309 | 9999             charset:      charset, | 
| bsw/jbe@1309 | 10000             stylesheets:  this.config.stylesheets | 
| bsw/jbe@1309 | 10001           }); | 
| bsw/jbe@1309 | 10002 | 
| bsw/jbe@1309 | 10003       // Create the basic dom tree including proper DOCTYPE and charset | 
| bsw/jbe@1309 | 10004       iframeDocument.open("text/html", "replace"); | 
| bsw/jbe@1309 | 10005       iframeDocument.write(sandboxHtml); | 
| bsw/jbe@1309 | 10006       iframeDocument.close(); | 
| bsw/jbe@1309 | 10007 | 
| bsw/jbe@1309 | 10008       this.getWindow = function() { return iframe.contentWindow; }; | 
| bsw/jbe@1309 | 10009       this.getDocument = function() { return iframe.contentWindow.document; }; | 
| bsw/jbe@1309 | 10010 | 
| bsw/jbe@1309 | 10011       // Catch js errors and pass them to the parent's onerror event | 
| bsw/jbe@1309 | 10012       // addEventListener("error") doesn't work properly in some browsers | 
| bsw/jbe@1309 | 10013       // TODO: apparently this doesn't work in IE9! | 
| bsw/jbe@1309 | 10014       iframeWindow.onerror = function(errorMessage, fileName, lineNumber) { | 
| bsw/jbe@1309 | 10015         throw new Error("wysihtml.Sandbox: " + errorMessage, fileName, lineNumber); | 
| bsw/jbe@1309 | 10016       }; | 
| bsw/jbe@1309 | 10017 | 
| bsw/jbe@1309 | 10018       if (!wysihtml.browser.supportsSandboxedIframes()) { | 
| bsw/jbe@1309 | 10019         // Unset a bunch of sensitive variables | 
| bsw/jbe@1309 | 10020         // Please note: This isn't hack safe! | 
| bsw/jbe@1309 | 10021         // It more or less just takes care of basic attacks and prevents accidental theft of sensitive information | 
| bsw/jbe@1309 | 10022         // IE is secure though, which is the most important thing, since IE is the only browser, who | 
| bsw/jbe@1309 | 10023         // takes over scripts & styles into contentEditable elements when copied from external websites | 
| bsw/jbe@1309 | 10024         // or applications (Microsoft Word, ...) | 
| bsw/jbe@1309 | 10025         var i, length; | 
| bsw/jbe@1309 | 10026         for (i=0, length=windowProperties.length; i<length; i++) { | 
| bsw/jbe@1309 | 10027           this._unset(iframeWindow, windowProperties[i]); | 
| bsw/jbe@1309 | 10028         } | 
| bsw/jbe@1309 | 10029         for (i=0, length=windowProperties2.length; i<length; i++) { | 
| bsw/jbe@1309 | 10030           this._unset(iframeWindow, windowProperties2[i], wysihtml.EMPTY_FUNCTION); | 
| bsw/jbe@1309 | 10031         } | 
| bsw/jbe@1309 | 10032         for (i=0, length=documentProperties.length; i<length; i++) { | 
| bsw/jbe@1309 | 10033           this._unset(iframeDocument, documentProperties[i]); | 
| bsw/jbe@1309 | 10034         } | 
| bsw/jbe@1309 | 10035         // This doesn't work in Safari 5 | 
| bsw/jbe@1309 | 10036         // See http://stackoverflow.com/questions/992461/is-it-possible-to-override-document-cookie-in-webkit | 
| bsw/jbe@1309 | 10037         this._unset(iframeDocument, "cookie", "", true); | 
| bsw/jbe@1309 | 10038       } | 
| bsw/jbe@1309 | 10039 | 
| bsw/jbe@1309 | 10040       if (wysihtml.polyfills) { | 
| bsw/jbe@1309 | 10041         wysihtml.polyfills(iframeWindow, iframeDocument).apply(); | 
| bsw/jbe@1309 | 10042       } | 
| bsw/jbe@1309 | 10043 | 
| bsw/jbe@1309 | 10044       this.loaded = true; | 
| bsw/jbe@1309 | 10045 | 
| bsw/jbe@1309 | 10046       // Trigger the callback | 
| bsw/jbe@1309 | 10047       setTimeout(function() { that.callback(that); }, 0); | 
| bsw/jbe@1309 | 10048     }, | 
| bsw/jbe@1309 | 10049 | 
| bsw/jbe@1309 | 10050     _getHtml: function(templateVars) { | 
| bsw/jbe@1309 | 10051       var stylesheets = templateVars.stylesheets, | 
| bsw/jbe@1309 | 10052           html        = "", | 
| bsw/jbe@1309 | 10053           i           = 0, | 
| bsw/jbe@1309 | 10054           length; | 
| bsw/jbe@1309 | 10055       stylesheets = typeof(stylesheets) === "string" ? [stylesheets] : stylesheets; | 
| bsw/jbe@1309 | 10056       if (stylesheets) { | 
| bsw/jbe@1309 | 10057         length = stylesheets.length; | 
| bsw/jbe@1309 | 10058         for (; i<length; i++) { | 
| bsw/jbe@1309 | 10059           html += '<link rel="stylesheet" href="' + stylesheets[i] + '">'; | 
| bsw/jbe@1309 | 10060         } | 
| bsw/jbe@1309 | 10061       } | 
| bsw/jbe@1309 | 10062       templateVars.stylesheets = html; | 
| bsw/jbe@1309 | 10063 | 
| bsw/jbe@1309 | 10064       return wysihtml.lang.string( | 
| bsw/jbe@1309 | 10065         '<!DOCTYPE html><html><head>' | 
| bsw/jbe@1309 | 10066         + '<meta charset="#{charset}">#{stylesheets}</head>' | 
| bsw/jbe@1309 | 10067         + '<body></body></html>' | 
| bsw/jbe@1309 | 10068       ).interpolate(templateVars); | 
| bsw/jbe@1309 | 10069     }, | 
| bsw/jbe@1309 | 10070 | 
| bsw/jbe@1309 | 10071     /** | 
| bsw/jbe@1309 | 10072      * Method to unset/override existing variables | 
| bsw/jbe@1309 | 10073      * @example | 
| bsw/jbe@1309 | 10074      *    // Make cookie unreadable and unwritable | 
| bsw/jbe@1309 | 10075      *    this._unset(document, "cookie", "", true); | 
| bsw/jbe@1309 | 10076      */ | 
| bsw/jbe@1309 | 10077     _unset: function(object, property, value, setter) { | 
| bsw/jbe@1309 | 10078       try { object[property] = value; } catch(e) {} | 
| bsw/jbe@1309 | 10079 | 
| bsw/jbe@1309 | 10080       try { object.__defineGetter__(property, function() { return value; }); } catch(e) {} | 
| bsw/jbe@1309 | 10081       if (setter) { | 
| bsw/jbe@1309 | 10082         try { object.__defineSetter__(property, function() {}); } catch(e) {} | 
| bsw/jbe@1309 | 10083       } | 
| bsw/jbe@1309 | 10084 | 
| bsw/jbe@1309 | 10085       if (!wysihtml.browser.crashesWhenDefineProperty(property)) { | 
| bsw/jbe@1309 | 10086         try { | 
| bsw/jbe@1309 | 10087           var config = { | 
| bsw/jbe@1309 | 10088             get: function() { return value; } | 
| bsw/jbe@1309 | 10089           }; | 
| bsw/jbe@1309 | 10090           if (setter) { | 
| bsw/jbe@1309 | 10091             config.set = function() {}; | 
| bsw/jbe@1309 | 10092           } | 
| bsw/jbe@1309 | 10093           Object.defineProperty(object, property, config); | 
| bsw/jbe@1309 | 10094         } catch(e) {} | 
| bsw/jbe@1309 | 10095       } | 
| bsw/jbe@1309 | 10096     } | 
| bsw/jbe@1309 | 10097   }); | 
| bsw/jbe@1309 | 10098 })(wysihtml); | 
| bsw/jbe@1309 | 10099 | 
| bsw/jbe@1309 | 10100 (function() { | 
| bsw/jbe@1309 | 10101   var mapping = { | 
| bsw/jbe@1309 | 10102     "className": "class" | 
| bsw/jbe@1309 | 10103   }; | 
| bsw/jbe@1309 | 10104   wysihtml.dom.setAttributes = function(attributes) { | 
| bsw/jbe@1309 | 10105     return { | 
| bsw/jbe@1309 | 10106       on: function(element) { | 
| bsw/jbe@1309 | 10107         for (var i in attributes) { | 
| bsw/jbe@1309 | 10108           element.setAttribute(mapping[i] || i, attributes[i]); | 
| bsw/jbe@1309 | 10109         } | 
| bsw/jbe@1309 | 10110       } | 
| bsw/jbe@1309 | 10111     }; | 
| bsw/jbe@1309 | 10112   }; | 
| bsw/jbe@1309 | 10113 })(); | 
| bsw/jbe@1309 | 10114 | 
| bsw/jbe@1309 | 10115 wysihtml.dom.setStyles = function(styles) { | 
| bsw/jbe@1309 | 10116   return { | 
| bsw/jbe@1309 | 10117     on: function(element) { | 
| bsw/jbe@1309 | 10118       var style = element.style; | 
| bsw/jbe@1309 | 10119       if (typeof(styles) === "string") { | 
| bsw/jbe@1309 | 10120         style.cssText += ";" + styles; | 
| bsw/jbe@1309 | 10121         return; | 
| bsw/jbe@1309 | 10122       } | 
| bsw/jbe@1309 | 10123       for (var i in styles) { | 
| bsw/jbe@1309 | 10124         if (i === "float") { | 
| bsw/jbe@1309 | 10125           style.cssFloat = styles[i]; | 
| bsw/jbe@1309 | 10126           style.styleFloat = styles[i]; | 
| bsw/jbe@1309 | 10127         } else { | 
| bsw/jbe@1309 | 10128           style[i] = styles[i]; | 
| bsw/jbe@1309 | 10129         } | 
| bsw/jbe@1309 | 10130       } | 
| bsw/jbe@1309 | 10131     } | 
| bsw/jbe@1309 | 10132   }; | 
| bsw/jbe@1309 | 10133 }; | 
| bsw/jbe@1309 | 10134 | 
| bsw/jbe@1309 | 10135 /** | 
| bsw/jbe@1309 | 10136  * Simulate HTML5 placeholder attribute | 
| bsw/jbe@1309 | 10137  * | 
| bsw/jbe@1309 | 10138  * Needed since | 
| bsw/jbe@1309 | 10139  *    - div[contentEditable] elements don't support it | 
| bsw/jbe@1309 | 10140  *    - older browsers (such as IE8 and Firefox 3.6) don't support it at all | 
| bsw/jbe@1309 | 10141  * | 
| bsw/jbe@1309 | 10142  * @param {Object} parent Instance of main wysihtml.Editor class | 
| bsw/jbe@1309 | 10143  * @param {Element} view Instance of wysihtml.views.* class | 
| bsw/jbe@1309 | 10144  * @param {String} placeholderText | 
| bsw/jbe@1309 | 10145  * | 
| bsw/jbe@1309 | 10146  * @example | 
| bsw/jbe@1309 | 10147  *    wysihtml.dom.simulatePlaceholder(this, composer, "Foobar"); | 
| bsw/jbe@1309 | 10148  */ | 
| bsw/jbe@1309 | 10149 (function(dom) { | 
| bsw/jbe@1309 | 10150   dom.simulatePlaceholder = function(editor, view, placeholderText, placeholderClassName) { | 
| bsw/jbe@1309 | 10151     var CLASS_NAME = placeholderClassName || "wysihtml-placeholder", | 
| bsw/jbe@1309 | 10152         unset = function() { | 
| bsw/jbe@1309 | 10153           var composerIsVisible   = view.element.offsetWidth > 0 && view.element.offsetHeight > 0; | 
| bsw/jbe@1309 | 10154           if (view.hasPlaceholderSet()) { | 
| bsw/jbe@1309 | 10155             view.clear(); | 
| bsw/jbe@1309 | 10156             view.element.focus(); | 
| bsw/jbe@1309 | 10157             if (composerIsVisible ) { | 
| bsw/jbe@1309 | 10158               setTimeout(function() { | 
| bsw/jbe@1309 | 10159                 var sel = view.selection.getSelection(); | 
| bsw/jbe@1309 | 10160                 if (!sel.focusNode || !sel.anchorNode) { | 
| bsw/jbe@1309 | 10161                   view.selection.selectNode(view.element.firstChild || view.element); | 
| bsw/jbe@1309 | 10162                 } | 
| bsw/jbe@1309 | 10163               }, 0); | 
| bsw/jbe@1309 | 10164             } | 
| bsw/jbe@1309 | 10165           } | 
| bsw/jbe@1309 | 10166           view.placeholderSet = false; | 
| bsw/jbe@1309 | 10167           dom.removeClass(view.element, CLASS_NAME); | 
| bsw/jbe@1309 | 10168         }, | 
| bsw/jbe@1309 | 10169         set = function() { | 
| bsw/jbe@1309 | 10170           if (view.isEmpty() && !view.placeholderSet) { | 
| bsw/jbe@1309 | 10171             view.placeholderSet = true; | 
| bsw/jbe@1309 | 10172             view.setValue(placeholderText, false); | 
| bsw/jbe@1309 | 10173             dom.addClass(view.element, CLASS_NAME); | 
| bsw/jbe@1309 | 10174           } | 
| bsw/jbe@1309 | 10175         }; | 
| bsw/jbe@1309 | 10176 | 
| bsw/jbe@1309 | 10177     editor | 
| bsw/jbe@1309 | 10178       .on("set_placeholder", set) | 
| bsw/jbe@1309 | 10179       .on("unset_placeholder", unset) | 
| bsw/jbe@1309 | 10180       .on("focus:composer", unset) | 
| bsw/jbe@1309 | 10181       .on("paste:composer", unset) | 
| bsw/jbe@1309 | 10182       .on("blur:composer", set); | 
| bsw/jbe@1309 | 10183 | 
| bsw/jbe@1309 | 10184     set(); | 
| bsw/jbe@1309 | 10185   }; | 
| bsw/jbe@1309 | 10186 })(wysihtml.dom); | 
| bsw/jbe@1309 | 10187 | 
| bsw/jbe@1309 | 10188 (function(dom) { | 
| bsw/jbe@1309 | 10189   var documentElement = document.documentElement; | 
| bsw/jbe@1309 | 10190   if ("textContent" in documentElement) { | 
| bsw/jbe@1309 | 10191     dom.setTextContent = function(element, text) { | 
| bsw/jbe@1309 | 10192       element.textContent = text; | 
| bsw/jbe@1309 | 10193     }; | 
| bsw/jbe@1309 | 10194 | 
| bsw/jbe@1309 | 10195     dom.getTextContent = function(element) { | 
| bsw/jbe@1309 | 10196       return element.textContent; | 
| bsw/jbe@1309 | 10197     }; | 
| bsw/jbe@1309 | 10198   } else if ("innerText" in documentElement) { | 
| bsw/jbe@1309 | 10199     dom.setTextContent = function(element, text) { | 
| bsw/jbe@1309 | 10200       element.innerText = text; | 
| bsw/jbe@1309 | 10201     }; | 
| bsw/jbe@1309 | 10202 | 
| bsw/jbe@1309 | 10203     dom.getTextContent = function(element) { | 
| bsw/jbe@1309 | 10204       return element.innerText; | 
| bsw/jbe@1309 | 10205     }; | 
| bsw/jbe@1309 | 10206   } else { | 
| bsw/jbe@1309 | 10207     dom.setTextContent = function(element, text) { | 
| bsw/jbe@1309 | 10208       element.nodeValue = text; | 
| bsw/jbe@1309 | 10209     }; | 
| bsw/jbe@1309 | 10210 | 
| bsw/jbe@1309 | 10211     dom.getTextContent = function(element) { | 
| bsw/jbe@1309 | 10212       return element.nodeValue; | 
| bsw/jbe@1309 | 10213     }; | 
| bsw/jbe@1309 | 10214   } | 
| bsw/jbe@1309 | 10215 })(wysihtml.dom); | 
| bsw/jbe@1309 | 10216 | 
| bsw/jbe@1309 | 10217 /* Unwraps element and returns list of childNodes that the node contained. | 
| bsw/jbe@1309 | 10218  * | 
| bsw/jbe@1309 | 10219  * Example: | 
| bsw/jbe@1309 | 10220  *    var childnodes = wysihtml.dom.unwrap(document.querySelector('.unwrap-me')); | 
| bsw/jbe@1309 | 10221 */ | 
| bsw/jbe@1309 | 10222 | 
| bsw/jbe@1309 | 10223 wysihtml.dom.unwrap = function(node) { | 
| bsw/jbe@1309 | 10224   var children = []; | 
| bsw/jbe@1309 | 10225   if (node.parentNode) { | 
| bsw/jbe@1309 | 10226     while (node.lastChild) { | 
| bsw/jbe@1309 | 10227       children.unshift(node.lastChild); | 
| bsw/jbe@1309 | 10228       wysihtml.dom.insert(node.lastChild).after(node); | 
| bsw/jbe@1309 | 10229     } | 
| bsw/jbe@1309 | 10230     node.parentNode.removeChild(node); | 
| bsw/jbe@1309 | 10231   } | 
| bsw/jbe@1309 | 10232   return children; | 
| bsw/jbe@1309 | 10233 }; | 
| bsw/jbe@1309 | 10234 | 
| bsw/jbe@1309 | 10235 /** | 
| bsw/jbe@1309 | 10236  * Fix most common html formatting misbehaviors of browsers implementation when inserting | 
| bsw/jbe@1309 | 10237  * content via copy & paste contentEditable | 
| bsw/jbe@1309 | 10238  * | 
| bsw/jbe@1309 | 10239  * @author Christopher Blum | 
| bsw/jbe@1309 | 10240  */ | 
| bsw/jbe@1309 | 10241 wysihtml.quirks.cleanPastedHTML = (function() { | 
| bsw/jbe@1309 | 10242 | 
| bsw/jbe@1309 | 10243   var styleToRegex = function (styleStr) { | 
| bsw/jbe@1309 | 10244     var trimmedStr = wysihtml.lang.string(styleStr).trim(), | 
| bsw/jbe@1309 | 10245         escapedStr = trimmedStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); | 
| bsw/jbe@1309 | 10246 | 
| bsw/jbe@1309 | 10247     return new RegExp("^((?!^" + escapedStr + "$).)*$", "i"); | 
| bsw/jbe@1309 | 10248   }; | 
| bsw/jbe@1309 | 10249 | 
| bsw/jbe@1309 | 10250   var extendRulesWithStyleExceptions = function (rules, exceptStyles) { | 
| bsw/jbe@1309 | 10251     var newRules = wysihtml.lang.object(rules).clone(true), | 
| bsw/jbe@1309 | 10252         tag, style; | 
| bsw/jbe@1309 | 10253 | 
| bsw/jbe@1309 | 10254     for (tag in newRules.tags) { | 
| bsw/jbe@1309 | 10255 | 
| bsw/jbe@1309 | 10256       if (newRules.tags.hasOwnProperty(tag)) { | 
| bsw/jbe@1309 | 10257         if (newRules.tags[tag].keep_styles) { | 
| bsw/jbe@1309 | 10258           for (style in newRules.tags[tag].keep_styles) { | 
| bsw/jbe@1309 | 10259             if (newRules.tags[tag].keep_styles.hasOwnProperty(style)) { | 
| bsw/jbe@1309 | 10260               if (exceptStyles[style]) { | 
| bsw/jbe@1309 | 10261                 newRules.tags[tag].keep_styles[style] = styleToRegex(exceptStyles[style]); | 
| bsw/jbe@1309 | 10262               } | 
| bsw/jbe@1309 | 10263             } | 
| bsw/jbe@1309 | 10264           } | 
| bsw/jbe@1309 | 10265         } | 
| bsw/jbe@1309 | 10266       } | 
| bsw/jbe@1309 | 10267     } | 
| bsw/jbe@1309 | 10268 | 
| bsw/jbe@1309 | 10269     return newRules; | 
| bsw/jbe@1309 | 10270   }; | 
| bsw/jbe@1309 | 10271 | 
| bsw/jbe@1309 | 10272   var pickRuleset = function(ruleset, html) { | 
| bsw/jbe@1309 | 10273     var pickedSet, defaultSet; | 
| bsw/jbe@1309 | 10274 | 
| bsw/jbe@1309 | 10275     if (!ruleset) { | 
| bsw/jbe@1309 | 10276       return null; | 
| bsw/jbe@1309 | 10277     } | 
| bsw/jbe@1309 | 10278 | 
| bsw/jbe@1309 | 10279     for (var i = 0, max = ruleset.length; i < max; i++) { | 
| bsw/jbe@1309 | 10280       if (!ruleset[i].condition) { | 
| bsw/jbe@1309 | 10281         defaultSet = ruleset[i].set; | 
| bsw/jbe@1309 | 10282       } | 
| bsw/jbe@1309 | 10283       if (ruleset[i].condition && ruleset[i].condition.test(html)) { | 
| bsw/jbe@1309 | 10284         return ruleset[i].set; | 
| bsw/jbe@1309 | 10285       } | 
| bsw/jbe@1309 | 10286     } | 
| bsw/jbe@1309 | 10287 | 
| bsw/jbe@1309 | 10288     return defaultSet; | 
| bsw/jbe@1309 | 10289   }; | 
| bsw/jbe@1309 | 10290 | 
| bsw/jbe@1309 | 10291   return function(html, options) { | 
| bsw/jbe@1309 | 10292     var exceptStyles = { | 
| bsw/jbe@1309 | 10293           'color': wysihtml.dom.getStyle("color").from(options.referenceNode), | 
| bsw/jbe@1309 | 10294           'fontSize': wysihtml.dom.getStyle("font-size").from(options.referenceNode) | 
| bsw/jbe@1309 | 10295         }, | 
| bsw/jbe@1309 | 10296         rules = extendRulesWithStyleExceptions(pickRuleset(options.rules, html) || {}, exceptStyles), | 
| bsw/jbe@1309 | 10297         newHtml; | 
| bsw/jbe@1309 | 10298 | 
| bsw/jbe@1309 | 10299     newHtml = wysihtml.dom.parse(html, { | 
| bsw/jbe@1309 | 10300       "rules": rules, | 
| bsw/jbe@1309 | 10301       "cleanUp": true, // <span> elements, empty or without attributes, should be removed/replaced with their content | 
| bsw/jbe@1309 | 10302       "context": options.referenceNode.ownerDocument, | 
| bsw/jbe@1309 | 10303       "uneditableClass": options.uneditableClass, | 
| bsw/jbe@1309 | 10304       "clearInternals" : true, // don't paste temprorary selection and other markings | 
| bsw/jbe@1309 | 10305       "unjoinNbsps" : true | 
| bsw/jbe@1309 | 10306     }); | 
| bsw/jbe@1309 | 10307 | 
| bsw/jbe@1309 | 10308     return newHtml; | 
| bsw/jbe@1309 | 10309   }; | 
| bsw/jbe@1309 | 10310 | 
| bsw/jbe@1309 | 10311 })(); | 
| bsw/jbe@1309 | 10312 | 
| bsw/jbe@1309 | 10313 /** | 
| bsw/jbe@1309 | 10314  * IE and Opera leave an empty paragraph in the contentEditable element after clearing it | 
| bsw/jbe@1309 | 10315  * | 
| bsw/jbe@1309 | 10316  * @param {Object} contentEditableElement The contentEditable element to observe for clearing events | 
| bsw/jbe@1309 | 10317  * @exaple | 
| bsw/jbe@1309 | 10318  *    wysihtml.quirks.ensureProperClearing(myContentEditableElement); | 
| bsw/jbe@1309 | 10319  */ | 
| bsw/jbe@1309 | 10320 wysihtml.quirks.ensureProperClearing = (function() { | 
| bsw/jbe@1309 | 10321   var clearIfNecessary = function() { | 
| bsw/jbe@1309 | 10322     var element = this; | 
| bsw/jbe@1309 | 10323     setTimeout(function() { | 
| bsw/jbe@1309 | 10324       var innerHTML = element.innerHTML.toLowerCase(); | 
| bsw/jbe@1309 | 10325       if (innerHTML == "<p> </p>" || | 
| bsw/jbe@1309 | 10326           innerHTML == "<p> </p><p> </p>") { | 
| bsw/jbe@1309 | 10327         element.innerHTML = ""; | 
| bsw/jbe@1309 | 10328       } | 
| bsw/jbe@1309 | 10329     }, 0); | 
| bsw/jbe@1309 | 10330   }; | 
| bsw/jbe@1309 | 10331 | 
| bsw/jbe@1309 | 10332   return function(composer) { | 
| bsw/jbe@1309 | 10333     wysihtml.dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary); | 
| bsw/jbe@1309 | 10334   }; | 
| bsw/jbe@1309 | 10335 })(); | 
| bsw/jbe@1309 | 10336 | 
| bsw/jbe@1309 | 10337 // See https://bugzilla.mozilla.org/show_bug.cgi?id=664398 | 
| bsw/jbe@1309 | 10338 // | 
| bsw/jbe@1309 | 10339 // In Firefox this: | 
| bsw/jbe@1309 | 10340 //      var d = document.createElement("div"); | 
| bsw/jbe@1309 | 10341 //      d.innerHTML ='<a href="~"></a>'; | 
| bsw/jbe@1309 | 10342 //      d.innerHTML; | 
| bsw/jbe@1309 | 10343 // will result in: | 
| bsw/jbe@1309 | 10344 //      <a href="%7E"></a> | 
| bsw/jbe@1309 | 10345 // which is wrong | 
| bsw/jbe@1309 | 10346 (function(wysihtml) { | 
| bsw/jbe@1309 | 10347   var TILDE_ESCAPED = "%7E"; | 
| bsw/jbe@1309 | 10348   wysihtml.quirks.getCorrectInnerHTML = function(element) { | 
| bsw/jbe@1309 | 10349     var innerHTML = element.innerHTML; | 
| bsw/jbe@1309 | 10350     if (innerHTML.indexOf(TILDE_ESCAPED) === -1) { | 
| bsw/jbe@1309 | 10351       return innerHTML; | 
| bsw/jbe@1309 | 10352     } | 
| bsw/jbe@1309 | 10353 | 
| bsw/jbe@1309 | 10354     var elementsWithTilde = element.querySelectorAll("[href*='~'], [src*='~']"), | 
| bsw/jbe@1309 | 10355         url, | 
| bsw/jbe@1309 | 10356         urlToSearch, | 
| bsw/jbe@1309 | 10357         length, | 
| bsw/jbe@1309 | 10358         i; | 
| bsw/jbe@1309 | 10359     for (i=0, length=elementsWithTilde.length; i<length; i++) { | 
| bsw/jbe@1309 | 10360       url         = elementsWithTilde[i].href || elementsWithTilde[i].src; | 
| bsw/jbe@1309 | 10361       urlToSearch = wysihtml.lang.string(url).replace("~").by(TILDE_ESCAPED); | 
| bsw/jbe@1309 | 10362       innerHTML   = wysihtml.lang.string(innerHTML).replace(urlToSearch).by(url); | 
| bsw/jbe@1309 | 10363     } | 
| bsw/jbe@1309 | 10364     return innerHTML; | 
| bsw/jbe@1309 | 10365   }; | 
| bsw/jbe@1309 | 10366 })(wysihtml); | 
| bsw/jbe@1309 | 10367 | 
| bsw/jbe@1309 | 10368 /** | 
| bsw/jbe@1309 | 10369  * Force rerendering of a given element | 
| bsw/jbe@1309 | 10370  * Needed to fix display misbehaviors of IE | 
| bsw/jbe@1309 | 10371  * | 
| bsw/jbe@1309 | 10372  * @param {Element} element The element object which needs to be rerendered | 
| bsw/jbe@1309 | 10373  * @example | 
| bsw/jbe@1309 | 10374  *    wysihtml.quirks.redraw(document.body); | 
| bsw/jbe@1309 | 10375  */ | 
| bsw/jbe@1309 | 10376 (function(wysihtml) { | 
| bsw/jbe@1309 | 10377   var CLASS_NAME = "wysihtml-quirks-redraw"; | 
| bsw/jbe@1309 | 10378 | 
| bsw/jbe@1309 | 10379   wysihtml.quirks.redraw = function(element) { | 
| bsw/jbe@1309 | 10380     wysihtml.dom.addClass(element, CLASS_NAME); | 
| bsw/jbe@1309 | 10381     wysihtml.dom.removeClass(element, CLASS_NAME); | 
| bsw/jbe@1309 | 10382 | 
| bsw/jbe@1309 | 10383     // Following hack is needed for firefox to make sure that image resize handles are properly removed | 
| bsw/jbe@1309 | 10384     try { | 
| bsw/jbe@1309 | 10385       var doc = element.ownerDocument; | 
| bsw/jbe@1309 | 10386       doc.execCommand("italic", false, null); | 
| bsw/jbe@1309 | 10387       doc.execCommand("italic", false, null); | 
| bsw/jbe@1309 | 10388     } catch(e) {} | 
| bsw/jbe@1309 | 10389   }; | 
| bsw/jbe@1309 | 10390 })(wysihtml); | 
| bsw/jbe@1309 | 10391 | 
| bsw/jbe@1309 | 10392 (function(wysihtml) { | 
| bsw/jbe@1309 | 10393 | 
| bsw/jbe@1309 | 10394   // List of supported color format parsing methods | 
| bsw/jbe@1309 | 10395   // If radix is not defined 10 is expected as default | 
| bsw/jbe@1309 | 10396   var colorParseMethods = { | 
| bsw/jbe@1309 | 10397         rgba : { | 
| bsw/jbe@1309 | 10398           regex: /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i, | 
| bsw/jbe@1309 | 10399           name: "rgba" | 
| bsw/jbe@1309 | 10400         }, | 
| bsw/jbe@1309 | 10401         rgb : { | 
| bsw/jbe@1309 | 10402           regex: /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i, | 
| bsw/jbe@1309 | 10403           name: "rgb" | 
| bsw/jbe@1309 | 10404         }, | 
| bsw/jbe@1309 | 10405         hex6 : { | 
| bsw/jbe@1309 | 10406           regex: /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i, | 
| bsw/jbe@1309 | 10407           name: "hex", | 
| bsw/jbe@1309 | 10408           radix: 16 | 
| bsw/jbe@1309 | 10409         }, | 
| bsw/jbe@1309 | 10410         hex3 : { | 
| bsw/jbe@1309 | 10411           regex: /^#([0-9a-f])([0-9a-f])([0-9a-f])/i, | 
| bsw/jbe@1309 | 10412           name: "hex", | 
| bsw/jbe@1309 | 10413           radix: 16 | 
| bsw/jbe@1309 | 10414         } | 
| bsw/jbe@1309 | 10415       }, | 
| bsw/jbe@1309 | 10416       // Takes a style key name as an argument and makes a regex that can be used to the match key:value pair from style string | 
| bsw/jbe@1309 | 10417       makeParamRegExp = function (p) { | 
| bsw/jbe@1309 | 10418         return new RegExp("(^|\\s|;)" + p + "\\s*:\\s*[^;$]+", "gi"); | 
| bsw/jbe@1309 | 10419       }; | 
| bsw/jbe@1309 | 10420 | 
| bsw/jbe@1309 | 10421   // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns suitable parsing method for it | 
| bsw/jbe@1309 | 10422   function getColorParseMethod (colorStr) { | 
| bsw/jbe@1309 | 10423     var prop, colorTypeConf; | 
| bsw/jbe@1309 | 10424 | 
| bsw/jbe@1309 | 10425     for (prop in colorParseMethods) { | 
| bsw/jbe@1309 | 10426       if (!colorParseMethods.hasOwnProperty(prop)) { continue; } | 
| bsw/jbe@1309 | 10427 | 
| bsw/jbe@1309 | 10428       colorTypeConf = colorParseMethods[prop]; | 
| bsw/jbe@1309 | 10429 | 
| bsw/jbe@1309 | 10430       if (colorTypeConf.regex.test(colorStr)) { | 
| bsw/jbe@1309 | 10431         return colorTypeConf; | 
| bsw/jbe@1309 | 10432       } | 
| bsw/jbe@1309 | 10433     } | 
| bsw/jbe@1309 | 10434   } | 
| bsw/jbe@1309 | 10435 | 
| bsw/jbe@1309 | 10436   // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns the type of that color format "hex", "rgb", "rgba". | 
| bsw/jbe@1309 | 10437   function getColorFormat (colorStr) { | 
| bsw/jbe@1309 | 10438     var type = getColorParseMethod(colorStr); | 
| bsw/jbe@1309 | 10439 | 
| bsw/jbe@1309 | 10440     return type ? type.name : undefined; | 
| bsw/jbe@1309 | 10441   } | 
| bsw/jbe@1309 | 10442 | 
| bsw/jbe@1309 | 10443   // Public API functions for styleParser | 
| bsw/jbe@1309 | 10444   wysihtml.quirks.styleParser = { | 
| bsw/jbe@1309 | 10445 | 
| bsw/jbe@1309 | 10446     // Takes color string value as an argument and returns suitable parsing method for it | 
| bsw/jbe@1309 | 10447     getColorParseMethod : getColorParseMethod, | 
| bsw/jbe@1309 | 10448 | 
| bsw/jbe@1309 | 10449     // Takes color string value as an argument and returns the type of that color format "hex", "rgb", "rgba". | 
| bsw/jbe@1309 | 10450     getColorFormat : getColorFormat, | 
| bsw/jbe@1309 | 10451 | 
| bsw/jbe@1309 | 10452     /* Parses a color string to and array of [red, green, blue, alpha]. | 
| bsw/jbe@1309 | 10453      * paramName: optional argument to parse color value directly from style string parameter | 
| bsw/jbe@1309 | 10454      * | 
| bsw/jbe@1309 | 10455      * Examples: | 
| bsw/jbe@1309 | 10456      *    var colorArray = wysihtml.quirks.styleParser.parseColor("#ABC");            // [170, 187, 204, 1] | 
| bsw/jbe@1309 | 10457      *    var colorArray = wysihtml.quirks.styleParser.parseColor("#AABBCC");         // [170, 187, 204, 1] | 
| bsw/jbe@1309 | 10458      *    var colorArray = wysihtml.quirks.styleParser.parseColor("rgb(1,2,3)");      // [1, 2, 3, 1] | 
| bsw/jbe@1309 | 10459      *    var colorArray = wysihtml.quirks.styleParser.parseColor("rgba(1,2,3,0.5)"); // [1, 2, 3, 0.5] | 
| bsw/jbe@1309 | 10460      * | 
| bsw/jbe@1309 | 10461      *    var colorArray = wysihtml.quirks.styleParser.parseColor("background-color: #ABC; color: #000;", "background-color"); // [170, 187, 204, 1] | 
| bsw/jbe@1309 | 10462      *    var colorArray = wysihtml.quirks.styleParser.parseColor("background-color: #ABC; color: #000;", "color");            // [0, 0, 0, 1] | 
| bsw/jbe@1309 | 10463      */ | 
| bsw/jbe@1309 | 10464     parseColor : function (stylesStr, paramName) { | 
| bsw/jbe@1309 | 10465       var paramsRegex, params, colorType, colorMatch, radix, | 
| bsw/jbe@1309 | 10466           colorStr = stylesStr; | 
| bsw/jbe@1309 | 10467 | 
| bsw/jbe@1309 | 10468       if (paramName) { | 
| bsw/jbe@1309 | 10469         paramsRegex = makeParamRegExp(paramName); | 
| bsw/jbe@1309 | 10470 | 
| bsw/jbe@1309 | 10471         if (!(params = stylesStr.match(paramsRegex))) { return false; } | 
| bsw/jbe@1309 | 10472 | 
| bsw/jbe@1309 | 10473         params = params.pop().split(":")[1]; | 
| bsw/jbe@1309 | 10474         colorStr = wysihtml.lang.string(params).trim(); | 
| bsw/jbe@1309 | 10475       } | 
| bsw/jbe@1309 | 10476 | 
| bsw/jbe@1309 | 10477       if (!(colorType = getColorParseMethod(colorStr))) { return false; } | 
| bsw/jbe@1309 | 10478       if (!(colorMatch = colorStr.match(colorType.regex))) { return false; } | 
| bsw/jbe@1309 | 10479 | 
| bsw/jbe@1309 | 10480       radix = colorType.radix || 10; | 
| bsw/jbe@1309 | 10481 | 
| bsw/jbe@1309 | 10482       if (colorType === colorParseMethods.hex3) { | 
| bsw/jbe@1309 | 10483         colorMatch.shift(); | 
| bsw/jbe@1309 | 10484         colorMatch.push(1); | 
| bsw/jbe@1309 | 10485         return wysihtml.lang.array(colorMatch).map(function(d, idx) { | 
| bsw/jbe@1309 | 10486           return (idx < 3) ? (parseInt(d, radix) * radix) + parseInt(d, radix): parseFloat(d); | 
| bsw/jbe@1309 | 10487         }); | 
| bsw/jbe@1309 | 10488       } | 
| bsw/jbe@1309 | 10489 | 
| bsw/jbe@1309 | 10490       colorMatch.shift(); | 
| bsw/jbe@1309 | 10491 | 
| bsw/jbe@1309 | 10492       if (!colorMatch[3]) { | 
| bsw/jbe@1309 | 10493         colorMatch.push(1); | 
| bsw/jbe@1309 | 10494       } | 
| bsw/jbe@1309 | 10495 | 
| bsw/jbe@1309 | 10496       return wysihtml.lang.array(colorMatch).map(function(d, idx) { | 
| bsw/jbe@1309 | 10497         return (idx < 3) ? parseInt(d, radix): parseFloat(d); | 
| bsw/jbe@1309 | 10498       }); | 
| bsw/jbe@1309 | 10499     }, | 
| bsw/jbe@1309 | 10500 | 
| bsw/jbe@1309 | 10501     /* Takes rgba color array [r,g,b,a] as a value and formats it to color string with given format type | 
| bsw/jbe@1309 | 10502      * If no format is given, rgba/rgb is returned based on alpha value | 
| bsw/jbe@1309 | 10503      * | 
| bsw/jbe@1309 | 10504      * Example: | 
| bsw/jbe@1309 | 10505      *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "hash");  // "#AABBCC" | 
| bsw/jbe@1309 | 10506      *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "hex");  // "AABBCC" | 
| bsw/jbe@1309 | 10507      *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "csv");  // "170, 187, 204, 1" | 
| bsw/jbe@1309 | 10508      *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "rgba");  // "rgba(170,187,204,1)" | 
| bsw/jbe@1309 | 10509      *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "rgb");  // "rgb(170,187,204)" | 
| bsw/jbe@1309 | 10510      * | 
| bsw/jbe@1309 | 10511      *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 0.5]);  // "rgba(170,187,204,0.5)" | 
| bsw/jbe@1309 | 10512      *    var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1]);  // "rgb(170,187,204)" | 
| bsw/jbe@1309 | 10513      */ | 
| bsw/jbe@1309 | 10514     unparseColor: function(val, colorFormat) { | 
| bsw/jbe@1309 | 10515       var hexRadix = 16; | 
| bsw/jbe@1309 | 10516 | 
| bsw/jbe@1309 | 10517       if (colorFormat === "hex") { | 
| bsw/jbe@1309 | 10518         return (val[0].toString(hexRadix) + val[1].toString(hexRadix) + val[2].toString(hexRadix)).toUpperCase(); | 
| bsw/jbe@1309 | 10519       } else if (colorFormat === "hash") { | 
| bsw/jbe@1309 | 10520         return "#" + (val[0].toString(hexRadix) + val[1].toString(hexRadix) + val[2].toString(hexRadix)).toUpperCase(); | 
| bsw/jbe@1309 | 10521       } else if (colorFormat === "rgb") { | 
| bsw/jbe@1309 | 10522         return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")"; | 
| bsw/jbe@1309 | 10523       } else if (colorFormat === "rgba") { | 
| bsw/jbe@1309 | 10524         return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")"; | 
| bsw/jbe@1309 | 10525       } else if (colorFormat === "csv") { | 
| bsw/jbe@1309 | 10526         return  val[0] + "," + val[1] + "," + val[2] + "," + val[3]; | 
| bsw/jbe@1309 | 10527       } | 
| bsw/jbe@1309 | 10528 | 
| bsw/jbe@1309 | 10529       if (val[3] && val[3] !== 1) { | 
| bsw/jbe@1309 | 10530         return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")"; | 
| bsw/jbe@1309 | 10531       } else { | 
| bsw/jbe@1309 | 10532         return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")"; | 
| bsw/jbe@1309 | 10533       } | 
| bsw/jbe@1309 | 10534     }, | 
| bsw/jbe@1309 | 10535 | 
| bsw/jbe@1309 | 10536     // Parses font size value from style string | 
| bsw/jbe@1309 | 10537     parseFontSize: function(stylesStr) { | 
| bsw/jbe@1309 | 10538       var params = stylesStr.match(makeParamRegExp("font-size")); | 
| bsw/jbe@1309 | 10539       if (params) { | 
| bsw/jbe@1309 | 10540         return wysihtml.lang.string(params[params.length - 1].split(":")[1]).trim(); | 
| bsw/jbe@1309 | 10541       } | 
| bsw/jbe@1309 | 10542       return false; | 
| bsw/jbe@1309 | 10543     } | 
| bsw/jbe@1309 | 10544   }; | 
| bsw/jbe@1309 | 10545 | 
| bsw/jbe@1309 | 10546 })(wysihtml); | 
| bsw/jbe@1309 | 10547 | 
| bsw/jbe@1309 | 10548 /** | 
| bsw/jbe@1309 | 10549  * Selection API | 
| bsw/jbe@1309 | 10550  * | 
| bsw/jbe@1309 | 10551  * @example | 
| bsw/jbe@1309 | 10552  *    var selection = new wysihtml.Selection(editor); | 
| bsw/jbe@1309 | 10553  */ | 
| bsw/jbe@1309 | 10554 (function(wysihtml) { | 
| bsw/jbe@1309 | 10555   var dom = wysihtml.dom; | 
| bsw/jbe@1309 | 10556 | 
| bsw/jbe@1309 | 10557   function _getCumulativeOffsetTop(element) { | 
| bsw/jbe@1309 | 10558     var top = 0; | 
| bsw/jbe@1309 | 10559     if (element.parentNode) { | 
| bsw/jbe@1309 | 10560       do { | 
| bsw/jbe@1309 | 10561         top += element.offsetTop || 0; | 
| bsw/jbe@1309 | 10562         element = element.offsetParent; | 
| bsw/jbe@1309 | 10563       } while (element); | 
| bsw/jbe@1309 | 10564     } | 
| bsw/jbe@1309 | 10565     return top; | 
| bsw/jbe@1309 | 10566   } | 
| bsw/jbe@1309 | 10567 | 
| bsw/jbe@1309 | 10568   // Provides the depth of ``descendant`` relative to ``ancestor`` | 
| bsw/jbe@1309 | 10569   function getDepth(ancestor, descendant) { | 
| bsw/jbe@1309 | 10570       var ret = 0; | 
| bsw/jbe@1309 | 10571       while (descendant !== ancestor) { | 
| bsw/jbe@1309 | 10572           ret++; | 
| bsw/jbe@1309 | 10573           descendant = descendant.parentNode; | 
| bsw/jbe@1309 | 10574           if (!descendant) | 
| bsw/jbe@1309 | 10575               throw new Error("not a descendant of ancestor!"); | 
| bsw/jbe@1309 | 10576       } | 
| bsw/jbe@1309 | 10577       return ret; | 
| bsw/jbe@1309 | 10578   } | 
| bsw/jbe@1309 | 10579 | 
| bsw/jbe@1309 | 10580   function getRangeNode(node, offset) { | 
| bsw/jbe@1309 | 10581     if (node.nodeType === 3) { | 
| bsw/jbe@1309 | 10582       return node; | 
| bsw/jbe@1309 | 10583     } else { | 
| bsw/jbe@1309 | 10584       return node.childNodes[offset] || node; | 
| bsw/jbe@1309 | 10585     } | 
| bsw/jbe@1309 | 10586   } | 
| bsw/jbe@1309 | 10587 | 
| bsw/jbe@1309 | 10588   function getWebkitSelectionFixNode(container) { | 
| bsw/jbe@1309 | 10589     var blankNode = document.createElement('span'); | 
| bsw/jbe@1309 | 10590 | 
| bsw/jbe@1309 | 10591     var placeholderRemover = function(event) { | 
| bsw/jbe@1309 | 10592       // Self-destructs the caret and keeps the text inserted into it by user | 
| bsw/jbe@1309 | 10593       var lastChild; | 
| bsw/jbe@1309 | 10594 | 
| bsw/jbe@1309 | 10595       container.removeEventListener('mouseup', placeholderRemover); | 
| bsw/jbe@1309 | 10596       container.removeEventListener('keydown', placeholderRemover); | 
| bsw/jbe@1309 | 10597       container.removeEventListener('touchstart', placeholderRemover); | 
| bsw/jbe@1309 | 10598       container.removeEventListener('focus', placeholderRemover); | 
| bsw/jbe@1309 | 10599       container.removeEventListener('blur', placeholderRemover); | 
| bsw/jbe@1309 | 10600       container.removeEventListener('paste', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10601       container.removeEventListener('drop', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10602       container.removeEventListener('beforepaste', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10603 | 
| bsw/jbe@1309 | 10604       if (blankNode && blankNode.parentNode) { | 
| bsw/jbe@1309 | 10605         blankNode.parentNode.removeChild(blankNode); | 
| bsw/jbe@1309 | 10606       } | 
| bsw/jbe@1309 | 10607     }, | 
| bsw/jbe@1309 | 10608     delayedPlaceholderRemover = function (event) { | 
| bsw/jbe@1309 | 10609       if (blankNode && blankNode.parentNode) { | 
| bsw/jbe@1309 | 10610         setTimeout(placeholderRemover, 0); | 
| bsw/jbe@1309 | 10611       } | 
| bsw/jbe@1309 | 10612     }; | 
| bsw/jbe@1309 | 10613 | 
| bsw/jbe@1309 | 10614     blankNode.appendChild(container.ownerDocument.createTextNode(wysihtml.INVISIBLE_SPACE)); | 
| bsw/jbe@1309 | 10615     blankNode.className = '_wysihtml-temp-caret-fix'; | 
| bsw/jbe@1309 | 10616     blankNode.style.display = 'block'; | 
| bsw/jbe@1309 | 10617     blankNode.style.minWidth = '1px'; | 
| bsw/jbe@1309 | 10618     blankNode.style.height = '0px'; | 
| bsw/jbe@1309 | 10619 | 
| bsw/jbe@1309 | 10620     container.addEventListener('mouseup', placeholderRemover); | 
| bsw/jbe@1309 | 10621     container.addEventListener('keydown', placeholderRemover); | 
| bsw/jbe@1309 | 10622     container.addEventListener('touchstart', placeholderRemover); | 
| bsw/jbe@1309 | 10623     container.addEventListener('focus', placeholderRemover); | 
| bsw/jbe@1309 | 10624     container.addEventListener('blur', placeholderRemover); | 
| bsw/jbe@1309 | 10625     container.addEventListener('paste', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10626     container.addEventListener('drop', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10627     container.addEventListener('beforepaste', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10628 | 
| bsw/jbe@1309 | 10629     return blankNode; | 
| bsw/jbe@1309 | 10630   } | 
| bsw/jbe@1309 | 10631 | 
| bsw/jbe@1309 | 10632   // Should fix the obtained ranges that cannot surrond contents normally to apply changes upon | 
| bsw/jbe@1309 | 10633   // Being considerate to firefox that sets range start start out of span and end inside on doubleclick initiated selection | 
| bsw/jbe@1309 | 10634   function expandRangeToSurround(range) { | 
| bsw/jbe@1309 | 10635       if (range.canSurroundContents()) return; | 
| bsw/jbe@1309 | 10636 | 
| bsw/jbe@1309 | 10637       var common = range.commonAncestorContainer, | 
| bsw/jbe@1309 | 10638           start_depth = getDepth(common, range.startContainer), | 
| bsw/jbe@1309 | 10639           end_depth = getDepth(common, range.endContainer); | 
| bsw/jbe@1309 | 10640 | 
| bsw/jbe@1309 | 10641       while(!range.canSurroundContents()) { | 
| bsw/jbe@1309 | 10642         // In the following branches, we cannot just decrement the depth variables because the setStartBefore/setEndAfter may move the start or end of the range more than one level relative to ``common``. So we need to recompute the depth. | 
| bsw/jbe@1309 | 10643         if (start_depth > end_depth) { | 
| bsw/jbe@1309 | 10644             range.setStartBefore(range.startContainer); | 
| bsw/jbe@1309 | 10645             start_depth = getDepth(common, range.startContainer); | 
| bsw/jbe@1309 | 10646         } | 
| bsw/jbe@1309 | 10647         else { | 
| bsw/jbe@1309 | 10648             range.setEndAfter(range.endContainer); | 
| bsw/jbe@1309 | 10649             end_depth = getDepth(common, range.endContainer); | 
| bsw/jbe@1309 | 10650         } | 
| bsw/jbe@1309 | 10651       } | 
| bsw/jbe@1309 | 10652   } | 
| bsw/jbe@1309 | 10653 | 
| bsw/jbe@1309 | 10654   wysihtml.Selection = Base.extend( | 
| bsw/jbe@1309 | 10655     /** @scope wysihtml.Selection.prototype */ { | 
| bsw/jbe@1309 | 10656     constructor: function(editor, contain, unselectableClass) { | 
| bsw/jbe@1309 | 10657       // Make sure that our external range library is initialized | 
| bsw/jbe@1309 | 10658       rangy.init(); | 
| bsw/jbe@1309 | 10659 | 
| bsw/jbe@1309 | 10660       this.editor   = editor; | 
| bsw/jbe@1309 | 10661       this.composer = editor.composer; | 
| bsw/jbe@1309 | 10662       this.doc      = this.composer.doc; | 
| bsw/jbe@1309 | 10663       this.win      = this.composer.win; | 
| bsw/jbe@1309 | 10664       this.contain = contain; | 
| bsw/jbe@1309 | 10665       this.unselectableClass = unselectableClass || false; | 
| bsw/jbe@1309 | 10666     }, | 
| bsw/jbe@1309 | 10667 | 
| bsw/jbe@1309 | 10668     /** | 
| bsw/jbe@1309 | 10669      * Get the current selection as a bookmark to be able to later restore it | 
| bsw/jbe@1309 | 10670      * | 
| bsw/jbe@1309 | 10671      * @return {Object} An object that represents the current selection | 
| bsw/jbe@1309 | 10672      */ | 
| bsw/jbe@1309 | 10673     getBookmark: function() { | 
| bsw/jbe@1309 | 10674       var range = this.getRange(); | 
| bsw/jbe@1309 | 10675       return range && range.cloneRange(); | 
| bsw/jbe@1309 | 10676     }, | 
| bsw/jbe@1309 | 10677 | 
| bsw/jbe@1309 | 10678     /** | 
| bsw/jbe@1309 | 10679      * Restore a selection retrieved via wysihtml.Selection.prototype.getBookmark | 
| bsw/jbe@1309 | 10680      * | 
| bsw/jbe@1309 | 10681      * @param {Object} bookmark An object that represents the current selection | 
| bsw/jbe@1309 | 10682      */ | 
| bsw/jbe@1309 | 10683     setBookmark: function(bookmark) { | 
| bsw/jbe@1309 | 10684       if (!bookmark) { | 
| bsw/jbe@1309 | 10685         return; | 
| bsw/jbe@1309 | 10686       } | 
| bsw/jbe@1309 | 10687 | 
| bsw/jbe@1309 | 10688       this.setSelection(bookmark); | 
| bsw/jbe@1309 | 10689     }, | 
| bsw/jbe@1309 | 10690 | 
| bsw/jbe@1309 | 10691     /** | 
| bsw/jbe@1309 | 10692      * Set the caret in front of the given node | 
| bsw/jbe@1309 | 10693      * | 
| bsw/jbe@1309 | 10694      * @param {Object} node The element or text node where to position the caret in front of | 
| bsw/jbe@1309 | 10695      * @example | 
| bsw/jbe@1309 | 10696      *    selection.setBefore(myElement); | 
| bsw/jbe@1309 | 10697      */ | 
| bsw/jbe@1309 | 10698     setBefore: function(node) { | 
| bsw/jbe@1309 | 10699       var range = rangy.createRange(this.doc); | 
| bsw/jbe@1309 | 10700       range.setStartBefore(node); | 
| bsw/jbe@1309 | 10701       range.setEndBefore(node); | 
| bsw/jbe@1309 | 10702       return this.setSelection(range); | 
| bsw/jbe@1309 | 10703     }, | 
| bsw/jbe@1309 | 10704 | 
| bsw/jbe@1309 | 10705     // Constructs a self removing whitespace (ain absolute positioned span) for placing selection caret when normal methods fail. | 
| bsw/jbe@1309 | 10706     // Webkit has an issue with placing caret into places where there are no textnodes near by. | 
| bsw/jbe@1309 | 10707     createTemporaryCaretSpaceAfter: function (node) { | 
| bsw/jbe@1309 | 10708       var caretPlaceholder = this.doc.createElement('span'), | 
| bsw/jbe@1309 | 10709           caretPlaceholderText = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE), | 
| bsw/jbe@1309 | 10710           placeholderRemover = (function(event) { | 
| bsw/jbe@1309 | 10711             // Self-destructs the caret and keeps the text inserted into it by user | 
| bsw/jbe@1309 | 10712             var lastChild; | 
| bsw/jbe@1309 | 10713 | 
| bsw/jbe@1309 | 10714             this.contain.removeEventListener('mouseup', placeholderRemover); | 
| bsw/jbe@1309 | 10715             this.contain.removeEventListener('keydown', keyDownHandler); | 
| bsw/jbe@1309 | 10716             this.contain.removeEventListener('touchstart', placeholderRemover); | 
| bsw/jbe@1309 | 10717             this.contain.removeEventListener('focus', placeholderRemover); | 
| bsw/jbe@1309 | 10718             this.contain.removeEventListener('blur', placeholderRemover); | 
| bsw/jbe@1309 | 10719             this.contain.removeEventListener('paste', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10720             this.contain.removeEventListener('drop', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10721             this.contain.removeEventListener('beforepaste', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10722 | 
| bsw/jbe@1309 | 10723             // If user inserted sth it is in the placeholder and sgould be unwrapped and stripped of invisible whitespace hack | 
| bsw/jbe@1309 | 10724             // Otherwise the wrapper can just be removed | 
| bsw/jbe@1309 | 10725             if (caretPlaceholder && caretPlaceholder.parentNode) { | 
| bsw/jbe@1309 | 10726               caretPlaceholder.innerHTML = caretPlaceholder.innerHTML.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ""); | 
| bsw/jbe@1309 | 10727               if ((/[^\s]+/).test(caretPlaceholder.innerHTML)) { | 
| bsw/jbe@1309 | 10728                 lastChild = caretPlaceholder.lastChild; | 
| bsw/jbe@1309 | 10729                 wysihtml.dom.unwrap(caretPlaceholder); | 
| bsw/jbe@1309 | 10730                 this.setAfter(lastChild); | 
| bsw/jbe@1309 | 10731               } else { | 
| bsw/jbe@1309 | 10732                 caretPlaceholder.parentNode.removeChild(caretPlaceholder); | 
| bsw/jbe@1309 | 10733               } | 
| bsw/jbe@1309 | 10734 | 
| bsw/jbe@1309 | 10735             } | 
| bsw/jbe@1309 | 10736           }).bind(this), | 
| bsw/jbe@1309 | 10737           delayedPlaceholderRemover = function (event) { | 
| bsw/jbe@1309 | 10738             if (caretPlaceholder && caretPlaceholder.parentNode) { | 
| bsw/jbe@1309 | 10739               setTimeout(placeholderRemover, 0); | 
| bsw/jbe@1309 | 10740             } | 
| bsw/jbe@1309 | 10741           }, | 
| bsw/jbe@1309 | 10742           keyDownHandler = function(event) { | 
| bsw/jbe@1309 | 10743             if (event.which !== 8 && event.which !== 91 && event.which !== 17 && (event.which !== 86 || (!event.ctrlKey && !event.metaKey))) { | 
| bsw/jbe@1309 | 10744               placeholderRemover(); | 
| bsw/jbe@1309 | 10745             } | 
| bsw/jbe@1309 | 10746           }; | 
| bsw/jbe@1309 | 10747 | 
| bsw/jbe@1309 | 10748       caretPlaceholder.className = '_wysihtml-temp-caret-fix'; | 
| bsw/jbe@1309 | 10749       caretPlaceholder.style.position = 'absolute'; | 
| bsw/jbe@1309 | 10750       caretPlaceholder.style.display = 'block'; | 
| bsw/jbe@1309 | 10751       caretPlaceholder.style.minWidth = '1px'; | 
| bsw/jbe@1309 | 10752       caretPlaceholder.style.zIndex = '99999'; | 
| bsw/jbe@1309 | 10753       caretPlaceholder.appendChild(caretPlaceholderText); | 
| bsw/jbe@1309 | 10754 | 
| bsw/jbe@1309 | 10755       node.parentNode.insertBefore(caretPlaceholder, node.nextSibling); | 
| bsw/jbe@1309 | 10756       this.setBefore(caretPlaceholderText); | 
| bsw/jbe@1309 | 10757 | 
| bsw/jbe@1309 | 10758       // Remove the caret fix on any of the following events (some are delayed as content change happens after event) | 
| bsw/jbe@1309 | 10759       this.contain.addEventListener('mouseup', placeholderRemover); | 
| bsw/jbe@1309 | 10760       this.contain.addEventListener('keydown', keyDownHandler); | 
| bsw/jbe@1309 | 10761       this.contain.addEventListener('touchstart', placeholderRemover); | 
| bsw/jbe@1309 | 10762       this.contain.addEventListener('focus', placeholderRemover); | 
| bsw/jbe@1309 | 10763       this.contain.addEventListener('blur', placeholderRemover); | 
| bsw/jbe@1309 | 10764       this.contain.addEventListener('paste', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10765       this.contain.addEventListener('drop', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10766       this.contain.addEventListener('beforepaste', delayedPlaceholderRemover); | 
| bsw/jbe@1309 | 10767 | 
| bsw/jbe@1309 | 10768       return caretPlaceholder; | 
| bsw/jbe@1309 | 10769     }, | 
| bsw/jbe@1309 | 10770 | 
| bsw/jbe@1309 | 10771     /** | 
| bsw/jbe@1309 | 10772      * Set the caret after the given node | 
| bsw/jbe@1309 | 10773      * | 
| bsw/jbe@1309 | 10774      * @param {Object} node The element or text node where to position the caret in front of | 
| bsw/jbe@1309 | 10775      * @example | 
| bsw/jbe@1309 | 10776      *    selection.setBefore(myElement); | 
| bsw/jbe@1309 | 10777      * callback is an optional parameter accepting a function to execute when selection ahs been set | 
| bsw/jbe@1309 | 10778      */ | 
| bsw/jbe@1309 | 10779     setAfter: function(node, notVisual, callback) { | 
| bsw/jbe@1309 | 10780       var win = this.win, | 
| bsw/jbe@1309 | 10781           range = rangy.createRange(this.doc), | 
| bsw/jbe@1309 | 10782           fixWebkitSelection = function() { | 
| bsw/jbe@1309 | 10783             // Webkit fails to add selection if there are no textnodes in that region | 
| bsw/jbe@1309 | 10784             // (like an uneditable container at the end of content). | 
| bsw/jbe@1309 | 10785             var parent = node.parentNode, | 
| bsw/jbe@1309 | 10786                 lastSibling = parent ? parent.childNodes[parent.childNodes.length - 1] : null; | 
| bsw/jbe@1309 | 10787 | 
| bsw/jbe@1309 | 10788             if (!sel || (lastSibling === node && node.nodeType === 1 && win.getComputedStyle(node).display === "block")) { | 
| bsw/jbe@1309 | 10789               if (notVisual) { | 
| bsw/jbe@1309 | 10790                 // If setAfter is used as internal between actions, self-removing caretPlaceholder has simpler implementation | 
| bsw/jbe@1309 | 10791                 // and remove itself in call stack end instead on user interaction | 
| bsw/jbe@1309 | 10792                 var caretPlaceholder = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE); | 
| bsw/jbe@1309 | 10793                 node.parentNode.insertBefore(caretPlaceholder, node.nextSibling); | 
| bsw/jbe@1309 | 10794                 this.selectNode(caretPlaceholder); | 
| bsw/jbe@1309 | 10795                 setTimeout(function() { | 
| bsw/jbe@1309 | 10796                   if (caretPlaceholder && caretPlaceholder.parentNode) { | 
| bsw/jbe@1309 | 10797                     caretPlaceholder.parentNode.removeChild(caretPlaceholder); | 
| bsw/jbe@1309 | 10798                   } | 
| bsw/jbe@1309 | 10799                 }, 0); | 
| bsw/jbe@1309 | 10800               } else { | 
| bsw/jbe@1309 | 10801                 this.createTemporaryCaretSpaceAfter(node); | 
| bsw/jbe@1309 | 10802               } | 
| bsw/jbe@1309 | 10803             } | 
| bsw/jbe@1309 | 10804           }.bind(this), | 
| bsw/jbe@1309 | 10805           sel; | 
| bsw/jbe@1309 | 10806 | 
| bsw/jbe@1309 | 10807       range.setStartAfter(node); | 
| bsw/jbe@1309 | 10808       range.setEndAfter(node); | 
| bsw/jbe@1309 | 10809 | 
| bsw/jbe@1309 | 10810       // In IE contenteditable must be focused before we can set selection | 
| bsw/jbe@1309 | 10811       // thus setting the focus if activeElement is not this composer | 
| bsw/jbe@1309 | 10812       if (!document.activeElement || document.activeElement !== this.composer.element) { | 
| bsw/jbe@1309 | 10813         var scrollPos = this.composer.getScrollPos(); | 
| bsw/jbe@1309 | 10814         this.composer.element.focus(); | 
| bsw/jbe@1309 | 10815         this.composer.setScrollPos(scrollPos); | 
| bsw/jbe@1309 | 10816         setTimeout(function() { | 
| bsw/jbe@1309 | 10817           sel = this.setSelection(range); | 
| bsw/jbe@1309 | 10818           fixWebkitSelection(); | 
| bsw/jbe@1309 | 10819           if (callback) { | 
| bsw/jbe@1309 | 10820             callback(sel); | 
| bsw/jbe@1309 | 10821           } | 
| bsw/jbe@1309 | 10822         }.bind(this), 0); | 
| bsw/jbe@1309 | 10823       } else { | 
| bsw/jbe@1309 | 10824         sel = this.setSelection(range); | 
| bsw/jbe@1309 | 10825         fixWebkitSelection(); | 
| bsw/jbe@1309 | 10826         if (callback) { | 
| bsw/jbe@1309 | 10827           callback(sel); | 
| bsw/jbe@1309 | 10828         } | 
| bsw/jbe@1309 | 10829       } | 
| bsw/jbe@1309 | 10830     }, | 
| bsw/jbe@1309 | 10831 | 
| bsw/jbe@1309 | 10832     /** | 
| bsw/jbe@1309 | 10833      * Ability to select/mark nodes | 
| bsw/jbe@1309 | 10834      * | 
| bsw/jbe@1309 | 10835      * @param {Element} node The node/element to select | 
| bsw/jbe@1309 | 10836      * @example | 
| bsw/jbe@1309 | 10837      *    selection.selectNode(document.getElementById("my-image")); | 
| bsw/jbe@1309 | 10838      */ | 
| bsw/jbe@1309 | 10839     selectNode: function(node, avoidInvisibleSpace) { | 
| bsw/jbe@1309 | 10840       var range           = rangy.createRange(this.doc), | 
| bsw/jbe@1309 | 10841           isElement       = node.nodeType === wysihtml.ELEMENT_NODE, | 
| bsw/jbe@1309 | 10842           canHaveHTML     = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"), | 
| bsw/jbe@1309 | 10843           content         = isElement ? node.innerHTML : node.data, | 
| bsw/jbe@1309 | 10844           isEmpty         = (content === "" || content === wysihtml.INVISIBLE_SPACE), | 
| bsw/jbe@1309 | 10845           displayStyle    = dom.getStyle("display").from(node), | 
| bsw/jbe@1309 | 10846           isBlockElement  = (displayStyle === "block" || displayStyle === "list-item"); | 
| bsw/jbe@1309 | 10847 | 
| bsw/jbe@1309 | 10848       if (isEmpty && isElement && canHaveHTML && !avoidInvisibleSpace) { | 
| bsw/jbe@1309 | 10849         // Make sure that caret is visible in node by inserting a zero width no breaking space | 
| bsw/jbe@1309 | 10850         try { node.innerHTML = wysihtml.INVISIBLE_SPACE; } catch(e) {} | 
| bsw/jbe@1309 | 10851       } | 
| bsw/jbe@1309 | 10852       if (canHaveHTML) { | 
| bsw/jbe@1309 | 10853         range.selectNodeContents(node); | 
| bsw/jbe@1309 | 10854       } else { | 
| bsw/jbe@1309 | 10855         range.selectNode(node); | 
| bsw/jbe@1309 | 10856       } | 
| bsw/jbe@1309 | 10857 | 
| bsw/jbe@1309 | 10858       if (canHaveHTML && isEmpty && isElement) { | 
| bsw/jbe@1309 | 10859         range.collapse(isBlockElement); | 
| bsw/jbe@1309 | 10860       } else if (canHaveHTML && isEmpty) { | 
| bsw/jbe@1309 | 10861         range.setStartAfter(node); | 
| bsw/jbe@1309 | 10862         range.setEndAfter(node); | 
| bsw/jbe@1309 | 10863       } | 
| bsw/jbe@1309 | 10864 | 
| bsw/jbe@1309 | 10865       this.setSelection(range); | 
| bsw/jbe@1309 | 10866     }, | 
| bsw/jbe@1309 | 10867 | 
| bsw/jbe@1309 | 10868     /** | 
| bsw/jbe@1309 | 10869      * Get the node which contains the selection | 
| bsw/jbe@1309 | 10870      * | 
| bsw/jbe@1309 | 10871      * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange" | 
| bsw/jbe@1309 | 10872      * @return {Object} The node that contains the caret | 
| bsw/jbe@1309 | 10873      * @example | 
| bsw/jbe@1309 | 10874      *    var nodeThatContainsCaret = selection.getSelectedNode(); | 
| bsw/jbe@1309 | 10875      */ | 
| bsw/jbe@1309 | 10876     getSelectedNode: function(controlRange) { | 
| bsw/jbe@1309 | 10877       var selection, | 
| bsw/jbe@1309 | 10878           range; | 
| bsw/jbe@1309 | 10879 | 
| bsw/jbe@1309 | 10880       if (controlRange && this.doc.selection && this.doc.selection.type === "Control") { | 
| bsw/jbe@1309 | 10881         range = this.doc.selection.createRange(); | 
| bsw/jbe@1309 | 10882         if (range && range.length) { | 
| bsw/jbe@1309 | 10883           return range.item(0); | 
| bsw/jbe@1309 | 10884         } | 
| bsw/jbe@1309 | 10885       } | 
| bsw/jbe@1309 | 10886 | 
| bsw/jbe@1309 | 10887       selection = this.getSelection(this.doc); | 
| bsw/jbe@1309 | 10888       if (selection.focusNode === selection.anchorNode) { | 
| bsw/jbe@1309 | 10889         return selection.focusNode; | 
| bsw/jbe@1309 | 10890       } else { | 
| bsw/jbe@1309 | 10891         range = this.getRange(this.doc); | 
| bsw/jbe@1309 | 10892         return range ? range.commonAncestorContainer : this.doc.body; | 
| bsw/jbe@1309 | 10893       } | 
| bsw/jbe@1309 | 10894     }, | 
| bsw/jbe@1309 | 10895 | 
| bsw/jbe@1309 | 10896     fixSelBorders: function() { | 
| bsw/jbe@1309 | 10897       var range = this.getRange(); | 
| bsw/jbe@1309 | 10898       expandRangeToSurround(range); | 
| bsw/jbe@1309 | 10899       this.setSelection(range); | 
| bsw/jbe@1309 | 10900     }, | 
| bsw/jbe@1309 | 10901 | 
| bsw/jbe@1309 | 10902     getSelectedOwnNodes: function(controlRange) { | 
| bsw/jbe@1309 | 10903       var selection, | 
| bsw/jbe@1309 | 10904           ranges = this.getOwnRanges(), | 
| bsw/jbe@1309 | 10905           ownNodes = []; | 
| bsw/jbe@1309 | 10906 | 
| bsw/jbe@1309 | 10907       for (var i = 0, maxi = ranges.length; i < maxi; i++) { | 
| bsw/jbe@1309 | 10908           ownNodes.push(ranges[i].commonAncestorContainer || this.doc.body); | 
| bsw/jbe@1309 | 10909       } | 
| bsw/jbe@1309 | 10910       return ownNodes; | 
| bsw/jbe@1309 | 10911     }, | 
| bsw/jbe@1309 | 10912 | 
| bsw/jbe@1309 | 10913     findNodesInSelection: function(nodeTypes) { | 
| bsw/jbe@1309 | 10914       var ranges = this.getOwnRanges(), | 
| bsw/jbe@1309 | 10915           nodes = [], curNodes; | 
| bsw/jbe@1309 | 10916       for (var i = 0, maxi = ranges.length; i < maxi; i++) { | 
| bsw/jbe@1309 | 10917         curNodes = ranges[i].getNodes([1], function(node) { | 
| bsw/jbe@1309 | 10918             return wysihtml.lang.array(nodeTypes).contains(node.nodeName); | 
| bsw/jbe@1309 | 10919         }); | 
| bsw/jbe@1309 | 10920         nodes = nodes.concat(curNodes); | 
| bsw/jbe@1309 | 10921       } | 
| bsw/jbe@1309 | 10922       return nodes; | 
| bsw/jbe@1309 | 10923     }, | 
| bsw/jbe@1309 | 10924 | 
| bsw/jbe@1309 | 10925     filterElements: function(filter) { | 
| bsw/jbe@1309 | 10926       var ranges = this.getOwnRanges(), | 
| bsw/jbe@1309 | 10927           nodes = [], curNodes; | 
| bsw/jbe@1309 | 10928 | 
| bsw/jbe@1309 | 10929       for (var i = 0, maxi = ranges.length; i < maxi; i++) { | 
| bsw/jbe@1309 | 10930         curNodes = ranges[i].getNodes([1], function(element){ | 
| bsw/jbe@1309 | 10931           return filter(element, ranges[i]); | 
| bsw/jbe@1309 | 10932         }); | 
| bsw/jbe@1309 | 10933         nodes = nodes.concat(curNodes); | 
| bsw/jbe@1309 | 10934       } | 
| bsw/jbe@1309 | 10935       return nodes; | 
| bsw/jbe@1309 | 10936     }, | 
| bsw/jbe@1309 | 10937 | 
| bsw/jbe@1309 | 10938     containsUneditable: function() { | 
| bsw/jbe@1309 | 10939       var uneditables = this.getOwnUneditables(), | 
| bsw/jbe@1309 | 10940           selection = this.getSelection(); | 
| bsw/jbe@1309 | 10941 | 
| bsw/jbe@1309 | 10942       for (var i = 0, maxi = uneditables.length; i < maxi; i++) { | 
| bsw/jbe@1309 | 10943         if (selection.containsNode(uneditables[i])) { | 
| bsw/jbe@1309 | 10944           return true; | 
| bsw/jbe@1309 | 10945         } | 
| bsw/jbe@1309 | 10946       } | 
| bsw/jbe@1309 | 10947 | 
| bsw/jbe@1309 | 10948       return false; | 
| bsw/jbe@1309 | 10949     }, | 
| bsw/jbe@1309 | 10950 | 
| bsw/jbe@1309 | 10951     // Deletes selection contents making sure uneditables/unselectables are not partially deleted | 
| bsw/jbe@1309 | 10952     // Triggers wysihtml:uneditable:delete custom event on all deleted uneditables if customevents suppoorted | 
| bsw/jbe@1309 | 10953     deleteContents: function()  { | 
| bsw/jbe@1309 | 10954       var range = this.getRange(); | 
| bsw/jbe@1309 | 10955       this.deleteRangeContents(range); | 
| bsw/jbe@1309 | 10956       this.setSelection(range); | 
| bsw/jbe@1309 | 10957     }, | 
| bsw/jbe@1309 | 10958 | 
| bsw/jbe@1309 | 10959     // Makes sure all uneditable sare notified before deleting contents | 
| bsw/jbe@1309 | 10960     deleteRangeContents: function (range) { | 
| bsw/jbe@1309 | 10961       var startParent, endParent, uneditables, ev; | 
| bsw/jbe@1309 | 10962 | 
| bsw/jbe@1309 | 10963       if (this.unselectableClass) { | 
| bsw/jbe@1309 | 10964         if ((startParent = wysihtml.dom.getParentElement(range.startContainer, { query: "." + this.unselectableClass }, false, this.contain))) { | 
| bsw/jbe@1309 | 10965           range.setStartBefore(startParent); | 
| bsw/jbe@1309 | 10966         } | 
| bsw/jbe@1309 | 10967         if ((endParent = wysihtml.dom.getParentElement(range.endContainer, { query: "." + this.unselectableClass }, false, this.contain))) { | 
| bsw/jbe@1309 | 10968           range.setEndAfter(endParent); | 
| bsw/jbe@1309 | 10969         } | 
| bsw/jbe@1309 | 10970 | 
| bsw/jbe@1309 | 10971         // If customevents present notify uneditable elements of being deleted | 
| bsw/jbe@1309 | 10972         uneditables = range.getNodes([1], (function (node) { | 
| bsw/jbe@1309 | 10973           return wysihtml.dom.hasClass(node, this.unselectableClass); | 
| bsw/jbe@1309 | 10974         }).bind(this)); | 
| bsw/jbe@1309 | 10975         for (var i = uneditables.length; i--;) { | 
| bsw/jbe@1309 | 10976           try { | 
| bsw/jbe@1309 | 10977             ev = new CustomEvent("wysihtml:uneditable:delete"); | 
| bsw/jbe@1309 | 10978             uneditables[i].dispatchEvent(ev); | 
| bsw/jbe@1309 | 10979           } catch (err) {} | 
| bsw/jbe@1309 | 10980         } | 
| bsw/jbe@1309 | 10981       } | 
| bsw/jbe@1309 | 10982       range.deleteContents(); | 
| bsw/jbe@1309 | 10983     }, | 
| bsw/jbe@1309 | 10984 | 
| bsw/jbe@1309 | 10985     getCaretNode: function () { | 
| bsw/jbe@1309 | 10986       var selection = this.getSelection(); | 
| bsw/jbe@1309 | 10987       return (selection && selection.anchorNode) ? getRangeNode(selection.anchorNode, selection.anchorOffset) : null; | 
| bsw/jbe@1309 | 10988     }, | 
| bsw/jbe@1309 | 10989 | 
| bsw/jbe@1309 | 10990     getPreviousNode: function(node, ignoreEmpty) { | 
| bsw/jbe@1309 | 10991       var displayStyle; | 
| bsw/jbe@1309 | 10992       if (!node) { | 
| bsw/jbe@1309 | 10993         var selection = this.getSelection(); | 
| bsw/jbe@1309 | 10994         node = (selection && selection.anchorNode) ? getRangeNode(selection.anchorNode, selection.anchorOffset) : null; | 
| bsw/jbe@1309 | 10995       } | 
| bsw/jbe@1309 | 10996 | 
| bsw/jbe@1309 | 10997       if (node === this.contain) { | 
| bsw/jbe@1309 | 10998           return false; | 
| bsw/jbe@1309 | 10999       } | 
| bsw/jbe@1309 | 11000 | 
| bsw/jbe@1309 | 11001       var ret = node.previousSibling, | 
| bsw/jbe@1309 | 11002           parent; | 
| bsw/jbe@1309 | 11003 | 
| bsw/jbe@1309 | 11004       if (ret === this.contain) { | 
| bsw/jbe@1309 | 11005           return false; | 
| bsw/jbe@1309 | 11006       } | 
| bsw/jbe@1309 | 11007 | 
| bsw/jbe@1309 | 11008       if (ret && ret.nodeType !== 3 && ret.nodeType !== 1) { | 
| bsw/jbe@1309 | 11009          // do not count comments and other node types | 
| bsw/jbe@1309 | 11010          ret = this.getPreviousNode(ret, ignoreEmpty); | 
| bsw/jbe@1309 | 11011       } else if (ret && ret.nodeType === 3 && (/^\s*$/).test(ret.textContent)) { | 
| bsw/jbe@1309 | 11012         // do not count empty textnodes as previous nodes | 
| bsw/jbe@1309 | 11013         ret = this.getPreviousNode(ret, ignoreEmpty); | 
| bsw/jbe@1309 | 11014       } else if (ignoreEmpty && ret && ret.nodeType === 1) { | 
| bsw/jbe@1309 | 11015         // Do not count empty nodes if param set. | 
| bsw/jbe@1309 | 11016         // Contenteditable tends to bypass and delete these silently when deleting with caret when element is inline-like | 
| bsw/jbe@1309 | 11017         displayStyle = wysihtml.dom.getStyle("display").from(ret); | 
| bsw/jbe@1309 | 11018         if ( | 
| bsw/jbe@1309 | 11019             !wysihtml.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) && | 
| bsw/jbe@1309 | 11020             !wysihtml.lang.array(["block", "inline-block", "flex", "list-item", "table"]).contains(displayStyle) && | 
| bsw/jbe@1309 | 11021             (/^[\s]*$/).test(ret.innerHTML) | 
| bsw/jbe@1309 | 11022           ) { | 
| bsw/jbe@1309 | 11023             ret = this.getPreviousNode(ret, ignoreEmpty); | 
| bsw/jbe@1309 | 11024           } | 
| bsw/jbe@1309 | 11025       } else if (!ret && node !== this.contain) { | 
| bsw/jbe@1309 | 11026         parent = node.parentNode; | 
| bsw/jbe@1309 | 11027         if (parent !== this.contain) { | 
| bsw/jbe@1309 | 11028             ret = this.getPreviousNode(parent, ignoreEmpty); | 
| bsw/jbe@1309 | 11029         } | 
| bsw/jbe@1309 | 11030       } | 
| bsw/jbe@1309 | 11031 | 
| bsw/jbe@1309 | 11032       return (ret !== this.contain) ? ret : false; | 
| bsw/jbe@1309 | 11033     }, | 
| bsw/jbe@1309 | 11034 | 
| bsw/jbe@1309 | 11035     // Gather info about caret location (caret node, previous and next node) | 
| bsw/jbe@1309 | 11036     getNodesNearCaret: function() { | 
| bsw/jbe@1309 | 11037       if (!this.isCollapsed()) { | 
| bsw/jbe@1309 | 11038         throw "Selection must be caret when using selection.getNodesNearCaret()"; | 
| bsw/jbe@1309 | 11039       } | 
| bsw/jbe@1309 | 11040 | 
| bsw/jbe@1309 | 11041       var r = this.getOwnRanges(), | 
| bsw/jbe@1309 | 11042           caretNode, prevNode, nextNode, offset; | 
| bsw/jbe@1309 | 11043 | 
| bsw/jbe@1309 | 11044       if (r && r.length > 0) { | 
| bsw/jbe@1309 | 11045         if (r[0].startContainer.nodeType === 1) { | 
| bsw/jbe@1309 | 11046           caretNode = r[0].startContainer.childNodes[r[0].startOffset - 1]; | 
| bsw/jbe@1309 | 11047           if (!caretNode && r[0].startOffset === 0) { | 
| bsw/jbe@1309 | 11048             // Is first position before all nodes | 
| bsw/jbe@1309 | 11049             nextNode = r[0].startContainer.childNodes[0]; | 
| bsw/jbe@1309 | 11050           } else if (caretNode) { | 
| bsw/jbe@1309 | 11051             prevNode = caretNode.previousSibling; | 
| bsw/jbe@1309 | 11052             nextNode = caretNode.nextSibling; | 
| bsw/jbe@1309 | 11053           } | 
| bsw/jbe@1309 | 11054         } else { | 
| bsw/jbe@1309 | 11055           if (r[0].startOffset === 0 && r[0].startContainer.previousSibling) { | 
| bsw/jbe@1309 | 11056             caretNode = r[0].startContainer.previousSibling; | 
| bsw/jbe@1309 | 11057             if (caretNode.nodeType === 3) { | 
| bsw/jbe@1309 | 11058               offset = caretNode.data.length; | 
| bsw/jbe@1309 | 11059             } | 
| bsw/jbe@1309 | 11060           } else { | 
| bsw/jbe@1309 | 11061             caretNode = r[0].startContainer; | 
| bsw/jbe@1309 | 11062             offset = r[0].startOffset; | 
| bsw/jbe@1309 | 11063           } | 
| bsw/jbe@1309 | 11064           prevNode = caretNode.previousSibling; | 
| bsw/jbe@1309 | 11065           nextNode = caretNode.nextSibling; | 
| bsw/jbe@1309 | 11066         } | 
| bsw/jbe@1309 | 11067 | 
| bsw/jbe@1309 | 11068         return { | 
| bsw/jbe@1309 | 11069           "caretNode": caretNode, | 
| bsw/jbe@1309 | 11070           "prevNode": prevNode, | 
| bsw/jbe@1309 | 11071           "nextNode": nextNode, | 
| bsw/jbe@1309 | 11072           "textOffset": offset | 
| bsw/jbe@1309 | 11073         }; | 
| bsw/jbe@1309 | 11074       } | 
| bsw/jbe@1309 | 11075 | 
| bsw/jbe@1309 | 11076       return null; | 
| bsw/jbe@1309 | 11077     }, | 
| bsw/jbe@1309 | 11078 | 
| bsw/jbe@1309 | 11079     getSelectionParentsByTag: function(tagName) { | 
| bsw/jbe@1309 | 11080       var nodes = this.getSelectedOwnNodes(), | 
| bsw/jbe@1309 | 11081           curEl, parents = []; | 
| bsw/jbe@1309 | 11082 | 
| bsw/jbe@1309 | 11083       for (var i = 0, maxi = nodes.length; i < maxi; i++) { | 
| bsw/jbe@1309 | 11084         curEl = (nodes[i].nodeName &&  nodes[i].nodeName === 'LI') ? nodes[i] : wysihtml.dom.getParentElement(nodes[i], { query: 'li'}, false, this.contain); | 
| bsw/jbe@1309 | 11085         if (curEl) { | 
| bsw/jbe@1309 | 11086           parents.push(curEl); | 
| bsw/jbe@1309 | 11087         } | 
| bsw/jbe@1309 | 11088       } | 
| bsw/jbe@1309 | 11089       return (parents.length) ? parents : null; | 
| bsw/jbe@1309 | 11090     }, | 
| bsw/jbe@1309 | 11091 | 
| bsw/jbe@1309 | 11092     getRangeToNodeEnd: function() { | 
| bsw/jbe@1309 | 11093       if (this.isCollapsed()) { | 
| bsw/jbe@1309 | 11094         var range = this.getRange(), | 
| bsw/jbe@1309 | 11095             sNode, pos, lastR; | 
| bsw/jbe@1309 | 11096         if (range) { | 
| bsw/jbe@1309 | 11097           sNode = range.startContainer; | 
| bsw/jbe@1309 | 11098           pos = range.startOffset; | 
| bsw/jbe@1309 | 11099           lastR = rangy.createRange(this.doc); | 
| bsw/jbe@1309 | 11100 | 
| bsw/jbe@1309 | 11101           lastR.selectNodeContents(sNode); | 
| bsw/jbe@1309 | 11102           lastR.setStart(sNode, pos); | 
| bsw/jbe@1309 | 11103           return lastR; | 
| bsw/jbe@1309 | 11104         } | 
| bsw/jbe@1309 | 11105       } | 
| bsw/jbe@1309 | 11106     }, | 
| bsw/jbe@1309 | 11107 | 
| bsw/jbe@1309 | 11108     getRangeToNodeBeginning: function() { | 
| bsw/jbe@1309 | 11109       if (this.isCollapsed()) { | 
| bsw/jbe@1309 | 11110         var range = this.getRange(), | 
| bsw/jbe@1309 | 11111             sNode = range.startContainer, | 
| bsw/jbe@1309 | 11112             pos = range.startOffset, | 
| bsw/jbe@1309 | 11113             lastR = rangy.createRange(this.doc); | 
| bsw/jbe@1309 | 11114 | 
| bsw/jbe@1309 | 11115         lastR.selectNodeContents(sNode); | 
| bsw/jbe@1309 | 11116         lastR.setEnd(sNode, pos); | 
| bsw/jbe@1309 | 11117         return lastR; | 
| bsw/jbe@1309 | 11118       } | 
| bsw/jbe@1309 | 11119     }, | 
| bsw/jbe@1309 | 11120 | 
| bsw/jbe@1309 | 11121     // This function returns if caret is last in a node (no textual visible content follows) | 
| bsw/jbe@1309 | 11122     caretIsInTheEndOfNode: function(ignoreIfSpaceIsBeforeCaret) { | 
| bsw/jbe@1309 | 11123       var r = rangy.createRange(this.doc), | 
| bsw/jbe@1309 | 11124           s = this.getSelection(), | 
| bsw/jbe@1309 | 11125           rangeToNodeEnd = this.getRangeToNodeEnd(), | 
| bsw/jbe@1309 | 11126           endc, endtxt, beginc, begintxt; | 
| bsw/jbe@1309 | 11127 | 
| bsw/jbe@1309 | 11128       if (rangeToNodeEnd) { | 
| bsw/jbe@1309 | 11129         endc = rangeToNodeEnd.cloneContents(); | 
| bsw/jbe@1309 | 11130         endtxt = endc.textContent; | 
| bsw/jbe@1309 | 11131 | 
| bsw/jbe@1309 | 11132         if ((/^\s*$/).test(endtxt)) { | 
| bsw/jbe@1309 | 11133           if (ignoreIfSpaceIsBeforeCaret) { | 
| bsw/jbe@1309 | 11134             beginc = this.getRangeToNodeBeginning().cloneContents(); | 
| bsw/jbe@1309 | 11135             begintxt = beginc.textContent; | 
| bsw/jbe@1309 | 11136             return !(/[\u00A0 ][\s\uFEFF]*$/).test(begintxt); | 
| bsw/jbe@1309 | 11137           } else { | 
| bsw/jbe@1309 | 11138             return true; | 
| bsw/jbe@1309 | 11139           } | 
| bsw/jbe@1309 | 11140         } else { | 
| bsw/jbe@1309 | 11141           return false; | 
| bsw/jbe@1309 | 11142         } | 
| bsw/jbe@1309 | 11143       } else { | 
| bsw/jbe@1309 | 11144         return false; | 
| bsw/jbe@1309 | 11145       } | 
| bsw/jbe@1309 | 11146     }, | 
| bsw/jbe@1309 | 11147 | 
| bsw/jbe@1309 | 11148     caretIsFirstInSelection: function(includeLineBreaks) { | 
| bsw/jbe@1309 | 11149       var r = rangy.createRange(this.doc), | 
| bsw/jbe@1309 | 11150           s = this.getSelection(), | 
| bsw/jbe@1309 | 11151           range = this.getRange(), | 
| bsw/jbe@1309 | 11152           startNode = getRangeNode(range.startContainer, range.startOffset); | 
| bsw/jbe@1309 | 11153 | 
| bsw/jbe@1309 | 11154       if (startNode) { | 
| bsw/jbe@1309 | 11155         if (startNode.nodeType === wysihtml.TEXT_NODE) { | 
| bsw/jbe@1309 | 11156           if (!startNode.parentNode) { | 
| bsw/jbe@1309 | 11157             return false; | 
| bsw/jbe@1309 | 11158           } | 
| bsw/jbe@1309 | 11159           if (!this.isCollapsed() || (startNode.parentNode.firstChild !== startNode && !wysihtml.dom.domNode(startNode.previousSibling).is.block())) { | 
| bsw/jbe@1309 | 11160             return false; | 
| bsw/jbe@1309 | 11161           } | 
| bsw/jbe@1309 | 11162           var ws = this.win.getComputedStyle(startNode.parentNode).whiteSpace; | 
| bsw/jbe@1309 | 11163           return (ws === "pre" || ws === "pre-wrap") ? range.startOffset === 0 : (/^\s*$/).test(startNode.data.substr(0,range.startOffset)); | 
| bsw/jbe@1309 | 11164         } else if (includeLineBreaks && wysihtml.dom.domNode(startNode).is.lineBreak()) { | 
| bsw/jbe@1309 | 11165           return true; | 
| bsw/jbe@1309 | 11166         } else { | 
| bsw/jbe@1309 | 11167           r.selectNodeContents(this.getRange().commonAncestorContainer); | 
| bsw/jbe@1309 | 11168           r.collapse(true); | 
| bsw/jbe@1309 | 11169           return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset); | 
| bsw/jbe@1309 | 11170         } | 
| bsw/jbe@1309 | 11171       } | 
| bsw/jbe@1309 | 11172     }, | 
| bsw/jbe@1309 | 11173 | 
| bsw/jbe@1309 | 11174     caretIsInTheBeginnig: function(ofNode) { | 
| bsw/jbe@1309 | 11175         var selection = this.getSelection(), | 
| bsw/jbe@1309 | 11176             node = selection.anchorNode, | 
| bsw/jbe@1309 | 11177             offset = selection.anchorOffset; | 
| bsw/jbe@1309 | 11178         if (ofNode && node) { | 
| bsw/jbe@1309 | 11179           return (offset === 0 && (node.nodeName && node.nodeName === ofNode.toUpperCase() || wysihtml.dom.getParentElement(node.parentNode, { query: ofNode }, 1))); | 
| bsw/jbe@1309 | 11180         } else if (node) { | 
| bsw/jbe@1309 | 11181           return (offset === 0 && !this.getPreviousNode(node, true)); | 
| bsw/jbe@1309 | 11182         } | 
| bsw/jbe@1309 | 11183     }, | 
| bsw/jbe@1309 | 11184 | 
| bsw/jbe@1309 | 11185     // Returns object describing node/text before selection | 
| bsw/jbe@1309 | 11186     // If includePrevLeaves is true returns  also previous last leaf child if selection is in the beginning of current node | 
| bsw/jbe@1309 | 11187     getBeforeSelection: function(includePrevLeaves) { | 
| bsw/jbe@1309 | 11188       var sel = this.getSelection(), | 
| bsw/jbe@1309 | 11189           startNode = (sel.isBackwards()) ? sel.focusNode : sel.anchorNode, | 
| bsw/jbe@1309 | 11190           startOffset = (sel.isBackwards()) ? sel.focusOffset : sel.anchorOffset, | 
| bsw/jbe@1309 | 11191           rng = this.createRange(), endNode, inTmpCaret; | 
| bsw/jbe@1309 | 11192 | 
| bsw/jbe@1309 | 11193       // If start is textnode and all is whitespace before caret. Set start offset to 0 | 
| bsw/jbe@1309 | 11194       if (startNode && startNode.nodeType === 3 && (/^\s*$/).test(startNode.data.slice(0, startOffset))) { | 
| bsw/jbe@1309 | 11195         startOffset = 0; | 
| bsw/jbe@1309 | 11196       } | 
| bsw/jbe@1309 | 11197 | 
| bsw/jbe@1309 | 11198       // Escape temproray helper nodes if selection in them | 
| bsw/jbe@1309 | 11199       inTmpCaret = wysihtml.dom.getParentElement(startNode, { query: '._wysihtml-temp-caret-fix' }, 1); | 
| bsw/jbe@1309 | 11200       if (inTmpCaret) { | 
| bsw/jbe@1309 | 11201         startNode = inTmpCaret.parentNode; | 
| bsw/jbe@1309 | 11202         startOffset = Array.prototype.indexOf.call(startNode.childNodes, inTmpCaret); | 
| bsw/jbe@1309 | 11203       } | 
| bsw/jbe@1309 | 11204 | 
| bsw/jbe@1309 | 11205       if (startNode) { | 
| bsw/jbe@1309 | 11206         if (startOffset > 0) { | 
| bsw/jbe@1309 | 11207           if (startNode.nodeType === 3) { | 
| bsw/jbe@1309 | 11208             rng.setStart(startNode, 0); | 
| bsw/jbe@1309 | 11209             rng.setEnd(startNode, startOffset); | 
| bsw/jbe@1309 | 11210             return { | 
| bsw/jbe@1309 | 11211               type: "text", | 
| bsw/jbe@1309 | 11212               range: rng, | 
| bsw/jbe@1309 | 11213               offset : startOffset, | 
| bsw/jbe@1309 | 11214               node: startNode | 
| bsw/jbe@1309 | 11215             }; | 
| bsw/jbe@1309 | 11216           } else { | 
| bsw/jbe@1309 | 11217             rng.setStartBefore(startNode.childNodes[0]); | 
| bsw/jbe@1309 | 11218             endNode = startNode.childNodes[startOffset - 1]; | 
| bsw/jbe@1309 | 11219             rng.setEndAfter(endNode); | 
| bsw/jbe@1309 | 11220             return { | 
| bsw/jbe@1309 | 11221               type: "element", | 
| bsw/jbe@1309 | 11222               range: rng, | 
| bsw/jbe@1309 | 11223               offset : startOffset, | 
| bsw/jbe@1309 | 11224               node: endNode | 
| bsw/jbe@1309 | 11225             }; | 
| bsw/jbe@1309 | 11226           } | 
| bsw/jbe@1309 | 11227         } else { | 
| bsw/jbe@1309 | 11228           rng.setStartAndEnd(startNode, 0); | 
| bsw/jbe@1309 | 11229 | 
| bsw/jbe@1309 | 11230           if (includePrevLeaves) { | 
| bsw/jbe@1309 | 11231             var prevNode = this.getPreviousNode(startNode, true), | 
| bsw/jbe@1309 | 11232                 prevLeaf = null; | 
| bsw/jbe@1309 | 11233 | 
| bsw/jbe@1309 | 11234             if(prevNode) { | 
| bsw/jbe@1309 | 11235               if (prevNode.nodeType === 1 && wysihtml.dom.hasClass(prevNode, this.unselectableClass)) { | 
| bsw/jbe@1309 | 11236                 prevLeaf = prevNode; | 
| bsw/jbe@1309 | 11237               } else { | 
| bsw/jbe@1309 | 11238                 prevLeaf = wysihtml.dom.domNode(prevNode).lastLeafNode(); | 
| bsw/jbe@1309 | 11239               } | 
| bsw/jbe@1309 | 11240             } | 
| bsw/jbe@1309 | 11241 | 
| bsw/jbe@1309 | 11242             if (prevLeaf) { | 
| bsw/jbe@1309 | 11243               return { | 
| bsw/jbe@1309 | 11244                 type: "leafnode", | 
| bsw/jbe@1309 | 11245                 range: rng, | 
| bsw/jbe@1309 | 11246                 offset : startOffset, | 
| bsw/jbe@1309 | 11247                 node: prevLeaf | 
| bsw/jbe@1309 | 11248               }; | 
| bsw/jbe@1309 | 11249             } | 
| bsw/jbe@1309 | 11250           } | 
| bsw/jbe@1309 | 11251 | 
| bsw/jbe@1309 | 11252           return { | 
| bsw/jbe@1309 | 11253             type: "none", | 
| bsw/jbe@1309 | 11254             range: rng, | 
| bsw/jbe@1309 | 11255             offset : startOffset, | 
| bsw/jbe@1309 | 11256             node: startNode | 
| bsw/jbe@1309 | 11257           }; | 
| bsw/jbe@1309 | 11258         } | 
| bsw/jbe@1309 | 11259       } | 
| bsw/jbe@1309 | 11260       return null; | 
| bsw/jbe@1309 | 11261     }, | 
| bsw/jbe@1309 | 11262 | 
| bsw/jbe@1309 | 11263     // TODO: Figure out a method from following 2 that would work universally | 
| bsw/jbe@1309 | 11264     executeAndRestoreRangy: function(method, restoreScrollPosition) { | 
| bsw/jbe@1309 | 11265       var sel = rangy.saveSelection(this.win); | 
| bsw/jbe@1309 | 11266       if (!sel) { | 
| bsw/jbe@1309 | 11267         method(); | 
| bsw/jbe@1309 | 11268       } else { | 
| bsw/jbe@1309 | 11269         try { | 
| bsw/jbe@1309 | 11270           method(); | 
| bsw/jbe@1309 | 11271         } catch(e) { | 
| bsw/jbe@1309 | 11272           setTimeout(function() { throw e; }, 0); | 
| bsw/jbe@1309 | 11273         } | 
| bsw/jbe@1309 | 11274       } | 
| bsw/jbe@1309 | 11275       rangy.restoreSelection(sel); | 
| bsw/jbe@1309 | 11276     }, | 
| bsw/jbe@1309 | 11277 | 
| bsw/jbe@1309 | 11278     // TODO: has problems in chrome 12. investigate block level and uneditable area inbetween | 
| bsw/jbe@1309 | 11279     executeAndRestore: function(method, restoreScrollPosition) { | 
| bsw/jbe@1309 | 11280       var body                  = this.doc.body, | 
| bsw/jbe@1309 | 11281           oldScrollTop          = restoreScrollPosition && body.scrollTop, | 
| bsw/jbe@1309 | 11282           oldScrollLeft         = restoreScrollPosition && body.scrollLeft, | 
| bsw/jbe@1309 | 11283           className             = "_wysihtml-temp-placeholder", | 
| bsw/jbe@1309 | 11284           placeholderHtml       = '<span class="' + className + '">' + wysihtml.INVISIBLE_SPACE + '</span>', | 
| bsw/jbe@1309 | 11285           range                 = this.getRange(true), | 
| bsw/jbe@1309 | 11286           caretPlaceholder, | 
| bsw/jbe@1309 | 11287           newCaretPlaceholder, | 
| bsw/jbe@1309 | 11288           nextSibling, prevSibling, | 
| bsw/jbe@1309 | 11289           node, node2, range2, | 
| bsw/jbe@1309 | 11290           newRange; | 
| bsw/jbe@1309 | 11291 | 
| bsw/jbe@1309 | 11292       // Nothing selected, execute and say goodbye | 
| bsw/jbe@1309 | 11293       if (!range) { | 
| bsw/jbe@1309 | 11294         method(body, body); | 
| bsw/jbe@1309 | 11295         return; | 
| bsw/jbe@1309 | 11296       } | 
| bsw/jbe@1309 | 11297 | 
| bsw/jbe@1309 | 11298       if (!range.collapsed) { | 
| bsw/jbe@1309 | 11299         range2 = range.cloneRange(); | 
| bsw/jbe@1309 | 11300         node2 = range2.createContextualFragment(placeholderHtml); | 
| bsw/jbe@1309 | 11301         range2.collapse(false); | 
| bsw/jbe@1309 | 11302         range2.insertNode(node2); | 
| bsw/jbe@1309 | 11303         range2.detach(); | 
| bsw/jbe@1309 | 11304       } | 
| bsw/jbe@1309 | 11305 | 
| bsw/jbe@1309 | 11306       node = range.createContextualFragment(placeholderHtml); | 
| bsw/jbe@1309 | 11307       range.insertNode(node); | 
| bsw/jbe@1309 | 11308 | 
| bsw/jbe@1309 | 11309       if (node2) { | 
| bsw/jbe@1309 | 11310         caretPlaceholder = this.contain.querySelectorAll("." + className); | 
| bsw/jbe@1309 | 11311         range.setStartBefore(caretPlaceholder[0]); | 
| bsw/jbe@1309 | 11312         range.setEndAfter(caretPlaceholder[caretPlaceholder.length -1]); | 
| bsw/jbe@1309 | 11313       } | 
| bsw/jbe@1309 | 11314       this.setSelection(range); | 
| bsw/jbe@1309 | 11315 | 
| bsw/jbe@1309 | 11316       // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder | 
| bsw/jbe@1309 | 11317       try { | 
| bsw/jbe@1309 | 11318         method(range.startContainer, range.endContainer); | 
| bsw/jbe@1309 | 11319       } catch(e) { | 
| bsw/jbe@1309 | 11320         setTimeout(function() { throw e; }, 0); | 
| bsw/jbe@1309 | 11321       } | 
| bsw/jbe@1309 | 11322       caretPlaceholder = this.contain.querySelectorAll("." + className); | 
| bsw/jbe@1309 | 11323       if (caretPlaceholder && caretPlaceholder.length) { | 
| bsw/jbe@1309 | 11324         newRange = rangy.createRange(this.doc); | 
| bsw/jbe@1309 | 11325         nextSibling = caretPlaceholder[0].nextSibling; | 
| bsw/jbe@1309 | 11326         if (caretPlaceholder.length > 1) { | 
| bsw/jbe@1309 | 11327           prevSibling = caretPlaceholder[caretPlaceholder.length -1].previousSibling; | 
| bsw/jbe@1309 | 11328         } | 
| bsw/jbe@1309 | 11329         if (prevSibling && nextSibling) { | 
| bsw/jbe@1309 | 11330           newRange.setStartBefore(nextSibling); | 
| bsw/jbe@1309 | 11331           newRange.setEndAfter(prevSibling); | 
| bsw/jbe@1309 | 11332         } else { | 
| bsw/jbe@1309 | 11333           newCaretPlaceholder = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE); | 
| bsw/jbe@1309 | 11334           dom.insert(newCaretPlaceholder).after(caretPlaceholder[0]); | 
| bsw/jbe@1309 | 11335           newRange.setStartBefore(newCaretPlaceholder); | 
| bsw/jbe@1309 | 11336           newRange.setEndAfter(newCaretPlaceholder); | 
| bsw/jbe@1309 | 11337         } | 
| bsw/jbe@1309 | 11338         this.setSelection(newRange); | 
| bsw/jbe@1309 | 11339         for (var i = caretPlaceholder.length; i--;) { | 
| bsw/jbe@1309 | 11340           caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]); | 
| bsw/jbe@1309 | 11341         } | 
| bsw/jbe@1309 | 11342 | 
| bsw/jbe@1309 | 11343       } else { | 
| bsw/jbe@1309 | 11344         // fallback for when all hell breaks loose | 
| bsw/jbe@1309 | 11345         this.contain.focus(); | 
| bsw/jbe@1309 | 11346       } | 
| bsw/jbe@1309 | 11347 | 
| bsw/jbe@1309 | 11348       if (restoreScrollPosition) { | 
| bsw/jbe@1309 | 11349         body.scrollTop  = oldScrollTop; | 
| bsw/jbe@1309 | 11350         body.scrollLeft = oldScrollLeft; | 
| bsw/jbe@1309 | 11351       } | 
| bsw/jbe@1309 | 11352 | 
| bsw/jbe@1309 | 11353       // Remove it again, just to make sure that the placeholder is definitely out of the dom tree | 
| bsw/jbe@1309 | 11354       try { | 
| bsw/jbe@1309 | 11355         caretPlaceholder.parentNode.removeChild(caretPlaceholder); | 
| bsw/jbe@1309 | 11356       } catch(e2) {} | 
| bsw/jbe@1309 | 11357     }, | 
| bsw/jbe@1309 | 11358 | 
| bsw/jbe@1309 | 11359     set: function(node, offset) { | 
| bsw/jbe@1309 | 11360       var newRange = rangy.createRange(this.doc); | 
| bsw/jbe@1309 | 11361       newRange.setStart(node, offset || 0); | 
| bsw/jbe@1309 | 11362       this.setSelection(newRange); | 
| bsw/jbe@1309 | 11363     }, | 
| bsw/jbe@1309 | 11364 | 
| bsw/jbe@1309 | 11365     /** | 
| bsw/jbe@1309 | 11366      * Insert html at the caret or selection position and move the cursor after the inserted html | 
| bsw/jbe@1309 | 11367      * Replaces selection content if present | 
| bsw/jbe@1309 | 11368      * | 
| bsw/jbe@1309 | 11369      * @param {String} html HTML string to insert | 
| bsw/jbe@1309 | 11370      * @example | 
| bsw/jbe@1309 | 11371      *    selection.insertHTML("<p>foobar</p>"); | 
| bsw/jbe@1309 | 11372      */ | 
| bsw/jbe@1309 | 11373     insertHTML: function(html) { | 
| bsw/jbe@1309 | 11374       var range     = this.getRange(), | 
| bsw/jbe@1309 | 11375           node = this.doc.createElement('DIV'), | 
| bsw/jbe@1309 | 11376           fragment = this.doc.createDocumentFragment(), | 
| bsw/jbe@1309 | 11377           lastChild, lastEditorElement; | 
| bsw/jbe@1309 | 11378 | 
| bsw/jbe@1309 | 11379       if (range) { | 
| bsw/jbe@1309 | 11380         range.deleteContents(); | 
| bsw/jbe@1309 | 11381         node.innerHTML = html; | 
| bsw/jbe@1309 | 11382         lastChild = node.lastChild; | 
| bsw/jbe@1309 | 11383 | 
| bsw/jbe@1309 | 11384         while (node.firstChild) { | 
| bsw/jbe@1309 | 11385           fragment.appendChild(node.firstChild); | 
| bsw/jbe@1309 | 11386         } | 
| bsw/jbe@1309 | 11387         range.insertNode(fragment); | 
| bsw/jbe@1309 | 11388 | 
| bsw/jbe@1309 | 11389         lastEditorElement = this.contain.lastChild; | 
| bsw/jbe@1309 | 11390         while (lastEditorElement && lastEditorElement.nodeType === 3 && lastEditorElement.previousSibling && (/^\s*$/).test(lastEditorElement.data)) { | 
| bsw/jbe@1309 | 11391           lastEditorElement = lastEditorElement.previousSibling; | 
| bsw/jbe@1309 | 11392         } | 
| bsw/jbe@1309 | 11393 | 
| bsw/jbe@1309 | 11394         if (lastChild) { | 
| bsw/jbe@1309 | 11395           // fixes some pad cases mostly on webkit where last nr is needed | 
| bsw/jbe@1309 | 11396           if (lastEditorElement && lastChild === lastEditorElement && lastChild.nodeType === 1) { | 
| bsw/jbe@1309 | 11397             this.contain.appendChild(this.doc.createElement('br')); | 
| bsw/jbe@1309 | 11398           } | 
| bsw/jbe@1309 | 11399           this.setAfter(lastChild); | 
| bsw/jbe@1309 | 11400         } | 
| bsw/jbe@1309 | 11401       } | 
| bsw/jbe@1309 | 11402     }, | 
| bsw/jbe@1309 | 11403 | 
| bsw/jbe@1309 | 11404     /** | 
| bsw/jbe@1309 | 11405      * Insert a node at the caret position and move the cursor behind it | 
| bsw/jbe@1309 | 11406      * | 
| bsw/jbe@1309 | 11407      * @param {Object} node HTML string to insert | 
| bsw/jbe@1309 | 11408      * @example | 
| bsw/jbe@1309 | 11409      *    selection.insertNode(document.createTextNode("foobar")); | 
| bsw/jbe@1309 | 11410      */ | 
| bsw/jbe@1309 | 11411     insertNode: function(node) { | 
| bsw/jbe@1309 | 11412       var range = this.getRange(); | 
| bsw/jbe@1309 | 11413       if (range) { | 
| bsw/jbe@1309 | 11414         range.deleteContents(); | 
| bsw/jbe@1309 | 11415         range.insertNode(node); | 
| bsw/jbe@1309 | 11416       } | 
| bsw/jbe@1309 | 11417     }, | 
| bsw/jbe@1309 | 11418 | 
| bsw/jbe@1309 | 11419     canAppendChild: function (node) { | 
| bsw/jbe@1309 | 11420       var anchorNode, anchorNodeTagNameLower, | 
| bsw/jbe@1309 | 11421           voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"], | 
| bsw/jbe@1309 | 11422           range = this.getRange(); | 
| bsw/jbe@1309 | 11423 | 
| bsw/jbe@1309 | 11424       anchorNode = node || range.startContainer; | 
| bsw/jbe@1309 | 11425 | 
| bsw/jbe@1309 | 11426       if (anchorNode) { | 
| bsw/jbe@1309 | 11427         anchorNodeTagNameLower = (anchorNode.tagName || anchorNode.nodeName).toLowerCase(); | 
| bsw/jbe@1309 | 11428       } | 
| bsw/jbe@1309 | 11429 | 
| bsw/jbe@1309 | 11430       return voidElements.indexOf(anchorNodeTagNameLower) === -1; | 
| bsw/jbe@1309 | 11431     }, | 
| bsw/jbe@1309 | 11432 | 
| bsw/jbe@1309 | 11433     splitElementAtCaret: function (element, insertNode) { | 
| bsw/jbe@1309 | 11434       var sel = this.getSelection(), | 
| bsw/jbe@1309 | 11435           range, contentAfterRangeStart, | 
| bsw/jbe@1309 | 11436           firstChild, lastChild, childNodes; | 
| bsw/jbe@1309 | 11437 | 
| bsw/jbe@1309 | 11438       if (sel.rangeCount > 0) { | 
| bsw/jbe@1309 | 11439         range = sel.getRangeAt(0).cloneRange(); // Create a copy of the selection range to work with | 
| bsw/jbe@1309 | 11440 | 
| bsw/jbe@1309 | 11441         range.setEndAfter(element); // Place the end of the range after the element | 
| bsw/jbe@1309 | 11442         contentAfterRangeStart = range.extractContents(); // Extract the contents of the element after the caret into a fragment | 
| bsw/jbe@1309 | 11443 | 
| bsw/jbe@1309 | 11444         childNodes = contentAfterRangeStart.childNodes; | 
| bsw/jbe@1309 | 11445 | 
| bsw/jbe@1309 | 11446         // Empty elements are cleaned up from extracted content | 
| bsw/jbe@1309 | 11447         for (var i = childNodes.length; i --;) { | 
| bsw/jbe@1309 | 11448           if (!wysihtml.dom.domNode(childNodes[i]).is.visible()) { | 
| bsw/jbe@1309 | 11449             contentAfterRangeStart.removeChild(childNodes[i]); | 
| bsw/jbe@1309 | 11450           } | 
| bsw/jbe@1309 | 11451         } | 
| bsw/jbe@1309 | 11452 | 
| bsw/jbe@1309 | 11453         element.parentNode.insertBefore(contentAfterRangeStart, element.nextSibling); | 
| bsw/jbe@1309 | 11454 | 
| bsw/jbe@1309 | 11455         if (insertNode) { | 
| bsw/jbe@1309 | 11456           firstChild = insertNode.firstChild || insertNode; | 
| bsw/jbe@1309 | 11457           lastChild = insertNode.lastChild || insertNode; | 
| bsw/jbe@1309 | 11458 | 
| bsw/jbe@1309 | 11459           element.parentNode.insertBefore(insertNode, element.nextSibling); | 
| bsw/jbe@1309 | 11460 | 
| bsw/jbe@1309 | 11461           // Select inserted node contents | 
| bsw/jbe@1309 | 11462           if (firstChild && lastChild) { | 
| bsw/jbe@1309 | 11463              range.setStartBefore(firstChild); | 
| bsw/jbe@1309 | 11464              range.setEndAfter(lastChild); | 
| bsw/jbe@1309 | 11465              this.setSelection(range); | 
| bsw/jbe@1309 | 11466           } | 
| bsw/jbe@1309 | 11467         } else { | 
| bsw/jbe@1309 | 11468           range.setStartAfter(element); | 
| bsw/jbe@1309 | 11469           range.setEndAfter(element); | 
| bsw/jbe@1309 | 11470         } | 
| bsw/jbe@1309 | 11471 | 
| bsw/jbe@1309 | 11472         if (!wysihtml.dom.domNode(element).is.visible()) { | 
| bsw/jbe@1309 | 11473           if (wysihtml.dom.getTextContent(element) === '') { | 
| bsw/jbe@1309 | 11474             element.parentNode.removeChild(element); | 
| bsw/jbe@1309 | 11475           } else { | 
| bsw/jbe@1309 | 11476             element.parentNode.replaceChild(this.doc.createTextNode(" "), element); | 
| bsw/jbe@1309 | 11477           } | 
| bsw/jbe@1309 | 11478         } | 
| bsw/jbe@1309 | 11479 | 
| bsw/jbe@1309 | 11480 | 
| bsw/jbe@1309 | 11481       } | 
| bsw/jbe@1309 | 11482     }, | 
| bsw/jbe@1309 | 11483 | 
| bsw/jbe@1309 | 11484     /** | 
| bsw/jbe@1309 | 11485      * Wraps current selection with the given node | 
| bsw/jbe@1309 | 11486      * | 
| bsw/jbe@1309 | 11487      * @param {Object} node The node to surround the selected elements with | 
| bsw/jbe@1309 | 11488      */ | 
| bsw/jbe@1309 | 11489     surround: function(nodeOptions) { | 
| bsw/jbe@1309 | 11490       var ranges = this.getOwnRanges(), | 
| bsw/jbe@1309 | 11491           node, nodes = []; | 
| bsw/jbe@1309 | 11492       if (ranges.length == 0) { | 
| bsw/jbe@1309 | 11493         return nodes; | 
| bsw/jbe@1309 | 11494       } | 
| bsw/jbe@1309 | 11495 | 
| bsw/jbe@1309 | 11496       for (var i = ranges.length; i--;) { | 
| bsw/jbe@1309 | 11497         node = this.doc.createElement(nodeOptions.nodeName); | 
| bsw/jbe@1309 | 11498         nodes.push(node); | 
| bsw/jbe@1309 | 11499         if (nodeOptions.className) { | 
| bsw/jbe@1309 | 11500           node.className = nodeOptions.className; | 
| bsw/jbe@1309 | 11501         } | 
| bsw/jbe@1309 | 11502         if (nodeOptions.cssStyle) { | 
| bsw/jbe@1309 | 11503           node.setAttribute('style', nodeOptions.cssStyle); | 
| bsw/jbe@1309 | 11504         } | 
| bsw/jbe@1309 | 11505         try { | 
| bsw/jbe@1309 | 11506           // This only works when the range boundaries are not overlapping other elements | 
| bsw/jbe@1309 | 11507           ranges[i].surroundContents(node); | 
| bsw/jbe@1309 | 11508           this.selectNode(node); | 
| bsw/jbe@1309 | 11509         } catch(e) { | 
| bsw/jbe@1309 | 11510           // fallback | 
| bsw/jbe@1309 | 11511           node.appendChild(ranges[i].extractContents()); | 
| bsw/jbe@1309 | 11512           ranges[i].insertNode(node); | 
| bsw/jbe@1309 | 11513         } | 
| bsw/jbe@1309 | 11514       } | 
| bsw/jbe@1309 | 11515       return nodes; | 
| bsw/jbe@1309 | 11516     }, | 
| bsw/jbe@1309 | 11517 | 
| bsw/jbe@1309 | 11518     /** | 
| bsw/jbe@1309 | 11519      * Scroll the current caret position into the view | 
| bsw/jbe@1309 | 11520      * FIXME: This is a bit hacky, there might be a smarter way of doing this | 
| bsw/jbe@1309 | 11521      * | 
| bsw/jbe@1309 | 11522      * @example | 
| bsw/jbe@1309 | 11523      *    selection.scrollIntoView(); | 
| bsw/jbe@1309 | 11524      */ | 
| bsw/jbe@1309 | 11525     scrollIntoView: function() { | 
| bsw/jbe@1309 | 11526       var doc           = this.doc, | 
| bsw/jbe@1309 | 11527           tolerance     = 5, // px | 
| bsw/jbe@1309 | 11528           hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight, | 
| bsw/jbe@1309 | 11529           tempElement   = doc._wysihtmlScrollIntoViewElement = doc._wysihtmlScrollIntoViewElement || (function() { | 
| bsw/jbe@1309 | 11530             var element = doc.createElement("span"); | 
| bsw/jbe@1309 | 11531             // The element needs content in order to be able to calculate it's position properly | 
| bsw/jbe@1309 | 11532             element.innerHTML = wysihtml.INVISIBLE_SPACE; | 
| bsw/jbe@1309 | 11533             return element; | 
| bsw/jbe@1309 | 11534           })(), | 
| bsw/jbe@1309 | 11535           offsetTop; | 
| bsw/jbe@1309 | 11536 | 
| bsw/jbe@1309 | 11537       if (hasScrollBars) { | 
| bsw/jbe@1309 | 11538         this.insertNode(tempElement); | 
| bsw/jbe@1309 | 11539         offsetTop = _getCumulativeOffsetTop(tempElement); | 
| bsw/jbe@1309 | 11540         tempElement.parentNode.removeChild(tempElement); | 
| bsw/jbe@1309 | 11541         if (offsetTop >= (doc.body.scrollTop + doc.documentElement.offsetHeight - tolerance)) { | 
| bsw/jbe@1309 | 11542           doc.body.scrollTop = offsetTop; | 
| bsw/jbe@1309 | 11543         } | 
| bsw/jbe@1309 | 11544       } | 
| bsw/jbe@1309 | 11545     }, | 
| bsw/jbe@1309 | 11546 | 
| bsw/jbe@1309 | 11547     /** | 
| bsw/jbe@1309 | 11548      * Select line where the caret is in | 
| bsw/jbe@1309 | 11549      */ | 
| bsw/jbe@1309 | 11550     selectLine: function() { | 
| bsw/jbe@1309 | 11551       var r = rangy.createRange(); | 
| bsw/jbe@1309 | 11552       if (wysihtml.browser.supportsSelectionModify()) { | 
| bsw/jbe@1309 | 11553         this._selectLine_W3C(); | 
| bsw/jbe@1309 | 11554       } else if (r.nativeRange && r.nativeRange.getBoundingClientRect) { | 
| bsw/jbe@1309 | 11555         // For IE Edge as it ditched the old api and did not fully implement the new one (as expected) | 
| bsw/jbe@1309 | 11556         this._selectLineUniversal(); | 
| bsw/jbe@1309 | 11557       } | 
| bsw/jbe@1309 | 11558     }, | 
| bsw/jbe@1309 | 11559 | 
| bsw/jbe@1309 | 11560     includeRangyRangeHelpers: function() { | 
| bsw/jbe@1309 | 11561       var s = this.getSelection(), | 
| bsw/jbe@1309 | 11562           r = s.getRangeAt(0), | 
| bsw/jbe@1309 | 11563           isHelperNode = function(node) { | 
| bsw/jbe@1309 | 11564             return (node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary')); | 
| bsw/jbe@1309 | 11565           }, | 
| bsw/jbe@1309 | 11566           getNodeLength = function (node) { | 
| bsw/jbe@1309 | 11567             if (node.nodeType === 1) { | 
| bsw/jbe@1309 | 11568               return node.childNodes && node.childNodes.length || 0; | 
| bsw/jbe@1309 | 11569             } else { | 
| bsw/jbe@1309 | 11570               return node.data && node.data.length || 0; | 
| bsw/jbe@1309 | 11571             } | 
| bsw/jbe@1309 | 11572           }, | 
| bsw/jbe@1309 | 11573           anode = s.anchorNode.nodeType === 1 ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode, | 
| bsw/jbe@1309 | 11574           fnode = s.focusNode.nodeType === 1 ? s.focusNode.childNodes[s.focusOffset] : s.focusNode; | 
| bsw/jbe@1309 | 11575 | 
| bsw/jbe@1309 | 11576       if (fnode && s.focusOffset === getNodeLength(fnode) && fnode.nextSibling && isHelperNode(fnode.nextSibling)) { | 
| bsw/jbe@1309 | 11577         r.setEndAfter(fnode.nextSibling); | 
| bsw/jbe@1309 | 11578       } | 
| bsw/jbe@1309 | 11579       if (anode && s.anchorOffset === 0 && anode.previousSibling && isHelperNode(anode.previousSibling)) { | 
| bsw/jbe@1309 | 11580         r.setStartBefore(anode.previousSibling); | 
| bsw/jbe@1309 | 11581       } | 
| bsw/jbe@1309 | 11582       r.select(); | 
| bsw/jbe@1309 | 11583     }, | 
| bsw/jbe@1309 | 11584 | 
| bsw/jbe@1309 | 11585     /** | 
| bsw/jbe@1309 | 11586      * See https://developer.mozilla.org/en/DOM/Selection/modify | 
| bsw/jbe@1309 | 11587      */ | 
| bsw/jbe@1309 | 11588     _selectLine_W3C: function() { | 
| bsw/jbe@1309 | 11589       var selection = this.win.getSelection(), | 
| bsw/jbe@1309 | 11590           initialBoundry = [selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset]; | 
| bsw/jbe@1309 | 11591 | 
| bsw/jbe@1309 | 11592       selection.modify("move", "left", "lineboundary"); | 
| bsw/jbe@1309 | 11593       selection.modify("extend", "right", "lineboundary"); | 
| bsw/jbe@1309 | 11594 | 
| bsw/jbe@1309 | 11595       // IF lineboundary extending did not change selection try universal fallback (FF fails sometimes without a reason) | 
| bsw/jbe@1309 | 11596       if (selection.anchorNode === initialBoundry[0] && | 
| bsw/jbe@1309 | 11597           selection.anchorOffset === initialBoundry[1] && | 
| bsw/jbe@1309 | 11598           selection.focusNode === initialBoundry[2] && | 
| bsw/jbe@1309 | 11599           selection.focusOffset === initialBoundry[3] | 
| bsw/jbe@1309 | 11600       ) { | 
| bsw/jbe@1309 | 11601         this._selectLineUniversal(); | 
| bsw/jbe@1309 | 11602       } else { | 
| bsw/jbe@1309 | 11603         this.includeRangyRangeHelpers(); | 
| bsw/jbe@1309 | 11604       } | 
| bsw/jbe@1309 | 11605     }, | 
| bsw/jbe@1309 | 11606 | 
| bsw/jbe@1309 | 11607     // collapses selection to current line beginning or end | 
| bsw/jbe@1309 | 11608     toLineBoundary: function (location, collapse) { | 
| bsw/jbe@1309 | 11609       collapse = (typeof collapse === 'undefined') ? false : collapse; | 
| bsw/jbe@1309 | 11610       if (wysihtml.browser.supportsSelectionModify()) { | 
| bsw/jbe@1309 | 11611         var selection = this.win.getSelection(); | 
| bsw/jbe@1309 | 11612 | 
| bsw/jbe@1309 | 11613         selection.modify("extend", location, "lineboundary"); | 
| bsw/jbe@1309 | 11614         if (collapse) { | 
| bsw/jbe@1309 | 11615           if (location === "left") { | 
| bsw/jbe@1309 | 11616             selection.collapseToStart(); | 
| bsw/jbe@1309 | 11617           } else if (location === "right") { | 
| bsw/jbe@1309 | 11618             selection.collapseToEnd(); | 
| bsw/jbe@1309 | 11619           } | 
| bsw/jbe@1309 | 11620         } | 
| bsw/jbe@1309 | 11621       } | 
| bsw/jbe@1309 | 11622     }, | 
| bsw/jbe@1309 | 11623 | 
| bsw/jbe@1309 | 11624     getRangeRect: function(r) { | 
| bsw/jbe@1309 | 11625       var textNode = this.doc.createTextNode("i"), | 
| bsw/jbe@1309 | 11626           testNode = this.doc.createTextNode("i"), | 
| bsw/jbe@1309 | 11627           rect, cr; | 
| bsw/jbe@1309 | 11628 | 
| bsw/jbe@1309 | 11629       /*testNode.style.visibility = "hidden"; | 
| bsw/jbe@1309 | 11630       testNode.style.width = "0px"; | 
| bsw/jbe@1309 | 11631       testNode.style.display = "inline-block"; | 
| bsw/jbe@1309 | 11632       testNode.style.overflow = "hidden"; | 
| bsw/jbe@1309 | 11633       testNode.appendChild(textNode);*/ | 
| bsw/jbe@1309 | 11634 | 
| bsw/jbe@1309 | 11635       if (r.collapsed) { | 
| bsw/jbe@1309 | 11636         r.insertNode(testNode); | 
| bsw/jbe@1309 | 11637         r.selectNode(testNode); | 
| bsw/jbe@1309 | 11638         rect = r.nativeRange.getBoundingClientRect(); | 
| bsw/jbe@1309 | 11639         r.deleteContents(); | 
| bsw/jbe@1309 | 11640 | 
| bsw/jbe@1309 | 11641       } else { | 
| bsw/jbe@1309 | 11642         rect = r.nativeRange.getBoundingClientRect(); | 
| bsw/jbe@1309 | 11643       } | 
| bsw/jbe@1309 | 11644 | 
| bsw/jbe@1309 | 11645       return rect; | 
| bsw/jbe@1309 | 11646 | 
| bsw/jbe@1309 | 11647     }, | 
| bsw/jbe@1309 | 11648 | 
| bsw/jbe@1309 | 11649     _selectLineUniversal: function() { | 
| bsw/jbe@1309 | 11650       var s = this.getSelection(), | 
| bsw/jbe@1309 | 11651           r = s.getRangeAt(0), | 
| bsw/jbe@1309 | 11652           rect, | 
| bsw/jbe@1309 | 11653           startRange, endRange, testRange, | 
| bsw/jbe@1309 | 11654           count = 0, | 
| bsw/jbe@1309 | 11655           amount, testRect, found, | 
| bsw/jbe@1309 | 11656           that = this, | 
| bsw/jbe@1309 | 11657           isLineBreakingElement = function(el) { | 
| bsw/jbe@1309 | 11658             return el && el.nodeType === 1 && (that.win.getComputedStyle(el).display === "block" || wysihtml.lang.array(['BR', 'HR']).contains(el.nodeName)); | 
| bsw/jbe@1309 | 11659           }, | 
| bsw/jbe@1309 | 11660           prevNode = function(node) { | 
| bsw/jbe@1309 | 11661             var pnode = node; | 
| bsw/jbe@1309 | 11662             if (pnode) { | 
| bsw/jbe@1309 | 11663               while (pnode && ((pnode.nodeType === 1 && pnode.classList.contains('rangySelectionBoundary')) || (pnode.nodeType === 3 && (/^\s*$/).test(pnode.data)))) { | 
| bsw/jbe@1309 | 11664                 pnode = pnode.previousSibling; | 
| bsw/jbe@1309 | 11665               } | 
| bsw/jbe@1309 | 11666             } | 
| bsw/jbe@1309 | 11667             return pnode; | 
| bsw/jbe@1309 | 11668           }; | 
| bsw/jbe@1309 | 11669 | 
| bsw/jbe@1309 | 11670       startRange = r.cloneRange(); | 
| bsw/jbe@1309 | 11671       endRange = r.cloneRange(); | 
| bsw/jbe@1309 | 11672 | 
| bsw/jbe@1309 | 11673       if (r.collapsed) { | 
| bsw/jbe@1309 | 11674         // Collapsed state can not have a bounding rect. Thus need to expand it at least by 1 character first while not crossing line boundary | 
| bsw/jbe@1309 | 11675         // TODO: figure out a shorter and more readable way | 
| bsw/jbe@1309 | 11676         if (r.startContainer.nodeType === 3 && r.startOffset < r.startContainer.data.length) { | 
| bsw/jbe@1309 | 11677           r.moveEnd('character', 1); | 
| bsw/jbe@1309 | 11678         } else if (r.startContainer.nodeType === 1 && r.startContainer.childNodes[r.startOffset] && r.startContainer.childNodes[r.startOffset].nodeType === 3 && r.startContainer.childNodes[r.startOffset].data.length > 0) { | 
| bsw/jbe@1309 | 11679           r.moveEnd('character', 1); | 
| bsw/jbe@1309 | 11680         } else if ( | 
| bsw/jbe@1309 | 11681           r.startOffset > 0 && | 
| bsw/jbe@1309 | 11682           ( | 
| bsw/jbe@1309 | 11683             r.startContainer.nodeType === 3 || | 
| bsw/jbe@1309 | 11684             ( | 
| bsw/jbe@1309 | 11685               r.startContainer.nodeType === 1 && | 
| bsw/jbe@1309 | 11686               !isLineBreakingElement(prevNode(r.startContainer.childNodes[r.startOffset - 1])) | 
| bsw/jbe@1309 | 11687             ) | 
| bsw/jbe@1309 | 11688           ) | 
| bsw/jbe@1309 | 11689         ) { | 
| bsw/jbe@1309 | 11690           r.moveStart('character', -1); | 
| bsw/jbe@1309 | 11691         } | 
| bsw/jbe@1309 | 11692       } | 
| bsw/jbe@1309 | 11693       if (!r.collapsed) { | 
| bsw/jbe@1309 | 11694         r.insertNode(this.doc.createTextNode(wysihtml.INVISIBLE_SPACE)); | 
| bsw/jbe@1309 | 11695       } | 
| bsw/jbe@1309 | 11696 | 
| bsw/jbe@1309 | 11697       // Is probably just empty line as can not be expanded | 
| bsw/jbe@1309 | 11698       rect = r.nativeRange.getBoundingClientRect(); | 
| bsw/jbe@1309 | 11699       // If startnode is not line break allready move the start position of range by -1 character until clientRect top changes; | 
| bsw/jbe@1309 | 11700       do { | 
| bsw/jbe@1309 | 11701         amount = r.moveStart('character', -1); | 
| bsw/jbe@1309 | 11702         testRect =  r.nativeRange.getBoundingClientRect(); | 
| bsw/jbe@1309 | 11703 | 
| bsw/jbe@1309 | 11704         if (!testRect || Math.floor(testRect.top) !== Math.floor(rect.top)) { | 
| bsw/jbe@1309 | 11705           r.moveStart('character', 1); | 
| bsw/jbe@1309 | 11706           found = true; | 
| bsw/jbe@1309 | 11707         } | 
| bsw/jbe@1309 | 11708         count++; | 
| bsw/jbe@1309 | 11709       } while (amount !== 0 && !found && count < 2000); | 
| bsw/jbe@1309 | 11710       count = 0; | 
| bsw/jbe@1309 | 11711       found = false; | 
| bsw/jbe@1309 | 11712       rect = r.nativeRange.getBoundingClientRect(); | 
| bsw/jbe@1309 | 11713 | 
| bsw/jbe@1309 | 11714       if (r.endContainer !== this.contain || (this.contain.lastChild && this.contain.childNodes[r.endOffset] !== this.contain.lastChild)) { | 
| bsw/jbe@1309 | 11715         do { | 
| bsw/jbe@1309 | 11716           amount = r.moveEnd('character', 1); | 
| bsw/jbe@1309 | 11717           testRect =  r.nativeRange.getBoundingClientRect(); | 
| bsw/jbe@1309 | 11718           if (!testRect || Math.floor(testRect.bottom) !== Math.floor(rect.bottom)) { | 
| bsw/jbe@1309 | 11719             r.moveEnd('character', -1); | 
| bsw/jbe@1309 | 11720 | 
| bsw/jbe@1309 | 11721             // Fix a IE line end marked by linebreak element although caret is before it | 
| bsw/jbe@1309 | 11722             // If causes problems should be changed to be applied only to IE | 
| bsw/jbe@1309 | 11723             if (r.endContainer && r.endContainer.nodeType === 1 && r.endContainer.childNodes[r.endOffset] && r.endContainer.childNodes[r.endOffset].nodeType === 1 && r.endContainer.childNodes[r.endOffset].nodeName === "BR" && r.endContainer.childNodes[r.endOffset].previousSibling) { | 
| bsw/jbe@1309 | 11724               if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 1) { | 
| bsw/jbe@1309 | 11725                 r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.childNodes.length); | 
| bsw/jbe@1309 | 11726               } else if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 3) { | 
| bsw/jbe@1309 | 11727                 r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.data.length); | 
| bsw/jbe@1309 | 11728               } | 
| bsw/jbe@1309 | 11729             } | 
| bsw/jbe@1309 | 11730             found = true; | 
| bsw/jbe@1309 | 11731           } | 
| bsw/jbe@1309 | 11732           count++; | 
| bsw/jbe@1309 | 11733         } while (amount !== 0 && !found && count < 2000); | 
| bsw/jbe@1309 | 11734       } | 
| bsw/jbe@1309 | 11735       r.select(); | 
| bsw/jbe@1309 | 11736       this.includeRangyRangeHelpers(); | 
| bsw/jbe@1309 | 11737     }, | 
| bsw/jbe@1309 | 11738 | 
| bsw/jbe@1309 | 11739     getText: function() { | 
| bsw/jbe@1309 | 11740       var selection = this.getSelection(); | 
| bsw/jbe@1309 | 11741       return selection ? selection.toString() : ""; | 
| bsw/jbe@1309 | 11742     }, | 
| bsw/jbe@1309 | 11743 | 
| bsw/jbe@1309 | 11744     getNodes: function(nodeType, filter) { | 
| bsw/jbe@1309 | 11745       var range = this.getRange(); | 
| bsw/jbe@1309 | 11746       if (range) { | 
| bsw/jbe@1309 | 11747         return range.getNodes(Array.isArray(nodeType) ? nodeType : [nodeType], filter); | 
| bsw/jbe@1309 | 11748       } else { | 
| bsw/jbe@1309 | 11749         return []; | 
| bsw/jbe@1309 | 11750       } | 
| bsw/jbe@1309 | 11751     }, | 
| bsw/jbe@1309 | 11752 | 
| bsw/jbe@1309 | 11753     // Gets all the elements in selection with nodeType | 
| bsw/jbe@1309 | 11754     // Ignores the elements not belonging to current editable area | 
| bsw/jbe@1309 | 11755     // If filter is defined nodes must pass the filter function with true to be included in list | 
| bsw/jbe@1309 | 11756     getOwnNodes: function(nodeType, filter, splitBounds) { | 
| bsw/jbe@1309 | 11757       var ranges = this.getOwnRanges(), | 
| bsw/jbe@1309 | 11758           nodes = []; | 
| bsw/jbe@1309 | 11759       for (var r = 0, rmax = ranges.length; r < rmax; r++) { | 
| bsw/jbe@1309 | 11760         if (ranges[r]) { | 
| bsw/jbe@1309 | 11761           if (splitBounds) { | 
| bsw/jbe@1309 | 11762             ranges[r].splitBoundaries(); | 
| bsw/jbe@1309 | 11763           } | 
| bsw/jbe@1309 | 11764           nodes = nodes.concat(ranges[r].getNodes(Array.isArray(nodeType) ? nodeType : [nodeType], filter)); | 
| bsw/jbe@1309 | 11765         } | 
| bsw/jbe@1309 | 11766       } | 
| bsw/jbe@1309 | 11767 | 
| bsw/jbe@1309 | 11768       return nodes; | 
| bsw/jbe@1309 | 11769     }, | 
| bsw/jbe@1309 | 11770 | 
| bsw/jbe@1309 | 11771     fixRangeOverflow: function(range) { | 
| bsw/jbe@1309 | 11772       if (this.contain && this.contain.firstChild && range) { | 
| bsw/jbe@1309 | 11773         var containment = range.compareNode(this.contain); | 
| bsw/jbe@1309 | 11774         if (containment !== 2) { | 
| bsw/jbe@1309 | 11775           if (containment === 1) { | 
| bsw/jbe@1309 | 11776             range.setStartBefore(this.contain.firstChild); | 
| bsw/jbe@1309 | 11777           } | 
| bsw/jbe@1309 | 11778           if (containment === 0) { | 
| bsw/jbe@1309 | 11779             range.setEndAfter(this.contain.lastChild); | 
| bsw/jbe@1309 | 11780           } | 
| bsw/jbe@1309 | 11781           if (containment === 3) { | 
| bsw/jbe@1309 | 11782             range.setStartBefore(this.contain.firstChild); | 
| bsw/jbe@1309 | 11783             range.setEndAfter(this.contain.lastChild); | 
| bsw/jbe@1309 | 11784           } | 
| bsw/jbe@1309 | 11785         } else if (this._detectInlineRangeProblems(range)) { | 
| bsw/jbe@1309 | 11786           var previousElementSibling = range.endContainer.previousElementSibling; | 
| bsw/jbe@1309 | 11787           if (previousElementSibling) { | 
| bsw/jbe@1309 | 11788             range.setEnd(previousElementSibling, this._endOffsetForNode(previousElementSibling)); | 
| bsw/jbe@1309 | 11789           } | 
| bsw/jbe@1309 | 11790         } | 
| bsw/jbe@1309 | 11791       } | 
| bsw/jbe@1309 | 11792     }, | 
| bsw/jbe@1309 | 11793 | 
| bsw/jbe@1309 | 11794     _endOffsetForNode: function(node) { | 
| bsw/jbe@1309 | 11795       var range = document.createRange(); | 
| bsw/jbe@1309 | 11796       range.selectNodeContents(node); | 
| bsw/jbe@1309 | 11797       return range.endOffset; | 
| bsw/jbe@1309 | 11798     }, | 
| bsw/jbe@1309 | 11799 | 
| bsw/jbe@1309 | 11800     _detectInlineRangeProblems: function(range) { | 
| bsw/jbe@1309 | 11801       var position = dom.compareDocumentPosition(range.startContainer, range.endContainer); | 
| bsw/jbe@1309 | 11802       return ( | 
| bsw/jbe@1309 | 11803         range.endOffset == 0 && | 
| bsw/jbe@1309 | 11804         position & 4 //Node.DOCUMENT_POSITION_FOLLOWING | 
| bsw/jbe@1309 | 11805       ); | 
| bsw/jbe@1309 | 11806     }, | 
| bsw/jbe@1309 | 11807 | 
| bsw/jbe@1309 | 11808     getRange: function(dontFix) { | 
| bsw/jbe@1309 | 11809       var selection = this.getSelection(), | 
| bsw/jbe@1309 | 11810           range = selection && selection.rangeCount && selection.getRangeAt(0); | 
| bsw/jbe@1309 | 11811 | 
| bsw/jbe@1309 | 11812       if (dontFix !== true) { | 
| bsw/jbe@1309 | 11813         this.fixRangeOverflow(range); | 
| bsw/jbe@1309 | 11814       } | 
| bsw/jbe@1309 | 11815 | 
| bsw/jbe@1309 | 11816       return range; | 
| bsw/jbe@1309 | 11817     }, | 
| bsw/jbe@1309 | 11818 | 
| bsw/jbe@1309 | 11819     getOwnUneditables: function() { | 
| bsw/jbe@1309 | 11820       var allUneditables = dom.query(this.contain, '.' + this.unselectableClass), | 
| bsw/jbe@1309 | 11821           deepUneditables = dom.query(allUneditables, '.' + this.unselectableClass); | 
| bsw/jbe@1309 | 11822 | 
| bsw/jbe@1309 | 11823       return wysihtml.lang.array(allUneditables).without(deepUneditables); | 
| bsw/jbe@1309 | 11824     }, | 
| bsw/jbe@1309 | 11825 | 
| bsw/jbe@1309 | 11826     // Returns an array of ranges that belong only to this editable | 
| bsw/jbe@1309 | 11827     // Needed as uneditable block in contenteditabel can split range into pieces | 
| bsw/jbe@1309 | 11828     // If manipulating content reverse loop is usually needed as manipulation can shift subsequent ranges | 
| bsw/jbe@1309 | 11829     getOwnRanges: function()  { | 
| bsw/jbe@1309 | 11830       var ranges = [], | 
| bsw/jbe@1309 | 11831           r = this.getRange(), | 
| bsw/jbe@1309 | 11832           tmpRanges; | 
| bsw/jbe@1309 | 11833 | 
| bsw/jbe@1309 | 11834       if (r) { ranges.push(r); } | 
| bsw/jbe@1309 | 11835 | 
| bsw/jbe@1309 | 11836       if (this.unselectableClass && this.contain && r) { | 
| bsw/jbe@1309 | 11837         var uneditables = this.getOwnUneditables(), | 
| bsw/jbe@1309 | 11838             tmpRange; | 
| bsw/jbe@1309 | 11839         if (uneditables.length > 0) { | 
| bsw/jbe@1309 | 11840           for (var i = 0, imax = uneditables.length; i < imax; i++) { | 
| bsw/jbe@1309 | 11841             tmpRanges = []; | 
| bsw/jbe@1309 | 11842             for (var j = 0, jmax = ranges.length; j < jmax; j++) { | 
| bsw/jbe@1309 | 11843               if (ranges[j]) { | 
| bsw/jbe@1309 | 11844                 switch (ranges[j].compareNode(uneditables[i])) { | 
| bsw/jbe@1309 | 11845                   case 2: | 
| bsw/jbe@1309 | 11846                     // all selection inside uneditable. remove | 
| bsw/jbe@1309 | 11847                   break; | 
| bsw/jbe@1309 | 11848                   case 3: | 
| bsw/jbe@1309 | 11849                     //section begins before and ends after uneditable. spilt | 
| bsw/jbe@1309 | 11850                     tmpRange = ranges[j].cloneRange(); | 
| bsw/jbe@1309 | 11851                     tmpRange.setEndBefore(uneditables[i]); | 
| bsw/jbe@1309 | 11852                     tmpRanges.push(tmpRange); | 
| bsw/jbe@1309 | 11853 | 
| bsw/jbe@1309 | 11854                     tmpRange = ranges[j].cloneRange(); | 
| bsw/jbe@1309 | 11855                     tmpRange.setStartAfter(uneditables[i]); | 
| bsw/jbe@1309 | 11856                     tmpRanges.push(tmpRange); | 
| bsw/jbe@1309 | 11857                   break; | 
| bsw/jbe@1309 | 11858                   default: | 
| bsw/jbe@1309 | 11859                     // in all other cases uneditable does not touch selection. dont modify | 
| bsw/jbe@1309 | 11860                     tmpRanges.push(ranges[j]); | 
| bsw/jbe@1309 | 11861                 } | 
| bsw/jbe@1309 | 11862               } | 
| bsw/jbe@1309 | 11863               ranges = tmpRanges; | 
| bsw/jbe@1309 | 11864             } | 
| bsw/jbe@1309 | 11865           } | 
| bsw/jbe@1309 | 11866         } | 
| bsw/jbe@1309 | 11867       } | 
| bsw/jbe@1309 | 11868       return ranges; | 
| bsw/jbe@1309 | 11869     }, | 
| bsw/jbe@1309 | 11870 | 
| bsw/jbe@1309 | 11871     getSelection: function() { | 
| bsw/jbe@1309 | 11872       return rangy.getSelection(this.win); | 
| bsw/jbe@1309 | 11873     }, | 
| bsw/jbe@1309 | 11874 | 
| bsw/jbe@1309 | 11875     // Sets selection in document to a given range | 
| bsw/jbe@1309 | 11876     // Set selection method detects if it fails to set any selection in document and returns null on fail | 
| bsw/jbe@1309 | 11877     // (especially needed in webkit where some ranges just can not create selection for no reason) | 
| bsw/jbe@1309 | 11878     setSelection: function(range) { | 
| bsw/jbe@1309 | 11879       var selection = rangy.getSelection(this.win); | 
| bsw/jbe@1309 | 11880       selection.setSingleRange(range); | 
| bsw/jbe@1309 | 11881       return (selection && selection.anchorNode && selection.focusNode) ? selection : null; | 
| bsw/jbe@1309 | 11882     }, | 
| bsw/jbe@1309 | 11883 | 
| bsw/jbe@1309 | 11884 | 
| bsw/jbe@1309 | 11885 | 
| bsw/jbe@1309 | 11886     // Webkit has an ancient error of not selecting all contents when uneditable block element is first or last in editable area | 
| bsw/jbe@1309 | 11887     selectAll: function() { | 
| bsw/jbe@1309 | 11888       var range = this.createRange(), | 
| bsw/jbe@1309 | 11889           composer = this.composer, | 
| bsw/jbe@1309 | 11890           that = this, | 
| bsw/jbe@1309 | 11891           blankEndNode = getWebkitSelectionFixNode(this.composer.element), | 
| bsw/jbe@1309 | 11892           blankStartNode = getWebkitSelectionFixNode(this.composer.element), | 
| bsw/jbe@1309 | 11893           s; | 
| bsw/jbe@1309 | 11894 | 
| bsw/jbe@1309 | 11895       var doSelect = function() { | 
| bsw/jbe@1309 | 11896         range.setStart(composer.element, 0); | 
| bsw/jbe@1309 | 11897         range.setEnd(composer.element, composer.element.childNodes.length); | 
| bsw/jbe@1309 | 11898         s = that.setSelection(range); | 
| bsw/jbe@1309 | 11899       }; | 
| bsw/jbe@1309 | 11900 | 
| bsw/jbe@1309 | 11901       var notSelected = function() { | 
| bsw/jbe@1309 | 11902         return !s || (s.nativeSelection && s.nativeSelection.type && (s.nativeSelection.type === "Caret" || s.nativeSelection.type === "None")); | 
| bsw/jbe@1309 | 11903       } | 
| bsw/jbe@1309 | 11904 | 
| bsw/jbe@1309 | 11905       wysihtml.dom.removeInvisibleSpaces(this.composer.element); | 
| bsw/jbe@1309 | 11906       doSelect(); | 
| bsw/jbe@1309 | 11907 | 
| bsw/jbe@1309 | 11908       if (this.composer.element.firstChild && notSelected())  { | 
| bsw/jbe@1309 | 11909         // Try fixing end | 
| bsw/jbe@1309 | 11910         this.composer.element.appendChild(blankEndNode); | 
| bsw/jbe@1309 | 11911         doSelect(); | 
| bsw/jbe@1309 | 11912 | 
| bsw/jbe@1309 | 11913         if (notSelected()) { | 
| bsw/jbe@1309 | 11914           // Remove end fix | 
| bsw/jbe@1309 | 11915           blankEndNode.parentNode.removeChild(blankEndNode); | 
| bsw/jbe@1309 | 11916 | 
| bsw/jbe@1309 | 11917           // Try fixing beginning | 
| bsw/jbe@1309 | 11918           this.composer.element.insertBefore(blankStartNode, this.composer.element.firstChild); | 
| bsw/jbe@1309 | 11919           doSelect(); | 
| bsw/jbe@1309 | 11920 | 
| bsw/jbe@1309 | 11921           if (notSelected()) { | 
| bsw/jbe@1309 | 11922             // Try fixing both | 
| bsw/jbe@1309 | 11923             this.composer.element.appendChild(blankEndNode); | 
| bsw/jbe@1309 | 11924             doSelect(); | 
| bsw/jbe@1309 | 11925           } | 
| bsw/jbe@1309 | 11926         } | 
| bsw/jbe@1309 | 11927       } | 
| bsw/jbe@1309 | 11928     }, | 
| bsw/jbe@1309 | 11929 | 
| bsw/jbe@1309 | 11930     createRange: function() { | 
| bsw/jbe@1309 | 11931       return rangy.createRange(this.doc); | 
| bsw/jbe@1309 | 11932     }, | 
| bsw/jbe@1309 | 11933 | 
| bsw/jbe@1309 | 11934     isCollapsed: function() { | 
| bsw/jbe@1309 | 11935         return this.getSelection().isCollapsed; | 
| bsw/jbe@1309 | 11936     }, | 
| bsw/jbe@1309 | 11937 | 
| bsw/jbe@1309 | 11938     getHtml: function() { | 
| bsw/jbe@1309 | 11939       return this.getSelection().toHtml(); | 
| bsw/jbe@1309 | 11940     }, | 
| bsw/jbe@1309 | 11941 | 
| bsw/jbe@1309 | 11942     getPlainText: function () { | 
| bsw/jbe@1309 | 11943       return this.getSelection().toString(); | 
| bsw/jbe@1309 | 11944     }, | 
| bsw/jbe@1309 | 11945 | 
| bsw/jbe@1309 | 11946     isEndToEndInNode: function(nodeNames) { | 
| bsw/jbe@1309 | 11947       var range = this.getRange(), | 
| bsw/jbe@1309 | 11948           parentElement = range.commonAncestorContainer, | 
| bsw/jbe@1309 | 11949           startNode = range.startContainer, | 
| bsw/jbe@1309 | 11950           endNode = range.endContainer; | 
| bsw/jbe@1309 | 11951 | 
| bsw/jbe@1309 | 11952 | 
| bsw/jbe@1309 | 11953         if (parentElement.nodeType === wysihtml.TEXT_NODE) { | 
| bsw/jbe@1309 | 11954           parentElement = parentElement.parentNode; | 
| bsw/jbe@1309 | 11955         } | 
| bsw/jbe@1309 | 11956 | 
| bsw/jbe@1309 | 11957         if (startNode.nodeType === wysihtml.TEXT_NODE && !(/^\s*$/).test(startNode.data.substr(range.startOffset))) { | 
| bsw/jbe@1309 | 11958           return false; | 
| bsw/jbe@1309 | 11959         } | 
| bsw/jbe@1309 | 11960 | 
| bsw/jbe@1309 | 11961         if (endNode.nodeType === wysihtml.TEXT_NODE && !(/^\s*$/).test(endNode.data.substr(range.endOffset))) { | 
| bsw/jbe@1309 | 11962           return false; | 
| bsw/jbe@1309 | 11963         } | 
| bsw/jbe@1309 | 11964 | 
| bsw/jbe@1309 | 11965         while (startNode && startNode !== parentElement) { | 
| bsw/jbe@1309 | 11966           if (startNode.nodeType !== wysihtml.TEXT_NODE && !wysihtml.dom.contains(parentElement, startNode)) { | 
| bsw/jbe@1309 | 11967             return false; | 
| bsw/jbe@1309 | 11968           } | 
| bsw/jbe@1309 | 11969           if (wysihtml.dom.domNode(startNode).prev({ignoreBlankTexts: true})) { | 
| bsw/jbe@1309 | 11970             return false; | 
| bsw/jbe@1309 | 11971           } | 
| bsw/jbe@1309 | 11972           startNode = startNode.parentNode; | 
| bsw/jbe@1309 | 11973         } | 
| bsw/jbe@1309 | 11974 | 
| bsw/jbe@1309 | 11975         while (endNode && endNode !== parentElement) { | 
| bsw/jbe@1309 | 11976           if (endNode.nodeType !== wysihtml.TEXT_NODE && !wysihtml.dom.contains(parentElement, endNode)) { | 
| bsw/jbe@1309 | 11977             return false; | 
| bsw/jbe@1309 | 11978           } | 
| bsw/jbe@1309 | 11979           if (wysihtml.dom.domNode(endNode).next({ignoreBlankTexts: true})) { | 
| bsw/jbe@1309 | 11980             return false; | 
| bsw/jbe@1309 | 11981           } | 
| bsw/jbe@1309 | 11982           endNode = endNode.parentNode; | 
| bsw/jbe@1309 | 11983         } | 
| bsw/jbe@1309 | 11984 | 
| bsw/jbe@1309 | 11985         return (wysihtml.lang.array(nodeNames).contains(parentElement.nodeName)) ? parentElement : false; | 
| bsw/jbe@1309 | 11986     }, | 
| bsw/jbe@1309 | 11987 | 
| bsw/jbe@1309 | 11988     isInThisEditable: function() { | 
| bsw/jbe@1309 | 11989       var sel = this.getSelection(), | 
| bsw/jbe@1309 | 11990           fnode = sel.focusNode, | 
| bsw/jbe@1309 | 11991           anode = sel.anchorNode; | 
| bsw/jbe@1309 | 11992 | 
| bsw/jbe@1309 | 11993       // In IE node contains will not work for textnodes, thus taking parentNode | 
| bsw/jbe@1309 | 11994       if (fnode && fnode.nodeType !== 1) { | 
| bsw/jbe@1309 | 11995         fnode = fnode.parentNode; | 
| bsw/jbe@1309 | 11996       } | 
| bsw/jbe@1309 | 11997 | 
| bsw/jbe@1309 | 11998       if (anode && anode.nodeType !== 1) { | 
| bsw/jbe@1309 | 11999         anode = anode.parentNode; | 
| bsw/jbe@1309 | 12000       } | 
| bsw/jbe@1309 | 12001 | 
| bsw/jbe@1309 | 12002       return anode && fnode && | 
| bsw/jbe@1309 | 12003              (wysihtml.dom.contains(this.composer.element, fnode) || this.composer.element === fnode) && | 
| bsw/jbe@1309 | 12004              (wysihtml.dom.contains(this.composer.element, anode) || this.composer.element === anode); | 
| bsw/jbe@1309 | 12005     }, | 
| bsw/jbe@1309 | 12006 | 
| bsw/jbe@1309 | 12007     deselect: function() { | 
| bsw/jbe@1309 | 12008       var sel = this.getSelection(); | 
| bsw/jbe@1309 | 12009       sel && sel.removeAllRanges(); | 
| bsw/jbe@1309 | 12010     } | 
| bsw/jbe@1309 | 12011   }); | 
| bsw/jbe@1309 | 12012 | 
| bsw/jbe@1309 | 12013 })(wysihtml); | 
| bsw/jbe@1309 | 12014 | 
| bsw/jbe@1309 | 12015 /** | 
| bsw/jbe@1309 | 12016  * Rich Text Query/Formatting Commands | 
| bsw/jbe@1309 | 12017  * | 
| bsw/jbe@1309 | 12018  * @example | 
| bsw/jbe@1309 | 12019  *    var commands = new wysihtml.Commands(editor); | 
| bsw/jbe@1309 | 12020  */ | 
| bsw/jbe@1309 | 12021 wysihtml.Commands = Base.extend( | 
| bsw/jbe@1309 | 12022   /** @scope wysihtml.Commands.prototype */ { | 
| bsw/jbe@1309 | 12023   constructor: function(editor) { | 
| bsw/jbe@1309 | 12024     this.editor   = editor; | 
| bsw/jbe@1309 | 12025     this.composer = editor.composer; | 
| bsw/jbe@1309 | 12026     this.doc      = this.composer.doc; | 
| bsw/jbe@1309 | 12027   }, | 
| bsw/jbe@1309 | 12028 | 
| bsw/jbe@1309 | 12029   /** | 
| bsw/jbe@1309 | 12030    * Check whether the browser supports the given command | 
| bsw/jbe@1309 | 12031    * | 
| bsw/jbe@1309 | 12032    * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList") | 
| bsw/jbe@1309 | 12033    * @example | 
| bsw/jbe@1309 | 12034    *    commands.supports("createLink"); | 
| bsw/jbe@1309 | 12035    */ | 
| bsw/jbe@1309 | 12036   support: function(command) { | 
| bsw/jbe@1309 | 12037     return wysihtml.browser.supportsCommand(this.doc, command); | 
| bsw/jbe@1309 | 12038   }, | 
| bsw/jbe@1309 | 12039 | 
| bsw/jbe@1309 | 12040   /** | 
| bsw/jbe@1309 | 12041    * Check whether the browser supports the given command | 
| bsw/jbe@1309 | 12042    * | 
| bsw/jbe@1309 | 12043    * @param {String} command The command string which to execute (eg. "bold", "italic", "insertUnorderedList") | 
| bsw/jbe@1309 | 12044    * @param {String} [value] The command value parameter, needed for some commands ("createLink", "insertImage", ...), optional for commands that don't require one ("bold", "underline", ...) | 
| bsw/jbe@1309 | 12045    * @example | 
| bsw/jbe@1309 | 12046    *    commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg"); | 
| bsw/jbe@1309 | 12047    */ | 
| bsw/jbe@1309 | 12048   exec: function(command, value) { | 
| bsw/jbe@1309 | 12049     var obj     = wysihtml.commands[command], | 
| bsw/jbe@1309 | 12050         args    = wysihtml.lang.array(arguments).get(), | 
| bsw/jbe@1309 | 12051         method  = obj && obj.exec, | 
| bsw/jbe@1309 | 12052         result  = null; | 
| bsw/jbe@1309 | 12053 | 
| bsw/jbe@1309 | 12054     // If composer ahs placeholder unset it before command | 
| bsw/jbe@1309 | 12055     // Do not apply on commands that are behavioral | 
| bsw/jbe@1309 | 12056     if (this.composer.hasPlaceholderSet() && !wysihtml.lang.array(['styleWithCSS', 'enableObjectResizing', 'enableInlineTableEditing']).contains(command)) { | 
| bsw/jbe@1309 | 12057       this.composer.element.innerHTML = ""; | 
| bsw/jbe@1309 | 12058       this.composer.selection.selectNode(this.composer.element); | 
| bsw/jbe@1309 | 12059     } | 
| bsw/jbe@1309 | 12060 | 
| bsw/jbe@1309 | 12061     this.editor.fire("beforecommand:composer"); | 
| bsw/jbe@1309 | 12062 | 
| bsw/jbe@1309 | 12063     if (method) { | 
| bsw/jbe@1309 | 12064       args.unshift(this.composer); | 
| bsw/jbe@1309 | 12065       result = method.apply(obj, args); | 
| bsw/jbe@1309 | 12066     } else { | 
| bsw/jbe@1309 | 12067       try { | 
| bsw/jbe@1309 | 12068         // try/catch for buggy firefox | 
| bsw/jbe@1309 | 12069         result = this.doc.execCommand(command, false, value); | 
| bsw/jbe@1309 | 12070       } catch(e) {} | 
| bsw/jbe@1309 | 12071     } | 
| bsw/jbe@1309 | 12072 | 
| bsw/jbe@1309 | 12073     this.editor.fire("aftercommand:composer"); | 
| bsw/jbe@1309 | 12074     return result; | 
| bsw/jbe@1309 | 12075   }, | 
| bsw/jbe@1309 | 12076 | 
| bsw/jbe@1309 | 12077   remove: function(command, commandValue) { | 
| bsw/jbe@1309 | 12078     var obj     = wysihtml.commands[command], | 
| bsw/jbe@1309 | 12079         args    = wysihtml.lang.array(arguments).get(), | 
| bsw/jbe@1309 | 12080         method  = obj && obj.remove; | 
| bsw/jbe@1309 | 12081     if (method) { | 
| bsw/jbe@1309 | 12082       args.unshift(this.composer); | 
| bsw/jbe@1309 | 12083       return method.apply(obj, args); | 
| bsw/jbe@1309 | 12084     } | 
| bsw/jbe@1309 | 12085   }, | 
| bsw/jbe@1309 | 12086 | 
| bsw/jbe@1309 | 12087   /** | 
| bsw/jbe@1309 | 12088    * Check whether the current command is active | 
| bsw/jbe@1309 | 12089    * If the caret is within a bold text, then calling this with command "bold" should return true | 
| bsw/jbe@1309 | 12090    * | 
| bsw/jbe@1309 | 12091    * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList") | 
| bsw/jbe@1309 | 12092    * @param {String} [commandValue] The command value parameter (eg. for "insertImage" the image src) | 
| bsw/jbe@1309 | 12093    * @return {Boolean} Whether the command is active | 
| bsw/jbe@1309 | 12094    * @example | 
| bsw/jbe@1309 | 12095    *    var isCurrentSelectionBold = commands.state("bold"); | 
| bsw/jbe@1309 | 12096    */ | 
| bsw/jbe@1309 | 12097   state: function(command, commandValue) { | 
| bsw/jbe@1309 | 12098     var obj     = wysihtml.commands[command], | 
| bsw/jbe@1309 | 12099         args    = wysihtml.lang.array(arguments).get(), | 
| bsw/jbe@1309 | 12100         method  = obj && obj.state; | 
| bsw/jbe@1309 | 12101     if (method) { | 
| bsw/jbe@1309 | 12102       args.unshift(this.composer); | 
| bsw/jbe@1309 | 12103       return method.apply(obj, args); | 
| bsw/jbe@1309 | 12104     } else { | 
| bsw/jbe@1309 | 12105       try { | 
| bsw/jbe@1309 | 12106         // try/catch for buggy firefox | 
| bsw/jbe@1309 | 12107         return this.doc.queryCommandState(command); | 
| bsw/jbe@1309 | 12108       } catch(e) { | 
| bsw/jbe@1309 | 12109         return false; | 
| bsw/jbe@1309 | 12110       } | 
| bsw/jbe@1309 | 12111     } | 
| bsw/jbe@1309 | 12112   }, | 
| bsw/jbe@1309 | 12113 | 
| bsw/jbe@1309 | 12114   /* Get command state parsed value if command has stateValue parsing function */ | 
| bsw/jbe@1309 | 12115   stateValue: function(command) { | 
| bsw/jbe@1309 | 12116     var obj     = wysihtml.commands[command], | 
| bsw/jbe@1309 | 12117         args    = wysihtml.lang.array(arguments).get(), | 
| bsw/jbe@1309 | 12118         method  = obj && obj.stateValue; | 
| bsw/jbe@1309 | 12119     if (method) { | 
| bsw/jbe@1309 | 12120       args.unshift(this.composer); | 
| bsw/jbe@1309 | 12121       return method.apply(obj, args); | 
| bsw/jbe@1309 | 12122     } else { | 
| bsw/jbe@1309 | 12123       return false; | 
| bsw/jbe@1309 | 12124     } | 
| bsw/jbe@1309 | 12125   } | 
| bsw/jbe@1309 | 12126 }); | 
| bsw/jbe@1309 | 12127 | 
| bsw/jbe@1309 | 12128 (function(wysihtml) { | 
| bsw/jbe@1309 | 12129 | 
| bsw/jbe@1309 | 12130   var nodeOptions = { | 
| bsw/jbe@1309 | 12131     nodeName: "A", | 
| bsw/jbe@1309 | 12132     toggle: false | 
| bsw/jbe@1309 | 12133   }; | 
| bsw/jbe@1309 | 12134 | 
| bsw/jbe@1309 | 12135   function getOptions(value) { | 
| bsw/jbe@1309 | 12136     var options = typeof value === 'object' ? value : {'href': value}; | 
| bsw/jbe@1309 | 12137     return wysihtml.lang.object({}).merge(nodeOptions).merge({'attribute': value}).get(); | 
| bsw/jbe@1309 | 12138   } | 
| bsw/jbe@1309 | 12139 | 
| bsw/jbe@1309 | 12140   wysihtml.commands.createLink  = { | 
| bsw/jbe@1309 | 12141     exec: function(composer, command, value) { | 
| bsw/jbe@1309 | 12142       var opts = getOptions(value); | 
| bsw/jbe@1309 | 12143 | 
| bsw/jbe@1309 | 12144       if (composer.selection.isCollapsed() && !this.state(composer, command)) { | 
| bsw/jbe@1309 | 12145         var textNode = composer.doc.createTextNode(opts.attribute.href); | 
| bsw/jbe@1309 | 12146         composer.selection.insertNode(textNode); | 
| bsw/jbe@1309 | 12147         composer.selection.selectNode(textNode); | 
| bsw/jbe@1309 | 12148       } | 
| bsw/jbe@1309 | 12149       wysihtml.commands.formatInline.exec(composer, command, opts); | 
| bsw/jbe@1309 | 12150     }, | 
| bsw/jbe@1309 | 12151 | 
| bsw/jbe@1309 | 12152     state: function(composer, command) { | 
| bsw/jbe@1309 | 12153       return wysihtml.commands.formatInline.state(composer, command, nodeOptions); | 
| bsw/jbe@1309 | 12154     } | 
| bsw/jbe@1309 | 12155   }; | 
| bsw/jbe@1309 | 12156 | 
| bsw/jbe@1309 | 12157 })(wysihtml); | 
| bsw/jbe@1309 | 12158 | 
| bsw/jbe@1309 | 12159 /* Formatblock | 
| bsw/jbe@1309 | 12160  * Is used to insert block level elements | 
| bsw/jbe@1309 | 12161  * It tries to solve the case that some block elements should not contain other block level elements (h1-6, p, ...) | 
| bsw/jbe@1309 | 12162  * | 
| bsw/jbe@1309 | 12163 */ | 
| bsw/jbe@1309 | 12164 (function(wysihtml) { | 
| bsw/jbe@1309 | 12165 | 
| bsw/jbe@1309 | 12166   var dom = wysihtml.dom, | 
| bsw/jbe@1309 | 12167       // When the caret is within a H1 and the H4 is invoked, the H1 should turn into H4 | 
| bsw/jbe@1309 | 12168       // instead of creating a H4 within a H1 which would result in semantically invalid html | 
| bsw/jbe@1309 | 12169       UNNESTABLE_BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre", | 
| bsw/jbe@1309 | 12170       BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote", | 
| bsw/jbe@1309 | 12171       INLINE_ELEMENTS = "b, big, i, small, tt, abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var, a, bdo, br, q, span, sub, sup, button, label, textarea, input, select, u"; | 
| bsw/jbe@1309 | 12172 | 
| bsw/jbe@1309 | 12173   function correctOptionsForSimilarityCheck(options) { | 
| bsw/jbe@1309 | 12174     return { | 
| bsw/jbe@1309 | 12175       nodeName: options.nodeName || null, | 
| bsw/jbe@1309 | 12176       className: (!options.classRegExp) ? options.className || null : null, | 
| bsw/jbe@1309 | 12177       classRegExp: options.classRegExp || null, | 
| bsw/jbe@1309 | 12178       styleProperty: options.styleProperty || null | 
| bsw/jbe@1309 | 12179     }; | 
| bsw/jbe@1309 | 12180   } | 
| bsw/jbe@1309 | 12181 | 
| bsw/jbe@1309 | 12182   function getRangeNode(node, offset) { | 
| bsw/jbe@1309 | 12183     if (node.nodeType === 3) { | 
| bsw/jbe@1309 | 12184       return node; | 
| bsw/jbe@1309 | 12185     } else { | 
| bsw/jbe@1309 | 12186       return node.childNodes[offset] || node; | 
| bsw/jbe@1309 | 12187     } | 
| bsw/jbe@1309 | 12188   } | 
| bsw/jbe@1309 | 12189 | 
| bsw/jbe@1309 | 12190   // Returns if node is a line break | 
| bsw/jbe@1309 | 12191   function isBr(n) { | 
| bsw/jbe@1309 | 12192     return n && n.nodeType === 1 && n.nodeName === "BR"; | 
| bsw/jbe@1309 | 12193   } | 
| bsw/jbe@1309 | 12194 | 
| bsw/jbe@1309 | 12195   // Is block level element | 
| bsw/jbe@1309 | 12196   function isBlock(n, composer) { | 
| bsw/jbe@1309 | 12197     return n && n.nodeType === 1 && composer.win.getComputedStyle(n).display === "block"; | 
| bsw/jbe@1309 | 12198   } | 
| bsw/jbe@1309 | 12199 | 
| bsw/jbe@1309 | 12200   // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring) | 
| bsw/jbe@1309 | 12201   function isBookmark(n) { | 
| bsw/jbe@1309 | 12202     return n && n.nodeType === 1 && n.classList.contains('rangySelectionBoundary'); | 
| bsw/jbe@1309 | 12203   } | 
| bsw/jbe@1309 | 12204 | 
| bsw/jbe@1309 | 12205   // Is line breaking node | 
| bsw/jbe@1309 | 12206   function isLineBreaking(n, composer) { | 
| bsw/jbe@1309 | 12207     return isBr(n) || isBlock(n, composer); | 
| bsw/jbe@1309 | 12208   } | 
| bsw/jbe@1309 | 12209 | 
| bsw/jbe@1309 | 12210   // Removes empty block level elements | 
| bsw/jbe@1309 | 12211   function cleanup(composer, newBlockElements) { | 
| bsw/jbe@1309 | 12212     wysihtml.dom.removeInvisibleSpaces(composer.element); | 
| bsw/jbe@1309 | 12213     var container = composer.element, | 
| bsw/jbe@1309 | 12214         allElements = container.querySelectorAll(BLOCK_ELEMENTS), | 
| bsw/jbe@1309 | 12215         noEditQuery = composer.config.classNames.uneditableContainer + ([""]).concat(BLOCK_ELEMENTS.split(',')).join(", " + composer.config.classNames.uneditableContainer + ' '), | 
| bsw/jbe@1309 | 12216         uneditables = container.querySelectorAll(noEditQuery), | 
| bsw/jbe@1309 | 12217         elements = wysihtml.lang.array(allElements).without(uneditables), // Lets not touch uneditable elements and their contents | 
| bsw/jbe@1309 | 12218         nbIdx; | 
| bsw/jbe@1309 | 12219 | 
| bsw/jbe@1309 | 12220     for (var i = elements.length; i--;) { | 
| bsw/jbe@1309 | 12221       if (elements[i].innerHTML.replace(/[\uFEFF]/g, '') === "" && (newBlockElements.length === 0 || elements[i] !== newBlockElements[newBlockElements.length - 1])) { | 
| bsw/jbe@1309 | 12222         // If cleanup removes some new block elements. remove them from newblocks array too | 
| bsw/jbe@1309 | 12223         nbIdx = wysihtml.lang.array(newBlockElements).indexOf(elements[i]); | 
| bsw/jbe@1309 | 12224         if (nbIdx > -1) { | 
| bsw/jbe@1309 | 12225           newBlockElements.splice(nbIdx, 1); | 
| bsw/jbe@1309 | 12226         } | 
| bsw/jbe@1309 | 12227         elements[i].parentNode.removeChild(elements[i]); | 
| bsw/jbe@1309 | 12228       } | 
| bsw/jbe@1309 | 12229     } | 
| bsw/jbe@1309 | 12230 | 
| bsw/jbe@1309 | 12231     return newBlockElements; | 
| bsw/jbe@1309 | 12232   } | 
| bsw/jbe@1309 | 12233 | 
| bsw/jbe@1309 | 12234   function defaultNodeName(composer) { | 
| bsw/jbe@1309 | 12235     return composer.config.useLineBreaks ? "DIV" : "P"; | 
| bsw/jbe@1309 | 12236   } | 
| bsw/jbe@1309 | 12237 | 
| bsw/jbe@1309 | 12238   // The outermost un-nestable block element parent of from node | 
| bsw/jbe@1309 | 12239   function findOuterBlock(node, container, allBlocks) { | 
| bsw/jbe@1309 | 12240     var n = node, | 
| bsw/jbe@1309 | 12241         block = null; | 
| bsw/jbe@1309 | 12242 | 
| bsw/jbe@1309 | 12243     while (n && container && n !== container) { | 
| bsw/jbe@1309 | 12244       if (n.nodeType === 1 && n.matches(allBlocks ? BLOCK_ELEMENTS : UNNESTABLE_BLOCK_ELEMENTS)) { | 
| bsw/jbe@1309 | 12245         block = n; | 
| bsw/jbe@1309 | 12246       } | 
| bsw/jbe@1309 | 12247       n = n.parentNode; | 
| bsw/jbe@1309 | 12248     } | 
| bsw/jbe@1309 | 12249 | 
| bsw/jbe@1309 | 12250     return block; | 
| bsw/jbe@1309 | 12251   } | 
| bsw/jbe@1309 | 12252 | 
| bsw/jbe@1309 | 12253   // Clone for splitting the inner inline element out of its parent inline elements context | 
| bsw/jbe@1309 | 12254   // For example if selection is in bold and italic, clone the outer nodes and wrap these around content and return | 
| bsw/jbe@1309 | 12255   function cloneOuterInlines(node, container) { | 
| bsw/jbe@1309 | 12256     var n = node, | 
| bsw/jbe@1309 | 12257         innerNode, | 
| bsw/jbe@1309 | 12258         parentNode, | 
| bsw/jbe@1309 | 12259         el = null, | 
| bsw/jbe@1309 | 12260         el2; | 
| bsw/jbe@1309 | 12261 | 
| bsw/jbe@1309 | 12262     while (n && container && n !== container) { | 
| bsw/jbe@1309 | 12263       if (n.nodeType === 1 && n.matches(INLINE_ELEMENTS)) { | 
| bsw/jbe@1309 | 12264         parentNode = n; | 
| bsw/jbe@1309 | 12265         if (el === null) { | 
| bsw/jbe@1309 | 12266           el = n.cloneNode(false); | 
| bsw/jbe@1309 | 12267           innerNode = el; | 
| bsw/jbe@1309 | 12268         } else { | 
| bsw/jbe@1309 | 12269           el2 = n.cloneNode(false); | 
| bsw/jbe@1309 | 12270           el2.appendChild(el); | 
| bsw/jbe@1309 | 12271           el = el2; | 
| bsw/jbe@1309 | 12272         } | 
| bsw/jbe@1309 | 12273       } | 
| bsw/jbe@1309 | 12274       n = n.parentNode; | 
| bsw/jbe@1309 | 12275     } | 
| bsw/jbe@1309 | 12276 | 
| bsw/jbe@1309 | 12277     return { | 
| bsw/jbe@1309 | 12278       parent: parentNode, | 
| bsw/jbe@1309 | 12279       outerNode: el, | 
| bsw/jbe@1309 | 12280       innerNode: innerNode | 
| bsw/jbe@1309 | 12281     }; | 
| bsw/jbe@1309 | 12282   } | 
| bsw/jbe@1309 | 12283 | 
| bsw/jbe@1309 | 12284   // Formats an element according to options nodeName, className, styleProperty, styleValue | 
| bsw/jbe@1309 | 12285   // If element is not defined, creates new element | 
| bsw/jbe@1309 | 12286   // if opotions is null, remove format instead | 
| bsw/jbe@1309 | 12287   function applyOptionsToElement(element, options, composer) { | 
| bsw/jbe@1309 | 12288 | 
| bsw/jbe@1309 | 12289     if (!element) { | 
| bsw/jbe@1309 | 12290       element = composer.doc.createElement(options.nodeName || defaultNodeName(composer)); | 
| bsw/jbe@1309 | 12291       // Add invisible space as otherwise webkit cannot set selection or range to it correctly | 
| bsw/jbe@1309 | 12292       element.appendChild(composer.doc.createTextNode(wysihtml.INVISIBLE_SPACE)); | 
| bsw/jbe@1309 | 12293     } | 
| bsw/jbe@1309 | 12294 | 
| bsw/jbe@1309 | 12295     if (options.nodeName && element.nodeName !== options.nodeName) { | 
| bsw/jbe@1309 | 12296       element = dom.renameElement(element, options.nodeName); | 
| bsw/jbe@1309 | 12297     } | 
| bsw/jbe@1309 | 12298 | 
| bsw/jbe@1309 | 12299     // Remove similar classes before applying className | 
| bsw/jbe@1309 | 12300     if (options.classRegExp) { | 
| bsw/jbe@1309 | 12301       element.className = element.className.replace(options.classRegExp, ""); | 
| bsw/jbe@1309 | 12302     } | 
| bsw/jbe@1309 | 12303     if (options.className) { | 
| bsw/jbe@1309 | 12304       element.classList.add(options.className); | 
| bsw/jbe@1309 | 12305     } | 
| bsw/jbe@1309 | 12306 | 
| bsw/jbe@1309 | 12307     if (options.styleProperty && typeof options.styleValue !== "undefined") { | 
| bsw/jbe@1309 | 12308       element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue; | 
| bsw/jbe@1309 | 12309     } | 
| bsw/jbe@1309 | 12310 | 
| bsw/jbe@1309 | 12311     return element; | 
| bsw/jbe@1309 | 12312   } | 
| bsw/jbe@1309 | 12313 | 
| bsw/jbe@1309 | 12314   // Unsets element properties by options | 
| bsw/jbe@1309 | 12315   // If nodename given and matches current element, element is unwrapped or converted to default node (depending on presence of class and style attributes) | 
| bsw/jbe@1309 | 12316   function removeOptionsFromElement(element, options, composer) { | 
| bsw/jbe@1309 | 12317     var style, classes, | 
| bsw/jbe@1309 | 12318         prevNode = element.previousSibling, | 
| bsw/jbe@1309 | 12319         nextNode = element.nextSibling, | 
| bsw/jbe@1309 | 12320         unwrapped = false; | 
| bsw/jbe@1309 | 12321 | 
| bsw/jbe@1309 | 12322     if (options.styleProperty) { | 
| bsw/jbe@1309 | 12323       element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = ''; | 
| bsw/jbe@1309 | 12324     } | 
| bsw/jbe@1309 | 12325     if (options.className) { | 
| bsw/jbe@1309 | 12326       element.classList.remove(options.className); | 
| bsw/jbe@1309 | 12327     } | 
| bsw/jbe@1309 | 12328 | 
| bsw/jbe@1309 | 12329     if (options.classRegExp) { | 
| bsw/jbe@1309 | 12330       element.className = element.className.replace(options.classRegExp, ""); | 
| bsw/jbe@1309 | 12331     } | 
| bsw/jbe@1309 | 12332 | 
| bsw/jbe@1309 | 12333     // Clean up blank class attribute | 
| bsw/jbe@1309 | 12334     if (element.getAttribute('class') !== null && element.getAttribute('class').trim() === "") { | 
| bsw/jbe@1309 | 12335       element.removeAttribute('class'); | 
| bsw/jbe@1309 | 12336     } | 
| bsw/jbe@1309 | 12337 | 
| bsw/jbe@1309 | 12338     if (options.nodeName && element.nodeName.toLowerCase() === options.nodeName.toLowerCase()) { | 
| bsw/jbe@1309 | 12339       style = element.getAttribute('style'); | 
| bsw/jbe@1309 | 12340       if (!style || style.trim() === '') { | 
| bsw/jbe@1309 | 12341         dom.unwrap(element); | 
| bsw/jbe@1309 | 12342         unwrapped = true; | 
| bsw/jbe@1309 | 12343       } else { | 
| bsw/jbe@1309 | 12344         element = dom.renameElement(element, defaultNodeName(composer)); | 
| bsw/jbe@1309 | 12345       } | 
| bsw/jbe@1309 | 12346     } | 
| bsw/jbe@1309 | 12347 | 
| bsw/jbe@1309 | 12348     // Clean up blank style attribute | 
| bsw/jbe@1309 | 12349     if (element.getAttribute('style') !== null && element.getAttribute('style').trim() === "") { | 
| bsw/jbe@1309 | 12350       element.removeAttribute('style'); | 
| bsw/jbe@1309 | 12351     } | 
| bsw/jbe@1309 | 12352 | 
| bsw/jbe@1309 | 12353     if (unwrapped) { | 
| bsw/jbe@1309 | 12354       applySurroundingLineBreaks(prevNode, nextNode, composer); | 
| bsw/jbe@1309 | 12355     } | 
| bsw/jbe@1309 | 12356   } | 
| bsw/jbe@1309 | 12357 | 
| bsw/jbe@1309 | 12358   // Unwraps block level elements from inside content | 
| bsw/jbe@1309 | 12359   // Useful as not all block level elements can contain other block-levels | 
| bsw/jbe@1309 | 12360   function unwrapBlocksFromContent(element) { | 
| bsw/jbe@1309 | 12361     var blocks = element.querySelectorAll(BLOCK_ELEMENTS) || [], // Find unnestable block elements in extracted contents | 
| bsw/jbe@1309 | 12362         nextEl, prevEl; | 
| bsw/jbe@1309 | 12363 | 
| bsw/jbe@1309 | 12364     for (var i = blocks.length; i--;) { | 
| bsw/jbe@1309 | 12365       nextEl = wysihtml.dom.domNode(blocks[i]).next({nodeTypes: [1,3], ignoreBlankTexts: true}), | 
| bsw/jbe@1309 | 12366       prevEl = wysihtml.dom.domNode(blocks[i]).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 12367 | 
| bsw/jbe@1309 | 12368       if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') { | 
| bsw/jbe@1309 | 12369         if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') { | 
| bsw/jbe@1309 | 12370           blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl); | 
| bsw/jbe@1309 | 12371         } | 
| bsw/jbe@1309 | 12372       } | 
| bsw/jbe@1309 | 12373       if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') { | 
| bsw/jbe@1309 | 12374         if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') { | 
| bsw/jbe@1309 | 12375           blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl); | 
| bsw/jbe@1309 | 12376         } | 
| bsw/jbe@1309 | 12377       } | 
| bsw/jbe@1309 | 12378       wysihtml.dom.unwrap(blocks[i]); | 
| bsw/jbe@1309 | 12379     } | 
| bsw/jbe@1309 | 12380   } | 
| bsw/jbe@1309 | 12381 | 
| bsw/jbe@1309 | 12382   // Fix ranges that visually cover whole block element to actually cover the block | 
| bsw/jbe@1309 | 12383   function fixRangeCoverage(range, composer) { | 
| bsw/jbe@1309 | 12384     var node, | 
| bsw/jbe@1309 | 12385         start = range.startContainer, | 
| bsw/jbe@1309 | 12386         end = range.endContainer; | 
| bsw/jbe@1309 | 12387 | 
| bsw/jbe@1309 | 12388     // If range has only one childNode and it is end to end the range, extend the range to contain the container element too | 
| bsw/jbe@1309 | 12389     // This ensures the wrapper node is modified and optios added to it | 
| bsw/jbe@1309 | 12390     if (start && start.nodeType === 1 && start === end) { | 
| bsw/jbe@1309 | 12391       if (start.firstChild === start.lastChild && range.endOffset === 1) { | 
| bsw/jbe@1309 | 12392         if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') { | 
| bsw/jbe@1309 | 12393           range.setStartBefore(start); | 
| bsw/jbe@1309 | 12394           range.setEndAfter(end); | 
| bsw/jbe@1309 | 12395         } | 
| bsw/jbe@1309 | 12396       } | 
| bsw/jbe@1309 | 12397       return; | 
| bsw/jbe@1309 | 12398     } | 
| bsw/jbe@1309 | 12399 | 
| bsw/jbe@1309 | 12400     // If range starts outside of node and ends inside at textrange and covers the whole node visually, extend end to cover the node end too | 
| bsw/jbe@1309 | 12401     if (start && start.nodeType === 1 && end.nodeType === 3) { | 
| bsw/jbe@1309 | 12402       if (start.firstChild === end && range.endOffset === end.data.length) { | 
| bsw/jbe@1309 | 12403         if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') { | 
| bsw/jbe@1309 | 12404           range.setEndAfter(start); | 
| bsw/jbe@1309 | 12405         } | 
| bsw/jbe@1309 | 12406       } | 
| bsw/jbe@1309 | 12407       return; | 
| bsw/jbe@1309 | 12408     } | 
| bsw/jbe@1309 | 12409 | 
| bsw/jbe@1309 | 12410     // If range ends outside of node and starts inside at textrange and covers the whole node visually, extend start to cover the node start too | 
| bsw/jbe@1309 | 12411     if (end && end.nodeType === 1 && start.nodeType === 3) { | 
| bsw/jbe@1309 | 12412       if (end.firstChild === start && range.startOffset === 0) { | 
| bsw/jbe@1309 | 12413         if (end !== composer.element && end.nodeName !== 'LI' && end.nodeName !== 'TD') { | 
| bsw/jbe@1309 | 12414           range.setStartBefore(end); | 
| bsw/jbe@1309 | 12415         } | 
| bsw/jbe@1309 | 12416       } | 
| bsw/jbe@1309 | 12417       return; | 
| bsw/jbe@1309 | 12418     } | 
| bsw/jbe@1309 | 12419 | 
| bsw/jbe@1309 | 12420     // If range covers a whole textnode and the textnode is the only child of node, extend range to node | 
| bsw/jbe@1309 | 12421     if (start && start.nodeType === 3 && start === end && start.parentNode.childNodes.length === 1) { | 
| bsw/jbe@1309 | 12422       if (range.endOffset == end.data.length && range.startOffset === 0) { | 
| bsw/jbe@1309 | 12423         node = start.parentNode; | 
| bsw/jbe@1309 | 12424         if (node !== composer.element && node.nodeName !== 'LI' && node.nodeName !== 'TD') { | 
| bsw/jbe@1309 | 12425           range.setStartBefore(node); | 
| bsw/jbe@1309 | 12426           range.setEndAfter(node); | 
| bsw/jbe@1309 | 12427         } | 
| bsw/jbe@1309 | 12428       } | 
| bsw/jbe@1309 | 12429       return; | 
| bsw/jbe@1309 | 12430     } | 
| bsw/jbe@1309 | 12431   } | 
| bsw/jbe@1309 | 12432 | 
| bsw/jbe@1309 | 12433   // Scans ranges array for insertion points that are not allowed to insert block tags fixes/splits illegal ranges | 
| bsw/jbe@1309 | 12434   // Some places do not allow block level elements inbetween (inside ul and outside li) | 
| bsw/jbe@1309 | 12435   // TODO: might need extending for other nodes besides li (maybe dd,dl,dt) | 
| bsw/jbe@1309 | 12436   function fixNotPermittedInsertionPoints(ranges) { | 
| bsw/jbe@1309 | 12437     var newRanges = [], | 
| bsw/jbe@1309 | 12438         lis, j, maxj, tmpRange, rangePos, closestLI; | 
| bsw/jbe@1309 | 12439 | 
| bsw/jbe@1309 | 12440     for (var i = 0, maxi = ranges.length; i < maxi; i++) { | 
| bsw/jbe@1309 | 12441 | 
| bsw/jbe@1309 | 12442       // Fixes range start and end positions if inside UL or OL element (outside of LI) | 
| bsw/jbe@1309 | 12443       if (ranges[i].startContainer.nodeType === 1 && ranges[i].startContainer.matches('ul, ol')) { | 
| bsw/jbe@1309 | 12444         ranges[i].setStart(ranges[i].startContainer.childNodes[ranges[i].startOffset], 0); | 
| bsw/jbe@1309 | 12445       } | 
| bsw/jbe@1309 | 12446       if (ranges[i].endContainer.nodeType === 1 && ranges[i].endContainer.matches('ul, ol')) { | 
| bsw/jbe@1309 | 12447         closestLI = ranges[i].endContainer.childNodes[Math.max(ranges[i].endOffset - 1, 0)]; | 
| bsw/jbe@1309 | 12448         if (closestLI.childNodes) { | 
| bsw/jbe@1309 | 12449           ranges[i].setEnd(closestLI, closestLI.childNodes.length); | 
| bsw/jbe@1309 | 12450         } | 
| bsw/jbe@1309 | 12451       } | 
| bsw/jbe@1309 | 12452 | 
| bsw/jbe@1309 | 12453       // Get all LI eleemnts in selection (fully or partially covered) | 
| bsw/jbe@1309 | 12454       // And make sure ranges are either inside LI or outside UL/OL | 
| bsw/jbe@1309 | 12455       // Split and add new ranges as needed to cover same range content | 
| bsw/jbe@1309 | 12456       // TODO: Needs improvement to accept DL, DD, DT | 
| bsw/jbe@1309 | 12457       lis = ranges[i].getNodes([1], function(node) { | 
| bsw/jbe@1309 | 12458         return node.nodeName === "LI"; | 
| bsw/jbe@1309 | 12459       }); | 
| bsw/jbe@1309 | 12460       if (lis.length > 0) { | 
| bsw/jbe@1309 | 12461 | 
| bsw/jbe@1309 | 12462         for (j = 0, maxj = lis.length; j < maxj; j++) { | 
| bsw/jbe@1309 | 12463           rangePos = ranges[i].compareNode(lis[j]); | 
| bsw/jbe@1309 | 12464 | 
| bsw/jbe@1309 | 12465           // Fixes start of range that crosses LI border | 
| bsw/jbe@1309 | 12466           if (rangePos === ranges[i].NODE_AFTER || rangePos === ranges[i].NODE_INSIDE) { | 
| bsw/jbe@1309 | 12467             // Range starts before and ends inside the node | 
| bsw/jbe@1309 | 12468 | 
| bsw/jbe@1309 | 12469             tmpRange = ranges[i].cloneRange(); | 
| bsw/jbe@1309 | 12470             closestLI = wysihtml.dom.domNode(lis[j]).prev({nodeTypes: [1]}); | 
| bsw/jbe@1309 | 12471 | 
| bsw/jbe@1309 | 12472             if (closestLI) { | 
| bsw/jbe@1309 | 12473               tmpRange.setEnd(closestLI, closestLI.childNodes.length); | 
| bsw/jbe@1309 | 12474             } else if (lis[j].closest('ul, ol')) { | 
| bsw/jbe@1309 | 12475               tmpRange.setEndBefore(lis[j].closest('ul, ol')); | 
| bsw/jbe@1309 | 12476             } else { | 
| bsw/jbe@1309 | 12477               tmpRange.setEndBefore(lis[j]); | 
| bsw/jbe@1309 | 12478             } | 
| bsw/jbe@1309 | 12479             newRanges.push(tmpRange); | 
| bsw/jbe@1309 | 12480             ranges[i].setStart(lis[j], 0); | 
| bsw/jbe@1309 | 12481           } | 
| bsw/jbe@1309 | 12482 | 
| bsw/jbe@1309 | 12483           // Fixes end of range that crosses li border | 
| bsw/jbe@1309 | 12484           if (rangePos === ranges[i].NODE_BEFORE || rangePos === ranges[i].NODE_INSIDE) { | 
| bsw/jbe@1309 | 12485             // Range starts inside the node and ends after node | 
| bsw/jbe@1309 | 12486 | 
| bsw/jbe@1309 | 12487             tmpRange = ranges[i].cloneRange(); | 
| bsw/jbe@1309 | 12488             tmpRange.setEnd(lis[j], lis[j].childNodes.length); | 
| bsw/jbe@1309 | 12489             newRanges.push(tmpRange); | 
| bsw/jbe@1309 | 12490 | 
| bsw/jbe@1309 | 12491             // Find next LI in list and if present set range to it, else | 
| bsw/jbe@1309 | 12492             closestLI = wysihtml.dom.domNode(lis[j]).next({nodeTypes: [1]}); | 
| bsw/jbe@1309 | 12493             if (closestLI) { | 
| bsw/jbe@1309 | 12494               ranges[i].setStart(closestLI, 0); | 
| bsw/jbe@1309 | 12495             } else if (lis[j].closest('ul, ol')) { | 
| bsw/jbe@1309 | 12496               ranges[i].setStartAfter(lis[j].closest('ul, ol')); | 
| bsw/jbe@1309 | 12497             } else { | 
| bsw/jbe@1309 | 12498               ranges[i].setStartAfter(lis[j]); | 
| bsw/jbe@1309 | 12499             } | 
| bsw/jbe@1309 | 12500           } | 
| bsw/jbe@1309 | 12501         } | 
| bsw/jbe@1309 | 12502         newRanges.push(ranges[i]); | 
| bsw/jbe@1309 | 12503       } else { | 
| bsw/jbe@1309 | 12504         newRanges.push(ranges[i]); | 
| bsw/jbe@1309 | 12505       } | 
| bsw/jbe@1309 | 12506     } | 
| bsw/jbe@1309 | 12507     return newRanges; | 
| bsw/jbe@1309 | 12508   } | 
| bsw/jbe@1309 | 12509 | 
| bsw/jbe@1309 | 12510   // Return options object with nodeName set if original did not have any | 
| bsw/jbe@1309 | 12511   // Node name is set to local or global default | 
| bsw/jbe@1309 | 12512   function getOptionsWithNodename(options, defaultName, composer) { | 
| bsw/jbe@1309 | 12513     var correctedOptions = (options) ? wysihtml.lang.object(options).clone(true) : null; | 
| bsw/jbe@1309 | 12514     if (correctedOptions) { | 
| bsw/jbe@1309 | 12515       correctedOptions.nodeName = correctedOptions.nodeName || defaultName || defaultNodeName(composer); | 
| bsw/jbe@1309 | 12516     } | 
| bsw/jbe@1309 | 12517     return correctedOptions; | 
| bsw/jbe@1309 | 12518   } | 
| bsw/jbe@1309 | 12519 | 
| bsw/jbe@1309 | 12520   // Injects document fragment to range ensuring outer elements are split to a place where block elements are allowed to be inserted | 
| bsw/jbe@1309 | 12521   // Also wraps empty clones of split parent tags around fragment to keep formatting | 
| bsw/jbe@1309 | 12522   // If firstOuterBlock is given assume that instead of finding outer (useful for solving cases of some blocks are allowed into others while others are not) | 
| bsw/jbe@1309 | 12523   function injectFragmentToRange(fragment, range, composer, firstOuterBlock) { | 
| bsw/jbe@1309 | 12524     var rangeStartContainer = range.startContainer, | 
| bsw/jbe@1309 | 12525         firstOuterBlock = firstOuterBlock || findOuterBlock(rangeStartContainer, composer.element, true), | 
| bsw/jbe@1309 | 12526         outerInlines, first, last, prev, next; | 
| bsw/jbe@1309 | 12527 | 
| bsw/jbe@1309 | 12528     if (firstOuterBlock) { | 
| bsw/jbe@1309 | 12529       // If selection starts inside un-nestable block, split-escape the unnestable point and insert node between | 
| bsw/jbe@1309 | 12530       first = fragment.firstChild; | 
| bsw/jbe@1309 | 12531       last = fragment.lastChild; | 
| bsw/jbe@1309 | 12532 | 
| bsw/jbe@1309 | 12533       composer.selection.splitElementAtCaret(firstOuterBlock, fragment); | 
| bsw/jbe@1309 | 12534 | 
| bsw/jbe@1309 | 12535       next = wysihtml.dom.domNode(last).next({nodeTypes: [1,3], ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 12536       prev = wysihtml.dom.domNode(first).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 12537 | 
| bsw/jbe@1309 | 12538       if (first && !isLineBreaking(first, composer) && prev && !isLineBreaking(prev, composer)) { | 
| bsw/jbe@1309 | 12539         first.parentNode.insertBefore(composer.doc.createElement('br'), first); | 
| bsw/jbe@1309 | 12540       } | 
| bsw/jbe@1309 | 12541 | 
| bsw/jbe@1309 | 12542       if (last && !isLineBreaking(last, composer) && next && !isLineBreaking(next, composer)) { | 
| bsw/jbe@1309 | 12543         next.parentNode.insertBefore(composer.doc.createElement('br'), next); | 
| bsw/jbe@1309 | 12544       } | 
| bsw/jbe@1309 | 12545 | 
| bsw/jbe@1309 | 12546     } else { | 
| bsw/jbe@1309 | 12547       // Ensure node does not get inserted into an inline where it is not allowed | 
| bsw/jbe@1309 | 12548       outerInlines = cloneOuterInlines(rangeStartContainer, composer.element); | 
| bsw/jbe@1309 | 12549       if (outerInlines.outerNode && outerInlines.innerNode && outerInlines.parent) { | 
| bsw/jbe@1309 | 12550         if (fragment.childNodes.length === 1) { | 
| bsw/jbe@1309 | 12551           while(fragment.firstChild.firstChild) { | 
| bsw/jbe@1309 | 12552             outerInlines.innerNode.appendChild(fragment.firstChild.firstChild); | 
| bsw/jbe@1309 | 12553           } | 
| bsw/jbe@1309 | 12554           fragment.firstChild.appendChild(outerInlines.outerNode); | 
| bsw/jbe@1309 | 12555         } | 
| bsw/jbe@1309 | 12556         composer.selection.splitElementAtCaret(outerInlines.parent, fragment); | 
| bsw/jbe@1309 | 12557       } else { | 
| bsw/jbe@1309 | 12558         var fc = fragment.firstChild, | 
| bsw/jbe@1309 | 12559             lc = fragment.lastChild; | 
| bsw/jbe@1309 | 12560 | 
| bsw/jbe@1309 | 12561         range.insertNode(fragment); | 
| bsw/jbe@1309 | 12562         // restore range position as it might get lost in webkit sometimes | 
| bsw/jbe@1309 | 12563         range.setStartBefore(fc); | 
| bsw/jbe@1309 | 12564         range.setEndAfter(lc); | 
| bsw/jbe@1309 | 12565       } | 
| bsw/jbe@1309 | 12566     } | 
| bsw/jbe@1309 | 12567   } | 
| bsw/jbe@1309 | 12568 | 
| bsw/jbe@1309 | 12569   // Removes all block formatting from range | 
| bsw/jbe@1309 | 12570   function clearRangeBlockFromating(range, closestBlockName, composer) { | 
| bsw/jbe@1309 | 12571     var r = range.cloneRange(), | 
| bsw/jbe@1309 | 12572         prevNode = getRangeNode(r.startContainer, r.startOffset).previousSibling, | 
| bsw/jbe@1309 | 12573         nextNode = getRangeNode(r.endContainer, r.endOffset).nextSibling, | 
| bsw/jbe@1309 | 12574         content = r.extractContents(), | 
| bsw/jbe@1309 | 12575         fragment = composer.doc.createDocumentFragment(), | 
| bsw/jbe@1309 | 12576         children, blocks, | 
| bsw/jbe@1309 | 12577         first = true; | 
| bsw/jbe@1309 | 12578 | 
| bsw/jbe@1309 | 12579     while(content.firstChild) { | 
| bsw/jbe@1309 | 12580       // Iterate over all selection content first level childNodes | 
| bsw/jbe@1309 | 12581       if (content.firstChild.nodeType === 1 && content.firstChild.matches(BLOCK_ELEMENTS)) { | 
| bsw/jbe@1309 | 12582         // If node is a block element | 
| bsw/jbe@1309 | 12583         // Split block formating and add new block to wrap caret | 
| bsw/jbe@1309 | 12584 | 
| bsw/jbe@1309 | 12585         unwrapBlocksFromContent(content.firstChild); | 
| bsw/jbe@1309 | 12586         children = wysihtml.dom.unwrap(content.firstChild); | 
| bsw/jbe@1309 | 12587 | 
| bsw/jbe@1309 | 12588         // Add line break before if needed | 
| bsw/jbe@1309 | 12589         if (children.length > 0) { | 
| bsw/jbe@1309 | 12590           if ( | 
| bsw/jbe@1309 | 12591             (fragment.lastChild && (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer))) || | 
| bsw/jbe@1309 | 12592             (!fragment.lastChild && prevNode && (prevNode.nodeType !== 1 || isLineBreaking(prevNode, composer))) | 
| bsw/jbe@1309 | 12593           ){ | 
| bsw/jbe@1309 | 12594             fragment.appendChild(composer.doc.createElement('BR')); | 
| bsw/jbe@1309 | 12595           } | 
| bsw/jbe@1309 | 12596         } | 
| bsw/jbe@1309 | 12597 | 
| bsw/jbe@1309 | 12598         for (var c = 0, cmax = children.length; c < cmax; c++) { | 
| bsw/jbe@1309 | 12599           fragment.appendChild(children[c]); | 
| bsw/jbe@1309 | 12600         } | 
| bsw/jbe@1309 | 12601 | 
| bsw/jbe@1309 | 12602         // Add line break after if needed | 
| bsw/jbe@1309 | 12603         if (children.length > 0) { | 
| bsw/jbe@1309 | 12604           if (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer)) { | 
| bsw/jbe@1309 | 12605             if (nextNode || fragment.lastChild !== content.lastChild) { | 
| bsw/jbe@1309 | 12606               fragment.appendChild(composer.doc.createElement('BR')); | 
| bsw/jbe@1309 | 12607             } | 
| bsw/jbe@1309 | 12608           } | 
| bsw/jbe@1309 | 12609         } | 
| bsw/jbe@1309 | 12610 | 
| bsw/jbe@1309 | 12611       } else { | 
| bsw/jbe@1309 | 12612         fragment.appendChild(content.firstChild); | 
| bsw/jbe@1309 | 12613       } | 
| bsw/jbe@1309 | 12614 | 
| bsw/jbe@1309 | 12615       first = false; | 
| bsw/jbe@1309 | 12616     } | 
| bsw/jbe@1309 | 12617     blocks = wysihtml.lang.array(fragment.childNodes).get(); | 
| bsw/jbe@1309 | 12618     injectFragmentToRange(fragment, r, composer); | 
| bsw/jbe@1309 | 12619     return blocks; | 
| bsw/jbe@1309 | 12620   } | 
| bsw/jbe@1309 | 12621 | 
| bsw/jbe@1309 | 12622   // When block node is inserted, look surrounding nodes and remove surplous linebreak tags (as block format breaks line itself) | 
| bsw/jbe@1309 | 12623   function removeSurroundingLineBreaks(prevNode, nextNode, composer) { | 
| bsw/jbe@1309 | 12624     var prevPrev = prevNode && wysihtml.dom.domNode(prevNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 12625     if (isBr(nextNode)) { | 
| bsw/jbe@1309 | 12626       nextNode.parentNode.removeChild(nextNode); | 
| bsw/jbe@1309 | 12627     } | 
| bsw/jbe@1309 | 12628     if (isBr(prevNode) && (!prevPrev || prevPrev.nodeType !== 1 || composer.win.getComputedStyle(prevPrev).display !== "block")) { | 
| bsw/jbe@1309 | 12629       prevNode.parentNode.removeChild(prevNode); | 
| bsw/jbe@1309 | 12630     } | 
| bsw/jbe@1309 | 12631   } | 
| bsw/jbe@1309 | 12632 | 
| bsw/jbe@1309 | 12633   function applySurroundingLineBreaks(prevNode, nextNode, composer) { | 
| bsw/jbe@1309 | 12634     var prevPrev; | 
| bsw/jbe@1309 | 12635 | 
| bsw/jbe@1309 | 12636     if (prevNode && isBookmark(prevNode)) { | 
| bsw/jbe@1309 | 12637       prevNode = prevNode.previousSibling; | 
| bsw/jbe@1309 | 12638     } | 
| bsw/jbe@1309 | 12639     if (nextNode && isBookmark(nextNode)) { | 
| bsw/jbe@1309 | 12640       nextNode = nextNode.nextSibling; | 
| bsw/jbe@1309 | 12641     } | 
| bsw/jbe@1309 | 12642 | 
| bsw/jbe@1309 | 12643     prevPrev = prevNode && prevNode.previousSibling; | 
| bsw/jbe@1309 | 12644 | 
| bsw/jbe@1309 | 12645     if (prevNode && (prevNode.nodeType !== 1 || (composer.win.getComputedStyle(prevNode).display !== "block" && !isBr(prevNode))) && prevNode.parentNode) { | 
| bsw/jbe@1309 | 12646       prevNode.parentNode.insertBefore(composer.doc.createElement('br'), prevNode.nextSibling); | 
| bsw/jbe@1309 | 12647     } | 
| bsw/jbe@1309 | 12648 | 
| bsw/jbe@1309 | 12649     if (nextNode && (nextNode.nodeType !== 1 || composer.win.getComputedStyle(nextNode).display !== "block") && nextNode.parentNode) { | 
| bsw/jbe@1309 | 12650       nextNode.parentNode.insertBefore(composer.doc.createElement('br'), nextNode); | 
| bsw/jbe@1309 | 12651     } | 
| bsw/jbe@1309 | 12652   } | 
| bsw/jbe@1309 | 12653 | 
| bsw/jbe@1309 | 12654   var isWhitespaceBefore = function (textNode, offset) { | 
| bsw/jbe@1309 | 12655     var str = textNode.data ? textNode.data.slice(0, offset) : ""; | 
| bsw/jbe@1309 | 12656     return (/^\s*$/).test(str); | 
| bsw/jbe@1309 | 12657   } | 
| bsw/jbe@1309 | 12658 | 
| bsw/jbe@1309 | 12659   var isWhitespaceAfter = function (textNode, offset) { | 
| bsw/jbe@1309 | 12660     var str = textNode.data ? textNode.data.slice(offset) : ""; | 
| bsw/jbe@1309 | 12661     return (/^\s*$/).test(str); | 
| bsw/jbe@1309 | 12662   } | 
| bsw/jbe@1309 | 12663 | 
| bsw/jbe@1309 | 12664   var trimBlankTextsAndBreaks = function(fragment) { | 
| bsw/jbe@1309 | 12665     if (fragment) { | 
| bsw/jbe@1309 | 12666       while (fragment.firstChild && fragment.firstChild.nodeType === 3 && (/^\s*$/).test(fragment.firstChild.data) && fragment.lastChild !== fragment.firstChild) { | 
| bsw/jbe@1309 | 12667         fragment.removeChild(fragment.firstChild); | 
| bsw/jbe@1309 | 12668       } | 
| bsw/jbe@1309 | 12669 | 
| bsw/jbe@1309 | 12670       while (fragment.lastChild && fragment.lastChild.nodeType === 3 && (/^\s*$/).test(fragment.lastChild.data) && fragment.lastChild !== fragment.firstChild) { | 
| bsw/jbe@1309 | 12671         fragment.removeChild(fragment.lastChild); | 
| bsw/jbe@1309 | 12672       } | 
| bsw/jbe@1309 | 12673 | 
| bsw/jbe@1309 | 12674       if (fragment.firstChild && fragment.firstChild.nodeType === 1 && fragment.firstChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) { | 
| bsw/jbe@1309 | 12675         fragment.removeChild(fragment.firstChild); | 
| bsw/jbe@1309 | 12676       } | 
| bsw/jbe@1309 | 12677 | 
| bsw/jbe@1309 | 12678       if (fragment.lastChild && fragment.lastChild.nodeType === 1 && fragment.lastChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) { | 
| bsw/jbe@1309 | 12679         fragment.removeChild(fragment.lastChild); | 
| bsw/jbe@1309 | 12680       } | 
| bsw/jbe@1309 | 12681     } | 
| bsw/jbe@1309 | 12682   } | 
| bsw/jbe@1309 | 12683 | 
| bsw/jbe@1309 | 12684   // Wrap the range with a block level element | 
| bsw/jbe@1309 | 12685   // If element is one of unnestable block elements (ex: h2 inside h1), split nodes and insert between so nesting does not occur | 
| bsw/jbe@1309 | 12686   function wrapRangeWithElement(range, options, closestBlockName, composer) { | 
| bsw/jbe@1309 | 12687     var similarOptions = options ? correctOptionsForSimilarityCheck(options) : null, | 
| bsw/jbe@1309 | 12688         r = range.cloneRange(), | 
| bsw/jbe@1309 | 12689         rangeStartContainer = r.startContainer, | 
| bsw/jbe@1309 | 12690         startNode = getRangeNode(r.startContainer, r.startOffset), | 
| bsw/jbe@1309 | 12691         endNode = getRangeNode(r.endContainer, r.endOffset), | 
| bsw/jbe@1309 | 12692         prevNode = (r.startContainer === startNode && startNode.nodeType === 3 && !isWhitespaceBefore(startNode, r.startOffset)) ? startNode :  wysihtml.dom.domNode(startNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}), | 
| bsw/jbe@1309 | 12693         nextNode = ( | 
| bsw/jbe@1309 | 12694           ( | 
| bsw/jbe@1309 | 12695             r.endContainer.nodeType === 1 && | 
| bsw/jbe@1309 | 12696             r.endContainer.childNodes[r.endOffset] === endNode && | 
| bsw/jbe@1309 | 12697             ( | 
| bsw/jbe@1309 | 12698               endNode.nodeType === 1 || | 
| bsw/jbe@1309 | 12699               !isWhitespaceAfter(endNode, r.endOffset) && | 
| bsw/jbe@1309 | 12700               !wysihtml.dom.domNode(endNode).is.rangyBookmark() | 
| bsw/jbe@1309 | 12701             ) | 
| bsw/jbe@1309 | 12702           ) || ( | 
| bsw/jbe@1309 | 12703             r.endContainer === endNode && | 
| bsw/jbe@1309 | 12704             endNode.nodeType === 3 && | 
| bsw/jbe@1309 | 12705             !isWhitespaceAfter(endNode, r.endOffset) | 
| bsw/jbe@1309 | 12706           ) | 
| bsw/jbe@1309 | 12707         ) ? endNode : wysihtml.dom.domNode(endNode).next({nodeTypes: [1,3], ignoreBlankTexts: true}), | 
| bsw/jbe@1309 | 12708         content = r.extractContents(), | 
| bsw/jbe@1309 | 12709         fragment = composer.doc.createDocumentFragment(), | 
| bsw/jbe@1309 | 12710         similarOuterBlock = similarOptions ? wysihtml.dom.getParentElement(rangeStartContainer, similarOptions, null, composer.element) : null, | 
| bsw/jbe@1309 | 12711         splitAllBlocks = !closestBlockName || !options || (options.nodeName === "BLOCKQUOTE" && closestBlockName === "BLOCKQUOTE"), | 
| bsw/jbe@1309 | 12712         firstOuterBlock = similarOuterBlock || findOuterBlock(rangeStartContainer, composer.element, splitAllBlocks), // The outermost un-nestable block element parent of selection start | 
| bsw/jbe@1309 | 12713         wrapper, blocks, children, | 
| bsw/jbe@1309 | 12714         firstc, lastC; | 
| bsw/jbe@1309 | 12715 | 
| bsw/jbe@1309 | 12716     if (wysihtml.dom.domNode(nextNode).is.rangyBookmark()) { | 
| bsw/jbe@1309 | 12717       endNode = nextNode; | 
| bsw/jbe@1309 | 12718       nextNode = endNode.nextSibling; | 
| bsw/jbe@1309 | 12719     } | 
| bsw/jbe@1309 | 12720 | 
| bsw/jbe@1309 | 12721     trimBlankTextsAndBreaks(content); | 
| bsw/jbe@1309 | 12722 | 
| bsw/jbe@1309 | 12723     if (options && options.nodeName === "BLOCKQUOTE") { | 
| bsw/jbe@1309 | 12724 | 
| bsw/jbe@1309 | 12725       // If blockquote is to be inserted no quessing just add it as outermost block on line or selection | 
| bsw/jbe@1309 | 12726       var tmpEl = applyOptionsToElement(null, options, composer); | 
| bsw/jbe@1309 | 12727       tmpEl.appendChild(content); | 
| bsw/jbe@1309 | 12728       fragment.appendChild(tmpEl); | 
| bsw/jbe@1309 | 12729       blocks = [tmpEl]; | 
| bsw/jbe@1309 | 12730 | 
| bsw/jbe@1309 | 12731     } else { | 
| bsw/jbe@1309 | 12732 | 
| bsw/jbe@1309 | 12733       if (!content.firstChild) { | 
| bsw/jbe@1309 | 12734         // IF selection is caret (can happen if line is empty) add format around tag | 
| bsw/jbe@1309 | 12735         fragment.appendChild(applyOptionsToElement(null, options, composer)); | 
| bsw/jbe@1309 | 12736       } else { | 
| bsw/jbe@1309 | 12737 | 
| bsw/jbe@1309 | 12738         while(content.firstChild) { | 
| bsw/jbe@1309 | 12739           // Iterate over all selection content first level childNodes | 
| bsw/jbe@1309 | 12740 | 
| bsw/jbe@1309 | 12741           if (content.firstChild.nodeType == 1 && content.firstChild.matches(BLOCK_ELEMENTS)) { | 
| bsw/jbe@1309 | 12742 | 
| bsw/jbe@1309 | 12743             // If node is a block element | 
| bsw/jbe@1309 | 12744             // Escape(split) block formatting at caret | 
| bsw/jbe@1309 | 12745             applyOptionsToElement(content.firstChild, options, composer); | 
| bsw/jbe@1309 | 12746             if (content.firstChild.matches(UNNESTABLE_BLOCK_ELEMENTS)) { | 
| bsw/jbe@1309 | 12747               unwrapBlocksFromContent(content.firstChild); | 
| bsw/jbe@1309 | 12748             } | 
| bsw/jbe@1309 | 12749             fragment.appendChild(content.firstChild); | 
| bsw/jbe@1309 | 12750 | 
| bsw/jbe@1309 | 12751           } else { | 
| bsw/jbe@1309 | 12752 | 
| bsw/jbe@1309 | 12753             // Wrap subsequent non-block nodes inside new block element | 
| bsw/jbe@1309 | 12754             wrapper = applyOptionsToElement(null, getOptionsWithNodename(options, closestBlockName, composer), composer); | 
| bsw/jbe@1309 | 12755             while(content.firstChild && (content.firstChild.nodeType !== 1 || !content.firstChild.matches(BLOCK_ELEMENTS))) { | 
| bsw/jbe@1309 | 12756               if (content.firstChild.nodeType == 1 && wrapper.matches(UNNESTABLE_BLOCK_ELEMENTS)) { | 
| bsw/jbe@1309 | 12757                 unwrapBlocksFromContent(content.firstChild); | 
| bsw/jbe@1309 | 12758               } | 
| bsw/jbe@1309 | 12759               wrapper.appendChild(content.firstChild); | 
| bsw/jbe@1309 | 12760             } | 
| bsw/jbe@1309 | 12761             fragment.appendChild(wrapper); | 
| bsw/jbe@1309 | 12762           } | 
| bsw/jbe@1309 | 12763         } | 
| bsw/jbe@1309 | 12764       } | 
| bsw/jbe@1309 | 12765 | 
| bsw/jbe@1309 | 12766       blocks = wysihtml.lang.array(fragment.childNodes).get(); | 
| bsw/jbe@1309 | 12767     } | 
| bsw/jbe@1309 | 12768     injectFragmentToRange(fragment, r, composer, firstOuterBlock); | 
| bsw/jbe@1309 | 12769     removeSurroundingLineBreaks(prevNode, nextNode, composer); | 
| bsw/jbe@1309 | 12770 | 
| bsw/jbe@1309 | 12771     // Fix webkit madness by inserting linebreak rangy after cursor marker to blank last block | 
| bsw/jbe@1309 | 12772     // (if it contains rangy bookmark, so selection can be restored later correctly) | 
| bsw/jbe@1309 | 12773     if (blocks.length > 0 && | 
| bsw/jbe@1309 | 12774       ( | 
| bsw/jbe@1309 | 12775         typeof blocks[blocks.length - 1].lastChild === "undefined" || wysihtml.dom.domNode(blocks[blocks.length - 1].lastChild).is.rangyBookmark() | 
| bsw/jbe@1309 | 12776       ) | 
| bsw/jbe@1309 | 12777     ) { | 
| bsw/jbe@1309 | 12778       blocks[blocks.length - 1].appendChild(composer.doc.createElement('br')); | 
| bsw/jbe@1309 | 12779     } | 
| bsw/jbe@1309 | 12780     return blocks; | 
| bsw/jbe@1309 | 12781   } | 
| bsw/jbe@1309 | 12782 | 
| bsw/jbe@1309 | 12783   // Find closest block level element | 
| bsw/jbe@1309 | 12784   function getParentBlockNodeName(element, composer) { | 
| bsw/jbe@1309 | 12785     var parentNode = wysihtml.dom.getParentElement(element, { | 
| bsw/jbe@1309 | 12786           query: BLOCK_ELEMENTS | 
| bsw/jbe@1309 | 12787         }, null, composer.element); | 
| bsw/jbe@1309 | 12788 | 
| bsw/jbe@1309 | 12789     return (parentNode) ? parentNode.nodeName : null; | 
| bsw/jbe@1309 | 12790   } | 
| bsw/jbe@1309 | 12791 | 
| bsw/jbe@1309 | 12792   // Expands caret to cover the closest block that: | 
| bsw/jbe@1309 | 12793   //   * cannot contain other block level elements (h1-6,p, etc) | 
| bsw/jbe@1309 | 12794   //   * Has the same nodeName that is to be inserted | 
| bsw/jbe@1309 | 12795   //   * has insertingNodeName | 
| bsw/jbe@1309 | 12796   //   * is DIV if insertingNodeName is not present | 
| bsw/jbe@1309 | 12797   // | 
| bsw/jbe@1309 | 12798   // If nothing found selects the current line | 
| bsw/jbe@1309 | 12799   function expandCaretToBlock(composer, insertingNodeName) { | 
| bsw/jbe@1309 | 12800     var parent = wysihtml.dom.getParentElement(composer.selection.getOwnRanges()[0].startContainer, { | 
| bsw/jbe@1309 | 12801           query: UNNESTABLE_BLOCK_ELEMENTS + ', ' + (insertingNodeName ? insertingNodeName.toLowerCase() : 'div'), | 
| bsw/jbe@1309 | 12802         }, null, composer.element), | 
| bsw/jbe@1309 | 12803         range; | 
| bsw/jbe@1309 | 12804 | 
| bsw/jbe@1309 | 12805     if (parent) { | 
| bsw/jbe@1309 | 12806       range = composer.selection.createRange(); | 
| bsw/jbe@1309 | 12807       range.selectNode(parent); | 
| bsw/jbe@1309 | 12808       composer.selection.setSelection(range); | 
| bsw/jbe@1309 | 12809     } else if (!composer.isEmpty()) { | 
| bsw/jbe@1309 | 12810       composer.selection.selectLine(); | 
| bsw/jbe@1309 | 12811     } | 
| bsw/jbe@1309 | 12812   } | 
| bsw/jbe@1309 | 12813 | 
| bsw/jbe@1309 | 12814   // Set selection to begin inside first created block element (beginning of it) and end inside (and after content) of last block element | 
| bsw/jbe@1309 | 12815   // TODO: Checking nodetype might be unnescescary as nodes inserted by formatBlock are nodetype 1 anyway | 
| bsw/jbe@1309 | 12816   function selectElements(newBlockElements, composer) { | 
| bsw/jbe@1309 | 12817     var range = composer.selection.createRange(), | 
| bsw/jbe@1309 | 12818         lastEl = newBlockElements[newBlockElements.length - 1], | 
| bsw/jbe@1309 | 12819         lastOffset = (lastEl.nodeType === 1 && lastEl.childNodes) ? lastEl.childNodes.length | 0 :  lastEl.length || 0; | 
| bsw/jbe@1309 | 12820 | 
| bsw/jbe@1309 | 12821     range.setStart(newBlockElements[0], 0); | 
| bsw/jbe@1309 | 12822     range.setEnd(lastEl, lastOffset); | 
| bsw/jbe@1309 | 12823     range.select(); | 
| bsw/jbe@1309 | 12824   } | 
| bsw/jbe@1309 | 12825 | 
| bsw/jbe@1309 | 12826   // Get all ranges from selection (takes out uneditables and out of editor parts) and apply format to each | 
| bsw/jbe@1309 | 12827   // Return created/modified block level elements | 
| bsw/jbe@1309 | 12828   // Method can be either "apply" or "remove" | 
| bsw/jbe@1309 | 12829   function formatSelection(method, composer, options) { | 
| bsw/jbe@1309 | 12830     var ranges = composer.selection.getOwnRanges(), | 
| bsw/jbe@1309 | 12831         newBlockElements = [], | 
| bsw/jbe@1309 | 12832         closestBlockName; | 
| bsw/jbe@1309 | 12833 | 
| bsw/jbe@1309 | 12834     // Some places do not allow block level elements inbetween (inside ul and outside li, inside table and outside of td/th) | 
| bsw/jbe@1309 | 12835     ranges = fixNotPermittedInsertionPoints(ranges); | 
| bsw/jbe@1309 | 12836 | 
| bsw/jbe@1309 | 12837     for (var i = ranges.length; i--;) { | 
| bsw/jbe@1309 | 12838       fixRangeCoverage(ranges[i], composer); | 
| bsw/jbe@1309 | 12839       closestBlockName = getParentBlockNodeName(ranges[i].startContainer, composer); | 
| bsw/jbe@1309 | 12840       if (method === "remove") { | 
| bsw/jbe@1309 | 12841         newBlockElements = newBlockElements.concat(clearRangeBlockFromating(ranges[i], closestBlockName, composer)); | 
| bsw/jbe@1309 | 12842       } else { | 
| bsw/jbe@1309 | 12843         newBlockElements = newBlockElements.concat(wrapRangeWithElement(ranges[i], options, closestBlockName, composer)); | 
| bsw/jbe@1309 | 12844       } | 
| bsw/jbe@1309 | 12845     } | 
| bsw/jbe@1309 | 12846     return newBlockElements; | 
| bsw/jbe@1309 | 12847   } | 
| bsw/jbe@1309 | 12848 | 
| bsw/jbe@1309 | 12849   // If properties is passed as a string, look for tag with that tagName/query | 
| bsw/jbe@1309 | 12850   function parseOptions(options) { | 
| bsw/jbe@1309 | 12851     if (typeof options === "string") { | 
| bsw/jbe@1309 | 12852       options = { | 
| bsw/jbe@1309 | 12853         nodeName: options.toUpperCase() | 
| bsw/jbe@1309 | 12854       }; | 
| bsw/jbe@1309 | 12855     } | 
| bsw/jbe@1309 | 12856     return options; | 
| bsw/jbe@1309 | 12857   } | 
| bsw/jbe@1309 | 12858 | 
| bsw/jbe@1309 | 12859   function caretIsOnEmptyLine(composer) { | 
| bsw/jbe@1309 | 12860     var caretInfo; | 
| bsw/jbe@1309 | 12861     if (composer.selection.isCollapsed()) { | 
| bsw/jbe@1309 | 12862       caretInfo = composer.selection.getNodesNearCaret(); | 
| bsw/jbe@1309 | 12863       if (caretInfo && caretInfo.caretNode) { | 
| bsw/jbe@1309 | 12864         if ( | 
| bsw/jbe@1309 | 12865           // caret is allready breaknode | 
| bsw/jbe@1309 | 12866           wysihtml.dom.domNode(caretInfo.caretNode).is.lineBreak() || | 
| bsw/jbe@1309 | 12867           // caret is textnode | 
| bsw/jbe@1309 | 12868           (caretInfo.caretNode.nodeType === 3 && caretInfo.textOffset === 0 && (!caretInfo.prevNode || wysihtml.dom.domNode(caretInfo.prevNode).is.lineBreak())) || | 
| bsw/jbe@1309 | 12869           // Caret is temprorary rangy selection marker | 
| bsw/jbe@1309 | 12870           (caretInfo.caretNode.nodeType === 1 && caretInfo.caretNode.classList.contains('rangySelectionBoundary') && | 
| bsw/jbe@1309 | 12871             (!caretInfo.prevNode || wysihtml.dom.domNode(caretInfo.prevNode).is.lineBreak() || wysihtml.dom.domNode(caretInfo.prevNode).is.block()) && | 
| bsw/jbe@1309 | 12872             (!caretInfo.nextNode || wysihtml.dom.domNode(caretInfo.nextNode).is.lineBreak() || wysihtml.dom.domNode(caretInfo.nextNode).is.block()) | 
| bsw/jbe@1309 | 12873           ) | 
| bsw/jbe@1309 | 12874         ) { | 
| bsw/jbe@1309 | 12875           return true; | 
| bsw/jbe@1309 | 12876         } | 
| bsw/jbe@1309 | 12877       } | 
| bsw/jbe@1309 | 12878     } | 
| bsw/jbe@1309 | 12879     return false; | 
| bsw/jbe@1309 | 12880   } | 
| bsw/jbe@1309 | 12881 | 
| bsw/jbe@1309 | 12882   wysihtml.commands.formatBlock = { | 
| bsw/jbe@1309 | 12883     exec: function(composer, command, options) { | 
| bsw/jbe@1309 | 12884       options = parseOptions(options); | 
| bsw/jbe@1309 | 12885       var newBlockElements = [], | 
| bsw/jbe@1309 | 12886           ranges, range, bookmark, state, closestBlockName; | 
| bsw/jbe@1309 | 12887 | 
| bsw/jbe@1309 | 12888       // Find if current format state is active if options.toggle is set as true | 
| bsw/jbe@1309 | 12889       // In toggle case active state elemets are formatted instead of working directly on selection | 
| bsw/jbe@1309 | 12890       if (options && options.toggle) { | 
| bsw/jbe@1309 | 12891         state = this.state(composer, command, options); | 
| bsw/jbe@1309 | 12892       } | 
| bsw/jbe@1309 | 12893       if (state) { | 
| bsw/jbe@1309 | 12894         // Remove format from state nodes if toggle set and state on and selection is collapsed | 
| bsw/jbe@1309 | 12895         bookmark = rangy.saveSelection(composer.win); | 
| bsw/jbe@1309 | 12896         for (var j = 0, jmax = state.length; j < jmax; j++) { | 
| bsw/jbe@1309 | 12897           removeOptionsFromElement(state[j], options, composer); | 
| bsw/jbe@1309 | 12898         } | 
| bsw/jbe@1309 | 12899 | 
| bsw/jbe@1309 | 12900       } else { | 
| bsw/jbe@1309 | 12901         // If selection is caret expand it to cover nearest suitable block element or row if none found | 
| bsw/jbe@1309 | 12902         if (composer.selection.isCollapsed()) { | 
| bsw/jbe@1309 | 12903           bookmark = rangy.saveSelection(composer.win); | 
| bsw/jbe@1309 | 12904           if (caretIsOnEmptyLine(composer)) { | 
| bsw/jbe@1309 | 12905             composer.selection.selectLine(); | 
| bsw/jbe@1309 | 12906           } else { | 
| bsw/jbe@1309 | 12907             expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined); | 
| bsw/jbe@1309 | 12908           } | 
| bsw/jbe@1309 | 12909         } | 
| bsw/jbe@1309 | 12910         if (options) { | 
| bsw/jbe@1309 | 12911           newBlockElements = formatSelection("apply", composer, options); | 
| bsw/jbe@1309 | 12912         } else { | 
| bsw/jbe@1309 | 12913           // Options == null means block formatting should be removed from selection | 
| bsw/jbe@1309 | 12914           newBlockElements = formatSelection("remove", composer); | 
| bsw/jbe@1309 | 12915         } | 
| bsw/jbe@1309 | 12916 | 
| bsw/jbe@1309 | 12917       } | 
| bsw/jbe@1309 | 12918 | 
| bsw/jbe@1309 | 12919       // Remove empty block elements that may be left behind | 
| bsw/jbe@1309 | 12920       // Also remove them from new blocks list | 
| bsw/jbe@1309 | 12921       newBlockElements = cleanup(composer, newBlockElements); | 
| bsw/jbe@1309 | 12922 | 
| bsw/jbe@1309 | 12923       // Restore selection | 
| bsw/jbe@1309 | 12924       if (bookmark) { | 
| bsw/jbe@1309 | 12925         rangy.restoreSelection(bookmark); | 
| bsw/jbe@1309 | 12926       } else { | 
| bsw/jbe@1309 | 12927         selectElements(newBlockElements, composer); | 
| bsw/jbe@1309 | 12928       } | 
| bsw/jbe@1309 | 12929     }, | 
| bsw/jbe@1309 | 12930 | 
| bsw/jbe@1309 | 12931     // Removes all block formatting from selection | 
| bsw/jbe@1309 | 12932     remove: function(composer, command, options) { | 
| bsw/jbe@1309 | 12933       options = parseOptions(options); | 
| bsw/jbe@1309 | 12934       var newBlockElements, bookmark; | 
| bsw/jbe@1309 | 12935 | 
| bsw/jbe@1309 | 12936       // If selection is caret expand it to cover nearest suitable block element or row if none found | 
| bsw/jbe@1309 | 12937       if (composer.selection.isCollapsed()) { | 
| bsw/jbe@1309 | 12938         bookmark = rangy.saveSelection(composer.win); | 
| bsw/jbe@1309 | 12939         expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined); | 
| bsw/jbe@1309 | 12940       } | 
| bsw/jbe@1309 | 12941 | 
| bsw/jbe@1309 | 12942       newBlockElements = formatSelection("remove", composer); | 
| bsw/jbe@1309 | 12943       newBlockElements = cleanup(composer, newBlockElements); | 
| bsw/jbe@1309 | 12944 | 
| bsw/jbe@1309 | 12945       // Restore selection | 
| bsw/jbe@1309 | 12946       if (bookmark) { | 
| bsw/jbe@1309 | 12947         rangy.restoreSelection(bookmark); | 
| bsw/jbe@1309 | 12948       } else { | 
| bsw/jbe@1309 | 12949         selectElements(newBlockElements, composer); | 
| bsw/jbe@1309 | 12950       } | 
| bsw/jbe@1309 | 12951     }, | 
| bsw/jbe@1309 | 12952 | 
| bsw/jbe@1309 | 12953     // If options as null is passed returns status describing all block level elements | 
| bsw/jbe@1309 | 12954     state: function(composer, command, options) { | 
| bsw/jbe@1309 | 12955       options = parseOptions(options); | 
| bsw/jbe@1309 | 12956 | 
| bsw/jbe@1309 | 12957       var nodes = composer.selection.filterElements((function (element) { // Finds matching elements inside selection | 
| bsw/jbe@1309 | 12958             return wysihtml.dom.domNode(element).test(options || { query: BLOCK_ELEMENTS }); | 
| bsw/jbe@1309 | 12959           }).bind(this)), | 
| bsw/jbe@1309 | 12960           parentNodes = composer.selection.getSelectedOwnNodes(), | 
| bsw/jbe@1309 | 12961           parent; | 
| bsw/jbe@1309 | 12962 | 
| bsw/jbe@1309 | 12963       // Finds matching elements that are parents of selection and adds to nodes list | 
| bsw/jbe@1309 | 12964       for (var i = 0, maxi = parentNodes.length; i < maxi; i++) { | 
| bsw/jbe@1309 | 12965         parent = dom.getParentElement(parentNodes[i], options || { query: BLOCK_ELEMENTS }, null, composer.element); | 
| bsw/jbe@1309 | 12966         if (parent && nodes.indexOf(parent) === -1) { | 
| bsw/jbe@1309 | 12967           nodes.push(parent); | 
| bsw/jbe@1309 | 12968         } | 
| bsw/jbe@1309 | 12969       } | 
| bsw/jbe@1309 | 12970 | 
| bsw/jbe@1309 | 12971       return (nodes.length === 0) ? false : nodes; | 
| bsw/jbe@1309 | 12972     } | 
| bsw/jbe@1309 | 12973 | 
| bsw/jbe@1309 | 12974   }; | 
| bsw/jbe@1309 | 12975 })(wysihtml); | 
| bsw/jbe@1309 | 12976 | 
| bsw/jbe@1309 | 12977 /** | 
| bsw/jbe@1309 | 12978  * Unifies all inline tags additions and removals | 
| bsw/jbe@1309 | 12979  * See https://github.com/Voog/wysihtml/pull/169 for specification of action | 
| bsw/jbe@1309 | 12980  */ | 
| bsw/jbe@1309 | 12981 | 
| bsw/jbe@1309 | 12982 (function(wysihtml) { | 
| bsw/jbe@1309 | 12983 | 
| bsw/jbe@1309 | 12984   var defaultTag = "SPAN", | 
| bsw/jbe@1309 | 12985       INLINE_ELEMENTS = "b, big, i, small, tt, abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var, a, bdo, br, q, span, sub, sup, button, label, textarea, input, select, u", | 
| bsw/jbe@1309 | 12986       queryAliasMap = { | 
| bsw/jbe@1309 | 12987         "b": "b, strong", | 
| bsw/jbe@1309 | 12988         "strong": "b, strong", | 
| bsw/jbe@1309 | 12989         "em": "em, i", | 
| bsw/jbe@1309 | 12990         "i": "em, i" | 
| bsw/jbe@1309 | 12991       }; | 
| bsw/jbe@1309 | 12992 | 
| bsw/jbe@1309 | 12993   function hasNoClass(element) { | 
| bsw/jbe@1309 | 12994     return (/^\s*$/).test(element.className); | 
| bsw/jbe@1309 | 12995   } | 
| bsw/jbe@1309 | 12996 | 
| bsw/jbe@1309 | 12997   function hasNoStyle(element) { | 
| bsw/jbe@1309 | 12998     return !element.getAttribute('style') || (/^\s*$/).test(element.getAttribute('style')); | 
| bsw/jbe@1309 | 12999   } | 
| bsw/jbe@1309 | 13000 | 
| bsw/jbe@1309 | 13001   // Associative arrays in javascript are really objects and do not have length defined | 
| bsw/jbe@1309 | 13002   // Thus have to check emptyness in a different way | 
| bsw/jbe@1309 | 13003   function hasNoAttributes(element) { | 
| bsw/jbe@1309 | 13004     var attr = wysihtml.dom.getAttributes(element); | 
| bsw/jbe@1309 | 13005     return wysihtml.lang.object(attr).isEmpty(); | 
| bsw/jbe@1309 | 13006   } | 
| bsw/jbe@1309 | 13007 | 
| bsw/jbe@1309 | 13008   // compares two nodes if they are semantically the same | 
| bsw/jbe@1309 | 13009   // Used in cleanup to find consequent semantically similar elements for merge | 
| bsw/jbe@1309 | 13010   function isSameNode(element1, element2) { | 
| bsw/jbe@1309 | 13011     var classes1, classes2, | 
| bsw/jbe@1309 | 13012         attr1, attr2; | 
| bsw/jbe@1309 | 13013 | 
| bsw/jbe@1309 | 13014     if (element1.nodeType !== 1 || element2.nodeType !== 1) { | 
| bsw/jbe@1309 | 13015       return false; | 
| bsw/jbe@1309 | 13016     } | 
| bsw/jbe@1309 | 13017 | 
| bsw/jbe@1309 | 13018     if (element1.nodeName !== element2.nodeName) { | 
| bsw/jbe@1309 | 13019       return false; | 
| bsw/jbe@1309 | 13020     } | 
| bsw/jbe@1309 | 13021 | 
| bsw/jbe@1309 | 13022     classes1 = element1.className.trim().replace(/\s+/g, ' ').split(' '); | 
| bsw/jbe@1309 | 13023     classes2 = element2.className.trim().replace(/\s+/g, ' ').split(' '); | 
| bsw/jbe@1309 | 13024     if (wysihtml.lang.array(classes1).without(classes2).length > 0) { | 
| bsw/jbe@1309 | 13025       return false; | 
| bsw/jbe@1309 | 13026     } | 
| bsw/jbe@1309 | 13027 | 
| bsw/jbe@1309 | 13028     attr1 = wysihtml.dom.getAttributes(element1); | 
| bsw/jbe@1309 | 13029     attr2 = wysihtml.dom.getAttributes(element2); | 
| bsw/jbe@1309 | 13030 | 
| bsw/jbe@1309 | 13031     if (attr1.length !== attr2.length || !wysihtml.lang.object(wysihtml.lang.object(attr1).difference(attr2)).isEmpty()) { | 
| bsw/jbe@1309 | 13032       return false; | 
| bsw/jbe@1309 | 13033     } | 
| bsw/jbe@1309 | 13034 | 
| bsw/jbe@1309 | 13035     return true; | 
| bsw/jbe@1309 | 13036   } | 
| bsw/jbe@1309 | 13037 | 
| bsw/jbe@1309 | 13038   function createWrapNode(textNode, options) { | 
| bsw/jbe@1309 | 13039     var nodeName = options && options.nodeName || defaultTag, | 
| bsw/jbe@1309 | 13040         element = textNode.ownerDocument.createElement(nodeName); | 
| bsw/jbe@1309 | 13041 | 
| bsw/jbe@1309 | 13042     // Remove similar classes before applying className | 
| bsw/jbe@1309 | 13043     if (options.classRegExp) { | 
| bsw/jbe@1309 | 13044       element.className = element.className.replace(options.classRegExp, ""); | 
| bsw/jbe@1309 | 13045     } | 
| bsw/jbe@1309 | 13046 | 
| bsw/jbe@1309 | 13047     if (options.className) { | 
| bsw/jbe@1309 | 13048       element.classList.add(options.className); | 
| bsw/jbe@1309 | 13049     } | 
| bsw/jbe@1309 | 13050 | 
| bsw/jbe@1309 | 13051     if (options.styleProperty && typeof options.styleValue !== "undefined") { | 
| bsw/jbe@1309 | 13052       element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue; | 
| bsw/jbe@1309 | 13053     } | 
| bsw/jbe@1309 | 13054 | 
| bsw/jbe@1309 | 13055     if (options.attribute) { | 
| bsw/jbe@1309 | 13056       if (typeof options.attribute === "object") { | 
| bsw/jbe@1309 | 13057         for (var a in options.attribute) { | 
| bsw/jbe@1309 | 13058           if (options.attribute.hasOwnProperty(a)) { | 
| bsw/jbe@1309 | 13059             element.setAttribute(a, options.attribute[a]); | 
| bsw/jbe@1309 | 13060           } | 
| bsw/jbe@1309 | 13061         } | 
| bsw/jbe@1309 | 13062       } else if (typeof options.attributeValue !== "undefined") { | 
| bsw/jbe@1309 | 13063         element.setAttribute(options.attribute, options.attributeValue); | 
| bsw/jbe@1309 | 13064       } | 
| bsw/jbe@1309 | 13065     } | 
| bsw/jbe@1309 | 13066 | 
| bsw/jbe@1309 | 13067     return element; | 
| bsw/jbe@1309 | 13068   } | 
| bsw/jbe@1309 | 13069 | 
| bsw/jbe@1309 | 13070   // Tests if attr2 list contains all attributes present in attr1 | 
| bsw/jbe@1309 | 13071   // Note: attr 1 can have more attributes than attr2 | 
| bsw/jbe@1309 | 13072   function containsSameAttributes(attr1, attr2) { | 
| bsw/jbe@1309 | 13073     for (var a in attr1) { | 
| bsw/jbe@1309 | 13074       if (attr1.hasOwnProperty(a)) { | 
| bsw/jbe@1309 | 13075         if (typeof attr2[a] === undefined || attr2[a] !== attr1[a]) { | 
| bsw/jbe@1309 | 13076           return false; | 
| bsw/jbe@1309 | 13077         } | 
| bsw/jbe@1309 | 13078       } | 
| bsw/jbe@1309 | 13079     } | 
| bsw/jbe@1309 | 13080     return true; | 
| bsw/jbe@1309 | 13081   } | 
| bsw/jbe@1309 | 13082 | 
| bsw/jbe@1309 | 13083   // If attrbutes and values are the same > remove | 
| bsw/jbe@1309 | 13084   // if attributes or values | 
| bsw/jbe@1309 | 13085   function updateElementAttributes(element, newAttributes, toggle) { | 
| bsw/jbe@1309 | 13086     var attr = wysihtml.dom.getAttributes(element), | 
| bsw/jbe@1309 | 13087         fullContain = containsSameAttributes(newAttributes, attr), | 
| bsw/jbe@1309 | 13088         attrDifference = wysihtml.lang.object(attr).difference(newAttributes), | 
| bsw/jbe@1309 | 13089         a, b; | 
| bsw/jbe@1309 | 13090 | 
| bsw/jbe@1309 | 13091     if (fullContain && toggle !== false) { | 
| bsw/jbe@1309 | 13092       for (a in newAttributes) { | 
| bsw/jbe@1309 | 13093         if (newAttributes.hasOwnProperty(a)) { | 
| bsw/jbe@1309 | 13094           element.removeAttribute(a); | 
| bsw/jbe@1309 | 13095         } | 
| bsw/jbe@1309 | 13096       } | 
| bsw/jbe@1309 | 13097     } else { | 
| bsw/jbe@1309 | 13098 | 
| bsw/jbe@1309 | 13099       /*if (!wysihtml.lang.object(attrDifference).isEmpty()) { | 
| bsw/jbe@1309 | 13100         for (b in attrDifference) { | 
| bsw/jbe@1309 | 13101           if (attrDifference.hasOwnProperty(b)) { | 
| bsw/jbe@1309 | 13102             element.removeAttribute(b); | 
| bsw/jbe@1309 | 13103           } | 
| bsw/jbe@1309 | 13104         } | 
| bsw/jbe@1309 | 13105       }*/ | 
| bsw/jbe@1309 | 13106 | 
| bsw/jbe@1309 | 13107       for (a in newAttributes) { | 
| bsw/jbe@1309 | 13108         if (newAttributes.hasOwnProperty(a)) { | 
| bsw/jbe@1309 | 13109           element.setAttribute(a, newAttributes[a]); | 
| bsw/jbe@1309 | 13110         } | 
| bsw/jbe@1309 | 13111       } | 
| bsw/jbe@1309 | 13112     } | 
| bsw/jbe@1309 | 13113   } | 
| bsw/jbe@1309 | 13114 | 
| bsw/jbe@1309 | 13115   function updateFormatOfElement(element, options) { | 
| bsw/jbe@1309 | 13116     var attr, newNode, a, newAttributes, nodeNameQuery, nodeQueryMatch; | 
| bsw/jbe@1309 | 13117 | 
| bsw/jbe@1309 | 13118     if (options.className) { | 
| bsw/jbe@1309 | 13119       if (options.toggle !== false && element.classList.contains(options.className)) { | 
| bsw/jbe@1309 | 13120         element.classList.remove(options.className); | 
| bsw/jbe@1309 | 13121       } else { | 
| bsw/jbe@1309 | 13122         if (options.classRegExp) { | 
| bsw/jbe@1309 | 13123           element.className = element.className.replace(options.classRegExp, ''); | 
| bsw/jbe@1309 | 13124         } | 
| bsw/jbe@1309 | 13125         element.classList.add(options.className); | 
| bsw/jbe@1309 | 13126       } | 
| bsw/jbe@1309 | 13127       if (hasNoClass(element)) { | 
| bsw/jbe@1309 | 13128         element.removeAttribute('class'); | 
| bsw/jbe@1309 | 13129       } | 
| bsw/jbe@1309 | 13130     } | 
| bsw/jbe@1309 | 13131 | 
| bsw/jbe@1309 | 13132     // change/remove style | 
| bsw/jbe@1309 | 13133     if (options.styleProperty) { | 
| bsw/jbe@1309 | 13134       if (options.toggle !== false && element.style[wysihtml.browser.fixStyleKey(options.styleProperty)].trim().replace(/, /g, ",") === options.styleValue) { | 
| bsw/jbe@1309 | 13135         element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = ''; | 
| bsw/jbe@1309 | 13136       } else { | 
| bsw/jbe@1309 | 13137         element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue; | 
| bsw/jbe@1309 | 13138       } | 
| bsw/jbe@1309 | 13139     } | 
| bsw/jbe@1309 | 13140     if (hasNoStyle(element)) { | 
| bsw/jbe@1309 | 13141       element.removeAttribute('style'); | 
| bsw/jbe@1309 | 13142     } | 
| bsw/jbe@1309 | 13143 | 
| bsw/jbe@1309 | 13144     if (options.attribute) { | 
| bsw/jbe@1309 | 13145       if (typeof options.attribute === "object") { | 
| bsw/jbe@1309 | 13146         newAttributes =  options.attribute; | 
| bsw/jbe@1309 | 13147       } else { | 
| bsw/jbe@1309 | 13148         newAttributes = {}; | 
| bsw/jbe@1309 | 13149         newAttributes[options.attribute] = options.attributeValue || ''; | 
| bsw/jbe@1309 | 13150       } | 
| bsw/jbe@1309 | 13151       updateElementAttributes(element, newAttributes, options.toggle); | 
| bsw/jbe@1309 | 13152     } | 
| bsw/jbe@1309 | 13153 | 
| bsw/jbe@1309 | 13154 | 
| bsw/jbe@1309 | 13155     // Handle similar semantically same elements (queryAliasMap) | 
| bsw/jbe@1309 | 13156     nodeNameQuery = options.nodeName ? queryAliasMap[options.nodeName.toLowerCase()] || options.nodeName.toLowerCase() : null; | 
| bsw/jbe@1309 | 13157     nodeQueryMatch = nodeNameQuery ? wysihtml.dom.domNode(element).test({ query: nodeNameQuery }) : false; | 
| bsw/jbe@1309 | 13158 | 
| bsw/jbe@1309 | 13159     // Unwrap element if no attributes present and node name given | 
| bsw/jbe@1309 | 13160     // or no attributes and if no nodename set but node is the default | 
| bsw/jbe@1309 | 13161     if (!options.nodeName || options.nodeName === defaultTag || nodeQueryMatch) { | 
| bsw/jbe@1309 | 13162       if ( | 
| bsw/jbe@1309 | 13163         ((options.toggle !== false && nodeQueryMatch) || (!options.nodeName && element.nodeName === defaultTag)) && | 
| bsw/jbe@1309 | 13164         hasNoClass(element) && hasNoStyle(element) && hasNoAttributes(element) | 
| bsw/jbe@1309 | 13165       ) { | 
| bsw/jbe@1309 | 13166         wysihtml.dom.unwrap(element); | 
| bsw/jbe@1309 | 13167       } | 
| bsw/jbe@1309 | 13168 | 
| bsw/jbe@1309 | 13169     } | 
| bsw/jbe@1309 | 13170   } | 
| bsw/jbe@1309 | 13171 | 
| bsw/jbe@1309 | 13172   // Fetch all textnodes in selection | 
| bsw/jbe@1309 | 13173   // Empty textnodes are ignored except the one containing text caret | 
| bsw/jbe@1309 | 13174   function getSelectedTextNodes(selection, splitBounds) { | 
| bsw/jbe@1309 | 13175     var textNodes = []; | 
| bsw/jbe@1309 | 13176 | 
| bsw/jbe@1309 | 13177     if (!selection.isCollapsed()) { | 
| bsw/jbe@1309 | 13178       textNodes = textNodes.concat(selection.getOwnNodes([3], function(node) { | 
| bsw/jbe@1309 | 13179         // Exclude empty nodes except caret node | 
| bsw/jbe@1309 | 13180         return (!wysihtml.dom.domNode(node).is.emptyTextNode()); | 
| bsw/jbe@1309 | 13181       }, splitBounds)); | 
| bsw/jbe@1309 | 13182     } | 
| bsw/jbe@1309 | 13183 | 
| bsw/jbe@1309 | 13184     return textNodes; | 
| bsw/jbe@1309 | 13185   } | 
| bsw/jbe@1309 | 13186 | 
| bsw/jbe@1309 | 13187   function findSimilarTextNodeWrapper(textNode, options, container, exact) { | 
| bsw/jbe@1309 | 13188     var node = textNode, | 
| bsw/jbe@1309 | 13189         similarOptions = exact ? options : correctOptionsForSimilarityCheck(options); | 
| bsw/jbe@1309 | 13190 | 
| bsw/jbe@1309 | 13191     do { | 
| bsw/jbe@1309 | 13192       if (node.nodeType === 1 && isSimilarNode(node, similarOptions)) { | 
| bsw/jbe@1309 | 13193         return node; | 
| bsw/jbe@1309 | 13194       } | 
| bsw/jbe@1309 | 13195       node = node.parentNode; | 
| bsw/jbe@1309 | 13196     } while (node && node !== container); | 
| bsw/jbe@1309 | 13197 | 
| bsw/jbe@1309 | 13198     return null; | 
| bsw/jbe@1309 | 13199   } | 
| bsw/jbe@1309 | 13200 | 
| bsw/jbe@1309 | 13201   function correctOptionsForSimilarityCheck(options) { | 
| bsw/jbe@1309 | 13202     return { | 
| bsw/jbe@1309 | 13203       nodeName: options.nodeName || null, | 
| bsw/jbe@1309 | 13204       className: (!options.classRegExp) ? options.className || null : null, | 
| bsw/jbe@1309 | 13205       classRegExp: options.classRegExp || null, | 
| bsw/jbe@1309 | 13206       styleProperty: options.styleProperty || null | 
| bsw/jbe@1309 | 13207     }; | 
| bsw/jbe@1309 | 13208   } | 
| bsw/jbe@1309 | 13209 | 
| bsw/jbe@1309 | 13210   // Finds inline node with similar nodeName/style/className | 
| bsw/jbe@1309 | 13211   // If nodeName is specified inline node with the same (or alias) nodeName is expected to prove similar regardless of attributes | 
| bsw/jbe@1309 | 13212   function isSimilarNode(node, options) { | 
| bsw/jbe@1309 | 13213     var o; | 
| bsw/jbe@1309 | 13214     if (options.nodeName) { | 
| bsw/jbe@1309 | 13215       var query = queryAliasMap[options.nodeName.toLowerCase()] || options.nodeName.toLowerCase(); | 
| bsw/jbe@1309 | 13216       return wysihtml.dom.domNode(node).test({ query: query }); | 
| bsw/jbe@1309 | 13217     } else { | 
| bsw/jbe@1309 | 13218       o = wysihtml.lang.object(options).clone(); | 
| bsw/jbe@1309 | 13219       o.query = INLINE_ELEMENTS; // make sure only inline elements with styles and classes are counted | 
| bsw/jbe@1309 | 13220       return wysihtml.dom.domNode(node).test(o); | 
| bsw/jbe@1309 | 13221     } | 
| bsw/jbe@1309 | 13222   } | 
| bsw/jbe@1309 | 13223 | 
| bsw/jbe@1309 | 13224   function selectRange(composer, range) { | 
| bsw/jbe@1309 | 13225     var d = document.documentElement || document.body, | 
| bsw/jbe@1309 | 13226         oldScrollTop  = d.scrollTop, | 
| bsw/jbe@1309 | 13227         oldScrollLeft = d.scrollLeft, | 
| bsw/jbe@1309 | 13228         selection = rangy.getSelection(composer.win); | 
| bsw/jbe@1309 | 13229 | 
| bsw/jbe@1309 | 13230     rangy.getSelection(composer.win).removeAllRanges(); | 
| bsw/jbe@1309 | 13231 | 
| bsw/jbe@1309 | 13232     // IE looses focus of contenteditable on removeallranges and can not set new selection unless contenteditable is focused again | 
| bsw/jbe@1309 | 13233     try { | 
| bsw/jbe@1309 | 13234       rangy.getSelection(composer.win).addRange(range); | 
| bsw/jbe@1309 | 13235     } catch (e) {} | 
| bsw/jbe@1309 | 13236     if (!composer.doc.activeElement || !wysihtml.dom.contains(composer.element, composer.doc.activeElement)) { | 
| bsw/jbe@1309 | 13237       composer.element.focus(); | 
| bsw/jbe@1309 | 13238       d.scrollTop  = oldScrollTop; | 
| bsw/jbe@1309 | 13239       d.scrollLeft = oldScrollLeft; | 
| bsw/jbe@1309 | 13240       rangy.getSelection(composer.win).addRange(range); | 
| bsw/jbe@1309 | 13241     } | 
| bsw/jbe@1309 | 13242   } | 
| bsw/jbe@1309 | 13243 | 
| bsw/jbe@1309 | 13244   function selectTextNodes(textNodes, composer) { | 
| bsw/jbe@1309 | 13245     var range = rangy.createRange(composer.doc), | 
| bsw/jbe@1309 | 13246         lastText = textNodes[textNodes.length - 1]; | 
| bsw/jbe@1309 | 13247 | 
| bsw/jbe@1309 | 13248     if (textNodes[0] && lastText) { | 
| bsw/jbe@1309 | 13249       range.setStart(textNodes[0], 0); | 
| bsw/jbe@1309 | 13250       range.setEnd(lastText, lastText.length); | 
| bsw/jbe@1309 | 13251       selectRange(composer, range); | 
| bsw/jbe@1309 | 13252     } | 
| bsw/jbe@1309 | 13253 | 
| bsw/jbe@1309 | 13254   } | 
| bsw/jbe@1309 | 13255 | 
| bsw/jbe@1309 | 13256   function selectTextNode(composer, node, start, end) { | 
| bsw/jbe@1309 | 13257     var range = rangy.createRange(composer.doc); | 
| bsw/jbe@1309 | 13258     if (node) { | 
| bsw/jbe@1309 | 13259       range.setStart(node, start); | 
| bsw/jbe@1309 | 13260       range.setEnd(node, typeof end !== 'undefined' ? end : start); | 
| bsw/jbe@1309 | 13261       selectRange(composer, range); | 
| bsw/jbe@1309 | 13262     } | 
| bsw/jbe@1309 | 13263   } | 
| bsw/jbe@1309 | 13264 | 
| bsw/jbe@1309 | 13265   function getState(composer, options, exact) { | 
| bsw/jbe@1309 | 13266     var searchNodes = getSelectedTextNodes(composer.selection), | 
| bsw/jbe@1309 | 13267         nodes = [], | 
| bsw/jbe@1309 | 13268         partial = false, | 
| bsw/jbe@1309 | 13269         node, range, caretNode; | 
| bsw/jbe@1309 | 13270 | 
| bsw/jbe@1309 | 13271     if (composer.selection.isInThisEditable()) { | 
| bsw/jbe@1309 | 13272 | 
| bsw/jbe@1309 | 13273       if (searchNodes.length === 0 && composer.selection.isCollapsed()) { | 
| bsw/jbe@1309 | 13274         caretNode = composer.selection.getSelection().anchorNode; | 
| bsw/jbe@1309 | 13275         if (!caretNode) { | 
| bsw/jbe@1309 | 13276           // selection not in editor | 
| bsw/jbe@1309 | 13277           return { | 
| bsw/jbe@1309 | 13278               nodes: [], | 
| bsw/jbe@1309 | 13279               partial: false | 
| bsw/jbe@1309 | 13280           }; | 
| bsw/jbe@1309 | 13281         } | 
| bsw/jbe@1309 | 13282         if (caretNode.nodeType === 3) { | 
| bsw/jbe@1309 | 13283           searchNodes = [caretNode]; | 
| bsw/jbe@1309 | 13284         } | 
| bsw/jbe@1309 | 13285       } | 
| bsw/jbe@1309 | 13286 | 
| bsw/jbe@1309 | 13287       // Handle collapsed selection caret | 
| bsw/jbe@1309 | 13288       if (!searchNodes.length) { | 
| bsw/jbe@1309 | 13289         range = composer.selection.getOwnRanges()[0]; | 
| bsw/jbe@1309 | 13290         if (range) { | 
| bsw/jbe@1309 | 13291           searchNodes = [range.endContainer]; | 
| bsw/jbe@1309 | 13292         } | 
| bsw/jbe@1309 | 13293       } | 
| bsw/jbe@1309 | 13294 | 
| bsw/jbe@1309 | 13295       for (var i = 0, maxi = searchNodes.length; i < maxi; i++) { | 
| bsw/jbe@1309 | 13296         node = findSimilarTextNodeWrapper(searchNodes[i], options, composer.element, exact); | 
| bsw/jbe@1309 | 13297         if (node) { | 
| bsw/jbe@1309 | 13298           nodes.push(node); | 
| bsw/jbe@1309 | 13299         } else { | 
| bsw/jbe@1309 | 13300           partial = true; | 
| bsw/jbe@1309 | 13301         } | 
| bsw/jbe@1309 | 13302       } | 
| bsw/jbe@1309 | 13303 | 
| bsw/jbe@1309 | 13304     } | 
| bsw/jbe@1309 | 13305 | 
| bsw/jbe@1309 | 13306     return { | 
| bsw/jbe@1309 | 13307       nodes: nodes, | 
| bsw/jbe@1309 | 13308       partial: partial | 
| bsw/jbe@1309 | 13309     }; | 
| bsw/jbe@1309 | 13310   } | 
| bsw/jbe@1309 | 13311 | 
| bsw/jbe@1309 | 13312   // Returns if caret is inside a word in textnode (not on boundary) | 
| bsw/jbe@1309 | 13313   // If selection anchornode is not text node, returns false | 
| bsw/jbe@1309 | 13314   function caretIsInsideWord(selection) { | 
| bsw/jbe@1309 | 13315     var anchor, offset, beforeChar, afterChar; | 
| bsw/jbe@1309 | 13316     if (selection) { | 
| bsw/jbe@1309 | 13317       anchor = selection.anchorNode; | 
| bsw/jbe@1309 | 13318       offset = selection.anchorOffset; | 
| bsw/jbe@1309 | 13319       if (anchor && anchor.nodeType === 3 && offset > 0 && offset < anchor.data.length) { | 
| bsw/jbe@1309 | 13320         beforeChar = anchor.data[offset - 1]; | 
| bsw/jbe@1309 | 13321         afterChar = anchor.data[offset]; | 
| bsw/jbe@1309 | 13322         return (/\w/).test(beforeChar) && (/\w/).test(afterChar); | 
| bsw/jbe@1309 | 13323       } | 
| bsw/jbe@1309 | 13324     } | 
| bsw/jbe@1309 | 13325     return false; | 
| bsw/jbe@1309 | 13326   } | 
| bsw/jbe@1309 | 13327 | 
| bsw/jbe@1309 | 13328   // Returns a range and textnode containing object from caret position covering a whole word | 
| bsw/jbe@1309 | 13329   // wordOffsety describes the original position of caret in the new textNode | 
| bsw/jbe@1309 | 13330   // Caret has to be inside a textNode. | 
| bsw/jbe@1309 | 13331   function getRangeForWord(selection) { | 
| bsw/jbe@1309 | 13332     var anchor, offset, doc, range, offsetStart, offsetEnd, beforeChar, afterChar, | 
| bsw/jbe@1309 | 13333         txtNodes = []; | 
| bsw/jbe@1309 | 13334     if (selection) { | 
| bsw/jbe@1309 | 13335       anchor = selection.anchorNode; | 
| bsw/jbe@1309 | 13336       offset = offsetStart = offsetEnd = selection.anchorOffset; | 
| bsw/jbe@1309 | 13337       doc = anchor.ownerDocument; | 
| bsw/jbe@1309 | 13338       range = rangy.createRange(doc); | 
| bsw/jbe@1309 | 13339 | 
| bsw/jbe@1309 | 13340       if (anchor && anchor.nodeType === 3) { | 
| bsw/jbe@1309 | 13341 | 
| bsw/jbe@1309 | 13342         while (offsetStart > 0 && (/\w/).test(anchor.data[offsetStart - 1])) { | 
| bsw/jbe@1309 | 13343           offsetStart--; | 
| bsw/jbe@1309 | 13344         } | 
| bsw/jbe@1309 | 13345 | 
| bsw/jbe@1309 | 13346         while (offsetEnd < anchor.data.length && (/\w/).test(anchor.data[offsetEnd])) { | 
| bsw/jbe@1309 | 13347           offsetEnd++; | 
| bsw/jbe@1309 | 13348         } | 
| bsw/jbe@1309 | 13349 | 
| bsw/jbe@1309 | 13350         range.setStartAndEnd(anchor, offsetStart, offsetEnd); | 
| bsw/jbe@1309 | 13351         range.splitBoundaries(); | 
| bsw/jbe@1309 | 13352         txtNodes = range.getNodes([3], function(node) { | 
| bsw/jbe@1309 | 13353           return (!wysihtml.dom.domNode(node).is.emptyTextNode()); | 
| bsw/jbe@1309 | 13354         }); | 
| bsw/jbe@1309 | 13355 | 
| bsw/jbe@1309 | 13356         return { | 
| bsw/jbe@1309 | 13357           wordOffset: offset - offsetStart, | 
| bsw/jbe@1309 | 13358           range: range, | 
| bsw/jbe@1309 | 13359           textNode: txtNodes[0] | 
| bsw/jbe@1309 | 13360         }; | 
| bsw/jbe@1309 | 13361 | 
| bsw/jbe@1309 | 13362       } | 
| bsw/jbe@1309 | 13363     } | 
| bsw/jbe@1309 | 13364     return false; | 
| bsw/jbe@1309 | 13365   } | 
| bsw/jbe@1309 | 13366 | 
| bsw/jbe@1309 | 13367   // Contents of 2 elements are merged to fitst element. second element is removed as consequence | 
| bsw/jbe@1309 | 13368   function mergeContents(element1, element2) { | 
| bsw/jbe@1309 | 13369     while (element2.firstChild) { | 
| bsw/jbe@1309 | 13370       element1.appendChild(element2.firstChild); | 
| bsw/jbe@1309 | 13371     } | 
| bsw/jbe@1309 | 13372     element2.parentNode.removeChild(element2); | 
| bsw/jbe@1309 | 13373   } | 
| bsw/jbe@1309 | 13374 | 
| bsw/jbe@1309 | 13375   function mergeConsequentSimilarElements(elements) { | 
| bsw/jbe@1309 | 13376     for (var i = elements.length; i--;) { | 
| bsw/jbe@1309 | 13377 | 
| bsw/jbe@1309 | 13378       if (elements[i] && elements[i].parentNode) { // Test if node is not allready removed in cleanup | 
| bsw/jbe@1309 | 13379 | 
| bsw/jbe@1309 | 13380         if (elements[i].nextSibling && isSameNode(elements[i], elements[i].nextSibling)) { | 
| bsw/jbe@1309 | 13381           mergeContents(elements[i], elements[i].nextSibling); | 
| bsw/jbe@1309 | 13382         } | 
| bsw/jbe@1309 | 13383 | 
| bsw/jbe@1309 | 13384         if (elements[i].previousSibling && isSameNode(elements[i]  , elements[i].previousSibling)) { | 
| bsw/jbe@1309 | 13385           mergeContents(elements[i].previousSibling, elements[i]); | 
| bsw/jbe@1309 | 13386         } | 
| bsw/jbe@1309 | 13387 | 
| bsw/jbe@1309 | 13388       } | 
| bsw/jbe@1309 | 13389     } | 
| bsw/jbe@1309 | 13390   } | 
| bsw/jbe@1309 | 13391 | 
| bsw/jbe@1309 | 13392   function cleanupAndSetSelection(composer, textNodes, options) { | 
| bsw/jbe@1309 | 13393     if (textNodes.length > 0) { | 
| bsw/jbe@1309 | 13394       selectTextNodes(textNodes, composer); | 
| bsw/jbe@1309 | 13395     } | 
| bsw/jbe@1309 | 13396     mergeConsequentSimilarElements(getState(composer, options).nodes); | 
| bsw/jbe@1309 | 13397     if (textNodes.length > 0) { | 
| bsw/jbe@1309 | 13398       selectTextNodes(textNodes, composer); | 
| bsw/jbe@1309 | 13399     } | 
| bsw/jbe@1309 | 13400   } | 
| bsw/jbe@1309 | 13401 | 
| bsw/jbe@1309 | 13402   function cleanupAndSetCaret(composer, textNode, offset, options) { | 
| bsw/jbe@1309 | 13403     selectTextNode(composer, textNode, offset); | 
| bsw/jbe@1309 | 13404     mergeConsequentSimilarElements(getState(composer, options).nodes); | 
| bsw/jbe@1309 | 13405     selectTextNode(composer, textNode, offset); | 
| bsw/jbe@1309 | 13406   } | 
| bsw/jbe@1309 | 13407 | 
| bsw/jbe@1309 | 13408   // Formats a textnode with given options | 
| bsw/jbe@1309 | 13409   function formatTextNode(textNode, options) { | 
| bsw/jbe@1309 | 13410     var wrapNode = createWrapNode(textNode, options); | 
| bsw/jbe@1309 | 13411 | 
| bsw/jbe@1309 | 13412     textNode.parentNode.insertBefore(wrapNode, textNode); | 
| bsw/jbe@1309 | 13413     wrapNode.appendChild(textNode); | 
| bsw/jbe@1309 | 13414   } | 
| bsw/jbe@1309 | 13415 | 
| bsw/jbe@1309 | 13416   // Changes/toggles format of a textnode | 
| bsw/jbe@1309 | 13417   function unformatTextNode(textNode, composer, options) { | 
| bsw/jbe@1309 | 13418     var container = composer.element, | 
| bsw/jbe@1309 | 13419         wrapNode = findSimilarTextNodeWrapper(textNode, options, container), | 
| bsw/jbe@1309 | 13420         newWrapNode; | 
| bsw/jbe@1309 | 13421 | 
| bsw/jbe@1309 | 13422     if (wrapNode) { | 
| bsw/jbe@1309 | 13423       newWrapNode = wrapNode.cloneNode(false); | 
| bsw/jbe@1309 | 13424 | 
| bsw/jbe@1309 | 13425       wysihtml.dom.domNode(textNode).escapeParent(wrapNode, newWrapNode); | 
| bsw/jbe@1309 | 13426       updateFormatOfElement(newWrapNode, options); | 
| bsw/jbe@1309 | 13427     } | 
| bsw/jbe@1309 | 13428   } | 
| bsw/jbe@1309 | 13429 | 
| bsw/jbe@1309 | 13430   // Removes the format around textnode | 
| bsw/jbe@1309 | 13431   function removeFormatFromTextNode(textNode, composer, options) { | 
| bsw/jbe@1309 | 13432     var container = composer.element, | 
| bsw/jbe@1309 | 13433         wrapNode = findSimilarTextNodeWrapper(textNode, options, container); | 
| bsw/jbe@1309 | 13434 | 
| bsw/jbe@1309 | 13435     if (wrapNode) { | 
| bsw/jbe@1309 | 13436       wysihtml.dom.domNode(textNode).escapeParent(wrapNode); | 
| bsw/jbe@1309 | 13437     } | 
| bsw/jbe@1309 | 13438   } | 
| bsw/jbe@1309 | 13439 | 
| bsw/jbe@1309 | 13440   // Creates node around caret formated with options | 
| bsw/jbe@1309 | 13441   function formatTextRange(range, composer, options) { | 
| bsw/jbe@1309 | 13442     var wrapNode = createWrapNode(range.endContainer, options); | 
| bsw/jbe@1309 | 13443 | 
| bsw/jbe@1309 | 13444     range.surroundContents(wrapNode); | 
| bsw/jbe@1309 | 13445     composer.selection.selectNode(wrapNode); | 
| bsw/jbe@1309 | 13446   } | 
| bsw/jbe@1309 | 13447 | 
| bsw/jbe@1309 | 13448   // Changes/toggles format of whole selection | 
| bsw/jbe@1309 | 13449   function updateFormat(composer, textNodes, state, options) { | 
| bsw/jbe@1309 | 13450     var exactState = getState(composer, options, true), | 
| bsw/jbe@1309 | 13451         selection = composer.selection.getSelection(), | 
| bsw/jbe@1309 | 13452         wordObj, textNode, newNode, i; | 
| bsw/jbe@1309 | 13453 | 
| bsw/jbe@1309 | 13454     if (!textNodes.length) { | 
| bsw/jbe@1309 | 13455       // Selection is caret | 
| bsw/jbe@1309 | 13456 | 
| bsw/jbe@1309 | 13457 | 
| bsw/jbe@1309 | 13458       if (options.toggle !== false) { | 
| bsw/jbe@1309 | 13459         if (caretIsInsideWord(selection)) { | 
| bsw/jbe@1309 | 13460 | 
| bsw/jbe@1309 | 13461           // Unformat whole word | 
| bsw/jbe@1309 | 13462           wordObj = getRangeForWord(selection); | 
| bsw/jbe@1309 | 13463           textNode = wordObj.textNode; | 
| bsw/jbe@1309 | 13464           unformatTextNode(wordObj.textNode, composer, options); | 
| bsw/jbe@1309 | 13465           cleanupAndSetCaret(composer, wordObj.textNode, wordObj.wordOffset, options); | 
| bsw/jbe@1309 | 13466 | 
| bsw/jbe@1309 | 13467         } else { | 
| bsw/jbe@1309 | 13468 | 
| bsw/jbe@1309 | 13469           // Escape caret out of format | 
| bsw/jbe@1309 | 13470           textNode = composer.doc.createTextNode(wysihtml.INVISIBLE_SPACE); | 
| bsw/jbe@1309 | 13471           newNode = state.nodes[0].cloneNode(false); | 
| bsw/jbe@1309 | 13472           newNode.appendChild(textNode); | 
| bsw/jbe@1309 | 13473           composer.selection.splitElementAtCaret(state.nodes[0], newNode); | 
| bsw/jbe@1309 | 13474           updateFormatOfElement(newNode, options); | 
| bsw/jbe@1309 | 13475           cleanupAndSetSelection(composer, [textNode], options); | 
| bsw/jbe@1309 | 13476           var s = composer.selection.getSelection(); | 
| bsw/jbe@1309 | 13477           if (s.anchorNode && s.focusNode) { | 
| bsw/jbe@1309 | 13478             // Has an error in IE when collapsing selection. probably from rangy | 
| bsw/jbe@1309 | 13479             try { | 
| bsw/jbe@1309 | 13480               s.collapseToEnd(); | 
| bsw/jbe@1309 | 13481             } catch (e) {} | 
| bsw/jbe@1309 | 13482           } | 
| bsw/jbe@1309 | 13483         } | 
| bsw/jbe@1309 | 13484       } else { | 
| bsw/jbe@1309 | 13485         // In non-toggle mode the closest state element has to be found and the state updated differently | 
| bsw/jbe@1309 | 13486         for (i = state.nodes.length; i--;) { | 
| bsw/jbe@1309 | 13487           updateFormatOfElement(state.nodes[i], options); | 
| bsw/jbe@1309 | 13488         } | 
| bsw/jbe@1309 | 13489       } | 
| bsw/jbe@1309 | 13490 | 
| bsw/jbe@1309 | 13491     } else { | 
| bsw/jbe@1309 | 13492 | 
| bsw/jbe@1309 | 13493       if (!exactState.partial && options.toggle !== false) { | 
| bsw/jbe@1309 | 13494 | 
| bsw/jbe@1309 | 13495         // If whole selection (all textnodes) are in the applied format | 
| bsw/jbe@1309 | 13496         // remove the format from selection | 
| bsw/jbe@1309 | 13497         // Non-toggle mode never removes. Remove has to be called explicitly | 
| bsw/jbe@1309 | 13498         for (i = textNodes.length; i--;) { | 
| bsw/jbe@1309 | 13499           unformatTextNode(textNodes[i], composer, options); | 
| bsw/jbe@1309 | 13500         } | 
| bsw/jbe@1309 | 13501 | 
| bsw/jbe@1309 | 13502       } else { | 
| bsw/jbe@1309 | 13503 | 
| bsw/jbe@1309 | 13504         // Selection is partially in format | 
| bsw/jbe@1309 | 13505         // change it to new if format if textnode allreafy in similar state | 
| bsw/jbe@1309 | 13506         // else just apply | 
| bsw/jbe@1309 | 13507 | 
| bsw/jbe@1309 | 13508         for (i = textNodes.length; i--;) { | 
| bsw/jbe@1309 | 13509 | 
| bsw/jbe@1309 | 13510           if (findSimilarTextNodeWrapper(textNodes[i], options, composer.element)) { | 
| bsw/jbe@1309 | 13511             unformatTextNode(textNodes[i], composer, options); | 
| bsw/jbe@1309 | 13512           } | 
| bsw/jbe@1309 | 13513 | 
| bsw/jbe@1309 | 13514           if (!findSimilarTextNodeWrapper(textNodes[i], options, composer.element)) { | 
| bsw/jbe@1309 | 13515             formatTextNode(textNodes[i], options); | 
| bsw/jbe@1309 | 13516           } | 
| bsw/jbe@1309 | 13517         } | 
| bsw/jbe@1309 | 13518 | 
| bsw/jbe@1309 | 13519       } | 
| bsw/jbe@1309 | 13520 | 
| bsw/jbe@1309 | 13521       cleanupAndSetSelection(composer, textNodes, options); | 
| bsw/jbe@1309 | 13522     } | 
| bsw/jbe@1309 | 13523   } | 
| bsw/jbe@1309 | 13524 | 
| bsw/jbe@1309 | 13525   // Removes format from selection | 
| bsw/jbe@1309 | 13526   function removeFormat(composer, textNodes, state, options) { | 
| bsw/jbe@1309 | 13527     var textNode, textOffset, newNode, i, | 
| bsw/jbe@1309 | 13528         selection = composer.selection.getSelection(); | 
| bsw/jbe@1309 | 13529 | 
| bsw/jbe@1309 | 13530     if (!textNodes.length) { | 
| bsw/jbe@1309 | 13531       textNode = selection.anchorNode; | 
| bsw/jbe@1309 | 13532       textOffset = selection.anchorOffset; | 
| bsw/jbe@1309 | 13533 | 
| bsw/jbe@1309 | 13534       for (i = state.nodes.length; i--;) { | 
| bsw/jbe@1309 | 13535         wysihtml.dom.unwrap(state.nodes[i]); | 
| bsw/jbe@1309 | 13536       } | 
| bsw/jbe@1309 | 13537 | 
| bsw/jbe@1309 | 13538       cleanupAndSetCaret(composer, textNode, textOffset, options); | 
| bsw/jbe@1309 | 13539     } else { | 
| bsw/jbe@1309 | 13540       for (i = textNodes.length; i--;) { | 
| bsw/jbe@1309 | 13541         removeFormatFromTextNode(textNodes[i], composer, options); | 
| bsw/jbe@1309 | 13542       } | 
| bsw/jbe@1309 | 13543       cleanupAndSetSelection(composer, textNodes, options); | 
| bsw/jbe@1309 | 13544     } | 
| bsw/jbe@1309 | 13545   } | 
| bsw/jbe@1309 | 13546 | 
| bsw/jbe@1309 | 13547   // Adds format to selection | 
| bsw/jbe@1309 | 13548   function applyFormat(composer, textNodes, options) { | 
| bsw/jbe@1309 | 13549     var wordObj, i, | 
| bsw/jbe@1309 | 13550         selection = composer.selection.getSelection(); | 
| bsw/jbe@1309 | 13551 | 
| bsw/jbe@1309 | 13552     if (!textNodes.length) { | 
| bsw/jbe@1309 | 13553       // Handle collapsed selection caret and return | 
| bsw/jbe@1309 | 13554       if (caretIsInsideWord(selection)) { | 
| bsw/jbe@1309 | 13555 | 
| bsw/jbe@1309 | 13556         wordObj = getRangeForWord(selection); | 
| bsw/jbe@1309 | 13557         formatTextNode(wordObj.textNode, options); | 
| bsw/jbe@1309 | 13558         cleanupAndSetCaret(composer, wordObj.textNode, wordObj.wordOffset, options); | 
| bsw/jbe@1309 | 13559 | 
| bsw/jbe@1309 | 13560       } else { | 
| bsw/jbe@1309 | 13561         var r = composer.selection.getOwnRanges()[0]; | 
| bsw/jbe@1309 | 13562         if (r) { | 
| bsw/jbe@1309 | 13563           formatTextRange(r, composer, options); | 
| bsw/jbe@1309 | 13564         } | 
| bsw/jbe@1309 | 13565       } | 
| bsw/jbe@1309 | 13566 | 
| bsw/jbe@1309 | 13567     } else { | 
| bsw/jbe@1309 | 13568       // Handle textnodes in selection and apply format | 
| bsw/jbe@1309 | 13569       for (i = textNodes.length; i--;) { | 
| bsw/jbe@1309 | 13570         formatTextNode(textNodes[i], options); | 
| bsw/jbe@1309 | 13571       } | 
| bsw/jbe@1309 | 13572       cleanupAndSetSelection(composer, textNodes, options); | 
| bsw/jbe@1309 | 13573     } | 
| bsw/jbe@1309 | 13574   } | 
| bsw/jbe@1309 | 13575 | 
| bsw/jbe@1309 | 13576   // If properties is passed as a string, correct options with that nodeName | 
| bsw/jbe@1309 | 13577   function fixOptions(options) { | 
| bsw/jbe@1309 | 13578     options = (typeof options === "string") ? { nodeName: options } : options; | 
| bsw/jbe@1309 | 13579     if (options.nodeName) { options.nodeName = options.nodeName.toUpperCase(); } | 
| bsw/jbe@1309 | 13580     return options; | 
| bsw/jbe@1309 | 13581   } | 
| bsw/jbe@1309 | 13582 | 
| bsw/jbe@1309 | 13583   wysihtml.commands.formatInline = { | 
| bsw/jbe@1309 | 13584 | 
| bsw/jbe@1309 | 13585     // Basics: | 
| bsw/jbe@1309 | 13586     // In case of plain text or inline state not set wrap all non-empty textnodes with | 
| bsw/jbe@1309 | 13587     // In case a similar inline wrapper node is detected on one of textnodes, the wrapper node is changed (if fully contained) or split and changed (partially contained) | 
| bsw/jbe@1309 | 13588     //    In case of changing mode every textnode is addressed separatly | 
| bsw/jbe@1309 | 13589     exec: function(composer, command, options) { | 
| bsw/jbe@1309 | 13590       options = fixOptions(options); | 
| bsw/jbe@1309 | 13591 | 
| bsw/jbe@1309 | 13592       // Join adjactent textnodes first | 
| bsw/jbe@1309 | 13593       composer.element.normalize(); | 
| bsw/jbe@1309 | 13594 | 
| bsw/jbe@1309 | 13595       var textNodes = getSelectedTextNodes(composer.selection, true), | 
| bsw/jbe@1309 | 13596           state = getState(composer, options); | 
| bsw/jbe@1309 | 13597       if (state.nodes.length > 0) { | 
| bsw/jbe@1309 | 13598         // Text allready has the format applied | 
| bsw/jbe@1309 | 13599         updateFormat(composer, textNodes, state, options); | 
| bsw/jbe@1309 | 13600       } else { | 
| bsw/jbe@1309 | 13601         // Selection is not in the applied format | 
| bsw/jbe@1309 | 13602         applyFormat(composer, textNodes, options); | 
| bsw/jbe@1309 | 13603       } | 
| bsw/jbe@1309 | 13604       composer.element.normalize(); | 
| bsw/jbe@1309 | 13605     }, | 
| bsw/jbe@1309 | 13606 | 
| bsw/jbe@1309 | 13607     remove: function(composer, command, options) { | 
| bsw/jbe@1309 | 13608       options = fixOptions(options); | 
| bsw/jbe@1309 | 13609       composer.element.normalize(); | 
| bsw/jbe@1309 | 13610 | 
| bsw/jbe@1309 | 13611       var textNodes = getSelectedTextNodes(composer.selection, true), | 
| bsw/jbe@1309 | 13612           state = getState(composer, options); | 
| bsw/jbe@1309 | 13613 | 
| bsw/jbe@1309 | 13614       if (state.nodes.length > 0) { | 
| bsw/jbe@1309 | 13615         // Text allready has the format applied | 
| bsw/jbe@1309 | 13616         removeFormat(composer, textNodes, state, options); | 
| bsw/jbe@1309 | 13617       } | 
| bsw/jbe@1309 | 13618 | 
| bsw/jbe@1309 | 13619       composer.element.normalize(); | 
| bsw/jbe@1309 | 13620     }, | 
| bsw/jbe@1309 | 13621 | 
| bsw/jbe@1309 | 13622     state: function(composer, command, options) { | 
| bsw/jbe@1309 | 13623       options = fixOptions(options); | 
| bsw/jbe@1309 | 13624       var nodes = getState(composer, options, true).nodes; | 
| bsw/jbe@1309 | 13625       return (nodes.length === 0) ? false : nodes; | 
| bsw/jbe@1309 | 13626     } | 
| bsw/jbe@1309 | 13627   }; | 
| bsw/jbe@1309 | 13628 | 
| bsw/jbe@1309 | 13629 })(wysihtml); | 
| bsw/jbe@1309 | 13630 | 
| bsw/jbe@1309 | 13631 (function(wysihtml){ | 
| bsw/jbe@1309 | 13632   wysihtml.commands.indentList = { | 
| bsw/jbe@1309 | 13633     exec: function(composer, command, value) { | 
| bsw/jbe@1309 | 13634       var listEls = composer.selection.getSelectionParentsByTag('LI'); | 
| bsw/jbe@1309 | 13635       if (listEls) { | 
| bsw/jbe@1309 | 13636         return this.tryToPushLiLevel(listEls, composer.selection); | 
| bsw/jbe@1309 | 13637       } | 
| bsw/jbe@1309 | 13638       return false; | 
| bsw/jbe@1309 | 13639     }, | 
| bsw/jbe@1309 | 13640 | 
| bsw/jbe@1309 | 13641     state: function(composer, command) { | 
| bsw/jbe@1309 | 13642         return false; | 
| bsw/jbe@1309 | 13643     }, | 
| bsw/jbe@1309 | 13644 | 
| bsw/jbe@1309 | 13645     tryToPushLiLevel: function(liNodes, selection) { | 
| bsw/jbe@1309 | 13646       var listTag, list, prevLi, liNode, prevLiList, | 
| bsw/jbe@1309 | 13647           found = false; | 
| bsw/jbe@1309 | 13648 | 
| bsw/jbe@1309 | 13649       selection.executeAndRestoreRangy(function() { | 
| bsw/jbe@1309 | 13650 | 
| bsw/jbe@1309 | 13651         for (var i = liNodes.length; i--;) { | 
| bsw/jbe@1309 | 13652           liNode = liNodes[i]; | 
| bsw/jbe@1309 | 13653           listTag = (liNode.parentNode.nodeName === 'OL') ? 'OL' : 'UL'; | 
| bsw/jbe@1309 | 13654           list = liNode.ownerDocument.createElement(listTag); | 
| bsw/jbe@1309 | 13655           prevLi = wysihtml.dom.domNode(liNode).prev({nodeTypes: [wysihtml.ELEMENT_NODE]}); | 
| bsw/jbe@1309 | 13656           prevLiList = (prevLi) ? prevLi.querySelector('ul, ol') : null; | 
| bsw/jbe@1309 | 13657 | 
| bsw/jbe@1309 | 13658           if (prevLi) { | 
| bsw/jbe@1309 | 13659             if (prevLiList) { | 
| bsw/jbe@1309 | 13660               prevLiList.appendChild(liNode); | 
| bsw/jbe@1309 | 13661             } else { | 
| bsw/jbe@1309 | 13662               list.appendChild(liNode); | 
| bsw/jbe@1309 | 13663               prevLi.appendChild(list); | 
| bsw/jbe@1309 | 13664             } | 
| bsw/jbe@1309 | 13665             found = true; | 
| bsw/jbe@1309 | 13666           } | 
| bsw/jbe@1309 | 13667         } | 
| bsw/jbe@1309 | 13668 | 
| bsw/jbe@1309 | 13669       }); | 
| bsw/jbe@1309 | 13670       return found; | 
| bsw/jbe@1309 | 13671     } | 
| bsw/jbe@1309 | 13672   }; | 
| bsw/jbe@1309 | 13673 }(wysihtml)); | 
| bsw/jbe@1309 | 13674 | 
| bsw/jbe@1309 | 13675 (function(wysihtml){ | 
| bsw/jbe@1309 | 13676   wysihtml.commands.insertHTML = { | 
| bsw/jbe@1309 | 13677     exec: function(composer, command, html) { | 
| bsw/jbe@1309 | 13678         composer.selection.insertHTML(html); | 
| bsw/jbe@1309 | 13679     }, | 
| bsw/jbe@1309 | 13680 | 
| bsw/jbe@1309 | 13681     state: function() { | 
| bsw/jbe@1309 | 13682       return false; | 
| bsw/jbe@1309 | 13683     } | 
| bsw/jbe@1309 | 13684   }; | 
| bsw/jbe@1309 | 13685 }(wysihtml)); | 
| bsw/jbe@1309 | 13686 | 
| bsw/jbe@1309 | 13687 (function(wysihtml) { | 
| bsw/jbe@1309 | 13688   var LINE_BREAK = "<br>" + (wysihtml.browser.needsSpaceAfterLineBreak() ? " " : ""); | 
| bsw/jbe@1309 | 13689 | 
| bsw/jbe@1309 | 13690   wysihtml.commands.insertLineBreak = { | 
| bsw/jbe@1309 | 13691     exec: function(composer, command) { | 
| bsw/jbe@1309 | 13692       composer.selection.insertHTML(LINE_BREAK); | 
| bsw/jbe@1309 | 13693     }, | 
| bsw/jbe@1309 | 13694 | 
| bsw/jbe@1309 | 13695     state: function() { | 
| bsw/jbe@1309 | 13696       return false; | 
| bsw/jbe@1309 | 13697     } | 
| bsw/jbe@1309 | 13698   }; | 
| bsw/jbe@1309 | 13699 })(wysihtml); | 
| bsw/jbe@1309 | 13700 | 
| bsw/jbe@1309 | 13701 wysihtml.commands.insertList = (function(wysihtml) { | 
| bsw/jbe@1309 | 13702 | 
| bsw/jbe@1309 | 13703   var isNode = function(node, name) { | 
| bsw/jbe@1309 | 13704     if (node && node.nodeName) { | 
| bsw/jbe@1309 | 13705       if (typeof name === 'string') { | 
| bsw/jbe@1309 | 13706         name = [name]; | 
| bsw/jbe@1309 | 13707       } | 
| bsw/jbe@1309 | 13708       for (var n = name.length; n--;) { | 
| bsw/jbe@1309 | 13709         if (node.nodeName === name[n]) { | 
| bsw/jbe@1309 | 13710           return true; | 
| bsw/jbe@1309 | 13711         } | 
| bsw/jbe@1309 | 13712       } | 
| bsw/jbe@1309 | 13713     } | 
| bsw/jbe@1309 | 13714     return false; | 
| bsw/jbe@1309 | 13715   }; | 
| bsw/jbe@1309 | 13716 | 
| bsw/jbe@1309 | 13717   var findListEl = function(node, nodeName, composer) { | 
| bsw/jbe@1309 | 13718     var ret = { | 
| bsw/jbe@1309 | 13719           el: null, | 
| bsw/jbe@1309 | 13720           other: false | 
| bsw/jbe@1309 | 13721         }; | 
| bsw/jbe@1309 | 13722 | 
| bsw/jbe@1309 | 13723     if (node) { | 
| bsw/jbe@1309 | 13724       var parentLi = wysihtml.dom.getParentElement(node, { query: "li" }, false, composer.element), | 
| bsw/jbe@1309 | 13725           otherNodeName = (nodeName === "UL") ? "OL" : "UL"; | 
| bsw/jbe@1309 | 13726 | 
| bsw/jbe@1309 | 13727       if (isNode(node, nodeName)) { | 
| bsw/jbe@1309 | 13728         ret.el = node; | 
| bsw/jbe@1309 | 13729       } else if (isNode(node, otherNodeName)) { | 
| bsw/jbe@1309 | 13730         ret = { | 
| bsw/jbe@1309 | 13731           el: node, | 
| bsw/jbe@1309 | 13732           other: true | 
| bsw/jbe@1309 | 13733         }; | 
| bsw/jbe@1309 | 13734       } else if (parentLi) { | 
| bsw/jbe@1309 | 13735         if (isNode(parentLi.parentNode, nodeName)) { | 
| bsw/jbe@1309 | 13736           ret.el = parentLi.parentNode; | 
| bsw/jbe@1309 | 13737         } else if (isNode(parentLi.parentNode, otherNodeName)) { | 
| bsw/jbe@1309 | 13738           ret = { | 
| bsw/jbe@1309 | 13739             el : parentLi.parentNode, | 
| bsw/jbe@1309 | 13740             other: true | 
| bsw/jbe@1309 | 13741           }; | 
| bsw/jbe@1309 | 13742         } | 
| bsw/jbe@1309 | 13743       } | 
| bsw/jbe@1309 | 13744     } | 
| bsw/jbe@1309 | 13745 | 
| bsw/jbe@1309 | 13746     // do not count list elements outside of composer | 
| bsw/jbe@1309 | 13747     if (ret.el && !composer.element.contains(ret.el)) { | 
| bsw/jbe@1309 | 13748       ret.el = null; | 
| bsw/jbe@1309 | 13749     } | 
| bsw/jbe@1309 | 13750 | 
| bsw/jbe@1309 | 13751     return ret; | 
| bsw/jbe@1309 | 13752   }; | 
| bsw/jbe@1309 | 13753 | 
| bsw/jbe@1309 | 13754   var handleSameTypeList = function(el, nodeName, composer) { | 
| bsw/jbe@1309 | 13755     var otherNodeName = (nodeName === "UL") ? "OL" : "UL", | 
| bsw/jbe@1309 | 13756         otherLists, innerLists; | 
| bsw/jbe@1309 | 13757     // Unwrap list | 
| bsw/jbe@1309 | 13758     // <ul><li>foo</li><li>bar</li></ul> | 
| bsw/jbe@1309 | 13759     // becomes: | 
| bsw/jbe@1309 | 13760     // foo<br>bar<br> | 
| bsw/jbe@1309 | 13761 | 
| bsw/jbe@1309 | 13762     composer.selection.executeAndRestoreRangy(function() { | 
| bsw/jbe@1309 | 13763       otherLists = getListsInSelection(otherNodeName, composer); | 
| bsw/jbe@1309 | 13764       if (otherLists.length) { | 
| bsw/jbe@1309 | 13765         for (var l = otherLists.length; l--;) { | 
| bsw/jbe@1309 | 13766           wysihtml.dom.renameElement(otherLists[l], nodeName.toLowerCase()); | 
| bsw/jbe@1309 | 13767         } | 
| bsw/jbe@1309 | 13768       } else { | 
| bsw/jbe@1309 | 13769         innerLists = getListsInSelection(['OL', 'UL'], composer); | 
| bsw/jbe@1309 | 13770         for (var i = innerLists.length; i--;) { | 
| bsw/jbe@1309 | 13771           wysihtml.dom.resolveList(innerLists[i], composer.config.useLineBreaks); | 
| bsw/jbe@1309 | 13772         } | 
| bsw/jbe@1309 | 13773         if (innerLists.length === 0) { | 
| bsw/jbe@1309 | 13774           wysihtml.dom.resolveList(el, composer.config.useLineBreaks); | 
| bsw/jbe@1309 | 13775         } | 
| bsw/jbe@1309 | 13776       } | 
| bsw/jbe@1309 | 13777     }); | 
| bsw/jbe@1309 | 13778   }; | 
| bsw/jbe@1309 | 13779 | 
| bsw/jbe@1309 | 13780   var handleOtherTypeList =  function(el, nodeName, composer) { | 
| bsw/jbe@1309 | 13781     var otherNodeName = (nodeName === "UL") ? "OL" : "UL"; | 
| bsw/jbe@1309 | 13782     // Turn an ordered list into an unordered list | 
| bsw/jbe@1309 | 13783     // <ol><li>foo</li><li>bar</li></ol> | 
| bsw/jbe@1309 | 13784     // becomes: | 
| bsw/jbe@1309 | 13785     // <ul><li>foo</li><li>bar</li></ul> | 
| bsw/jbe@1309 | 13786     // Also rename other lists in selection | 
| bsw/jbe@1309 | 13787     composer.selection.executeAndRestoreRangy(function() { | 
| bsw/jbe@1309 | 13788       var renameLists = [el].concat(getListsInSelection(otherNodeName, composer)); | 
| bsw/jbe@1309 | 13789 | 
| bsw/jbe@1309 | 13790       // All selection inner lists get renamed too | 
| bsw/jbe@1309 | 13791       for (var l = renameLists.length; l--;) { | 
| bsw/jbe@1309 | 13792         wysihtml.dom.renameElement(renameLists[l], nodeName.toLowerCase()); | 
| bsw/jbe@1309 | 13793       } | 
| bsw/jbe@1309 | 13794     }); | 
| bsw/jbe@1309 | 13795   }; | 
| bsw/jbe@1309 | 13796 | 
| bsw/jbe@1309 | 13797   var getListsInSelection = function(nodeName, composer) { | 
| bsw/jbe@1309 | 13798       var ranges = composer.selection.getOwnRanges(), | 
| bsw/jbe@1309 | 13799           renameLists = []; | 
| bsw/jbe@1309 | 13800 | 
| bsw/jbe@1309 | 13801       for (var r = ranges.length; r--;) { | 
| bsw/jbe@1309 | 13802         renameLists = renameLists.concat(ranges[r].getNodes([1], function(node) { | 
| bsw/jbe@1309 | 13803           return isNode(node, nodeName); | 
| bsw/jbe@1309 | 13804         })); | 
| bsw/jbe@1309 | 13805       } | 
| bsw/jbe@1309 | 13806 | 
| bsw/jbe@1309 | 13807       return renameLists; | 
| bsw/jbe@1309 | 13808   }; | 
| bsw/jbe@1309 | 13809 | 
| bsw/jbe@1309 | 13810   var createListFallback = function(nodeName, composer) { | 
| bsw/jbe@1309 | 13811     var sel = rangy.saveSelection(composer.win); | 
| bsw/jbe@1309 | 13812 | 
| bsw/jbe@1309 | 13813     // Fallback for Create list | 
| bsw/jbe@1309 | 13814     var tempClassName =  "_wysihtml-temp-" + new Date().getTime(), | 
| bsw/jbe@1309 | 13815         isEmpty, list; | 
| bsw/jbe@1309 | 13816 | 
| bsw/jbe@1309 | 13817     composer.commands.exec("formatBlock", { | 
| bsw/jbe@1309 | 13818       "nodeName": "div", | 
| bsw/jbe@1309 | 13819       "className": tempClassName | 
| bsw/jbe@1309 | 13820     }); | 
| bsw/jbe@1309 | 13821 | 
| bsw/jbe@1309 | 13822     var tempElement = composer.element.querySelector("." + tempClassName); | 
| bsw/jbe@1309 | 13823 | 
| bsw/jbe@1309 | 13824     // This space causes new lists to never break on enter | 
| bsw/jbe@1309 | 13825     var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g; | 
| bsw/jbe@1309 | 13826     tempElement.innerHTML = tempElement.innerHTML.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ""); | 
| bsw/jbe@1309 | 13827     if (tempElement) { | 
| bsw/jbe@1309 | 13828       isEmpty = (/^(\s|(<br>))+$/i).test(tempElement.innerHTML); | 
| bsw/jbe@1309 | 13829       list = wysihtml.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.classNames.uneditableContainer); | 
| bsw/jbe@1309 | 13830       if (sel) { | 
| bsw/jbe@1309 | 13831         rangy.restoreSelection(sel); | 
| bsw/jbe@1309 | 13832       } | 
| bsw/jbe@1309 | 13833       if (isEmpty) { | 
| bsw/jbe@1309 | 13834         composer.selection.selectNode(list.querySelector("li"), true); | 
| bsw/jbe@1309 | 13835       } | 
| bsw/jbe@1309 | 13836     } | 
| bsw/jbe@1309 | 13837   }; | 
| bsw/jbe@1309 | 13838 | 
| bsw/jbe@1309 | 13839   return { | 
| bsw/jbe@1309 | 13840     exec: function(composer, command, nodeName) { | 
| bsw/jbe@1309 | 13841       var doc           = composer.doc, | 
| bsw/jbe@1309 | 13842           cmd           = (nodeName === "OL") ? "insertOrderedList" : "insertUnorderedList", | 
| bsw/jbe@1309 | 13843           s = composer.selection.getSelection(), | 
| bsw/jbe@1309 | 13844           anode = s.anchorNode.nodeType === 1 && s.anchorNode.firstChild ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode, | 
| bsw/jbe@1309 | 13845           fnode = s.focusNode.nodeType === 1 && s.focusNode.firstChild ? s.focusNode.childNodes[s.focusOffset] || s.focusNode.lastChild : s.focusNode, | 
| bsw/jbe@1309 | 13846           selectedNode, list; | 
| bsw/jbe@1309 | 13847 | 
| bsw/jbe@1309 | 13848       if (s.isBackwards()) { | 
| bsw/jbe@1309 | 13849         // swap variables | 
| bsw/jbe@1309 | 13850         anode = [fnode, fnode = anode][0]; | 
| bsw/jbe@1309 | 13851       } | 
| bsw/jbe@1309 | 13852 | 
| bsw/jbe@1309 | 13853       if (wysihtml.dom.domNode(fnode).is.emptyTextNode(true) && fnode) { | 
| bsw/jbe@1309 | 13854         fnode = wysihtml.dom.domNode(fnode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 13855       } | 
| bsw/jbe@1309 | 13856       if (wysihtml.dom.domNode(anode).is.emptyTextNode(true) && anode) { | 
| bsw/jbe@1309 | 13857         anode = wysihtml.dom.domNode(anode).next({nodeTypes: [1,3], ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 13858       } | 
| bsw/jbe@1309 | 13859 | 
| bsw/jbe@1309 | 13860       if (anode && fnode) { | 
| bsw/jbe@1309 | 13861         if (anode === fnode) { | 
| bsw/jbe@1309 | 13862           selectedNode = anode; | 
| bsw/jbe@1309 | 13863         } else { | 
| bsw/jbe@1309 | 13864           selectedNode = wysihtml.dom.domNode(anode).commonAncestor(fnode, composer.element); | 
| bsw/jbe@1309 | 13865         } | 
| bsw/jbe@1309 | 13866       } else { | 
| bsw/jbe@1309 | 13867         selectedNode  = composer.selection.getSelectedNode(); | 
| bsw/jbe@1309 | 13868       } | 
| bsw/jbe@1309 | 13869 | 
| bsw/jbe@1309 | 13870       list = findListEl(selectedNode, nodeName, composer); | 
| bsw/jbe@1309 | 13871 | 
| bsw/jbe@1309 | 13872       if (!list.el) { | 
| bsw/jbe@1309 | 13873         if (composer.commands.support(cmd)) { | 
| bsw/jbe@1309 | 13874           doc.execCommand(cmd, false, null); | 
| bsw/jbe@1309 | 13875         } else { | 
| bsw/jbe@1309 | 13876           createListFallback(nodeName, composer); | 
| bsw/jbe@1309 | 13877         } | 
| bsw/jbe@1309 | 13878       } else if (list.other) { | 
| bsw/jbe@1309 | 13879         handleOtherTypeList(list.el, nodeName, composer); | 
| bsw/jbe@1309 | 13880       } else { | 
| bsw/jbe@1309 | 13881         handleSameTypeList(list.el, nodeName, composer); | 
| bsw/jbe@1309 | 13882       } | 
| bsw/jbe@1309 | 13883     }, | 
| bsw/jbe@1309 | 13884 | 
| bsw/jbe@1309 | 13885     state: function(composer, command, nodeName) { | 
| bsw/jbe@1309 | 13886       var selectedNode = composer.selection.getSelectedNode(), | 
| bsw/jbe@1309 | 13887           list         = findListEl(selectedNode, nodeName, composer); | 
| bsw/jbe@1309 | 13888 | 
| bsw/jbe@1309 | 13889       return (list.el && !list.other) ? list.el : false; | 
| bsw/jbe@1309 | 13890     } | 
| bsw/jbe@1309 | 13891   }; | 
| bsw/jbe@1309 | 13892 | 
| bsw/jbe@1309 | 13893 })(wysihtml); | 
| bsw/jbe@1309 | 13894 | 
| bsw/jbe@1309 | 13895 (function(wysihtml){ | 
| bsw/jbe@1309 | 13896 | 
| bsw/jbe@1309 | 13897   wysihtml.commands.outdentList = { | 
| bsw/jbe@1309 | 13898     exec: function(composer, command, value) { | 
| bsw/jbe@1309 | 13899       var listEls = composer.selection.getSelectionParentsByTag('LI'); | 
| bsw/jbe@1309 | 13900       if (listEls) { | 
| bsw/jbe@1309 | 13901         return this.tryToPullLiLevel(listEls, composer); | 
| bsw/jbe@1309 | 13902       } | 
| bsw/jbe@1309 | 13903       return false; | 
| bsw/jbe@1309 | 13904     }, | 
| bsw/jbe@1309 | 13905 | 
| bsw/jbe@1309 | 13906     state: function(composer, command) { | 
| bsw/jbe@1309 | 13907         return false; | 
| bsw/jbe@1309 | 13908     }, | 
| bsw/jbe@1309 | 13909 | 
| bsw/jbe@1309 | 13910     tryToPullLiLevel: function(liNodes, composer) { | 
| bsw/jbe@1309 | 13911       var listNode, outerListNode, outerLiNode, list, prevLi, liNode, afterList, | 
| bsw/jbe@1309 | 13912           found = false, | 
| bsw/jbe@1309 | 13913           that = this; | 
| bsw/jbe@1309 | 13914 | 
| bsw/jbe@1309 | 13915       composer.selection.executeAndRestoreRangy(function() { | 
| bsw/jbe@1309 | 13916 | 
| bsw/jbe@1309 | 13917         for (var i = liNodes.length; i--;) { | 
| bsw/jbe@1309 | 13918           liNode = liNodes[i]; | 
| bsw/jbe@1309 | 13919           if (liNode.parentNode) { | 
| bsw/jbe@1309 | 13920             listNode = liNode.parentNode; | 
| bsw/jbe@1309 | 13921 | 
| bsw/jbe@1309 | 13922             if (listNode.tagName === 'OL' || listNode.tagName === 'UL') { | 
| bsw/jbe@1309 | 13923               found = true; | 
| bsw/jbe@1309 | 13924 | 
| bsw/jbe@1309 | 13925               outerListNode = wysihtml.dom.getParentElement(listNode.parentNode, { query: 'ol, ul' }, false, composer.element); | 
| bsw/jbe@1309 | 13926               outerLiNode = wysihtml.dom.getParentElement(listNode.parentNode, { query: 'li' }, false, composer.element); | 
| bsw/jbe@1309 | 13927 | 
| bsw/jbe@1309 | 13928               if (outerListNode && outerLiNode) { | 
| bsw/jbe@1309 | 13929 | 
| bsw/jbe@1309 | 13930                 if (liNode.nextSibling) { | 
| bsw/jbe@1309 | 13931                   afterList = that.getAfterList(listNode, liNode); | 
| bsw/jbe@1309 | 13932                   liNode.appendChild(afterList); | 
| bsw/jbe@1309 | 13933                 } | 
| bsw/jbe@1309 | 13934                 outerListNode.insertBefore(liNode, outerLiNode.nextSibling); | 
| bsw/jbe@1309 | 13935 | 
| bsw/jbe@1309 | 13936               } else { | 
| bsw/jbe@1309 | 13937 | 
| bsw/jbe@1309 | 13938                 if (liNode.nextSibling) { | 
| bsw/jbe@1309 | 13939                   afterList = that.getAfterList(listNode, liNode); | 
| bsw/jbe@1309 | 13940                   liNode.appendChild(afterList); | 
| bsw/jbe@1309 | 13941                 } | 
| bsw/jbe@1309 | 13942 | 
| bsw/jbe@1309 | 13943                 for (var j = liNode.childNodes.length; j--;) { | 
| bsw/jbe@1309 | 13944                   listNode.parentNode.insertBefore(liNode.childNodes[j], listNode.nextSibling); | 
| bsw/jbe@1309 | 13945                 } | 
| bsw/jbe@1309 | 13946 | 
| bsw/jbe@1309 | 13947                 listNode.parentNode.insertBefore(document.createElement('br'), listNode.nextSibling); | 
| bsw/jbe@1309 | 13948                 liNode.parentNode.removeChild(liNode); | 
| bsw/jbe@1309 | 13949 | 
| bsw/jbe@1309 | 13950               } | 
| bsw/jbe@1309 | 13951 | 
| bsw/jbe@1309 | 13952               // cleanup | 
| bsw/jbe@1309 | 13953               if (listNode.childNodes.length === 0) { | 
| bsw/jbe@1309 | 13954                   listNode.parentNode.removeChild(listNode); | 
| bsw/jbe@1309 | 13955               } | 
| bsw/jbe@1309 | 13956             } | 
| bsw/jbe@1309 | 13957           } | 
| bsw/jbe@1309 | 13958         } | 
| bsw/jbe@1309 | 13959 | 
| bsw/jbe@1309 | 13960       }); | 
| bsw/jbe@1309 | 13961       return found; | 
| bsw/jbe@1309 | 13962     }, | 
| bsw/jbe@1309 | 13963 | 
| bsw/jbe@1309 | 13964     getAfterList: function(listNode, liNode) { | 
| bsw/jbe@1309 | 13965       var nodeName = listNode.nodeName, | 
| bsw/jbe@1309 | 13966           newList = document.createElement(nodeName); | 
| bsw/jbe@1309 | 13967 | 
| bsw/jbe@1309 | 13968       while (liNode.nextSibling) { | 
| bsw/jbe@1309 | 13969         newList.appendChild(liNode.nextSibling); | 
| bsw/jbe@1309 | 13970       } | 
| bsw/jbe@1309 | 13971       return newList; | 
| bsw/jbe@1309 | 13972     } | 
| bsw/jbe@1309 | 13973 | 
| bsw/jbe@1309 | 13974   }; | 
| bsw/jbe@1309 | 13975 }(wysihtml)); | 
| bsw/jbe@1309 | 13976 | 
| bsw/jbe@1309 | 13977 (function(wysihtml){ | 
| bsw/jbe@1309 | 13978   wysihtml.commands.redo = { | 
| bsw/jbe@1309 | 13979     exec: function(composer) { | 
| bsw/jbe@1309 | 13980       return composer.undoManager.redo(); | 
| bsw/jbe@1309 | 13981     }, | 
| bsw/jbe@1309 | 13982 | 
| bsw/jbe@1309 | 13983     state: function(composer) { | 
| bsw/jbe@1309 | 13984       return false; | 
| bsw/jbe@1309 | 13985     } | 
| bsw/jbe@1309 | 13986   }; | 
| bsw/jbe@1309 | 13987 }(wysihtml)); | 
| bsw/jbe@1309 | 13988 | 
| bsw/jbe@1309 | 13989 (function(wysihtml) { | 
| bsw/jbe@1309 | 13990 | 
| bsw/jbe@1309 | 13991   var nodeOptions = { | 
| bsw/jbe@1309 | 13992     nodeName: "A" | 
| bsw/jbe@1309 | 13993   }; | 
| bsw/jbe@1309 | 13994 | 
| bsw/jbe@1309 | 13995   wysihtml.commands.removeLink = { | 
| bsw/jbe@1309 | 13996     exec: function(composer, command) { | 
| bsw/jbe@1309 | 13997       wysihtml.commands.formatInline.remove(composer, command, nodeOptions); | 
| bsw/jbe@1309 | 13998     }, | 
| bsw/jbe@1309 | 13999 | 
| bsw/jbe@1309 | 14000     state: function(composer, command) { | 
| bsw/jbe@1309 | 14001       return wysihtml.commands.formatInline.state(composer, command, nodeOptions); | 
| bsw/jbe@1309 | 14002     } | 
| bsw/jbe@1309 | 14003   }; | 
| bsw/jbe@1309 | 14004 | 
| bsw/jbe@1309 | 14005 })(wysihtml); | 
| bsw/jbe@1309 | 14006 | 
| bsw/jbe@1309 | 14007 (function(wysihtml){ | 
| bsw/jbe@1309 | 14008   wysihtml.commands.undo = { | 
| bsw/jbe@1309 | 14009     exec: function(composer) { | 
| bsw/jbe@1309 | 14010       return composer.undoManager.undo(); | 
| bsw/jbe@1309 | 14011     }, | 
| bsw/jbe@1309 | 14012 | 
| bsw/jbe@1309 | 14013     state: function(composer) { | 
| bsw/jbe@1309 | 14014       return false; | 
| bsw/jbe@1309 | 14015     } | 
| bsw/jbe@1309 | 14016   }; | 
| bsw/jbe@1309 | 14017 }(wysihtml)); | 
| bsw/jbe@1309 | 14018 | 
| bsw/jbe@1309 | 14019 /** | 
| bsw/jbe@1309 | 14020  * Undo Manager for wysihtml | 
| bsw/jbe@1309 | 14021  * slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface | 
| bsw/jbe@1309 | 14022  */ | 
| bsw/jbe@1309 | 14023 (function(wysihtml) { | 
| bsw/jbe@1309 | 14024   var Z_KEY               = 90, | 
| bsw/jbe@1309 | 14025       Y_KEY               = 89, | 
| bsw/jbe@1309 | 14026       BACKSPACE_KEY       = 8, | 
| bsw/jbe@1309 | 14027       DELETE_KEY          = 46, | 
| bsw/jbe@1309 | 14028       MAX_HISTORY_ENTRIES = 25, | 
| bsw/jbe@1309 | 14029       DATA_ATTR_NODE      = "data-wysihtml-selection-node", | 
| bsw/jbe@1309 | 14030       DATA_ATTR_OFFSET    = "data-wysihtml-selection-offset", | 
| bsw/jbe@1309 | 14031       UNDO_HTML           = '<span id="_wysihtml-undo" class="_wysihtml-temp">' + wysihtml.INVISIBLE_SPACE + '</span>', | 
| bsw/jbe@1309 | 14032       REDO_HTML           = '<span id="_wysihtml-redo" class="_wysihtml-temp">' + wysihtml.INVISIBLE_SPACE + '</span>', | 
| bsw/jbe@1309 | 14033       dom                 = wysihtml.dom; | 
| bsw/jbe@1309 | 14034 | 
| bsw/jbe@1309 | 14035   function cleanTempElements(doc) { | 
| bsw/jbe@1309 | 14036     var tempElement; | 
| bsw/jbe@1309 | 14037     while (tempElement = doc.querySelector("._wysihtml-temp")) { | 
| bsw/jbe@1309 | 14038       tempElement.parentNode.removeChild(tempElement); | 
| bsw/jbe@1309 | 14039     } | 
| bsw/jbe@1309 | 14040   } | 
| bsw/jbe@1309 | 14041 | 
| bsw/jbe@1309 | 14042   wysihtml.UndoManager = wysihtml.lang.Dispatcher.extend( | 
| bsw/jbe@1309 | 14043     /** @scope wysihtml.UndoManager.prototype */ { | 
| bsw/jbe@1309 | 14044     constructor: function(editor) { | 
| bsw/jbe@1309 | 14045       this.editor = editor; | 
| bsw/jbe@1309 | 14046       this.composer = editor.composer; | 
| bsw/jbe@1309 | 14047       this.element = this.composer.element; | 
| bsw/jbe@1309 | 14048 | 
| bsw/jbe@1309 | 14049       this.position = 0; | 
| bsw/jbe@1309 | 14050       this.historyStr = []; | 
| bsw/jbe@1309 | 14051       this.historyDom = []; | 
| bsw/jbe@1309 | 14052 | 
| bsw/jbe@1309 | 14053       this.transact(); | 
| bsw/jbe@1309 | 14054 | 
| bsw/jbe@1309 | 14055       this._observe(); | 
| bsw/jbe@1309 | 14056     }, | 
| bsw/jbe@1309 | 14057 | 
| bsw/jbe@1309 | 14058     _observe: function() { | 
| bsw/jbe@1309 | 14059       var that      = this, | 
| bsw/jbe@1309 | 14060           doc       = this.composer.sandbox.getDocument(), | 
| bsw/jbe@1309 | 14061           lastKey; | 
| bsw/jbe@1309 | 14062 | 
| bsw/jbe@1309 | 14063       // Catch CTRL+Z and CTRL+Y | 
| bsw/jbe@1309 | 14064       dom.observe(this.element, "keydown", function(event) { | 
| bsw/jbe@1309 | 14065         if (event.altKey || (!event.ctrlKey && !event.metaKey)) { | 
| bsw/jbe@1309 | 14066           return; | 
| bsw/jbe@1309 | 14067         } | 
| bsw/jbe@1309 | 14068 | 
| bsw/jbe@1309 | 14069         var keyCode = event.keyCode, | 
| bsw/jbe@1309 | 14070             isUndo = keyCode === Z_KEY && !event.shiftKey, | 
| bsw/jbe@1309 | 14071             isRedo = (keyCode === Z_KEY && event.shiftKey) || (keyCode === Y_KEY); | 
| bsw/jbe@1309 | 14072 | 
| bsw/jbe@1309 | 14073         if (isUndo) { | 
| bsw/jbe@1309 | 14074           that.undo(); | 
| bsw/jbe@1309 | 14075           event.preventDefault(); | 
| bsw/jbe@1309 | 14076         } else if (isRedo) { | 
| bsw/jbe@1309 | 14077           that.redo(); | 
| bsw/jbe@1309 | 14078           event.preventDefault(); | 
| bsw/jbe@1309 | 14079         } | 
| bsw/jbe@1309 | 14080       }); | 
| bsw/jbe@1309 | 14081 | 
| bsw/jbe@1309 | 14082       // Catch delete and backspace | 
| bsw/jbe@1309 | 14083       dom.observe(this.element, "keydown", function(event) { | 
| bsw/jbe@1309 | 14084         var keyCode = event.keyCode; | 
| bsw/jbe@1309 | 14085         if (keyCode === lastKey) { | 
| bsw/jbe@1309 | 14086           return; | 
| bsw/jbe@1309 | 14087         } | 
| bsw/jbe@1309 | 14088 | 
| bsw/jbe@1309 | 14089         lastKey = keyCode; | 
| bsw/jbe@1309 | 14090 | 
| bsw/jbe@1309 | 14091         if (keyCode === BACKSPACE_KEY || keyCode === DELETE_KEY) { | 
| bsw/jbe@1309 | 14092           that.transact(); | 
| bsw/jbe@1309 | 14093         } | 
| bsw/jbe@1309 | 14094       }); | 
| bsw/jbe@1309 | 14095 | 
| bsw/jbe@1309 | 14096       this.editor | 
| bsw/jbe@1309 | 14097         .on("newword:composer", function() { | 
| bsw/jbe@1309 | 14098           that.transact(); | 
| bsw/jbe@1309 | 14099         }) | 
| bsw/jbe@1309 | 14100 | 
| bsw/jbe@1309 | 14101         .on("beforecommand:composer", function() { | 
| bsw/jbe@1309 | 14102           that.transact(); | 
| bsw/jbe@1309 | 14103         }); | 
| bsw/jbe@1309 | 14104     }, | 
| bsw/jbe@1309 | 14105 | 
| bsw/jbe@1309 | 14106     transact: function() { | 
| bsw/jbe@1309 | 14107       var previousHtml      = this.historyStr[this.position - 1], | 
| bsw/jbe@1309 | 14108           currentHtml       = this.composer.getValue(false, false), | 
| bsw/jbe@1309 | 14109           composerIsVisible   = this.element.offsetWidth > 0 && this.element.offsetHeight > 0, | 
| bsw/jbe@1309 | 14110           range, node, offset, element, position; | 
| bsw/jbe@1309 | 14111 | 
| bsw/jbe@1309 | 14112       if (currentHtml === previousHtml) { | 
| bsw/jbe@1309 | 14113         return; | 
| bsw/jbe@1309 | 14114       } | 
| bsw/jbe@1309 | 14115 | 
| bsw/jbe@1309 | 14116       var length = this.historyStr.length = this.historyDom.length = this.position; | 
| bsw/jbe@1309 | 14117       if (length > MAX_HISTORY_ENTRIES) { | 
| bsw/jbe@1309 | 14118         this.historyStr.shift(); | 
| bsw/jbe@1309 | 14119         this.historyDom.shift(); | 
| bsw/jbe@1309 | 14120         this.position--; | 
| bsw/jbe@1309 | 14121       } | 
| bsw/jbe@1309 | 14122 | 
| bsw/jbe@1309 | 14123       this.position++; | 
| bsw/jbe@1309 | 14124 | 
| bsw/jbe@1309 | 14125       if (composerIsVisible) { | 
| bsw/jbe@1309 | 14126         // Do not start saving selection if composer is not visible | 
| bsw/jbe@1309 | 14127         range   = this.composer.selection.getRange(); | 
| bsw/jbe@1309 | 14128         node    = (range && range.startContainer) ? range.startContainer : this.element; | 
| bsw/jbe@1309 | 14129         offset  = (range && range.startOffset) ? range.startOffset : 0; | 
| bsw/jbe@1309 | 14130 | 
| bsw/jbe@1309 | 14131         if (node.nodeType === wysihtml.ELEMENT_NODE) { | 
| bsw/jbe@1309 | 14132           element = node; | 
| bsw/jbe@1309 | 14133         } else { | 
| bsw/jbe@1309 | 14134           element  = node.parentNode; | 
| bsw/jbe@1309 | 14135           position = this.getChildNodeIndex(element, node); | 
| bsw/jbe@1309 | 14136         } | 
| bsw/jbe@1309 | 14137 | 
| bsw/jbe@1309 | 14138         element.setAttribute(DATA_ATTR_OFFSET, offset); | 
| bsw/jbe@1309 | 14139         if (typeof(position) !== "undefined") { | 
| bsw/jbe@1309 | 14140           element.setAttribute(DATA_ATTR_NODE, position); | 
| bsw/jbe@1309 | 14141         } | 
| bsw/jbe@1309 | 14142       } | 
| bsw/jbe@1309 | 14143 | 
| bsw/jbe@1309 | 14144       var clone = this.element.cloneNode(!!currentHtml); | 
| bsw/jbe@1309 | 14145       this.historyDom.push(clone); | 
| bsw/jbe@1309 | 14146       this.historyStr.push(currentHtml); | 
| bsw/jbe@1309 | 14147 | 
| bsw/jbe@1309 | 14148       if (element) { | 
| bsw/jbe@1309 | 14149         element.removeAttribute(DATA_ATTR_OFFSET); | 
| bsw/jbe@1309 | 14150         element.removeAttribute(DATA_ATTR_NODE); | 
| bsw/jbe@1309 | 14151       } | 
| bsw/jbe@1309 | 14152 | 
| bsw/jbe@1309 | 14153     }, | 
| bsw/jbe@1309 | 14154 | 
| bsw/jbe@1309 | 14155     undo: function() { | 
| bsw/jbe@1309 | 14156       this.transact(); | 
| bsw/jbe@1309 | 14157 | 
| bsw/jbe@1309 | 14158       if (!this.undoPossible()) { | 
| bsw/jbe@1309 | 14159         return; | 
| bsw/jbe@1309 | 14160       } | 
| bsw/jbe@1309 | 14161 | 
| bsw/jbe@1309 | 14162       this.set(this.historyDom[--this.position - 1]); | 
| bsw/jbe@1309 | 14163       this.editor.fire("undo:composer"); | 
| bsw/jbe@1309 | 14164     }, | 
| bsw/jbe@1309 | 14165 | 
| bsw/jbe@1309 | 14166     redo: function() { | 
| bsw/jbe@1309 | 14167       if (!this.redoPossible()) { | 
| bsw/jbe@1309 | 14168         return; | 
| bsw/jbe@1309 | 14169       } | 
| bsw/jbe@1309 | 14170 | 
| bsw/jbe@1309 | 14171       this.set(this.historyDom[++this.position - 1]); | 
| bsw/jbe@1309 | 14172       this.editor.fire("redo:composer"); | 
| bsw/jbe@1309 | 14173     }, | 
| bsw/jbe@1309 | 14174 | 
| bsw/jbe@1309 | 14175     undoPossible: function() { | 
| bsw/jbe@1309 | 14176       return this.position > 1; | 
| bsw/jbe@1309 | 14177     }, | 
| bsw/jbe@1309 | 14178 | 
| bsw/jbe@1309 | 14179     redoPossible: function() { | 
| bsw/jbe@1309 | 14180       return this.position < this.historyStr.length; | 
| bsw/jbe@1309 | 14181     }, | 
| bsw/jbe@1309 | 14182 | 
| bsw/jbe@1309 | 14183     set: function(historyEntry) { | 
| bsw/jbe@1309 | 14184       this.element.innerHTML = ""; | 
| bsw/jbe@1309 | 14185 | 
| bsw/jbe@1309 | 14186       var i = 0, | 
| bsw/jbe@1309 | 14187           childNodes = historyEntry.childNodes, | 
| bsw/jbe@1309 | 14188           length = historyEntry.childNodes.length; | 
| bsw/jbe@1309 | 14189 | 
| bsw/jbe@1309 | 14190       for (; i<length; i++) { | 
| bsw/jbe@1309 | 14191         this.element.appendChild(childNodes[i].cloneNode(true)); | 
| bsw/jbe@1309 | 14192       } | 
| bsw/jbe@1309 | 14193 | 
| bsw/jbe@1309 | 14194       // Restore selection | 
| bsw/jbe@1309 | 14195       var offset, | 
| bsw/jbe@1309 | 14196           node, | 
| bsw/jbe@1309 | 14197           position; | 
| bsw/jbe@1309 | 14198 | 
| bsw/jbe@1309 | 14199       if (historyEntry.hasAttribute(DATA_ATTR_OFFSET)) { | 
| bsw/jbe@1309 | 14200         offset    = historyEntry.getAttribute(DATA_ATTR_OFFSET); | 
| bsw/jbe@1309 | 14201         position  = historyEntry.getAttribute(DATA_ATTR_NODE); | 
| bsw/jbe@1309 | 14202         node      = this.element; | 
| bsw/jbe@1309 | 14203       } else { | 
| bsw/jbe@1309 | 14204         node      = this.element.querySelector("[" + DATA_ATTR_OFFSET + "]") || this.element; | 
| bsw/jbe@1309 | 14205         offset    = node.getAttribute(DATA_ATTR_OFFSET); | 
| bsw/jbe@1309 | 14206         position  = node.getAttribute(DATA_ATTR_NODE); | 
| bsw/jbe@1309 | 14207         node.removeAttribute(DATA_ATTR_OFFSET); | 
| bsw/jbe@1309 | 14208         node.removeAttribute(DATA_ATTR_NODE); | 
| bsw/jbe@1309 | 14209       } | 
| bsw/jbe@1309 | 14210 | 
| bsw/jbe@1309 | 14211       if (position !== null) { | 
| bsw/jbe@1309 | 14212         node = this.getChildNodeByIndex(node, +position); | 
| bsw/jbe@1309 | 14213       } | 
| bsw/jbe@1309 | 14214 | 
| bsw/jbe@1309 | 14215       this.composer.selection.set(node, offset); | 
| bsw/jbe@1309 | 14216     }, | 
| bsw/jbe@1309 | 14217 | 
| bsw/jbe@1309 | 14218     getChildNodeIndex: function(parent, child) { | 
| bsw/jbe@1309 | 14219       var i           = 0, | 
| bsw/jbe@1309 | 14220           childNodes  = parent.childNodes, | 
| bsw/jbe@1309 | 14221           length      = childNodes.length; | 
| bsw/jbe@1309 | 14222       for (; i<length; i++) { | 
| bsw/jbe@1309 | 14223         if (childNodes[i] === child) { | 
| bsw/jbe@1309 | 14224           return i; | 
| bsw/jbe@1309 | 14225         } | 
| bsw/jbe@1309 | 14226       } | 
| bsw/jbe@1309 | 14227     }, | 
| bsw/jbe@1309 | 14228 | 
| bsw/jbe@1309 | 14229     getChildNodeByIndex: function(parent, index) { | 
| bsw/jbe@1309 | 14230       return parent.childNodes[index]; | 
| bsw/jbe@1309 | 14231     } | 
| bsw/jbe@1309 | 14232   }); | 
| bsw/jbe@1309 | 14233 })(wysihtml); | 
| bsw/jbe@1309 | 14234 | 
| bsw/jbe@1309 | 14235 /** | 
| bsw/jbe@1309 | 14236  * TODO: the following methods still need unit test coverage | 
| bsw/jbe@1309 | 14237  */ | 
| bsw/jbe@1309 | 14238 wysihtml.views.View = Base.extend( | 
| bsw/jbe@1309 | 14239   /** @scope wysihtml.views.View.prototype */ { | 
| bsw/jbe@1309 | 14240   constructor: function(parent, textareaElement, config) { | 
| bsw/jbe@1309 | 14241     this.parent   = parent; | 
| bsw/jbe@1309 | 14242     this.element  = textareaElement; | 
| bsw/jbe@1309 | 14243     this.config   = config; | 
| bsw/jbe@1309 | 14244     if (!this.config.noTextarea) { | 
| bsw/jbe@1309 | 14245         this._observeViewChange(); | 
| bsw/jbe@1309 | 14246     } | 
| bsw/jbe@1309 | 14247   }, | 
| bsw/jbe@1309 | 14248 | 
| bsw/jbe@1309 | 14249   _observeViewChange: function() { | 
| bsw/jbe@1309 | 14250     var that = this; | 
| bsw/jbe@1309 | 14251     this.parent.on("beforeload", function() { | 
| bsw/jbe@1309 | 14252       that.parent.on("change_view", function(view) { | 
| bsw/jbe@1309 | 14253         if (view === that.name) { | 
| bsw/jbe@1309 | 14254           that.parent.currentView = that; | 
| bsw/jbe@1309 | 14255           that.show(); | 
| bsw/jbe@1309 | 14256           // Using tiny delay here to make sure that the placeholder is set before focusing | 
| bsw/jbe@1309 | 14257           setTimeout(function() { that.focus(); }, 0); | 
| bsw/jbe@1309 | 14258         } else { | 
| bsw/jbe@1309 | 14259           that.hide(); | 
| bsw/jbe@1309 | 14260         } | 
| bsw/jbe@1309 | 14261       }); | 
| bsw/jbe@1309 | 14262     }); | 
| bsw/jbe@1309 | 14263   }, | 
| bsw/jbe@1309 | 14264 | 
| bsw/jbe@1309 | 14265   focus: function() { | 
| bsw/jbe@1309 | 14266     if (this.element && this.element.ownerDocument && this.element.ownerDocument.querySelector(":focus") === this.element) { | 
| bsw/jbe@1309 | 14267       return; | 
| bsw/jbe@1309 | 14268     } | 
| bsw/jbe@1309 | 14269 | 
| bsw/jbe@1309 | 14270     try { if(this.element) { this.element.focus(); } } catch(e) {} | 
| bsw/jbe@1309 | 14271   }, | 
| bsw/jbe@1309 | 14272 | 
| bsw/jbe@1309 | 14273   hide: function() { | 
| bsw/jbe@1309 | 14274     this.element.style.display = "none"; | 
| bsw/jbe@1309 | 14275   }, | 
| bsw/jbe@1309 | 14276 | 
| bsw/jbe@1309 | 14277   show: function() { | 
| bsw/jbe@1309 | 14278     this.element.style.display = ""; | 
| bsw/jbe@1309 | 14279   }, | 
| bsw/jbe@1309 | 14280 | 
| bsw/jbe@1309 | 14281   disable: function() { | 
| bsw/jbe@1309 | 14282     this.element.setAttribute("disabled", "disabled"); | 
| bsw/jbe@1309 | 14283   }, | 
| bsw/jbe@1309 | 14284 | 
| bsw/jbe@1309 | 14285   enable: function() { | 
| bsw/jbe@1309 | 14286     this.element.removeAttribute("disabled"); | 
| bsw/jbe@1309 | 14287   } | 
| bsw/jbe@1309 | 14288 }); | 
| bsw/jbe@1309 | 14289 | 
| bsw/jbe@1309 | 14290 (function(wysihtml) { | 
| bsw/jbe@1309 | 14291   var dom       = wysihtml.dom, | 
| bsw/jbe@1309 | 14292       browser   = wysihtml.browser; | 
| bsw/jbe@1309 | 14293 | 
| bsw/jbe@1309 | 14294   wysihtml.views.Composer = wysihtml.views.View.extend( | 
| bsw/jbe@1309 | 14295     /** @scope wysihtml.views.Composer.prototype */ { | 
| bsw/jbe@1309 | 14296     name: "composer", | 
| bsw/jbe@1309 | 14297 | 
| bsw/jbe@1309 | 14298     constructor: function(parent, editableElement, config) { | 
| bsw/jbe@1309 | 14299       this.base(parent, editableElement, config); | 
| bsw/jbe@1309 | 14300       if (!this.config.noTextarea) { | 
| bsw/jbe@1309 | 14301           this.textarea = this.parent.textarea; | 
| bsw/jbe@1309 | 14302       } else { | 
| bsw/jbe@1309 | 14303           this.editableArea = editableElement; | 
| bsw/jbe@1309 | 14304       } | 
| bsw/jbe@1309 | 14305       if (this.config.contentEditableMode) { | 
| bsw/jbe@1309 | 14306           this._initContentEditableArea(); | 
| bsw/jbe@1309 | 14307       } else { | 
| bsw/jbe@1309 | 14308           this._initSandbox(); | 
| bsw/jbe@1309 | 14309       } | 
| bsw/jbe@1309 | 14310     }, | 
| bsw/jbe@1309 | 14311 | 
| bsw/jbe@1309 | 14312     clear: function() { | 
| bsw/jbe@1309 | 14313       this.element.innerHTML = browser.displaysCaretInEmptyContentEditableCorrectly() ? "" : "<br>"; | 
| bsw/jbe@1309 | 14314     }, | 
| bsw/jbe@1309 | 14315 | 
| bsw/jbe@1309 | 14316     getValue: function(parse, clearInternals) { | 
| bsw/jbe@1309 | 14317       var value = this.isEmpty() ? "" : wysihtml.quirks.getCorrectInnerHTML(this.element); | 
| bsw/jbe@1309 | 14318       if (parse !== false) { | 
| bsw/jbe@1309 | 14319         value = this.parent.parse(value, (clearInternals === false) ? false : true); | 
| bsw/jbe@1309 | 14320       } | 
| bsw/jbe@1309 | 14321       return value; | 
| bsw/jbe@1309 | 14322     }, | 
| bsw/jbe@1309 | 14323 | 
| bsw/jbe@1309 | 14324     setValue: function(html, parse) { | 
| bsw/jbe@1309 | 14325       if (parse !== false) { | 
| bsw/jbe@1309 | 14326         html = this.parent.parse(html); | 
| bsw/jbe@1309 | 14327       } | 
| bsw/jbe@1309 | 14328 | 
| bsw/jbe@1309 | 14329       try { | 
| bsw/jbe@1309 | 14330         this.element.innerHTML = html; | 
| bsw/jbe@1309 | 14331       } catch (e) { | 
| bsw/jbe@1309 | 14332         this.element.innerText = html; | 
| bsw/jbe@1309 | 14333       } | 
| bsw/jbe@1309 | 14334     }, | 
| bsw/jbe@1309 | 14335 | 
| bsw/jbe@1309 | 14336     cleanUp: function(rules) { | 
| bsw/jbe@1309 | 14337       var bookmark; | 
| bsw/jbe@1309 | 14338       if (this.selection && this.selection.isInThisEditable()) { | 
| bsw/jbe@1309 | 14339         bookmark = rangy.saveSelection(this.win); | 
| bsw/jbe@1309 | 14340       } | 
| bsw/jbe@1309 | 14341       this.parent.parse(this.element, undefined, rules); | 
| bsw/jbe@1309 | 14342       if (bookmark) { | 
| bsw/jbe@1309 | 14343         rangy.restoreSelection(bookmark); | 
| bsw/jbe@1309 | 14344       } | 
| bsw/jbe@1309 | 14345     }, | 
| bsw/jbe@1309 | 14346 | 
| bsw/jbe@1309 | 14347     show: function() { | 
| bsw/jbe@1309 | 14348       this.editableArea.style.display = this._displayStyle || ""; | 
| bsw/jbe@1309 | 14349 | 
| bsw/jbe@1309 | 14350       if (!this.config.noTextarea && !this.textarea.element.disabled) { | 
| bsw/jbe@1309 | 14351         // Firefox needs this, otherwise contentEditable becomes uneditable | 
| bsw/jbe@1309 | 14352         this.disable(); | 
| bsw/jbe@1309 | 14353         this.enable(); | 
| bsw/jbe@1309 | 14354       } | 
| bsw/jbe@1309 | 14355     }, | 
| bsw/jbe@1309 | 14356 | 
| bsw/jbe@1309 | 14357     hide: function() { | 
| bsw/jbe@1309 | 14358       this._displayStyle = dom.getStyle("display").from(this.editableArea); | 
| bsw/jbe@1309 | 14359       if (this._displayStyle === "none") { | 
| bsw/jbe@1309 | 14360         this._displayStyle = null; | 
| bsw/jbe@1309 | 14361       } | 
| bsw/jbe@1309 | 14362       this.editableArea.style.display = "none"; | 
| bsw/jbe@1309 | 14363     }, | 
| bsw/jbe@1309 | 14364 | 
| bsw/jbe@1309 | 14365     disable: function() { | 
| bsw/jbe@1309 | 14366       this.parent.fire("disable:composer"); | 
| bsw/jbe@1309 | 14367       this.element.removeAttribute("contentEditable"); | 
| bsw/jbe@1309 | 14368     }, | 
| bsw/jbe@1309 | 14369 | 
| bsw/jbe@1309 | 14370     enable: function() { | 
| bsw/jbe@1309 | 14371       this.parent.fire("enable:composer"); | 
| bsw/jbe@1309 | 14372       this.element.setAttribute("contentEditable", "true"); | 
| bsw/jbe@1309 | 14373     }, | 
| bsw/jbe@1309 | 14374 | 
| bsw/jbe@1309 | 14375     focus: function(setToEnd) { | 
| bsw/jbe@1309 | 14376       // IE 8 fires the focus event after .focus() | 
| bsw/jbe@1309 | 14377       // This is needed by our simulate_placeholder.js to work | 
| bsw/jbe@1309 | 14378       // therefore we clear it ourselves this time | 
| bsw/jbe@1309 | 14379       if (wysihtml.browser.doesAsyncFocus() && this.hasPlaceholderSet()) { | 
| bsw/jbe@1309 | 14380         this.clear(); | 
| bsw/jbe@1309 | 14381       } | 
| bsw/jbe@1309 | 14382 | 
| bsw/jbe@1309 | 14383       this.base(); | 
| bsw/jbe@1309 | 14384 | 
| bsw/jbe@1309 | 14385       var lastChild = this.element.lastChild; | 
| bsw/jbe@1309 | 14386       if (setToEnd && lastChild && this.selection) { | 
| bsw/jbe@1309 | 14387         if (lastChild.nodeName === "BR") { | 
| bsw/jbe@1309 | 14388           this.selection.setBefore(this.element.lastChild); | 
| bsw/jbe@1309 | 14389         } else { | 
| bsw/jbe@1309 | 14390           this.selection.setAfter(this.element.lastChild); | 
| bsw/jbe@1309 | 14391         } | 
| bsw/jbe@1309 | 14392       } | 
| bsw/jbe@1309 | 14393     }, | 
| bsw/jbe@1309 | 14394 | 
| bsw/jbe@1309 | 14395     getScrollPos: function() { | 
| bsw/jbe@1309 | 14396       if (this.doc && this.win) { | 
| bsw/jbe@1309 | 14397         var pos = {}; | 
| bsw/jbe@1309 | 14398 | 
| bsw/jbe@1309 | 14399         if (typeof this.win.pageYOffset !== "undefined") { | 
| bsw/jbe@1309 | 14400           pos.y = this.win.pageYOffset; | 
| bsw/jbe@1309 | 14401         } else { | 
| bsw/jbe@1309 | 14402           pos.y = (this.doc.documentElement || this.doc.body.parentNode || this.doc.body).scrollTop; | 
| bsw/jbe@1309 | 14403         } | 
| bsw/jbe@1309 | 14404 | 
| bsw/jbe@1309 | 14405         if (typeof this.win.pageXOffset !== "undefined") { | 
| bsw/jbe@1309 | 14406           pos.x = this.win.pageXOffset; | 
| bsw/jbe@1309 | 14407         } else { | 
| bsw/jbe@1309 | 14408           pos.x = (this.doc.documentElement || this.doc.body.parentNode || this.doc.body).scrollLeft; | 
| bsw/jbe@1309 | 14409         } | 
| bsw/jbe@1309 | 14410 | 
| bsw/jbe@1309 | 14411         return pos; | 
| bsw/jbe@1309 | 14412       } | 
| bsw/jbe@1309 | 14413     }, | 
| bsw/jbe@1309 | 14414 | 
| bsw/jbe@1309 | 14415     setScrollPos: function(pos) { | 
| bsw/jbe@1309 | 14416       if (pos && typeof pos.x !== "undefined" && typeof pos.y !== "undefined") { | 
| bsw/jbe@1309 | 14417         this.win.scrollTo(pos.x, pos.y); | 
| bsw/jbe@1309 | 14418       } | 
| bsw/jbe@1309 | 14419     }, | 
| bsw/jbe@1309 | 14420 | 
| bsw/jbe@1309 | 14421     getTextContent: function() { | 
| bsw/jbe@1309 | 14422       return dom.getTextContent(this.element); | 
| bsw/jbe@1309 | 14423     }, | 
| bsw/jbe@1309 | 14424 | 
| bsw/jbe@1309 | 14425     hasPlaceholderSet: function() { | 
| bsw/jbe@1309 | 14426       return this.getTextContent() == ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder")) && this.placeholderSet; | 
| bsw/jbe@1309 | 14427     }, | 
| bsw/jbe@1309 | 14428 | 
| bsw/jbe@1309 | 14429     isEmpty: function() { | 
| bsw/jbe@1309 | 14430       var innerHTML = this.element.innerHTML.toLowerCase(); | 
| bsw/jbe@1309 | 14431       return (/^(\s|<br>|<\/br>|<p>|<\/p>)*$/i).test(innerHTML)  || | 
| bsw/jbe@1309 | 14432              innerHTML === ""            || | 
| bsw/jbe@1309 | 14433              innerHTML === "<br>"        || | 
| bsw/jbe@1309 | 14434              innerHTML === "<p></p>"     || | 
| bsw/jbe@1309 | 14435              innerHTML === "<p><br></p>" || | 
| bsw/jbe@1309 | 14436              this.hasPlaceholderSet(); | 
| bsw/jbe@1309 | 14437     }, | 
| bsw/jbe@1309 | 14438 | 
| bsw/jbe@1309 | 14439     _initContentEditableArea: function() { | 
| bsw/jbe@1309 | 14440         var that = this; | 
| bsw/jbe@1309 | 14441         if (this.config.noTextarea) { | 
| bsw/jbe@1309 | 14442             this.sandbox = new dom.ContentEditableArea(function() { | 
| bsw/jbe@1309 | 14443                 that._create(); | 
| bsw/jbe@1309 | 14444             }, { | 
| bsw/jbe@1309 | 14445               className: this.config.classNames.sandbox | 
| bsw/jbe@1309 | 14446             }, this.editableArea); | 
| bsw/jbe@1309 | 14447         } else { | 
| bsw/jbe@1309 | 14448             this.sandbox = new dom.ContentEditableArea(function() { | 
| bsw/jbe@1309 | 14449                 that._create(); | 
| bsw/jbe@1309 | 14450             }, { | 
| bsw/jbe@1309 | 14451               className: this.config.classNames.sandbox | 
| bsw/jbe@1309 | 14452             }); | 
| bsw/jbe@1309 | 14453             this.editableArea = this.sandbox.getContentEditable(); | 
| bsw/jbe@1309 | 14454             dom.insert(this.editableArea).after(this.textarea.element); | 
| bsw/jbe@1309 | 14455             this._createWysiwygFormField(); | 
| bsw/jbe@1309 | 14456         } | 
| bsw/jbe@1309 | 14457     }, | 
| bsw/jbe@1309 | 14458 | 
| bsw/jbe@1309 | 14459     _initSandbox: function() { | 
| bsw/jbe@1309 | 14460       var that = this; | 
| bsw/jbe@1309 | 14461       this.sandbox = new dom.Sandbox(function() { | 
| bsw/jbe@1309 | 14462         that._create(); | 
| bsw/jbe@1309 | 14463       }, { | 
| bsw/jbe@1309 | 14464         stylesheets:  this.config.stylesheets, | 
| bsw/jbe@1309 | 14465         className: this.config.classNames.sandbox | 
| bsw/jbe@1309 | 14466       }); | 
| bsw/jbe@1309 | 14467       this.editableArea  = this.sandbox.getIframe(); | 
| bsw/jbe@1309 | 14468 | 
| bsw/jbe@1309 | 14469       var textareaElement = this.textarea.element; | 
| bsw/jbe@1309 | 14470       dom.insert(this.editableArea).after(textareaElement); | 
| bsw/jbe@1309 | 14471 | 
| bsw/jbe@1309 | 14472       this._createWysiwygFormField(); | 
| bsw/jbe@1309 | 14473     }, | 
| bsw/jbe@1309 | 14474 | 
| bsw/jbe@1309 | 14475     // Creates hidden field which tells the server after submit, that the user used an wysiwyg editor | 
| bsw/jbe@1309 | 14476     _createWysiwygFormField: function() { | 
| bsw/jbe@1309 | 14477         if (this.textarea.element.form) { | 
| bsw/jbe@1309 | 14478           var hiddenField = document.createElement("input"); | 
| bsw/jbe@1309 | 14479           hiddenField.type   = "hidden"; | 
| bsw/jbe@1309 | 14480           hiddenField.name   = "_wysihtml_mode"; | 
| bsw/jbe@1309 | 14481           hiddenField.value  = 1; | 
| bsw/jbe@1309 | 14482           dom.insert(hiddenField).after(this.textarea.element); | 
| bsw/jbe@1309 | 14483         } | 
| bsw/jbe@1309 | 14484     }, | 
| bsw/jbe@1309 | 14485 | 
| bsw/jbe@1309 | 14486     _create: function() { | 
| bsw/jbe@1309 | 14487       var that = this; | 
| bsw/jbe@1309 | 14488       this.doc                = this.sandbox.getDocument(); | 
| bsw/jbe@1309 | 14489       this.win                = this.sandbox.getWindow(); | 
| bsw/jbe@1309 | 14490       this.element            = (this.config.contentEditableMode) ? this.sandbox.getContentEditable() : this.doc.body; | 
| bsw/jbe@1309 | 14491       if (!this.config.noTextarea) { | 
| bsw/jbe@1309 | 14492           this.textarea           = this.parent.textarea; | 
| bsw/jbe@1309 | 14493           this.element.innerHTML  = this.textarea.getValue(true, false); | 
| bsw/jbe@1309 | 14494       } else { | 
| bsw/jbe@1309 | 14495           this.cleanUp(); // cleans contenteditable on initiation as it may contain html | 
| bsw/jbe@1309 | 14496       } | 
| bsw/jbe@1309 | 14497 | 
| bsw/jbe@1309 | 14498       // Make sure our selection handler is ready | 
| bsw/jbe@1309 | 14499       this.selection = new wysihtml.Selection(this.parent, this.element, this.config.classNames.uneditableContainer); | 
| bsw/jbe@1309 | 14500 | 
| bsw/jbe@1309 | 14501       // Make sure commands dispatcher is ready | 
| bsw/jbe@1309 | 14502       this.commands  = new wysihtml.Commands(this.parent); | 
| bsw/jbe@1309 | 14503 | 
| bsw/jbe@1309 | 14504       if (!this.config.noTextarea) { | 
| bsw/jbe@1309 | 14505           dom.copyAttributes([ | 
| bsw/jbe@1309 | 14506               "className", "spellcheck", "title", "lang", "dir", "accessKey" | 
| bsw/jbe@1309 | 14507           ]).from(this.textarea.element).to(this.element); | 
| bsw/jbe@1309 | 14508       } | 
| bsw/jbe@1309 | 14509 | 
| bsw/jbe@1309 | 14510       this._initAutoLinking(); | 
| bsw/jbe@1309 | 14511 | 
| bsw/jbe@1309 | 14512       dom.addClass(this.element, this.config.classNames.composer); | 
| bsw/jbe@1309 | 14513       // | 
| bsw/jbe@1309 | 14514       // Make the editor look like the original textarea, by syncing styles | 
| bsw/jbe@1309 | 14515       if (this.config.style && !this.config.contentEditableMode) { | 
| bsw/jbe@1309 | 14516         this.style(); | 
| bsw/jbe@1309 | 14517       } | 
| bsw/jbe@1309 | 14518 | 
| bsw/jbe@1309 | 14519       this.observe(); | 
| bsw/jbe@1309 | 14520 | 
| bsw/jbe@1309 | 14521       var name = this.config.name; | 
| bsw/jbe@1309 | 14522       if (name) { | 
| bsw/jbe@1309 | 14523         dom.addClass(this.element, name); | 
| bsw/jbe@1309 | 14524         if (!this.config.contentEditableMode) { dom.addClass(this.editableArea, name); } | 
| bsw/jbe@1309 | 14525       } | 
| bsw/jbe@1309 | 14526 | 
| bsw/jbe@1309 | 14527       this.enable(); | 
| bsw/jbe@1309 | 14528 | 
| bsw/jbe@1309 | 14529       if (!this.config.noTextarea && this.textarea.element.disabled) { | 
| bsw/jbe@1309 | 14530         this.disable(); | 
| bsw/jbe@1309 | 14531       } | 
| bsw/jbe@1309 | 14532 | 
| bsw/jbe@1309 | 14533       // Simulate html5 placeholder attribute on contentEditable element | 
| bsw/jbe@1309 | 14534       var placeholderText = typeof(this.config.placeholder) === "string" | 
| bsw/jbe@1309 | 14535         ? this.config.placeholder | 
| bsw/jbe@1309 | 14536         : ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder")); | 
| bsw/jbe@1309 | 14537       if (placeholderText) { | 
| bsw/jbe@1309 | 14538         dom.simulatePlaceholder(this.parent, this, placeholderText, this.config.classNames.placeholder); | 
| bsw/jbe@1309 | 14539       } | 
| bsw/jbe@1309 | 14540 | 
| bsw/jbe@1309 | 14541       // Make sure that the browser avoids using inline styles whenever possible | 
| bsw/jbe@1309 | 14542       this.commands.exec("styleWithCSS", false); | 
| bsw/jbe@1309 | 14543 | 
| bsw/jbe@1309 | 14544       this._initObjectResizing(); | 
| bsw/jbe@1309 | 14545       this._initUndoManager(); | 
| bsw/jbe@1309 | 14546       this._initLineBreaking(); | 
| bsw/jbe@1309 | 14547 | 
| bsw/jbe@1309 | 14548       // Simulate html5 autofocus on contentEditable element | 
| bsw/jbe@1309 | 14549       // This doesn't work on IOS (5.1.1) | 
| bsw/jbe@1309 | 14550       if (!this.config.noTextarea && (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) && !browser.isIos()) { | 
| bsw/jbe@1309 | 14551         setTimeout(function() { that.focus(true); }, 100); | 
| bsw/jbe@1309 | 14552       } | 
| bsw/jbe@1309 | 14553 | 
| bsw/jbe@1309 | 14554       // IE sometimes leaves a single paragraph, which can't be removed by the user | 
| bsw/jbe@1309 | 14555       if (!browser.clearsContentEditableCorrectly()) { | 
| bsw/jbe@1309 | 14556         wysihtml.quirks.ensureProperClearing(this); | 
| bsw/jbe@1309 | 14557       } | 
| bsw/jbe@1309 | 14558 | 
| bsw/jbe@1309 | 14559       // Set up a sync that makes sure that textarea and editor have the same content | 
| bsw/jbe@1309 | 14560       if (this.initSync && this.config.sync) { | 
| bsw/jbe@1309 | 14561         this.initSync(); | 
| bsw/jbe@1309 | 14562       } | 
| bsw/jbe@1309 | 14563 | 
| bsw/jbe@1309 | 14564       // Okay hide the textarea, we are ready to go | 
| bsw/jbe@1309 | 14565       if (!this.config.noTextarea) { this.textarea.hide(); } | 
| bsw/jbe@1309 | 14566 | 
| bsw/jbe@1309 | 14567       // Fire global (before-)load event | 
| bsw/jbe@1309 | 14568       this.parent.fire("beforeload").fire("load"); | 
| bsw/jbe@1309 | 14569     }, | 
| bsw/jbe@1309 | 14570 | 
| bsw/jbe@1309 | 14571     _initAutoLinking: function() { | 
| bsw/jbe@1309 | 14572       var that                           = this, | 
| bsw/jbe@1309 | 14573           supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(), | 
| bsw/jbe@1309 | 14574           supportsAutoLinking            = browser.doesAutoLinkingInContentEditable(); | 
| bsw/jbe@1309 | 14575 | 
| bsw/jbe@1309 | 14576       if (supportsDisablingOfAutoLinking) { | 
| bsw/jbe@1309 | 14577         this.commands.exec("AutoUrlDetect", false, false); | 
| bsw/jbe@1309 | 14578       } | 
| bsw/jbe@1309 | 14579 | 
| bsw/jbe@1309 | 14580       if (!this.config.autoLink) { | 
| bsw/jbe@1309 | 14581         return; | 
| bsw/jbe@1309 | 14582       } | 
| bsw/jbe@1309 | 14583 | 
| bsw/jbe@1309 | 14584       // Only do the auto linking by ourselves when the browser doesn't support auto linking | 
| bsw/jbe@1309 | 14585       // OR when he supports auto linking but we were able to turn it off (IE9+) | 
| bsw/jbe@1309 | 14586       if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) { | 
| bsw/jbe@1309 | 14587         this.parent.on("newword:composer", function() { | 
| bsw/jbe@1309 | 14588           if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) { | 
| bsw/jbe@1309 | 14589             var nodeWithSelection = that.selection.getSelectedNode(), | 
| bsw/jbe@1309 | 14590                 uneditables = that.element.querySelectorAll("." + that.config.classNames.uneditableContainer), | 
| bsw/jbe@1309 | 14591                 isInUneditable = false; | 
| bsw/jbe@1309 | 14592 | 
| bsw/jbe@1309 | 14593             for (var i = uneditables.length; i--;) { | 
| bsw/jbe@1309 | 14594               if (wysihtml.dom.contains(uneditables[i], nodeWithSelection)) { | 
| bsw/jbe@1309 | 14595                 isInUneditable = true; | 
| bsw/jbe@1309 | 14596               } | 
| bsw/jbe@1309 | 14597             } | 
| bsw/jbe@1309 | 14598 | 
| bsw/jbe@1309 | 14599             if (!isInUneditable) dom.autoLink(nodeWithSelection, [that.config.classNames.uneditableContainer]); | 
| bsw/jbe@1309 | 14600           } | 
| bsw/jbe@1309 | 14601         }); | 
| bsw/jbe@1309 | 14602 | 
| bsw/jbe@1309 | 14603         dom.observe(this.element, "blur", function() { | 
| bsw/jbe@1309 | 14604           dom.autoLink(that.element, [that.config.classNames.uneditableContainer]); | 
| bsw/jbe@1309 | 14605         }); | 
| bsw/jbe@1309 | 14606       } | 
| bsw/jbe@1309 | 14607 | 
| bsw/jbe@1309 | 14608       // Assuming we have the following: | 
| bsw/jbe@1309 | 14609       //  <a href="http://www.google.de">http://www.google.de</a> | 
| bsw/jbe@1309 | 14610       // If a user now changes the url in the innerHTML we want to make sure that | 
| bsw/jbe@1309 | 14611       // it's synchronized with the href attribute (as long as the innerHTML is still a url) | 
| bsw/jbe@1309 | 14612       var // Use a live NodeList to check whether there are any links in the document | 
| bsw/jbe@1309 | 14613           links           = this.sandbox.getDocument().getElementsByTagName("a"), | 
| bsw/jbe@1309 | 14614           // The autoLink helper method reveals a reg exp to detect correct urls | 
| bsw/jbe@1309 | 14615           urlRegExp       = dom.autoLink.URL_REG_EXP, | 
| bsw/jbe@1309 | 14616           getTextContent  = function(element) { | 
| bsw/jbe@1309 | 14617             var textContent = wysihtml.lang.string(dom.getTextContent(element)).trim(); | 
| bsw/jbe@1309 | 14618             if (textContent.substr(0, 4) === "www.") { | 
| bsw/jbe@1309 | 14619               textContent = "http://" + textContent; | 
| bsw/jbe@1309 | 14620             } | 
| bsw/jbe@1309 | 14621             return textContent; | 
| bsw/jbe@1309 | 14622           }; | 
| bsw/jbe@1309 | 14623 | 
| bsw/jbe@1309 | 14624       dom.observe(this.element, "keydown", function(event) { | 
| bsw/jbe@1309 | 14625         if (!links.length) { | 
| bsw/jbe@1309 | 14626           return; | 
| bsw/jbe@1309 | 14627         } | 
| bsw/jbe@1309 | 14628 | 
| bsw/jbe@1309 | 14629         var selectedNode = that.selection.getSelectedNode(event.target.ownerDocument), | 
| bsw/jbe@1309 | 14630             link         = dom.getParentElement(selectedNode, { query: "a" }, 4), | 
| bsw/jbe@1309 | 14631             textContent; | 
| bsw/jbe@1309 | 14632 | 
| bsw/jbe@1309 | 14633         if (!link) { | 
| bsw/jbe@1309 | 14634           return; | 
| bsw/jbe@1309 | 14635         } | 
| bsw/jbe@1309 | 14636 | 
| bsw/jbe@1309 | 14637         textContent = getTextContent(link); | 
| bsw/jbe@1309 | 14638         // keydown is fired before the actual content is changed | 
| bsw/jbe@1309 | 14639         // therefore we set a timeout to change the href | 
| bsw/jbe@1309 | 14640         setTimeout(function() { | 
| bsw/jbe@1309 | 14641           var newTextContent = getTextContent(link); | 
| bsw/jbe@1309 | 14642           if (newTextContent === textContent) { | 
| bsw/jbe@1309 | 14643             return; | 
| bsw/jbe@1309 | 14644           } | 
| bsw/jbe@1309 | 14645 | 
| bsw/jbe@1309 | 14646           // Only set href when new href looks like a valid url | 
| bsw/jbe@1309 | 14647           if (newTextContent.match(urlRegExp)) { | 
| bsw/jbe@1309 | 14648             link.setAttribute("href", newTextContent); | 
| bsw/jbe@1309 | 14649           } | 
| bsw/jbe@1309 | 14650         }, 0); | 
| bsw/jbe@1309 | 14651       }); | 
| bsw/jbe@1309 | 14652     }, | 
| bsw/jbe@1309 | 14653 | 
| bsw/jbe@1309 | 14654     _initObjectResizing: function() { | 
| bsw/jbe@1309 | 14655       this.commands.exec("enableObjectResizing", true); | 
| bsw/jbe@1309 | 14656 | 
| bsw/jbe@1309 | 14657       // IE sets inline styles after resizing objects | 
| bsw/jbe@1309 | 14658       // The following lines make sure that the width/height css properties | 
| bsw/jbe@1309 | 14659       // are copied over to the width/height attributes | 
| bsw/jbe@1309 | 14660       if (browser.supportsEvent("resizeend")) { | 
| bsw/jbe@1309 | 14661         var properties        = ["width", "height"], | 
| bsw/jbe@1309 | 14662             propertiesLength  = properties.length, | 
| bsw/jbe@1309 | 14663             element           = this.element; | 
| bsw/jbe@1309 | 14664 | 
| bsw/jbe@1309 | 14665         dom.observe(element, "resizeend", function(event) { | 
| bsw/jbe@1309 | 14666           var target = event.target || event.srcElement, | 
| bsw/jbe@1309 | 14667               style  = target.style, | 
| bsw/jbe@1309 | 14668               i      = 0, | 
| bsw/jbe@1309 | 14669               property; | 
| bsw/jbe@1309 | 14670 | 
| bsw/jbe@1309 | 14671           if (target.nodeName !== "IMG") { | 
| bsw/jbe@1309 | 14672             return; | 
| bsw/jbe@1309 | 14673           } | 
| bsw/jbe@1309 | 14674 | 
| bsw/jbe@1309 | 14675           for (; i<propertiesLength; i++) { | 
| bsw/jbe@1309 | 14676             property = properties[i]; | 
| bsw/jbe@1309 | 14677             if (style[property]) { | 
| bsw/jbe@1309 | 14678               target.setAttribute(property, parseInt(style[property], 10)); | 
| bsw/jbe@1309 | 14679               style[property] = ""; | 
| bsw/jbe@1309 | 14680             } | 
| bsw/jbe@1309 | 14681           } | 
| bsw/jbe@1309 | 14682 | 
| bsw/jbe@1309 | 14683           // After resizing IE sometimes forgets to remove the old resize handles | 
| bsw/jbe@1309 | 14684           wysihtml.quirks.redraw(element); | 
| bsw/jbe@1309 | 14685         }); | 
| bsw/jbe@1309 | 14686       } | 
| bsw/jbe@1309 | 14687     }, | 
| bsw/jbe@1309 | 14688 | 
| bsw/jbe@1309 | 14689     _initUndoManager: function() { | 
| bsw/jbe@1309 | 14690       this.undoManager = new wysihtml.UndoManager(this.parent); | 
| bsw/jbe@1309 | 14691     }, | 
| bsw/jbe@1309 | 14692 | 
| bsw/jbe@1309 | 14693     _initLineBreaking: function() { | 
| bsw/jbe@1309 | 14694       var that                              = this, | 
| bsw/jbe@1309 | 14695           USE_NATIVE_LINE_BREAK_INSIDE_TAGS = "li, p, h1, h2, h3, h4, h5, h6", | 
| bsw/jbe@1309 | 14696           LIST_TAGS                         = "ul, ol, menu"; | 
| bsw/jbe@1309 | 14697 | 
| bsw/jbe@1309 | 14698       function adjust(selectedNode) { | 
| bsw/jbe@1309 | 14699         var parentElement = dom.getParentElement(selectedNode, { query: "p, div" }, 2); | 
| bsw/jbe@1309 | 14700         if (parentElement && dom.contains(that.element, parentElement)) { | 
| bsw/jbe@1309 | 14701           that.selection.executeAndRestoreRangy(function() { | 
| bsw/jbe@1309 | 14702             if (that.config.useLineBreaks) { | 
| bsw/jbe@1309 | 14703               if (!parentElement.firstChild || (parentElement.firstChild === parentElement.lastChild && parentElement.firstChild.nodeType === 1 && parentElement.firstChild.classList.contains('rangySelectionBoundary'))) { | 
| bsw/jbe@1309 | 14704                 parentElement.appendChild(that.doc.createElement('br')); | 
| bsw/jbe@1309 | 14705               } | 
| bsw/jbe@1309 | 14706               dom.replaceWithChildNodes(parentElement); | 
| bsw/jbe@1309 | 14707             } else if (parentElement.nodeName !== "P") { | 
| bsw/jbe@1309 | 14708               dom.renameElement(parentElement, "p"); | 
| bsw/jbe@1309 | 14709             } | 
| bsw/jbe@1309 | 14710           }); | 
| bsw/jbe@1309 | 14711         } | 
| bsw/jbe@1309 | 14712       } | 
| bsw/jbe@1309 | 14713 | 
| bsw/jbe@1309 | 14714       // Ensures when editor is empty and not line breaks mode, the inital state has a paragraph in it on focus with caret inside paragraph | 
| bsw/jbe@1309 | 14715       if (!this.config.useLineBreaks) { | 
| bsw/jbe@1309 | 14716         dom.observe(this.element, ["focus"], function() { | 
| bsw/jbe@1309 | 14717           if (that.isEmpty()) { | 
| bsw/jbe@1309 | 14718             setTimeout(function() { | 
| bsw/jbe@1309 | 14719               var paragraph = that.doc.createElement("P"); | 
| bsw/jbe@1309 | 14720               that.element.innerHTML = ""; | 
| bsw/jbe@1309 | 14721               that.element.appendChild(paragraph); | 
| bsw/jbe@1309 | 14722               if (!browser.displaysCaretInEmptyContentEditableCorrectly()) { | 
| bsw/jbe@1309 | 14723                 paragraph.innerHTML = "<br>"; | 
| bsw/jbe@1309 | 14724                 that.selection.setBefore(paragraph.firstChild); | 
| bsw/jbe@1309 | 14725               } else { | 
| bsw/jbe@1309 | 14726                 that.selection.selectNode(paragraph, true); | 
| bsw/jbe@1309 | 14727               } | 
| bsw/jbe@1309 | 14728             }, 0); | 
| bsw/jbe@1309 | 14729           } | 
| bsw/jbe@1309 | 14730         }); | 
| bsw/jbe@1309 | 14731       } | 
| bsw/jbe@1309 | 14732 | 
| bsw/jbe@1309 | 14733       dom.observe(this.element, "keydown", function(event) { | 
| bsw/jbe@1309 | 14734         var keyCode = event.keyCode; | 
| bsw/jbe@1309 | 14735 | 
| bsw/jbe@1309 | 14736         if (event.shiftKey || event.ctrlKey || event.defaultPrevented) { | 
| bsw/jbe@1309 | 14737           return; | 
| bsw/jbe@1309 | 14738         } | 
| bsw/jbe@1309 | 14739 | 
| bsw/jbe@1309 | 14740         if (keyCode !== wysihtml.ENTER_KEY && keyCode !== wysihtml.BACKSPACE_KEY) { | 
| bsw/jbe@1309 | 14741           return; | 
| bsw/jbe@1309 | 14742         } | 
| bsw/jbe@1309 | 14743         var blockElement = dom.getParentElement(that.selection.getSelectedNode(), { query: USE_NATIVE_LINE_BREAK_INSIDE_TAGS }, 4); | 
| bsw/jbe@1309 | 14744         if (blockElement) { | 
| bsw/jbe@1309 | 14745           setTimeout(function() { | 
| bsw/jbe@1309 | 14746             // Unwrap paragraph after leaving a list or a H1-6 | 
| bsw/jbe@1309 | 14747             var selectedNode = that.selection.getSelectedNode(), | 
| bsw/jbe@1309 | 14748                 list; | 
| bsw/jbe@1309 | 14749 | 
| bsw/jbe@1309 | 14750             if (blockElement.nodeName === "LI") { | 
| bsw/jbe@1309 | 14751               if (!selectedNode) { | 
| bsw/jbe@1309 | 14752                 return; | 
| bsw/jbe@1309 | 14753               } | 
| bsw/jbe@1309 | 14754 | 
| bsw/jbe@1309 | 14755               list = dom.getParentElement(selectedNode, { query: LIST_TAGS }, 2); | 
| bsw/jbe@1309 | 14756 | 
| bsw/jbe@1309 | 14757               if (!list) { | 
| bsw/jbe@1309 | 14758                 adjust(selectedNode); | 
| bsw/jbe@1309 | 14759               } | 
| bsw/jbe@1309 | 14760             } | 
| bsw/jbe@1309 | 14761 | 
| bsw/jbe@1309 | 14762             if (keyCode === wysihtml.ENTER_KEY && blockElement.nodeName.match(/^H[1-6]$/)) { | 
| bsw/jbe@1309 | 14763               adjust(selectedNode); | 
| bsw/jbe@1309 | 14764             } | 
| bsw/jbe@1309 | 14765           }, 0); | 
| bsw/jbe@1309 | 14766           return; | 
| bsw/jbe@1309 | 14767         } | 
| bsw/jbe@1309 | 14768         if (that.config.useLineBreaks && keyCode === wysihtml.ENTER_KEY && !wysihtml.browser.insertsLineBreaksOnReturn()) { | 
| bsw/jbe@1309 | 14769           event.preventDefault(); | 
| bsw/jbe@1309 | 14770           that.commands.exec("insertLineBreak"); | 
| bsw/jbe@1309 | 14771         } | 
| bsw/jbe@1309 | 14772       }); | 
| bsw/jbe@1309 | 14773     } | 
| bsw/jbe@1309 | 14774   }); | 
| bsw/jbe@1309 | 14775 })(wysihtml); | 
| bsw/jbe@1309 | 14776 | 
| bsw/jbe@1309 | 14777 (function(wysihtml) { | 
| bsw/jbe@1309 | 14778   var dom             = wysihtml.dom, | 
| bsw/jbe@1309 | 14779       doc             = document, | 
| bsw/jbe@1309 | 14780       win             = window, | 
| bsw/jbe@1309 | 14781       HOST_TEMPLATE   = doc.createElement("div"), | 
| bsw/jbe@1309 | 14782       /** | 
| bsw/jbe@1309 | 14783        * Styles to copy from textarea to the composer element | 
| bsw/jbe@1309 | 14784        */ | 
| bsw/jbe@1309 | 14785       TEXT_FORMATTING = [ | 
| bsw/jbe@1309 | 14786         "background-color", | 
| bsw/jbe@1309 | 14787         "color", "cursor", | 
| bsw/jbe@1309 | 14788         "font-family", "font-size", "font-style", "font-variant", "font-weight", | 
| bsw/jbe@1309 | 14789         "line-height", "letter-spacing", | 
| bsw/jbe@1309 | 14790         "text-align", "text-decoration", "text-indent", "text-rendering", | 
| bsw/jbe@1309 | 14791         "word-break", "word-wrap", "word-spacing" | 
| bsw/jbe@1309 | 14792       ], | 
| bsw/jbe@1309 | 14793       /** | 
| bsw/jbe@1309 | 14794        * Styles to copy from textarea to the iframe | 
| bsw/jbe@1309 | 14795        */ | 
| bsw/jbe@1309 | 14796       BOX_FORMATTING = [ | 
| bsw/jbe@1309 | 14797         "background-color", | 
| bsw/jbe@1309 | 14798         "border-collapse", | 
| bsw/jbe@1309 | 14799         "border-bottom-color", "border-bottom-style", "border-bottom-width", | 
| bsw/jbe@1309 | 14800         "border-left-color", "border-left-style", "border-left-width", | 
| bsw/jbe@1309 | 14801         "border-right-color", "border-right-style", "border-right-width", | 
| bsw/jbe@1309 | 14802         "border-top-color", "border-top-style", "border-top-width", | 
| bsw/jbe@1309 | 14803         "clear", "display", "float", | 
| bsw/jbe@1309 | 14804         "margin-bottom", "margin-left", "margin-right", "margin-top", | 
| bsw/jbe@1309 | 14805         "outline-color", "outline-offset", "outline-width", "outline-style", | 
| bsw/jbe@1309 | 14806         "padding-left", "padding-right", "padding-top", "padding-bottom", | 
| bsw/jbe@1309 | 14807         "position", "top", "left", "right", "bottom", "z-index", | 
| bsw/jbe@1309 | 14808         "vertical-align", "text-align", | 
| bsw/jbe@1309 | 14809         "-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing", | 
| bsw/jbe@1309 | 14810         "-webkit-box-shadow", "-moz-box-shadow", "-ms-box-shadow","box-shadow", | 
| bsw/jbe@1309 | 14811         "-webkit-border-top-right-radius", "-moz-border-radius-topright", "border-top-right-radius", | 
| bsw/jbe@1309 | 14812         "-webkit-border-bottom-right-radius", "-moz-border-radius-bottomright", "border-bottom-right-radius", | 
| bsw/jbe@1309 | 14813         "-webkit-border-bottom-left-radius", "-moz-border-radius-bottomleft", "border-bottom-left-radius", | 
| bsw/jbe@1309 | 14814         "-webkit-border-top-left-radius", "-moz-border-radius-topleft", "border-top-left-radius", | 
| bsw/jbe@1309 | 14815         "width", "height" | 
| bsw/jbe@1309 | 14816       ], | 
| bsw/jbe@1309 | 14817       ADDITIONAL_CSS_RULES = [ | 
| bsw/jbe@1309 | 14818         "html                 { height: 100%; }", | 
| bsw/jbe@1309 | 14819         "body                 { height: 100%; padding: 1px 0 0 0; margin: -1px 0 0 0; }", | 
| bsw/jbe@1309 | 14820         "body > p:first-child { margin-top: 0; }", | 
| bsw/jbe@1309 | 14821         "._wysihtml-temp     { display: none; }", | 
| bsw/jbe@1309 | 14822         wysihtml.browser.isGecko ? | 
| bsw/jbe@1309 | 14823           "body.placeholder { color: graytext !important; }" : | 
| bsw/jbe@1309 | 14824           "body.placeholder { color: #a9a9a9 !important; }", | 
| bsw/jbe@1309 | 14825         // Ensure that user see's broken images and can delete them | 
| bsw/jbe@1309 | 14826         "img:-moz-broken      { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }" | 
| bsw/jbe@1309 | 14827       ]; | 
| bsw/jbe@1309 | 14828 | 
| bsw/jbe@1309 | 14829   /** | 
| bsw/jbe@1309 | 14830    * With "setActive" IE offers a smart way of focusing elements without scrolling them into view: | 
| bsw/jbe@1309 | 14831    * http://msdn.microsoft.com/en-us/library/ms536738(v=vs.85).aspx | 
| bsw/jbe@1309 | 14832    * | 
| bsw/jbe@1309 | 14833    * Other browsers need a more hacky way: (pssst don't tell my mama) | 
| bsw/jbe@1309 | 14834    * In order to prevent the element being scrolled into view when focusing it, we simply | 
| bsw/jbe@1309 | 14835    * move it out of the scrollable area, focus it, and reset it's position | 
| bsw/jbe@1309 | 14836    */ | 
| bsw/jbe@1309 | 14837   var focusWithoutScrolling = function(element) { | 
| bsw/jbe@1309 | 14838     if (element.setActive) { | 
| bsw/jbe@1309 | 14839       // Following line could cause a js error when the textarea is invisible | 
| bsw/jbe@1309 | 14840       // See https://github.com/xing/wysihtml5/issues/9 | 
| bsw/jbe@1309 | 14841       try { element.setActive(); } catch(e) {} | 
| bsw/jbe@1309 | 14842     } else { | 
| bsw/jbe@1309 | 14843       var elementStyle = element.style, | 
| bsw/jbe@1309 | 14844           originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop, | 
| bsw/jbe@1309 | 14845           originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft, | 
| bsw/jbe@1309 | 14846           originalStyles = { | 
| bsw/jbe@1309 | 14847             position:         elementStyle.position, | 
| bsw/jbe@1309 | 14848             top:              elementStyle.top, | 
| bsw/jbe@1309 | 14849             left:             elementStyle.left, | 
| bsw/jbe@1309 | 14850             WebkitUserSelect: elementStyle.WebkitUserSelect | 
| bsw/jbe@1309 | 14851           }; | 
| bsw/jbe@1309 | 14852 | 
| bsw/jbe@1309 | 14853       dom.setStyles({ | 
| bsw/jbe@1309 | 14854         position:         "absolute", | 
| bsw/jbe@1309 | 14855         top:              "-99999px", | 
| bsw/jbe@1309 | 14856         left:             "-99999px", | 
| bsw/jbe@1309 | 14857         // Don't ask why but temporarily setting -webkit-user-select to none makes the whole thing performing smoother | 
| bsw/jbe@1309 | 14858         WebkitUserSelect: "none" | 
| bsw/jbe@1309 | 14859       }).on(element); | 
| bsw/jbe@1309 | 14860 | 
| bsw/jbe@1309 | 14861       element.focus(); | 
| bsw/jbe@1309 | 14862 | 
| bsw/jbe@1309 | 14863       dom.setStyles(originalStyles).on(element); | 
| bsw/jbe@1309 | 14864 | 
| bsw/jbe@1309 | 14865       if (win.scrollTo) { | 
| bsw/jbe@1309 | 14866         // Some browser extensions unset this method to prevent annoyances | 
| bsw/jbe@1309 | 14867         // "Better PopUp Blocker" for Chrome http://code.google.com/p/betterpopupblocker/source/browse/trunk/blockStart.js#100 | 
| bsw/jbe@1309 | 14868         // Issue: http://code.google.com/p/betterpopupblocker/issues/detail?id=1 | 
| bsw/jbe@1309 | 14869         win.scrollTo(originalScrollLeft, originalScrollTop); | 
| bsw/jbe@1309 | 14870       } | 
| bsw/jbe@1309 | 14871     } | 
| bsw/jbe@1309 | 14872   }; | 
| bsw/jbe@1309 | 14873 | 
| bsw/jbe@1309 | 14874 | 
| bsw/jbe@1309 | 14875   wysihtml.views.Composer.prototype.style = function() { | 
| bsw/jbe@1309 | 14876     var that                  = this, | 
| bsw/jbe@1309 | 14877         originalActiveElement = doc.querySelector(":focus"), | 
| bsw/jbe@1309 | 14878         textareaElement       = this.textarea.element, | 
| bsw/jbe@1309 | 14879         hasPlaceholder        = textareaElement.hasAttribute("placeholder"), | 
| bsw/jbe@1309 | 14880         originalPlaceholder   = hasPlaceholder && textareaElement.getAttribute("placeholder"), | 
| bsw/jbe@1309 | 14881         originalDisplayValue  = textareaElement.style.display, | 
| bsw/jbe@1309 | 14882         originalDisabled      = textareaElement.disabled, | 
| bsw/jbe@1309 | 14883         displayValueForCopying; | 
| bsw/jbe@1309 | 14884 | 
| bsw/jbe@1309 | 14885     this.focusStylesHost      = HOST_TEMPLATE.cloneNode(false); | 
| bsw/jbe@1309 | 14886     this.blurStylesHost       = HOST_TEMPLATE.cloneNode(false); | 
| bsw/jbe@1309 | 14887     this.disabledStylesHost   = HOST_TEMPLATE.cloneNode(false); | 
| bsw/jbe@1309 | 14888 | 
| bsw/jbe@1309 | 14889     // Remove placeholder before copying (as the placeholder has an affect on the computed style) | 
| bsw/jbe@1309 | 14890     if (hasPlaceholder) { | 
| bsw/jbe@1309 | 14891       textareaElement.removeAttribute("placeholder"); | 
| bsw/jbe@1309 | 14892     } | 
| bsw/jbe@1309 | 14893 | 
| bsw/jbe@1309 | 14894     if (textareaElement === originalActiveElement) { | 
| bsw/jbe@1309 | 14895       textareaElement.blur(); | 
| bsw/jbe@1309 | 14896     } | 
| bsw/jbe@1309 | 14897 | 
| bsw/jbe@1309 | 14898     // enable for copying styles | 
| bsw/jbe@1309 | 14899     textareaElement.disabled = false; | 
| bsw/jbe@1309 | 14900 | 
| bsw/jbe@1309 | 14901     // set textarea to display="none" to get cascaded styles via getComputedStyle | 
| bsw/jbe@1309 | 14902     textareaElement.style.display = displayValueForCopying = "none"; | 
| bsw/jbe@1309 | 14903 | 
| bsw/jbe@1309 | 14904     if ((textareaElement.getAttribute("rows") && dom.getStyle("height").from(textareaElement) === "auto") || | 
| bsw/jbe@1309 | 14905         (textareaElement.getAttribute("cols") && dom.getStyle("width").from(textareaElement) === "auto")) { | 
| bsw/jbe@1309 | 14906       textareaElement.style.display = displayValueForCopying = originalDisplayValue; | 
| bsw/jbe@1309 | 14907     } | 
| bsw/jbe@1309 | 14908 | 
| bsw/jbe@1309 | 14909     // --------- iframe styles (has to be set before editor styles, otherwise IE9 sets wrong fontFamily on blurStylesHost) --------- | 
| bsw/jbe@1309 | 14910     dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.editableArea).andTo(this.blurStylesHost); | 
| bsw/jbe@1309 | 14911 | 
| bsw/jbe@1309 | 14912     // --------- editor styles --------- | 
| bsw/jbe@1309 | 14913     dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.element).andTo(this.blurStylesHost); | 
| bsw/jbe@1309 | 14914 | 
| bsw/jbe@1309 | 14915     // --------- apply standard rules --------- | 
| bsw/jbe@1309 | 14916     dom.insertCSS(ADDITIONAL_CSS_RULES).into(this.element.ownerDocument); | 
| bsw/jbe@1309 | 14917 | 
| bsw/jbe@1309 | 14918     // --------- :disabled styles --------- | 
| bsw/jbe@1309 | 14919     textareaElement.disabled = true; | 
| bsw/jbe@1309 | 14920     dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.disabledStylesHost); | 
| bsw/jbe@1309 | 14921     dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.disabledStylesHost); | 
| bsw/jbe@1309 | 14922     textareaElement.disabled = originalDisabled; | 
| bsw/jbe@1309 | 14923 | 
| bsw/jbe@1309 | 14924     // --------- :focus styles --------- | 
| bsw/jbe@1309 | 14925     textareaElement.style.display = originalDisplayValue; | 
| bsw/jbe@1309 | 14926     focusWithoutScrolling(textareaElement); | 
| bsw/jbe@1309 | 14927     textareaElement.style.display = displayValueForCopying; | 
| bsw/jbe@1309 | 14928 | 
| bsw/jbe@1309 | 14929     dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.focusStylesHost); | 
| bsw/jbe@1309 | 14930     dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.focusStylesHost); | 
| bsw/jbe@1309 | 14931 | 
| bsw/jbe@1309 | 14932     // reset textarea | 
| bsw/jbe@1309 | 14933     textareaElement.style.display = originalDisplayValue; | 
| bsw/jbe@1309 | 14934 | 
| bsw/jbe@1309 | 14935     dom.copyStyles(["display"]).from(textareaElement).to(this.editableArea); | 
| bsw/jbe@1309 | 14936 | 
| bsw/jbe@1309 | 14937     // Make sure that we don't change the display style of the iframe when copying styles oblur/onfocus | 
| bsw/jbe@1309 | 14938     // this is needed for when the change_view event is fired where the iframe is hidden and then | 
| bsw/jbe@1309 | 14939     // the blur event fires and re-displays it | 
| bsw/jbe@1309 | 14940     var boxFormattingStyles = wysihtml.lang.array(BOX_FORMATTING).without(["display"]); | 
| bsw/jbe@1309 | 14941 | 
| bsw/jbe@1309 | 14942     // --------- restore focus --------- | 
| bsw/jbe@1309 | 14943     if (originalActiveElement) { | 
| bsw/jbe@1309 | 14944       focusWithoutScrolling(originalActiveElement); | 
| bsw/jbe@1309 | 14945     } else { | 
| bsw/jbe@1309 | 14946       textareaElement.blur(); | 
| bsw/jbe@1309 | 14947     } | 
| bsw/jbe@1309 | 14948 | 
| bsw/jbe@1309 | 14949     // --------- restore placeholder --------- | 
| bsw/jbe@1309 | 14950     if (hasPlaceholder) { | 
| bsw/jbe@1309 | 14951       textareaElement.setAttribute("placeholder", originalPlaceholder); | 
| bsw/jbe@1309 | 14952     } | 
| bsw/jbe@1309 | 14953 | 
| bsw/jbe@1309 | 14954     // --------- Sync focus/blur styles --------- | 
| bsw/jbe@1309 | 14955     this.parent.on("focus:composer", function() { | 
| bsw/jbe@1309 | 14956       dom.copyStyles(boxFormattingStyles) .from(that.focusStylesHost).to(that.editableArea); | 
| bsw/jbe@1309 | 14957       dom.copyStyles(TEXT_FORMATTING)     .from(that.focusStylesHost).to(that.element); | 
| bsw/jbe@1309 | 14958     }); | 
| bsw/jbe@1309 | 14959 | 
| bsw/jbe@1309 | 14960     this.parent.on("blur:composer", function() { | 
| bsw/jbe@1309 | 14961       dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea); | 
| bsw/jbe@1309 | 14962       dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element); | 
| bsw/jbe@1309 | 14963     }); | 
| bsw/jbe@1309 | 14964 | 
| bsw/jbe@1309 | 14965     this.parent.observe("disable:composer", function() { | 
| bsw/jbe@1309 | 14966       dom.copyStyles(boxFormattingStyles) .from(that.disabledStylesHost).to(that.editableArea); | 
| bsw/jbe@1309 | 14967       dom.copyStyles(TEXT_FORMATTING)     .from(that.disabledStylesHost).to(that.element); | 
| bsw/jbe@1309 | 14968     }); | 
| bsw/jbe@1309 | 14969 | 
| bsw/jbe@1309 | 14970     this.parent.observe("enable:composer", function() { | 
| bsw/jbe@1309 | 14971       dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea); | 
| bsw/jbe@1309 | 14972       dom.copyStyles(TEXT_FORMATTING)     .from(that.blurStylesHost).to(that.element); | 
| bsw/jbe@1309 | 14973     }); | 
| bsw/jbe@1309 | 14974 | 
| bsw/jbe@1309 | 14975     return this; | 
| bsw/jbe@1309 | 14976   }; | 
| bsw/jbe@1309 | 14977 })(wysihtml); | 
| bsw/jbe@1309 | 14978 | 
| bsw/jbe@1309 | 14979 /** | 
| bsw/jbe@1309 | 14980  * Taking care of events | 
| bsw/jbe@1309 | 14981  *  - Simulating 'change' event on contentEditable element | 
| bsw/jbe@1309 | 14982  *  - Handling drag & drop logic | 
| bsw/jbe@1309 | 14983  *  - Catch paste events | 
| bsw/jbe@1309 | 14984  *  - Dispatch proprietary newword:composer event | 
| bsw/jbe@1309 | 14985  *  - Keyboard shortcuts | 
| bsw/jbe@1309 | 14986  */ | 
| bsw/jbe@1309 | 14987 (function(wysihtml) { | 
| bsw/jbe@1309 | 14988   var dom       = wysihtml.dom, | 
| bsw/jbe@1309 | 14989       domNode = dom.domNode, | 
| bsw/jbe@1309 | 14990       browser   = wysihtml.browser, | 
| bsw/jbe@1309 | 14991       /** | 
| bsw/jbe@1309 | 14992        * Map keyCodes to query commands | 
| bsw/jbe@1309 | 14993        */ | 
| bsw/jbe@1309 | 14994       shortcuts = { | 
| bsw/jbe@1309 | 14995         "66": "bold",     // B | 
| bsw/jbe@1309 | 14996         "73": "italic",   // I | 
| bsw/jbe@1309 | 14997         "85": "underline" // U | 
| bsw/jbe@1309 | 14998       }; | 
| bsw/jbe@1309 | 14999 | 
| bsw/jbe@1309 | 15000   var actions = { | 
| bsw/jbe@1309 | 15001 | 
| bsw/jbe@1309 | 15002     // Adds multiple eventlisteners to target, bound to one callback | 
| bsw/jbe@1309 | 15003     // TODO: If needed elsewhere make it part of wysihtml.dom or sth | 
| bsw/jbe@1309 | 15004     addListeners: function (target, events, callback) { | 
| bsw/jbe@1309 | 15005       for(var i = 0, max = events.length; i < max; i++) { | 
| bsw/jbe@1309 | 15006         target.addEventListener(events[i], callback, false); | 
| bsw/jbe@1309 | 15007       } | 
| bsw/jbe@1309 | 15008     }, | 
| bsw/jbe@1309 | 15009 | 
| bsw/jbe@1309 | 15010     // Removes multiple eventlisteners from target, bound to one callback | 
| bsw/jbe@1309 | 15011     // TODO: If needed elsewhere make it part of wysihtml.dom or sth | 
| bsw/jbe@1309 | 15012     removeListeners: function (target, events, callback) { | 
| bsw/jbe@1309 | 15013       for(var i = 0, max = events.length; i < max; i++) { | 
| bsw/jbe@1309 | 15014         target.removeEventListener(events[i], callback, false); | 
| bsw/jbe@1309 | 15015       } | 
| bsw/jbe@1309 | 15016     }, | 
| bsw/jbe@1309 | 15017 | 
| bsw/jbe@1309 | 15018     // Override for giving user ability to delete last line break in table cell | 
| bsw/jbe@1309 | 15019     fixLastBrDeletionInTable: function(composer, force) { | 
| bsw/jbe@1309 | 15020       if (composer.selection.caretIsInTheEndOfNode()) { | 
| bsw/jbe@1309 | 15021         var sel = composer.selection.getSelection(), | 
| bsw/jbe@1309 | 15022             aNode = sel.anchorNode; | 
| bsw/jbe@1309 | 15023         if (aNode && aNode.nodeType === 1 && (wysihtml.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) { | 
| bsw/jbe@1309 | 15024           var nextNode = aNode.childNodes[sel.anchorOffset]; | 
| bsw/jbe@1309 | 15025           if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") { | 
| bsw/jbe@1309 | 15026             nextNode.parentNode.removeChild(nextNode); | 
| bsw/jbe@1309 | 15027             return true; | 
| bsw/jbe@1309 | 15028           } | 
| bsw/jbe@1309 | 15029         } | 
| bsw/jbe@1309 | 15030       } | 
| bsw/jbe@1309 | 15031       return false; | 
| bsw/jbe@1309 | 15032     }, | 
| bsw/jbe@1309 | 15033 | 
| bsw/jbe@1309 | 15034     // If found an uneditable before caret then notify it before deletion | 
| bsw/jbe@1309 | 15035     handleUneditableDeletion: function(composer) { | 
| bsw/jbe@1309 | 15036       var before = composer.selection.getBeforeSelection(true); | 
| bsw/jbe@1309 | 15037       if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer)) { | 
| bsw/jbe@1309 | 15038         if (actions.fixLastBrDeletionInTable(composer, true)) { | 
| bsw/jbe@1309 | 15039           return true; | 
| bsw/jbe@1309 | 15040         } | 
| bsw/jbe@1309 | 15041         try { | 
| bsw/jbe@1309 | 15042           var ev = new CustomEvent("wysihtml:uneditable:delete", {bubbles: true, cancelable: false}); | 
| bsw/jbe@1309 | 15043           before.node.dispatchEvent(ev); | 
| bsw/jbe@1309 | 15044         } catch (err) {} | 
| bsw/jbe@1309 | 15045         before.node.parentNode.removeChild(before.node); | 
| bsw/jbe@1309 | 15046         return true; | 
| bsw/jbe@1309 | 15047       } | 
| bsw/jbe@1309 | 15048       return false; | 
| bsw/jbe@1309 | 15049     }, | 
| bsw/jbe@1309 | 15050 | 
| bsw/jbe@1309 | 15051     // Deletion with caret in the beginning of headings and other block elvel elements needs special attention | 
| bsw/jbe@1309 | 15052     // Not allways does it concate text to previous block node correctly (browsers do unexpected miracles here especially webkit) | 
| bsw/jbe@1309 | 15053     fixDeleteInTheBeginningOfBlock: function(composer) { | 
| bsw/jbe@1309 | 15054       var selection = composer.selection, | 
| bsw/jbe@1309 | 15055           prevNode = selection.getPreviousNode(); | 
| bsw/jbe@1309 | 15056 | 
| bsw/jbe@1309 | 15057       if (selection.caretIsFirstInSelection(wysihtml.browser.usesControlRanges()) && prevNode) { | 
| bsw/jbe@1309 | 15058         if (prevNode.nodeType === 1 && | 
| bsw/jbe@1309 | 15059             wysihtml.dom.domNode(prevNode).is.block() && | 
| bsw/jbe@1309 | 15060             !domNode(prevNode).test({ | 
| bsw/jbe@1309 | 15061               query: "ol, ul, table, tr, dl" | 
| bsw/jbe@1309 | 15062             }) | 
| bsw/jbe@1309 | 15063         ) { | 
| bsw/jbe@1309 | 15064           if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) { | 
| bsw/jbe@1309 | 15065             // If heading is empty remove the heading node | 
| bsw/jbe@1309 | 15066             prevNode.parentNode.removeChild(prevNode); | 
| bsw/jbe@1309 | 15067             return true; | 
| bsw/jbe@1309 | 15068           } else { | 
| bsw/jbe@1309 | 15069             if (prevNode.lastChild) { | 
| bsw/jbe@1309 | 15070               var selNode = prevNode.lastChild, | 
| bsw/jbe@1309 | 15071                   selectedNode = selection.getSelectedNode(), | 
| bsw/jbe@1309 | 15072                   commonAncestorNode = domNode(prevNode).commonAncestor(selectedNode, composer.element), | 
| bsw/jbe@1309 | 15073                   curNode = wysihtml.dom.getParentElement(selectedNode, { | 
| bsw/jbe@1309 | 15074                     query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote" | 
| bsw/jbe@1309 | 15075                   }, false, commonAncestorNode || composer.element); | 
| bsw/jbe@1309 | 15076 | 
| bsw/jbe@1309 | 15077               if (curNode) { | 
| bsw/jbe@1309 | 15078                 domNode(curNode).transferContentTo(prevNode, true); | 
| bsw/jbe@1309 | 15079                 selection.setAfter(selNode); | 
| bsw/jbe@1309 | 15080                 return true; | 
| bsw/jbe@1309 | 15081               } else if (wysihtml.browser.usesControlRanges()) { | 
| bsw/jbe@1309 | 15082                 selectedNode = selection.getCaretNode(); | 
| bsw/jbe@1309 | 15083                 domNode(selectedNode).transferContentTo(prevNode, true); | 
| bsw/jbe@1309 | 15084                 selection.setAfter(selNode); | 
| bsw/jbe@1309 | 15085                 return true; | 
| bsw/jbe@1309 | 15086               } | 
| bsw/jbe@1309 | 15087             } | 
| bsw/jbe@1309 | 15088           } | 
| bsw/jbe@1309 | 15089         } | 
| bsw/jbe@1309 | 15090       } | 
| bsw/jbe@1309 | 15091       return false; | 
| bsw/jbe@1309 | 15092     }, | 
| bsw/jbe@1309 | 15093 | 
| bsw/jbe@1309 | 15094     /* In IE when deleting with caret at the begining of LI, list gets broken into half instead of merging the LI with previous */ | 
| bsw/jbe@1309 | 15095     /* This does not match other browsers an is less intuitive from UI standpoint, thus has to be fixed */ | 
| bsw/jbe@1309 | 15096     fixDeleteInTheBeginningOfLi: function(composer) { | 
| bsw/jbe@1309 | 15097       if (wysihtml.browser.hasLiDeletingProblem()) { | 
| bsw/jbe@1309 | 15098         var selection = composer.selection.getSelection(), | 
| bsw/jbe@1309 | 15099             aNode = selection.anchorNode, | 
| bsw/jbe@1309 | 15100             listNode, prevNode, firstNode, | 
| bsw/jbe@1309 | 15101             isInBeginnig = composer.selection.caretIsFirstInSelection(), | 
| bsw/jbe@1309 | 15102             prevNode, | 
| bsw/jbe@1309 | 15103             intermediaryNode; | 
| bsw/jbe@1309 | 15104 | 
| bsw/jbe@1309 | 15105         // Fix caret at the beginnig of first textNode in LI | 
| bsw/jbe@1309 | 15106         if (aNode.nodeType === 3 && selection.anchorOffset === 0 && aNode === aNode.parentNode.firstChild) { | 
| bsw/jbe@1309 | 15107           aNode = aNode.parentNode; | 
| bsw/jbe@1309 | 15108           isInBeginnig = true; | 
| bsw/jbe@1309 | 15109         } | 
| bsw/jbe@1309 | 15110 | 
| bsw/jbe@1309 | 15111         if (isInBeginnig && aNode && aNode.nodeType === 1 && aNode.nodeName === "LI") { | 
| bsw/jbe@1309 | 15112           prevNode = domNode(aNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 15113           if (!prevNode && aNode.parentNode && (aNode.parentNode.nodeName === "UL" || aNode.parentNode.nodeName === "OL")) { | 
| bsw/jbe@1309 | 15114             prevNode = domNode(aNode.parentNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); | 
| bsw/jbe@1309 | 15115             intermediaryNode = aNode.parentNode; | 
| bsw/jbe@1309 | 15116           } | 
| bsw/jbe@1309 | 15117           if (prevNode) { | 
| bsw/jbe@1309 | 15118             firstNode = aNode.firstChild; | 
| bsw/jbe@1309 | 15119             domNode(aNode).transferContentTo(prevNode, true); | 
| bsw/jbe@1309 | 15120 | 
| bsw/jbe@1309 | 15121             if (intermediaryNode && intermediaryNode.children.length === 0){ | 
| bsw/jbe@1309 | 15122               intermediaryNode.remove(); | 
| bsw/jbe@1309 | 15123             } | 
| bsw/jbe@1309 | 15124 | 
| bsw/jbe@1309 | 15125             if (firstNode) { | 
| bsw/jbe@1309 | 15126               composer.selection.setBefore(firstNode); | 
| bsw/jbe@1309 | 15127             } else if (prevNode) { | 
| bsw/jbe@1309 | 15128               if (prevNode.nodeType === 1) { | 
| bsw/jbe@1309 | 15129                 if (prevNode.lastChild) { | 
| bsw/jbe@1309 | 15130                   composer.selection.setAfter(prevNode.lastChild); | 
| bsw/jbe@1309 | 15131                 } else { | 
| bsw/jbe@1309 | 15132                   composer.selection.selectNode(prevNode); | 
| bsw/jbe@1309 | 15133                 } | 
| bsw/jbe@1309 | 15134               } else { | 
| bsw/jbe@1309 | 15135                 composer.selection.setAfter(prevNode); | 
| bsw/jbe@1309 | 15136               } | 
| bsw/jbe@1309 | 15137             } | 
| bsw/jbe@1309 | 15138             return true; | 
| bsw/jbe@1309 | 15139           } | 
| bsw/jbe@1309 | 15140         } | 
| bsw/jbe@1309 | 15141       } | 
| bsw/jbe@1309 | 15142       return false; | 
| bsw/jbe@1309 | 15143     }, | 
| bsw/jbe@1309 | 15144 | 
| bsw/jbe@1309 | 15145     fixDeleteInTheBeginningOfControlSelection: function(composer) { | 
| bsw/jbe@1309 | 15146       var selection = composer.selection, | 
| bsw/jbe@1309 | 15147           prevNode = selection.getPreviousNode(), | 
| bsw/jbe@1309 | 15148           selectedNode = selection.getSelectedNode(), | 
| bsw/jbe@1309 | 15149           afterCaretNode; | 
| bsw/jbe@1309 | 15150 | 
| bsw/jbe@1309 | 15151       if (selection.caretIsFirstInSelection()) { | 
| bsw/jbe@1309 | 15152         if (selectedNode.nodeType === 3) { | 
| bsw/jbe@1309 | 15153           selectedNode = selectedNode.parentNode; | 
| bsw/jbe@1309 | 15154         } | 
| bsw/jbe@1309 | 15155         afterCaretNode = selectedNode.firstChild; | 
| bsw/jbe@1309 | 15156         domNode(selectedNode).transferContentTo(prevNode, true); | 
| bsw/jbe@1309 | 15157         if (afterCaretNode) { | 
| bsw/jbe@1309 | 15158           composer.selection.setBefore(afterCaretNode); | 
| bsw/jbe@1309 | 15159         } | 
| bsw/jbe@1309 | 15160         return true; | 
| bsw/jbe@1309 | 15161       } | 
| bsw/jbe@1309 | 15162       return false; | 
| bsw/jbe@1309 | 15163     }, | 
| bsw/jbe@1309 | 15164 | 
| bsw/jbe@1309 | 15165     // Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature) | 
| bsw/jbe@1309 | 15166     // Returns true if some corrections is applied so events know when to prevent default | 
| bsw/jbe@1309 | 15167     doLineBreaksModeEnterWithCaret: function(composer) { | 
| bsw/jbe@1309 | 15168       var breakNodes = "p, pre, div, blockquote", | 
| bsw/jbe@1309 | 15169           caretInfo, parent, txtNode, | 
| bsw/jbe@1309 | 15170           ret = false; | 
| bsw/jbe@1309 | 15171 | 
| bsw/jbe@1309 | 15172       caretInfo = composer.selection.getNodesNearCaret(); | 
| bsw/jbe@1309 | 15173       if (caretInfo) { | 
| bsw/jbe@1309 | 15174 | 
| bsw/jbe@1309 | 15175         if (caretInfo.caretNode || caretInfo.nextNode) { | 
| bsw/jbe@1309 | 15176           parent = dom.getParentElement(caretInfo.caretNode || caretInfo.nextNode, { query: breakNodes }, 2); | 
| bsw/jbe@1309 | 15177           if (parent === composer.element) { | 
| bsw/jbe@1309 | 15178             parent = undefined; | 
| bsw/jbe@1309 | 15179           } | 
| bsw/jbe@1309 | 15180         } | 
| bsw/jbe@1309 | 15181 | 
| bsw/jbe@1309 | 15182         if (parent && caretInfo.caretNode) { | 
| bsw/jbe@1309 | 15183           if (domNode(caretInfo.caretNode).is.lineBreak()) { | 
| bsw/jbe@1309 | 15184 | 
| bsw/jbe@1309 | 15185             if (composer.config.doubleLineBreakEscapesBlock) { | 
| bsw/jbe@1309 | 15186               // Double enter (enter on blank line) exits block element in useLineBreaks mode. | 
| bsw/jbe@1309 | 15187               ret = true; | 
| bsw/jbe@1309 | 15188               caretInfo.caretNode.parentNode.removeChild(caretInfo.caretNode); | 
| bsw/jbe@1309 | 15189 | 
| bsw/jbe@1309 | 15190               // Ensure surplous line breaks are not added to preceding element | 
| bsw/jbe@1309 | 15191               if (domNode(caretInfo.nextNode).is.lineBreak()) { | 
| bsw/jbe@1309 | 15192                 caretInfo.nextNode.parentNode.removeChild(caretInfo.nextNode); | 
| bsw/jbe@1309 | 15193               } | 
| bsw/jbe@1309 | 15194 | 
| bsw/jbe@1309 | 15195               var brNode = composer.doc.createElement('br'); | 
| bsw/jbe@1309 | 15196               if (domNode(caretInfo.nextNode).is.lineBreak() && caretInfo.nextNode === parent.lastChild) { | 
| bsw/jbe@1309 | 15197                 parent.parentNode.insertBefore(brNode, parent.nextSibling); | 
| bsw/jbe@1309 | 15198               } else { | 
| bsw/jbe@1309 | 15199                 composer.selection.splitElementAtCaret(parent, brNode); | 
| bsw/jbe@1309 | 15200               } | 
| bsw/jbe@1309 | 15201 | 
| bsw/jbe@1309 | 15202               // Ensure surplous blank lines are not added to preceding element | 
| bsw/jbe@1309 | 15203               if (caretInfo.nextNode && caretInfo.nextNode.nodeType === 3) { | 
| bsw/jbe@1309 | 15204                 // Replaces blank lines at the beginning of textnode | 
| bsw/jbe@1309 | 15205                 caretInfo.nextNode.data = caretInfo.nextNode.data.replace(/^ *[\r\n]+/, ''); | 
| bsw/jbe@1309 | 15206               } | 
| bsw/jbe@1309 | 15207               composer.selection.setBefore(brNode); | 
| bsw/jbe@1309 | 15208             } | 
| bsw/jbe@1309 | 15209 | 
| bsw/jbe@1309 | 15210           } else if (caretInfo.caretNode.nodeType === 3 && wysihtml.browser.hasCaretBlockElementIssue() && caretInfo.textOffset === caretInfo.caretNode.data.length && !caretInfo.nextNode) { | 
| bsw/jbe@1309 | 15211 | 
| bsw/jbe@1309 | 15212             // This fixes annoying webkit issue when you press enter at the end of a block then seemingly nothing happens. | 
| bsw/jbe@1309 | 15213             // in reality one line break is generated and cursor is reported after it, but when entering something cursor jumps before the br | 
| bsw/jbe@1309 | 15214             ret = true; | 
| bsw/jbe@1309 | 15215             var br1 = composer.doc.createElement('br'), | 
| bsw/jbe@1309 | 15216                 br2 = composer.doc.createElement('br'), | 
| bsw/jbe@1309 | 15217                 f = composer.doc.createDocumentFragment(); | 
| bsw/jbe@1309 | 15218             f.appendChild(br1); | 
| bsw/jbe@1309 | 15219             f.appendChild(br2); | 
| bsw/jbe@1309 | 15220             composer.selection.insertNode(f); | 
| bsw/jbe@1309 | 15221             composer.selection.setBefore(br2); | 
| bsw/jbe@1309 | 15222 | 
| bsw/jbe@1309 | 15223           } | 
| bsw/jbe@1309 | 15224         } | 
| bsw/jbe@1309 | 15225       } | 
| bsw/jbe@1309 | 15226       return ret; | 
| bsw/jbe@1309 | 15227     } | 
| bsw/jbe@1309 | 15228   }; | 
| bsw/jbe@1309 | 15229 | 
| bsw/jbe@1309 | 15230   var handleDeleteKeyPress = function(event, composer) { | 
| bsw/jbe@1309 | 15231     var selection = composer.selection, | 
| bsw/jbe@1309 | 15232         element = composer.element; | 
| bsw/jbe@1309 | 15233 | 
| bsw/jbe@1309 | 15234     if (selection.isCollapsed()) { | 
| bsw/jbe@1309 | 15235       /** | 
| bsw/jbe@1309 | 15236        * when the editor is empty in useLineBreaks = false mode, preserve | 
| bsw/jbe@1309 | 15237        * the default value in it which is <p><br></p> | 
| bsw/jbe@1309 | 15238        */ | 
| bsw/jbe@1309 | 15239       if (composer.isEmpty() && !composer.config.useLineBreaks) { | 
| bsw/jbe@1309 | 15240         event.preventDefault(); | 
| bsw/jbe@1309 | 15241         return; | 
| bsw/jbe@1309 | 15242       } | 
| bsw/jbe@1309 | 15243       if (actions.handleUneditableDeletion(composer)) { | 
| bsw/jbe@1309 | 15244         event.preventDefault(); | 
| bsw/jbe@1309 | 15245         return; | 
| bsw/jbe@1309 | 15246       } | 
| bsw/jbe@1309 | 15247       if (actions.fixDeleteInTheBeginningOfLi(composer)) { | 
| bsw/jbe@1309 | 15248         event.preventDefault(); | 
| bsw/jbe@1309 | 15249         return; | 
| bsw/jbe@1309 | 15250       } | 
| bsw/jbe@1309 | 15251       if (actions.fixDeleteInTheBeginningOfBlock(composer)) { | 
| bsw/jbe@1309 | 15252         event.preventDefault(); | 
| bsw/jbe@1309 | 15253         return; | 
| bsw/jbe@1309 | 15254       } | 
| bsw/jbe@1309 | 15255       if (actions.fixLastBrDeletionInTable(composer)) { | 
| bsw/jbe@1309 | 15256         event.preventDefault(); | 
| bsw/jbe@1309 | 15257         return; | 
| bsw/jbe@1309 | 15258       } | 
| bsw/jbe@1309 | 15259       if (wysihtml.browser.usesControlRanges()) { | 
| bsw/jbe@1309 | 15260         if (actions.fixDeleteInTheBeginningOfControlSelection(composer)) { | 
| bsw/jbe@1309 | 15261           event.preventDefault(); | 
| bsw/jbe@1309 | 15262           return; | 
| bsw/jbe@1309 | 15263         } | 
| bsw/jbe@1309 | 15264       } | 
| bsw/jbe@1309 | 15265     } else { | 
| bsw/jbe@1309 | 15266       if (selection.containsUneditable()) { | 
| bsw/jbe@1309 | 15267         event.preventDefault(); | 
| bsw/jbe@1309 | 15268         selection.deleteContents(); | 
| bsw/jbe@1309 | 15269       } | 
| bsw/jbe@1309 | 15270     } | 
| bsw/jbe@1309 | 15271   }; | 
| bsw/jbe@1309 | 15272 | 
| bsw/jbe@1309 | 15273   var handleEnterKeyPress = function(event, composer) { | 
| bsw/jbe@1309 | 15274     if (composer.config.useLineBreaks && !event.shiftKey && !event.ctrlKey) { | 
| bsw/jbe@1309 | 15275       // Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature) | 
| bsw/jbe@1309 | 15276 | 
| bsw/jbe@1309 | 15277       var breakNodes = "p, pre, div, blockquote", | 
| bsw/jbe@1309 | 15278           caretInfo, parent, txtNode; | 
| bsw/jbe@1309 | 15279 | 
| bsw/jbe@1309 | 15280       if (composer.selection.isCollapsed()) { | 
| bsw/jbe@1309 | 15281         if (actions.doLineBreaksModeEnterWithCaret(composer)) { | 
| bsw/jbe@1309 | 15282           event.preventDefault(); | 
| bsw/jbe@1309 | 15283         } | 
| bsw/jbe@1309 | 15284       } | 
| bsw/jbe@1309 | 15285     } | 
| bsw/jbe@1309 | 15286 | 
| bsw/jbe@1309 | 15287     if (browser.hasCaretAtLinkEndInsertionProblems() && composer.selection.caretIsInTheEndOfNode()) { | 
| bsw/jbe@1309 | 15288       var target = composer.selection.getSelectedNode(true), | 
| bsw/jbe@1309 | 15289           targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element | 
| bsw/jbe@1309 | 15290           invisibleSpace, space; | 
| bsw/jbe@1309 | 15291 | 
| bsw/jbe@1309 | 15292       if (targetEl && targetEl.closest('a') && target.nodeType === 3 && target === targetEl.lastChild) { | 
| bsw/jbe@1309 | 15293         // Seems like enter was pressed and caret was at the end of link node | 
| bsw/jbe@1309 | 15294         // This means user wants to escape the link now (caret is last in link node too). | 
| bsw/jbe@1309 | 15295         composer.selection.setAfter(targetEl); | 
| bsw/jbe@1309 | 15296       } | 
| bsw/jbe@1309 | 15297     } | 
| bsw/jbe@1309 | 15298   }; | 
| bsw/jbe@1309 | 15299 | 
| bsw/jbe@1309 | 15300   var handleTabKeyDown = function(composer, element, shiftKey) { | 
| bsw/jbe@1309 | 15301     if (!composer.selection.isCollapsed()) { | 
| bsw/jbe@1309 | 15302       composer.selection.deleteContents(); | 
| bsw/jbe@1309 | 15303     } else if (composer.selection.caretIsInTheBeginnig('li')) { | 
| bsw/jbe@1309 | 15304       if (shiftKey) { | 
| bsw/jbe@1309 | 15305         if (composer.commands.exec('outdentList')) return; | 
| bsw/jbe@1309 | 15306       } else { | 
| bsw/jbe@1309 | 15307         if (composer.commands.exec('indentList')) return; | 
| bsw/jbe@1309 | 15308       } | 
| bsw/jbe@1309 | 15309     } | 
| bsw/jbe@1309 | 15310 | 
| bsw/jbe@1309 | 15311     // Is   close enough to tab. Could not find enough counter arguments for now. | 
| bsw/jbe@1309 | 15312     composer.commands.exec("insertHTML", " "); | 
| bsw/jbe@1309 | 15313   }; | 
| bsw/jbe@1309 | 15314 | 
| bsw/jbe@1309 | 15315   var handleDomNodeRemoved = function(event) { | 
| bsw/jbe@1309 | 15316       if (this.domNodeRemovedInterval) { | 
| bsw/jbe@1309 | 15317         clearInterval(domNodeRemovedInterval); | 
| bsw/jbe@1309 | 15318       } | 
| bsw/jbe@1309 | 15319       this.parent.fire("destroy:composer"); | 
| bsw/jbe@1309 | 15320   }; | 
| bsw/jbe@1309 | 15321 | 
| bsw/jbe@1309 | 15322   // Listens to "drop", "paste", "mouseup", "focus", "keyup" events and fires | 
| bsw/jbe@1309 | 15323   var handleUserInteraction = function (event) { | 
| bsw/jbe@1309 | 15324     this.parent.fire("beforeinteraction", event).fire("beforeinteraction:composer", event); | 
| bsw/jbe@1309 | 15325     setTimeout((function() { | 
| bsw/jbe@1309 | 15326       this.parent.fire("interaction", event).fire("interaction:composer", event); | 
| bsw/jbe@1309 | 15327     }).bind(this), 0); | 
| bsw/jbe@1309 | 15328   }; | 
| bsw/jbe@1309 | 15329 | 
| bsw/jbe@1309 | 15330   var handleFocus = function(event) { | 
| bsw/jbe@1309 | 15331     this.parent.fire("focus", event).fire("focus:composer", event); | 
| bsw/jbe@1309 | 15332 | 
| bsw/jbe@1309 | 15333     // Delay storing of state until all focus handler are fired | 
| bsw/jbe@1309 | 15334     // especially the one which resets the placeholder | 
| bsw/jbe@1309 | 15335     setTimeout((function() { | 
| bsw/jbe@1309 | 15336       this.focusState = this.getValue(false, false); | 
| bsw/jbe@1309 | 15337     }).bind(this), 0); | 
| bsw/jbe@1309 | 15338   }; | 
| bsw/jbe@1309 | 15339 | 
| bsw/jbe@1309 | 15340   var handleBlur = function(event) { | 
| bsw/jbe@1309 | 15341     if (this.focusState !== this.getValue(false, false)) { | 
| bsw/jbe@1309 | 15342       //create change event if supported (all except IE8) | 
| bsw/jbe@1309 | 15343       var changeevent = event; | 
| bsw/jbe@1309 | 15344       if(typeof Object.create == 'function') { | 
| bsw/jbe@1309 | 15345         changeevent = Object.create(event, { type: { value: 'change' } }); | 
| bsw/jbe@1309 | 15346       } | 
| bsw/jbe@1309 | 15347       this.parent.fire("change", changeevent).fire("change:composer", changeevent); | 
| bsw/jbe@1309 | 15348     } | 
| bsw/jbe@1309 | 15349     this.parent.fire("blur", event).fire("blur:composer", event); | 
| bsw/jbe@1309 | 15350   }; | 
| bsw/jbe@1309 | 15351 | 
| bsw/jbe@1309 | 15352   var handlePaste = function(event) { | 
| bsw/jbe@1309 | 15353     this.parent.fire(event.type, event).fire(event.type + ":composer", event); | 
| bsw/jbe@1309 | 15354     if (event.type === "paste") { | 
| bsw/jbe@1309 | 15355       setTimeout((function() { | 
| bsw/jbe@1309 | 15356         this.parent.fire("newword:composer"); | 
| bsw/jbe@1309 | 15357       }).bind(this), 0); | 
| bsw/jbe@1309 | 15358     } | 
| bsw/jbe@1309 | 15359   }; | 
| bsw/jbe@1309 | 15360 | 
| bsw/jbe@1309 | 15361   var handleCopy = function(event) { | 
| bsw/jbe@1309 | 15362     if (this.config.copyedFromMarking) { | 
| bsw/jbe@1309 | 15363       // If supported the copied source can be based directly on selection | 
| bsw/jbe@1309 | 15364       // Very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection. | 
| bsw/jbe@1309 | 15365       if (wysihtml.browser.supportsModernPaste()) { | 
| bsw/jbe@1309 | 15366         event.clipboardData.setData("text/html", this.config.copyedFromMarking + this.selection.getHtml()); | 
| bsw/jbe@1309 | 15367         event.clipboardData.setData("text/plain", this.selection.getPlainText()); | 
| bsw/jbe@1309 | 15368         event.preventDefault(); | 
| bsw/jbe@1309 | 15369       } | 
| bsw/jbe@1309 | 15370       this.parent.fire(event.type, event).fire(event.type + ":composer", event); | 
| bsw/jbe@1309 | 15371     } | 
| bsw/jbe@1309 | 15372   }; | 
| bsw/jbe@1309 | 15373 | 
| bsw/jbe@1309 | 15374   var handleKeyUp = function(event) { | 
| bsw/jbe@1309 | 15375     var keyCode = event.keyCode; | 
| bsw/jbe@1309 | 15376     if (keyCode === wysihtml.SPACE_KEY || keyCode === wysihtml.ENTER_KEY) { | 
| bsw/jbe@1309 | 15377       this.parent.fire("newword:composer"); | 
| bsw/jbe@1309 | 15378     } | 
| bsw/jbe@1309 | 15379   }; | 
| bsw/jbe@1309 | 15380 | 
| bsw/jbe@1309 | 15381   var handleMouseDown = function(event) { | 
| bsw/jbe@1309 | 15382     if (!browser.canSelectImagesInContentEditable()) { | 
| bsw/jbe@1309 | 15383       // Make sure that images are selected when clicking on them | 
| bsw/jbe@1309 | 15384       var target = event.target, | 
| bsw/jbe@1309 | 15385           allImages = this.element.querySelectorAll('img'), | 
| bsw/jbe@1309 | 15386           notMyImages = this.element.querySelectorAll('.' + this.config.classNames.uneditableContainer + ' img'), | 
| bsw/jbe@1309 | 15387           myImages = wysihtml.lang.array(allImages).without(notMyImages); | 
| bsw/jbe@1309 | 15388 | 
| bsw/jbe@1309 | 15389       if (target.nodeName === "IMG" && wysihtml.lang.array(myImages).contains(target)) { | 
| bsw/jbe@1309 | 15390         this.selection.selectNode(target); | 
| bsw/jbe@1309 | 15391       } | 
| bsw/jbe@1309 | 15392     } | 
| bsw/jbe@1309 | 15393 | 
| bsw/jbe@1309 | 15394     // Saves mousedown position for IE controlSelect fix | 
| bsw/jbe@1309 | 15395     if (wysihtml.browser.usesControlRanges()) { | 
| bsw/jbe@1309 | 15396       this.selection.lastMouseDownPos = {x: event.clientX, y: event.clientY}; | 
| bsw/jbe@1309 | 15397       setTimeout(function() { | 
| bsw/jbe@1309 | 15398         delete this.selection.lastMouseDownPos; | 
| bsw/jbe@1309 | 15399       }.bind(this), 0); | 
| bsw/jbe@1309 | 15400     } | 
| bsw/jbe@1309 | 15401   }; | 
| bsw/jbe@1309 | 15402 | 
| bsw/jbe@1309 | 15403   // IE has this madness of control selects of overflowed and some other elements (weird box around element on selection and second click selects text) | 
| bsw/jbe@1309 | 15404   // This fix handles the second click problem by adding cursor to the right position under cursor inside when controlSelection is made | 
| bsw/jbe@1309 | 15405   var handleIEControlSelect = function(event) { | 
| bsw/jbe@1309 | 15406     var target = event.target, | 
| bsw/jbe@1309 | 15407         pos = this.selection.lastMouseDownPos; | 
| bsw/jbe@1309 | 15408     if (pos) { | 
| bsw/jbe@1309 | 15409       var caretPosition = document.body.createTextRange(); | 
| bsw/jbe@1309 | 15410         setTimeout(function() { | 
| bsw/jbe@1309 | 15411           try { | 
| bsw/jbe@1309 | 15412             caretPosition.moveToPoint(pos.x, pos.y); | 
| bsw/jbe@1309 | 15413             caretPosition.select(); | 
| bsw/jbe@1309 | 15414           } catch (e) {} | 
| bsw/jbe@1309 | 15415         }.bind(this), 0); | 
| bsw/jbe@1309 | 15416     } | 
| bsw/jbe@1309 | 15417   }; | 
| bsw/jbe@1309 | 15418 | 
| bsw/jbe@1309 | 15419   var handleClick = function(event) { | 
| bsw/jbe@1309 | 15420     if (this.config.classNames.uneditableContainer) { | 
| bsw/jbe@1309 | 15421       // If uneditables is configured, makes clicking on uneditable move caret after clicked element (so it can be deleted like text) | 
| bsw/jbe@1309 | 15422       // If uneditable needs text selection itself event.stopPropagation can be used to prevent this behaviour | 
| bsw/jbe@1309 | 15423       var uneditable = wysihtml.dom.getParentElement(event.target, { query: "." + this.config.classNames.uneditableContainer }, false, this.element); | 
| bsw/jbe@1309 | 15424       if (uneditable) { | 
| bsw/jbe@1309 | 15425         this.selection.setAfter(uneditable); | 
| bsw/jbe@1309 | 15426       } | 
| bsw/jbe@1309 | 15427     } | 
| bsw/jbe@1309 | 15428   }; | 
| bsw/jbe@1309 | 15429 | 
| bsw/jbe@1309 | 15430   var handleDrop = function(event) { | 
| bsw/jbe@1309 | 15431     if (!browser.canSelectImagesInContentEditable()) { | 
| bsw/jbe@1309 | 15432       // TODO: if I knew how to get dropped elements list from event I could limit it to only IMG element case | 
| bsw/jbe@1309 | 15433       setTimeout((function() { | 
| bsw/jbe@1309 | 15434         this.selection.getSelection().removeAllRanges(); | 
| bsw/jbe@1309 | 15435       }).bind(this), 0); | 
| bsw/jbe@1309 | 15436     } | 
| bsw/jbe@1309 | 15437   }; | 
| bsw/jbe@1309 | 15438 | 
| bsw/jbe@1309 | 15439   var handleKeyDown = function(event) { | 
| bsw/jbe@1309 | 15440     var keyCode = event.keyCode, | 
| bsw/jbe@1309 | 15441         command = shortcuts[keyCode], | 
| bsw/jbe@1309 | 15442         target = this.selection.getSelectedNode(true), | 
| bsw/jbe@1309 | 15443         targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element | 
| bsw/jbe@1309 | 15444         parent; | 
| bsw/jbe@1309 | 15445 | 
| bsw/jbe@1309 | 15446     // Select all (meta/ctrl + a) | 
| bsw/jbe@1309 | 15447     if ((event.ctrlKey || event.metaKey) && !event.altKey && keyCode === 65) { | 
| bsw/jbe@1309 | 15448       this.selection.selectAll(); | 
| bsw/jbe@1309 | 15449       event.preventDefault(); | 
| bsw/jbe@1309 | 15450       return; | 
| bsw/jbe@1309 | 15451     } | 
| bsw/jbe@1309 | 15452 | 
| bsw/jbe@1309 | 15453     // Shortcut logic | 
| bsw/jbe@1309 | 15454     if ((event.ctrlKey || event.metaKey) && !event.altKey && command) { | 
| bsw/jbe@1309 | 15455       this.commands.exec(command); | 
| bsw/jbe@1309 | 15456       event.preventDefault(); | 
| bsw/jbe@1309 | 15457     } | 
| bsw/jbe@1309 | 15458 | 
| bsw/jbe@1309 | 15459     if (keyCode === wysihtml.BACKSPACE_KEY) { | 
| bsw/jbe@1309 | 15460       // Delete key override for special cases | 
| bsw/jbe@1309 | 15461       handleDeleteKeyPress(event, this); | 
| bsw/jbe@1309 | 15462     } | 
| bsw/jbe@1309 | 15463 | 
| bsw/jbe@1309 | 15464     // Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor | 
| bsw/jbe@1309 | 15465     if (keyCode === wysihtml.BACKSPACE_KEY || keyCode === wysihtml.DELETE_KEY) { | 
| bsw/jbe@1309 | 15466       if (target && target.nodeName === "IMG") { | 
| bsw/jbe@1309 | 15467         event.preventDefault(); | 
| bsw/jbe@1309 | 15468         parent = target.parentNode; | 
| bsw/jbe@1309 | 15469         parent.removeChild(target);// delete the <img> | 
| bsw/jbe@1309 | 15470         // And it's parent <a> too if it hasn't got any other child nodes | 
| bsw/jbe@1309 | 15471         if (parent.nodeName === "A" && !parent.firstChild) { | 
| bsw/jbe@1309 | 15472           parent.parentNode.removeChild(parent); | 
| bsw/jbe@1309 | 15473         } | 
| bsw/jbe@1309 | 15474         setTimeout((function() { | 
| bsw/jbe@1309 | 15475           wysihtml.quirks.redraw(this.element); | 
| bsw/jbe@1309 | 15476         }).bind(this), 0); | 
| bsw/jbe@1309 | 15477       } | 
| bsw/jbe@1309 | 15478     } | 
| bsw/jbe@1309 | 15479 | 
| bsw/jbe@1309 | 15480     if (this.config.handleTabKey && keyCode === wysihtml.TAB_KEY) { | 
| bsw/jbe@1309 | 15481       // TAB key handling | 
| bsw/jbe@1309 | 15482       event.preventDefault(); | 
| bsw/jbe@1309 | 15483       handleTabKeyDown(this, this.element, event.shiftKey); | 
| bsw/jbe@1309 | 15484     } | 
| bsw/jbe@1309 | 15485 | 
| bsw/jbe@1309 | 15486     if (keyCode === wysihtml.ENTER_KEY) { | 
| bsw/jbe@1309 | 15487       handleEnterKeyPress(event, this); | 
| bsw/jbe@1309 | 15488     } | 
| bsw/jbe@1309 | 15489 | 
| bsw/jbe@1309 | 15490   }; | 
| bsw/jbe@1309 | 15491 | 
| bsw/jbe@1309 | 15492   var handleKeyPress = function(event) { | 
| bsw/jbe@1309 | 15493 | 
| bsw/jbe@1309 | 15494     // This block should run only if some character is inserted (nor command keys like delete, backspace, enter, etc.) | 
| bsw/jbe@1309 | 15495     if (event.which !== 0) { | 
| bsw/jbe@1309 | 15496 | 
| bsw/jbe@1309 | 15497       // Test if caret is last in a link in webkit and try to fix webkit problem, | 
| bsw/jbe@1309 | 15498       // that all inserted content is added outside of link. | 
| bsw/jbe@1309 | 15499       // This issue was added as a not thought through fix for getting caret after link in contenteditable if it is last in editable area. | 
| bsw/jbe@1309 | 15500       // Allthough it fixes this minor case it actually introduces a cascade of problems when editing links. | 
| bsw/jbe@1309 | 15501       // The standard approachi in other wysiwygs seems as a step backwards - introducing a separate modal for managing links content text. | 
| bsw/jbe@1309 | 15502       // I find it to be too big of a tradeoff in terms of expected simple UI flow, thus trying to fight against it. | 
| bsw/jbe@1309 | 15503       // Also adds link escaping by double space with caret at the end of link for all browsers | 
| bsw/jbe@1309 | 15504 | 
| bsw/jbe@1309 | 15505       if (this.selection.caretIsInTheEndOfNode()) { | 
| bsw/jbe@1309 | 15506         var target = this.selection.getSelectedNode(true), | 
| bsw/jbe@1309 | 15507             targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element | 
| bsw/jbe@1309 | 15508             invisibleSpace, space; | 
| bsw/jbe@1309 | 15509 | 
| bsw/jbe@1309 | 15510         if (targetEl && targetEl.closest('a') && target === targetEl.lastChild) { | 
| bsw/jbe@1309 | 15511 | 
| bsw/jbe@1309 | 15512           if (event.which !== 32 || this.selection.caretIsInTheEndOfNode(true) && browser.hasCaretAtLinkEndInsertionProblems()) { | 
| bsw/jbe@1309 | 15513             // Executed if there is no whitespace before caret in textnode in case of pressing space. | 
| bsw/jbe@1309 | 15514             // Whitespace before marks that user wants to escape the node by pressing double space. | 
| bsw/jbe@1309 | 15515             // Otherwise insert the character in the link not out as it would like to go natively | 
| bsw/jbe@1309 | 15516 | 
| bsw/jbe@1309 | 15517             invisibleSpace = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE); | 
| bsw/jbe@1309 | 15518             this.selection.insertNode(invisibleSpace); | 
| bsw/jbe@1309 | 15519             this.selection.setBefore(invisibleSpace); | 
| bsw/jbe@1309 | 15520             setTimeout(function() { | 
| bsw/jbe@1309 | 15521 | 
| bsw/jbe@1309 | 15522               if (invisibleSpace.textContent.length > 1) { | 
| bsw/jbe@1309 | 15523                 invisibleSpace.textContent = invisibleSpace.textContent.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ''); | 
| bsw/jbe@1309 | 15524                 this.selection.setAfter(invisibleSpace); | 
| bsw/jbe@1309 | 15525               } else { | 
| bsw/jbe@1309 | 15526                 invisibleSpace.remove(); | 
| bsw/jbe@1309 | 15527               } | 
| bsw/jbe@1309 | 15528 | 
| bsw/jbe@1309 | 15529             }.bind(this), 0); | 
| bsw/jbe@1309 | 15530           } else if (event.which === 32) { | 
| bsw/jbe@1309 | 15531             // Seems like space was pressed and there was a space before the caret allready | 
| bsw/jbe@1309 | 15532             // This means user wants to escape the link now (caret is last in link node too) so we let the native browser do it-s job and escape. | 
| bsw/jbe@1309 | 15533             // But lets move the trailing space too out of link if present | 
| bsw/jbe@1309 | 15534 | 
| bsw/jbe@1309 | 15535             if (target.nodeType === 3 && (/[\u00A0 ]$/).test(target.textContent)) { | 
| bsw/jbe@1309 | 15536 | 
| bsw/jbe@1309 | 15537               target.textContent = target.textContent.replace(/[\u00A0 ]$/, ''); | 
| bsw/jbe@1309 | 15538               space = this.doc.createTextNode(' '); | 
| bsw/jbe@1309 | 15539               targetEl.parentNode.insertBefore(space, targetEl.nextSibling); | 
| bsw/jbe@1309 | 15540               this.selection.setAfter(space, false); | 
| bsw/jbe@1309 | 15541               event.preventDefault(); | 
| bsw/jbe@1309 | 15542 | 
| bsw/jbe@1309 | 15543             } | 
| bsw/jbe@1309 | 15544           } | 
| bsw/jbe@1309 | 15545         } | 
| bsw/jbe@1309 | 15546       } | 
| bsw/jbe@1309 | 15547     } | 
| bsw/jbe@1309 | 15548   } | 
| bsw/jbe@1309 | 15549 | 
| bsw/jbe@1309 | 15550   var handleIframeFocus = function(event) { | 
| bsw/jbe@1309 | 15551     setTimeout((function() { | 
| bsw/jbe@1309 | 15552       if (this.doc.querySelector(":focus") !== this.element) { | 
| bsw/jbe@1309 | 15553         this.focus(); | 
| bsw/jbe@1309 | 15554       } | 
| bsw/jbe@1309 | 15555     }).bind(this), 0); | 
| bsw/jbe@1309 | 15556   }; | 
| bsw/jbe@1309 | 15557 | 
| bsw/jbe@1309 | 15558   var handleIframeBlur = function(event) { | 
| bsw/jbe@1309 | 15559     setTimeout((function() { | 
| bsw/jbe@1309 | 15560       this.selection.getSelection().removeAllRanges(); | 
| bsw/jbe@1309 | 15561     }).bind(this), 0); | 
| bsw/jbe@1309 | 15562   }; | 
| bsw/jbe@1309 | 15563 | 
| bsw/jbe@1309 | 15564   // Testing requires actions to be accessible from out of scope | 
| bsw/jbe@1309 | 15565   wysihtml.views.Composer.prototype.observeActions = actions; | 
| bsw/jbe@1309 | 15566 | 
| bsw/jbe@1309 | 15567   wysihtml.views.Composer.prototype.observe = function() { | 
| bsw/jbe@1309 | 15568     var that                = this, | 
| bsw/jbe@1309 | 15569         container           = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(), | 
| bsw/jbe@1309 | 15570         element             = this.element, | 
| bsw/jbe@1309 | 15571         focusBlurElement    = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? this.element : this.sandbox.getWindow(); | 
| bsw/jbe@1309 | 15572 | 
| bsw/jbe@1309 | 15573     this.focusState = this.getValue(false, false); | 
| bsw/jbe@1309 | 15574     this.actions = actions; | 
| bsw/jbe@1309 | 15575 | 
| bsw/jbe@1309 | 15576     // --------- destroy:composer event --------- | 
| bsw/jbe@1309 | 15577     container.addEventListener(["DOMNodeRemoved"], handleDomNodeRemoved.bind(this), false); | 
| bsw/jbe@1309 | 15578 | 
| bsw/jbe@1309 | 15579     // DOMNodeRemoved event is not supported in IE 8 | 
| bsw/jbe@1309 | 15580     // TODO: try to figure out a polyfill style fix, so it could be transferred to polyfills and removed if ie8 is not needed | 
| bsw/jbe@1309 | 15581     if (!browser.supportsMutationEvents()) { | 
| bsw/jbe@1309 | 15582       this.domNodeRemovedInterval = setInterval(function() { | 
| bsw/jbe@1309 | 15583         if (!dom.contains(document.documentElement, container)) { | 
| bsw/jbe@1309 | 15584           handleDomNodeRemoved.call(this); | 
| bsw/jbe@1309 | 15585         } | 
| bsw/jbe@1309 | 15586       }, 250); | 
| bsw/jbe@1309 | 15587     } | 
| bsw/jbe@1309 | 15588 | 
| bsw/jbe@1309 | 15589     actions.addListeners(focusBlurElement, ['drop', 'paste', 'mouseup', 'focus', 'keyup'], handleUserInteraction.bind(this)); | 
| bsw/jbe@1309 | 15590     focusBlurElement.addEventListener('focus', handleFocus.bind(this), false); | 
| bsw/jbe@1309 | 15591     focusBlurElement.addEventListener('blur',  handleBlur.bind(this), false); | 
| bsw/jbe@1309 | 15592 | 
| bsw/jbe@1309 | 15593     actions.addListeners(this.element, ['drop', 'paste', 'beforepaste'], handlePaste.bind(this), false); | 
| bsw/jbe@1309 | 15594     this.element.addEventListener('copy',       handleCopy.bind(this), false); | 
| bsw/jbe@1309 | 15595     this.element.addEventListener('mousedown',  handleMouseDown.bind(this), false); | 
| bsw/jbe@1309 | 15596     this.element.addEventListener('click',      handleClick.bind(this), false); | 
| bsw/jbe@1309 | 15597     this.element.addEventListener('drop',       handleDrop.bind(this), false); | 
| bsw/jbe@1309 | 15598     this.element.addEventListener('keyup',      handleKeyUp.bind(this), false); | 
| bsw/jbe@1309 | 15599     this.element.addEventListener('keydown',    handleKeyDown.bind(this), false); | 
| bsw/jbe@1309 | 15600     this.element.addEventListener('keypress',   handleKeyPress.bind(this), false); | 
| bsw/jbe@1309 | 15601 | 
| bsw/jbe@1309 | 15602     // IE controlselect madness fix | 
| bsw/jbe@1309 | 15603     if (wysihtml.browser.usesControlRanges()) { | 
| bsw/jbe@1309 | 15604       this.element.addEventListener('mscontrolselect', handleIEControlSelect.bind(this), false); | 
| bsw/jbe@1309 | 15605     } | 
| bsw/jbe@1309 | 15606 | 
| bsw/jbe@1309 | 15607     this.element.addEventListener("dragenter", (function() { | 
| bsw/jbe@1309 | 15608       this.parent.fire("unset_placeholder"); | 
| bsw/jbe@1309 | 15609     }).bind(this), false); | 
| bsw/jbe@1309 | 15610 | 
| bsw/jbe@1309 | 15611   }; | 
| bsw/jbe@1309 | 15612 })(wysihtml); | 
| bsw/jbe@1309 | 15613 | 
| bsw/jbe@1309 | 15614 /** | 
| bsw/jbe@1309 | 15615  * Class that takes care that the value of the composer and the textarea is always in sync | 
| bsw/jbe@1309 | 15616  */ | 
| bsw/jbe@1309 | 15617 (function(wysihtml) { | 
| bsw/jbe@1309 | 15618   var INTERVAL = 400; | 
| bsw/jbe@1309 | 15619 | 
| bsw/jbe@1309 | 15620   wysihtml.views.Synchronizer = Base.extend( | 
| bsw/jbe@1309 | 15621     /** @scope wysihtml.views.Synchronizer.prototype */ { | 
| bsw/jbe@1309 | 15622 | 
| bsw/jbe@1309 | 15623     constructor: function(editor, textarea, composer) { | 
| bsw/jbe@1309 | 15624       this.editor   = editor; | 
| bsw/jbe@1309 | 15625       this.textarea = textarea; | 
| bsw/jbe@1309 | 15626       this.composer = composer; | 
| bsw/jbe@1309 | 15627 | 
| bsw/jbe@1309 | 15628       this._observe(); | 
| bsw/jbe@1309 | 15629     }, | 
| bsw/jbe@1309 | 15630 | 
| bsw/jbe@1309 | 15631     /** | 
| bsw/jbe@1309 | 15632      * Sync html from composer to textarea | 
| bsw/jbe@1309 | 15633      * Takes care of placeholders | 
| bsw/jbe@1309 | 15634      * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the textarea | 
| bsw/jbe@1309 | 15635      */ | 
| bsw/jbe@1309 | 15636     fromComposerToTextarea: function(shouldParseHtml) { | 
| bsw/jbe@1309 | 15637       this.textarea.setValue(wysihtml.lang.string(this.composer.getValue(false, false)).trim(), shouldParseHtml); | 
| bsw/jbe@1309 | 15638     }, | 
| bsw/jbe@1309 | 15639 | 
| bsw/jbe@1309 | 15640     /** | 
| bsw/jbe@1309 | 15641      * Sync value of textarea to composer | 
| bsw/jbe@1309 | 15642      * Takes care of placeholders | 
| bsw/jbe@1309 | 15643      * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer | 
| bsw/jbe@1309 | 15644      */ | 
| bsw/jbe@1309 | 15645     fromTextareaToComposer: function(shouldParseHtml) { | 
| bsw/jbe@1309 | 15646       var textareaValue = this.textarea.getValue(false, false); | 
| bsw/jbe@1309 | 15647       if (textareaValue) { | 
| bsw/jbe@1309 | 15648         this.composer.setValue(textareaValue, shouldParseHtml); | 
| bsw/jbe@1309 | 15649       } else { | 
| bsw/jbe@1309 | 15650         this.composer.clear(); | 
| bsw/jbe@1309 | 15651         this.editor.fire("set_placeholder"); | 
| bsw/jbe@1309 | 15652       } | 
| bsw/jbe@1309 | 15653     }, | 
| bsw/jbe@1309 | 15654 | 
| bsw/jbe@1309 | 15655     /** | 
| bsw/jbe@1309 | 15656      * Invoke syncing based on view state | 
| bsw/jbe@1309 | 15657      * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer/textarea | 
| bsw/jbe@1309 | 15658      */ | 
| bsw/jbe@1309 | 15659     sync: function(shouldParseHtml) { | 
| bsw/jbe@1309 | 15660       if (this.editor.currentView.name === "textarea") { | 
| bsw/jbe@1309 | 15661         this.fromTextareaToComposer(shouldParseHtml); | 
| bsw/jbe@1309 | 15662       } else { | 
| bsw/jbe@1309 | 15663         this.fromComposerToTextarea(shouldParseHtml); | 
| bsw/jbe@1309 | 15664       } | 
| bsw/jbe@1309 | 15665     }, | 
| bsw/jbe@1309 | 15666 | 
| bsw/jbe@1309 | 15667     /** | 
| bsw/jbe@1309 | 15668      * Initializes interval-based syncing | 
| bsw/jbe@1309 | 15669      * also makes sure that on-submit the composer's content is synced with the textarea | 
| bsw/jbe@1309 | 15670      * immediately when the form gets submitted | 
| bsw/jbe@1309 | 15671      */ | 
| bsw/jbe@1309 | 15672     _observe: function() { | 
| bsw/jbe@1309 | 15673       var interval, | 
| bsw/jbe@1309 | 15674           that          = this, | 
| bsw/jbe@1309 | 15675           form          = this.textarea.element.form, | 
| bsw/jbe@1309 | 15676           startInterval = function() { | 
| bsw/jbe@1309 | 15677             interval = setInterval(function() { that.fromComposerToTextarea(); }, INTERVAL); | 
| bsw/jbe@1309 | 15678           }, | 
| bsw/jbe@1309 | 15679           stopInterval  = function() { | 
| bsw/jbe@1309 | 15680             clearInterval(interval); | 
| bsw/jbe@1309 | 15681             interval = null; | 
| bsw/jbe@1309 | 15682           }; | 
| bsw/jbe@1309 | 15683 | 
| bsw/jbe@1309 | 15684       startInterval(); | 
| bsw/jbe@1309 | 15685 | 
| bsw/jbe@1309 | 15686       if (form) { | 
| bsw/jbe@1309 | 15687         // If the textarea is in a form make sure that after onreset and onsubmit the composer | 
| bsw/jbe@1309 | 15688         // has the correct state | 
| bsw/jbe@1309 | 15689         wysihtml.dom.observe(form, "submit", function() { | 
| bsw/jbe@1309 | 15690           that.sync(true); | 
| bsw/jbe@1309 | 15691         }); | 
| bsw/jbe@1309 | 15692         wysihtml.dom.observe(form, "reset", function() { | 
| bsw/jbe@1309 | 15693           setTimeout(function() { that.fromTextareaToComposer(); }, 0); | 
| bsw/jbe@1309 | 15694         }); | 
| bsw/jbe@1309 | 15695       } | 
| bsw/jbe@1309 | 15696 | 
| bsw/jbe@1309 | 15697       this.editor.on("change_view", function(view) { | 
| bsw/jbe@1309 | 15698         if (view === "composer" && !interval) { | 
| bsw/jbe@1309 | 15699           that.fromTextareaToComposer(true); | 
| bsw/jbe@1309 | 15700           startInterval(); | 
| bsw/jbe@1309 | 15701         } else if (view === "textarea") { | 
| bsw/jbe@1309 | 15702           that.fromComposerToTextarea(true); | 
| bsw/jbe@1309 | 15703           stopInterval(); | 
| bsw/jbe@1309 | 15704         } | 
| bsw/jbe@1309 | 15705       }); | 
| bsw/jbe@1309 | 15706 | 
| bsw/jbe@1309 | 15707       this.editor.on("destroy:composer", stopInterval); | 
| bsw/jbe@1309 | 15708     } | 
| bsw/jbe@1309 | 15709   }); | 
| bsw/jbe@1309 | 15710 })(wysihtml); | 
| bsw/jbe@1309 | 15711 | 
| bsw/jbe@1309 | 15712 (function(wysihtml) { | 
| bsw/jbe@1309 | 15713 | 
| bsw/jbe@1309 | 15714   wysihtml.views.SourceView = Base.extend( | 
| bsw/jbe@1309 | 15715     /** @scope wysihtml.views.SourceView.prototype */ { | 
| bsw/jbe@1309 | 15716 | 
| bsw/jbe@1309 | 15717     constructor: function(editor, composer) { | 
| bsw/jbe@1309 | 15718       this.editor   = editor; | 
| bsw/jbe@1309 | 15719       this.composer = composer; | 
| bsw/jbe@1309 | 15720 | 
| bsw/jbe@1309 | 15721       this._observe(); | 
| bsw/jbe@1309 | 15722     }, | 
| bsw/jbe@1309 | 15723 | 
| bsw/jbe@1309 | 15724     switchToTextarea: function(shouldParseHtml) { | 
| bsw/jbe@1309 | 15725       var composerStyles = this.composer.win.getComputedStyle(this.composer.element), | 
| bsw/jbe@1309 | 15726           width = parseFloat(composerStyles.width), | 
| bsw/jbe@1309 | 15727           height = Math.max(parseFloat(composerStyles.height), 100); | 
| bsw/jbe@1309 | 15728 | 
| bsw/jbe@1309 | 15729       if (!this.textarea) { | 
| bsw/jbe@1309 | 15730         this.textarea = this.composer.doc.createElement('textarea'); | 
| bsw/jbe@1309 | 15731         this.textarea.className = "wysihtml-source-view"; | 
| bsw/jbe@1309 | 15732       } | 
| bsw/jbe@1309 | 15733       this.textarea.style.width = width + 'px'; | 
| bsw/jbe@1309 | 15734       this.textarea.style.height = height + 'px'; | 
| bsw/jbe@1309 | 15735       this.textarea.value = this.editor.getValue(shouldParseHtml, true); | 
| bsw/jbe@1309 | 15736       this.composer.element.parentNode.insertBefore(this.textarea, this.composer.element); | 
| bsw/jbe@1309 | 15737       this.editor.currentView = "source"; | 
| bsw/jbe@1309 | 15738       this.composer.element.style.display = 'none'; | 
| bsw/jbe@1309 | 15739     }, | 
| bsw/jbe@1309 | 15740 | 
| bsw/jbe@1309 | 15741     switchToComposer: function(shouldParseHtml) { | 
| bsw/jbe@1309 | 15742       var textareaValue = this.textarea.value; | 
| bsw/jbe@1309 | 15743       if (textareaValue) { | 
| bsw/jbe@1309 | 15744         this.composer.setValue(textareaValue, shouldParseHtml); | 
| bsw/jbe@1309 | 15745       } else { | 
| bsw/jbe@1309 | 15746         this.composer.clear(); | 
| bsw/jbe@1309 | 15747         this.editor.fire("set_placeholder"); | 
| bsw/jbe@1309 | 15748       } | 
| bsw/jbe@1309 | 15749       this.textarea.parentNode.removeChild(this.textarea); | 
| bsw/jbe@1309 | 15750       this.editor.currentView = this.composer; | 
| bsw/jbe@1309 | 15751       this.composer.element.style.display = ''; | 
| bsw/jbe@1309 | 15752     }, | 
| bsw/jbe@1309 | 15753 | 
| bsw/jbe@1309 | 15754     _observe: function() { | 
| bsw/jbe@1309 | 15755       this.editor.on("change_view", function(view) { | 
| bsw/jbe@1309 | 15756         if (view === "composer") { | 
| bsw/jbe@1309 | 15757           this.switchToComposer(true); | 
| bsw/jbe@1309 | 15758         } else if (view === "textarea") { | 
| bsw/jbe@1309 | 15759           this.switchToTextarea(true); | 
| bsw/jbe@1309 | 15760         } | 
| bsw/jbe@1309 | 15761       }.bind(this)); | 
| bsw/jbe@1309 | 15762     } | 
| bsw/jbe@1309 | 15763 | 
| bsw/jbe@1309 | 15764   }); | 
| bsw/jbe@1309 | 15765 | 
| bsw/jbe@1309 | 15766 })(wysihtml); | 
| bsw/jbe@1309 | 15767 | 
| bsw/jbe@1309 | 15768 wysihtml.views.Textarea = wysihtml.views.View.extend( | 
| bsw/jbe@1309 | 15769   /** @scope wysihtml.views.Textarea.prototype */ { | 
| bsw/jbe@1309 | 15770   name: "textarea", | 
| bsw/jbe@1309 | 15771 | 
| bsw/jbe@1309 | 15772   constructor: function(parent, textareaElement, config) { | 
| bsw/jbe@1309 | 15773     this.base(parent, textareaElement, config); | 
| bsw/jbe@1309 | 15774 | 
| bsw/jbe@1309 | 15775     this._observe(); | 
| bsw/jbe@1309 | 15776   }, | 
| bsw/jbe@1309 | 15777 | 
| bsw/jbe@1309 | 15778   clear: function() { | 
| bsw/jbe@1309 | 15779     this.element.value = ""; | 
| bsw/jbe@1309 | 15780   }, | 
| bsw/jbe@1309 | 15781 | 
| bsw/jbe@1309 | 15782   getValue: function(parse) { | 
| bsw/jbe@1309 | 15783     var value = this.isEmpty() ? "" : this.element.value; | 
| bsw/jbe@1309 | 15784     if (parse !== false) { | 
| bsw/jbe@1309 | 15785       value = this.parent.parse(value); | 
| bsw/jbe@1309 | 15786     } | 
| bsw/jbe@1309 | 15787     return value; | 
| bsw/jbe@1309 | 15788   }, | 
| bsw/jbe@1309 | 15789 | 
| bsw/jbe@1309 | 15790   setValue: function(html, parse) { | 
| bsw/jbe@1309 | 15791     if (parse !== false) { | 
| bsw/jbe@1309 | 15792       html = this.parent.parse(html); | 
| bsw/jbe@1309 | 15793     } | 
| bsw/jbe@1309 | 15794     this.element.value = html; | 
| bsw/jbe@1309 | 15795   }, | 
| bsw/jbe@1309 | 15796 | 
| bsw/jbe@1309 | 15797   cleanUp: function(rules) { | 
| bsw/jbe@1309 | 15798       var html = this.parent.parse(this.element.value, undefined, rules); | 
| bsw/jbe@1309 | 15799       this.element.value = html; | 
| bsw/jbe@1309 | 15800   }, | 
| bsw/jbe@1309 | 15801 | 
| bsw/jbe@1309 | 15802   hasPlaceholderSet: function() { | 
| bsw/jbe@1309 | 15803     var supportsPlaceholder = wysihtml.browser.supportsPlaceholderAttributeOn(this.element), | 
| bsw/jbe@1309 | 15804         placeholderText     = this.element.getAttribute("placeholder") || null, | 
| bsw/jbe@1309 | 15805         value               = this.element.value, | 
| bsw/jbe@1309 | 15806         isEmpty             = !value; | 
| bsw/jbe@1309 | 15807     return (supportsPlaceholder && isEmpty) || (value === placeholderText); | 
| bsw/jbe@1309 | 15808   }, | 
| bsw/jbe@1309 | 15809 | 
| bsw/jbe@1309 | 15810   isEmpty: function() { | 
| bsw/jbe@1309 | 15811     return !wysihtml.lang.string(this.element.value).trim() || this.hasPlaceholderSet(); | 
| bsw/jbe@1309 | 15812   }, | 
| bsw/jbe@1309 | 15813 | 
| bsw/jbe@1309 | 15814   _observe: function() { | 
| bsw/jbe@1309 | 15815     var element = this.element, | 
| bsw/jbe@1309 | 15816         parent  = this.parent, | 
| bsw/jbe@1309 | 15817         eventMapping = { | 
| bsw/jbe@1309 | 15818           focusin:  "focus", | 
| bsw/jbe@1309 | 15819           focusout: "blur" | 
| bsw/jbe@1309 | 15820         }, | 
| bsw/jbe@1309 | 15821         /** | 
| bsw/jbe@1309 | 15822          * Calling focus() or blur() on an element doesn't synchronously trigger the attached focus/blur events | 
| bsw/jbe@1309 | 15823          * This is the case for focusin and focusout, so let's use them whenever possible, kkthxbai | 
| bsw/jbe@1309 | 15824          */ | 
| bsw/jbe@1309 | 15825         events = wysihtml.browser.supportsEvent("focusin") ? ["focusin", "focusout", "change"] : ["focus", "blur", "change"]; | 
| bsw/jbe@1309 | 15826 | 
| bsw/jbe@1309 | 15827     parent.on("beforeload", function() { | 
| bsw/jbe@1309 | 15828       wysihtml.dom.observe(element, events, function(event) { | 
| bsw/jbe@1309 | 15829         var eventName = eventMapping[event.type] || event.type; | 
| bsw/jbe@1309 | 15830         parent.fire(eventName).fire(eventName + ":textarea"); | 
| bsw/jbe@1309 | 15831       }); | 
| bsw/jbe@1309 | 15832 | 
| bsw/jbe@1309 | 15833       wysihtml.dom.observe(element, ["paste", "drop"], function() { | 
| bsw/jbe@1309 | 15834         setTimeout(function() { parent.fire("paste").fire("paste:textarea"); }, 0); | 
| bsw/jbe@1309 | 15835       }); | 
| bsw/jbe@1309 | 15836     }); | 
| bsw/jbe@1309 | 15837   } | 
| bsw/jbe@1309 | 15838 }); | 
| bsw/jbe@1309 | 15839 | 
| bsw/jbe@1309 | 15840 /** | 
| bsw/jbe@1309 | 15841  * WYSIHTML Editor | 
| bsw/jbe@1309 | 15842  * | 
| bsw/jbe@1309 | 15843  * @param {Element} editableElement Reference to the textarea which should be turned into a rich text interface | 
| bsw/jbe@1309 | 15844  * @param {Object} [config] See defaults object below for explanation of each individual config option | 
| bsw/jbe@1309 | 15845  * | 
| bsw/jbe@1309 | 15846  * @events | 
| bsw/jbe@1309 | 15847  *    load | 
| bsw/jbe@1309 | 15848  *    beforeload (for internal use only) | 
| bsw/jbe@1309 | 15849  *    focus | 
| bsw/jbe@1309 | 15850  *    focus:composer | 
| bsw/jbe@1309 | 15851  *    focus:textarea | 
| bsw/jbe@1309 | 15852  *    blur | 
| bsw/jbe@1309 | 15853  *    blur:composer | 
| bsw/jbe@1309 | 15854  *    blur:textarea | 
| bsw/jbe@1309 | 15855  *    change | 
| bsw/jbe@1309 | 15856  *    change:composer | 
| bsw/jbe@1309 | 15857  *    change:textarea | 
| bsw/jbe@1309 | 15858  *    paste | 
| bsw/jbe@1309 | 15859  *    paste:composer | 
| bsw/jbe@1309 | 15860  *    paste:textarea | 
| bsw/jbe@1309 | 15861  *    newword:composer | 
| bsw/jbe@1309 | 15862  *    destroy:composer | 
| bsw/jbe@1309 | 15863  *    undo:composer | 
| bsw/jbe@1309 | 15864  *    redo:composer | 
| bsw/jbe@1309 | 15865  *    beforecommand:composer | 
| bsw/jbe@1309 | 15866  *    aftercommand:composer | 
| bsw/jbe@1309 | 15867  *    enable:composer | 
| bsw/jbe@1309 | 15868  *    disable:composer | 
| bsw/jbe@1309 | 15869  *    change_view | 
| bsw/jbe@1309 | 15870  */ | 
| bsw/jbe@1309 | 15871 (function(wysihtml) { | 
| bsw/jbe@1309 | 15872   var undef; | 
| bsw/jbe@1309 | 15873 | 
| bsw/jbe@1309 | 15874   wysihtml.Editor = wysihtml.lang.Dispatcher.extend({ | 
| bsw/jbe@1309 | 15875     /** @scope wysihtml.Editor.prototype */ | 
| bsw/jbe@1309 | 15876     defaults: { | 
| bsw/jbe@1309 | 15877       // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body | 
| bsw/jbe@1309 | 15878       name:                 undef, | 
| bsw/jbe@1309 | 15879       // Whether the editor should look like the textarea (by adopting styles) | 
| bsw/jbe@1309 | 15880       style:                true, | 
| bsw/jbe@1309 | 15881       // Whether urls, entered by the user should automatically become clickable-links | 
| bsw/jbe@1309 | 15882       autoLink:             true, | 
| bsw/jbe@1309 | 15883       // Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation | 
| bsw/jbe@1309 | 15884       handleTabKey:         true, | 
| bsw/jbe@1309 | 15885       // Object which includes parser rules to apply when html gets cleaned | 
| bsw/jbe@1309 | 15886       // See parser_rules/*.js for examples | 
| bsw/jbe@1309 | 15887       parserRules:          { tags: { br: {}, span: {}, div: {}, p: {}, b: {}, i: {}, u: {} }, classes: {} }, | 
| bsw/jbe@1309 | 15888       // Object which includes parser when the user inserts content via copy & paste. If null parserRules will be used instead | 
| bsw/jbe@1309 | 15889       pasteParserRulesets: null, | 
| bsw/jbe@1309 | 15890       // Parser method to use when the user inserts content | 
| bsw/jbe@1309 | 15891       parser:               wysihtml.dom.parse, | 
| bsw/jbe@1309 | 15892       // By default wysihtml will insert a <br> for line breaks, set this to false to use <p> | 
| bsw/jbe@1309 | 15893       useLineBreaks:        true, | 
| bsw/jbe@1309 | 15894       // Double enter (enter on blank line) exits block element in useLineBreaks mode. | 
| bsw/jbe@1309 | 15895       // It enables a way of escaping out of block elements and splitting block elements | 
| bsw/jbe@1309 | 15896       doubleLineBreakEscapesBlock: true, | 
| bsw/jbe@1309 | 15897       // Array (or single string) of stylesheet urls to be loaded in the editor's iframe | 
| bsw/jbe@1309 | 15898       stylesheets:          [], | 
| bsw/jbe@1309 | 15899       // Placeholder text to use, defaults to the placeholder attribute on the textarea element | 
| bsw/jbe@1309 | 15900       placeholderText:      undef, | 
| bsw/jbe@1309 | 15901       // Whether the rich text editor should be rendered on touch devices (wysihtml >= 0.3.0 comes with basic support for iOS 5) | 
| bsw/jbe@1309 | 15902       supportTouchDevices:  true, | 
| bsw/jbe@1309 | 15903       // Whether senseless <span> elements (empty or without attributes) should be removed/replaced with their content | 
| bsw/jbe@1309 | 15904       cleanUp:              true, | 
| bsw/jbe@1309 | 15905       // Whether to use div instead of secure iframe | 
| bsw/jbe@1309 | 15906       contentEditableMode: false, | 
| bsw/jbe@1309 | 15907       classNames: { | 
| bsw/jbe@1309 | 15908         // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option | 
| bsw/jbe@1309 | 15909         composer: "wysihtml-editor", | 
| bsw/jbe@1309 | 15910         // Class name to add to the body when the wysihtml editor is supported | 
| bsw/jbe@1309 | 15911         body: "wysihtml-supported", | 
| bsw/jbe@1309 | 15912         // classname added to editable area element (iframe/div) on creation | 
| bsw/jbe@1309 | 15913         sandbox: "wysihtml-sandbox", | 
| bsw/jbe@1309 | 15914         // class on editable area with placeholder | 
| bsw/jbe@1309 | 15915         placeholder: "wysihtml-placeholder", | 
| bsw/jbe@1309 | 15916         // Classname of container that editor should not touch and pass through | 
| bsw/jbe@1309 | 15917         uneditableContainer: "wysihtml-uneditable-container" | 
| bsw/jbe@1309 | 15918       }, | 
| bsw/jbe@1309 | 15919       // Browsers that support copied source handling will get a marking of the origin of the copied source (for determinig code cleanup rules on paste) | 
| bsw/jbe@1309 | 15920       // Also copied source is based directly on selection - | 
| bsw/jbe@1309 | 15921       // (very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection). | 
| bsw/jbe@1309 | 15922       // If falsy value is passed source override is also disabled | 
| bsw/jbe@1309 | 15923       copyedFromMarking: '<meta name="copied-from" content="wysihtml">' | 
| bsw/jbe@1309 | 15924     }, | 
| bsw/jbe@1309 | 15925 | 
| bsw/jbe@1309 | 15926     constructor: function(editableElement, config) { | 
| bsw/jbe@1309 | 15927       this.editableElement  = typeof(editableElement) === "string" ? document.getElementById(editableElement) : editableElement; | 
| bsw/jbe@1309 | 15928       this.config           = wysihtml.lang.object({}).merge(this.defaults).merge(config).get(); | 
| bsw/jbe@1309 | 15929       this._isCompatible    = wysihtml.browser.supported(); | 
| bsw/jbe@1309 | 15930 | 
| bsw/jbe@1309 | 15931       // merge classNames | 
| bsw/jbe@1309 | 15932       if (config && config.classNames) { | 
| bsw/jbe@1309 | 15933         wysihtml.lang.object(this.config.classNames).merge(config.classNames); | 
| bsw/jbe@1309 | 15934       } | 
| bsw/jbe@1309 | 15935 | 
| bsw/jbe@1309 | 15936       if (this.editableElement.nodeName.toLowerCase() != "textarea") { | 
| bsw/jbe@1309 | 15937           this.config.contentEditableMode = true; | 
| bsw/jbe@1309 | 15938           this.config.noTextarea = true; | 
| bsw/jbe@1309 | 15939       } | 
| bsw/jbe@1309 | 15940       if (!this.config.noTextarea) { | 
| bsw/jbe@1309 | 15941           this.textarea         = new wysihtml.views.Textarea(this, this.editableElement, this.config); | 
| bsw/jbe@1309 | 15942           this.currentView      = this.textarea; | 
| bsw/jbe@1309 | 15943       } | 
| bsw/jbe@1309 | 15944 | 
| bsw/jbe@1309 | 15945       // Sort out unsupported/unwanted browsers here | 
| bsw/jbe@1309 | 15946       if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml.browser.isTouchDevice())) { | 
| bsw/jbe@1309 | 15947         var that = this; | 
| bsw/jbe@1309 | 15948         setTimeout(function() { that.fire("beforeload").fire("load"); }, 0); | 
| bsw/jbe@1309 | 15949         return; | 
| bsw/jbe@1309 | 15950       } | 
| bsw/jbe@1309 | 15951 | 
| bsw/jbe@1309 | 15952       // Add class name to body, to indicate that the editor is supported | 
| bsw/jbe@1309 | 15953       wysihtml.dom.addClass(document.body, this.config.classNames.body); | 
| bsw/jbe@1309 | 15954 | 
| bsw/jbe@1309 | 15955       this.composer = new wysihtml.views.Composer(this, this.editableElement, this.config); | 
| bsw/jbe@1309 | 15956       this.currentView = this.composer; | 
| bsw/jbe@1309 | 15957 | 
| bsw/jbe@1309 | 15958       if (typeof(this.config.parser) === "function") { | 
| bsw/jbe@1309 | 15959         this._initParser(); | 
| bsw/jbe@1309 | 15960       } | 
| bsw/jbe@1309 | 15961 | 
| bsw/jbe@1309 | 15962       this.on("beforeload", this.handleBeforeLoad); | 
| bsw/jbe@1309 | 15963     }, | 
| bsw/jbe@1309 | 15964 | 
| bsw/jbe@1309 | 15965     handleBeforeLoad: function() { | 
| bsw/jbe@1309 | 15966         if (!this.config.noTextarea) { | 
| bsw/jbe@1309 | 15967           this.synchronizer = new wysihtml.views.Synchronizer(this, this.textarea, this.composer); | 
| bsw/jbe@1309 | 15968         } else { | 
| bsw/jbe@1309 | 15969           this.sourceView = new wysihtml.views.SourceView(this, this.composer); | 
| bsw/jbe@1309 | 15970         } | 
| bsw/jbe@1309 | 15971         this.runEditorExtenders(); | 
| bsw/jbe@1309 | 15972     }, | 
| bsw/jbe@1309 | 15973 | 
| bsw/jbe@1309 | 15974     runEditorExtenders: function() { | 
| bsw/jbe@1309 | 15975       wysihtml.editorExtenders.forEach(function(extender) { | 
| bsw/jbe@1309 | 15976         extender(this); | 
| bsw/jbe@1309 | 15977       }.bind(this)); | 
| bsw/jbe@1309 | 15978     }, | 
| bsw/jbe@1309 | 15979 | 
| bsw/jbe@1309 | 15980     isCompatible: function() { | 
| bsw/jbe@1309 | 15981       return this._isCompatible; | 
| bsw/jbe@1309 | 15982     }, | 
| bsw/jbe@1309 | 15983 | 
| bsw/jbe@1309 | 15984     clear: function() { | 
| bsw/jbe@1309 | 15985       this.currentView.clear(); | 
| bsw/jbe@1309 | 15986       return this; | 
| bsw/jbe@1309 | 15987     }, | 
| bsw/jbe@1309 | 15988 | 
| bsw/jbe@1309 | 15989     getValue: function(parse, clearInternals) { | 
| bsw/jbe@1309 | 15990       return this.currentView.getValue(parse, clearInternals); | 
| bsw/jbe@1309 | 15991     }, | 
| bsw/jbe@1309 | 15992 | 
| bsw/jbe@1309 | 15993     setValue: function(html, parse) { | 
| bsw/jbe@1309 | 15994       this.fire("unset_placeholder"); | 
| bsw/jbe@1309 | 15995 | 
| bsw/jbe@1309 | 15996       if (!html) { | 
| bsw/jbe@1309 | 15997         return this.clear(); | 
| bsw/jbe@1309 | 15998       } | 
| bsw/jbe@1309 | 15999 | 
| bsw/jbe@1309 | 16000       this.currentView.setValue(html, parse); | 
| bsw/jbe@1309 | 16001       return this; | 
| bsw/jbe@1309 | 16002     }, | 
| bsw/jbe@1309 | 16003 | 
| bsw/jbe@1309 | 16004     cleanUp: function(rules) { | 
| bsw/jbe@1309 | 16005         this.currentView.cleanUp(rules); | 
| bsw/jbe@1309 | 16006     }, | 
| bsw/jbe@1309 | 16007 | 
| bsw/jbe@1309 | 16008     focus: function(setToEnd) { | 
| bsw/jbe@1309 | 16009       this.currentView.focus(setToEnd); | 
| bsw/jbe@1309 | 16010       return this; | 
| bsw/jbe@1309 | 16011     }, | 
| bsw/jbe@1309 | 16012 | 
| bsw/jbe@1309 | 16013     /** | 
| bsw/jbe@1309 | 16014      * Deactivate editor (make it readonly) | 
| bsw/jbe@1309 | 16015      */ | 
| bsw/jbe@1309 | 16016     disable: function() { | 
| bsw/jbe@1309 | 16017       this.currentView.disable(); | 
| bsw/jbe@1309 | 16018       return this; | 
| bsw/jbe@1309 | 16019     }, | 
| bsw/jbe@1309 | 16020 | 
| bsw/jbe@1309 | 16021     /** | 
| bsw/jbe@1309 | 16022      * Activate editor | 
| bsw/jbe@1309 | 16023      */ | 
| bsw/jbe@1309 | 16024     enable: function() { | 
| bsw/jbe@1309 | 16025       this.currentView.enable(); | 
| bsw/jbe@1309 | 16026       return this; | 
| bsw/jbe@1309 | 16027     }, | 
| bsw/jbe@1309 | 16028 | 
| bsw/jbe@1309 | 16029     isEmpty: function() { | 
| bsw/jbe@1309 | 16030       return this.currentView.isEmpty(); | 
| bsw/jbe@1309 | 16031     }, | 
| bsw/jbe@1309 | 16032 | 
| bsw/jbe@1309 | 16033     hasPlaceholderSet: function() { | 
| bsw/jbe@1309 | 16034       return this.currentView.hasPlaceholderSet(); | 
| bsw/jbe@1309 | 16035     }, | 
| bsw/jbe@1309 | 16036 | 
| bsw/jbe@1309 | 16037     destroy: function() { | 
| bsw/jbe@1309 | 16038       if (this.composer && this.composer.sandbox) { | 
| bsw/jbe@1309 | 16039         this.composer.sandbox.destroy(); | 
| bsw/jbe@1309 | 16040       } | 
| bsw/jbe@1309 | 16041       this.fire("destroy:composer"); | 
| bsw/jbe@1309 | 16042       this.off(); | 
| bsw/jbe@1309 | 16043     }, | 
| bsw/jbe@1309 | 16044 | 
| bsw/jbe@1309 | 16045     parse: function(htmlOrElement, clearInternals, customRules) { | 
| bsw/jbe@1309 | 16046       var parseContext = (this.config.contentEditableMode) ? document : ((this.composer) ? this.composer.sandbox.getDocument() : null); | 
| bsw/jbe@1309 | 16047       var returnValue = this.config.parser(htmlOrElement, { | 
| bsw/jbe@1309 | 16048         "rules": customRules || this.config.parserRules, | 
| bsw/jbe@1309 | 16049         "cleanUp": this.config.cleanUp, | 
| bsw/jbe@1309 | 16050         "context": parseContext, | 
| bsw/jbe@1309 | 16051         "uneditableClass": this.config.classNames.uneditableContainer, | 
| bsw/jbe@1309 | 16052         "clearInternals" : clearInternals | 
| bsw/jbe@1309 | 16053       }); | 
| bsw/jbe@1309 | 16054       if (typeof(htmlOrElement) === "object") { | 
| bsw/jbe@1309 | 16055         wysihtml.quirks.redraw(htmlOrElement); | 
| bsw/jbe@1309 | 16056       } | 
| bsw/jbe@1309 | 16057       return returnValue; | 
| bsw/jbe@1309 | 16058     }, | 
| bsw/jbe@1309 | 16059 | 
| bsw/jbe@1309 | 16060     /** | 
| bsw/jbe@1309 | 16061      * Prepare html parser logic | 
| bsw/jbe@1309 | 16062      *  - Observes for paste and drop | 
| bsw/jbe@1309 | 16063      */ | 
| bsw/jbe@1309 | 16064     _initParser: function() { | 
| bsw/jbe@1309 | 16065       var oldHtml; | 
| bsw/jbe@1309 | 16066 | 
| bsw/jbe@1309 | 16067       if (wysihtml.browser.supportsModernPaste()) { | 
| bsw/jbe@1309 | 16068         this.on("paste:composer", function(event) { | 
| bsw/jbe@1309 | 16069           event.preventDefault(); | 
| bsw/jbe@1309 | 16070           oldHtml = wysihtml.dom.getPastedHtml(event); | 
| bsw/jbe@1309 | 16071           if (oldHtml) { | 
| bsw/jbe@1309 | 16072             this._cleanAndPaste(oldHtml); | 
| bsw/jbe@1309 | 16073           } | 
| bsw/jbe@1309 | 16074         }.bind(this)); | 
| bsw/jbe@1309 | 16075 | 
| bsw/jbe@1309 | 16076       } else { | 
| bsw/jbe@1309 | 16077         this.on("beforepaste:composer", function(event) { | 
| bsw/jbe@1309 | 16078           event.preventDefault(); | 
| bsw/jbe@1309 | 16079           var scrollPos = this.composer.getScrollPos(); | 
| bsw/jbe@1309 | 16080 | 
| bsw/jbe@1309 | 16081           wysihtml.dom.getPastedHtmlWithDiv(this.composer, function(pastedHTML) { | 
| bsw/jbe@1309 | 16082             if (pastedHTML) { | 
| bsw/jbe@1309 | 16083               this._cleanAndPaste(pastedHTML); | 
| bsw/jbe@1309 | 16084             } | 
| bsw/jbe@1309 | 16085             this.composer.setScrollPos(scrollPos); | 
| bsw/jbe@1309 | 16086           }.bind(this)); | 
| bsw/jbe@1309 | 16087 | 
| bsw/jbe@1309 | 16088         }.bind(this)); | 
| bsw/jbe@1309 | 16089       } | 
| bsw/jbe@1309 | 16090     }, | 
| bsw/jbe@1309 | 16091 | 
| bsw/jbe@1309 | 16092     _cleanAndPaste: function (oldHtml) { | 
| bsw/jbe@1309 | 16093       var cleanHtml = wysihtml.quirks.cleanPastedHTML(oldHtml, { | 
| bsw/jbe@1309 | 16094         "referenceNode": this.composer.element, | 
| bsw/jbe@1309 | 16095         "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}], | 
| bsw/jbe@1309 | 16096         "uneditableClass": this.config.classNames.uneditableContainer | 
| bsw/jbe@1309 | 16097       }); | 
| bsw/jbe@1309 | 16098       this.composer.selection.deleteContents(); | 
| bsw/jbe@1309 | 16099       this.composer.selection.insertHTML(cleanHtml); | 
| bsw/jbe@1309 | 16100     } | 
| bsw/jbe@1309 | 16101   }); | 
| bsw/jbe@1309 | 16102 })(wysihtml); |