| 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);
 |