PageSourceSearch

https://hannahimlach.com/_jsapps/inline-editor/cargoEditor.js

js hannahimlach.com collected 2026-10-04 13:23:40 UTC 542,354 bytes, 14,903 lines download raw bytes

1/*! cargoEditor - v0.2.0 - 2015-05-05 */ 
2
3
4// Copyright 2011 Google Inc.
5//
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//     http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17var __extends = this.__extends || function (d, b) {
18    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
19    function __() { this.constructor = d; }
20    __.prototype = b.prototype;
21    d.prototype = new __();
22};
23var MutationObserverCtor;
24if (typeof WebKitMutationObserver !== 'undefined')
25    MutationObserverCtor = WebKitMutationObserver;
26else
27    MutationObserverCtor = MutationObserver;
28
29if (MutationObserverCtor === undefined) {
30    console.error('DOM Mutation Observers are required.');
31    console.error('https://developer.mozilla.org/en-US/docs/DOM/MutationObserver');
32    throw Error('DOM Mutation Observers are required');
33}
34
35var NodeMap = (function () {
36    function NodeMap() {
37        this.nodes = [];
38        this.values = [];
39    }
40    NodeMap.prototype.isIndex = function (s) {
41        return +s === s >>> 0;
42    };
43
44    NodeMap.prototype.nodeId = function (node) {
45        var id = node[NodeMap.ID_PROP];
46        if (!id)
47            id = node[NodeMap.ID_PROP] = NodeMap.nextId_++;
48        return id;
49    };
50
51    NodeMap.prototype.set = function (node, value) {
52        var id = this.nodeId(node);
53        this.nodes[id] = node;
54        this.values[id] = value;
55    };
56
57    NodeMap.prototype.get = function (node) {
58        var id = this.nodeId(node);
59        return this.values[id];
60    };
61
62    NodeMap.prototype.has = function (node) {
63        return this.nodeId(node) in this.nodes;
64    };
65
66    NodeMap.prototype.delete = function (node) {
67        var id = this.nodeId(node);
68        delete this.nodes[id];
69        this.values[id] = undefined;
70    };
71
72    NodeMap.prototype.keys = function () {
73        var nodes = [];
74        for (var id in this.nodes) {
75            if (!this.isIndex(id))
76                continue;
77            nodes.push(this.nodes[id]);
78        }
79
80        return nodes;
81    };
82    NodeMap.ID_PROP = '__mutation_summary_node_map_id__';
83    NodeMap.nextId_ = 1;
84    return NodeMap;
85})();
86
87/**
88*  var reachableMatchableProduct = [
89*  //  STAYED_OUT,  ENTERED,     STAYED_IN,   EXITED
90*    [ STAYED_OUT,  STAYED_OUT,  STAYED_OUT,  STAYED_OUT ], // STAYED_OUT
91*    [ STAYED_OUT,  ENTERED,     ENTERED,     STAYED_OUT ], // ENTERED
92*    [ STAYED_OUT,  ENTERED,     STAYED_IN,   EXITED     ], // STAYED_IN
93*    [ STAYED_OUT,  STAYED_OUT,  EXITED,      EXITED     ]  // EXITED
94*  ];
95*/
96var Movement;
97(function (Movement) {
98    Movement[Movement["STAYED_OUT"] = 0] = "STAYED_OUT";
99    Movement[Movement["ENTERED"] = 1] = "ENTERED";
100    Movement[Movement["STAYED_IN"] = 2] = "STAYED_IN";
101    Movement[Movement["REPARENTED"] = 3] = "REPARENTED";
102    Movement[Movement["REORDERED"] = 4] = "REORDERED";
103    Movement[Movement["EXITED"] = 5] = "EXITED";
104})(Movement || (Movement = {}));
105
106function enteredOrExited(changeType) {
107    return changeType === 1 /* ENTERED */ || changeType === 5 /* EXITED */;
108}
109
110var NodeChange = (function () {
111    function NodeChange(node, childList, attributes, characterData, oldParentNode, added, attributeOldValues, characterDataOldValue) {
112        if (typeof childList === "undefined") { childList = false; }
113        if (typeof attributes === "undefined") { attributes = false; }
114        if (typeof characterData === "undefined") { characterData = false; }
115        if (typeof oldParentNode === "undefined") { oldParentNode = null; }
116        if (typeof added === "undefined") { added = false; }
117        if (typeof attributeOldValues === "undefined") { attributeOldValues = null; }
118        if (typeof characterDataOldValue === "undefined") { characterDataOldValue = null; }
119        this.node = node;
120        this.childList = childList;
121        this.attributes = attributes;
122        this.characterData = characterData;
123        this.oldParentNode = oldParentNode;
124        this.added = added;
125        this.attributeOldValues = attributeOldValues;
126        this.characterDataOldValue = characterDataOldValue;
127        this.isCaseInsensitive = this.node.nodeType === Node.ELEMENT_NODE && this.node instanceof HTMLElement && this.node.ownerDocument instanceof HTMLDocument;
128    }
129    NodeChange.prototype.getAttributeOldValue = function (name) {
130        if (!this.attributeOldValues)
131            return undefined;
132        if (this.isCaseInsensitive)
133            name = name.toLowerCase();
134        return this.attributeOldValues[name];
135    };
136
137    NodeChange.prototype.getAttributeNamesMutated = function () {
138        var names = [];
139        if (!this.attributeOldValues)
140            return names;
141        for (var name in this.attributeOldValues) {
142            names.push(name);
143        }
144        return names;
145    };
146
147    NodeChange.prototype.attributeMutated = function (name, oldValue) {
148        this.attributes = true;
149        this.attributeOldValues = this.attributeOldValues || {};
150
151        if (name in this.attributeOldValues)
152            return;
153
154        this.attributeOldValues[name] = oldValue;
155    };
156
157    NodeChange.prototype.characterDataMutated = function (oldValue) {
158        if (this.characterData)
159            return;
160        this.characterData = true;
161        this.characterDataOldValue = oldValue;
162    };
163
164    // Note: is it possible to receive a removal followed by a removal. This
165    // can occur if the removed node is added to an non-observed node, that
166    // node is added to the observed area, and then the node removed from
167    // it.
168    NodeChange.prototype.removedFromParent = function (parent) {
169        this.childList = true;
170        if (this.added || this.oldParentNode)
171            this.added = false;
172        else
173            this.oldParentNode = parent;
174    };
175
176    NodeChange.prototype.insertedIntoParent = function () {
177        this.childList = true;
178        this.added = true;
179    };
180
181    // An node's oldParent is
182    //   -its present parent, if its parentNode was not changed.
183    //   -null if the first thing that happened to it was an add.
184    //   -the node it was removed from if the first thing that happened to it
185    //      was a remove.
186    NodeChange.prototype.getOldParent = function () {
187        if (this.childList) {
188            if (this.oldParentNode)
189                return this.oldParentNode;
190            if (this.added)
191                return null;
192        }
193
194        return this.node.parentNode;
195    };
196    return NodeChange;
197})();
198
199var ChildListChange = (function () {
200    function ChildListChange() {
201        this.added = new NodeMap();
202        this.removed = new NodeMap();
203        this.maybeMoved = new NodeMap();
204        this.oldPrevious = new NodeMap();
205        this.moved = undefined;
206    }
207    return ChildListChange;
208})();
209
210var TreeChanges = (function (_super) {
211    __extends(TreeChanges, _super);
212    function TreeChanges(rootNode, mutations) {
213        _super.call(this);
214
215        this.rootNode = rootNode;
216        this.reachableCache = undefined;
217        this.wasReachableCache = undefined;
218        this.anyParentsChanged = false;
219        this.anyAttributesChanged = false;
220        this.anyCharacterDataChanged = false;
221
222        for (var m = 0; m < mutations.length; m++) {
223            var mutation = mutations[m];
224            switch (mutation.type) {
225                case 'childList':
226                    this.anyParentsChanged = true;
227                    for (var i = 0; i < mutation.removedNodes.length; i++) {
228                        var node = mutation.removedNodes[i];
229                        this.getChange(node).removedFromParent(mutation.target);
230                    }
231                    for (var i = 0; i < mutation.addedNodes.length; i++) {
232                        var node = mutation.addedNodes[i];
233                        this.getChange(node).insertedIntoParent();
234                    }
235                    break;
236
237                case 'attributes':
238                    this.anyAttributesChanged = true;
239                    var change = this.getChange(mutation.target);
240                    change.attributeMutated(mutation.attributeName, mutation.oldValue);
241                    break;
242
243                case 'characterData':
244                    this.anyCharacterDataChanged = true;
245                    var change = this.getChange(mutation.target);
246                    change.characterDataMutated(mutation.oldValue);
247                    break;
248            }
249        }
250    }
251    TreeChanges.prototype.getChange = function (node) {
252        var change = this.get(node);
253        if (!change) {
254            change = new NodeChange(node);
255            this.set(node, change);
256        }
257        return change;
258    };
259
260    TreeChanges.prototype.getOldParent = function (node) {
261        var change = this.get(node);
262        return change ? change.getOldParent() : node.parentNode;
263    };
264
265    TreeChanges.prototype.getIsReachable = function (node) {
266        if (node === this.rootNode)
267            return true;
268        if (!node)
269            return false;
270
271        this.reachableCache = this.reachableCache || new NodeMap();
272        var isReachable = this.reachableCache.get(node);
273        if (isReachable === undefined) {
274            isReachable = this.getIsReachable(node.parentNode);
275            this.reachableCache.set(node, isReachable);
276        }
277        return isReachable;
278    };
279
280    // A node wasReachable if its oldParent wasReachable.
281    TreeChanges.prototype.getWasReachable = function (node) {
282        if (node === this.rootNode)
283            return true;
284        if (!node)
285            return false;
286
287        this.wasReachableCache = this.wasReachableCache || new NodeMap();
288        var wasReachable = this.wasReachableCache.get(node);
289        if (wasReachable === undefined) {
290            wasReachable = this.getWasReachable(this.getOldParent(node));
291            this.wasReachableCache.set(node, wasReachable);
292        }
293        return wasReachable;
294    };
295
296    TreeChanges.prototype.reachabilityChange = function (node) {
297        if (this.getIsReachable(node)) {
298            return this.getWasReachable(node) ? 2 /* STAYED_IN */ : 1 /* ENTERED */;
299        }
300
301        return this.getWasReachable(node) ? 5 /* EXITED */ : 0 /* STAYED_OUT */;
302    };
303    return TreeChanges;
304})(NodeMap);
305
306var MutationProjection = (function () {
307    // TOOD(any)
308    function MutationProjection(rootNode, mutations, selectors, calcReordered, calcOldPreviousSibling) {
309        this.rootNode = rootNode;
310        this.mutations = mutations;
311        this.selectors = selectors;
312        this.calcReordered = calcReordered;
313        this.calcOldPreviousSibling = calcOldPreviousSibling;
314        this.treeChanges = new TreeChanges(rootNode, mutations);
315        this.entered = [];
316        this.exited = [];
317        this.stayedIn = new NodeMap();
318        this.visited = new NodeMap();
319        this.childListChangeMap = undefined;
320        this.characterDataOnly = undefined;
321        this.matchCache = undefined;
322
323        this.processMutations();
324    }
325    MutationProjection.prototype.processMutations = function () {
326        if (!this.treeChanges.anyParentsChanged && !this.treeChanges.anyAttributesChanged)
327            return;
328
329        var changedNodes = this.treeChanges.keys();
330        for (var i = 0; i < changedNodes.length; i++) {
331            this.visitNode(changedNodes[i], undefined);
332        }
333    };
334
335    MutationProjection.prototype.visitNode = function (node, parentReachable) {
336        if (this.visited.has(node))
337            return;
338
339        this.visited.set(node, true);
340
341        var change = this.treeChanges.get(node);
342        var reachable = parentReachable;
343
344        // node inherits its parent's reachability change unless
345        // its parentNode was mutated.
346        if ((change && change.childList) || reachable == undefined)
347            reachable = this.treeChanges.reachabilityChange(node);
348
349        if (reachable === 0 /* STAYED_OUT */)
350            return;
351
352        // Cache match results for sub-patterns.
353        this.matchabilityChange(node);
354
355        if (reachable === 1 /* ENTERED */) {
356            this.entered.push(node);
357        } else if (reachable === 5 /* EXITED */) {
358            this.exited.push(node);
359            this.ensureHasOldPreviousSiblingIfNeeded(node);
360        } else if (reachable === 2 /* STAYED_IN */) {
361            var movement = 2 /* STAYED_IN */;
362
363            if (change && change.childList) {
364                if (change.oldParentNode !== node.parentNode) {
365                    movement = 3 /* REPARENTED */;
366                    this.ensureHasOldPreviousSiblingIfNeeded(node);
367                } else if (this.calcReordered && this.wasReordered(node)) {
368                    movement = 4 /* REORDERED */;
369                }
370            }
371
372            this.stayedIn.set(node, movement);
373        }
374
375        if (reachable === 2 /* STAYED_IN */)
376            return;
377
378        for (var child = node.firstChild; child; child = child.nextSibling) {
379            this.visitNode(child, reachable);
380        }
381    };
382
383    MutationProjection.prototype.ensureHasOldPreviousSiblingIfNeeded = function (node) {
384        if (!this.calcOldPreviousSibling)
385            return;
386
387        this.processChildlistChanges();
388
389        var parentNode = node.parentNode;
390        var nodeChange = this.treeChanges.get(node);
391        if (nodeChange && nodeChange.oldParentNode)
392            parentNode = nodeChange.oldParentNode;
393
394        var change = this.childListChangeMap.get(parentNode);
395        if (!change) {
396            change = new ChildListChange();
397            this.childListChangeMap.set(parentNode, change);
398        }
399
400        if (!change.oldPrevious.has(node)) {
401            change.oldPrevious.set(node, node.previousSibling);
402        }
403    };
404
405    MutationProjection.prototype.getChanged = function (summary, selectors, characterDataOnly) {
406        this.selectors = selectors;
407        this.characterDataOnly = characterDataOnly;
408
409        for (var i = 0; i < this.entered.length; i++) {
410            var node = this.entered[i];
411            var matchable = this.matchabilityChange(node);
412            if (matchable === 1 /* ENTERED */ || matchable === 2 /* STAYED_IN */)
413                summary.added.push(node);
414        }
415
416        var stayedInNodes = this.stayedIn.keys();
417        for (var i = 0; i < stayedInNodes.length; i++) {
418            var node = stayedInNodes[i];
419            var matchable = this.matchabilityChange(node);
420
421            if (matchable === 1 /* ENTERED */) {
422                summary.added.push(node);
423            } else if (matchable === 5 /* EXITED */) {
424                summary.removed.push(node);
425            } else if (matchable === 2 /* STAYED_IN */ && (summary.reparented || summary.reordered)) {
426                var movement = this.stayedIn.get(node);
427                if (summary.reparented && movement === 3 /* REPARENTED */)
428                    summary.reparented.push(node);
429                else if (summary.reordered && movement === 4 /* REORDERED */)
430                    summary.reordered.push(node);
431            }
432        }
433
434        for (var i = 0; i < this.exited.length; i++) {
435            var node = this.exited[i];
436            var matchable = this.matchabilityChange(node);
437            if (matchable === 5 /* EXITED */ || matchable === 2 /* STAYED_IN */)
438                summary.removed.push(node);
439        }
440    };
441
442    MutationProjection.prototype.getOldParentNode = function (node) {
443        var change = this.treeChanges.get(node);
444        if (change && change.childList)
445            return change.oldParentNode ? change.oldParentNode : null;
446
447        var reachabilityChange = this.treeChanges.reachabilityChange(node);
448        if (reachabilityChange === 0 /* STAYED_OUT */ || reachabilityChange === 1 /* ENTERED */)
449            throw Error('getOldParentNode requested on invalid node.');
450
451        return node.parentNode;
452    };
453
454    MutationProjection.prototype.getOldPreviousSibling = function (node) {
455        var parentNode = node.parentNode;
456        var nodeChange = this.treeChanges.get(node);
457        if (nodeChange && nodeChange.oldParentNode)
458            parentNode = nodeChange.oldParentNode;
459
460        var change = this.childListChangeMap.get(parentNode);
461        if (!change)
462            throw Error('getOldPreviousSibling requested on invalid node.');
463
464        return change.oldPrevious.get(node);
465    };
466
467    MutationProjection.prototype.getOldAttribute = function (element, attrName) {
468        var change = this.treeChanges.get(element);
469        if (!change || !change.attributes)
470            throw Error('getOldAttribute requested on invalid node.');
471
472        var value = change.getAttributeOldValue(attrName);
473        if (value === undefined)
474            throw Error('getOldAttribute requested for unchanged attribute name.');
475
476        return value;
477    };
478
479    MutationProjection.prototype.attributeChangedNodes = function (includeAttributes) {
480        if (!this.treeChanges.anyAttributesChanged)
481            return {};
482
483        var attributeFilter;
484        var caseInsensitiveFilter;
485        if (includeAttributes) {
486            attributeFilter = {};
487            caseInsensitiveFilter = {};
488            for (var i = 0; i < includeAttributes.length; i++) {
489                var attrName = includeAttributes[i];
490                attributeFilter[attrName] = true;
491                caseInsensitiveFilter[attrName.toLowerCase()] = attrName;
492            }
493        }
494
495        var result = {};
496        var nodes = this.treeChanges.keys();
497
498        for (var i = 0; i < nodes.length; i++) {
499            var node = nodes[i];
500
501            var change = this.treeChanges.get(node);
502            if (!change.attributes)
503                continue;
504
505            if (2 /* STAYED_IN */ !== this.treeChanges.reachabilityChange(node) || 2 /* STAYED_IN */ !== this.matchabilityChange(node)) {
506                continue;
507            }
508
509            var element = node;
510            var changedAttrNames = change.getAttributeNamesMutated();
511            for (var j = 0; j < changedAttrNames.length; j++) {
512                var attrName = changedAttrNames[j];
513
514                if (attributeFilter && !attributeFilter[attrName] && !(change.
514isCaseInsensitive && caseInsensitiveFilter[attrName])) {
515                    continue;
516                }
517
518                var oldValue = change.getAttributeOldValue(attrName);
519                if (oldValue === element.getAttribute(attrName))
520                    continue;
521
522                if (caseInsensitiveFilter && change.isCaseInsensitive)
523                    attrName = caseInsensitiveFilter[attrName];
524
525                result[attrName] = result[attrName] || [];
526                result[attrName].push(element);
527            }
528        }
529
530        return result;
531    };
532
533    MutationProjection.prototype.getOldCharacterData = function (node) {
534        var change = this.treeChanges.get(node);
535        if (!change || !change.characterData)
536            throw Error('getOldCharacterData requested on invalid node.');
537
538        return change.characterDataOldValue;
539    };
540
541    MutationProjection.prototype.getCharacterDataChanged = function () {
542        if (!this.treeChanges.anyCharacterDataChanged)
543            return [];
544
545        var nodes = this.treeChanges.keys();
546        var result = [];
547        for (var i = 0; i < nodes.length; i++) {
548            var target = nodes[i];
549            if (2 /* STAYED_IN */ !== this.treeChanges.reachabilityChange(target))
550                continue;
551
552            var change = this.treeChanges.get(target);
553            if (!change.characterData || target.textContent == change.characterDataOldValue)
554                continue;
555
556            result.push(target);
557        }
558
559        return result;
560    };
561
562    MutationProjection.prototype.computeMatchabilityChange = function (selector, el) {
563        if (!this.matchCache)
564            this.matchCache = [];
565        if (!this.matchCache[selector.uid])
566            this.matchCache[selector.uid] = new NodeMap();
567
568        var cache = this.matchCache[selector.uid];
569        var result = cache.get(el);
570        if (result === undefined) {
571            result = selector.matchabilityChange(el, this.treeChanges.get(el));
572            cache.set(el, result);
573        }
574        return result;
575    };
576
577    MutationProjection.prototype.matchabilityChange = function (node) {
578        var _this = this;
579        // TODO(rafaelw): Include PI, CDATA?
580        // Only include text nodes.
581        if (this.characterDataOnly) {
582            switch (node.nodeType) {
583                case Node.COMMENT_NODE:
584                case Node.TEXT_NODE:
585                    return 2 /* STAYED_IN */;
586                default:
587                    return 0 /* STAYED_OUT */;
588            }
589        }
590
591        // No element filter. Include all nodes.
592        if (!this.selectors)
593            return 2 /* STAYED_IN */;
594
595        // Element filter. Exclude non-elements.
596        if (node.nodeType !== Node.ELEMENT_NODE)
597            return 0 /* STAYED_OUT */;
598
599        var el = node;
600
601        var matchChanges = this.selectors.map(function (selector) {
602            return _this.computeMatchabilityChange(selector, el);
603        });
604
605        var accum = 0 /* STAYED_OUT */;
606        var i = 0;
607
608        while (accum !== 2 /* STAYED_IN */ && i < matchChanges.length) {
609            switch (matchChanges[i]) {
610                case 2 /* STAYED_IN */:
611                    accum = 2 /* STAYED_IN */;
612                    break;
613                case 1 /* ENTERED */:
614                    if (accum === 5 /* EXITED */)
615                        accum = 2 /* STAYED_IN */;
616                    else
617                        accum = 1 /* ENTERED */;
618                    break;
619                case 5 /* EXITED */:
620                    if (accum === 1 /* ENTERED */)
621                        accum = 2 /* STAYED_IN */;
622                    else
623                        accum = 5 /* EXITED */;
624                    break;
625            }
626
627            i++;
628        }
629
630        return accum;
631    };
632
633    MutationProjection.prototype.getChildlistChange = function (el) {
634        var change = this.childListChangeMap.get(el);
635        if (!change) {
636            change = new ChildListChange();
637            this.childListChangeMap.set(el, change);
638        }
639
640        return change;
641    };
642
643    MutationProjection.prototype.processChildlistChanges = function () {
644        if (this.childListChangeMap)
645            return;
646
647        this.childListChangeMap = new NodeMap();
648
649        for (var i = 0; i < this.mutations.length; i++) {
650            var mutation = this.mutations[i];
651            if (mutation.type != 'childList')
652                continue;
653
654            if (this.treeChanges.reachabilityChange(mutation.target) !== 2 /* STAYED_IN */ && !this.calcOldPreviousSibling)
655                continue;
656
657            var change = this.getChildlistChange(mutation.target);
658
659            var oldPrevious = mutation.previousSibling;
660
661            function recordOldPrevious(node, previous) {
662                if (!node || change.oldPrevious.has(node) || change.added.has(node) || change.maybeMoved.has(node))
663                    return;
664
665                if (previous && (change.added.has(previous) || change.maybeMoved.has(previous)))
666                    return;
667
668                change.oldPrevious.set(node, previous);
669            }
670
671            for (var j = 0; j < mutation.removedNodes.length; j++) {
672                var node = mutation.removedNodes[j];
673                recordOldPrevious(node, oldPrevious);
674
675                if (change.added.has(node)) {
676                    change.added.delete(node);
677                } else {
678                    change.removed.set(node, true);
679                    change.maybeMoved.delete(node);
680                }
681
682                oldPrevious = node;
683            }
684
685            recordOldPrevious(mutation.nextSibling, oldPrevious);
686
687            for (var j = 0; j < mutation.addedNodes.length; j++) {
688                var node = mutation.addedNodes[j];
689                if (change.removed.has(node)) {
690                    change.removed.delete(node);
691                    change.maybeMoved.set(node, true);
692                } else {
693                    change.added.set(node, true);
694                }
695            }
696        }
697    };
698
699    MutationProjection.prototype.wasReordered = function (node) {
700        if (!this.treeChanges.anyParentsChanged)
701            return false;
702
703        this.processChildlistChanges();
704
705        var parentNode = node.parentNode;
706        var nodeChange = this.treeChanges.get(node);
707        if (nodeChange && nodeChange.oldParentNode)
708            parentNode = nodeChange.oldParentNode;
709
710        var change = this.childListChangeMap.get(parentNode);
711        if (!change)
712            return false;
713
714        if (change.moved)
715            return change.moved.get(node);
716
717        change.moved = new NodeMap();
718        var pendingMoveDecision = new NodeMap();
719
720        function isMoved(node) {
721            if (!node)
722                return false;
723            if (!change.maybeMoved.has(node))
724                return false;
725
726            var didMove = change.moved.get(node);
727            if (didMove !== undefined)
728                return didMove;
729
730            if (pendingMoveDecision.has(node)) {
731                didMove = true;
732            } else {
733                pendingMoveDecision.set(node, true);
734                didMove = getPrevious(node) !== getOldPrevious(node);
735            }
736
737            if (pendingMoveDecision.has(node)) {
738                pendingMoveDecision.delete(node);
739                change.moved.set(node, didMove);
740            } else {
741                didMove = change.moved.get(node);
742            }
743
744            return didMove;
745        }
746
747        var oldPreviousCache = new NodeMap();
748        function getOldPrevious(node) {
749            var oldPrevious = oldPreviousCache.get(node);
750            if (oldPrevious !== undefined)
751                return oldPrevious;
752
753            oldPrevious = change.oldPrevious.get(node);
754            while (oldPrevious && (change.removed.has(oldPrevious) || isMoved(oldPrevious))) {
755                oldPrevious = getOldPrevious(oldPrevious);
756            }
757
758            if (oldPrevious === undefined)
759                oldPrevious = node.previousSibling;
760            oldPreviousCache.set(node, oldPrevious);
761
762            return oldPrevious;
763        }
764
765        var previousCache = new NodeMap();
766        function getPrevious(node) {
767            if (previousCache.has(node))
768                return previousCache.get(node);
769
770            var previous = node.previousSibling;
771            while (previous && (change.added.has(previous) || isMoved(previous)))
772                previous = previous.previousSibling;
773
774            previousCache.set(node, previous);
775            return previous;
776        }
777
778        change.maybeMoved.keys().forEach(isMoved);
779        return change.moved.get(node);
780    };
781    return MutationProjection;
782})();
783
784var Summary = (function () {
785    function Summary(projection, query) {
786        var _this = this;
787        this.projection = projection;
788        this.added = [];
789        this.removed = [];
790        this.reparented = query.all || query.element ? [] : undefined;
791        this.reordered = query.all ? [] : undefined;
792
793        projection.getChanged(this, query.elementFilter, query.characterData);
794
795        if (query.all || query.attribute || query.attributeList) {
796            var filter = query.attribute ? [query.attribute] : query.attributeList;
797            var attributeChanged = projection.attributeChangedNodes(filter);
798
799            if (query.attribute) {
800                this.valueChanged = attributeChanged[query.attribute] || [];
801            } else {
802                this.attributeChanged = attributeChanged;
803                if (query.attributeList) {
804                    query.attributeList.forEach(function (attrName) {
805                        if (!_this.attributeChanged.hasOwnProperty(attrName))
806                            _this.attributeChanged[attrName] = [];
807                    });
808                }
809            }
810        }
811
812        if (query.all || query.characterData) {
813            var characterDataChanged = projection.getCharacterDataChanged();
814
815            if (query.characterData)
816                this.valueChanged = characterDataChanged;
817            else
818                this.characterDataChanged = characterDataChanged;
819        }
820
821        if (this.reordered)
822            this.getOldPreviousSibling = projection.getOldPreviousSibling.bind(projection);
823    }
824    Summary.prototype.getOldParentNode = function (node) {
825        return this.projection.getOldParentNode(node);
826    };
827
828    Summary.prototype.getOldAttribute = function (node, name) {
829        return this.projection.getOldAttribute(node, name);
830    };
831
832    Summary.prototype.getOldCharacterData = function (node) {
833        return this.projection.getOldCharacterData(node);
834    };
835
836    Summary.prototype.getOldPreviousSibling = function (node) {
837        return this.projection.getOldPreviousSibling(node);
838    };
839    return Summary;
840})();
841
842// TODO(rafaelw): Allow ':' and '.' as valid name characters.
843var validNameInitialChar = /[a-zA-Z_]+/;
844var validNameNonInitialChar = /[a-zA-Z0-9_\-]+/;
845
846// TODO(rafaelw): Consider allowing backslash in the attrValue.
847// TODO(rafaelw): There's got a to be way to represent this state machine
848// more compactly???
849function escapeQuotes(value) {
850    return '"' + value.replace(/"/, '\\\"') + '"';
851}
852
853var Qualifier = (function () {
854    function Qualifier() {
855    }
856    Qualifier.prototype.matches = function (oldValue) {
857        if (oldValue === null)
858            return false;
859
860        if (this.attrValue === undefined)
861            return true;
862
863        if (!this.contains)
864            return this.attrValue == oldValue;
865
866        var tokens = oldValue.split(' ');
867        for (var i = 0; i < tokens.length; i++) {
868            if (this.attrValue === tokens[i])
869                return true;
870        }
871
872        return false;
873    };
874
875    Qualifier.prototype.toString = function () {
876        if (this.attrName === 'class' && this.contains)
877            return '.' + this.attrValue;
878
879        if (this.attrName === 'id' && !this.contains)
880            return '#' + this.attrValue;
881
882        if (this.contains)
883            return '[' + this.attrName + '~=' + escapeQuotes(this.attrValue) + ']';
884
885        if ('attrValue' in this)
886            return '[' + this.attrName + '=' + escapeQuotes(this.attrValue) + ']';
887
888        return '[' + this.attrName + ']';
889    };
890    return Qualifier;
891})();
892
893var Selector = (function () {
894    function Selector() {
895        this.uid = Selector.nextUid++;
896        this.qualifiers = [];
897    }
898    Object.defineProperty(Selector.prototype, "caseInsensitiveTagName", {
899        get: function () {
900            return this.tagName.toUpperCase();
901        },
902        enumerable: true,
903        configurable: true
904    });
905
906    Object.defineProperty(Selector.prototype, "selectorString", {
907        get: function () {
908            return this.tagName + this.qualifiers.join('');
909        },
910        enumerable: true,
911        configurable: true
912    });
913
914    Selector.prototype.isMatching = function (el) {
915        return el[Selector.matchesSelector](this.selectorString);
916    };
917
918    Selector.prototype.wasMatching = function (el, change, isMatching) {
919        if (!change || !change.attributes)
920            return isMatching;
921
922        var tagName = change.isCaseInsensitive ? this.caseInsensitiveTagName : this.tagName;
923        if (tagName !== '*' && tagName !== el.tagName)
924            return false;
925
926        var attributeOldValues = [];
927        var anyChanged = false;
928        for (var i = 0; i < this.qualifiers.length; i++) {
929            var qualifier = this.qualifiers[i];
930            var oldValue = change.getAttributeOldValue(qualifier.attrName);
931            attributeOldValues.push(oldValue);
932            anyChanged = anyChanged || (oldValue !== undefined);
933        }
934
935        if (!anyChanged)
936            return isMatching;
937
938        for (var i = 0; i < this.qualifiers.length; i++) {
939            var qualifier = this.qualifiers[i];
940            var oldValue = attributeOldValues[i];
941            if (oldValue === undefined)
942                oldValue = el.getAttribute(qualifier.attrName);
943            if (!qualifier.matches(oldValue))
944                return false;
945        }
946
947        return true;
948    };
949
950    Selector.prototype.matchabilityChange = function (el, change) {
951        var isMatching = this.isMatching(el);
952        if (isMatching)
953            return this.wasMatching(el, change, isMatching) ? 2 /* STAYED_IN */ : 1 /* ENTERED */;
954        else
955            return this.wasMatching(el, change, isMatching) ? 5 /* EXITED */ : 0 /* STAYED_OUT */;
956    };
957
958    Selector.parseSelectors = function (input) {
959        var selectors = [];
960        var currentSelector;
961        var currentQualifier;
962
963        function newSelector() {
964            if (currentSelector) {
965                if (currentQualifier) {
966                    currentSelector.qualifiers.push(currentQualifier);
967                    currentQualifier = undefined;
968                }
969
970                selectors.push(currentSelector);
971            }
972            currentSelector = new Selector();
973        }
974
975        function newQualifier() {
976            if (currentQualifier)
977                currentSelector.qualifiers.push(currentQualifier);
978
979            currentQualifier = new Qualifier();
980        }
981
982        var WHITESPACE = /\s/;
983        var valueQuoteChar;
984        var SYNTAX_ERROR = 'Invalid or unsupported selector syntax.';
985
986        var SELECTOR = 1;
987        var TAG_NAME = 2;
988        var QUALIFIER = 3;
989        var QUALIFIER_NAME_FIRST_CHAR = 4;
990        var QUALIFIER_NAME = 5;
991        var ATTR_NAME_FIRST_CHAR = 6;
992        var ATTR_NAME = 7;
993        var EQUIV_OR_ATTR_QUAL_END = 8;
994        var EQUAL = 9;
995        var ATTR_QUAL_END = 10;
996        var VALUE_FIRST_CHAR = 11;
997        var VALUE = 12;
998        var QUOTED_VALUE = 13;
999        var SELECTOR_SEPARATOR = 14;
1000
1001        var state = SELECTOR;
1002        var i = 0;
1003        while (i < input.length) {
1004            var c = input[i++];
1005
1006            switch (state) {
1007                case SELECTOR:
1008                    if (c.match(validNameInitialChar)) {
1009                        newSelector();
1010                        currentSelector.tagName = c;
1011                        state = TAG_NAME;
1012                        break;
1013                    }
1014
1015                    if (c == '*') {
1016                        newSelector();
1017                        currentSelector.tagName = '*';
1018                        state = QUALIFIER;
1019                        break;
1020                    }
1021
1022                    if (c == '.') {
1023                        newSelector();
1024                        newQualifier();
1025                        currentSelector.tagName = '*';
1026                        currentQualifier.attrName = 'class';
1027                        currentQualifier.contains = true;
1028                        state = QUALIFIER_NAME_FIRST_CHAR;
1029                        break;
1030                    }
1031                    if (c == '#') {
1032                        newSelector();
1033                        newQualifier();
1034                        currentSelector.tagName = '*';
1035                        currentQualifier.attrName = 'id';
1036                        state = QUALIFIER_NAME_FIRST_CHAR;
1037                        break;
1038                    }
1039                    if (c == '[') {
1040                        newSelector();
1041                        newQualifier();
1042                        currentSelector.tagName = '*';
1043                        currentQualifier.attrName = '';
1044                        state = ATTR_NAME_FIRST_CHAR;
1045                        break;
1046                    }
1047
1048                    if (c.match(WHITESPACE))
1049                        break;
1050
1051                    throw Error(SYNTAX_ERROR);
1052
1053                case TAG_NAME:
1054                    if (c.match(validNameNonInitialChar)) {
1055                        currentSelector.tagName += c;
1056                        break;
1057                    }
1058
1059                    if (c == '.') {
1060                        newQualifier();
1061                        currentQualifier.attrName = 'class';
1062                        currentQualifier.contains = true;
1063                        state = QUALIFIER_NAME_FIRST_CHAR;
1064                        break;
1065                    }
1066                    if (c == '#') {
1067                        newQualifier();
1068                        currentQualifier.attrName = 'id';
1069                        state = QUALIFIER_NAME_FIRST_CHAR;
1070                        break;
1071                    }
1072                    if (c == '[') {
1073                        newQualifier();
1074                        currentQualifier.attrName = '';
1075                        state = ATTR_NAME_FIRST_CHAR;
1076                        break;
1077                    }
1078
1079                    if (c.match(WHITESPACE)) {
1080                        state = SELECTOR_SEPARATOR;
1081                        break;
1082                    }
1083
1084                    if (c == ',') {
1085                        state = SELECTOR;
1086                        break;
1087                    }
1088
1089                    throw Error(SYNTAX_ERROR);
1090
1091                case QUALIFIER:
1092                    if (c == '.') {
1093                        newQualifier();
1094                        currentQualifier.attrName = 'class';
1095                        currentQualifier.contains = true;
1096                        state = QUALIFIER_NAME_FIRST_CHAR;
1097                        break;
1098                    }
1099                    if (c == '#') {
1100                        newQualifier();
1101                        currentQualifier.attrName = 'id';
1102                        state = QUALIFIER_NAME_FIRST_CHAR;
1103                        break;
1104                    }
1105                    if (c == '[') {
1106                        newQualifier();
1107                        currentQualifier.attrName = '';
1108                        state = ATTR_NAME_FIRST_CHAR;
1109                        break;
1110                    }
1111
1112                    if (c.match(WHITESPACE)) {
1113                        state = SELECTOR_SEPARATOR;
1114                        break;
1115                    }
1116
1117                    if (c == ',') {
1118                        state = SELECTOR;
1119                        break;
1120                    }
1121
1122                    throw Error(SYNTAX_ERROR);
1123
1124                case QUALIFIER_NAME_FIRST_CHAR:
1125                    if (c.match(validNameInitialChar)) {
1126                        currentQualifier.attrValue = c;
1127                        state = QUALIFIER_NAME;
1128                        break;
1129                    }
1130
1131                    throw Error(SYNTAX_ERROR);
1132
1133                case QUALIFIER_NAME:
1134                    if (c.match(validNameNonInitialChar)) {
1135                        currentQualifier.attrValue += c;
1136                        break;
1137                    }
1138
1139                    if (c == '.') {
1140                        newQualifier();
1141                        currentQualifier.attrName = 'class';
1142                        currentQualifier.contains = true;
1143                        state = QUALIFIER_NAME_FIRST_CHAR;
1144                        break;
1145                    }
1146                    if (c == '#') {
1147                        newQualifier();
1148                        currentQualifier.attrName = 'id';
1149                        state = QUALIFIER_NAME_FIRST_CHAR;
1150                        break;
1151                    }
1152                    if (c == '[') {
1153                        newQualifier();
1154                        state = ATTR_NAME_FIRST_CHAR;
1155                        break;
1156                    }
1157
1158                    if (c.match(WHITESPACE)) {
1159                        state = SELECTOR_SEPARATOR;
1160                        break;
1161                    }
1162                    if (c == ',') {
1163                        state = SELECTOR;
1164                        break;
1165                    }
1166
1167                    throw Error(SYNTAX_ERROR);
1168
1169                case ATTR_NAME_FIRST_CHAR:
1170                    if (c.match(validNameInitialChar)) {
1171                        currentQualifier.attrName = c;
1172                        state = ATTR_NAME;
1173                        break;
1174                    }
1175
1176                    if (c.match(WHITESPACE))
1177                        break;
1178
1179                    throw Error(SYNTAX_ERROR);
1180
1181                case ATTR_NAME:
1182                    if (c.match(validNameNonInitialChar)) {
1183                        currentQualifier.attrName += c;
1184                        break;
1185                    }
1186
1187                    if (c.match(WHITESPACE)) {
1188                        state = EQUIV_OR_ATTR_QUAL_END;
1189                        break;
1190                    }
1191
1192                    if (c == '~') {
1193                        currentQualifier.contains = true;
1194                        state = EQUAL;
1195                        break;
1196                    }
1197
1198                    if (c == '=') {
1199                        currentQualifier.attrValue = '';
1200                        state = VALUE_FIRST_CHAR;
1201                        break;
1202                    }
1203
1204                    if (c == ']') {
1205                        state = QUALIFIER;
1206                        break;
1207                    }
1208
1209                    throw Error(SYNTAX_ERROR);
1210
1211                case EQUIV_OR_ATTR_QUAL_END:
1212                    if (c == '~') {
1213                        currentQualifier.contains = true;
1214                        state = EQUAL;
1215                        break;
1216                    }
1217
1218                    if (c == '=') {
1219                        currentQualifier.attrValue = '';
1220                        state = VALUE_FIRST_CHAR;
1221                        break;
1222                    }
1223
1224                    if (c == ']') {
1225                        state = QUALIFIER;
1226                        break;
1227                    }
1228
1229                    if (c.match(WHITESPACE))
1230                        break;
1231
1232                    throw Error(SYNTAX_ERROR);
1233
1234                case EQUAL:
1235                    if (c == '=') {
1236                        currentQualifier.attrValue = '';
1237                        state = VALUE_FIRST_CHAR;
1238                        break;
1239                    }
1240
1241                    throw Error(SYNTAX_ERROR);
1242
1243                case ATTR_QUAL_END:
1244                    if (c == ']') {
1245                        state = QUALIFIER;
1246                        break;
1247                    }
1248
1249                    if (c.match(WHITESPACE))
1250                        break;
1251
1252                    throw Error(SYNTAX_ERROR);
1253
1254                case VALUE_FIRST_CHAR:
1255                    if (c.match(WHITESPACE))
1256                        break;
1257
1258                    if (c == '"' || c == "'") {
1259                        valueQuoteChar = c;
1260                        state = QUOTED_VALUE;
1261                        break;
1262                    }
1263
1264                    currentQualifier.attrValue += c;
1265                    state = VALUE;
1266                    break;
1267
1268                case VALUE:
1269                    if (c.match(WHITESPACE)) {
1270                        state = ATTR_QUAL_END;
1271                        break;
1272                    }
1273                    if (c == ']') {
1274                        state = QUALIFIER;
1275                        break;
1276                    }
1277                    if (c == "'" || c == '"')
1278                        throw Error(SYNTAX_ERROR);
1279
1280                    currentQualifier.attrValue += c;
1281                    break;
1282
1283                case QUOTED_VALUE:
1284                    if (c == valueQuoteChar) {
1285                        state = ATTR_QUAL_END;
1286                        break;
1287                    }
1288
1289                    currentQualifier.attrValue += c;
1290                    break;
1291
1292                case SELECTOR_SEPARATOR:
1293                    if (c.match(WHITESPACE))
1294                        break;
1295
1296                    if (c == ',') {
1297                        state = SELECTOR;
1298                        break;
1299                    }
1300
1301                    throw Error(SYNTAX_ERROR);
1302            }
1303        }
1304
1305        switch (state) {
1306            case SELECTOR:
1307            case TAG_NAME:
1308            case QUALIFIER:
1309            case QUALIFIER_NAME:
1310            case SELECTOR_SEPARATOR:
1311                // Valid end states.
1312                newSelector();
1313                break;
1314            default:
1315                throw Error(SYNTAX_ERROR);
1316        }
1317
1318        if (!selectors.length)
1319            throw Error(SYNTAX_ERROR);
1320
1321        return selectors;
1322    };
1323    Selector.nextUid = 1;
1324    Selector.matchesSelector = (function () {
1325        var element = document.createElement('div');
1326        if (typeof element['webkitMatchesSelector'] === 'function')
1327            return 'webkitMatchesSelector';
1328        if (typeof element['mozMatchesSelector'] === 'function')
1329            return 'mozMatchesSelector';
1330        if (typeof element['msMatchesSelector'] === 'function')
1331            return 'msMatchesSelector';
1332
1333        return 'matchesSelector';
1334    })();
1335    return Selector;
1336})();
1337
1338var attributeFilterPattern = /^([a-zA-Z:_]+[a-zA-Z0-9_\-:\.]*)$/;
1339
1340function validateAttribute(attribute) {
1341    if (typeof attribute != 'string')
1342        throw Error('Invalid request opion. attribute must be a non-zero length string.');
1343
1344    attribute = attribute.trim();
1345
1346    if (!attribute)
1347        throw Error('Invalid request opion. attribute must be a non-zero length string.');
1348
1349    if (!attribute.match(attributeFilterPattern))
1350        throw Error('Invalid request option. invalid attribute name: ' + attribute);
1351
1352    return attribute;
1353}
1354
1355function validateElementAttributes(attribs) {
1356    if (!attribs.trim().length)
1357        throw Error('Invalid request option: elementAttributes must contain at least one attribute.');
1358
1359    var lowerAttributes = {};
1360    var attributes = {};
1361
1362    var tokens = attribs.split(/\s+/);
1363    for (var i = 0; i < tokens.length; i++) {
1364        var name = tokens[i];
1365        if (!name)
1366            continue;
1367
1368        var name = validateAttribute(name);
1369        var nameLower = name.toLowerCase();
1370        if (lowerAttributes[nameLower])
1371            throw Error('Invalid request option: observing multiple case variations of the same attribute is not supported.');
1372
1373        attributes[name] = true;
1374        lowerAttributes[nameLower] = true;
1375    }
1376
1377    return Object.keys(attributes);
1378}
1379
1380function elementFilterAttributes(selectors) {
1381    var attributes = {};
1382
1383    selectors.forEach(function (selector) {
1384        selector.qualifiers.forEach(function (qualifier) {
1385            attributes[qualifier.attrName] = true;
1386        });
1387    });
1388
1389    return Object.keys(attributes);
1390}
1391
1392var MutationSummary = (function () {
1393    function MutationSummary(opts) {
1394        var _this = this;
1395        this.connected = false;
1396        this.suspended = false;
1397        this.queue = [];
1398        this.options = MutationSummary.validateOptions(opts);
1399        this.observerOptions = MutationSummary.createObserverOptions(this.options.queries);
1400        this.root = this.options.rootNode;
1401        this.callback = this.options.callback;
1402
1403        this.elementFilter = Array.prototype.concat.apply([], this.options.queries.map(function (query) {
1404            return query.elementFilter ? query.elementFilter : [];
1405        }));
1406        if (!this.elementFilter.length)
1407            this.elementFilter = undefined;
1408
1409        this.calcReordered = this.options.queries.some(function (query) {
1410            return query.all;
1411        });
1412
1413        this.queryValidators = []; // TODO(rafaelw): Shouldn't always define this.
1414        if (MutationSummary.createQueryValidator) {
1415            this.queryValidators = this.options.queries.map(function (query) {
1416                return MutationSummary.createQueryValidator(_this.root, query);
1417            });
1418        }
1419
1420        this.observer = new MutationObserverCtor(function (mutations) {
1421
1422            if(_this.suspended){
1423                _this.queue = _this.queue.concat(mutations);
1424                return;
1425            }
1426
1427            _this.observerCallback(mutations);
1428        });
1429
1430        this.reconnect();
1431    }
1432    MutationSummary.createObserverOptions = function (queries) {
1433        var observerOptions = {
1434            childList: true,
1435            subtree: true
1436        };
1437
1438        var attributeFilter;
1439        function observeAttributes(attributes) {
1440            if (observerOptions.attributes && !attributeFilter)
1441                return;
1442
1443            observerOptions.attributes = true;
1444            observerOptions.attributeOldValue = true;
1445
1446            if (!attributes) {
1447                // observe all.
1448                attributeFilter = undefined;
1449                return;
1450            }
1451
1452            // add to observed.
1453            attributeFilter = attributeFilter || {};
1454            attributes.forEach(function (attribute) {
1455                attributeFilter[attribute] = true;
1456                attributeFilter[attribute.toLowerCase()] = true;
1457            });
1458        }
1459
1460        queries.forEach(function (query) {
1461            if (query.characterData) {
1462                observerOptions.characterData = true;
1463                observerOptions.characterDataOldValue = true;
1464                return;
1465            }
1466
1467            if (query.all) {
1468                observeAttributes();
1469                observerOptions.characterData = true;
1470                observerOptions.characterDataOldValue = true;
1471                return;
1472            }
1473
1474            if (query.attribute) {
1475                observeAttributes([query.attribute.trim()]);
1476                return;
1477            }
1478
1479            var attributes = elementFilterAttributes(query.elementFilter).concat(query.attributeList || []);
1480            if (attributes.length)
1481                observeAttributes(attributes);
1482        });
1483
1484        if (attributeFilter)
1485            observerOptions.attributeFilter = Object.keys(attributeFilter);
1486
1487        return observerOptions;
1488    };
1489
1490    MutationSummary.validateOptions = function (options) {
1491        for (var prop in options) {
1492            if (!(prop in MutationSummary.optionKeys))
1493                throw Error('Invalid option: ' + prop);
1494        }
1495
1496        if (typeof options.callback !== 'function')
1497            throw Error('Invalid options: callback is required and must be a function');
1498
1499        if (!options.queries || !options.queries.length)
1500            throw Error('Invalid options: queries must contain at least one query request object.');
1501
1502        var opts = {
1503            callback: options.callback,
1504            rootNode: options.rootNode || document,
1505            observeOwnChanges: !!options.observeOwnChanges,
1506            oldPreviousSibling: !!options.oldPreviousSibling,
1507            queries: []
1508        };
1509
1510        for (var i = 0; i < options.queries.length; i++) {
1511            var request = options.queries[i];
1512
1513            // all
1514            if (request.all) {
1515                if (Object.keys(request).length > 1)
1516                    throw Error('Invalid request option. all has no options.');
1517
1518                opts.queries.push({ all: true });
1519                continue;
1520            }
1521
1522            // attribute
1523            if ('attribute' in request) {
1524                var query = {
1525                    attribute: validateAttribute(request.attribute)
1526                };
1527
1528                query.elementFilter = Selector.parseSelectors('*[' + query.attribute + ']');
1529
1530                if (Object.keys(request).length > 1)
1531                    throw Error('Invalid request option. attribute has no options.');
1532
1533                opts.queries.push(query);
1534                continue;
1535            }
1536
1537            // element
1538            if ('element' in request) {
1539                var requestOptionCount = Object.keys(request).length;
1540                var query = {
1541                    element: request.element,
1542                    elementFilter: Selector.parseSelectors(request.element)
1543                };
1544
1545                if (request.hasOwnProperty('elementAttributes')) {
1546                    query.attributeList = validateElementAttributes(request.elementAttributes);
1547                    requestOptionCount--;
1548                }
1549
1550                if (requestOptionCount > 1)
1551                    throw Error('Invalid request option. element only allows elementAttributes option.');
1552
1553                opts.queries.push(query);
1554                continue;
1555            }
1556
1557            // characterData
1558            if (request.characterData) {
1559                if (Object.keys(request).length > 1)
1560                    throw Error('Invalid request option. characterData has no options.');
1561
1562                opts.queries.push({ characterData: true });
1563                continue;
1564            }
1565
1566            throw Error('Invalid request option. Unknown query request.');
1567        }
1568
1569        return opts;
1570    };
1571
1572    MutationSummary.prototype.createSummaries = function (mutations) {
1573        if (!mutations || !mutations.length)
1574            return [];
1575
1576        var projection = new MutationProjection(this.root, mutations, this.elementFilter, this.calcReordered, this.options.oldPreviousSibling);
1577
1578        var summaries = [];
1579        for (var i = 0; i < this.options.queries.length; i++) {
1580            summaries.push(new Summary(projection, this.options.queries[i])
1580);
1581        }
1582
1583        return summaries;
1584    };
1585
1586    MutationSummary.prototype.checkpointQueryValidators = function () {
1587        this.queryValidators.forEach(function (validator) {
1588            if (validator)
1589                validator.recordPreviousState();
1590        });
1591    };
1592
1593    MutationSummary.prototype.runQueryValidators = function (summaries) {
1594        this.queryValidators.forEach(function (validator, index) {
1595            if (validator)
1596                validator.validate(summaries[index]);
1597        });
1598    };
1599
1600    MutationSummary.prototype.changesToReport = function (summaries) {
1601        return summaries.some(function (summary) {
1602            var summaryProps = [
1603                'added', 'removed', 'reordered', 'reparented',
1604                'valueChanged', 'characterDataChanged'];
1605            if (summaryProps.some(function (prop) {
1606                return summary[prop] && summary[prop].length;
1607            }))
1608                return true;
1609
1610            if (summary.attributeChanged) {
1611                var attrNames = Object.keys(summary.attributeChanged);
1612                var attrsChanged = attrNames.some(function (attrName) {
1613                    return !!summary.attributeChanged[attrName].length;
1614                });
1615                if (attrsChanged)
1616                    return true;
1617            }
1618            return false;
1619        });
1620    };
1621
1622    MutationSummary.prototype.observerCallback = function (mutations) {
1623        if (!this.options.observeOwnChanges)
1624            this.observer.disconnect();
1625
1626        var summaries = this.createSummaries(mutations);
1627        this.runQueryValidators(summaries);
1628
1629        if (this.options.observeOwnChanges)
1630            this.checkpointQueryValidators();
1631
1632        if (this.changesToReport(summaries))
1633            this.callback(summaries);
1634
1635        // disconnect() may have been called during the callback.
1636        if (!this.options.observeOwnChanges && this.connected) {
1637            this.checkpointQueryValidators();
1638            this.observer.observe(this.root, this.observerOptions);
1639        }
1640    };
1641
1642    MutationSummary.prototype.reconnect = function () {
1643        if (this.connected)
1644            throw Error('Already connected');
1645
1646        this.observer.observe(this.root, this.observerOptions);
1647        this.connected = true;
1648        this.checkpointQueryValidators();
1649    };
1650
1651    MutationSummary.prototype.takeSummaries = function () {
1652        if (!this.connected)
1653            throw Error('Not connected');
1654
1655        this.queue = this.queue.concat(this.observer.takeRecords());
1656        var summaries = this.createSummaries(this.queue);
1657        this.queue = [];
1658        
1659        return this.changesToReport(summaries) ? summaries : undefined;
1660    };
1661
1662    MutationSummary.prototype.suspendDelivery = function(){
1663        this.suspended = true;
1664    }
1665
1666    MutationSummary.prototype.resumeDelivery = function(){
1667        this.suspended = false;
1668        return this.takeSummaries();
1669    }
1670
1671    MutationSummary.prototype.disconnect = function () {
1672        var summaries = this.takeSummaries();
1673        this.observer.disconnect();
1674        this.connected = false;
1675        return summaries;
1676    };
1677
1678    MutationSummary.NodeMap = NodeMap;
1679    MutationSummary.parseElementFilter = Selector.parseSelectors;
1680
1681    MutationSummary.optionKeys = {
1682        'callback': true,
1683        'queries': true,
1684        'rootNode': true,
1685        'oldPreviousSibling': true,
1686        'observeOwnChanges': true
1687    };
1688    return MutationSummary;
1689})();
1690
1691
1692/*! cargoEditor - v0.2.0 - 2015-05-05 */ 
1693
1694
1695/**
1696 * The namespace in which all CargoEditor methods and properties reside.
1697 * @name CargoEditor
1698 * @namespace
1699 * @property {CargoEditor.EventManager} events This is a reference to the EventManager singleton 
1700 * @public
1701 * @author Aart Röst
1702 */
1703
1704(function(CargoEditor) {
1705
1706	'use strict';
1707
1708	// Some feature detection
1709
1710	if(typeof MutationObserver === 'undefined'){
1711		throw 'Unable to init the editor. MutationObservers are not supported in this browser.';
1712	}
1713
1714(function() {
1715
1716	/**
1717	 * Standalone extraction of Backbone.Events, no external dependency required.
1718	 * Degrades nicely when Backone/underscore are already available in the current
1719	 * global context.
1720	 *
1721	 * Note that docs suggest to use underscore's `_.extend()` method to add Events
1722	 * support to some given object. A `mixin()` method has been added to the Events
1723	 * prototype to avoid using underscore for that sole purpose:
1724	 *
1725	 *     var myEventEmitter = BackboneEvents.mixin({});
1726	 *
1727	 * Or for a function constructor:
1728	 *
1729	 *     function MyConstructor(){}
1730	 *     MyConstructor.prototype.foo = function(){}
1731	 *     BackboneEvents.mixin(MyConstructor.prototype);
1732	 *
1733	 * (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
1734	 * (c) 2013 Nicolas Perriault
1735	 */
1736
1737	/* global exports:true, define, module */
1738
1739	var evts = {};
1740
1741	(function() {
1742
1743		var root = this,
1744			nativeForEach = Array.prototype.forEach,
1745			hasOwnProperty = Object.prototype.hasOwnProperty,
1746			slice = Array.prototype.slice,
1747			idCounter = 0;
1748			
1749
1750		// Returns a partial implementation matching the minimal API subset required
1751		// by Backbone.Events
1752
1753		function miniscore() {
1754			return {
1755				keys: Object.keys || function(obj) {
1756					if (typeof obj !== "object" && typeof obj !== "function" || obj === null) {
1757						throw new TypeError("keys() called on a non-object");
1758					}
1759					var key, keys = [];
1760					for (key in obj) {
1761						if (obj.hasOwnProperty(key)) {
1762							keys[keys.length] = key;
1763						}
1764					}
1765					return keys;
1766				},
1767
1768				uniqueId: function(prefix) {
1769					var id = ++idCounter + '';
1770					return prefix ? prefix + id : id;
1771				},
1772
1773				has: function(obj, key) {
1774					return hasOwnProperty.call(obj, key);
1775				},
1776
1777				each: function(obj, iterator, context) {
1778					if (obj == null) return;
1779					if (nativeForEach && obj.forEach === nativeForEach) {
1780						obj.forEach(iterator, context);
1781					} else if (obj.length === +obj.length) {
1782						for (var i = 0, l = obj.length; i < l; i++) {
1783							iterator.call(context, obj[i], i, obj);
1784						}
1785					} else {
1786						for (var key in obj) {
1787							if (this.has(obj, key)) {
1788								iterator.call(context, obj[key], key, obj);
1789							}
1790						}
1791					}
1792				},
1793
1794				once: function(func) {
1795					var ran = false,
1796						memo;
1797					return function() {
1798						if (ran) return memo;
1799						ran = true;
1800						memo = func.apply(this, arguments);
1801						func = null;
1802						return memo;
1803					};
1804				}
1805			};
1806		}
1807
1808		var _ = miniscore(),
1809			Events;
1810
1811		// Backbone.Events
1812		// ---------------
1813
1814		// A module that can be mixed in to *any object* in order to provide it with
1815		// custom events. You may bind with `on` or remove with `off` callback
1816		// functions to an event; `trigger`-ing an event fires all callbacks in
1817		// succession.
1818		//
1819		//     var object = {};
1820		//     _.extend(object, Backbone.Events);
1821		//     object.on('expand', function(){ alert('expanded'); });
1822		//     object.trigger('expand');
1823		//
1824		Events = {
1825
1826			// Bind an event to a `callback` function. Passing `"all"` will bind
1827			// the callback to all events fired.
1828			on: function(name, callback, context) {
1829				if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
1830				this._events || (this._events = {});
1831				var events = this._events[name] || (this._events[name] = []);
1832				events.push({
1833					callback: callback,
1834					context: context,
1835					ctx: context || this
1836				});
1837				return this;
1838			},
1839
1840			// Bind an event to only be triggered a single time. After the first time
1841			// the callback is invoked, it will be removed.
1842			once: function(name, callback, context) {
1843				if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
1844				var self = this;
1845				var once = _.once(function() {
1846					self.off(name, once);
1847					callback.apply(this, arguments);
1848				});
1849				once._callback = callback;
1850				return this.on(name, once, context);
1851			},
1852
1853			// Remove one or many callbacks. If `context` is null, removes all
1854			// callbacks with that function. If `callback` is null, removes all
1855			// callbacks for the event. If `name` is null, removes all bound
1856			// callbacks for all events.
1857			off: function(name, callback, context) {
1858				var retain, ev, events, names, i, l, j, k;
1859				if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
1860				if (!name && !callback && !context) {
1861					this._events = {};
1862					return this;
1863				}
1864
1865				names = name ? [name] : _.keys(this._events);
1866				for (i = 0, l = names.length; i < l; i++) {
1867					name = names[i];
1868					if (events = this._events[name]) {
1869						this._events[name] = retain = [];
1870						if (callback || context) {
1871							for (j = 0, k = events.length; j < k; j++) {
1872								ev = events[j];
1873								if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
1874									(context && context !== ev.context)) {
1875									retain.push(ev);
1876								}
1877							}
1878						}
1879						if (!retain.length) delete this._events[name];
1880					}
1881				}
1882
1883				return this;
1884			},
1885
1886			// Trigger one or many events, firing all bound callbacks. Callbacks are
1887			// passed the same arguments as `trigger` is, apart from the event name
1888			// (unless you're listening on `"all"`, which will cause your callback to
1889			// receive the true name of the event as the first argument).
1890			trigger: function(name) {
1891				if (!this._events) return this;
1892				var args = slice.call(arguments, 1);
1893				if (!eventsApi(this, 'trigger', name, args)) return this;
1894				var events = this._events[name];
1895				var allEvents = this._events.all;
1896				if (events) triggerEvents(events, args);
1897				if (allEvents) triggerEvents(allEvents, arguments);
1898				return this;
1899			},
1900
1901			// Tell this object to stop listening to either specific events ... or
1902			// to every object it's currently listening to.
1903			stopListening: function(obj, name, callback) {
1904				var listeners = this._listeners;
1905				if (!listeners) return this;
1906				var deleteListener = !name && !callback;
1907				if (typeof name === 'object') callback = this;
1908				if (obj)(listeners = {})[obj._listenerId] = obj;
1909				for (var id in listeners) {
1910					listeners[id].off(name, callback, this);
1911					if (deleteListener) delete this._listeners[id];
1912				}
1913				return this;
1914			}
1915
1916		};
1917
1918		// Regular expression used to split event strings.
1919		var eventSplitter = /\s+/;
1920
1921		// Implement fancy features of the Events API such as multiple event
1922		// names `"change blur"` and jQuery-style event maps `{change: action}`
1923		// in terms of the existing API.
1924		var eventsApi = function(obj, action, name, rest) {
1925			if (!name) return true;
1926
1927			// Handle event maps.
1928			if (typeof name === 'object') {
1929				for (var key in name) {
1930					obj[action].apply(obj, [key, name[key]].concat(rest));
1931				}
1932				return false;
1933			}
1934
1935			// Handle space separated event names.
1936			if (eventSplitter.test(name)) {
1937				var names = name.split(eventSplitter);
1938				for (var i = 0, l = names.length; i < l; i++) {
1939					obj[action].apply(obj, [names[i]].concat(rest));
1940				}
1941				return false;
1942			}
1943
1944			return true;
1945		};
1946
1947		// A difficult-to-believe, but optimized internal dispatch function for
1948		// triggering events. Tries to keep the usual cases speedy (most internal
1949		// Backbone events have 3 arguments).
1950		var triggerEvents = function(events, args) {
1951			var ev, i = -1,
1952				l = events.length,
1953				a1 = args[0],
1954				a2 = args[1],
1955				a3 = args[2];
1956			switch (args.length) {
1957				case 0:
1958					while (++i < l)(ev = events[i]).callback.call(ev.ctx);
1959					return;
1960				case 1:
1961					while (++i < l)(ev = events[i]).callback.call(ev.ctx, a1);
1962					return;
1963				case 2:
1964					while (++i < l)(ev = events[i]).callback.call(ev.ctx, a1, a2);
1965					return;
1966				case 3:
1967					while (++i < l)(ev = events[i]).callback.call(ev.ctx, a1, a2, a3);
1968					return;
1969				default:
1970					while (++i < l)(ev = events[i]).callback.apply(ev.ctx, args);
1971			}
1972		};
1973
1974		var listenMethods = {
1975			listenTo: 'on',
1976			listenToOnce: 'once'
1977		};
1978
1979		// Inversion-of-control versions of `on` and `once`. Tell *this* object to
1980		// listen to an event in another object ... keeping track of what it's
1981		// listening to.
1982		_.each(listenMethods, function(implementation, method) {
1983			Events[method] = function(obj, name, callback) {
1984				var listeners = this._listeners || (this._listeners = {});
1985				var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
1986				listeners[id] = obj;
1987				if (typeof name === 'object') callback = this;
1988				obj[implementation](name, callback, this);
1989				return this;
1990			};
1991		});
1992
1993		// Aliases for backwards compatibility.
1994		Events.bind = Events.on;
1995		Events.unbind = Events.off;
1996		Events.fire = function(name, args){
1997
1998			console.warn('fire is deprecated', arguments, [name].concat(args));
1999
2000			Events.trigger.apply(this, [name].concat(args));
2001		}
2002
2003		// Mixin utility
2004		Events.mixin = function(proto) {
2005			var exports = ['on', 'once', 'off', 'trigger', 'stopListening', 'listenTo',
2006				'listenToOnce', 'bind', 'unbind', 'fire'
2007			];
2008			_.each(exports, function(name) {
2009				proto[name] = this[name];
2010			}, this);
2011			return proto;
2012		};
2013
2014		// Export Events as BackboneEvents depending on current context
2015		
2016		this.BackboneEvents = Events;
2017		
2018	}).call(evts);
2019
2020	this.events = evts.BackboneEvents.mixin({});
2021
2022}).call(this);
2023
2024/*
2025*   This is the root of the CargoEditor. Use this to define global public functions and methods.
2026*/
2027
2028var _core = this,
2029
2030	// array to keep track of all EditorInstances.
2031	editorInstances = [],
2032	activeEditor;
2033
2034/**
2035 *   Public method to create a private EditorInstance
2036 *   
2037 *   @method 
2038 *   @param {Element} element The element (Not a jQuery object!) that contains the HTML that needs to be edited.
2039 *   
2040 *   @memberOf CargoEditor
2041 */
2042this.addEditor = function(element) {
2043
2044	var editorInstance = new Editor(element);
2045
2046	if (editorInstance !== false) {
2047
2048		editorInstances.push(editorInstance);
2049
2050		return editorInstance;
2051
2052	}
2053
2054	// if no editor Instance is returned: return false.
2055	return false;
2056}
2057
2058this.events.on('editor-destroyed', function(editor){
2059
2060	if(editor === activeEditor) {
2061		activeEditor = undefined;
2062	}
2063	
2064	var index = editorInstances.indexOf(editor);
2065
2066	if(index !== -1){
2067
2068		editorInstances.splice(index, 1);
2069		editor = null;
2070
2071	}
2072
2073});
2074
2075this.getActiveRange = function(){
2076
2077	var selection = _core.rangy.getSelection();
2078
2079	if(selection.rangeCount > 0){
2080		return selection.getRangeAt(0);
2081	}
2082
2083	return undefined;
2084
2085}
2086
2087this.plugins = {};
2088
2089/**
2090 * Adds a plugin to CargoEditor. Sets up a namespace for the plugin in CargoEditor.plugins and calls the plugin function.
2091 *
2092 * @method 
2093 * @param {String} id The name of the plugin. Registers a namespace in CargoEditor.plugins.id
2094 * @param {Function} plugin The plugin. The this keyword refers to the plugin's namespace. The first parameter of the plugin is the CargoEditor object
2095 *
2096 * @memberOf CargoEditor
2097 */
2098
2099this.addPlugin = function(name, plugin) {
2100
2101	if(typeof plugin === 'function') {
2102
2103		// extend plugin
2104		/*plugin.prototype.events = [];
2105		plugin.prototype.addEvent = function(){
2106			this.events.push('test');
2107		}*/
2108
2109		plugin.prototype.events = _core.events;
2110		plugin.prototype.helpers = helpers;
2111
2112		plugin.prototype.pluginNS = _core.plugins;
2113		
2114		// Init plugin
2115		this.plugins[name] = new plugin(this);
2116
2117		/**
2118		 * Fires after a plugin has been added. Returns the plugin's name and namespace
2119		 *
2120		 * @event CargoEditor#plugin-initialized
2121		 * @property {String} name The plugin's name
2122		 * @property {Object} namespace The namespace the plugin has access to. Located at CargoEditor.plugins[name]
2123		 */
2124		
2125		_core.events.trigger('plugin-initialized', name, this.plugins[name]);
2126	}
2127
2128};
2129
2130/**
2131 *   Public method to retrieve a list of active EditorInstances
2132 *   
2133 *   @returns {Array} An array of all editor instances.
2134 *   
2135 *   @memberOf CargoEditor
2136 */
2137
2138this.getEditorInstances = function() {
2139
2140	return editorInstances;
2141
2142};
2143
2144/**
2145 *   Returns the EditorInstance that's currently focused
2146 *   @method 
2147 *   @returns {CargoEditor.EditorInstance|undefined} The active instance or undefined if none of the editors is active.
2148 *   @memberOf CargoEditor
2149 */
2150
2151this.getActiveEditor = function() {
2152
2153	return activeEditor;
2154	
2155};
2156
2157document.addEventListener('keydown', function(event) {
2158	if (!event) event = window.event;
2159
2160	if(event.keyCode === 46 || event.keyCode === 8){
2161		
2162		event.preventDefault();
2163
2164		_core.events.trigger('global-delete-pressed', event);
2165
2166	}
2167
2168}, false);
2169
2170var helpers = {
2171
2172	/**
2173	 * [extend description]
2174	 * @param  {Object} obj    [The original object]
2175	 * @param  {Object} source [The object that's going to be merged into the original object]
2176	 * @return {Object}        [The original object including the merged]
2177	 */
2178	extend: function(obj, source) {
2179
2180		if (Object.keys) {
2181			var keys = Object.keys(source);
2182			for (var i = 0, il = keys.length; i < il; i++) {
2183				var prop = keys[i];
2184				Object.defineProperty(obj, prop, Object.getOwnPropertyDescriptor(source, prop));
2185			}
2186		} else {
2187			var safeHasOwnProperty = {}.hasOwnProperty;
2188			for (var prop in source) {
2189				if (safeHasOwnProperty.call(source, prop)) {
2190					obj[prop] = source[prop];
2191				}
2192			}
2193		}
2194
2195		return obj;
2196
2197	},
2198
2199	loadScript: function(src, completeCallback){
2200		var script = document.createElement('script');
2201		script.async = false;
2202		script.src = src;
2203		script.onload = completeCallback;
2204		document.head.appendChild(script);
2205	},
2206
2207	hasClass: function(el, name) {
2208		if(el === undefined || name === undefined) {
2209			return;
2210		}
2211
2212		return new RegExp('(\\s|^)' + name + '(\\s|$)').test(el.className);
2213	},
2214
2215	addClass: function(el, name) {
2216		if(el === undefined || name === undefined) {
2217			return;
2218		}
2219
2220		if (!helpers.hasClass(el, name)) {
2221			el.className += (el.className ? ' ' : '') + name;
2222		}
2223	},
2224
2225	removeClass: function(el, name) {
2226		if(el === undefined || name === undefined) {
2227			return;
2228		}
2229
2230		if (helpers.hasClass(el, name)) {
2231			el.className = el.className.replace(new RegExp('(\\s|^)' + name + '(\\s|$)'), ' ').replace(/^\s+|\s+$/g, '');
2232		}
2233	},
2234
2235	/**
2236	* @method
2237	* @param {Node} node The reference node.
2238	* @returns {Node} The reference node's next sibling.
2239	*/
2240
2241	nextNode: function(node){
2242
2243		if (node.hasChildNodes()) {
2244		
2245			return node.firstChild;
2246		
2247		} else {
2248			
2249			while (node && !node.nextSibling) {
2250				node = node.parentNode;
2251			}
2252
2253			if (!node) {
2254				return null;
2255			}
2256
2257			return node.nextSibling;
2258		
2259		}
2260
2261	},
2262
2263	/**
2264	* @method
2265	* @param {Node} node The node that needs to be unwrapped.
2266	* @param {Boolean} [removeWrap=true] Delete the empty wrap
2267	*/
2268
2269	unwrap: function(node, removeWrap){
2270
2271		var parent = node.parentNode;
2272
2273		while (node.childNodes.length > 0) {
2274			parent.insertBefore(node.childNodes[0], node);
2275		}
2276
2277		if (parent !== null && removeWrap !== false) {
2278			parent.removeChild(node);
2279		}
2280
2281	},
2282
2283	/**
2284	* @method
2285	* @param {Node} node The node that's being wrapped.
2286	* @param {String} wrapNodeName The nodename of the element that's going to wrap the node.
2287	* @param {Object} [attributes] Optional Object containing attributes added to every wrapper. example: 
2288	* <pre><code>{
2289	*   'data-foo': 'bar',
2290	*   'class': 'big-text'
2291	* }</code></pre>
2292	*/
2293
2294	wrap: function(node, wrapNodeName, attributes){
2295		
2296		if(node !== null){
2297		
2298			var parent = node.parentNode,
2299				wrap = document.createElement(wrapNodeName);
2300
2301			if(typeof attributes === "object"){
2302				for(key in attributes){
2303					wrap.setAttribute(key, attributes[key]);
2304				}
2305			}
2306
2307			parent.insertBefore(wrap, node);
2308			wrap.appendChild(node);
2309
2310			if(node.isSaveable() === true){
2311				wrap.setSaveable(true);
2312			}
2313		
2314		}
2315
2316	},
2317
2318	/**
2319	* Recursively loops through all parent elements for a node.
2320	*
2321	* @method
2322	* @param {Node} node The node that's gonna be traversed. 
2323	* @param {Function} callback The callback that fires for every node found. The child is the first (and only) parameter of the callback. 
2324	* @param {Node} ceiling If this is set the function won't traverse past the given node. 
2325	*/
2326
2327	getAllParents: function(node, callback, ceiling) {
2328
2329		// recursively finds all parent nodes and fires a callback for every node found
2330		while (node.parentNode !== null && node.parentNode !== ceiling) {
2331
2332			if (callback(node.parentNode) === false) {
2333				return false;
2334			}
2335
2336			node = node.parentNode;
2337
2338		}
2339
2340	},
2341
2342	getAllSiblings: function(startNode, reverse) {
2343
2344		if(startNode !== undefined){
2345			
2346			var result = [];
2347			
2348			while((startNode = (reverse === true ? startNode.previousSibling : startNode.nextSibling)) !== null){
2349				result.push(startNode);
2350			}
2351			
2352			return result;
2353		}
2354		
2355		return [];
2356	},
2357
2358	/**
2359	* Recursively loops through all children of the given node and fires a callback for every node found.
2360	*
2361	* @method
2362	* @param {Node} node The node that's gonna be traversed. 
2363	* @param {Function} callback The callback that fires for every node found. The child is the first (and only) parameter of the callback. 
2364	*                            Return false to cancel the loop and return "continue" to skip the current node.
2365	* @param {Boolean} [reverse] [If true the loop starts at the last child node of every parent it encounters and reverses to the first. False by default.]
2366	*/
2367
2368	getAllDescendants: function(node, callback, reverse) {
2369
2370		// recursively finds all childnodes and fires a callback for every node found
2371		for (var i = (reverse ? node.childNodes.length - 1 : 0); (reverse ? (i >= 0) : (i < node.childNodes.length)); (reverse ? i-- : i++)) {
2372
2373			var child = node.childNodes[i],
2374				result = callback(child);
2375
2376			if(result === false){
2377				return false;
2378			}
2379
2380			// if "continue" is being returned from the callback we don't go down
2381			// the subtree of the current child.
2382			if(result !== "continue"){
2383				// recursively go down the tree.
2384				if(this.getAllDescendants(child, callback, reverse) === false){
2385					return false;
2386				}
2387			}
2388
2389		}
2390
2391	},
2392
2393	/**
2394	* Returns all nodes that are currently in the first Range of the current Selection.
2395	*
2396	* @method
2397	* @param {ElementNode} rootElement If this node is set the function will select all nodes between the selection and this element.
2398	* @returns {Object} An object that contains the current selection and an array of selected Nodes. 
2399	*   <pre><code> {
2400	*       'nodes': [],
2401	*       'selection': {}
2402	*   }</code></pre>
2403	*
2404	* @memberOf CargoEditor.helpers
2405	*/
2406
2407	getCurrentSelectionTree: function(rootElement) {
2408
2409		var selectedNodes	= [],
2410			selection 		= getSelection(),
2411			range 			= _core.getActiveRange(),
2412			results 		= {
2413								nodes: [],
2414								selection: {}
2415							};
2416
2417		results.selection = selection;
2418
2419		if (range !== false) {
2420			// selected nodes without parents.
2421			selectedNodes = range.getNodes();
2422
2423			// if a rootElement is set, traverse up and select all parentNodes.
2424			if (typeof rootElement === "object" && selectedNodes.indexOf(rootElement) === -1) {
2425
2426				for (var i = 0; i < selectedNodes.length; i++) {
2427
2428					var node = selectedNodes[i];
2429
2430					while (node !== null && node.parentNode !== rootElement) {
2431						node = node.parentNode;
2432
2433						if (selectedNodes.indexOf(node) < 0 && node !== null) {
2434							selectedNodes.push(node);
2435						}
2436
2437					}
2438
2439				}
2440
2441			}
2442
2443		}
2444
2445		results.nodes = selectedNodes;
2446	
2447		return results;
2448	},
2449
2450	getEditableNodesInRange: function(){
2451
2452		
2453		
2454	},
2455
2456	/**
2457	* Returns all nodes in the current Range. Also includes the partially selected nodes.
2458	*
2459	* @method
2460	* @param {Range} [range={@link CargoEditor.getActiveRange}] The range object we're getting the nodes from.
2461	* @returns {Array} Array of all nodes.
2462	*/
2463
2464	/*getSelectedNodes: function(range) {
2465
2466		if(range === undefined){
2467			range = _core.getActiveRange();
2468		}
2469
2470		if(range === false){
2471			return [];
2472		}
2473
2474		var node     = range.startContainer,
2475			endNode  = range.endContainer,
2476			_this 	 = this;
2477
2478		var hasEditableParent;
2479
2480		helpers.getAllParents(endNode, function(parent){
2481
2482			hasEditableParent = _core.isEditable(parent);
2483			
2484			if(hasEditableParent !== undefined){
2485				return false;
2486			}
2487
2488		});
2489
2490		if(hasEditableParent === false){
2491			return [];
2492		}
2493
2494		// a range that is contained within a single node
2495		if (node == endNode) {
2496			return [node];
2497		}
2498
2499		// Iterate nodes until we hit the end container
2500		var rangeNodes = [];
2501
2502		while (node && node != endNode) {
2503
2504			if(_core.isEditable(node) !== false){
2505
2506				rangeNodes.push(node);
2507				node = helpers.nextNode(node);
2508
2509			} else {
2510
2511				// don't traverse into the uneditable element.
2512				node = node.nextSibling;
2513
2514			}
2515
2516		}
2517
2518		// Add partially selected nodes at the start of the range
2519		node = range.startContainer;
2520
2521		while (node && node != range.commonAncestorContainer) {
2522			
2523			rangeNodes.unshift(node);
2524			node = node.parentNode;
2525
2526		}
2527
2528		return rangeNodes;
2529
2530	},*/
2531
2532	insertNodeAtCursorPosition: function(event, node){
2533
2534		if(event === undefined || node === undefined){
2535			console.warn('Missing parameters');
2536			return false;
2537		}
2538
2539		window.getSelection().removeAllRanges();
2540
2541		var newRange = document.createRange(),
2542			start;
2543
2544		if (document.caretPositionFromPoint) {
2545
2546				// Proper way
2547
2548				start = document.caretPositionFromPoint(event.clientX, event.clientY);
2549				newRange.setStart(start.offsetNode, start.offset);
2550				newRange.collapse();
2551				
2552				if(newRange.commonAncestorContainer !== node){
2553					newRange.insertNode(node);
2554				}
2555
2556			} else if (document.caretRangeFromPoint) {
2557
2558				// Webkit way
2559				
2560				start = document.caretRangeFromPoint(event.clientX, event.clientY);
2561				newRange.setStart(start.startContainer, start.startOffset);
2562				newRange.collapse();
2563
2564				if(newRange.commonAncestorContainer !== node){
2565					newRange.insertNode(node);
2566				} 
2567
2568			} else if (document.body.createTextRange) {
2569				
2570				// IE way
2571
2572				newRange = document.body.createTextRange();
2573				newRange.moveToPoint(event.clientX, event.clientY);
2574				var spanId = "temp_" + ("" + Math.random()).slice(2);
2575				newRange.pasteHTML('<span id="' + spanId + '">&nbsp;</span>');
2576
2577				var pastedSpan = document.getElementById(spanId);
2578
2579				if(pastedSpan.parentNode !== node){
2580					pastedSpan.parentNode.replaceChild(node, span);
2581				} else {
2582					if(pastedSpan.parentNode !== null){
2583						pastedSpan.parentNode.removeChild(pastedSpan);
2584					}
2585				}
2586				
2587			}
2588
2589			window.getSelection().addRange(newRange);
2590
2591			window.getSelection().removeAllRanges();
2592
2593	},
2594
2595	getCursorCoordinates: function() {
2596
2597	    var sel = document.selection, 
2598	    	range, 
2599	    	rects, 
2600	    	rect, 
2601	    	x = 0, 
2602	    	y = 0,
2603	    	h = 0;
2604	    
2605	    if (sel) {
2606
2607	        if (sel.type != "Control") {
2608
2609	            range = sel.createRange();
2610	            range.collapse(true);
2611
2612	            x = range.boundingLeft;
2613	            y = range.boundingTop;
2614	            h = range.boundingHeight;
2615
2616	        }
2617
2618	    } else if (window.getSelection) {
2619
2620	        sel = window.getSelection();
2621
2622	        if (sel.rangeCount) {
2623
2624	            range = sel.getRangeAt(0).cloneRange();
2625
2626	            if (range.getClientRects) {
2627
2628	            	if (!range.collapsed) {
2629		                var startRange = range.cloneRange();
2630		                startRange.collapse(true);
2631		                var selTop = startRange.getClientRects()[0].top;
2632		                startRange.detach();
2633		                var endRange = range.cloneRange();
2634		                endRange.collapse(false);
2635		                h = endRange.getClientRects()[0].bottom - selTop;
2636		                endRange.detach();
2637		            }
2638	                
2639	                range.collapse(true);
2640	                rects = range.getClientRects();
2641
2642	                if (rects.length > 0) {
2643	                    rect = range.getClientRects()[0];
2644	                }
2645
2646	                x = rect.left;
2647	                y = rect.top;
2648
2649	            }
2650	        }
2651	    }
2652
2653	    return { x: x, y: y, h: h };
2654	},
2655
2656	findClosestTextNode: function(referenceNode, reverse) {
2657
2658		var siblings, i,
2659			_self = this,
2660			isValidTextNode = function(node){
2661				
2662				if(node.nodeType === Node.TEXT_NODE && node.nodeValue.search(/\S/g) !== -1){
2663					return true;
2664				}  
2665				
2666				return false;
2667			};
2668
2669		var result;
2670		
2671		while(referenceNode !== null && referenceNode !== document.body){
2672
2673			if(referenceNode.contentEditable === "false"){
2674				continue;
2675			}
2676
2677			siblings = helpers.getAllSiblings(referenceNode, reverse);
2678
2679			for( i = 0; i < siblings.length; i++){
2680
2681				if(siblings[i].contentEditable === "false"){
2682					continue;
2683				}
2684
2685				if(isValidTextNode(siblings[i])){
2686					result = siblings[i];
2687					break;
2688				}
2689				
2690				if(siblings[i].childNodes.length > 0){
2691					
2692					helpers.getAllDescendants(siblings[i], function(child){
2693
2694						if(isValidTextNode(child)){
2695							result = child;
2696							return false;
2697						}
2698					}, reverse);
2699
2700					if(result !== undefined){
2701						break;
2702					}
2703					
2704				}
2705
2706			}
2707
2708			if(result !== undefined){
2709				break;
2710			}
2711			
2712			referenceNode = referenceNode.parentNode;
2713		}
2714
2715		return result;
2716
2717	},
2718
2719	loadJSON: function(url, callback, async){
2720		
2721		var request = new XMLHttpRequest();
2722
2723		if(async !== false){
2724			async = true;
2725		}
2726
2727		request.open('GET', url, async);
2728
2729		request.onload = function() {
2730		if (request.status >= 200 && request.status < 400){
2731			// Success!
2732			
2733				if(typeof callback === "function") {
2734					callback.call(this, JSON.parse(request.responseText));
2735				}
2736			} else {
2737				// We reached our target server, but it returned an error
2738				
2739			}
2740		};
2741
2742		request.onerror = function() {
2743			// There was a connection error of some sort
2744		};
2745
2746		request.send();
2747	},
2748
2749	/**
2750	 * Might wanna do this with a docfrag?
2751	 */
2752	
2753	stringToHTMLNodes: function(html) {
2754		var tmp = document.createElement('body'),
2755			result = [],
2756			child;
2757
2758		tmp.innerHTML = html;
2759
2760		while (child = tmp.lastChild) {
2761			result.push(child.cloneNode(true));
2762			tmp.removeChild(child);
2763		}
2764
2765		return result;
2766	},
2767
2768	warn: function(arg) {
2769		// TODO: make this failsafe.
2770		console.warn(arg);
2771
2772	},
2773
2774	log: function(arg) {
2775		// TODO: make this failsafe.
2776		console.warn(arg);
2777
2778	},
2779
2780	selectNode: function(node) {
2781		var newRange = document.createRange(),
2782			selection = getSelection();
2783
2784		newRange.selectNode(node);
2785
2786		selection.removeAllRanges();
2787		selection.addRange(newRange);
2788	},
2789
2790	logDocumentFragment: function(frag){
2791		var div = document.createElement('div');
2792		div.appendChild(frag.cloneNode(true));
2793		console.log(div);
2794	},
2795
2796	whiteSpaceHelpers: {
2797		/**
2798		 * https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Whitespace_in_the_DOM
2799		 * 
2800		 * Throughout, whitespace is defined as one of the characters
2801		 *  "\t" TAB \u0009
2802		 *  "\n" LF  \u000A
2803		 *  "\r" CR  \u000D
2804		 *  " "  SPC \u0020
2805		 *
2806		 * This does not use Javascript's "\s" because that includes non-breaking
2807		 * spaces (and also some other characters).
2808		 */
2809
2810
2811		/**
2812		 * Determine whether a node's text content is entirely whitespace.
2813		 *
2814		 * @param nod  A node implementing the |CharacterData| interface (i.e.,
2815		 *             a |Text|, |Comment|, or |CDATASection| node
2816		 * @return     True if all of the text content of |nod| is whitespace,
2817		 *             otherwise false.
2818		 */
2819		is_all_ws: function( nod ) {
2820			// Use ECMA-262 Edition 3 String and RegExp features
2821			return !(/[^\t\n\r ]/.test(nod.textContent));
2822		},
2823
2824
2825		/**
2826		 * Determine if a node should be ignored by the iterator functions.
2827		 *
2828		 * @param nod  An object implementing the DOM1 |Node| interface.
2829		 * @return     true if the node is:
2830		 *                1) A |Text| node that is all whitespace
2831		 *                2) A |Comment| node
2832		 *             and otherwise false.
2833		 */
2834
2835		is_ignorable: function( nod ) {
2836			return ( nod.nodeType == 8) || // A comment node
2837						 ( (nod.nodeType == 3) && is_all_ws(nod) ); // a text node, all ws
2838		},
2839
2840		/**
2841		 * Version of |previousSibling| that skips nodes that are entirely
2842		 * whitespace or comments.  (Normally |previousSibling| is a property
2843		 * of all DOM nodes that gives the sibling node, the node that is
2844		 * a child of the same parent, that occurs immediately before the
2845		 * reference node.)
2846		 *
2847		 * @param sib  The reference node.
2848		 * @return     Either:
2849		 *               1) The closest previous sibling to |sib| that is not
2850		 *                  ignorable according to |is_ignorable|, or
2851		 *               2) null if no such node exists.
2852		 */
2853		node_before: function( sib ) {
2854			while ((sib = sib.previousSibling)) {
2855				if (!is_ignorable(sib)) return sib;
2856			}
2857			return null;
2858		},
2859
2860		/**
2861		 * Version of |nextSibling| that skips nodes that are entirely
2862		 * whitespace or comments.
2863		 *
2864		 * @param sib  The reference node.
2865		 * @return     Either:
2866		 *               1) The closest next sibling to |sib| that is not
2867		 *                  ignorable according to |is_ignorable|, or
2868		 *               2) null if no such node exists.
2869		 */
2870		node_after: function( sib ) {
2871			while ((sib = sib.nextSibling)) {
2872				if (!is_ignorable(sib)) return sib;
2873			}
2874			return null;
2875		},
2876
2877		/**
2878		 * Version of |lastChild| that skips nodes that are entirely
2879		 * whitespace or comments.  (Normally |lastChild| is a property
2880		 * of all DOM nodes that gives the last of the nodes contained
2881		 * directly in the reference node.)
2882		 *
2883		 * @param sib  The reference node.
2884		 * @return     Either:
2885		 *               1) The last child of |sib| that is not
2886		 *                  ignorable according to |is_ignorable|, or
2887		 *               2) null if no such node exists.
2888		 */
2889		last_child: function( par ) {
2890			var res=par.lastChild;
2891			while (res) {
2892				if (!is_ignorable(res)) return res;
2893				res = res.previousSibling;
2894			}
2895			return null;
2896		},
2897
2898		/**
2899		 * Version of |firstChild| that skips nodes that are entirely
2900		 * whitespace and comments.
2901		 *
2902		 * @param sib  The reference node.
2903		 * @return     Either:
2904		 *               1) The first child of |sib| that is not
2905		 *                  ignorable according to |is_ignorable|, or
2906		 *               2) null if no such node exists.
2907		 */
2908		first_child: function( par ) {
2909			var res=par.firstChild;
2910			while (res) {
2911				if (!is_ignorable(res)) return res;
2912				res = res.nextSibling;
2913			}
2914			return null;
2915		},
2916
2917		/**
2918		 * Version of |data| that doesn't include whitespace at the beginning
2919		 * and end and normalizes all whitespace to a single space.  (Normally
2920		 * |data| is a property of text nodes that gives the text of the node.)
2921		 *
2922		 * @param txt  The text node whose data should be returned
2923		 * @return     A string giving the contents of the text node with
2924		 *             whitespace collapsed.
2925		 */
2926		data_of: function( txt ) {
2927			var data = txt.textContent;
2928			// Use ECMA-262 Edition 3 String and RegExp features
2929			data = data.replace(/[\t\n\r ]+/g, " ");
2930			if (data.charAt(0) == " ")
2931				data = data.substring(1, data.length);
2932			if (data.charAt(data.length - 1) == " ")
2933				data = data.substring(0, data.length - 1);
2934			return data;
2935		}
2936	}
2937}
2938
2939/**
2940 * Collection of useful helpers.
2941 * @name helpers
2942 * @namespace
2943 * @public
2944 * @memberOf CargoEditor
2945 * @author Aart Röst
2946 */
2947
2948this.helpers = helpers;
2949
2950
2951/**
2952 * Rangy, a cross-browser JavaScript range and selection library
2953 * https://github.com/timdown/rangy
2954 *
2955 * Copyright 2015, Tim Down
2956 * Licensed under the MIT license.
2957 * Version: 1.3.0-beta.1
vendor: 1,689 bytes, lines 2958-2961
2958 * Build date: 12 February 2015
2959 */
2960
2961;!function(e,t){ t.rangy=e() }(function(){function e(e,t){var n=typeof e[t];return n==v||!(n!=R||!e[t])||"unknown"==n}function t(e,t){return!(typeof e[t]!=R||!e[t])}function n(e,t){return typeof e[t]!=C}function r(e){return function(t,n){for(var r=n.length;r--;)if(!e(t,n[r]))return!1;return!0}}function o(e){return e&&w(e,y)&&T(e,S)}function i(e){return t(e,"body")?e.body:e.getElementsByTagName("body")[0]}function a(t){typeof console!=C&&e(console,"log")&&console.log(t)}function s(e,t){x&&t?alert(e):a(e)}function c(e){b.initialized=!0,b.supported=!1,s("Rangy is not supported in this environment. Reason: "+e,b.config.alertOnFail)}function d(e){s("Rangy warning: "+e,b.config.alertOnWarn)}function f(e){return e.message||e.description||String(e)}function u(){if(x&&!b.initialized){var t,n=!1,r=!1;e(document,"createRange")&&(t=document.createRange(),w(t,E)&&T(t,N)&&(n=!0));var s=i(document);if(!s||"body"!=s.nodeName.toLowerCase())return void c("No body element found");if(s&&e(s,"createTextRange")&&(t=s.createTextRange(),o(t)&&(r=!0)),!n&&!r)return void c("Neither Range nor TextRange are available");b.initialized=!0,b.features={implementsDomRange:n,implementsTextRange:r};var d,u;for(var l in D)(d=D[l])instanceof h&&d.init(d,b);for(var g=0,p=B.length;p>g;++g)try{B[g](b)}catch(m){u="Rangy init listener threw an exception. Continuing. Detail: "+f(m),a(u)}}}function l(e){e=e||window,u();for(var t=0,n=H.length;n>t;++t)H[t](e)}function h(e,t,n){this.name=e,this.dependencies=t,this.initialized=!1,this.supported=!1,this.initializer=n}function g(e,t,n){var r=new h(e,t,function(t){if(!t.initialized){t.initialized=!0;try{n(b,t),t.supported=!0}
vendor: 6,550 bytes, line 2961
2961catch(r){var o="Module '"+e+"' failed to load: "+f(r);a(o),r.stack&&a(r.stack)}}});return D[e]=r,r}function p(){}function m(){}var R="object",v="function",C="undefined",N=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],E=["setStart","setStartBefore","setStartAfter","setEnd","setEndBefore","setEndAfter","collapse","selectNode","selectNodeContents","compareBoundaryPoints","deleteContents","extractContents","cloneContents","insertNode","surroundContents","cloneRange","toString","detach"],S=["boundingHeight","boundingLeft","boundingTop","boundingWidth","htmlText","text"],y=["collapse","compareEndPoints","duplicate","moveToElementText","parentElement","select","setEndPoint","getBoundingClientRect"],w=r(e),O=r(t),T=r(n),_=[].forEach?function(e,t){e.forEach(t)}:function(e,t){for(var n=0,r=e.length;r>n;++n)t(e[n],n)},D={},x=typeof window!=C&&typeof document!=C,A={isHostMethod:e,isHostObject:t,isHostProperty:n,areHostMethods:w,areHostObjects:O,areHostProperties:T,isTextRange:o,getBody:i,forEach:_},b={version:"1.3.0-beta.1",initialized:!1,isBrowser:x,supported:!0,util:A,features:{},modules:D,config:{alertOnFail:!0,alertOnWarn:!1,preferTextRange:!1,autoInitialize:typeof rangyAutoInitialize==C?!0:rangyAutoInitialize}};b.fail=c,b.warn=d;var P;({}).hasOwnProperty?(A.extend=P=function(e,t,n){var r,o;for(var i in t)t.hasOwnProperty(i)&&(r=e[i],o=t[i],n&&null!==r&&"object"==typeof r&&null!==o&&"object"==typeof o&&P(r,o,!0),e[i]=o);return t.hasOwnProperty("toString")&&(e.toString=t.toString),e},A.createOptions=function(e,t){var n={};return P(n,t),e&&P(n,e),n}):c("hasOwnProperty not supported"),x||c("Rangy can only run in a browser"),function(){var e;if(x){var t=document.createElement("div");t.appendChild(document.createElement("span"));var n=[].slice;try{1==n.call(t.childNodes,0)[0].nodeType&&(e=function(e){return n.call(e,0)})}catch(r){}}e||(e=function(e){for(var t=[],n=0,r=e.length;r>n;++n)t[n]=e[n];return t}),A.toArray=e}();var I;x&&(e(document,"addEventListener")?I=function(e,t,n){e.addEventListener(t,n,!1)}:e(document,"attachEvent")?I=function(e,t,n){e.attachEvent("on"+t,n)}:c("Document does not have required addEventListener or attachEvent method"),A.addListener=I);var B=[];b.init=u,b.addInitListener=function(e){b.initialized?e(b):B.push(e)};var H=[];b.addShimListener=function(e){H.push(e)},x&&(b.shim=b.createMissingNativeApi=l),h.prototype={init:function(){for(var e,t,n=this.dependencies||[],r=0,o=n.length;o>r;++r){if(t=n[r],e=D[t],!(e&&e instanceof h))throw new Error("required module '"+t+"' not found");if(e.init(),!e.supported)throw new Error("required module '"+t+"' not supported")}this.initializer(this)},fail:function(e){throw this.initialized=!0,this.supported=!1,new Error("Module '"+this.name+"' failed to load: "+e)},warn:function(e){b.warn("Module "+this.name+": "+e)},deprecationNotice:function(e,t){b.warn("DEPRECATED: "+e+" in module "+this.name+"is deprecated. Please use "+t+" instead")},createError:function(e){return new Error("Error in Rangy "+this.name+" module: "+e)}},b.createModule=function(e){var t,n;2==arguments.length?(t=arguments[1],n=[]):(t=arguments[2],n=arguments[1]);var r=g(e,n,t);b.initialized&&b.supported&&r.init()},b.createCoreModule=function(e,t,n){g(e,t,n)},b.RangePrototype=p,b.rangePrototype=new p,b.selectionPrototype=new m,b.createCoreModule("DomUtil",[],function(e,t){function n(e){var t;return typeof e.namespaceURI==x||null===(t=e.namespaceURI)||"http://www.w3.org/1999/xhtml"==t}function r(e){var t=e.parentNode;return 1==t.nodeType?t:null}function o(e){for(var t=0;e=e.previousSibling;)++t;return t}function i(e){switch(e.nodeType){case 7:case 10:return 0;case 3:case 8:return e.length;default:return e.childNodes.length}}function a(e,t){var n,r=[];for(n=e;n;n=n.parentNode)r.push(n);for(n=t;n;n=n.parentNode)if(I(r,n))return n;return null}function s(e,t,n){for(var r=n?t:t.parentNode;r;){if(r===e)return!0;r=r.parentNode}return!1}function c(e,t){return s(e,t,!0)}function d(e,t,n){for(var r,o=n?e:e.parentNode;o;){if(r=o.parentNode,r===t)return o;o=r}return null}function f(e){var t=e.nodeType;return 3==t||4==t||8==t}function u(e){if(!e)return!1;var t=e.nodeType;return 3==t||8==t}function l(e,t){var n=t.nextSibling,r=t.parentNode;return n?r.insertBefore(e,n):r.appendChild(e),e}function h(e,t,n){var r=e.cloneNode(!1);if(r.deleteData(0,t),e.deleteData(t,e.length-t),l(r,e),n)for(var i,a=0;i=n[a++];)i.node==e&&i.offset>t?(i.node=r,i.offset-=t):i.node==e.parentNode&&i.offset>o(e)&&++i.offset;return r}function g(e){if(9==e.nodeType)return e;if(typeof e.ownerDocument!=x)return e.ownerDocument;if(typeof e.document!=x)return e.document;if(e.parentNode)return g(e.parentNode);throw t.createError("getDocument: no document found for node")}function p(e){var n=g(e);if(typeof n.defaultView!=x)return n.defaultView;if(typeof n.parentWindow!=x)return n.parentWindow;throw t.createError("Cannot get a window object for node")}function m(e){if(typeof e.contentDocument!=x)return e.contentDocument;if(typeof e.contentWindow!=x)return e.contentWindow.document;throw t.createError("getIframeDocument: No Document object found for iframe element")}function R(e){if(typeof e.contentWindow!=x)return e.contentWindow;if(typeof e.contentDocument!=x)return e.contentDocument.defaultView;throw t.createError("getIframeWindow: No Window object found for iframe element")}function v(e){return e&&A.isHostMethod(e,"setTimeout")&&A.isHostObject(e,"document")}function C(e,t,n){var r;if(e?A.isHostProperty(e,"nodeType")?r=1==e.nodeType&&"iframe"==e.tagName.toLowerCase()?m(e):g(e):v(e)&&(r=e.document):r=document,!r)throw t.createError(n+"(): Parameter must be a Window object or DOM node");return r}function N(e){for(var t;t=e.parentNode;)e=t;return e}function E(e,n,r,i){var s,c,f,u,l;if(e==r)return n===i?0:i>n?-1:1;if(s=d(r,e,!0))return n<=o(s)?-1:1;if(s=d(e,r,!0))return o(s)<i?-1:1;if(c=a(e,r),!c)throw new Error("comparePoints error: nodes have no common ancestor");if(f=e===c?c:d(e,c,!0),u=r===c?c:d(r,c,!0),f===u)throw t.createError("comparePoints got to case 4 and childA and childB are the same!");for(l=c.firstChild;l;){if(l===f)return-1;if(l===u)return 1;l=l.nextSibling}}function S(e){var t;try{return t=e.parentNode,!1}catch(n){return!0}}function y(e){if(!e)return"[No node]";if(B&&S(e))return"[Broken node]";if(f(e))return'"'+e.data+'"';if(1==e.nodeType){var t=e.id?' id="'+e.id+'"':"";return"<"+e.nodeName+t+">[index:"+o(e)+",length:"+e.childNodes.length+"]["+(e.innerHTML||"[innerHTML not supported]").slice(0,25)+"]"}
vendor: 2,367 bytes, line 2961
2961return e.nodeName}function w(e){for(var t,n=g(e).createDocumentFragment();t=e.firstChild;)n.appendChild(t);return n}function O(e){this.root=e,this._next=e}function T(e){return new O(e)}function _(e,t){this.node=e,this.offset=t}function D(e){this.code=this[e],this.codeName=e,this.message="DOMException: "+this.codeName}var x="undefined",A=e.util;A.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||t.fail("document missing a Node creation method"),A.isHostMethod(document,"getElementsByTagName")||t.fail("document missing getElementsByTagName method");var b=document.createElement("div");A.areHostMethods(b,["insertBefore","appendChild","cloneNode"]||!A.areHostObjects(b,["previousSibling","nextSibling","childNodes","parentNode"]))||t.fail("Incomplete Element implementation"),A.isHostProperty(b,"innerHTML")||t.fail("Element is missing innerHTML property");var P=document.createTextNode("test");A.areHostMethods(P,["splitText","deleteData","insertData","appendData","cloneNode"]||!A.areHostObjects(b,["previousSibling","nextSibling","childNodes","parentNode"])||!A.areHostProperties(P,["data"]))||t.fail("Incomplete Text Node implementation");var I=function(e,t){for(var n=e.length;n--;)if(e[n]===t)return!0;return!1},B=!1;!function(){var t=document.createElement("b");t.innerHTML="1";var n=t.firstChild;t.innerHTML="<br />",B=S(n),e.features.crashyTextNodes=B}();var H;typeof window.getComputedStyle!=x?H=function(e,t){return p(e).getComputedStyle(e,null)[t]}:typeof document.documentElement.currentStyle!=x?H=function(e,t){return e.currentStyle[t]}:t.fail("No means of obtaining computed style properties found"),O.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var e,t,n=this._current=this._next;if(this._current)if(e=n.firstChild)this._next=e;else{for(t=null;n!==this.root&&!(t=n.nextSibling);)n=n.parentNode;this._next=t}return this._current},detach:function(){this._current=this._next=this.root=null}},_.prototype={equals:function(e){return!!e&&this.node===e.node&&this.offset==e.offset},inspect:function(){return"[DomPosition("+y(this.node)+":"+this.offset+")]"},toString:function(){return this.inspect()}},D.prototype={INDEX_SIZE_ERR:1,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INVALID_STATE_ERR:11,INVALID_NODE_TYPE_ER
vendor: 22,513 bytes, lines 2961-2962
2961R:24},D.prototype.toString=function(){return this.message},e.dom={arrayContains:I,isHtmlNamespace:n,parentElement:r,getNodeIndex:o,getNodeLength:i,getCommonAncestor:a,isAncestorOf:s,isOrIsAncestorOf:c,getClosestAncestorIn:d,isCharacterDataNode:f,isTextOrCommentNode:u,insertAfter:l,splitDataNode:h,getDocument:g,getWindow:p,getIframeWindow:R,getIframeDocument:m,getBody:A.getBody,isWindow:v,getContentDocument:C,getRootContainer:N,comparePoints:E,isBrokenNode:S,inspectNode:y,getComputedStyleProperty:H,fragmentFromNodeChildren:w,createIterator:T,DomPosition:_},e.DOMException=D}),b.createCoreModule("DomRange",["DomUtil"],function(e){function t(e,t){return 3!=e.nodeType&&(F(e,t.startContainer)||F(e,t.endContainer))}function n(e){return e.document||j(e.startContainer)}function r(e){return new M(e.parentNode,k(e))}function o(e){return new M(e.parentNode,k(e)+1)}function i(e,t,n){var r=11==e.nodeType?e.firstChild:e;return W(t)?n==t.length?B.insertAfter(e,t):t.parentNode.insertBefore(e,0==n?t:U(t,n)):n>=t.childNodes.length?t.appendChild(e):t.insertBefore(e,t.childNodes[n]),r}function a(e,t,r){if(w(e),w(t),n(t)!=n(e))throw new L("WRONG_DOCUMENT_ERR");var o=z(e.startContainer,e.startOffset,t.endContainer,t.endOffset),i=z(e.endContainer,e.endOffset,t.startContainer,t.startOffset);return r?0>=o&&i>=0:0>o&&i>0}function s(e){for(var t,r,o,i=n(e.range).createDocumentFragment();r=e.next();){if(t=e.isPartiallySelectedSubtree(),r=r.cloneNode(!t),t&&(o=e.getSubtreeIterator(),r.appendChild(s(o)),o.detach()),10==r.nodeType)throw new L("HIERARCHY_REQUEST_ERR");i.appendChild(r)}return i}function c(e,t,n){var r,o;n=n||{stop:!1};for(var i,a;i=e.next();)if(e.isPartiallySelectedSubtree()){if(t(i)===!1)return void(n.stop=!0);if(a=e.getSubtreeIterator(),c(a,t,n),a.detach(),n.stop)return}else for(r=B.createIterator(i);o=r.next();)if(t(o)===!1)return void(n.stop=!0)}function d(e){for(var t;e.next();)e.isPartiallySelectedSubtree()?(t=e.getSubtreeIterator(),d(t),t.detach()):e.remove()}function f(e){for(var t,r,o=n(e.range).createDocumentFragment();t=e.next();){if(e.isPartiallySelectedSubtree()?(t=t.cloneNode(!1),r=e.getSubtreeIterator(),t.appendChild(f(r)),r.detach()):e.remove(),10==t.nodeType)throw new L("HIERARCHY_REQUEST_ERR");o.appendChild(t)}return o}function u(e,t,n){var r,o=!(!t||!t.length),i=!!n;o&&(r=new RegExp("^("+t.join("|")+")$"));var a=[];return c(new h(e,!1),function(t){if(!(o&&!r.test(t.nodeType)||i&&!n(t))){var s=e.startContainer;if(t!=s||!W(s)||e.startOffset!=s.length){var c=e.endContainer;t==c&&W(c)&&0==e.endOffset||a.push(t)}}}),a}function l(e){var t="undefined"==typeof e.getName?"Range":e.getName();return"["+t+"("+B.inspectNode(e.startContainer)+":"+e.startOffset+", "+B.inspectNode(e.endContainer)+":"+e.endOffset+")]"}function h(e,t){if(this.range=e,this.clonePartiallySelectedTextNodes=t,!e.collapsed){this.sc=e.startContainer,this.so=e.startOffset,this.ec=e.endContainer,this.eo=e.endOffset;var n=e.commonAncestorContainer;this.sc===this.ec&&W(this.sc)?(this.isSingleCharacterDataNode=!0,this._first=this._last=this._next=this.sc):(this._first=this._next=this.sc!==n||W(this.sc)?V(this.sc,n,!0):this.sc.childNodes[this.so],this._last=this.ec!==n||W(this.ec)?V(this.ec,n,!0):this.ec.childNodes[this.eo-1])}}function g(e){return function(t,n){for(var r,o=n?t:t.parentNode;o;){if(r=o.nodeType,Y(e,r))return o;o=o.parentNode}return null}}function p(e,t){if(nt(e,t))throw new L("INVALID_NODE_TYPE_ERR")}function m(e,t){if(!Y(t,e.nodeType))throw new L("INVALID_NODE_TYPE_ERR")}function R(e,t){if(0>t||t>(W(e)?e.length:e.childNodes.length))throw new L("INDEX_SIZE_ERR")}function v(e,t){if(et(e,!0)!==et(t,!0))throw new L("WRONG_DOCUMENT_ERR")}function C(e){if(tt(e,!0))throw new L("NO_MODIFICATION_ALLOWED_ERR")}function N(e,t){if(!e)throw new L(t)}function E(e){return G&&B.isBrokenNode(e)||!Y(Z,e.nodeType)&&!et(e,!0)}function S(e,t){return t<=(W(e)?e.length:e.childNodes.length)}function y(e){return!!e.startContainer&&!!e.endContainer&&!E(e.startContainer)&&!E(e.endContainer)&&S(e.startContainer,e.startOffset)&&S(e.endContainer,e.endOffset)}function w(e){if(!y(e))throw new Error("Range error: Range is no longer valid after DOM mutation ("+e.inspect()+")")}function O(e,t){w(e);var n=e.startContainer,r=e.startOffset,o=e.endContainer,i=e.endOffset,a=n===o;W(o)&&i>0&&i<o.length&&U(o,i,t),W(n)&&r>0&&r<n.length&&(n=U(n,r,t),a?(i-=r,o=n):o==n.parentNode&&i>=k(n)&&i++,r=0),e.setStartAndEnd(n,r,o,i)}function T(e){w(e);var t=e.commonAncestorContainer.parentNode.cloneNode(!1);return t.appendChild(e.cloneContents()),t.innerHTML}function _(e){e.START_TO_START=ct,e.START_TO_END=dt,e.END_TO_END=ft,e.END_TO_START=ut,e.NODE_BEFORE=lt,e.NODE_AFTER=ht,e.NODE_BEFORE_AND_AFTER=gt,e.NODE_INSIDE=pt}function D(e){_(e),_(e.prototype)}function x(e,t){return function(){w(this);var n,r,i=this.startContainer,a=this.startOffset,s=this.commonAncestorContainer,d=new h(this,!0);i!==s&&(n=V(i,s,!0),r=o(n),i=r.node,a=r.offset),c(d,C),d.reset();var f=e(d);return d.detach(),t(this,i,a,i,a),f}}function A(n,i){function a(e,t){return function(n){m(n,X),m(Q(n),Z);var i=(e?r:o)(n);(t?s:c)(this,i.node,i.offset)}}function s(e,t,n){var r=e.endContainer,o=e.endOffset;(t!==e.startContainer||n!==e.startOffset)&&((Q(t)!=Q(r)||1==z(t,n,r,o))&&(r=t,o=n),i(e,t,n,r,o))}function c(e,t,n){var r=e.startContainer,o=e.startOffset;(t!==e.endContainer||n!==e.endOffset)&&((Q(t)!=Q(r)||-1==z(t,n,r,o))&&(r=t,o=n),i(e,r,o,t,n))}var u=function(){};u.prototype=e.rangePrototype,n.prototype=new u,H.extend(n.prototype,{setStart:function(e,t){p(e,!0),R(e,t),s(this,e,t)},setEnd:function(e,t){p(e,!0),R(e,t),c(this,e,t)},setStartAndEnd:function(){var e=arguments,t=e[0],n=e[1],r=t,o=n;switch(e.length){case 3:o=e[2];break;case 4:r=e[2],o=e[3]}i(this,t,n,r,o)},setBoundary:function(e,t,n){this["set"+(n?"Start":"End")](e,t)},setStartBefore:a(!0,!0),setStartAfter:a(!1,!0),setEndBefore:a(!0,!1),setEndAfter:a(!1,!1),collapse:function(e){w(this),e?i(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):i(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(e){p(e,!0),i(this,e,0,e,q(e))},selectNode:function(e){p(e,!1),m(e,X);var t=r(e),n=o(e);i(this,t.node,t.offset,n.node,n.offset)},extractContents:x(f,i),deleteContents:x(d,i),canSurroundContents:function(){w(this),C(this.startContainer),C(this.endContainer);var e=new h(this,!0),n=e._first&&t(e._first,this)||e._last&&t(e._last,this);return e.detach(),!n},splitBoundaries:function(){O(this)},splitBoundariesPreservingPositions:function(e){O(this,e)},normalizeBoundaries:function(){w(this);var e=this.startContainer,t=this.startOffset,n=this.endContainer,r=this.endOffset,o=function(e){var t=e.nextSibling;t&&t.nodeType==e.nodeType&&(n=e,r=e.length,e.appendData(t.data),t.parentNode.removeChild(t))},a=function(o){var i=o.previousSibling;if(i&&i.nodeType==o.nodeType){e=o;var a=o.length;if(t=i.length,o.insertData(0,i.data),i.parentNode.removeChild(i),e==n)r+=t,n=e;else if(n==o.parentNode){var s=k(o);r==s?(n=o,r=a):r>s&&r--}}},s=!0;if(W(n))n.length==r&&o(n);else{if(r>0){var c=n.childNodes[r-1];c&&W(c)&&o(c)}s=!this.collapsed}if(s){if(W(e))0==t&&a(e);else if(t<e.childNodes.length){var d=e.childNodes[t];d&&W(d)&&a(d)}}else e=n,t=r;i(this,e,t,n,r)},collapseToPoint:function(e,t){p(e,!0),R(e,t),this.setStartAndEnd(e,t)}}),D(n)}function b(e){e.collapsed=e.startContainer===e.endContainer&&e.startOffset===e.endOffset,e.commonAncestorContainer=e.collapsed?e.startContainer:B.getCommonAncestor(e.startContainer,e.endContainer)}function P(e,t,n,r,o){e.startContainer=t,e.startOffset=n,e.endContainer=r,e.endOffset=o,e.document=B.getDocument(t),b(e)}function I(e){this.startContainer=e,this.startOffset=0,this.endContainer=e,this.endOffset=0,this.document=e,b(this)}var B=e.dom,H=e.util,M=B.DomPosition,L=e.DOMException,W=B.isCharacterDataNode,k=B.getNodeIndex,F=B.isOrIsAncestorOf,j=B.getDocument,z=B.comparePoints,U=B.splitDataNode,V=B.getClosestAncestorIn,q=B.getNodeLength,Y=B.arrayContains,Q=B.getRootContainer,G=e.features.crashyTextNodes;h.prototype={_current:null,_next:null,_first:null,_last:null,isSingleCharacterDataNode:!1,reset:function(){this._current=null,this._next=this._first},hasNext:function(){return!!this._next},next:function(){var e=this._current=this._next;return e&&(this._next=e!==this._last?e.nextSibling:null,W(e)&&this.clonePartiallySelectedTextNodes&&(e===this.ec&&(e=e.cloneNode(!0)).deleteData(this.eo,e.length-this.eo),this._current===this.sc&&(e=e.cloneNode(!0)).deleteData(0,this.so))),e},remove:function(){var e,t,n=this._current;!W(n)||n!==this.sc&&n!==this.ec?n.parentNode&&n.parentNode.removeChild(n):(e=n===this.sc?this.so:0,t=n===this.ec?this.eo:n.length,e!=t&&n.deleteData(e,t-e))},isPartiallySelectedSubtree:function(){var e=this._current;return t(e,this.range)},getSubtreeIterator:function(){var e;if(this.isSingleCharacterDataNode)e=this.range.cloneRange(),e.collapse(!1);else{e=new I(n(this.range));var t=this._current,r=t,o=0,i=t,a=q(t);F(t,this.sc)&&(r=this.sc,o=this.so),F(t,this.ec)&&(i=this.ec,a=this.eo),P(e,r,o,i,a)}return new h(e,this.clonePartiallySelectedTextNodes)},detach:function(){this.range=this._current=this._next=this._first=this._last=this.sc=this.so=this.ec=this.eo=null}};var X=[1,3,4,5,7,8,10],Z=[2,9,11],$=[5,6,10,12],J=[1,3,4,5,7,8,10,11],K=[1,3,4,5,7,8],et=g([9,11]),tt=g($),nt=g([6,10,12]),rt=document.createElement("style"),ot=!1;try{rt.innerHTML="<b>x</b>",ot=3==rt.firstChild.nodeType}catch(it){}e.features.htmlParsingConforms=ot;var at=ot?function(e){var t=this.startContainer,n=j(t);if(!t)throw new L("INVALID_STATE_ERR");var r=null;return 1==t.nodeType?r=t:W(t)&&(r=B.parentElement(t)),r=null===r||"HTML"==r.nodeName&&B.isHtmlNamespace(j(r).documentElement)&&B.isHtmlNamespace(r)?n.createElement("body"):r.cloneNode(!1),r.innerHTML=e,B.fragmentFromNodeChildren(r)}:function(e){var t=n(this),r=t.createElement("body");return r.innerHTML=e,B.fragmentFromNodeChildren(r)},st=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer"],ct=0,dt=1,ft=2,ut=3,lt=0,ht=1,gt=2,pt=3;H.extend(e.rangePrototype,{compareBoundaryPoints:function(e,t){w(this),v(this.startContainer,t.startContainer);var n,r,o,i,a=e==ut||e==ct?"start":"end",s=e==dt||e==ct?"start":"end";return n=this[a+"Container"],r=this[a+"Offset"],o=t[s+"Container"],i=t[s+"Offset"],z(n,r,o,i)},insertNode:function(e){if(w(this),m(e,J),C(this.startContainer),F(e,this.startContainer))throw new L("HIERARCHY_REQUEST_ERR");var t=i(e,this.startContainer,this.startOffset);this.setStartBefore(t)},cloneContents:function(){w(this);var e,t;if(this.collapsed)return n(this).createDocumentFragment();if(this.startContainer===this.endContainer&&W(this.startContainer))return e=this.startContainer.cloneNode(!0),e.data=e.data.slice(this.startOffset,this.endOffset),t=n(this).createDocumentFragment(),t.appendChild(e),t;var r=new h(this,!0);return e=s(r),r.detach(),e},canSurroundContents:function(){w(this),C(this.startContainer),C(this.endContainer);var e=new h(this,!0),n=e._first&&t(e._first,this)||e._last&&t(e._last,this);return e.detach(),!n},surroundContents:function(e){if(m(e,K),!this.canSurroundContents())throw new L("INVALID_STATE_ERR");var t=this.extractContents();if(e.hasChildNodes())for(;e.lastChild;)e.removeChild(e.lastChild);i(e,this.startContainer,this.startOffset),e.appendChild(t),this.selectNode(e)},cloneRange:function(){w(this);for(var e,t=new I(n(this)),r=st.length;r--;)e=st[r],t[e]=this[e];return t},toString:function(){w(this);var e=this.startContainer;if(e===this.endContainer&&W(e))return 3==e.nodeType||4==e.nodeType?e.data.slice(this.startOffset,this.endOffset):"";var t=[],n=new h(this,!0);return c(n,function(e){(3==e.nodeType||4==e.nodeType)&&t.push(e.data)}),n.detach(),t.join("")},compareNode:function(e){w(this);var t=e.parentNode,n=k(e);if(!t)throw new L("NOT_FOUND_ERR");var r=this.comparePoint(t,n),o=this.comparePoint(t,n+1);return 0>r?o>0?gt:lt:o>0?ht:pt},comparePoint:function(e,t){return w(this),N(e,"HIERARCHY_REQUEST_ERR"),v(e,this.startContainer),z(e,t,this.startContainer,this.startOffset)<0?-1:z(e,t,this.endContainer,this.endOffset)>0?1:0},createContextualFragment:at,toHtml:function(){return T(this)},intersectsNode:function(e,t){if(w(this),N(e,"NOT_FOUND_ERR"),j(e)!==n(this))return!1;var r=e.parentNode,o=k(e);N(r,"NOT_FOUND_ERR");var i=z(r,o,this.endContainer,this.endOffset),a=z(r,o+1,this.startContainer,this.startOffset);return t?0>=i&&a>=0:0>i&&a>0},isPointInRange:function(e,t){return w(this),N(e,"HIERARCHY_REQUEST_ERR"),v(e,this.startContainer),z(e,t,this.startContainer,this.startOffset)>=0&&z(e,t,this.endContainer,this.endOffset)<=0},intersectsRange:function(e){return a(this,e,!1)},intersectsOrTouchesRange:function(e){return a(this,e,!0)},intersection:function(e){if(this.intersectsRange(e)){var t=z(this.startContainer,this.startOffset,e.startContainer,e.startOffset),n=z(this.endContainer,this.endOffset,e.endContainer,e.endOffset),r=this.cloneRange();return-1==t&&r.setStart(e.startContainer,e.startOffset),1==n&&r.setEnd(e.endContainer,e.endOffset),r}return null},union:function(e){if(this.intersectsOrTouchesRange(e)){var t=this.cloneRange();return-1==z(e.startContainer,e.startOffset,this.startContainer,this.startOffset)&&t.setStart(e.startContainer,e.startOffset),1==z(e.endContainer,e.endOffset,this.endContainer,this.endOffset)&&t.setEnd(e.endContainer,e.endOffset),t}throw new L("Ranges do not intersect")},containsNode:function(e,t){return t?this.intersectsNode(e,!1):this.compareNode(e)==pt},containsNodeContents:function(e){return this.comparePoint(e,0)>=0&&this.comparePoint(e,q(e))<=0},containsRange:function(e){var t=this.intersection(e);return null!==t&&e.equals(t)},containsNodeText:function(e){var t=this.cloneRange();t.selectNode(e);var n=t.getNodes([3]);if(n.length>0){t.setStart(n[0],0);var r=n.pop();return t.setEnd(r,r.length),this.containsRange(t)}return this.containsNodeContents(e)},getNodes:function(e,t){return w(this),u(this,e,t)},getDocument:function(){return n(this)},collapseBefore:function(e){this.setEndBefore(e),this.collapse(!1)},collapseAfter:function(e){this.setStartAfter(e),this.collapse(!0)},getBookmark:function(t){var r=n(this),o=e.createRange(r);t=t||B.getBody(r),o.selectNodeContents(t);var i=this.intersection(o),a=0,s=0;return i&&(o.setEnd(i.startContainer,i.startOffset),a=o.toString().length,s=a+i.toString().length),{start:a,end:s,containerNode:t}},moveToBookmark:function(e){var t=e.containerNode,n=0;this.setStart(t,0),this.collapse(!0);for(var r,o,i,a,s=[t],c=!1,d=!1;!d&&(r=s.pop());)if(3==r.nodeType)o=n+r.length,!c&&e.start>=n&&e.start<=o&&(this.setStart(r,e.start-n),c=!0),c&&e.end>=n&&e.end<=o&&(this.setEnd(r,e.end-n),d=!0),n=o;else for(a=r.childNodes,i=a.length;i--;)s.push(a[i])},getName:function(){return"DomRange"},equals:function(e){return I.rangesEqual(this,e)},isValid:function(){return y(this)},inspect:function(){return l(this)},detach:function(){}}),A(I,P),H.extend(I,{rangeProperties:st,RangeIterator:h,copyComparisonConstants:D,createPrototypeRange:A,inspect:l,toHtml:T,getRangeDocument:n,rangesEqual:function(e,t){return e.startContainer===t.startContainer&&e.startOffset===t.startOffset&&e.endContainer===t.endContainer&&e.endOffset===t.endOffset}}),e.DomRange=I}),b.createCoreModule("WrappedRange",["DomRange"],function(e,t){var n,r,o=e.dom,i=e.util,a=o.DomPosition,s=e.DomRange,c=o.getBody,d=o.getContentDocument,f=o.isCharacterDataNode;if(e.features.implementsDomRange&&!function(){function r(e){for(var t,n=l.length;n--;)t=l[n],e[t]=e.nativeRange[t];e.collapsed=e.startContainer===e.endContainer&&e.startOffset===e.endOffset}function a(e,t,n,r,o){var i=e.startContainer!==t||e.startOffset!=n,a=e.endContainer!==r||e.endOffset!=o,s=!e.equals(e.nativeRange);(i||a||s)&&(e.setEnd(r,o),e.setStart(t,n))}var f,u,l=s.rangeProperties;n=function(e){if(!e)throw t.createError("WrappedRange: Range must be specified");this.nativeRange=e,r(this)},s.createPrototypeRange(n,a),f=n.prototype,f.selectNode=function(e){this.nativeRange.selectNode(e),r(this)},f.cloneContents=function(){return this.nativeRange.cloneContents()},f.surroundContents=function(e){this.nativeRange.surroundContents(e),r(this)},f.collapse=function(e){this.nativeRange.collapse(e),r(this)},f.cloneRange=function(){return new n(this.nativeRange.cloneRange())},f.refresh=function(){r(this)},f.toString=function(){return this.nativeRange.toString()};var h=document.createTextNode("test");c(document).appendChild(h);var g=document.createRange();g.setStart(h,0),g.setEnd(h,0);try{g.setStart(h,1),f.setStart=function(e,t){this.nativeRange.setStart(e,t),r(this)},f.setEnd=function(e,t){this.nativeRange.setEnd(e,t),r(this)},u=function(e){return function(t){this.nativeRange[e](t),r(this)}}}catch(p){f.setStart=function(e,t){try{this.nativeRange.setStart(e,t)}catch(n){this.nativeRange.setEnd(e,t),this.nativeRange.setStart(e,t)}r(this)},f.setEnd=function(e,t){try{this.nativeRange.setEnd(e,t)}catch(n){this.nativeRange.setStart(e,t),this.nativeRange.setEnd(e,t)}r(this)},u=function(e,t){return function(n){try{this.nativeRange[e](n)}catch(o){this.nativeRange[t](n),this.nativeRange[e](n)}r(this)}}}f.setStartBefore=u("setStartBefore","setEndBefore"),f.setStartAfter=u("setStartAfter","setEndAfter"),f.setEndBefore=u("setEndBefore","setStartBefore"),f.setEndAfter=u("setEndAfter","setStartAfter"),f.selectNodeContents=function(e){this.setStartAndEnd(e,0,o.getNodeLength(e))},g.selectNodeContents(h),g.setEnd(h,3);var m=document.createRange();m.selectNodeContents(h),m.setEnd(h,4),m.setStart(h,2),f.compareBoundaryPoints=-1==g.compareBoundaryPoints(g.START_TO_END,m)&&1==g.compareBoundaryPoints(g.END_TO_START,m)?function(e,t){return t=t.nativeRange||t,e==t.START_TO_END?e=t.END_TO_START:e==t.END_TO_START&&(e=t.START_TO_END),this.nativeRange.compareBoundaryPoints(e,t)}:function(e,t){return this.nativeRange.compareBoundaryPoints(e,t.nativeRange||t)};var R=document.createElement("div");R.innerHTML="123";var v=R.firstChild,C=c(document);C.appendChild(R),g.setStart(v,1),g.setEnd(v,2),g.deleteContents(),"13"==v.data&&(f.deleteContents=function(){this.nativeRange.deleteContents(),r(this)},f.extractContents=function(){var e=this.nativeRange.extractContents();return r(this),e}),C.removeChild(R),C=null,i.isHostMethod(g,"createContextualFragment")&&(f.createContextualFragment=function(e){return this.nativeRange.createContextualFragment(e)}),c(document).removeChild(h),f.getName=function(){return"WrappedRange"},e.WrappedRange=n,e.createNativeRange=function(e){return e=d(e,t,"createNativeRange"),e.createRange()}}(),e.features.implementsTextRange){var u=function(e){var t=e.parentElement(),n=e.duplicate();n.collapse(!0);var r=n.parentElement();n=e.duplicate(),n.collapse(!1);var i=n.parentElement(),a=r==i?r:o.getCommonAncestor(r,i);return a==t?a:o.getCommonAncestor(t,a)},l=function(e){return 0==e.compareEndPoints("StartToEnd",e)},h=function(e,t,n,r,i){var s=e.duplicate();s.collapse(n);var c=s.parentElement();if(o.isOrIsAncestorOf(t,c)||(c=t),!c.canHaveHTML){var d=new a(c.parentNode,o.getNodeIndex(c));return{boundaryPosition:d,nodeInfo:{nodeIndex:d.offset,containerElement:d.node}}}var u=o.getDocument(c).createElement("span");u.parentNode&&u.parentNode.removeChild(u);for(var l,h,g,p,m,R=n?"StartToStart":"StartToEnd",v=i&&i.containerElement==c?i.nodeIndex:0,C=c.childNodes.length,N=C,E=N;;){if(E==C?c.appendChild(u):c.insertBefore(u,c.childNodes[E]),s.moveToElementText(u),l=s.compareEndPoints(R,e),0==l||v==N)break;if(-1==l){if(N==v+1)break;v=E}else N=N==v+1?v:E;E=Math.floor((v+N)/2),c.removeChild(u)}if(m=u.nextSibling,-1==l&&m&&f(m)){s.setEndPoint(n?"EndToStart":"EndToEnd",e);var S;if(/[\r\n]/.test(m.data)){var y=s.duplicate(),w=y.text.replace(/\r\n/g,"\r").length;for(S=y.moveStart("character",w);-1==(l=y.compareEndPoints("StartToEnd",y));)S++,y.moveStart("character",1)}else S=s.text.length;p=new a(m,S)}else h=(r||!n)&&u.previousSibling,g=(r||n)&&u.nextSibling,p=g&&f(g)?new a(g,0):h&&f(h)?new a(h,h.data.length):new a(c,o.getNodeIndex(u));return u.parentNode.removeChild(u),{boundaryPosition:p,nodeInfo:{nodeIndex:E,containerElement:c}}},g=function(e,t){var n,r,i,a,s=e.offset,d=o.getDocument(e.node),u=c(d).createTextRange(),l=f(e.node);return l?(n=e.node,r=n.parentNode):(a=e.node.childNodes,n=s<a.length?a[s]:null,r=e.node),i=d.createElement("span"),i.innerHTML="&#feff;",n?r.insertBefore(i,n):r.appendChild(i),u.moveToElementText(i),u.collapse(!t),r.removeChild(i),l&&u[t?"moveStart":"moveEnd"]("character",s),u};r=function(e){this.textRange=e,this.refresh()},r.prototype=new s(document),r.prototype.refresh=function(){var e,t,n,r=u(this.textRange);l(this.textRange)?t=e=h(this.textRange,r,!0,!0).boundaryPosition:(n=h(this.textRange,r,!0,!1),e=n.boundaryPosition,t=h(this.textRange,r,!1,!1,n.nodeInfo).boundaryPosition),this.setStart(e.node,e.offset),this.setEnd(t.node,t.offset)},r.prototype.getName=function(){return"WrappedTextRange"},s.copyComparisonConstants(r);var p=function(e){if(e.collapsed)return g(new a(e.startContainer,e.startOffset),!0);var t=g(new a(e.startContainer,e.startOffset),!0),n=g(new a(e.endContainer,e.endOffset),!1),r=c(s.getRangeDocument(e)).createTextRange();return r.setEndPoint("StartToStart",t),r.setEndPoint("EndToEnd",n),r};if(r.rangeToTextRange=p,r.prototype.toTextRange=function(){return p(this)
2962},e.WrappedTextRange=r,!e.features.implementsDomRange||e.config.preferTextRange){var m=function(e){return e("return this;")()}(Function);"undefined"==typeof m.Range&&(m.Range=r),e.createNativeRange=function(e){return e=d(e,t,"createNativeRange"),c(e).createTextRange()},e.WrappedRange=r}}e.createRange=function(n){return n=d(n,t,"createRange"),new e.WrappedRange(e.createNativeRange(n))},e.createRangyRange=function(e){return e=d(e,t,"createRangyRange"),new s(e)},e.createIframeRange=function(n){return t.deprecationNotice("createIframeRange()","createRange(iframeEl)"),e.createRange(n)},e.createIframeRangyRange=function(n){return t.deprecationNotice("createIframeRangyRange()","createRangyRange(iframeEl)"),e.createRangyRange(n)},e.addShimListener(function(t){var n=t.document;"undefined"==typeof n.createRange&&(n.createRange=function(){return e.createRange(n)}),n=t=null})}),b.createCoreModule("WrappedSelection",["DomRange","WrappedRange"],function(e,t){function n(e){return"string"==typeof e?/^backward(s)?$/i.test(e):!!e}function r(e,n){if(e){if(D.isWindow(e))return e;if(e instanceof R)return e.win;var r=D.getContentDocument(e,t,n);return D.getWindow(r)}return window}
vendor: 5,301 bytes, line 2962
2962function o(e){return r(e,"getWinSelection").getSelection()}function i(e){return r(e,"getDocSelection").document.selection}function a(e){var t=!1;return e.anchorNode&&(t=1==D.comparePoints(e.anchorNode,e.anchorOffset,e.focusNode,e.focusOffset)),t}function s(e,t,n){var r=n?"end":"start",o=n?"start":"end";e.anchorNode=t[r+"Container"],e.anchorOffset=t[r+"Offset"],e.focusNode=t[o+"Container"],e.focusOffset=t[o+"Offset"]}function c(e){var t=e.nativeSelection;e.anchorNode=t.anchorNode,e.anchorOffset=t.anchorOffset,e.focusNode=t.focusNode,e.focusOffset=t.focusOffset}function d(e){e.anchorNode=e.focusNode=null,e.anchorOffset=e.focusOffset=0,e.rangeCount=0,e.isCollapsed=!0,e._ranges.length=0}function f(t){var n;return t instanceof b?(n=e.createNativeRange(t.getDocument()),n.setEnd(t.endContainer,t.endOffset),n.setStart(t.startContainer,t.startOffset)):t instanceof P?n=t.nativeRange:H.implementsDomRange&&t instanceof D.getWindow(t.startContainer).Range&&(n=t),n}function u(e){if(!e.length||1!=e[0].nodeType)return!1;for(var t=1,n=e.length;n>t;++t)if(!D.isAncestorOf(e[0],e[t]))return!1;return!0}function l(e){var n=e.getNodes();if(!u(n))throw t.createError("getSingleElementFromRange: range "+e.inspect()+" did not consist of a single element");return n[0]}function h(e){return!!e&&"undefined"!=typeof e.text}function g(e,t){var n=new P(t);e._ranges=[n],s(e,n,!1),e.rangeCount=1,e.isCollapsed=n.collapsed}function p(t){if(t._ranges.length=0,"None"==t.docSelection.type)d(t);else{var n=t.docSelection.createRange();if(h(n))g(t,n);else{t.rangeCount=n.length;for(var r,o=L(n.item(0)),i=0;i<t.rangeCount;++i)r=e.createRange(o),r.selectNode(n.item(i)),t._ranges.push(r);t.isCollapsed=1==t.rangeCount&&t._ranges[0].collapsed,s(t,t._ranges[t.rangeCount-1],!1)}}}function m(e,n){for(var r=e.docSelection.createRange(),o=l(n),i=L(r.item(0)),a=W(i).createControlRange(),s=0,c=r.length;c>s;++s)a.add(r.item(s));try{a.add(o)}catch(d){throw t.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)")}a.select(),p(e)}function R(e,t,n){this.nativeSelection=e,this.docSelection=t,this._ranges=[],this.win=n,this.refresh()}function v(e){e.win=e.anchorNode=e.focusNode=e._ranges=null,e.rangeCount=e.anchorOffset=e.focusOffset=0,e.detached=!0}function C(e,t){for(var n,r,o=tt.length;o--;)if(n=tt[o],r=n.selection,"deleteAll"==t)v(r);else if(n.win==e)return"delete"==t?(tt.splice(o,1),!0):r;return"deleteAll"==t&&(tt.length=0),null}function N(e,n){for(var r,o=L(n[0].startContainer),i=W(o).createControlRange(),a=0,s=n.length;s>a;++a){r=l(n[a]);try{i.add(r)}catch(c){throw t.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)")}}i.select(),p(e)}function E(e,t){if(e.win.document!=L(t))throw new I("WRONG_DOCUMENT_ERR")}function S(t){return function(n,r){var o;this.rangeCount?(o=this.getRangeAt(0),o["set"+(t?"Start":"End")](n,r)):(o=e.createRange(this.win.document),o.setStartAndEnd(n,r)),this.setSingleRange(o,this.isBackward())}}function y(e){var t=[],n=new B(e.anchorNode,e.anchorOffset),r=new B(e.focusNode,e.focusOffset),o="function"==typeof e.getName?e.getName():"Selection";if("undefined"!=typeof e.rangeCount)for(var i=0,a=e.rangeCount;a>i;++i)t[i]=b.inspect(e.getRangeAt(i));return"["+o+"(Ranges: "+t.join(", ")+")(anchor: "+n.inspect()+", focus: "+r.inspect()+"]"}e.config.checkSelectionRanges=!0;var w,O,T="boolean",_="number",D=e.dom,x=e.util,A=x.isHostMethod,b=e.DomRange,P=e.WrappedRange,I=e.DOMException,B=D.DomPosition,H=e.features,M="Control",L=D.getDocument,W=D.getBody,k=b.rangesEqual,F=A(window,"getSelection"),j=x.isHostObject(document,"selection");H.implementsWinGetSelection=F,H.implementsDocSelection=j;var z=j&&(!F||e.config.preferTextRange);z?(w=i,e.isSelectionValid=function(e){var t=r(e,"isSelectionValid").document,n=t.selection;return"None"!=n.type||L(n.createRange().parentElement())==t}):F?(w=o,e.isSelectionValid=function(){return!0}):t.fail("Neither document.selection or window.getSelection() detected."),e.getNativeSelection=w;var U=w(),V=e.createNativeRange(document),q=W(document),Y=x.areHostProperties(U,["anchorNode","focusNode","anchorOffset","focusOffset"]);H.selectionHasAnchorAndFocus=Y;var Q=A(U,"extend");H.selectionHasExtend=Q;var G=typeof U.rangeCount==_;H.selectionHasRangeCount=G;var X=!1,Z=!0,$=Q?function(t,n){var r=b.getRangeDocument(n),o=e.createRange(r);o.collapseToPoint(n.endContainer,n.endOffset),t.addRange(f(o)),t.extend(n.startContainer,n.startOffset)}:null;x.areHostMethods(U,["addRange","getRangeAt","removeAllRanges"])&&typeof U.rangeCount==_&&H.implementsDomRange&&!function(){var t=window.getSelection();if(t){for(var n=t.rangeCount,r=n>1,o=[],i=a(t),s=0;n>s;++s)o[s]=t.getRangeAt(s);var c=W(document),d=c.appendChild(document.createElement("div"));d.contentEditable="false";var f=d.appendChild(document.createTextNode("   ")),u=document.createRange();if(u.setStart(f,1),u.collapse(!0),t.addRange(u),Z=1==t.rangeCount,t.removeAllRanges(),!r){var l=window.navigator.appVersion.match(/Chrome\/(.*?) /);if(l&&parseInt(l[1])>=36)X=!1;else{var h=u.cloneRange();u.setStart(f,0),h.setEnd(f,3),h.setStart(f,2),t.addRange(u),t.addRange(h),X=2==t.rangeCount}}for(c.removeChild(d),t.removeAllRanges(),s=0;n>s;
vendor: 4,243 bytes, line 2962
2962++s)0==s&&i?$?$(t,o[s]):(e.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend"),t.addRange(o[s])):t.addRange(o[s])}}(),H.selectionSupportsMultipleRanges=X,H.collapsedNonEditableSelectionsSupported=Z;var J,K=!1;q&&A(q,"createControlRange")&&(J=q.createControlRange(),x.areHostProperties(J,["item","add"])&&(K=!0)),H.implementsControlRange=K,O=Y?function(e){return e.anchorNode===e.focusNode&&e.anchorOffset===e.focusOffset}:function(e){return e.rangeCount?e.getRangeAt(e.rangeCount-1).collapsed:!1};var et;A(U,"getRangeAt")?et=function(e,t){try{return e.getRangeAt(t)}catch(n){return null}}:Y&&(et=function(t){var n=L(t.anchorNode),r=e.createRange(n);return r.setStartAndEnd(t.anchorNode,t.anchorOffset,t.focusNode,t.focusOffset),r.collapsed!==this.isCollapsed&&r.setStartAndEnd(t.focusNode,t.focusOffset,t.anchorNode,t.anchorOffset),r}),R.prototype=e.selectionPrototype;var tt=[],nt=function(e){if(e&&e instanceof R)return e.refresh(),e;e=r(e,"getNativeSelection");var t=C(e),n=w(e),o=j?i(e):null;return t?(t.nativeSelection=n,t.docSelection=o,t.refresh()):(t=new R(n,o,e),tt.push({win:e,selection:t})),t};e.getSelection=nt,e.getIframeSelection=function(n){return t.deprecationNotice("getIframeSelection()","getSelection(iframeEl)"),e.getSelection(D.getIframeWindow(n))};var rt=R.prototype;if(!z&&Y&&x.areHostMethods(U,["removeAllRanges","addRange"])){rt.removeAllRanges=function(){this.nativeSelection.removeAllRanges(),d(this)};var ot=function(e,t){$(e.nativeSelection,t),e.refresh()};rt.addRange=G?function(t,r){if(K&&j&&this.docSelection.type==M)m(this,t);else if(n(r)&&Q)ot(this,t);else{var o;X?o=this.rangeCount:(this.removeAllRanges(),o=0);var i=f(t).cloneRange();try{this.nativeSelection.addRange(i)}catch(a){}if(this.rangeCount=this.nativeSelection.rangeCount,this.rangeCount==o+1){if(e.config.checkSelectionRanges){var c=et(this.nativeSelection,this.rangeCount-1);c&&!k(c,t)&&(t=new P(c))}this._ranges[this.rangeCount-1]=t,s(this,t,st(this.nativeSelection)),this.isCollapsed=O(this)}else this.refresh()}}:function(e,t){n(t)&&Q?ot(this,e):(this.nativeSelection.addRange(f(e)),this.refresh())},rt.setRanges=function(e){if(K&&j&&e.length>1)N(this,e);else{this.removeAllRanges();for(var t=0,n=e.length;n>t;++t)this.addRange(e[t])}}}else{if(!(A(U,"empty")&&A(V,"select")&&K&&z))return t.fail("No means of selecting a Range or TextRange was found"),!1;rt.removeAllRanges=function(){try{if(this.docSelection.empty(),"None"!=this.docSelection.type){var e;if(this.anchorNode)e=L(this.anchorNode);else if(this.docSelection.type==M){var t=this.docSelection.createRange();t.length&&(e=L(t.item(0)))}if(e){var n=W(e).createTextRange();n.select(),this.docSelection.empty()}}}catch(r){}d(this)},rt.addRange=function(t){this.docSelection.type==M?m(this,t):(e.WrappedTextRange.rangeToTextRange(t).select(),this._ranges[0]=t,this.rangeCount=1,this.isCollapsed=this._ranges[0].collapsed,s(this,t,!1))},rt.setRanges=function(e){this.removeAllRanges();var t=e.length;t>1?N(this,e):t&&this.addRange(e[0])}}rt.getRangeAt=function(e){if(0>e||e>=this.rangeCount)throw new I("INDEX_SIZE_ERR");return this._ranges[e].cloneRange()};var it;if(z)it=function(t){var n;e.isSelectionValid(t.win)?n=t.docSelection.createRange():(n=W(t.win.document).createTextRange(),n.collapse(!0)),t.docSelection.type==M?p(t):h(n)?g(t,n):d(t)};else if(A(U,"getRangeAt")&&typeof U.rangeCount==_)it=function(t){if(K&&j&&t.docSelection.type==M)p(t);else if(t._ranges.length=t.rangeCount=t.nativeSelection.rangeCount,t.rangeCount){for(var n=0,r=t.rangeCount;r>n;++n)t._ranges[n]=new e.WrappedRange(t.nativeSelection.getRangeAt(n));s(t,t._ranges[t.rangeCount-1],st(t.nativeSelection)),t.isCollapsed=O(t)}else d(t)};else{if(!Y||typeof U.isCollapsed!=T||typeof V.collapsed!=T||!H.implementsDomRange)return t.fail("No means of obtaining a Range or TextRange from the user's selection was found"),!1;it=function(e){var t,n=e.nativeSelection;n.anchorNode?(t=et(n,0),e._ranges=[t],e.rangeCount=1,c(e),e.isCollapsed=O(e)):d(e)}}rt.refresh=function(e){var t=e?this._ranges.slice(0):null,n=this.anchorNode,r=this.anchorOffset;if(it(this),e){var o=t.length;if(o!=this._ranges.length)return!0;
vendor: 4,223 bytes, lines 2962-2975
2962if(this.anchorNode!=n||this.anchorOffset!=r)return!0;for(;o--;)if(!k(t[o],this._ranges[o]))return!0;return!1}};var at=function(e,t){var n=e.getAllRanges();e.removeAllRanges();for(var r=0,o=n.length;o>r;++r)k(t,n[r])||e.addRange(n[r]);e.rangeCount||d(e)};rt.removeRange=K&&j?function(e){if(this.docSelection.type==M){for(var t,n=this.docSelection.createRange(),r=l(e),o=L(n.item(0)),i=W(o).createControlRange(),a=!1,s=0,c=n.length;c>s;++s)t=n.item(s),t!==r||a?i.add(n.item(s)):a=!0;i.select(),p(this)}else at(this,e)}:function(e){at(this,e)};var st;!z&&Y&&H.implementsDomRange?(st=a,rt.isBackward=function(){return st(this)}):st=rt.isBackward=function(){return!1},rt.isBackwards=rt.isBackward,rt.toString=function(){for(var e=[],t=0,n=this.rangeCount;n>t;++t)e[t]=""+this._ranges[t];return e.join("")},rt.collapse=function(t,n){E(this,t);var r=e.createRange(t);r.collapseToPoint(t,n),this.setSingleRange(r),this.isCollapsed=!0},rt.collapseToStart=function(){if(!this.rangeCount)throw new I("INVALID_STATE_ERR");var e=this._ranges[0];this.collapse(e.startContainer,e.startOffset)},rt.collapseToEnd=function(){if(!this.rangeCount)throw new I("INVALID_STATE_ERR");var e=this._ranges[this.rangeCount-1];this.collapse(e.endContainer,e.endOffset)},rt.selectAllChildren=function(t){E(this,t);var n=e.createRange(t);n.selectNodeContents(t),this.setSingleRange(n)},rt.deleteFromDocument=function(){if(K&&j&&this.docSelection.type==M){for(var e,t=this.docSelection.createRange();t.length;)e=t.item(0),t.remove(e),e.parentNode.removeChild(e);this.refresh()}else if(this.rangeCount){var n=this.getAllRanges();if(n.length){this.removeAllRanges();for(var r=0,o=n.length;o>r;++r)n[r].deleteContents();this.addRange(n[o-1])}}},rt.eachRange=function(e,t){for(var n=0,r=this._ranges.length;r>n;++n)if(e(this.getRangeAt(n)))return t},rt.getAllRanges=function(){var e=[];return this.eachRange(function(t){e.push(t)}),e},rt.setSingleRange=function(e,t){this.removeAllRanges(),this.addRange(e,t)},rt.callMethodOnEachRange=function(e,t){var n=[];return this.eachRange(function(r){n.push(r[e].apply(r,t))}),n},rt.setStart=S(!0),rt.setEnd=S(!1),e.rangePrototype.select=function(e){nt(this.getDocument()).setSingleRange(this,e)},rt.changeEachRange=function(e){var t=[],n=this.isBackward();this.eachRange(function(n){e(n),t.push(n)}),this.removeAllRanges(),n&&1==t.length?this.addRange(t[0],"backward"):this.setRanges(t)},rt.containsNode=function(e,t){return this.eachRange(function(n){return n.containsNode(e,t)},!0)||!1},rt.getBookmark=function(e){return{backward:this.isBackward(),rangeBookmarks:this.callMethodOnEachRange("getBookmark",[e])}},rt.moveToBookmark=function(t){for(var n,r,o=[],i=0;n=t.rangeBookmarks[i++];)r=e.createRange(this.win),r.moveToBookmark(n),o.push(r);t.backward?this.setSingleRange(o[0],"backward"):this.setRanges(o)},rt.toHtml=function(){var e=[];return this.eachRange(function(t){e.push(b.toHtml(t))}),e.join("")},H.implementsTextRange&&(rt.getNativeTextRange=function(){var n;if(n=this.docSelection){var r=n.createRange();if(h(r))return r;throw t.createError("getNativeTextRange: selection is a control selection")}if(this.rangeCount>0)return e.WrappedTextRange.rangeToTextRange(this.getRangeAt(0));throw t.createError("getNativeTextRange: selection contains no range")}),rt.getName=function(){return"WrappedSelection"},rt.inspect=function(){return y(this)},rt.detach=function(){C(this.win,"delete"),v(this)},R.detachAll=function(){C(null,"deleteAll")},R.inspect=y,R.isDirectionBackward=n,e.Selection=R,e.selectionPrototype=rt,e.addShimListener(function(e){"undefined"==typeof e.getSelection&&(e.getSelection=function(){return nt(e)}),e=null})});var M=!1,L=function(){M||(M=!0,!b.initialized&&b.config.autoInitialize&&u())};return x&&("complete"==document.readyState?L():(e(document,"addEventListener")&&document.addEventListener("DOMContentLoaded",L,!1),I(window,"load",L))),b},this);
2963
2964/**
2965 * Class Applier module for Rangy.
2966 * Adds, removes and toggles classes on Ranges and Selections
2967 *
2968 * Part of Rangy, a cross-browser JavaScript range and selection library
2969 * https://github.com/timdown/rangy
2970 *
2971 * Depends on Rangy core.
2972 *
2973 * Copyright 2015, Tim Down
2974 * Licensed under the MIT license.
2975 * Version: 1.3.0-beta.1
2976 * Build date: 12 February 2015
2977 */
2978
2979;!function(e,t){ e(t.rangy) }(function(e){return e.createModule("ClassApplier",["WrappedSelection"],function(e,t){function n(e,t){for(var n in e)if(e.hasOwnProperty(n)&&t(n,e[n])===!1)return!1;return!0}function s(e){return e.replace(/^\s\s*/,"").replace(/\s\s*$/,"")}function r(e){return e&&e.split(/\s+/).sort().join(" ")}function o(e){return r(e.className)}function a(e,t){return o(e)==o(t)}function l(e,t){for(var n=t.split(/\s+/),i=0,r=n.length;r>i;++i)if(!I(e,s(n[i])))return!1;return!0}function u(e,t,n,s,i){var r=e.node,o=e.offset,a=r,l=o;r==s&&o>i&&++l,r!=t||o!=n&&o!=n+1||(a=s,l+=i-n),r==t&&o>n+1&&--l,e.node=a,e.offset=l}function f(e,t,n){e.node==t&&e.offset>n&&--e.offset}function c(e,t,n,s){-1==n&&(n=t.childNodes.length);var i=e.parentNode,r=M.getNodeIndex(e);$(s,function(e){u(e,i,r,t,n)}),t.childNodes.length==n?t.appendChild(e):t.insertBefore(e,t.childNodes[n])}function d(e,t){var n=e.parentNode,s=M.getNodeIndex(e);$(t,function(e){f(e,n,s)}),e.parentNode.removeChild(e)}function p(e,t,n,s,i){for(var r,o=[];r=e.firstChild;)c(r,t,n++,i),o.push(r);return s&&d(e,i),o}function h(e,t){return p(e,e.parentNode,M.getNodeIndex(e),!0,t)}function m(e,t){var n=e.cloneRange();n.selectNodeContents(t);var s=n.intersection(e),i=s?s.toString():"";return""!=i}function g(e){for(var t,n=e.getNodes([3]),s=0;(t=n[s])&&!m(e,t);)++s;for(var i=n.length-1;(t=n[i])&&!m(e,t);)--i;return n.slice(s,i+1)}function N(e,t){if(e.attributes.length!=t.attributes.length)return!1;for(var n,s,i,r=0,o=e.attributes.length;o>r;++r)if(n=e.attributes[r],i=n.name,"class"!=i){if(s=t.attributes.getNamedItem(i),null===n!=(null===s))return!1;if(n.specified!=s.specified)return!1;if(n.specified&&n.nodeValue!==s.nodeValue)return!1}return!0}function v(e,t){for(var n,s=0,i=e.attributes.length;i>s;++s)if(n=e.attributes[s].name,(!t||!z(t,n))&&e.attributes[s].specified&&"class"!=n)return!0;return!1}function y(e){var t;return e&&1==e.nodeType&&((t=e.parentNode)&&9==t.nodeType&&"on"==t.designMode||V(e)&&!V(e.parentNode))}function C(e){return(V(e)||1!=e.nodeType&&V(e.parentNode))&&!y(e)}function E(e){return e&&1==e.nodeType&&!k.test(U(e,"display"))}function T(e){if(0==e.data.length)return!0;if(q.test(e.data))return!1;var t=U(e.parentNode,"whiteSpace");switch(t){case"pre":case"pre-wrap":case"-moz-pre-wrap":return!1;case"pre-line":if(/[\r\n]/.test(e.data))return!1}return E(e.previousSibling)||E(e.nextSibling)}function b(e){var t,n,s=[];for(t=0;n=e[t++];)s.push(new j(n.startContainer,n.startOffset),new j(n.endContainer,n.endOffset));return s}function A(e,t){for(var n,s,i,r=0,o=e.length;o>r;++r)n=e[r],s=t[2*r],i=t[2*r+1],n.setStartAndEnd(s.node,s.offset,i.node,i.offset)}function S(e,t){return M.isCharacterDataNode(e)?0==t?!!e.previousSibling:t==e.length?!!e.nextSibling:!0:t>0&&t<e.childNodes.length}function x(e,n,s,i){var r,o,a=0==s;if(M.isAncestorOf(n,e))return e;if(M.isCharacterDataNode(n)){var l=M.getNodeIndex(n);if(0==s)s=l;else{if(s!=n.length)throw t.createError("splitNodeAt() should not be called with offset in the middle of a data node ("+s+" in "+n.data);s=l+1}n=n.parentNode}if(S(n,s)){r=n.cloneNode(!1),o=n.parentNode,r.id&&r.removeAttribute("id");for(var u,f=0;u=n.childNodes[s];)c(u,r,f++,i);return c(r,o,M.getNodeIndex(n)+1,i),n==e?r:x(e,o,M.getNodeIndex(r),i)}if(e!=n){r=n.parentNode;var d=M.getNodeIndex(n);return a||d++,x(e,r,d,i)}return e}function R(e,t){return e.namespaceURI==t.namespaceURI&&e.tagName.toLowerCase()==t.tagName.toLowerCase()&&a(e,t)&&N(e,t)&&"inline"==U(e,"display")&&"inline"==U(t,"display")}function P(e){var t=e?"nextSibling":"previousSibling";return function(n,s){var i=n.parentNode,r=n[t];if(r){if(r&&3==r.nodeType)return r}else if(s&&(r=i[t],r&&1==r.nodeType&&R(i,r))){var o=r[e?"firstChild":"lastChild"];if(o&&3==o.nodeType)return o}return null}}function w(e){this.isElementMerge=1==e.nodeType,this.textNodes=[];var t=this.isElementMerge?e.lastChild:e;t&&(this.textNodes[0]=t)}function O(e,t,i){var o,a,l,u,f=this;f.cssClass=f.className=e;var c=null,d={};if("object"==typeof t&&null!==t){for("undefined"!=typeof t.elementTagName&&(t.elementTagName=t.elementTagName.toLowerCase()),i=t.tagNames,c=t.elementProperties,d=t.elementAttributes,a=0;u=J[a++];)t.hasOwnProperty(u)&&(f[u]=t[u]);o=t.normalize}else o=t;f.normalize="undefined"==typeof o?!0:o,f.attrExceptions=[];var p=document.createElement(f.elementTagName);f.elementProperties=f.copyPropertiesToElement(c,p,!0),n(d,function(e){f.attrExceptions.push(e)}),f.elementAttributes=d,f.elementSortedClassName=f.elementProperties.hasOwnProperty("className")?r(f.elementProperties.className+" "+e):e,f.applyToAnyTagName=!1;var h=typeof i;if("string"==h)"*"==i?f.applyToAnyTagName=!0:f.tagNames=s(i.toLowerCase()).split(/\s*,\s*/);else if("object"==h&&"number"==typeof i.length)for(f.tagNames=[],a=0,l=i.length;l>a;++a)"*"==i[a]?f.applyToAnyTagName=!0:f.tagNames.push(i[a].toLowerCase());else f.tagNames=[f.elementTagName]}function W(e,t,n){return new O(e,t,n)}var I,L,H,M=e.dom,j=M.DomPosition,z=M.arrayContains,B=M.isHtmlNamespace,$=e.util.forEach,D="span";e.util.isHostObject(document.createElement("div"),"classList")?(I=function(e,t){return e.classList.contains(t)},L=function(e,t){return e.classList.add(t)},H=function(e,t){return e.classList.remove(t)}):(I=function(e,t){return e.className&&new RegExp("(?:^|\\s)"+t+"(?:\\
2979s|$)").test(e.className)},L=function(e,t){e.className?I(e,t)||(e.className+=" "+t):e.className=t},H=function(){function e(e,t,n){return t&&n?" ":""}return function(t,n){t.className&&(t.className=t.className.replace(new RegExp("(^|\\s)"+n+"(\\s|$)"),e))}}());var U=M.getComputedStyleProperty,V=function(){var e=document.createElement("div");return"boolean"==typeof e.isContentEditable?function(e){return e&&1==e.nodeType&&e.isContentEditable}:function(e){return e&&1==e.nodeType&&"false"!=e.contentEditable?"true"==e.contentEditable||V(e.parentNode):!1}}(),k=/^inline(-block|-table)?$/i,q=/[^\r\n\t\f \u200B]/,F=P(!1),G=P(!0);w.prototype={doMerge:function(e){var t=this.textNodes,n=t[0];if(t.length>1){var s,i=M.getNodeIndex(n),r=[],o=0;$(t,function(t,a){s=t.parentNode,a>0&&(s.removeChild(t),s.hasChildNodes()||s.parentNode.removeChild(s),e&&$(e,function(e){e.node==t&&(e.node=n,e.offset+=o),e.node==s&&e.offset>i&&(--e.offset,e.offset==i+1&&len-1>a&&(e.node=n,e.offset=o))})),r[a]=t.data,o+=t.data.length}),n.data=r.join("")}return n.data},getLength:function(){for(var e=this.textNodes.length,t=0;e--;)t+=this.textNodes[e].length;return t},toString:function(){var e=[];return $(this.textNodes,function(t,n){e[n]="'"+t.data+"'"}),"[Merge("+e.join(",")+")]"}};var J=["elementTagName","ignoreWhiteSpace","applyToEditableOnly","useExistingElements","removeEmptyElements","onElementCreate"],K={};O.prototype={elementTagName:D,elementProperties:{},elementAttributes:{},ignoreWhiteSpace:!0,applyToEditableOnly:!1,useExistingElements:!0,removeEmptyElements:!0,onElementCreate:null,copyPropertiesToElement:function(e,t,n){var s,i,o,a,l,u,f={};for(var c in e)if(e.hasOwnProperty(c))if(a=e[c],l=t[c],"className"==c)L(t,a),L(t,this.className),t[c]=r(t[c]),n&&(f[c]=a);else if("style"==c){i=l,n&&(f[c]=o={});for(s in e[c])e[c].hasOwnProperty(s)&&(i[s]=a[s],n&&(o[s]=i[s]));this.attrExceptions.push(c)}else t[c]=a,n&&(f[c]=t[c],u=K.hasOwnProperty(c)?K[c]:c,this.attrExceptions.push(u));return n?f:""},copyAttributesToElement:function(e,t){for(var n in e)e.hasOwnProperty(n)&&!/^class(?:Name)?$/i.test(n)&&t.setAttribute(n,e[n])},appliesToElement:function(e){return z(this.tagNames,e.tagName.toLowerCase())},getEmptyElements:function(e){var t=this;return e.getNodes([1],function(e){return t.appliesToElement(e)&&!e.hasChildNodes()})},hasClass:function(e){return 1==e.nodeType&&(this.applyToAnyTagName||this.appliesToElement(e))&&I(e,this.className)},getSelfOrAncestorWithClass:function(e){for(;e;){if(this.hasClass(e))return e;e=e.parentNode}return null},isModifiable:function(e){return!this.applyToEditableOnly||C(e)},isIgnorableWhiteSpaceNode:function(e){return this.ignoreWhiteSpace&&e&&3==e.nodeType&&T(e)},postApply:function(e,t,n,s){var r,o,a=e[0],l=e[e.length-1],u=[],f=a,c=l,d=0,p=l.length;$(e,function(e){o=F(e,!s),o?(r||(r=new w(o),u.push(r)),r.textNodes.push(e),e===a&&(f=r.textNodes[0],d=f.length),e===l&&(c=r.textNodes[0],p=r.getLength())):r=null});var h=G(l,!s);if(h&&(r||(r=new w(l),u.push(r)),r.textNodes.push(h)),u.length){for(var i=0,len=u.length;len>i;++i)u[i].doMerge(n);t.setStartAndEnd(f,d,c,p)}},createContainer:function(e){var t=e.createElement(this.elementTagName);return this.copyPropertiesToElement(this.elementProperties,t,!1),this.copyAttributesToElement(this.elementAttributes,t),L(t,this.className),this.onElementCreate&&this.onElementCreate(t,this),t},elementHasProperties:function(e,t){var s=this;return n(t,function(t,n){if("className"==t)return l(e,n);if("object"==typeof n){if(!s.elementHasProperties(e[t],n))return!1}else if(e[t]!==n)return!1})},elementHasAttributes:function(e,t){return n(t,function(t,n){return e.getAttribute(t)!==n?!1:void 0})},applyToTextNode:function(e){var t=e.parentNode;if(1==t.childNodes.length&&this.useExistingElements&&B(t)&&this.appliesToElement(t)&&this.elementHasProperties(t,this.elementProperties)&&this.elementHasAttributes(t,this.elementAttributes))L(t,this.className);else{var n=this.createContainer(M.getDocument(e));e.parentNode.insertBefore(n,e),n.appendChild(e)}},isRemovable:function(e){return B(e)&&e.tagName.toLowerCase()==this.elementTagName&&o(e)==this.elementSortedClassName&&this.elementHasProperties(e,this.elementProperties)&&!v(e,this.attrExceptions)&&this.elementHasAttributes(e,this.elementAttributes)&&this.isModifiable(e)},isEmptyContainer:function(e){var t=e.childNodes.length;return 1==e.nodeType&&this.isRemovable(e)&&(0==t||1==t&&this.isEmptyContainer(e.firstChild))},removeEmptyContainers:function(e){var t=this,n=e.getNodes([1],function(e){return t.isEmptyContainer(e)}),s=[e],i=b(s);$(n,function(e){d(e,i)}),A(s,i)},undoToTextNode:function(e,t,n,s){if(!t.containsNode(n)){var i=t.cloneRange();i.selectNode(n),i.isPointInRange(t.endContainer,t.endOffset)&&(x(n,t.endContainer,t.endOffset,s),t.setEndAfter(n)),i.isPointInRange(t.startContainer,t.startOffset)&&(n=x(n,t.startContainer,t.startOffset,s))}this.isRemovable(n)?h(n,s):H(n,this.className)},splitAncestorWithClass:function(e,t,n){var s=this.getSelfOrAncestorWithClass(e);s&&x(s,e,t,n)},undoToAncestor:function(e,t){this.isRemovable(e)?h(e,t):H(e,this.className)},applyToRange:function(e,t){var n=this;t=t||[];var s=b(t||[]);e.splitBoundariesPreservingPositions(s),n.removeEmptyElements&&n.removeEmptyContainers(e);var i=g(e);if(i.length){$(i,function(e){n.isIgnorableWhiteSpaceNode(e)||n.getSelfOrAncestorWithClass(e)||!n.isModifiable(e)||n.applyToTextNode(e,s)});var r=i[i.length-1];e.setStartAndEnd(i[0],0,r,r.length),n.normalize&&n.postApply(i,e,s,!1),A(t,s)}var o=n.getEmptyElements(e);$(o,function(e){L(e,n.className)})},applyToRanges:function(e){for(var t=e.length;t--;)this.applyToRange(e[t],e);return e},applyToSelection:function(t){var n=e.getSelection(t);n.setRanges(this.applyToRanges(n.getAllRanges()))},undoToRange:function(e,t){var n=this;t=t||[];var s=b(t);e.splitBoundariesPreservingPositions(s),n.removeEmptyElements&&n.removeEmptyContainers(e,s);var i,r,o=g(e),a=o[o.length-1];if(o.length){n.splitAncestorWithClass(e.endContainer,e.endOffset,s),n.splitAncestorWithClass(e.startContainer,e.startOffset,s);for(var l=0,u=o.length;u>l;++l)i=o[l],r=n.getSelfOrAncestorWithClass(i),r&&n.isModifiable(i)&&n.undoToAncestor(r,s);e.setStartAndEnd(o[0],0,a,a.length),n.normalize&&n.postApply(o,e,s,!0),A(t,s)}var f=n.getEmptyElements(e);$(f,function(e){H(e,n.className)})},undoToRanges:function(e){for(var t=e.length;t--;)this.undoToRange(e[t],e);return e},undoToSelection:function(t){var n=e.getSelection(t),s=e.getSelection(t).getAllRanges();this.undoToRanges(s),n.setRanges(s)},isAppliedToRange:function(e){if(e.collapsed||""==e.toString())return!!this.getSelfOrAncestorWithClass(e.commonAncestorContainer);var t=e.getNodes([3]);if(t.length)for(var n,s=0;n=t[s++];)if(!this.isIgnorableWhiteSpaceNode(n)&&m(e,n)&&this.isModifiable(n)&&!this.getSelfOrAncestorWithClass(n))return!1;return!0},isAppliedToRanges:function(e){var t=e.length;if(0==t)return!1;for(;t--;)if(!this.isAppliedToRange(e[t]))return!1;return!0},isAppliedToSelection:function(t){var n=e.getSelection(t);return this.isAppliedToRanges(n.getAllRanges())},toggleRange:function(e){this.isAppliedToRange(e)?this.undoToRange(e):this.applyToRange(e)},toggleSelection:function(e){this.isAppliedToSelection(e)?this.undoToSelection(e):this.applyToSelection(e)},getElementsWithClassIntersectingRange:function(e){var t=[],n=this;return e.getNodes([3],function(e){var s=n.getSelfOrAncestorWithClass(e);
2979s&&!z(t,s)&&t.push(s)}),t},detach:function(){}},O.util={hasClass:I,addClass:L,removeClass:H,hasSameClasses:a,hasAllClasses:l,replaceWithOwnChildren:h,elementsHaveSameNonClassAttributes:N,elementHasNonClassAttributes:v,splitNodeAt:x,isEditableElement:V,isEditingHost:y,isEditable:C},e.CssClassApplier=e.ClassApplier=O,e.createCssClassApplier=e.createClassApplier=W}),e},this);
2980
2981
2982
2983   /**
2984   * An Editor instance is created for every individual editor. It handles all input for that element
2985   *  <br /><br />
2986   * <br /><br />
2987   * This is a private class and can only be instanciated via {@link CargoEditor.addEditor}. Use {@link CargoEditor.getActiveEditor} or {@link CargoEditor.getEditorInstances} to retrieve instances.
2988   * <pre><code>
2989   *  $('article').each(function() {
2990   *    if (!$(this).is('[contenteditable]')) {
2991   *        CargoEditor.addEditor(this);
2992   *    }
2993   *  });
2994   * </pre></code>
2995   *
2996   * @constructor 
2997   * @private
2998   * @memberOf CargoEditor
2999   * @returns {CargoEditor.Editor|Boolean} Returns an Editor object if init is successful. In case the init fails it returns false.
3000   */
3001
3002
3003	var Editor = function(element) {
3004
3005		var _this = this,
3006			isActive = false,
3007			isEmpty = false,
3008			element = element,
3009			userInitiatedMutationInProgress = false,
3010			emulatingMutation = false,
3011			mutationSummaryObserver,
3012			historyDelay = 1250,
3013			executionDepth = 0,
3014			dragster;
3015
3016		/*
3017		 * Private function to handle mutation summary callbacks.
3018		 * To trigger this use the generateMutationSummary method.
3019		 */
3020			
3021		function _handleSummaryCallback(summaries){
3022
3023			if(summaries !== undefined){
3024
3025				if(summaries.length > 0){
3026
3027					summaries.forEach(function(summary){
3028						_core.events.trigger('editor-content-change', _this, summary, userInitiatedMutationInProgress);
3029					});
3030
3031				}
3032
3033			}
3034
3035			return summaries;
3036
3037		};
3038
3039		this.keyEvents = {};
3040		this.historyTimeoutInEffect = false;
3041		this.elementEvents = {};
3042		this.editorEvents = {};
3043		this.core = _core;
3044
3045		var initialize = function() {
3046
3047			element.setAttribute('contenteditable', 'true');
3048			element.setAttribute('spellcheck', 'false');
3049
3050			mutationSummaryObserver = new MutationSummary({
3051				callback: function(){
3052					
3053					/**
3054					 * This is not supposed to fire as we suspend
3055					 * summary deliveries and only manually retrieve changes
3056					 * when a mutation starts and finishes.
3057					 *
3058					 * However there might be a bug or some other unexpected
3059					 * cause for this to fire. In that case we still handle
3060					 * the callback.
3061					 */
3062					
3063					_handleSummaryCallback.apply(this, arguments);
3064
3065				},
3066				rootNode: _this.getElement(),
3067				oldPreviousSibling: true,
3068				observeOwnChanges: false,
3069				queries: [{
3070					all: true
3071				}]
3072			});
3073
3074			/*
3075			 * Suspending summary delivery allows us to control when summaries are reported.
3076			 *
3077			 * By calling the generateMutationSummary function we can report all the changes that 
3078			 * occured to the editor's DOM since the last time generateMutationSummary was called.
3079			 */
3080			
3081			mutationSummaryObserver.suspendDelivery();
3082
3083			this.addEditorEventListener('mutation-start', function(editor, ignoreHistory) {
3084
3085				// every instance listens to this, but only the targeted instance should respond.
3086				if(editor === _this) {
3087
3088					executionDepth++;
3089
3090					// report any queued mutations that were caught in between our mutations, 
3091					// could be DOM manipulations from another script or something.
3092					
3093					if(ignoreHistory !== true){
3094						_this.generateMutationSummary();
3095					}
3096
3097					userInitiatedMutationInProgress = true;
3098
3099				}
3100
3101			});
3102
3103			this.addEditorEventListener('mutation-success', function(editor, ignoreHistory) {
3104
3105				// every instance listens to this, but only the targeted instance should respond.
3106				if(editor === _this) {
3107
3108					// report any queued mutations that have not been reported yet
3109					if(ignoreHistory !== true){
3110						_this.generateMutationSummary();
3111					}
3112
3113					if(executionDepth <= 1) {
3114						userInitiatedMutationInProgress = false;
3115					} else {
3116						console.info('Nested mutation ended (stack size: ' + executionDepth + '). Not ending userInitiated state.');
3117					}
3118
3119				}
3120
3121			});
3122
3123			this.addEditorEventListener('mutation-end', function(editor, ignoreHistory) {
3124
3125				executionDepth--;
3126
3127			});
3128
3129			this.addEditorEventListener('editor-content-change', function(editor, summary, userGenerated) {
3130
3131				// if nodes are added through a user interaction: mark them as saveable.
3132				if(editor === _this && userGenerated === true) {
3133					
3134					summary.added.forEach(function(addedNode){
3135						
3136						addedNode.setSaveable(true);
3137
3138						if(addedNode.childNodes.length > 0){
3139							
3140							helpers.getAllDescendants(addedNode, function(childNode){
3141								childNode.setSaveable(true);
3142							});
3143
3144						}
3145
3146					});
3147
3148					if(summary.added.length > 0 || summary.characterDataChanged.length > 0){
3149						_core.events.trigger('cursor-activity', editor);
3150					}
3151
3152				}
3153
3154				_this.checkIfEmpty();
3155
3156			});
3157
3158			// setup Dragster
3159			dragster = new _core.Dragster( element );
3160
3161			this.addElementEventListener( "dragster:enter", function (event) {
3162
3163				event.dataTransfer = event.detail.dataTransfer;
3164				_core.events.trigger('editor-dragin', _this, event);
3165
3166			});
3167
3168			this.addElementEventListener( "dragster:leave", function (event) {
3169			
3170				event.dataTransfer = event.detail.dataTransfer;
3171				_core.events.trigger('editor-dragout', _this, event);
3172
3173			});
3174
3175			/**
3176			 * DOM Events
3177			 */
3178			this.addElementEventListener('paste', function(event){
3179
3180				_core.events.trigger('editor-paste', _this, event);
3181
3182			});
3183
3184			this.addElementEventListener('dragenter', function(event){
3185
3186				_core.events.trigger('editor-dragenter', _this, event);
3187
3188			});
3189
3190			this.addElementEventListener('dragleave', function(event){
3191
3192				_core.events.trigger('editor-dragleave', _this, event);
3193
3194			});
3195
3196			this.addElementEventListener('dragstart', function(event){
3197
3198				_core.events.trigger('editor-dragstart', _this, event);
3199
3200			});
3201
3202			this.addElementEventListener('dragover', function(event){
3203
3204				_core.events.trigger('editor-dragover', _this, event);
3205
3206			});
3207
3208			this.addElementEventListener('dragend', function(event){
3209
3210				_core.events.trigger('editor-dragend', _this, event);
3211
3212			});
3213
3214			this.addElementEventListener('drop', function(event){
3215
3216				console.log('drop', _this, event);
3217
3218				_core.events.trigger('editor-drop', _this, event);
3219
3220				event.preventDefault();
3221
3222			});
3223
3224			this.addElementEventListener('mouseenter', function(event){
3225
3226					_core.events.trigger('editor-mouseenter', _this, event);
3227
3228			});
3229
3230			this.addElementEventListener('mouseleave', function(event){
3231
3232					_core.events.trigger('editor-mouseleave', _this, event);
3233
3234			});
3235
3236			this.addElementEventListener('click', function(event){
3237
3238					_core.events.trigger('editor-click', _this, event);
3239
3240			});
3241
3242			this.addElementEventListener('mouseup', function(event){
3243				
3244				/**
3245				 * Fires when a the cursor has moved.
3246				 *
3247				 * @event CargoEditor#cursor-activity
3248				 * @property {CargoEditor.Editor} editor The editor instance containing the cursor
3249				 */
3250
3251				_core.events.trigger('cursor-activity', _this);
3252
3253			});
3254
3255			this.addElementEventListener('focus', function() {
3256
3257				/**
3258				 * Fires when an editor recieves focus.
3259				 *
3260				 * @event CargoEditor#editor-activated
3261				 * @property {CargoEditor.Editor} editor The editor instance that's receiving focus
3262				 */
3263
3264				_core.events.trigger('editor-activated', _this);
3265				isActive = true;
3266
3267			});
3268
3269			this.addElementEventListener('blur', function() {
3270
3271				/**
3272				 * Fires when an editor loses focus.
3273				 *
3274				 * @event CargoEditor#editor-deactivated
3275				 * @property {CargoEditor.Editor} editor The editor instance that's losing focus
3276				 */
3277				
3278				_core.events.trigger('editor-deactivated', _this);
3279				isActive = false;
3280
3281			});
3282
3283			// keyPresses are handled by the browser's contentEditable implementation,
3284			// however we need to fire a mutation event for them too.
3285
3286			this.addElementEventListener('keypress', function(event) {
3287
3288				// in case keyPress fires before keyUp occured. This happens
3289				// when a key is pressed and the previous key hasn't been released yet.
3290				if(emulatingMutation === true){
3291
3292					_core.events.trigger('mutation-end', _this);
3293					_core.events.trigger('mutation-success', _this, _this.historyTimeoutInEffect);
3294
3295				}
3296
3297				emulatingMutation = true;
3298				userInitiatedMutationInProgress = true;
3299
3300				_core.events.trigger('mutation-start', _this, _this.historyTimeoutInEffect);
3301
3302				// IE doesn't support the input event, so we
3303				// do it like this.
3304				setTimeout(function(){
3305
3306					// this can replace the 'input' event callback
3307
3308				});
3309
3310			});
3311
3312			this.addElementEventListener('input', function(event){
3313
3314				_this.checkIfEmpty();
3315
3316				if(emulatingMutation === true){
3317				
3318					_core.events.trigger('mutation-end', _this, _this.historyTimeoutInEffect);
3319					_core.events.trigger('mutation-success', _this, _this.historyTimeoutInEffect);
3320
3321					emulatingMutation = false;
3322
3323				}
3324
3325				if(_this.historyTimeoutInEffect === false){
3326
3327					_this.historyTimeoutInEffect = true;
3328
3329					setTimeout(function(){
3330
3331						_this.historyTimeoutInEffect = false;
3332
3333					}, historyDelay);
3334
3335				}
3336			});
3337
3338			this.addElementEventListener('keydown', function(event) {
3339
3340				// if the arrow keys are pressed, we assume the cursor
3341				// is being moved
3342				switch (event.keyCode) {
3343					case 37:
3344					case 38:
3345					case 39:
3346					case 40:
3347
3348						// moving cursor with arrow keys
3349						setTimeout(function(){
3350							_core.events.trigger('cursor-activity', _this);
3351						});
3352						break;
3353
3354					case 65: // a
3355
3356						if(event.metaKey) {
3357
3358							// cmd/ctrl + a, select all.
3359							setTimeout(function(){
3360								_core.events.trigger('cursor-activity', _this);
3361							}, 5);
3362
3363						}
3364
3365						break;
3366
3367				}
3368
3369
3370			});
3371
3372			/**
3373			 * Custom key listeners
3374			 */
3375			
3376			// ignore the default contentEditable shortcuts
3377			this.addKeyListener('cmd + b / cmd + i', function(event){
3378
3379				event.preventDefault();
3380
3381			});
3382
3383			this.addKeyListener('backspace', function(event){
3384
3385				_core.execCommand('delete');
3386				event.preventDefault();
3387
3388			});
3389
3390			this.addKeyListener('delete', function(event){
3391
3392				_core.execCommand('forwardDelete');
3393				event.preventDefault();
3394
3395			});
3396
3397			this.addKeyListener('tab', function(event){
3398				
3399				event.preventDefault();
3400
3401				var in_code = false,
3402					selectedNodeList = helpers.getCurrentSelectionTree(event.target.parentNode).nodes,
3403					range = _core.getActiveRange(),
3404					i, nn;
3405
3406				for (i = 0; i < selectedNodeList.length; i++) {
3407					nn = selectedNodeList[i].nodeName.toLowerCase();
3408
3409					if (nn === "code" || nn === "pre" || nn === "xpm") {
3410						in_code = true;
3411						break; 
3412					}
3413				}
3414
3415				if(in_code){
3416
3417					userInitiatedMutationInProgress = true;
3418					_core.execCommand('inserthtml', false, '\t');
3419
3420				} else {
3421
3422					if(range !== null){
3423
3424						if(range.collapsed){
3425
3426							userInitiatedMutationInProgress = true;
3427							_core.execCommand('inserthtml', false, '&nbsp;&nbsp;&nbsp;&nbsp;');
3428
3429						} else {
3430
3431							userInitiatedMutationInProgress = true;
3432							_core.execCommand('indent');
3433
3434						}
3435
3436					} else {
3437
3438						userInitiatedMutationInProgress = true;
3439						_core.execCommand('indent');
3440
3441					}
3442
3443				}
3444
3445			});
3446
3447			this.addKeyListener('shift+tab', function(event){
3448
3449				event.preventDefault();
3450
3451				// command ain't workin.
3452				//_core.execCommand('outdent');
3453
3454				_core.mutationManager.execute(function(){
3455					
3456					var range = _core.getActiveRange();
3457
3458					if(range !== undefined){
3459
3460						var parent = range.commonAncestorContainer;
3461
3462						while(parent.parentNode !== null){
3463
3464							if(parent.nodeName === "BLOCKQUOTE"){
3465
3466								helpers.unwrap(parent, true);
3467								break;
3468
3469							}
3470
3471							parent = parent.parentNode;
3472						}
3473
3474					}
3475
3476				});
3477
3478			});
3479
3480			this.addKeyListener('enter', function(event){
3481
3482				var in_list = false,
3483					in_code = false,
3484					selectedNodeList = helpers.getCurrentSelectionTree(event.target.parentNode).nodes,
3485					i, nn;
3486
3487				for (i = 0; i < selectedNodeList.length; i++) {
3488					nn = selectedNodeList[i].nodeName.toLowerCase();
3489
3490					if (nn === "li" || nn === "ul" || nn == "ol") { in_list = true; }
3491					if (nn === "code" || nn === "pre" || nn === "xpm") { in_code = true; }
3492				}
3493
3494				if (event.keyCode === 13 && !in_list) {
3495
3496					if(in_code) {
3497						
3498						userInitiatedMutationInProgress = true;
3499						_core.execCommand('inserthtml', false, '\n');
3500
3501					} else {
3502						
3503						userInitiatedMutationInProgress = true;
3504						_core.execCommand('insertlinebreak');
3505
3506					}
3507
3508					event.preventDefault();
3509					event.stopPropagation();
3510
3511				}
3512
3513			});
3514
3515			/**
3516			 * Fires when a new editor has been instanciated
3517			 *
3518			 * @event CargoEditor#editor-instanciated
3519			 * @property {CargoEditor.Editor} editor The instanciated editor
3520			 */
3521
3522			_core.events.trigger('editor-instanciated', _this);
3523			
3524			this.checkIfEmpty();
3525
3526			return this;
3527
3528		};
3529
3530		/*
3531		 *   Getters
3532		 */
3533
3534		/**
3535		* Returns the element that's being used as editor.
3536		* @method
3537		* @returns {Element} the DOM object for the element.
3538		*/
3539
3540		this.getElement = function() {
3541			return element;
3542		}
3543
3544		/**
3545		* Returns the element that's being used as editor.
3546		* @method
3547		* @returns {Element} the DOM object for the element.
3548		*/
3549
3550		this.getMutationSummaryObserver = function() {
3551			return mutationSummaryObserver;
3552		}
3553
3554		this.generateMutationSummary = function(){
3555			return _handleSummaryCallback(mutationSummaryObserver.takeSummaries());
3556		}
3557
3558		/**
3559		* Returns true if the editor is in focus and false if it's not.
3560		* @method
3561		* @returns {Boolean}
3562		*/
3563
3564		this.isActive = function() {
3565			return isActive;
3566		}
3567
3568		/**
3569		* Returns saveable nodes.
3570		* @method
3571		* @returns {String}
3572		*
3573		*/
3574
3575		this.getSaveableNodes = function() {
3576
3577			var _this = this,
3578				frag = document.createDocumentFragment(),
3579				nodesToUnwrap = [],
3580				nodesToRemove = [];
3581
3582			frag.appendChild(element.persistCloneNode(true));
3583
3584			// postpone removing and unwrapping to not mess with the structure while
3585			// traversing it.
3586			helpers.getAllDescendants(frag, function(child){
3587				if(child.isSaveable() === false){
3588					if(child.childNodes.length > 0) {
3589						nodesToUnwrap.push(child);
3590					} else {
3591						nodesToRemove.push(child);
3592					}
3593				}
3594			});
3595
3596			for(var i = nodesToUnwrap.length - 1; i >= 0; i--){
3597				helpers.unwrap(nodesToUnwrap[i], true);
3598			}
3599
3600			for(var i = 0; i < nodesToRemove.length; i++){
3601				nodesToRemove[i].parentNode.removeChild(nodesToRemove[i]);
3602			}
3603
3604			return frag;
3605
3606		},
3607
3608		this.markAsClean = function(){
3609
3610			_core.events.trigger('editor-mark-clean', _this);
3611
3612		},
3613
3614		this.checkIfEmpty = function(){
3615
3616			// if an image is present, the editor is not empty.
3617			var containsVisibleElement = false;
3618
3619			helpers.getAllDescendants(element, function(node){
3620				if(node.nodeName === "IMG"){
3621					containsVisibleElement = true;
3622				}
3623			});
3624
3625			if( ! /[^\t\n\r\s]/.test(element.textContent) && ! containsVisibleElement ){
3626
3627				/**
3628				 * @event editor-no-content
3629				 *
3630				 * Fires when the editor contains no text or just whitespace.
3631				 */
3632				
3633				if(isEmpty === false){
3634					
3635					isEmpty = true;
3636					_this.moveCaret();
3637					_core.events.trigger('editor-no-content', _this);
3638
3639				}
3640
3641			} else {
3642
3643				if(isEmpty === true){
3644					
3645					isEmpty = false;
3646
3647					/**
3648					 * @event editor-has-content
3649					 *
3650					 * Fires when the editor used to contain no text or just whitespace and now has content
3651					 */
3652
3653					_core.events.trigger('editor-has-content', _this);
3654
3655				}
3656
3657			}
3658
3659		},
3660
3661		this.moveCaret = function(node, index){
3662
3663			if(node === undefined) {
3664				node = element.firstChild;
3665			}
3666
3667			if(node === null){
3668				node = element.lastChild;
3669			}
3670
3671			if(isNaN(index)){
3672				index = 0;
3673			}
3674
3675			var range = _core.rangy.createRange();
3676
3677			if(node.nodeType === Node.TEXT_NODE){
3678				
3679				range.setStart(node, index);
3680
3681			} else {
3682
3683				if(index > 0) {
3684					range.setStartAfter(node);
3685				} else {
3686					range.setStartBefore(node);
3687				}
3688				
3689			}
3690			
3691			range.collapse(true);
3692
3693			var sel = _core.rangy.getSelection();
3694			sel.setSingleRange(range);
3695
3696			_core.events.trigger('cursor-activity', _this);
3697
3698		},
3699
3700		this.moveCaretToEnd = function(){
3701
3702			var range = _core.rangy.createRange();
3703				range.selectNode(element.lastChild);
3704				range.collapse(false);
3705
3706			var sel = _core.rangy.getSelection();
3707				sel.setSingleRange(range);
3708
3709			_core.events.trigger('cursor-activity', _this);
3710
3711		},
3712
3713		this.scrollIntoView = function(){
3714
3715			/*var range;
3716
3717			if((range = _core.getActiveRange()) === undefined){
3718				return;
3719			}
3720
3721			var parent = range.commonAncestorContainer,
3722				inEditor = false;
3723
3724			while(parent.parentNode !== null){
3725
3726				if(parent === element){
3727					inEditor = true;
3728					break;
3729				}
3730
3731				parent = parent.parentNode;
3732			}
3733
3734			if( ! inEditor ) {
3735				return;
3736			}
3737
3738			var cursorPosition = helpers.getCursorCoordinates(),
3739				windowHeight = window.innerHeight;
3740
3741			console.log(cursorPosition, windowHeight);*/
3742
3743		},
3744
3745		/**
3746		* Returns the all HTML inside the editor as a string.
3747		* @method
3748		* @returns {String}
3749		*/
3750
3751		this.getHTML = function() {
3752			return element.innerHTML;
3753		}
3754
3755		/**
3756		 * Remove self from editor list and all other references, 
3757		 * unbind all event listeners,
3758		 * release mutation observer,
3759		 * remove contenteditable and other attributes
3760		 */
3761
3762		this.destroy = function(){
3763
3764			if( this.destroyed === true ) {
3765				return;
3766			}
3767
3768			_core.events.trigger('editor-before-destroy', _this);
3769
3770			var key;
3771
3772			// Remove key codes
3773			for(key in this.keyEvents){
3774				this.removeKeyListener(key);
3775			}
3776
3777			// remove DOM event listeners
3778			for(key in this.elementEvents){
3779
3780				while(this.elementEvents[key] !== undefined && this.elementEvents[key].length > 0){
3781					this.removeElementEventListener(key, this.elementEvents[key][0]);
3782				}
3783
3784			}
3785
3786			// remove Editor event listeners
3787			for(key in this.editorEvents){
3788
3789				while(this.editorEvents[key] !== undefined && this.editorEvents[key].length > 0){
3790					this.removeEditorEventListener(key, this.editorEvents[key][0]);
3791				}
3792
3793			}
3794
3795			// dragster
3796			if(dragster !== null){
3797				dragster.removeListeners();
3798				dragster = null;
3799			}
3800
3801			// MutationSummary
3802			if(mutationSummaryObserver !== null){
3803				mutationSummaryObserver.disconnect();
3804				mutationSummaryObserver = null;
3805			}
3806
3807			// attributes
3808			element.removeAttribute('contenteditable');
3809			element.removeAttribute('spellcheck');
3810
3811			_core.events.trigger('editor-destroyed', _this);
3812
3813			this.destroyed = true;
3814
3815		}
3816
3817		return initialize.call(this);
3818
3819	};
3820
3821	helpers.extend(Editor.prototype, {
3822
3823		addKeyListener: function(key, callBack){
3824
3825			if(this.keyEvents.hasOwnProperty(key)){
3826				this.removeKeyListener(key);
3827			}
3828
3829			var callBack = _core.jwerty.event(key, callBack, this);
3830
3831			this.addElementEventListener('keydown', callBack);
3832
3833			this.keyEvents[key] = callBack;
3834		
3835
3836		}, 
3837
3838		removeKeyListener: function(key){
3839
3840			if(this.keyEvents.hasOwnProperty(key)){
3841
3842				var callBack = this.keyEvents[key];
3843
3844				this.removeElementEventListener('keydown', callBack);
3845
3846				delete this.keyEvents[key];
3847
3848			}
3849
3850		},
3851
3852		/**
3853		 * Adds an event listener to the editor's element
3854		 */
3855		
3856		addElementEventListener: function(name, callback) {
3857
3858			if( ! this.elementEvents.hasOwnProperty(name) ) {
3859				this.elementEvents[name] = [];
3860			}
3861
3862			if( this.elementEvents[name].indexOf(callback) === -1 ){
3863				
3864				this.elementEvents[name].push(callback);
3865
3866			}
3867
3868			return this.getElement().addEventListener(name, callback, false);
3869
3870		},
3871
3872		/**
3873		 * Removes an event listener from the editor's element
3874		 */
3875		
3876		removeElementEventListener: function(name, callback) {
3877
3878			if( this.elementEvents.hasOwnProperty(name) ) {
3879
3880				var callbackIndex = this.elementEvents[name].indexOf(callback);
3881				
3882				if( callbackIndex !== -1 ){
3883
3884					this.elementEvents[name].splice(callbackIndex, 1);
3885
3886					if(this.elementEvents[name].length === 0){
3887						delete this.elementEvents[name];
3888					}
3889
3890				}
3891
3892			}
3893
3894			// attempt to remove even if it's not in our list. Just 
3895			// in case the event wasn't added through addElementEventListener 
3896			this.getElement().removeEventListener(name, callback);
3897
3898			callback = null;
3899
3900		},
3901
3902		addEditorEventListener: function(name, callback) {
3903
3904			if( ! this.editorEvents.hasOwnProperty(name) ) {
3905				this.editorEvents[name] = [];
3906			}
3907
3908			if( this.editorEvents[name].indexOf(callback) === -1 ){
3909				
3910				this.editorEvents[name].push(callback);
3911
3912			}
3913
3914			this.core.events.on(name, callback);
3915
3916		},
3917
3918		removeEditorEventListener: function(name, callback) {
3919
3920			if( this.editorEvents.hasOwnProperty(name) ) {
3921
3922				var callbackIndex = this.editorEvents[name].indexOf(callback);
3923				
3924				if( callbackIndex !== -1 ){
3925
3926					this.editorEvents[name].splice(callbackIndex, 1);
3927
3928					if(this.editorEvents[name].length === 0){
3929						delete this.editorEvents[name];
3930					}
3931
3932				}
3933
3934			}
3935			
3936			this.core.events.off(name, callback);
3937
3938			callback = null;
3939
3940		}
3941
3942	});
3943
3944(function(){
3945
3946	/**
3947	* The MutationManager wraps function calls (like execCommand) with some extra functionality.
3948	*
3949	* Listen to mutation events by
3950	*
3951	* @class 
3952	* @private
3953	* @memberOf CargoEditor
3954	*/
3955	
3956	var MutationManager = function(){};
3957
3958	helpers.extend(MutationManager.prototype, {
3959
3960		/**
3961		* Executes a mutation
3962		* @method
3963		* @param {Function} mutation The mutation
3964		* @param {Array} args Arguments passed to the subscriptions
3965		*
3966		* @memberOf CargoEditor.MutationManager
3967		*/
3968	
3969		execute: function(mutation, ignoreHistory){
3970
3971			ignoreHistory = !!ignoreHistory;
3972
3973			this.before(ignoreHistory);
3974				
3975			if (mutation) {
3976				if(mutation() === false) {
3977					this.error(ignoreHistory);
3978				} else {
3979					this.success(ignoreHistory);
3980				}
3981			}
3982
3983			this.always(ignoreHistory);
3984
3985		},
3986
3987		/**
3988		 * Fires before anything has changed.
3989		 *
3990		 * @memberOf CargoEditor.MutationManager
3991		 */
3992		
3993		before: function(ignoreHistory){
3994			/**
3995			 * Fires before a mutation executes
3996			 *
3997			 * @event CargoEditor#mutation-start
3998			 * @property {CargoEditor.EditorInstance} editor The editor on which the mutation is applied
3999			 */
4000			
4001			_core.events.trigger('mutation-start', _core.getActiveEditor(), ignoreHistory);
4002		},
4003
4004		/**
4005		 * Always fires regardless of mutation outcome.
4006		 *
4007		 * @memberOf CargoEditor.MutationManager
4008		 */
4009		
4010		always: function(ignoreHistory){
4011
4012			/**
4013			 * Fires when a mutation is done. Regardless if it failed or succeeded.
4014			 *
4015			 * @event CargoEditor#mutation-end
4016			 * @property {CargoEditor.EditorInstance} editor The editor on which the mutation is applied
4017			 */
4018			
4019			_core.events.trigger('mutation-end', _core.getActiveEditor(), ignoreHistory);
4020		}, 
4021
4022		/**
4023		 *	Fires if the mutation threw an exception.
4024		 *
4025		 *  @memberOf CargoEditor.MutationManager
4026		 */
4027
4028		error: function(ignoreHistory){
4029			console.warn(getLocalization('mutation-fail'));
4030
4031			/**
4032			 * Fires when a mutation failed.
4033			 *
4034			 * @event CargoEditor#mutation-fail
4035			 * @property {CargoEditor.EditorInstance} editor The editor on which the mutation is applied
4036			 */
4037			
4038			_core.events.trigger('mutation-fail', _core.getActiveEditor(), ignoreHistory);
4039		},
4040
4041		/**
4042		 *	Fires if the mutation completed without errors. Fires before always.
4043		 *
4044		 * @memberOf CargoEditor.MutationManager
4045		 */
4046
4047		success: function(ignoreHistory){
4048
4049			/**
4050			 * Fires when a mutation was a success.
4051			 *
4052			 * @event CargoEditor#mutation-success
4053			 * @property {CargoEditor.EditorInstance} editor The editor on which the mutation is applied
4054			 */
4055			
4056			_core.events.trigger('mutation-success', _core.getActiveEditor(), ignoreHistory);
4057		} 
4058	});
4059
4060	this.mutationManager = new MutationManager;
4061
4062}).call(this);
4063
4064// Generated by CoffeeScript 1.6.3
4065(function() {
4066	var Dragster,
4067		__bind = function(fn, me) {
4068			return function() {
4069				return fn.apply(me, arguments);
4070			};
4071		};
4072
4073	Dragster = (function() {
4074		function Dragster(el) {
4075			this.el = el;
4076			this.dragleave = __bind(this.dragleave, this);
4077			this.dragenter = __bind(this.dragenter, this);
4078
4079			var _this = this;
4080
4081			if (this.supportsEventConstructors()) {
4082				this.first = false;
4083				this.second = false;
4084				this.el.addEventListener("dragenter", this.dragenter, false);
4085				this.el.addEventListener("dragleave", this.dragleave, false);
4086				this.el.addEventListener("drop", function(){
4087					_this.first = false;
4088					_this.second = false;
4089				}, false);
4090			}
4091		}
4092
4093		Dragster.prototype.dragenter = function(event) {
4094
4095			if (this.first) {
4096				return this.second = true;
4097			} else {
4098				this.first = true;
4099
4100				return this.el.dispatchEvent(new CustomEvent('dragster:enter', {
4101					bubbles: true,
4102					cancelable: true,
4103					detail: {
4104						dataTransfer: event.dataTransfer,
4105						originalEvent: event
4106					}
4107				}));
4108			}
4109
4110		};
4111
4112		Dragster.prototype.dragleave = function(event) {
4113
4114			if (this.second) {
4115				this.second = false;
4116			} else if (this.first) {
4117				this.first = false;
4118			}
4119			if (!this.first && !this.second) {
4120				return this.el.dispatchEvent(new CustomEvent('dragster:leave', {
4121					bubbles: true,
4122					cancelable: true,
4123					detail: {
4124						dataTransfer: event.dataTransfer,
4125						originalEvent: event
4126					}
4127				}));
4128			}
4129			
4130		};
4131
4132		Dragster.prototype.removeListeners = function() {
4133			this.el.removeEventListener("dragenter", this.dragenter, false);
4134			return this.el.removeEventListener("dragleave", this.dragleave, false);
4135		};
4136
4137		Dragster.prototype.supportsEventConstructors = function() {
4138			try {
4139				new CustomEvent("z");
4140			} catch (_error) {
4141				return false;
4142			}
4143			return true;
4144		};
4145
4146		Dragster.prototype.reset = function() {
4147			this.first = false;
4148			return this.second = false;
4149		};
4150
4151		return Dragster;
4152
4153	})();
4154
4155	this.Dragster = Dragster;
4156
4157}).call(this);
4158
4159(function(){
4160
4161	/**
4162	 * The following code is the work of Aryeh Gregor and released under the CC0 1.0 Universal license. (http://creativecommons.org/publicdomain/zero/1.0/)
4163	 *
4164	 * https://github.com/ayg/editing
4165	 *
4166	 * Some modifications by the Aloha-Editor project
4167	 * https://github.com/alohaeditor/Aloha-Editor/blob/dev/src/lib/aloha/engine.js
4168	 * 
4169	 */
4170	
4171
4172	"use strict";
4173
4174	var mapUtil = {},
4175		enableContentEditableDeletion = true,
4176		ContentRules = {};
4177
4178	(function(){
4179
4180		'use strict';
4181
4182		/**
4183		 * White list node names for list elements.
4184		 * @type {Array.<string>}
4185		 */
4186		var LIST_WHITELIST_NODE_NAMES = ['li'];
4187
4188		/**
4189		 * White list node names for table elements.
4190		 * @type {Array.<string>}
4191		 */
4192		var TABLE_WHITELIST_NODE_NAMES = ['caption', 'colgroup', 'col', 'thead', 'tbody', 'tfoot', 'td', 'th'];
4193
4194		/**
4195		 * Add default rules for some elements.
4196		 * @param {Object.<string, Array.<string>>}
4197		 * @return {Object.<string, Array.<string>>}
4198		 */
4199		function addDefaultRules(mapRules) {
4200			var selector;
4201			for (selector in mapRules) {
4202				if (mapRules.hasOwnProperty(selector)) {
4203					if (Arrays.contains(mapRules[selector], 'ol') || Arrays.contains(mapRules[selector], 'ul')) {
4204						mapRules[selector] = Arrays.concat(mapRules[selector], LIST_WHITELIST_NODE_NAMES);
4205					}
4206					if (Arrays.contains(mapRules[selector], 'table')) {
4207						mapRules[selector] = Arrays.concat(mapRules[selector], TABLE_WHITELIST_NODE_NAMES);
4208					}
4209				}
4210			}
4211			return mapRules;
4212		}
4213
4214		/**
4215		 * Whitelist rules.
4216		 *
4217		 * @private
4218		 * @type {Object.<string, Array.<string>>}
4219		 */
4220		var whitelist = addDefaultRules({});
4221
4222		/**
4223		 * Blacklist rules.
4224		 *
4225		 * @private
4226		 * @type {Object.<string, Array.<string>>}
4227		 */
4228		var blacklist = {};
4229
4230		/**
4231		 * Translation rules.
4232		 *
4233		 * @private
4234		 * @type {Object<string, string>}
4235		 */
4236		var translations = {};
4237
4238		/**
4239		 * Retrieves a list of all rules in a specified table that are applicable
4240		 * the given editable.
4241		 *
4242		 * @private
4243		 * @param {Element}                        editable
4244		 * @param {Object<string, Array.<string>}} table
4245		 */
4246		function getRules(editable, table) {
4247			var $editable = $(editable);
4248			var rules = [];
4249			var selector;
4250			for (selector in table) {
4251				if (table.hasOwnProperty(selector) && $editable.is(selector)) {
4252					rules.push(table[selector]);
4253				}
4254			}
4255			return rules;
4256		}
4257
4258		/**
4259		 * Concatenates the given list of lists into a single set.
4260		 *
4261		 * @private
4262		 * @param  {Array.<Array<string>>} lists
4263		 * @return {Array.<string>}
4264		 */
4265		function setcat(lists) {
4266			var result = [];
4267			$.each(lists, function (index, item) {
4268				result = result.concat(item);
4269			});
4270			$.unique(result);
4271			return result;
4272		}
4273
4274		/**
4275		 * These element's cannot be simply unwrapped because they have dependent
4276		 * children.
4277		 *
4278		 * @private
4279		 * @see  GROUPED_ELEMENTS
4280		 * @type {<string, boolean>}
4281		 */
4282		var GROUP_CONTAINERS = {
4283			FIELDSET : true,
4284			OBJECT   : true,
4285			FIGURE   : true,
4286			AUDIO    : true,
4287			SELECT   : true,
4288			COLGROUP : true,
4289			HGROUP   : true,
4290			TABLE    : true,
4291			TBODY    : true,
4292			TR       : true,
4293			OL       : true,
4294			UL       : true,
4295			DL       : true,
4296			MENU     : true
4297		};
4298
4299		/**
4300		 * These element's cannot be simply unwrapped because their parents only
4301		 * allow these as their immediate child nodes.
4302		 *
4303		 * @private
4304		 * @see  GROUP_CONTAINERS
4305		 * @type {<string, Array.<string>}
4306		 */
4307		var GROUPED_ELEMENTS = {
4308			LI    : ['OL', 'UL', 'DL'],
4309			DT    : ['DL'],
4310			DD    : ['DL'],
4311			TBODY : ['TABLE'],
4312			TR    : ['TABLE', 'TBODY'],
4313			TH    : ['TABLE', 'TBODY'],
4314			TD    : ['TR', 'TH']
4315		};
4316
4317		/**
4318		 * Checks whether nodes of the specified nodeName are allowed in the given
4319		 * editable.
4320		 *
4321		 * @param  {Element} editable
4322		 * @param  {string}  nodeName
4323		 * @return {boolean}
4324		 */
4325		function isAllowed(editable, nodeName) {
4326			var white = getRules(editable, whitelist);
4327			// Because if no rules are configured for this editable then permit all
4328			if (white.length > 0) {
4329				// Because textnode are always to be permitted by default. They
4330				// must be explicitly blacklisted if undesired
4331				if (!Arrays.contains(setcat(['#text'].concat(white)), nodeName.toLowerCase())) {
4332					return false;
4333				}
4334			}
4335			var black = getRules(editable, blacklist);
4336			if (black.length > 0) {
4337				return !Arrays.contains(setcat(black), nodeName.toLowerCase());
4338			}
4339			return true;
4340		}
4341
4342		/**
4343		 * Translates nodes from one name to another (eg: i to em) if translation is
4344		 * configured for the given editable.
4345		 *
4346		 * @param  {Element} editable
4347		 * @param  {string}  nodeName
4348		 * @return {string}  Translated nodeName
4349		 */
4350		function translate(editable, nodeName) {
4351			var rules = $.extend.apply({}, getRules(editable, translations));
4352			return rules[nodeName.toLowerCase()] || nodeName;
4353		};
4354
4355		function forward(node) {
4356			if (node.firstChild) {
4357				return node.firstChild;
4358			}
4359			var next = node;
4360			while (next && !next.nextSibling) {
4361				next = next.parentNode;
4362			}
4363			return next && next.nextSibling;
4364		};
4365
4366		function backward(node) {
4367			var prev = node.previousSibling;
4368			while (prev && prev.lastChild) {
4369				prev = prev.lastChild;
4370			}
4371			return prev || node.parentNode;
4372		};
4373
4374		function removeShallow(node) {
4375			var parent = node.parentNode;
4376			moveNextAll(parent, node.firstChild, node);
4377			parent.removeChild(node);
4378		};
4379
4380		function moveNextAll(parent, firstChild, refChild) {
4381			while (firstChild) {
4382				var nextChild = firstChild.nextSibling;
4383				parent.insertBefore(firstChild, refChild);
4384				firstChild = nextChild;
4385			}
4386		};
4387
4388		/**
4389		 * Given a string of html content to be inserted in a specified editable
4390		 * element, will strip away any elements which are disallowed and translate
4391		 * any according to the Content Rules configuration.
4392		 *
4393		 * @param  {string}  content
4394		 * @param  {Element} editable
4395		 * @return {string}
4396		 */
4397		function applyRules(content, editable) {
4398
4399			var doc = editable.ownerDocument;
4400			var container = doc.createElement('div');
4401
4402			container.innerHTML = content;
4403
4404			var node = forward(container);
4405
4406			while (node) {
4407				//skip over node if it is a aloha-block, because we want to keep elements inside
4408				if ($(node).hasClass('aloha-block')) {
4409					node = node.nextSibling || forward(node.lastChild || node);
4410				} else {
4411					var translation = translate(editable, node.nodeName);
4412					if (translation !== node.nodeName) {
4413						var replacement = doc.createElement(translation);
4414						replacement.innerHTML = node.innerHTML;
4415						node.parentNode.replaceChild(replacement, node);
4416						node = replacement;
4417					}
4418					if (isAllowed(editable, node.nodeName)) {
4419						node = forward(node);
4420					} else if (GROUP_CONTAINERS[node.nodeName] || GROUPED_ELEMENTS[node.nodeName]) {
4421						// Because `node` is being entirely removed, we skip over, and
4422						// do not descend its subtree
4423						var prev = backward(node);
4424						node.parentNode.removeChild(node);
4425						node = forward(prev);
4426					} else {
4427						var next = forward(node);
4428						removeShallow(node);
4429						node = next;
4430					}
4431				}
4432			}
4433			return container.innerHTML;
4434		}
4435
4436		this.isAllowed = isAllowed;
4437		this.translate = translate;
4438		this.applyRules = applyRules;
4439
4440	}).call(ContentRules);
4441
4442	console.log(ContentRules);
4443 
4444	(function(){
4445
4446		/**
4447		 * Checks whether the given object has no own or inherited properties.
4448		 *
4449		 * @param {!Object} obj The object to check.
4450		 * @return {boolean} True if the object is empty. eg: isEmpty({}) == true
4451		 */
4452		this.isEmpty = function(obj) {
4453			var name;
4454			for (name in obj) {
4455				if (obj.hasOwnProperty(name)) {
4456					return false;
4457				}
4458			}
4459			return true;
4460		}
4461
4462		/**
4463		 * Fill the given map with the given keys mapped to the given value.
4464		 *
4465		 * @param map
4466		 *        The given map will have one entry added for each given key.
4467		 * @param keys
4468		 *        An array of string keys. Javascript mapUtil can only
4469		 *        contain string keys, so these must be strings or
4470		 *        or they will be cast to string.
4471		 * @param value
4472		 *        A single value that each given key will map to.
4473		 * @return
4474		 *        The given map.
4475		 */
4476		this.fillKeys = function(map, keys, value) {
4477			var i = keys.length;
4478			while (i--) {
4479				map[keys[i]] = value;
4480			}
4481			return map;
4482		}
4483
4484		/**
4485		 * Fill the given map with entries from the given tuples.
4486		 *
4487		 * @param map
4488		 *        The given map will have one entry added for each item in
4489		 *        the given array.
4490		 * @param tuples
4491		 *        An array of [key, value] tuples. Javascript mapUtil can only
4492		 *        contain string keys, so the keys must be strings or
4493		 *        or they will be cast to string.
4494		 * @return
4495		 *        The given map.
4496		 */
4497		this.fillTuples = function(map, tuples) {
4498			var i = tuples.length,
4499				tuple;
4500			while (i--) {
4501				tuple = tuples[i];
4502				map[tuple[0]] = tuple[1];
4503			}
4504			return map;
4505		}
4506
4507		/**
4508		 * Returns an array of the map's keys.
4509		 */
4510		this.keys = function(map) {
4511			var ks = [],
4512				k;
4513			for (k in map) {
4514				if (map.hasOwnProperty(k)) {
4515					ks.push(k);
4516				}
4517			}
4518			return ks;
4519		}
4520
4521		/**
4522		 * For each mapping, call cb(value, key, map).
4523		 *
4524		 * Emulates ECMAScript edition 5 Array.forEach.
4525		 *
4526		 * Contrary to "for (key in map)" iterates only over the
4527		 * "hasOwnProperty" properties of the map, which is usually what you
4528		 * want.
4529		 */
4530		this.forEach = function(map, cb) {
4531			var key;
4532			for (key in map) {
4533				if (map.hasOwnProperty(key)) {
4534					cb(map[key], key, map);
4535				}
4536			}
4537		}
4538
4539
4540	}).call(mapUtil);
4541
4542	var blockLevelElements = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'div', 'pre'],
4543		listElements = ['li', 'ol', 'ul'],
4544		nonEmptyTags = ['br'];
4545
4546	function isBlockLevelElement(node) {
4547		if (!node) {
4548			return false;
4549		}
4550		if (node.nodeType === 1 && jQuery.inArray(node.nodeName.toLowerCase(), blockLevelElements) >= 0) {
4551			return true;
4552		}
4553		return false;
4554	}
4555
4556	function isEmptyBlockLevelElement(node) {
4557
4558		if (!isBlockLevelElement(node)) {
4559			return false;
4560		}
4561
4562		if (!node.childNodes) {
4563			return true;
4564		}
4565
4566		var len = node.childNodes.length, breakFound = false, i, child;
4567		for (i = 0; i < len; i++) {
4568			child = node.childNodes[i];
4569			if (child.nodeName === 'BR') {
4570				if (breakFound) {
4571					// we found another break, so the element is not empty
4572					return false;
4573				} else {
4574					// first break found, no harm done (so far)
4575					breakFound = true;
4576				}
4577			} else if (!isEmptyNode(child)) {
4578				return false;
4579			}
4580		}
4581
4582		return true;
4583
4584	}
4585
4586	function isEmptyNode(domObject) {
4587		// a non dom object is considered empty
4588		if (!domObject) {
4589			return true;
4590		}
4591
4592		// some tags are considered to be non-empty
4593		if (jQuery.inArray(domObject.nodeName.toLowerCase(), nonEmptyTags) != -1) {
4594			return false;
4595		}
4596
4597		// text nodes are not empty, if they contain non-whitespace characters
4598		if (domObject.nodeType === 3) {
4599			if (domObject.data.search(/\S/) == -1) {
4600				return true;
4601			} else if (domObject.data.length === 1 // Fix FOR IE no width chars
4602					&& domObject.data.charCodeAt(0) >= 0x2000) {
4603				return true;
4604			}
4605
4606			return false;
4607		}
4608
4609		// all other nodes are not empty if they contain at least one child which is not empty
4610		var i, childNodes;
4611		for (i = 0, childNodes = domObject.childNodes.length; i < childNodes; ++i) {
4612			if (!isEmptyNode(domObject.childNodes[i])) {
4613				return false;
4614			}
4615		}
4616
4617		// found no contents, so the element is empty
4618		return true;
4619	}
4620
4621	/**
4622	 * Gets the index of the given node within its parent element.
4623	 * @param {Element} node
4624	 * @return {number}
4625	 *         Index in the parent node or -1 if no node given or node has no
4626	 *         parent.
4627	 */
4628	
4629	function getIndexInParent(node) {
4630		if (!node) {
4631			return -1;
4632		}
4633
4634		var i,
4635		    childNodes = node.parentNode.childNodes,
4636			len = childNodes.length;
4637		for (i = 0; i < len; i++) {
4638			if (childNodes[i] === node) {
4639				return i;
4640			}
4641		}
4642
4643		return -1;
4644	}
4645
4646	var blocksTagnameMap = {},
4647		BLOCKLEVEL_ELEMENTS = [
4648			'address',
4649			'article',    // HTML5
4650			'aside',      // HTML5
4651			'audio',      // HTML5
4652			'blockquote',
4653			'canvas',     // HTML5
4654			'dd',
4655			'div',
4656			'dl',
4657			'fieldset',
4658			'figcaption',
4659			'figure',
4660			'footer',
4661			'form',
4662			'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
4663			'header',
4664			'hgroup',
4665			'hr',
4666			'noscript',
4667			'ol',
4668			'output',
4669			'p',
4670			'pre',
4671			'section',   // HTML5
4672			'table',
4673			'tfoot',
4674			'ul',
4675			'video'      // HTML5
4676		];
4677	
4678	mapUtil.fillKeys(blocksTagnameMap, BLOCKLEVEL_ELEMENTS, true);
4679
4680	mapUtil.fillKeys(
4681		blocksTagnameMap,
4682		Array.prototype.map.call(BLOCKLEVEL_ELEMENTS, function (str) {
4683			return str.toUpperCase();
4684		}),
4685		true
4686	);
4687
4688	function getInternetExplorerVersion() {
4689		var rv = -1;
4690		if (navigator.appName == 'Microsoft Internet Explorer')
4691		{
4692			var ua = navigator.userAgent;
4693			var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
4694			if (re.exec(ua) != null)
4695				rv = parseFloat( RegExp.$1 );
4696		}
4697		else if (navigator.appName == 'Netscape')
4698		{
4699			var ua = navigator.userAgent;
4700			var re  = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})");
4701			if (re.exec(ua) != null)
4702				rv = parseFloat( RegExp.$1 );
4703		}
4704		return rv;
4705	}
4706
4707	var isMSIE = getInternetExplorerVersion() !== -1;
4708
4709	function isBlock(node) {
4710		return blocksTagnameMap[node.nodeName];
4711	}
4712
4713	function findNodeRight(node, condition) {
4714		while (node && !condition(node)) {
4715			node = node.previousSibling;
4716		}
4717		return node;
4718	}
4719
4720	function findNodeLeft(node, condition) {
4721		while (node && !condition(node)) {
4722			node = node.nextSibling;
4723		}
4724		return node;
4725	}
4726
4727	function getOffsetHeight(node) {
4728		if (!node) {
4729			return 0;
4730		}
4731		var offsetHeight = node.offsetHeight;
4732		if (offsetHeight === 0) {
4733			offsetHeight = node.offsetHeight;
4734		}
4735		return offsetHeight;
4736	}
4737
4738	function isHtmlElement(node, tags) {
4739		if (typeof tags == "string") {
4740			tags = [tags];
4741		}
4742		if (typeof tags == "object") {
4743			tags = tags.map(function(tag) { return tag.toUpperCase() });
4744		}
4745		return node
4746			&& node.nodeType == Node.ELEMENT_NODE
4747			&& isHtmlNamespace(node.namespaceURI)
4748			&& (typeof tags == "undefined" || tags.indexOf(node.tagName) != -1);
4749	}
4750
4751	var $_;
4752
4753	var shims = {
4754		// Function bind
4755		bind: function (owner) {
4756			var obj = this.obj || this;
4757			var native_method = Function.prototype.bind;
4758			var args = Array.prototype.slice.call(arguments, 1);
4759
4760			if (native_method) {
4761				return native_method.apply(obj, arguments);
4762			}
4763			return function () {
4764				return obj.apply(owner, arguments.length === 0 ? args : args.concat(Array.prototype.slice.call(arguments)));
4765			};
4766		},
4767
4768		// String trim
4769		trim: function () {
4770			var obj = this.obj || this;
4771			var native_method = String.prototype.trim;
4772
4773			if (native_method) {
4774				return native_method.call(obj);
4775			}
4776			return obj.replace(/^\s+/, '').replace(/\s+$/, '');
4777		},
4778
4779		// Array methods 
4780		// i optional
4781		indexOf: function (find, i) {
4782			var obj = this.obj || this;
4783			var native_method = Array.prototype.indexOf;
4784
4785			if (native_method) {
4786				return native_method.call(obj, find, i);
4787			}
4788			if (i === undefined) {
4789				i = 0;
4790			}
4791			if (i < 0) {
4792				i += obj.length;
4793			}
4794			if (i < 0) {
4795				i = 0;
4796			}
4797			var n;
4798			for (n = obj.length; i < n; i++) {
4799				if (undefined !== obj[i] && obj[i] === find) {
4800					return i;
4801				}
4802			}
4803			return -1;
4804		},
4805
4806		// that optional
4807		forEach: function (action, that) {
4808			var obj = this.obj || this;
4809			var native_method = Array.prototype.forEach;
4810
4811			if (native_method) {
4812				return native_method.call(obj, action, that);
4813			}
4814			var i, n;
4815			for (i = 0, n = obj.length; i < n; i++) {
4816				if (undefined !== obj[i]) {
4817					action.call(that, obj[i], i, obj);
4818				}
4819			}
4820		},
4821
4822		// that optional
4823		// chain optional
4824		map: function (mapper, that, chain) {
4825			var obj = this.obj || this;
4826			var native_method = Array.prototype.map;
4827			var returnWrapper = (typeof arguments[arguments.length - 1] == "boolean") ? Array.prototype.pop.call(arguments) : false;
4828			var result = [];
4829
4830			if (native_method) {
4831				result = native_method.call(obj, mapper, that);
4832			} else {
4833				var other = [];
4834				var i, n;
4835				for (i = 0, n = obj.length; i < n; i++) {
4836					if (undefined !== obj[i]) {
4837						other[i] = mapper.call(that, obj[i], i, obj);
4838					}
4839				}
4840				result = other;
4841			}
4842
4843			return returnWrapper ? $_(result) : result;
4844		},
4845
4846		// that optional
4847		// chain optional
4848		filter: function (filterFunc, that, chain) {
4849			var obj = this.obj || this;
4850			var native_method = Array.prototype.filter;
4851			var returnWrapper = (typeof arguments[arguments.length - 1] == "boolean") ? Array.prototype.pop.call(arguments) : false;
4852			var result = [];
4853
4854			if (native_method) {
4855				result = native_method.call(obj, filterFunc, that);
4856			} else {
4857				var other = [],
4858				    v,
4859				    i,
4860				    n;
4861				for (i = 0, n = obj.length; i < n; i++) {
4862					if (undefined !== obj[i] && filterFunc.call(that, v = obj[i], i, obj)) {
4863						other.push(v);
4864					}
4865				}
4866				result = other;
4867			}
4868
4869			return returnWrapper ? $_(result) : result;
4870		},
4871
4872		// that optional
4873		every: function (tester, that) {
4874			var obj = this.obj || this;
4875			var native_method = Array.prototype.every;
4876
4877			if (native_method) {
4878				return native_method.call(obj, tester, that);
4879			}
4880			var i, n;
4881			for (i = 0, n = obj.length; i < n; i++) {
4882				if (undefined !== obj[i] && !tester.call(that, obj[i], i, obj)) {
4883					return false;
4884				}
4885			}
4886			return true;
4887		},
4888
4889		// that optional
4890		some: function (tester, that) {
4891			var obj = this.obj || this;
4892			var native_method = Array.prototype.some;
4893
4894			if (native_method) {
4895				return native_method.call(obj, tester, that);
4896			}
4897			var i, n;
4898			for (i = 0, n = obj.length; i < n; i++) {
4899				if (undefined !== obj[i] && tester.call(that, obj[i], i, obj)) {
4900					return true;
4901				}
4902			}
4903			return false;
4904		},
4905
4906		// Since IE7 doesn't support 'hasAttribute' method on nodes
4907		// TODO: raise an exception if the object is not an node
4908		hasAttribute: function (attr) {
4909			var obj = this.obj || this;
4910			var native_method = obj.hasAttribute;
4911
4912			if (native_method) {
4913				return obj.hasAttribute(attr);
4914			}
4915			return !!obj.getAttribute(attr);
4916		}
4917
4918	};
4919
4920	$_ = function (obj) {
4921		var Wrapper = function () {};
4922		Wrapper.prototype = shims;
4923
4924		var wrapper_instance = new Wrapper();
4925		wrapper_instance.obj = obj;
4926		return wrapper_instance;
4927	};
4928
4929	var shim;
4930	for (shim in shims) {
4931		if (shims.hasOwnProperty(shim)) {
4932			$_[shim] = shims[shim];
4933		}
4934	}
4935
4936
4937	// Node constants
4938	// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1841493061
4939	if (typeof window.Node != 'undefined') {
4940		$_.Node = window.Node;
4941	} else {
4942		$_.Node = {
4943			'ELEMENT_NODE': 1,
4944			'ATTRIBUTE_NODE': 2,
4945			'TEXT_NODE': 3,
4946			'CDATA_SECTION_NODE': 4,
4947			'ENTITY_REFERENCE_NODE': 5,
4948			'ENTITY_NODE': 6,
4949			'PROCESSING_INSTRUCTION_NODE': 7,
4950			'COMMENT_NODE': 8,
4951			'DOCUMENT_NODE': 9,
4952			'DOCUMENT_TYPE_NODE': 10,
4953			'DOCUMENT_FRAGMENT_NODE': 11,
4954			'NOTATION_NODE': 12,
4955			//The two nodes are disconnected. Order between disconnected nodes is always implementation-specific.
4956			'DOCUMENT_POSITION_DISCONNECTED': 0x01,
4957			//The second node precedes the reference node.
4958			'DOCUMENT_POSITION_PRECEDING': 0x02,
4959			//The node follows the reference node.
4960			'DOCUMENT_POSITION_FOLLOWING': 0x04,
4961			//The node contains the reference node. A node which contains is always preceding, too.
4962			'DOCUMENT_POSITION_CONTAINS': 0x08,
4963			//The node is contained by the reference node. A node which is contained is always following, too.
4964			'DOCUMENT_POSITION_CONTAINED_BY': 0x10,
4965			//The determination of preceding versus following is implementation-specific.
4966			'DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC': 0x20
4967		};
4968	}
4969
4970	//node.ownerDocument gives the document object, which isn't the right info for a disconnect
4971	function getRootParent(node) {
4972		var parent = null;
4973
4974		if (node) {
4975			do {
4976				parent = node;
4977			} while (null != (node = node.parentNode));
4978		}
4979
4980		return parent;
4981	}
4982
4983	//Compare Position - MIT Licensed, John Resig; http://ejohn.org/blog/comparing-document-position/
4984	//Already checked for equality and disconnect
4985	function comparePosition(node1, node2) {
4986		return (node1.contains(node2) && 16) + (node2.contains(node1) && 8) + (node1.sourceIndex >= 0 && node2.sourceIndex >
4986= 0 ? (node1.sourceIndex < node2.sourceIndex && 4) + (node1.sourceIndex > node2.sourceIndex && 2) : 1);
4987	}
4988
4989	//get a node with a sourceIndex to use
4990	function getUseNode(node) {
4991		//if the node already has a sourceIndex, use that node
4992		if (null != node.sourceIndex) {
4993			return node;
4994		}
4995		//otherwise, insert a comment (which has a sourceIndex but minimal DOM impact) before the node and use that
4996		return node.parentNode.insertBefore(document.createComment(""), node);
4997	}
4998
4999	// http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-compareDocumentPosition
5000	// FIXME: Check if the DOMNode prototype can be set.
5001	$_.compareDocumentPosition = function (node1, node2) {
5002
5003		if (document.documentElement.compareDocumentPosition) {
5004			return node1.compareDocumentPosition(node2);
5005		}
5006
5007		if (!document.documentElement.contains) {
5008			throw 'neither compareDocumentPosition nor contains is supported by this browser.';
5009		}
5010
5011		if (node1 == node2) {
5012			return 0;
5013		}
5014
5015		//if they don't have the same parent, there's a disconnect
5016		if (getRootParent(node1) != getRootParent(node2)) {
5017			return 1;
5018		}
5019
5020		//use this if both nodes have a sourceIndex (text nodes don't)
5021		if (null != node1.sourceIndex && null != node2.sourceIndex) {
5022			return comparePosition(node1, node2);
5023		}
5024
5025		//document will definitely contain the other node
5026		if (node1 == document) {
5027			return 20;
5028		}
5029		if (node2 == document) {
5030			return 10;
5031		}
5032
5033		//get sourceIndexes to use for both nodes
5034		var useNode1 = getUseNode(node1),
5035			useNode2 = getUseNode(node2);
5036
5037		//call this function again to get the result
5038		var result = comparePosition(useNode1, useNode2);
5039
5040		//clean up if needed
5041		if (node1 != useNode1) {
5042			useNode1.parentNode.removeChild(useNode1);
5043		}
5044		if (node2 != useNode2) {
5045			useNode2.parentNode.removeChild(useNode2);
5046		}
5047		return result;
5048	};
5049
5050	$_.getComputedStyle = function (node, style) {
5051		if (window.getComputedStyle) {
5052			return window.getComputedStyle(node, style);
5053		}
5054		if (node.currentStyle) {
5055			return node.currentStyle;
5056		}
5057		return null;
5058	};
5059	
5060	/**
5061	 *
5062	 * @param obj
5063	 * @param attr
5064	 * @returns {Boolean} true
5065	 */
5066	function hasAttribute(obj, attr) {
5067		var native_method = obj.hasAttribute;
5068		if (native_method) {
5069			return obj.hasAttribute(attr);
5070		}
5071		return (typeof obj.attributes[attr] !== 'undefined');
5072	}
5073
5074	/**
5075	 * Insert the node `node` after `preceding`.
5076	 *
5077	 * @param {Element} node
5078	 * @param {Element} preceding
5079	 * @return {Element}
5080	 */
5081	function insertAfter(node, preceding) {
5082		var next = preceding.nextSibling,
5083		    parent = preceding.parentNode;
5084		if (next) {
5085			parent.insertBefore(node, next);
5086		} else {
5087			parent.appendChild(node);
5088		}
5089		return node;
5090	}
5091
5092	/**
5093	 * Splits text node `node` at the given text index.
5094	 *
5095	 * Note that we cannot use splitText() because it is bugridden in IE 9.
5096	 *
5097	 * Borrowed from rangy.
5098	 *
5099	 * @param {Element} node
5100	 * @param {number} index
5101	 * @return {Element}
5102	 */
5103	function splitText(node, index) {
5104		var newNode = node.cloneNode(false);
5105		newNode.deleteData(0, index);
5106		node.deleteData(index, node.length - index);
5107		insertAfter(newNode, node);
5108		return newNode;
5109	}
5110
5111	var htmlNamespace = "http://www.w3.org/1999/xhtml";
5112
5113	var cssStylingFlag = false;
5114
5115	// This is bad :(
5116	var globalRange = null;
5117
5118	// Commands are stored in a dictionary where we call their actions and such
5119	var commands = {};
5120
5121	///////////////////////////////////////////////////////////////////////////////
5122	////////////////////////////// Utility functions //////////////////////////////
5123	///////////////////////////////////////////////////////////////////////////////
5124	//@{
5125
5126	// Opera 11 puts HTML elements in the null namespace, it seems.
5127	function isHtmlNamespace(ns) {
5128		return ns === null || !ns || ns === htmlNamespace;
5129	}
5130
5131	// "An HTML element is an Element whose namespace is the HTML namespace."
5132	//
5133	// I allow an extra argument to more easily check whether something is a
5134	// particular HTML element, like isNamedHtmlElement(node, 'OL').  It accepts arrays
5135	// too, like isHtmlElementInArray(node, ["OL", "UL"]) to check if it's an ol or ul.
5136	// TODO This function was prominent during profiling. Remove it
5137	//      and replace with calls to isAnyHtmlElement, isNamedHtmlElement
5138	//      and is isMappedHtmlElement.
5139	function isHtmlElement_obsolete(node, tags) {
5140		if (typeof tags == "string") {
5141			tags = [tags];
5142		}
5143		if (typeof tags == "object") {
5144			tags = $_(tags).map(function (tag) {
5145				return tag.toUpperCase();
5146			});
5147		}
5148		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI) && (typeof tags == "undefined" || $_(tags).indexOf(node.tagName) != -1);
5149	}
5150
5151	function isAnyHtmlElement(node) {
5152		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI);
5153	}
5154
5155	// name should be uppercase
5156	function isNamedHtmlElement(node, name) {
5157		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI)
5158		// This function is passed in a mix of upper and lower case names
5159			&& name.toUpperCase() === node.nodeName;
5160	}
5161
5162	// TODO remove when isHtmlElementInArray is removed
5163	function arrayContainsInsensitive(array, str) {
5164		var i, len;
5165		str = str.toUpperCase();
5166		for (i = 0, len = array.length; i < len; i++) {
5167			if (array[i].toUpperCase() === str) {
5168				return true;
5169			}
5170		}
5171		return false;
5172	}
5173	// TODO replace calls to this function with calls to isMappedHtmlElement()
5174	function isHtmlElementInArray(node, array) {
5175		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI)
5176		// This function is passed in a mix of upper and lower case names
5177			&& arrayContainsInsensitive(array, node.nodeName);
5178	}
5179
5180	// map must have all-uppercase keys
5181	function isMappedHtmlElement(node, map) {
5182		return node && node.nodeType == 1 && isHtmlNamespace(node.namespaceURI) && map[node.nodeName];
5183	}
5184
5185	/**
5186	 * Method to count the number of styles in the given style
5187	 */
5188	function getStyleLength(node) {
5189		var s;
5190		var styleLength = 0;
5191
5192		if (!node) {
5193			return 0;
5194		}
5195
5196		if (!node.style) {
5197			return 0;
5198		}
5199
5200		// some browsers support .length on styles
5201		if (typeof node.style.length !== 'undefined') {
5202			return node.style.length;
5203		}
5204
5205		/*jslint forin: true*/ //not sure whether node.style.hasOwnProperty is valid
5206		for (s in node.style) {
5207			if (node.style[s] && node.style[s] !== 0 && node.style[s] !== 'false') {
5208				styleLength++;
5209			}
5210		}
5211		/*jslint forin: false*/
5212
5213		return styleLength;
5214	}
5215
5216	function toArray(obj) {
5217		if (!obj) {
5218			return null;
5219		}
5220		var array = [],
5221			i,
5222		    l = obj.length;
5223		// iterate backwards ensuring that length is an UInt32
5224		i = l >>> 0;
5225		while (i--) {
5226			array[i] = obj[i];
5227		}
5228		return array;
5229	}
5230
5231	function nextNodeDescendants(node) {
5232		while (node && !node.nextSibling) {
5233			node = node.parentNode;
5234		}
5235		if (!node) {
5236			return null;
5237		}
5238		return node.nextSibling;
5239	}
5240
5241	function nextNode(node) {
5242		if (node.hasChildNodes()) {
5243			return node.firstChild;
5244		}
5245		return nextNodeDescendants(node);
5246	}
5247
5248	function previousNode(node) {
5249		if (node.previousSibling) {
5250			node = node.previousSibling;
5251			while (node.hasChildNodes()) {
5252				node = node.lastChild;
5253			}
5254			return node;
5255		}
5256		if (node.parentNode && node.parentNode.nodeType == Node.ELEMENT_NODE) {
5257			return node.parentNode;
5258		}
5259		return null;
5260	}
5261
5262	/**
5263	 * Returns true if ancestor is an ancestor of descendant, false otherwise.
5264	 */
5265	function isAncestor(ancestor, descendant) {
5266		return ancestor && descendant && Boolean(ancestor.compareDocumentPosition(descendant) & Node.DOCUMENT_POSITION_CONTAINED_BY);
5267	}
5268
5269	/**
5270	 * Returns true if ancestor is an ancestor of or equal to descendant, false
5271	 * otherwise.
5272	 */
5273	function isAncestorContainer(ancestor, descendant) {
5274		return (ancestor || descendant) && (ancestor == descendant || isAncestor(ancestor, descendant));
5275	}
5276
5277	/**
5278	 * Returns true if descendant is a descendant of ancestor, false otherwise.
5279	 */
5280	function isDescendant(descendant, ancestor) {
5281		return ancestor && descendant && Boolean(ancestor.compareDocumentPosition(descendant) & Node.DOCUMENT_POSITION_CONTAINED_BY);
5282	}
5283
5284	/**
5285	 * Returns true if node1 is before node2 in tree order, false otherwise.
5286	 */
5287	function isBefore(node1, node2) {
5288		return Boolean(node1.compareDocumentPosition(node2) & Node.DOCUMENT_POSITION_FOLLOWING);
5289	}
5290
5291	/**
5292	 * Returns true if node1 is after node2 in tree order, false otherwise.
5293	 */
5294	function isAfter(node1, node2) {
5295		return Boolean(node1.compareDocumentPosition(node2) & Node.DOCUMENT_POSITION_PRECEDING);
5296	}
5297
5298	function getAncestors(node) {
5299		var ancestors = [];
5300		while (node.parentNode) {
5301			ancestors.unshift(node.parentNode);
5302			node = node.parentNode;
5303		}
5304		return ancestors;
5305	}
5306
5307	function getDescendants(node) {
5308		var descendants = [];
5309		var stop = nextNodeDescendants(node);
5310		while (null != (node = nextNode(node)) && node != stop) {
5311			descendants.push(node);
5312		}
5313		return descendants;
5314	}
5315
5316	function convertProperty(property) {
5317		// Special-case for now
5318		var map = {
5319			"fontFamily": "font-family",
5320			"fontSize": "font-size",
5321			"fontStyle": "font-style",
5322			"fontWeight": "font-weight",
5323			"textDecoration": "text-decoration"
5324		};
5325		if (typeof map[property] != "undefined") {
5326			return map[property];
5327		}
5328
5329		return property;
5330	}
5331
5332	// Return the <font size=X> value for the given CSS size, or undefined if there
5333	// is none.
5334	function cssSizeToLegacy(cssVal) {
5335		return {
5336			"xx-small": 1,
5337			"small": 2,
5338			"medium": 3,
5339			"large": 4,
5340			"x-large": 5,
5341			"xx-large": 6,
5342			"xxx-large": 7
5343		}[cssVal];
5344	}
5345
5346	// Return the CSS size given a legacy size.
5347	function legacySizeToCss(legacyVal) {
5348		return {
5349			1: "xx-small",
5350			2: "small",
5351			3: "medium",
5352			4: "large",
5353			5: "x-large",
5354			6: "xx-large",
5355			7: "xxx-large"
5356		}[legacyVal];
5357	}
5358
5359	// "the directionality" from HTML.  I don't bother caring about non-HTML
5360	// elements.
5361	//
5362	// "The directionality of an element is either 'ltr' or 'rtl', and is
5363	// determined as per the first appropriate set of steps from the following
5364	// list:"
5365	function getDirectionality(element) {
5366		// "If the element's dir attribute is in the ltr state
5367		//     The directionality of the element is 'ltr'."
5368		if (element.dir == "ltr") {
5369			return "ltr";
5370		}
5371
5372		// "If the element's dir attribute is in the rtl state
5373		//     The directionality of the element is 'rtl'."
5374		if (element.dir == "rtl") {
5375			return "rtl";
5376		}
5377
5378		// "If the element's dir attribute is in the auto state
5379		// "If the element is a bdi element and the dir attribute is not in a
5380		// defined state (i.e. it is not present or has an invalid value)
5381		//     [lots of complicated stuff]
5382		//
5383		// Skip this, since no browser implements it anyway.
5384
5385		// "If the element is a root element and the dir attribute is not in a
5386		// defined state (i.e. it is not present or has an invalid value)
5387		//     The directionality of the element is 'ltr'."
5388		if (!isAnyHtmlElement(element.parentNode)) {
5389			return "ltr";
5390		}
5391
5392		// "If the element has a parent element and the dir attribute is not in a
5393		// defined state (i.e. it is not present or has an invalid value)
5394		//     The directionality of the element is the same as the element's
5395		//     parent element's directionality."
5396		return getDirectionality(element.parentNode);
5397	}
5398
5399	//@}
5400
5401	///////////////////////////////////////////////////////////////////////////////
5402	///////////////////////////// DOM Range functions /////////////////////////////
5403	///////////////////////////////////////////////////////////////////////////////
5404	//@{
5405
5406	// "The length of a Node node is the following, depending on node:
5407	//
5408	// ProcessingInstruction
5409	// DocumentType
5410	//   Always 0.
5411	// Text
5412	// Comment
5413	//   node's length.
5414	// Any other node
5415	//   node's childNodes's length."
5416	function getNodeLength(node) {
5417		switch (node.nodeType) {
5418		case Node.PROCESSING_INSTRUCTION_NODE:
5419		case Node.DOCUMENT_TYPE_NODE:
5420			return 0;
5421
5422		case Node.TEXT_NODE:
5423		case Node.COMMENT_NODE:
5424			return node.length;
5425
5426		default:
5427			return node.childNodes.length;
5428		}
5429	}
5430
5431	/**
5432	 * The position of two boundary points relative to one another, as defined by
5433	 * DOM Range.
5434	 */
5435	function getPosition(nodeA, offsetA, nodeB, offsetB) {
5436		// "If node A is the same as node B, return equal if offset A equals offset
5437		// B, before if offset A is less than offset B, and after if offset A is
5438		// greater than offset B."
5439		if (nodeA == nodeB) {
5440			if (offsetA == offsetB) {
5441				return "equal";
5442			}
5443			if (offsetA < offsetB) {
5444				return "before";
5445			}
5446			if (offsetA > offsetB) {
5447				return "after";
5448			}
5449		}
5450
5451		var documentPosition = nodeB.compareDocumentPosition(nodeA);
5452		// "If node A is after node B in tree order, compute the position of (node
5453		// B, offset B) relative to (node A, offset A). If it is before, return
5454		// after. If it is after, return before."
5455		if (documentPosition & Node.DOCUMENT_POSITION_FOLLOWING) {
5456			var pos = getPosition(nodeB, offsetB, nodeA, offsetA);
5457			if (pos == "before") {
5458				return "after";
5459			}
5460			if (pos == "after") {
5461				return "before";
5462			}
5463		}
5464
5465		// "If node A is an ancestor of node B:"
5466		if (documentPosition & Node.DOCUMENT_POSITION_CONTAINS) {
5467			// "Let child equal node B."
5468			var child = nodeB;
5469
5470			// "While child is not a child of node A, set child to its parent."
5471			while (child.parentNode != nodeA) {
5472				child = child.parentNode;
5473			}
5474
5475			// "If the index of child is less than offset A, return after."
5476			if (getIndexInParent(child) < offsetA) {
5477				return "after";
5478			}
5479		}
5480
5481		// "Return before."
5482		return "before";
5483	}
5484
5485	/**
5486	 * Returns the furthest ancestor of a Node as defined by DOM Range.
5487	 */
5488	function getFurthestAncestor(node) {
5489		
5490		var root = node;
5491		
5492		while (root.parentNode != null) {
5493			root = root.parentNode;
5494		}
5495
5496		return root;
5497
5498	}
5499
5500	/**
5501	 * "contained" as defined by DOM Range: "A Node node is contained in a range
5502	 * range if node's furthest ancestor is the same as range's root, and (node, 0)
5503	 * is after range's start, and (node, length of node) is before range's end."
5504	 */
5505	function isContained(node, range) {
5506		var pos1 = getPosition(node, 0, range.startContainer, range.startOffset);
5507		if (pos1 !== "after") {
5508			return false;
5509		}
5510		var pos2 = getPosition(node, getNodeLength(node), range.endContainer, range.endOffset);
5511		if (pos2 !== "before") {
5512			return false;
5513		}
5514		return getFurthestAncestor(node) == getFurthestAncestor(range.startContainer);
5515	}
5516
5517	/**
5518	 * Return all nodes contained in range that the provided function returns true
5519	 * for, omitting any with an ancestor already being returned.
5520	 */
5521	function getContainedNodes(range, condition) {
5522		if (typeof condition == "undefined") {
5523			condition = function () {
5524				return true;
5525			};
5526		}
5527		var node = range.startContainer;
5528		if (node.hasChildNodes() && range.startOffset < node.childNodes.length) {
5529			// A child is contained
5530			node = node.childNodes[range.startOffset];
5531		} else if (range.startOffset == getNodeLength(node)) {
5532			// No descendant can be contained
5533			node = nextNodeDescendants(node);
5534		} else {
5535			// No children; this node at least can't be contained
5536			node = nextNode(node);
5537		}
5538
5539		var stop = range.endContainer;
5540		if (stop.hasChildNodes() && range.endOffset < stop.childNodes.length) {
5541			// The node after the last contained node is a child
5542			stop = stop.childNodes[range.endOffset];
5543		} else {
5544			// This node and/or some of its children might be contained
5545			stop = nextNodeDescendants(stop);
5546		}
5547
5548		var nodeList = [];
5549		while (isBefore(node, stop)) {
5550			if (isContained(node, range) && condition(node)) {
5551				nodeList.push(node);
5552				node = nextNodeDescendants(node);
5553				continue;
5554			}
5555			node = nextNode(node);
5556		}
5557		return nodeList;
5558	}
5559
5560	/**
5561	 * As above, but includes nodes with an ancestor that's already been returned.
5562	 */
5563	function getAllContainedNodes(range, condition) {
5564		if (typeof condition == "undefined") {
5565			condition = function () {
5566				return true;
5567			};
5568		}
5569		var node = range.startContainer;
5570		if (node.hasChildNodes() && range.startOffset < node.childNodes.length) {
5571			// A child is contained
5572			node = node.childNodes[range.startOffset];
5573		} else if (range.startOffset == getNodeLength(node)) {
5574			// No descendant can be contained
5575			node = nextNodeDescendants(node);
5576		} else {
5577			// No children; this node at least can't be contained
5578			node = nextNode(node);
5579		}
5580
5581		var stop = range.endContainer;
5582		if (stop.hasChildNodes() && range.endOffset < stop.childNodes.length) {
5583			// The node after the last contained node is a child
5584			stop = stop.childNodes[range.endOffset];
5585		} else {
5586			// This node and/or some of its children might be contained
5587			stop = nextNodeDescendants(stop);
5588		}
5589
5590		var nodeList = [];
5591		while (isBefore(node, stop)) {
5592			if (isContained(node, range) && condition(node)) {
5593				nodeList.push(node);
5594			}
5595			node = nextNode(node);
5596		}
5597		return nodeList;
5598	}
5599
5600	// Returns either null, or something of the form rgb(x, y, z), or something of
5601	// the form rgb(x, y, z, w) with w != 0.
5602	function normalizeColor(color) {
5603		if (color.toLowerCase() == "currentcolor") {
5604			return null;
5605		}
5606
5607		var outerSpan = document.createElement("span");
5608		document.body.appendChild(outerSpan);
5609		outerSpan.style.color = "black";
5610
5611		var innerSpan = document.createElement("span");
5612		outerSpan.appendChild(innerSpan);
5613		innerSpan.style.color = color;
5614		color = $_.getComputedStyle(innerSpan).color;
5615
5616		if (color == "rgb(0, 0, 0)") {
5617			// Maybe it's really black, maybe it's invalid.
5618			outerSpan.color = "white";
5619			color = $_.getComputedStyle(innerSpan).color;
5620			if (color != "rgb(0, 0, 0)") {
5621				return null;
5622			}
5623		}
5624
5625		document.body.removeChild(outerSpan);
5626
5627		// I rely on the fact that browsers generally provide consistent syntax for
5628		// getComputedStyle(), although it's not standardized.  There are only two
5629		// exceptions I found:
5630		if (/^rgba\([0-9]+, [0-9]+, [0-9]+, 1\)$/.test(color)) {
5631			// IE10PP2 seems to do this sometimes.
5632			return color.replace("rgba", "rgb").replace(", 1)", ")");
5633		}
5634		if (color == "transparent") {
5635			// IE10PP2, Firefox 7.0a2, and Opera 11.50 all return "transparent" if
5636			// the specified value is "transparent".
5637			return "rgba(0, 0, 0, 0)";
5638		}
5639		return color;
5640	}
5641
5642	// Returns either null, or something of the form #xxxxxx, or the color itself
5643	// if it's a valid keyword.
5644	function parseSimpleColor(color) {
5645		color = color.toLowerCase();
5646		if ($_(["aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "green", "greenyellow", "grey", "honeydew", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey", "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "n
5646avy", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen"]).indexOf(color) != -1) {
5647			return color;
5648		}
5649
5650		color = normalizeColor(color);
5651		var matches = /^rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)$/.exec(color);
5652		if (matches) {
5653			return "#" + parseInt(matches[1], 10).toString(16).replace(/^.$/, "0$&") + parseInt(matches[2], 10).toString(16).replace(/^.$/, "0$&") + parseInt(matches[3], 10).toString(16).replace(/^.$/, "0$&");
5654		} else if (/^#[abcdef0123456789]+$/i.exec(color)) {
5655			// return hexadecimal color values (as returned by IE 7/8)
5656			return color;
5657		}
5658		return null;
5659	}
5660
5661	//@}
5662
5663	//////////////////////////////////////////////////////////////////////////////
5664	/////////////////////////// Edit command functions ///////////////////////////
5665	//////////////////////////////////////////////////////////////////////////////
5666
5667	/////////////////////////////////////////////////
5668	///// Methods of the HTMLDocument interface /////
5669	/////////////////////////////////////////////////
5670	//@{
5671
5672	var getStateOverride,
5673	    setStateOverride,
5674	    resetOverrides,
5675	    unsetStateOverride,
5676	    getValueOverride,
5677	    setValueOverride,
5678	    unsetValueOverride;
5679
5680	var executionStackDepth = 0;
5681
5682	// Helper function for fontSize's action plus queryOutputHelper.  It's just the
5683	// middle of fontSize's action, ripped out into its own function.
5684	function normalizeFontSize(value) {
5685		// "Strip leading and trailing whitespace from value."
5686		//
5687		// Cheap hack, not following the actual algorithm.
5688		value = $_(value).trim();
5689
5690		// "If value is a valid floating point number, or would be a valid
5691		// floating point number if a single leading "+" character were
5692		// stripped:"
5693		if (/^[\-+]?[0-9]+(\.[0-9]+)?([eE][\-+]?[0-9]+)?$/.test(value)) {
5694			var mode;
5695
5696			// "If the first character of value is "+", delete the character
5697			// and let mode be "relative-plus"."
5698			if (value[0] == "+") {
5699				value = value.slice(1);
5700				mode = "relative-plus";
5701				// "Otherwise, if the first character of value is "-", delete the
5702				// character and let mode be "relative-minus"."
5703			} else if (value[0] == "-") {
5704				value = value.slice(1);
5705				mode = "relative-minus";
5706				// "Otherwise, let mode be "absolute"."
5707			} else {
5708				mode = "absolute";
5709			}
5710
5711			// "Apply the rules for parsing non-negative integers to value, and
5712			// let number be the result."
5713			//
5714			// Another cheap hack.
5715			var num = parseInt(value, 10);
5716
5717			// "If mode is "relative-plus", add three to number."
5718			if (mode == "relative-plus") {
5719				num += 3;
5720			}
5721
5722			// "If mode is "relative-minus", negate number, then add three to
5723			// it."
5724			if (mode == "relative-minus") {
5725				num = 3 - num;
5726			}
5727
5728			// "If number is less than one, let number equal 1."
5729			if (num < 1) {
5730				num = 1;
5731			}
5732
5733			// "If number is greater than seven, let number equal 7."
5734			if (num > 7) {
5735				num = 7;
5736			}
5737
5738			// "Set value to the string here corresponding to number:" [table
5739			// omitted]
5740			value = {
5741				1: "xx-small",
5742				2: "small",
5743				3: "medium",
5744				4: "large",
5745				5: "x-large",
5746				6: "xx-large",
5747				7: "xxx-large"
5748			}[num];
5749		}
5750
5751		return value;
5752	}
5753
5754	function getLegacyFontSize(size) {
5755		// For convenience in other places in my code, I handle all sizes, not just
5756		// pixel sizes as the spec says.  This means pixel sizes have to be passed
5757		// in suffixed with "px", not as plain numbers.
5758		size = normalizeFontSize(size);
5759
5760		if (jQuery.inArray(size, ["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]) == -1 && !/^[0-9]+(\.[0-9]+)?(cm|mm|in|pt|pc|px)$/.test(size)) {
5761			// There is no sensible legacy size for things like "2em".
5762			return null;
5763		}
5764
5765		var font = document.createElement("font");
5766		document.body.appendChild(font);
5767		if (size == "xxx-large") {
5768			font.size = 7;
5769		} else {
5770			font.style.fontSize = size;
5771		}
5772		var pixelSize = parseInt($_.getComputedStyle(font).fontSize, 10);
5773		document.body.removeChild(font);
5774
5775		// "Let returned size be 1."
5776		var returnedSize = 1;
5777
5778		// "While returned size is less than 7:"
5779		while (returnedSize < 7) {
5780			// "Let lower bound be the resolved value of "font-size" in pixels
5781			// of a font element whose size attribute is set to returned size."
5782			font = document.createElement("font");
5783			font.size = returnedSize;
5784			document.body.appendChild(font);
5785			var lowerBound = parseInt($_.getComputedStyle(font).fontSize, 10);
5786
5787			// "Let upper bound be the resolved value of "font-size" in pixels
5788			// of a font element whose size attribute is set to one plus
5789			// returned size."
5790			font.size = 1 + returnedSize;
5791			var upperBound = parseInt($_.getComputedStyle(font).fontSize, 10);
5792			document.body.removeChild(font);
5793
5794			// "Let average be the average of upper bound and lower bound."
5795			var average = (upperBound + lowerBound) / 2;
5796
5797			// "If pixel size is less than average, return the one-element
5798			// string consisting of the digit returned size."
5799			if (pixelSize < average) {
5800				return String(returnedSize);
5801			}
5802
5803			// "Add one to returned size."
5804			returnedSize++;
5805		}
5806
5807		// "Return "7"."
5808		return "7";
5809	}
5810
5811	// Helper function for common behavior.
5812	function editCommandMethod(command, prop, range, callback) {
5813
5814		var ret;
5815
5816		// Set up our global range magic, but only if we're the outermost function
5817		if (executionStackDepth === 0) {
5818			globalRange = range;
5819		}
5820
5821		executionStackDepth++;
5822		
5823		try {
5824		
5825			ret = callback();
5826		
5827		} catch (e) {
5828		
5829			executionStackDepth--;
5830			throw e;
5831		
5832		}
5833		
5834		executionStackDepth--;
5835
5836		return ret;
5837	}
5838
5839	function myQueryCommandEnabled(command, range) {
5840		// "All of these methods must treat their command argument ASCII
5841		// case-insensitively."
5842		command = command.toLowerCase();
5843
5844		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
5845		return editCommandMethod(command, "action", range, (function (command) {
5846			return function () {
5847				// "Among commands defined in this specification, those listed in
5848				// Miscellaneous commands are always enabled. The other commands defined
5849				// here are enabled if the active range is not null, and disabled
5850				// otherwise."
5851				return jQuery.inArray(command, ["copy", "cut", "paste", "selectall", "stylewithcss", "usecss"]) != -1 || range !== null;
5852			};
5853		}(command)));
5854	}
5855
5856	function setActiveRange(range) {
5857
5858		if(range === undefined){
5859			return;
5860		}
5861
5862		var newRange = new _core.rangy.createRange();
5863
5864		newRange.setStart(range.startContainer, range.startOffset);
5865		newRange.setEnd(range.endContainer, range.endOffset);
5866
5867		var sel = _core.rangy.getSelection();
5868
5869		sel.removeAllRanges();
5870		sel.addRange(newRange);
5871
5872		/*console.log(range);
5873
5874		var rangeObject = new window.GENTICS.Utils.RangeObject();
5875
5876		rangeObject.startContainer = range.startContainer;
5877		rangeObject.startOffset = range.startOffset;
5878		rangeObject.endContainer = range.endContainer;
5879		rangeObject.endOffset = range.endOffset;
5880
5881		rangeObject.select();*/
5882	}
5883
5884	function myExecCommand(commandArg, showUiArg, valueArg, range) {
5885		// "All of these methods must treat their command argument ASCII
5886		// case-insensitively."
5887		var command = commandArg.toLowerCase();
5888		var showUi = showUiArg;
5889		var value = valueArg;
5890
5891		// "If only one argument was provided, let show UI be false."
5892		//
5893		// If range was passed, I can't actually detect how many args were passed
5894		// . . .
5895		if (arguments.length == 1 || (arguments.length >= 4 && typeof showUi == "undefined")) {
5896			showUi = false;
5897		}
5898
5899		// "If only one or two arguments were provided, let value be the empty
5900		// string."
5901		if (arguments.length <= 2 || (arguments.length >= 4 && typeof value == "undefined")) {
5902			value = "";
5903		}
5904
5905		var selection = _core.rangy.getSelection();
5906
5907		if (!range) {
5908
5909			if (selection.rangeCount === 0) {
5910				return;
5911			}
5912			
5913			range = selection.getRangeAt(0);
5914
5915		}
5916	
5917		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
5918		//
5919		// "If command has no action, raise an INVALID_ACCESS_ERR exception."
5920		return editCommandMethod(command, "action", range, (function (command, showUi, value) {
5921			
5922			return function () {
5923				// "If command is not enabled, return false."
5924				if (!myQueryCommandEnabled(command)) {
5925					return false;
5926				}
5927
5928				// "Take the action for command, passing value to the instructions as an
5929				// argument."
5930				commands[command].action(value, range);
5931
5932				// always fix the range after the command is complete
5933				setActiveRange(range);
5934
5935				// "Return true."
5936				return true;
5937			};
5938
5939		}(command, showUi, value)));
5940	}
5941
5942	function myQueryCommandIndeterm(command, range) {
5943		// "All of these methods must treat their command argument ASCII
5944		// case-insensitively."
5945		command = command.toLowerCase();
5946
5947		var selection = _core.rangy.getSelection();
5948
5949		if (!range) {
5950
5951			if (selection.rangeCount === 0) {
5952				return;
5953			}
5954			
5955			range = selection.getRangeAt(0);
5956
5957		}
5958
5959		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
5960		//
5961		// "If command has no indeterminacy, raise an INVALID_ACCESS_ERR
5962		// exception."
5963		return editCommandMethod(command, "indeterm", range, (function (command) {
5964			return function () {
5965				// "If command is not enabled, return false."
5966				if (!myQueryCommandEnabled(command, range)) {
5967					return false;
5968				}
5969
5970				// "Return true if command is indeterminate, otherwise false."
5971				return commands[command].indeterm(range);
5972			};
5973		}(command)));
5974	}
5975
5976	function myQueryCommandState(command, range) {
5977		// "All of these methods must treat their command argument ASCII
5978		// case-insensitively."
5979		command = command.toLowerCase();
5980
5981		var selection = _core.rangy.getSelection();
5982
5983		if (!range) {
5984
5985			if (selection.rangeCount === 0) {
5986				return;
5987			}
5988			
5989			range = selection.getRangeAt(0);
5990
5991		}
5992
5993		// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
5994		//
5995		// "If command has no state, raise an INVALID_ACCESS_ERR exception."
5996		return editCommandMethod(command, "state", range, (function (command) {
5997			return function () {
5998				// "If command is not enabled, return false."
5999				if (!myQueryCommandEnabled(command, range)) {
6000					return false;
6001				}
6002
6003				// "If the state override for command is set, return it."
6004				if (typeof getStateOverride(command, range) != "undefined") {
6005					return getStateOverride(command, range);
6006				}
6007
6008				// "Return true if command's state is true, otherwise false."
6009				return commands[command].state(range);
6010			};
6011		}(command)));
6012	}
6013
6014	// "When the queryCommandSupported(command) method on the HTMLDocument
6015	// interface is invoked, the user agent must return true if command is
6016	// supported, and false otherwise."
6017	function myQueryCommandSupported(command) {
6018		// "All of these methods must treat their command argument ASCII
6019		// case-insensitively."
6020		command = command.toLowerCase();
6021
6022		return commands.hasOwnProperty(command);
6023	}
6024
6025	function myQueryCommandValue(command, range) {
6026		// "All of these methods must treat their command argument ASCII
6027		// case-insensitively."
6028		command = command.toLowerCase();
6029
6030		var selection = _core.rangy.getSelection();
6031
6032		if (!range) {
6033
6034			if (selection.rangeCount === 0) {
6035				return;
6036			}
6037			
6038			range = selection.getRangeAt(0);
6039
6040		}
6041
6042		return editCommandMethod(command, "value", range, function () {
6043			// "If command is not supported or has no value, return the empty string."
6044			if (!commands.hasOwnProperty(command) || !commands[command].hasOwnProperty("value")) {
6045				return "";
6046			}
6047
6048			// "If command is "fontSize" and its value override is set, convert the
6049			// value override to an integer number of pixels and return the legacy
6050			// font size for the result."
6051			if (command == "fontsize" && getValueOverride("fontsize", range) !== undefined) {
6052				return getLegacyFontSize(getValueOverride("fontsize", range));
6053			}
6054
6055			// "If the value override for command is set, return it."
6056			if (typeof getValueOverride(command, range) != "undefined") {
6057				return getValueOverride(command, range);
6058			}
6059
6060			// "Return command's value."
6061			return commands[command].value(range);
6062		});
6063	}
6064	//@}
6065
6066	//////////////////////////////
6067	///// Common definitions /////
6068	//////////////////////////////
6069	//@{
6070
6071	// "A prohibited paragraph child name is "address", "article", "aside",
6072	// "blockquote", "caption", "center", "col", "colgroup", "dd", "details",
6073	// "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
6074	// "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
6075	// "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
6076	// "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or
6077	// "xmp"."
6078	var prohibitedParagraphChildNamesMap = {
6079		"ADDRESS": true,
6080		"ARTICLE": true,
6081		"ASIDE": true,
6082		"BLOCKQUOTE": true,
6083		"CAPTION": true,
6084		"CENTER": true,
6085		"COL": true,
6086		"COLGROUP": true,
6087		"DD": true,
6088		"DETAILS": true,
6089		"DIR": true,
6090		"DIV": true,
6091		"DL": true,
6092		"DT": true,
6093		"FIELDSET": true,
6094		"FIGCAPTION": true,
6095		"FIGURE": true,
6096		"FOOTER": true,
6097		"FORM": true,
6098		"H1": true,
6099		"H2": true,
6100		"H3": true,
6101		"H4": true,
6102		"H5": true,
6103		"H6": true,
6104		"HEADER": true,
6105		"HGROUP": true,
6106		"HR": true,
6107		"LI": true,
6108		"LISTING": true,
6109		"MENU": true,
6110		"NAV": true,
6111		"OL": true,
6112		"P": true,
6113		"PLAINTEXT": true,
6114		"PRE": true,
6115		"SECTION": true,
6116		"SUMMARY": true,
6117		"TABLE": true,
6118		"TBODY": true,
6119		"TD": true,
6120		"TFOOT": true,
6121		"TH": true,
6122		"THEAD": true,
6123		"TR": true,
6124		"UL": true,
6125		"XMP": true
6126	};
6127
6128	// "A prohibited paragraph child is an HTML element whose local name is a
6129	// prohibited paragraph child name."
6130	function isProhibitedParagraphChild(node) {
6131		return isMappedHtmlElement(node, prohibitedParagraphChildNamesMap);
6132	}
6133
6134	var nonBlockDisplayValuesMap = {
6135		"inline": true,
6136		"inline-block": true,
6137		"inline-table": true,
6138		"none": true
6139	};
6140
6141	// "A block node is either an Element whose "display" property does not have
6142	// resolved value "inline" or "inline-block" or "inline-table" or "none", or a
6143	// Document, or a DocumentFragment."
6144	function isBlockNode(node) {
6145		return node && ((node.nodeType == Node.ELEMENT_NODE && !nonBlockDisplayValuesMap[$_.getComputedStyle(node).display]) || node.nodeType == Node.DOCUMENT_NODE || node.nodeType == Node.DOCUMENT_FRAGMENT_NODE);
6146	}
6147
6148	// "An inline node is a node that is not a block node."
6149	function isInlineNode(node) {
6150		return node && !isBlockNode(node);
6151	}
6152
6153	// "An editing host is a node that is either an Element with a contenteditable
6154	// attribute set to the true state, or the Element child of a Document whose
6155	// designMode is enabled."
6156	function isEditingHost(node) {
6157		return node && node.nodeType == Node.ELEMENT_NODE && (node.contentEditable == "true" || (node.parentNode && node.parentNode.nodeType == Node.DOCUMENT_NODE && node.parentNode.designMode == "on"));
6158	}
6159
6160	// "Something is editable if it is a node which is not an editing host, does
6161	// not have a contenteditable attribute set to the false state, and whose
6162	// parent is an editing host or editable."
6163	function isEditable(node) {
6164		// This is slightly a lie, because we're excluding non-HTML elements with
6165		// contentEditable attributes.
6166		return node && !isEditingHost(node) && (node.nodeType != Node.ELEMENT_NODE || node.contentEditable != "false" || jQuery(node).hasClass('aloha-table-wrapper')) && (isEditingHost(node.parentNode) || isEditable(node.parentNode));
6167	}
6168
6169	// Helper function, not defined in the spec
6170	function hasEditableDescendants(node) {
6171		var i;
6172		for (i = 0; i < node.childNodes.length; i++) {
6173			if (isEditable(node.childNodes[i]) || hasEditableDescendants(node.childNodes[i])) {
6174				return true;
6175			}
6176		}
6177		return false;
6178	}
6179
6180	// "The editing host of node is null if node is neither editable nor an editing
6181	// host; node itself, if node is an editing host; or the nearest ancestor of
6182	// node that is an editing host, if node is editable."
6183	function getEditingHostOf(node) {
6184		if (isEditingHost(node)) {
6185			return node;
6186		}
6187		if (isEditable(node)) {
6188			var ancestor = node.parentNode;
6189			while (!isEditingHost(ancestor)) {
6190				ancestor = ancestor.parentNode;
6191			}
6192			return ancestor;
6193		}
6194		return null;
6195	}
6196
6197	// "Two nodes are in the same editing host if the editing host of the first is
6198	// non-null and the same as the editing host of the second."
6199	function inSameEditingHost(node1, node2) {
6200		return getEditingHostOf(node1) && getEditingHostOf(node1) == getEditingHostOf(node2);
6201	}
6202
6203	// "A collapsed line break is a br that begins a line box which has nothing
6204	// else in it, and therefore has zero height."
6205	function isCollapsedLineBreak(br) {
6206		if (!isNamedHtmlElement(br, 'br')) {
6207			return false;
6208		}
6209
6210		// Add a zwsp after it and see if that changes the height of the nearest
6211		// non-inline parent.  Note: this is not actually reliable, because the
6212		// parent might have a fixed height or something.
6213		var ref = br.parentNode;
6214		while ($_.getComputedStyle(ref).display == "inline") {
6215			ref = ref.parentNode;
6216		}
6217
6218		var origStyle = {
6219			height: ref.style.height,
6220			maxHeight: ref.style.maxHeight,
6221			minHeight: ref.style.minHeight
6222		};
6223
6224		ref.style.height = 'auto';
6225		ref.style.maxHeight = 'none';
6226
6227		var space = document.createTextNode('\u200b');
6228		var origHeight = ref.offsetHeight;
6229		if (origHeight == 0) {
6230			throw 'isCollapsedLineBreak: original height is zero, bug?';
6231		}
6232		br.parentNode.insertBefore(space, br.nextSibling);
6233		var finalHeight = ref.offsetHeight;
6234		space.parentNode.removeChild(space);
6235
6236		ref.style.height = origStyle.height;
6237		ref.style.maxHeight = origStyle.maxHeight;
6238
6239		// Allow some leeway in case the zwsp didn't create a whole new line, but
6240		// only made an existing line slightly higher.  Firefox 6.0a2 shows this
6241		// behavior when the first line is bold.
6242		return origHeight < finalHeight - 5;
6243	}
6244
6245	// "An extraneous line break is a br that has no visual effect, in that
6246	// removing it from the DOM would not change layout, except that a br that is
6247	// the sole child of an li is not extraneous."
6248	function isExtraneousLineBreak(br) {
6249
6250		if (!isNamedHtmlElement(br, 'br')) {
6251			return false;
6252		}
6253
6254		if (isNamedHtmlElement(br.parentNode, "li") && br.parentNode.childNodes.length == 1) {
6255			return false;
6256		}
6257
6258		// Make the line break disappear and see if that changes the block's
6259		// height.  Yes, this is an absurd hack.  We have to reset height etc. on
6260		// the reference node because otherwise its height won't change if it's not
6261		// auto.
6262		var ref = br.parentNode;
6263		while ($_.getComputedStyle(ref).display == "inline") {
6264			ref = ref.parentNode;
6265		}
6266
6267		var origStyle = {
6268			height: ref.style.height,
6269			maxHeight: ref.style.maxHeight,
6270			minHeight: ref.style.minHeight,
6271			contentEditable: ref.contentEditable
6272		};
6273
6274		ref.style.height = 'auto';
6275		ref.style.maxHeight = 'none';
6276		ref.style.minHeight = '0';
6277
6278		var origHeight = getOffsetHeight(ref);
6279		if (origHeight == 0) {
6280			throw "isExtraneousLineBreak: original height is zero, bug?";
6281		}
6282
6283		var origBrDisplay = br.style.display;
6284		br.style.display = 'none';
6285		var finalHeight = ref.offsetHeight;
6286
6287		// Restore original styles to the touched elements.
6288		ref.style.height = origStyle.height;
6289		ref.style.maxHeight = origStyle.maxHeight;
6290		ref.style.minHeight = origStyle.minHeight;
6291
6292		br.style.display = origBrDisplay;
6293
6294		return origHeight == finalHeight;
6295	}
6296
6297	// "A whitespace node is either a Text node whose data is the empty string; or
6298	// a Text node whose data consists only of one or more tabs (0x0009), line
6299	// feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
6300	// parent is an Element whose resolved value for "white-space" is "normal" or
6301	// "nowrap"; or a Text node whose data consists only of one or more tabs
6302	// (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
6303	// parent is an Element whose resolved value for "white-space" is "pre-line"."
6304	function isWhitespaceNode(node) {
6305		var nodeTypes = Node;
6306		if (node && node.nodeType === nodeTypes.TEXT_NODE) {
6307			var parentNode = node.parentNode;
6308
6309			var nodeData = node.data;
6310			if (jQuery.trim(nodeData).length === 0) {
6311				return true;
6312			} else if (parentNode && /^[\t\n\r ]+$/.test(nodeData)) {
6313				if (parentNode.nodeType === nodeTypes.ELEMENT_NODE) {
6314					if (jQuery.inArray($_.getComputedStyle(parentNode).whiteSpace, ["normal", "nowrap"]) !== -1) {
6315						return true;
6316					} else if ($_.getComputedStyle(parentNode).whiteSpace === "pre-line") {
6317						return true;
6318					}
6319				} else if (parentNode.nodeType === nodeTypes.DOCUMENT_FRAGMENT_NODE) {
6320					return true;
6321				}
6322			}
6323		}
6324		return false;
6325	}
6326
6327	/**
6328	 * Collapse sequences of ignorable whitespace (tab (0x0009), line feed (0x000A), carriage return (0x000D), space (0x0020)) to only one space.
6329	 * Preserve the given range if necessary.
6330	 * @param node text node
6331	 * @param range range
6332	 */
6333	function collapseWhitespace(node, range) {
6334		// "If node is neither editable nor an editing host, abort these steps."
6335		if (!isEditable(node) && !isEditingHost(node)) {
6336			return;
6337		}
6338
6339		// if the given node is not a text node, return
6340		if (!node || node.nodeType !== Node.TEXT_NODE) {
6341			return;
6342		}
6343
6344		// if the node is in a pre or pre-wrap node, return
6345		if (jQuery.inArray($_.getComputedStyle(node.parentNode).whiteSpace, ["pre", "pre-wrap"]) != -1) {
6346			return;
6347		}
6348
6349		// if the given node does not contain sequences of at least two consecutive ignorable whitespace characters, return
6350		if (!/[\t\n\r ]{2,}/.test(node.data)) {
6351			return;
6352		}
6353
6354		var newData = '';
6355		var correctStart = range.startContainer == node;
6356		var correctEnd = range.endContainer == node;
6357		var wsFound = false;
6358		var i;
6359
6360		// iterate through the node data
6361		for (i = 0; i < node.data.length; ++i) {
6362			if (/[\t\n\r ]/.test(node.data.substr(i, 1))) {
6363				// found a whitespace
6364				if (!wsFound) {
6365					// this is the first whitespace in the current sequence
6366					// add a whitespace to the new data sequence
6367					newData += ' ';
6368					// remember that we found a whitespace
6369					wsFound = true;
6370				} else {
6371					// this is not the first whitespace in the sequence, so omit this character
6372					if (correctStart && newData.length < range.startOffset) {
6373						range.startOffset--;
6374					}
6375					if (correctEnd && newData.length < range.endOffset) {
6376						range.endOffset--;
6377					}
6378				}
6379			} else {
6380				newData += node.data.substr(i, 1);
6381				wsFound = false;
6382			}
6383		}
6384
6385		// set the new data
6386		node.data = newData;
6387	}
6388
6389	// "node is a collapsed whitespace node if the following algorithm returns
6390	// true:"
6391	function isCollapsedWhitespaceNode(node) {
6392		// "If node is not a whitespace node, return false."
6393		if (!isWhitespaceNode(node)) {
6394			return false;
6395		}
6396
6397		// "If node's data is the empty string, return true."
6398		if (node.data == "") {
6399			return true;
6400		}
6401
6402		// "Let ancestor be node's parent."
6403		var ancestor = node.parentNode;
6404
6405		// "If ancestor is null, return true."
6406		if (!ancestor) {
6407			return true;
6408		}
6409
6410		// "If the "display" property of some ancestor of node has resolved value
6411		// "none", return true."
6412		if ($_(getAncestors(node)).some(function (ancestor) { return ancestor.nodeType == Node.ELEMENT_NODE && $_.getComputedStyle(ancestor).display == "none"; })) {
6413			return true;
6414		}
6415
6416		// "While ancestor is not a block node and its parent is not null, set
6417		// ancestor to its parent."
6418		while (!isBlockNode(ancestor) && ancestor.parentNode) {
6419			ancestor = ancestor.parentNode;
6420		}
6421
6422		// "Let reference be node."
6423		var reference = node;
6424
6425		// "While reference is a descendant of ancestor:"
6426		while (reference != ancestor) {
6427			// "Let reference be the node before it in tree order."
6428			reference = previousNode(reference);
6429
6430			// "If reference is a block node or a br, return true."
6431			if (isBlockNode(reference) || isNamedHtmlElement(reference, 'br')) {
6432				return true;
6433			}
6434
6435			// "If reference is a Text node that is not a whitespace node, or is an
6436			// img, break from this loop."
6437			if ((reference.nodeType == Node.TEXT_NODE && !isWhitespaceNode(reference)) || isNamedHtmlElement(reference, 'img')) {
6438				break;
6439			}
6440		}
6441
6442		// "Let reference be node."
6443		reference = node;
6444
6445		// "While reference is a descendant of ancestor:"
6446		var stop = nextNodeDescendants(ancestor);
6447		while (reference != stop) {
6448			// "Let reference be the node after it in tree order, or null if there
6449			// is no such node."
6450			reference = nextNode(reference);
6451
6452			// "If reference is a block node or a br, return true."
6453			if (isBlockNode(reference) || isNamedHtmlElement(reference, 'br')) {
6454				return true;
6455			}
6456
6457			// "If reference is a Text node that is not a whitespace node, or is an
6458			// img, break from this loop."
6459			if ((reference && reference.nodeType == Node.TEXT_NODE && !isWhitespaceNode(reference)) || isNamedHtmlElement(reference, 'img')) {
6460				break;
6461			}
6462		}
6463
6464		// "Return false."
6465		return false;
6466	}
6467
6468	// "Something is visible if it is a node that either is a block node, or a Text
6469	// node that is not a collapsed whitespace node, or an img, or a br that is not
6470	// an extraneous line break, or any node with a visible descendant; excluding
6471	// any node with an ancestor container Element whose "display" property has
6472	// resolved value "none"."
6473	function isVisible(node) {
6474		var i;
6475
6476		if (!node) {
6477			return false;
6478		}
6479
6480		if ($_(getAncestors(node).concat(node))
6481			    .filter(function (node) { return node.nodeType == Node.ELEMENT_NODE; }, true)
6482			    .some(function (node) { return $_.getComputedStyle(node).display == "none"; })) {
6483			return false;
6484		}
6485
6486		if (isBlockNode(node) || (node.nodeType == Node.TEXT_NODE && !isCollapsedWhitespaceNode(node)) || isNamedHtmlElement(node, 'img') || (isNamedHtmlElement(node, 'br') && !isExtraneousLineBreak(node))) {
6487			return true;
6488		}
6489
6490		for (i = 0; i < node.childNodes.length; i++) {
6491			if (isVisible(node.childNodes[i])) {
6492				return true;
6493			}
6494		}
6495
6496		return false;
6497	}
6498
6499	// "Something is invisible if it is a node that is not visible."
6500	function isInvisible(node) {
6501		return node && !isVisible(node);
6502	}
6503
6504	// "A collapsed block prop is either a collapsed line break that is not an
6505	// extraneous line break, or an Element that is an inline node and whose
6506	// children are all either invisible or collapsed block props and that has at
6507	// least one child that is a collapsed block prop."
6508	function isCollapsedBlockProp(node) {
6509		var i;
6510
6511		if (isCollapsedLineBreak(node) && !isExtraneousLineBreak(node)) {
6512			return true;
6513		}
6514
6515		if (!isInlineNode(node) || node.nodeType != Node.ELEMENT_NODE) {
6516			return false;
6517		}
6518
6519		var hasCollapsedBlockPropChild = false;
6520		for (i = 0; i < node.childNodes.length; i++) {
6521			if (!isInvisible(node.childNodes[i]) && !isCollapsedBlockProp(node.childNodes[i])) {
6522				return false;
6523			}
6524			if (isCollapsedBlockProp(node.childNodes[i])) {
6525				hasCollapsedBlockPropChild = true;
6526			}
6527		}
6528
6529		return hasCollapsedBlockPropChild;
6530	}
6531
6532	/**
6533	 * Checks whether the given node is a visible text node.
6534	 *
6535	 * @param {HTMLElement} node
6536	 * @return {Boolean} True if `node` is a visible text node.
6537	 */
6538	function isInvisibleTextNode(node) {
6539		if (node && node.nodeType !== Node.TEXT_NODE) {
6540			return false;
6541		}
6542		var offset = 0;
6543		var data = node.data;
6544		var len = data.length;
6545		while (offset < len && data.charAt(offset) === '\u200b') {
6546			offset++;
6547		}
6548		return offset === len;
6549	}
6550
6551	/**
6552	 * Complement of isInvisibleTextNode().
6553	 *
6554	 * @param {HTMLElement} node
6555	 * @return {Boolean} True if `node` is anything but an invisible text node.
6556	 */
6557	function isNotInvisibleTextNode(node) {
6558		return !isInvisibleTextNode(node);
6559	}
6560
6561	/**
6562	 * Checks whether the given node is a otherwise empty block-level element
6563	 * containing a propping <br> element.
6564	 *
6565	 * @param {HTMLElement} node
6566	 * @return {Boolean} True if `node` is a propped up block-level element.
6567	 */
6568	function isProppedBlock(node) {
6569		if (!isBlock(node)) {
6570			return false;
6571		}
6572		var child = findNodeRight(node.lastChild, isVisible);
6573		return (
6574			child
6575			&& 'br' === child.nodeName.toLowerCase()
6576			&& !findNodeRight(child.previousSibling, isVisible)
6577		);
6578	}
6579
6580	/**
6581	 * Checks whether the given node is a empty element, or an element that
6582	 * would otherwise be empty except for a propping <br>, or an element
6583	 * containing only invisible text nodes.
6584	 *
6585	 * @param {HTMLElement} node
6586	 * @return {Boolean} True if `node` can be considered empty.
6587	 */
6588	/*function isEmptyNode(node) {
6589		return (
6590			!node.hasChildNodes()
6591			|| isProppedBlock(node)
6592			|| !findNodeRight(node.lastChild, isNotInvisibleTextNode)
6593		);
6594	}*/
6595
6596	/**
6597	 * Check if the given node is a empty element which is the only
6598	 * immediate child of a editing host.
6599	 *
6600	 * @param {HTMLElement} node
6601	 * @return {Boolean} True if `node` can be regarded as empty and the
6602	 *                   only immediate child of its parent editing host.
6603	 */
6604	function isEmptyOnlyChildOfEditingHost(node) {
6605		return (
6606			node
6607				&& isEmptyNode(node)
6608					&& isEditingHost(node.parentNode)
6609						&& !node.previousSibling
6610							&& !node.nextSibling
6611		);
6612	}
6613
6614	/**
6615	 * Remove the given node and return the position from where it was
6616	 * removed.
6617	 *
6618	 * @param {HTMLElement} node Element to remove from DOM
6619	 * @return {object} Object containing node and offset index.
6620	 */
6621	function removeNode(node) {
6622		var ancestor = node.parentNode;
6623		var offset = getIndexInParent(node);
6624		ancestor.removeChild(node);
6625		return {
6626			node: ancestor,
6627			offset: offset
6628		};
6629	}
6630
6631	// Please note: This method is deprecated and will be removed.
6632	// Every command should use the value and range parameter.
6633	//
6634	// "The active range is the first range in the Selection given by calling
6635	// getSelection() on the context object, or null if there is no such range."
6636	//
6637	// We cheat and return globalRange if that's defined.  We also ensure that the
6638	// active range meets the requirements that selection boundary points are
6639	// supposed to meet, i.e., that the nodes are both Text or Element nodes that
6640	// descend from a Document.
6641	function getActiveRange() {
6642		var ret;
6643		if (globalRange) {
6644			ret = globalRange;
6645		} else if (_core.rangy.getSelection().rangeCount) {
6646			ret = _core.rangy.getSelection().getRangeAt(0);
6647		} else {
6648			return null;
6649		}
6650		if (jQuery.inArray(ret.startContainer.nodeType, [Node.TEXT_NODE, Node.ELEMENT_NODE]) == -1 || jQuery.inArray(ret.endContainer.nodeType, [Node.TEXT_NODE, Node.ELEMENT_NODE]) == -1 || !ret.startContainer.ownerDocument || !ret.endContainer.ownerDocument || !isDescendant(ret.startContainer, ret.startContainer.ownerDocument) || !isDescendant(ret.endContainer, ret.endContainer.ownerDocument)) {
6651			throw "Invalid active range; test bug?";
6652		}
6653		return ret;
6654	}
6655
6656	// "For some commands, each HTMLDocument must have a boolean state override
6657	// and/or a string value override. These do not change the command's state or
6658	// value, but change the way some algorithms behave, as specified in those
6659	// algorithms' definitions. Initially, both must be unset for every command.
6660	// Whenever the number of ranges in the Selection changes to something
6661	// different, and whenever a boundary point of the range at a given index in
6662	// the Selection changes to something different, the state override and value
6663	// override must be unset for every command."
6664	//
6665	// We implement this crudely by using setters and getters.  To verify that the
6666	// selection hasn't changed, we copy the active range and just check the
6667	// endpoints match.  This isn't really correct, but it's good enough for us.
6668	// Unset state/value overrides are undefined.  We put everything in a function
6669	// so no one can access anything except via the provided functions, since
6670	// otherwise callers might mistakenly use outdated overrides (if the selection
6671	// has changed).
6672	(function () {
6673		var stateOverrides = {};
6674		var valueOverrides = {};
6675		var storedRange = null;
6676
6677		resetOverrides = function (range) {
6678			if (!storedRange
6679				    || storedRange.startContainer != range.startContainer
6680				    || storedRange.endContainer != range.endContainer
6681				    || storedRange.startOffset != range.startOffset
6682				    || storedRange.endOffset != range.endOffset) {
6683				storedRange = {
6684					startContainer: range.startContainer,
6685					endContainer: range.endContainer,
6686					startOffset: range.startOffset,
6687					endOffset: range.endOffset
6688				};
6689				if (!mapUtil.isEmpty(stateOverrides) || !mapUtil.isEmpty(valueOverrides)) {
6690					stateOverrides = {};
6691					valueOverrides = {};
6692					return true;
6693				}
6694			}
6695			return false;
6696		};
6697
6698		getStateOverride = function (command, range) {
6699			resetOverrides(range);
6700			return stateOverrides[command];
6701		};
6702
6703		setStateOverride = function (command, newState, range) {
6704			resetOverrides(range);
6705			stateOverrides[command] = newState;
6706		};
6707
6708		unsetStateOverride = function (command, range) {
6709			resetOverrides(range);
6710			delete stateOverrides[command];
6711		};
6712
6713		getValueOverride = function (command, range) {
6714			resetOverrides(range);
6715			return valueOverrides[command];
6716		};
6717
6718		// "The value override for the backColor command must be the same as the
6719		// value override for the hiliteColor command, such that setting one sets
6720		// the other to the same thing and unsetting one unsets the other."
6721		setValueOverride = function (command, newValue, range) {
6722			
6723			resetOverrides(range);
6724			
6725			valueOverrides[command] = newValue;
6726			
6727			if (command == "backcolor") {
6728			
6729				valueOverrides.hilitecolor = newValue;
6730			
6731			} else if (command == "hilitecolor") {
6732			
6733				valueOverrides.backcolor = newValue;
6734			
6735			}
6736
6737		};
6738
6739		unsetValueOverride = function (command, range) {
6740
6741			resetOverrides(range);
6742
6743			delete valueOverrides[command];
6744
6745			if (command == "backcolor") {
6746
6747				delete valueOverrides.hilitecolor;
6748
6749			} else if (command == "hilitecolor") {
6750
6751				delete valueOverrides.backcolor;
6752
6753			}
6754
6755		};
6756	}());
6757
6758	//@}
6759
6760	/////////////////////////////
6761	///// Common algorithms /////
6762	/////////////////////////////
6763
6764	///// Assorted common algorithms /////
6765	//@{
6766
6767	function movePreservingRanges(node, newParent, newIndex, range) {
6768		// For convenience, I allow newIndex to be -1 to mean "insert at the end".
6769		if (newIndex == -1) {
6770			newIndex = newParent.childNodes.length;
6771		}
6772
6773		// "When the user agent is to move a Node to a new location, preserving
6774		// ranges, it must remove the Node from its original parent (if any), then
6775		// insert it in the new location. In doing so, however, it must ignore the
6776		// regular range mutation rules, and instead follow these rules:"
6777
6778		// "Let node be the moved Node, old parent and old index be the old parent
6779		// (which may be null) and index, and new parent and new index be the new
6780		// parent and index."
6781		var oldParent = node.parentNode;
6782		var oldIndex = getIndexInParent(node);
6783		var i;
6784
6785		// We only even attempt to preserve the global range object and the ranges
6786		// in the selection, not every range out there (the latter is probably
6787		// impossible).
6788		var ranges = [range];
6789		for (i = 0; i < _core.rangy.getSelection().rangeCount; i++) {
6790			ranges.push(_core.rangy.getSelection().getRangeAt(i));
6791		}
6792		var boundaryPoints = [];
6793		$_(ranges).forEach(function (range) {
6794			boundaryPoints.push([range.startContainer, range.startOffset]);
6795			boundaryPoints.push([range.endContainer, range.endOffset]);
6796		});
6797
6798		$_(boundaryPoints).forEach(function (boundaryPoint) {
6799			// "If a boundary point's node is the same as or a descendant of node,
6800			// leave it unchanged, so it moves to the new location."
6801			//
6802			// No modifications necessary.
6803
6804			// "If a boundary point's node is new parent and its offset is greater
6805			// than new index, add one to its offset."
6806			if (boundaryPoint[0] == newParent && boundaryPoint[1] > newIndex) {
6807				boundaryPoint[1]++;
6808			}
6809
6810			// "If a boundary point's node is old parent and its offset is old index or
6811			// old index + 1, set its node to new parent and add new index − old index
6812			// to its offset."
6813			if (boundaryPoint[0] == oldParent && (boundaryPoint[1] == oldIndex || boundaryPoint[1] == oldIndex + 1)) {
6814				boundaryPoint[0] = newParent;
6815				boundaryPoint[1] += newIndex - oldIndex;
6816			}
6817
6818			// "If a boundary point's node is old parent and its offset is greater than
6819			// old index + 1, subtract one from its offset."
6820			if (boundaryPoint[0] == oldParent && boundaryPoint[1] > oldIndex + 1) {
6821				boundaryPoint[1]--;
6822			}
6823		});
6824
6825		// Now actually move it and preserve the ranges.
6826		if (newParent.childNodes.length == newIndex) {
6827			newParent.appendChild(node);
6828		} else {
6829			newParent.insertBefore(node, newParent.childNodes[newIndex]);
6830		}
6831
6832		// if we're off actual node boundaries this implies that the move was
6833		// part of a deletion process (backspace). If that's the case we
6834		// attempt to fix this by restoring the range to the first index of
6835		// the node that has been moved
6836		var newRange = null;
6837		if (boundaryPoints[0][1] > boundaryPoints[0][0].childNodes.length && boundaryPoints[1][1] > boundaryPoints[1][0].childNodes.length) {
6838			range.setStart(node, 0);
6839			range.setEnd(node, 0);
6840		} else {
6841			range.setStart(boundaryPoints[0][0], boundaryPoints[0][1]);
6842			range.setEnd(boundaryPoints[1][0], boundaryPoints[1][1]);
6843
6844			_core.rangy.getSelection().removeAllRanges();
6845			for (i = 1; i < ranges.length; i++) {
6846				newRange = _core.rangy.createRange();
6847				newRange.setStart(boundaryPoints[2 * i][0], boundaryPoints[2 * i][1]);
6848				newRange.setEnd(boundaryPoints[2 * i + 1][0], boundaryPoints[2 * i + 1][1]);
6849				_core.rangy.getSelection().addRange(newRange);
6850			}
6851			if (newRange) {
6852				range = newRange;
6853			}
6854		}
6855	}
6856
6857	/**
6858	 * Copy all non empty attributes from an existing to a new element
6859	 *
6860	 * @param {dom} element The source DOM element
6861	 * @param {dom} newElement The new DOM element which will get the attributes of the source DOM element
6862	 * @return void
6863	 */
6864	function copyAttributes(element, newElement) {
6865
6866		// This is an IE7 workaround. We identified three places that were connected
6867		// to the mysterious ie7 crash:
6868		// 1. Add attribute to dom element (Initialization of jquery-ui sortable)
6869		// 2. Access the jquery expando attribute. Just reading the name is
6870		//    sufficient to make the browser vulnerable for the crash (Press enter)
6871		// 3. On editable blur the Aloha.editables[0].getContents(); gets invoked.
6872		//    This invokation somehow crashes the ie7. We assume that the access of
6873		//    shared expando attribute updates internal references which are not
6874		//    correclty handled during clone();
6875
6876		var attrs = element.attributes;
6877		var i;
6878		for (i = 0; i < attrs.length; i++) {
6879			var attr = attrs[i];
6880			// attr.specified is an IE specific check to exclude attributes that were never really set.
6881			if (typeof attr.specified === "undefined" || attr.specified) {
6882				if (typeof newElement.setAttributeNS === 'function') {
6883					newElement.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
6884				} else {
6885					// fixes https://github.com/alohaeditor/Aloha-Editor/issues/515
6886					newElement.setAttribute(attr.name, attr.value);
6887				}
6888			}
6889		}
6890	}
6891
6892	function setTagName(element, newName, range) {
6893		// "If element is an HTML element with local name equal to new name, return
6894		// element."
6895		if (isNamedHtmlElement(element, newName)) {
6896			return element;
6897		}
6898
6899		// "If element's parent is null, return element."
6900		if (!element.parentNode) {
6901			return element;
6902		}
6903
6904		// "Let replacement element be the result of calling createElement(new
6905		// name) on the ownerDocument of element."
6906		var replacementElement = element.ownerDocument.createElement(newName);
6907
6908		// "Insert replacement element into element's parent immediately before
6909		// element."
6910		element.parentNode.insertBefore(replacementElement, element);
6911
6912		// "Copy all attributes of element to replacement element, in order."
6913		copyAttributes(element, replacementElement);
6914
6915		// "While element has children, append the first child of element as the
6916		// last child of replacement element, preserving ranges."
6917		while (element.childNodes.length) {
6918			movePreservingRanges(element.firstChild, replacementElement, replacementElement.childNodes.length, range);
6919		}
6920
6921		// "Remove element from its parent."
6922		element.parentNode.removeChild(element);
6923
6924		// if the range still uses the old element, we modify it to the new one
6925		if (range.startContainer === element) {
6926			range.startContainer = replacementElement;
6927		}
6928		if (range.endContainer === element) {
6929			range.endContainer = replacementElement;
6930		}
6931
6932		// "Return replacement element."
6933		return replacementElement;
6934	}
6935
6936	function removeExtraneousLineBreaksBefore(node) {
6937		// "Let ref be the previousSibling of node."
6938		var ref = node.previousSibling;
6939
6940		// "If ref is null, abort these steps."
6941		if (!ref) {
6942			return;
6943		}
6944
6945		// "While ref has children, set ref to its lastChild."
6946		while (ref.hasChildNodes()) {
6947			ref = ref.lastChild;
6948		}
6949
6950		// "While ref is invisible but not an extraneous line break, and ref does
6951		// not equal node's parent, set ref to the node before it in tree order."
6952		while (isInvisible(ref) && !isExtraneousLineBreak(ref) && ref != node.parentNode) {
6953			ref = previousNode(ref);
6954		}
6955
6956		// "If ref is an editable extraneous line break, remove it from its
6957		// parent."
6958		if (isEditable(ref) && isExtraneousLineBreak(ref)) {
6959			ref.parentNode.removeChild(ref);
6960		}
6961	}
6962
6963	function removeExtraneousLineBreaksAtTheEndOf(node) {
6964		// "Let ref be node."
6965		var ref = node;
6966
6967		// "While ref has children, set ref to its lastChild."
6968		while (ref.hasChildNodes()) {
6969			ref = ref.lastChild;
6970		}
6971
6972		// "While ref is invisible but not an extraneous line break, and ref does
6973		// not equal node, set ref to the node before it in tree order."
6974		while (isInvisible(ref) && !isExtraneousLineBreak(ref) && ref != node) {
6975			ref = previousNode(ref);
6976		}
6977
6978		// "If ref is an editable extraneous line break, remove it from its
6979		// parent."
6980		if (isEditable(ref) && isExtraneousLineBreak(ref)) {
6981			ref.parentNode.removeChild(ref);
6982		}
6983	}
6984
6985	// "To remove extraneous line breaks from a node, first remove extraneous line
6986	// breaks before it, then remove extraneous line breaks at the end of it."
6987	function removeExtraneousLineBreaksFrom(node) {
6988		removeExtraneousLineBreaksBefore(node);
6989		removeExtraneousLineBreaksAtTheEndOf(node);
6990	}
6991
6992	//@}
6993	///// Wrapping a list of nodes /////
6994	//@{
6995
6996	function wrap(nodeList, siblingCriteria, newParentInstructions, range) {
6997		var i;
6998
6999		// "If not provided, sibling criteria returns false and new parent
7000		// instructions returns null."
7001		if (typeof siblingCriteria == "undefined") {
7002			siblingCriteria = function () {
7003				return false;
7004			};
7005		}
7006		if (typeof newParentInstructions == "undefined") {
7007			newParentInstructions = function () {
7008				return null;
7009			};
7010		}
7011
7012		// "If node list is empty, or the first member of node list is not
7013		// editable, return null and abort these steps."
7014		if (!nodeList.length || !isEditable(nodeList[0])) {
7015			return null;
7016		}
7017
7018		// "If node list's last member is an inline node that's not a br, and node
7019		// list's last member's nextSibling is a br, append that br to node list."
7020		if (isInlineNode(nodeList[nodeList.length - 1]) && !isNamedHtmlElement(nodeList[nodeList.length - 1], "br") && isNamedHtmlElement(nodeList[nodeList.length - 1].nextSibling, "br")) {
7021			nodeList.push(nodeList[nodeList.length - 1].nextSibling);
7022		}
7023
7024		// "If the previousSibling of the first member of node list is editable and
7025		// running sibling criteria on it returns true, let new parent be the
7026		// previousSibling of the first member of node list."
7027		var newParent;
7028		if (isEditable(nodeList[0].previousSibling) && siblingCriteria(nodeList[0].previousSibling)) {
7029			newParent = nodeList[0].previousSibling;
7030
7031			// "Otherwise, if the nextSibling of the last member of node list is
7032			// editable and running sibling criteria on it returns true, let new parent
7033			// be the nextSibling of the last member of node list."
7034		} else if (isEditable(nodeList[nodeList.length - 1].nextSibling) && siblingCriteria(nodeList[nodeList.length - 1].nextSibling)) {
7035			newParent = nodeList[nodeList.length - 1].nextSibling;
7036
7037			// "Otherwise, run new parent instructions, and let new parent be the
7038			// result."
7039		} else {
7040			newParent = newParentInstructions();
7041		}
7042
7043		// "If new parent is null, abort these steps and return null."
7044		if (!newParent) {
7045			return null;
7046		}
7047
7048		// "If new parent's parent is null:"
7049		if (!newParent.parentNode) {
7050			// "Insert new parent into the parent of the first member of node list
7051			// immediately before the first member of node list."
7052			nodeList[0].parentNode.insertBefore(newParent, nodeList[0]);
7053
7054			// "If any range has a boundary point with node equal to the parent of
7055			// new parent and offset equal to the index of new parent, add one to
7056			// that boundary point's offset."
7057			//
7058			// Try to fix range
7059			var startContainer = range.startContainer,
7060				startOffset = range.startOffset,
7061				endContainer = range.endContainer,
7062				endOffset = range.endOffset,
7063			    newParentIndex = getIndexInParent(newParent);
7064
7065			if (startOffset >= newParentIndex && startContainer == newParent.parentNode) {
7066				range.setStart(startContainer, startOffset + 1);
7067			}
7068			if (endOffset >= newParentIndex && endContainer == newParent.parentNode) {
7069				range.setEnd(endContainer, endOffset + 1);
7070			}
7071
7072			// Only try to fix the global range. TODO remove globalRange here
7073			if (globalRange && globalRange !== range) {
7074				startContainer = globalRange.startContainer;
7075				startOffset = globalRange.startOffset;
7076				endContainer = globalRange.endContainer;
7077				endOffset = globalRange.endOffset;
7078				if (startContainer == newParent.parentNode && startOffset >= newParentIndex) {
7079					globalRange.setStart(startContainer, startOffset + 1);
7080				}
7081				if (endContainer == newParent.parentNode && endOffset >= newParentIndex) {
7082					globalRange.setEnd(endContainer, endOffset + 1);
7083				}
7084			}
7085		}
7086
7087		// "Let original parent be the parent of the first member of node list."
7088		var originalParent = nodeList[0].parentNode;
7089
7090		// "If new parent is before the first member of node list in tree order:"
7091		if (isBefore(newParent, nodeList[0])) {
7092			// "If new parent is not an inline node, but the last child of new
7093			// parent and the first member of node list are both inline nodes, and
7094			// the last child of new parent is not a br, call createElement("br")
7095			// on the ownerDocument of new parent and append the result as the last
7096			// child of new parent."
7097			if (!isInlineNode(newParent) && isInlineNode(newParent.lastChild) && isInlineNode(nodeList[0]) && !isNamedHtmlElement(newParent.lastChild, "BR")) {
7098				newParent.appendChild(newParent.ownerDocument.createElement("br"));
7099			}
7100
7101			// "For each node in node list, append node as the last child of new
7102			// parent, preserving ranges."
7103			for (i = 0; i < nodeList.length; i++) {
7104				movePreservingRanges(nodeList[i], newParent, -1, range);
7105			}
7106
7107			// "Otherwise:"
7108		} else {
7109			// "If new parent is not an inline node, but the first child of new
7110			// parent and the last member of node list are both inline nodes, and
7111			// the last member of node list is not a br, call createElement("br")
7112			// on the ownerDocument of new parent and insert the result as the
7113			// first child of new parent."
7114			if (!isInlineNode(newParent) && isInlineNode(newParent.firstChild) && isInlineNode(nodeList[nodeList.length - 1]) && !isNamedHtmlElement(nodeList[nodeList.length - 1], "BR")) {
7115				newParent.insertBefore(newParent.ownerDocument.createElement("br"), newParent.firstChild);
7116			}
7117
7118			// "For each node in node list, in reverse order, insert node as the
7119			// first child of new parent, preserving ranges."
7120			for (i = nodeList.length - 1; i >= 0; i--) {
7121				movePreservingRanges(nodeList[i], newParent, 0, range);
7122			}
7123		}
7124
7125		// "If original parent is editable and has no children, remove it from its
7126		// parent."
7127		if (isEditable(originalParent) && !originalParent.hasChildNodes()) {
7128			originalParent.parentNode.removeChild(originalParent);
7129		}
7130
7131		// "If new parent's nextSibling is editable and running sibling criteria on
7132		// it returns true:"
7133		if (isEditable(newParent.nextSibling) && siblingCriteria(newParent.nextSibling)) {
7134			// "If new parent is not an inline node, but new parent's last child
7135			// and new parent's nextSibling's first child are both inline nodes,
7136			// and new parent's last child is not a br, call createElement("br") on
7137			// the ownerDocument of new parent and append the result as the last
7138			// child of new parent."
7139			if (!isInlineNode(newParent) && isInlineNode(newParent.lastChild) && isInlineNode(newParent.nextSibling.firstChild) && !isNamedHtmlElement(newParent.lastChild, "BR")) {
7140				newParent.appendChild(newParent.ownerDocument.createElement("br"));
7141			}
7142
7143			// "While new parent's nextSibling has children, append its first child
7144			// as the last child of new parent, preserving ranges."
7145			while (newParent.nextSibling.hasChildNodes()) {
7146				movePreservingRanges(newParent.nextSibling.firstChild, newParent, -1, range);
7147			}
7148
7149			// "Remove new parent's nextSibling from its parent."
7150			newParent.parentNode.removeChild(newParent.nextSibling);
7151		}
7152
7153		// "Remove extraneous line breaks from new parent."
7154		removeExtraneousLineBreaksFrom(newParent);
7155
7156		// "Return new parent."
7157		return newParent;
7158	}
7159
7160
7161	//@}
7162	///// Allowed children /////
7163	//@{
7164
7165	// "A name of an element with inline contents is "a", "abbr", "b", "bdi",
7166	// "bdo", "cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i",
7167	// "kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small",
7168	// "span", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike",
7169	// "xmp", "big", "blink", "font", "marquee", "nobr", or "tt"."
7170	var namesOfElementsWithInlineContentsMap = {
7171		"A": true,
7172		"ABBR": true,
7173		"B": true,
7174		"BDI": true,
7175		"BDO": true,
7176		"CITE": true,
7177		"CODE": true,
7178		"DFN": true,
7179		"EM": true,
7180		"H1": true,
7181		"H2": true,
7182		"H3": true,
7183		"H4": true,
7184		"H5": true,
7185		"H6": true,
7186		"I": true,
7187		"KBD": true,
7188		"MARK": true,
7189		"P": true,
7190		"PRE": true,
7191		"Q": true,
7192		"RP": true,
7193		"RT": true,
7194		"RUBY": true,
7195		"S": true,
7196		"SAMP": true,
7197		"SMALL": true,
7198		"SPAN": true,
7199		"STRONG": true,
7200		"SUB": true,
7201		"SUP": true,
7202		"U": true,
7203		"VAR": true,
7204		"ACRONYM": true,
7205		"LISTING": true,
7206		"STRIKE": true,
7207		"XMP": true,
7208		"BIG": true,
7209		"BLINK": true,
7210		"FONT": true,
7211		"MARQUEE": true,
7212		"NOBR": true,
7213		"TT": true
7214	};
7215
7216
7217	var tableRelatedElements = {
7218		"colgroup": true,
7219		"table": true,
7220		"tbody": true,
7221		"tfoot": true,
7222		"thead": true,
7223		"tr": true
7224	};
7225
7226	var scriptRelatedElements = {
7227		"script": true,
7228		"style": true,
7229		"plaintext": true,
7230		"xmp": true
7231	};
7232
7233	var prohibitedHeadingNestingMap = jQuery.extend({
7234		"H1": true,
7235		"H2": true,
7236		"H3": true,
7237		"H4": true,
7238		"H5": true,
7239		"H6": true
7240	}, prohibitedParagraphChildNamesMap);
7241
7242	var prohibitedTableNestingMap = {
7243		"CAPTION": true,
7244		"COL": true,
7245		"COLGROUP": true,
7246		"TBODY": true,
7247		"TD": true,
7248		"TFOOT": true,
7249		"TH": true,
7250		"THEAD": true,
7251		"TR": true
7252	};
7253
7254	var prohibitedDefNestingMap = {
7255		"DD": true,
7256		"DT": true
7257	};
7258
7259	var prohibitedNestingCombinationsMap = {
7260		"A": jQuery.extend({
7261			"A": true
7262		}, prohibitedParagraphChildNamesMap),
7263		"DD": prohibitedDefNestingMap,
7264		"DT": prohibitedDefNestingMap,
7265		"LI": {
7266			"LI": true
7267		},
7268		"NOBR": jQuery.extend({
7269			"NOBR": true
7270		}, prohibitedParagraphChildNamesMap),
7271		"H1": prohibitedHeadingNestingMap,
7272		"H2": prohibitedHeadingNestingMap,
7273		"H3": prohibitedHeadingNestingMap,
7274		"H4": prohibitedHeadingNestingMap,
7275		"H5": prohibitedHeadingNestingMap,
7276		"H6": prohibitedHeadingNestingMap,
7277		"TD": prohibitedTableNestingMap,
7278		"TH": prohibitedTableNestingMap,
7279		// this is the same as namesOfElementsWithInlineContentsMap excluding a and h1-h6 elements above
7280		"ABBR": prohibitedParagraphChildNamesMap,
7281		"B": prohibitedParagraphChildNamesMap,
7282		"BDI": prohibitedParagraphChildNamesMap,
7283		"BDO": prohibitedParagraphChildNamesMap,
7284		"CITE": prohibitedParagraphChildNamesMap,
7285		"CODE": prohibitedParagraphChildNamesMap,
7286		"DFN": prohibitedParagraphChildNamesMap,
7287		"EM": prohibitedParagraphChildNamesMap,
7288		"I": prohibitedParagraphChildNamesMap,
7289		"KBD": prohibitedParagraphChildNamesMap,
7290		"MARK": prohibitedParagraphChildNamesMap,
7291		"P": prohibitedParagraphChildNamesMap,
7292		"PRE": prohibitedParagraphChildNamesMap,
7293		"Q": prohibitedParagraphChildNamesMap,
7294		"RP": prohibitedParagraphChildNamesMap,
7295		"RT": prohibitedParagraphChildNamesMap,
7296		"RUBY": prohibitedParagraphChildNamesMap,
7297		"S": prohibitedParagraphChildNamesMap,
7298		"SAMP": prohibitedParagraphChildNamesMap,
7299		"SMALL": prohibitedParagraphChildNamesMap,
7300		"SPAN": prohibitedParagraphChildNamesMap,
7301		"STRONG": prohibitedParagraphChildNamesMap,
7302		"SUB": prohibitedParagraphChildNamesMap,
7303		"SUP": prohibitedParagraphChildNamesMap,
7304		"U": prohibitedParagraphChildNamesMap,
7305		"VAR": prohibitedParagraphChildNamesMap,
7306		"ACRONYM": prohibitedParagraphChildNamesMap,
7307		"LISTING": prohibitedParagraphChildNamesMap,
7308		"STRIKE": prohibitedParagraphChildNamesMap,
7309		"XMP": prohibitedParagraphChildNamesMap,
7310		"BIG": prohibitedParagraphChildNamesMap,
7311		"BLINK": prohibitedParagraphChildNamesMap,
7312		"FONT": prohibitedParagraphChildNamesMap,
7313		"MARQUEE": prohibitedParagraphChildNamesMap,
7314		"TT": prohibitedParagraphChildNamesMap
7315	};
7316
7317	// "An element with inline contents is an HTML element whose local name is a
7318	// name of an element with inline contents."
7319	function isElementWithInlineContents(node) {
7320		return isMappedHtmlElement(node, namesOfElementsWithInlineContentsMap);
7321	}
7322
7323	function isAllowedChild(child, parent_) {
7324		// "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or
7325		// an HTML element with local name equal to one of those, and child is a
7326		// Text node whose data does not consist solely of space characters, return
7327		// false."
7328		if ((tableRelatedElements[parent_] || isHtmlElementInArray(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"])) && typeof child == "object" && child.nodeType == Node.TEXT_NODE && !/^[ \t\n\f\r]*$/.test(child.data)) {
7329			return false;
7330		}
7331
7332		// "If parent is "script", "style", "plaintext", or "xmp", or an HTML
7333		// element with local name equal to one of those, and child is not a Text
7334		// node, return false."
7335		if ((scriptRelatedElements[parent_] || isHtmlElementInArray(parent_, ["script", "style", "plaintext", "xmp"])) && (typeof child != "object" || child.nodeType != Node.TEXT_NODE)) {
7336			return false;
7337		}
7338
7339		// "If child is a Document, DocumentFragment, or DocumentType, return
7340		// false."
7341		if (typeof child == "object" && (child.nodeType == Node.DOCUMENT_NODE || child.nodeType == Node.DOCUMENT_FRAGMENT_NODE || child.nodeType == Node.DOCUMENT_TYPE_NODE)) {
7342			return false;
7343		}
7344
7345		// "If child is an HTML element, set child to the local name of child."
7346		if (isAnyHtmlElement(child)) {
7347			child = child.tagName.toLowerCase();
7348		}
7349
7350		// "If child is not a string, return true."
7351		if (typeof child != "string") {
7352			return true;
7353		}
7354
7355		// "If parent is an HTML element:"
7356		if (isAnyHtmlElement(parent_)) {
7357
7358			// "If child is "a", and parent or some ancestor of parent is an a,
7359			// return false."
7360			//
7361			// "If child is a prohibited paragraph child name and parent or some
7362			// ancestor of parent is an element with inline contents, return
7363			// false."
7364			//
7365			// "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or
7366			// some ancestor of parent is an HTML element with local name "h1",
7367			// "h2", "h3", "h4", "h5", or "h6", return false."
7368			// 
7369			var ancestor = parent_;
7370
7371			while (ancestor) {
7372				if (child == "a" && isNamedHtmlElement(ancestor, 'a')) {
7373					return false;
7374				}
7375				if (prohibitedParagraphChildNamesMap[child.toUpperCase()] && isElementWithInlineContents(ancestor)) {
7376					return false;
7377				}
7378				if (/^h[1-6]$/.test(child) && isAnyHtmlElement(ancestor) && /^H[1-6]$/.test(ancestor.tagName)) {
7379					return false;
7380				}
7381				ancestor = ancestor.parentNode;
7382			}
7383
7384			// "Let parent be the local name of parent."
7385			parent_ = parent_.tagName.toLowerCase();
7386		}
7387
7388		// "If parent is an Element or DocumentFragment, return true."
7389		if (typeof parent_ == "object" && (parent_.nodeType == Node.ELEMENT_NODE || parent_.nodeType == Node.DOCUMENT_FRAGMENT_NODE)) {
7390			return true;
7391		}
7392
7393		// "If parent is not a string, return false."
7394		if (typeof parent_ != "string") {
7395			return false;
7396		}
7397
7398		// "If parent is on the left-hand side of an entry on the following list,
7399		// then return true if child is listed on the right-hand side of that
7400		// entry, and false otherwise."
7401		switch (parent_) {
7402		case "colgroup":
7403			return child == "col";
7404		case "table":
7405			return jQuery.inArray(child, ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]) != -1;
7406		case "tbody":
7407		case "thead":
7408		case "tfoot":
7409			return jQuery.inArray(child, ["td", "th", "tr"]) != -1;
7410		case "tr":
7411			return jQuery.inArray(child, ["td", "th"]) != -1;
7412		case "dl":
7413			return jQuery.inArray(child, ["dt", "dd"]) != -1;
7414		case "dir":
7415		case "ol":
7416		case "ul":
7417			return jQuery.inArray(child, ["dir", "li", "ol", "ul"]) != -1;
7418		case "hgroup":
7419			return (/^h[1-6]$/).test(child);
7420		}
7421
7422		// "If child is "body", "caption", "col", "colgroup", "frame", "frameset",
7423		// "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return
7424		// false."
7425		if (jQuery.inArray(child, ["body", "caption", "col", "colgroup", "frame", "frameset", "head", "html", "tbody", "td", "tfoot", "th", "thead", "tr"]) != -1) {
7426			return false;
7427		}
7428
7429		// "If child is "dd" or "dt" and parent is not "dl", return false."
7430		if (jQuery.inArray(child, ["dd", "dt"]) != -1 && parent_ != "dl") {
7431			return false;
7432		}
7433
7434		// "If child is "li" and parent is not "ol" or "ul", return false."
7435		if (child == "li" && parent_ != "ol" && parent_ != "ul") {
7436			return false;
7437		}
7438
7439		// "If parent is on the left-hand side of an entry on the following list
7440		// and child is listed on the right-hand side of that entry, return false."
7441		var leftSide = prohibitedNestingCombinationsMap[parent_.toUpperCase()];
7442		if (leftSide) {
7443			var rightSide = leftSide[child.toUpperCase()];
7444			if (rightSide) {
7445				return false;
7446			}
7447		}
7448
7449		// "Return true."
7450		return true;
7451	}
7452
7453
7454	//@}
7455
7456	//////////////////////////////////////
7457	///// Inline formatting commands /////
7458	//////////////////////////////////////
7459
7460	///// Inline formatting command definitions /////
7461	//@{
7462
7463	// "A node node is effectively contained in a range range if range is not
7464	// collapsed, and at least one of the following holds:"
7465	function isEffectivelyContained(node, range) {
7466
7467		if (range.collapsed) {
7468			return false;
7469		}
7470
7471		// "node is contained in range."
7472		if (isContained(node, range)) {
7473			return true;
7474		}
7475
7476		// "node is range's start node, it is a Text node, and its length is
7477		// different from range's start offset."
7478		if (node == range.startContainer && node.nodeType == Node.TEXT_NODE && getNodeLength(node) != range.startOffset) {
7479			return true;
7480		}
7481
7482		// "node is range's end node, it is a Text node, and range's end offset is
7483		// not 0."
7484		if (node == range.endContainer && node.nodeType == Node.TEXT_NODE && range.endOffset != 0) {
7485			return true;
7486		}
7487
7488		// "node has at least one child; and all its children are effectively
7489		// contained in range; and either range's start node is not a descendant of
7490		// node or is not a Text node or range's start offset is zero; and either
7491		// range's end node is not a descendant of node or is not a Text node or
7492		// range's end offset is its end node's length."
7493		if (node.hasChildNodes() && $_(node.childNodes).every(function (child) { return isEffectivelyContained(child, range); })
7494			    && (!isDescendant(range.startContainer, node)
7495					|| range.startContainer.nodeType != Node.TEXT_NODE
7496					|| range.startOffset == 0)
7497			    && (!isDescendant(range.endContainer, node)
7498					|| range.endContainer.nodeType != Node.TEXT_NODE
7499					|| range.endOffset == getNodeLength(range.endContainer))) {
7500			return true;
7501		}
7502
7503		return false;
7504
7505	}
7506
7507	// Like get(All)ContainedNodes(), but for effectively contained nodes.
7508	function getEffectivelyContainedNodes(range, condition) {
7509
7510		if (typeof condition == "undefined") {
7511			condition = function () {
7512				return true;
7513			};
7514		}
7515
7516		var node = range.startContainer;
7517
7518		while (isEffectivelyContained(node.parentNode, range)) {
7519			node = node.parentNode;
7520		}
7521
7522		var stop = nextNodeDescendants(range.endContainer);
7523
7524		var nodeList = [];
7525
7526		while (isBefore(node, stop)) {
7527
7528			if (isEffectivelyContained(node, range) && condition(node)) {
7529				nodeList.push(node);
7530				node = nextNodeDescendants(node);
7531				continue;
7532			}
7533
7534			node = nextNode(node);
7535
7536		}
7537		
7538		return nodeList;
7539
7540	}
7541
7542	function getAllEffectivelyContainedNodes(range, condition) {
7543
7544		if (typeof condition == "undefined") {
7545			condition = function () {
7546				return true;
7547			};
7548		}
7549
7550		var node = range.startContainer;
7551
7552		while (isEffectivelyContained(node.parentNode, range)) {
7553			node = node.parentNode;
7554		}
7555
7556		var stop = nextNodeDescendants(range.endContainer);
7557
7558		var nodeList = [];
7559
7560		while (isBefore(node, stop)) {
7561			
7562			if (isEffectivelyContained(node, range) && condition(node)) {
7563				nodeList.push(node);
7564			}
7565
7566			node = nextNode(node);
7567		}
7568
7569		return nodeList;
7570	}
7571
7572	// "A modifiable element is a b, em, i, s, span, strong, sub, sup, or u element
7573	// with no attributes except possibly style; or a font element with no
7574	// attributes except possibly style, color, face, and/or size; or an a element
7575	// with no attributes except possibly style and/or href."
7576	function isModifiableElement(node) {
7577		if (!isAnyHtmlElement(node)) {
7578			return false;
7579		}
7580
7581		if (jQuery.inArray(node.tagName, ["B", "EM", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"]) != -1) {
7582			if (node.attributes.length == 0) {
7583				return true;
7584			}
7585
7586			if (node.attributes.length == 1 && hasAttribute(node, "style")) {
7587				return true;
7588			}
7589		}
7590
7591		if (node.tagName == "FONT" || node.tagName == "A") {
7592			var numAttrs = node.attributes.length;
7593
7594			if (hasAttribute(node, "style")) {
7595				numAttrs--;
7596			}
7597
7598			if (node.tagName == "FONT") {
7599				if (hasAttribute(node, "color")) {
7600					numAttrs--;
7601				}
7602
7603				if (hasAttribute(node, "face")) {
7604					numAttrs--;
7605				}
7606
7607				if (hasAttribute(node, "size")) {
7608					numAttrs--;
7609				}
7610			}
7611
7612			if (node.tagName == "A" && hasAttribute(node, "href")) {
7613				numAttrs--;
7614			}
7615
7616			if (numAttrs == 0) {
7617				return true;
7618			}
7619		}
7620
7621		return false;
7622	}
7623
7624	function isSimpleModifiableElement(node) {
7625		// "A simple modifiable element is an HTML element for which at least one
7626		// of the following holds:"
7627		if (!isAnyHtmlElement(node)) {
7628			return false;
7629		}
7630
7631		// Only these elements can possibly be a simple modifiable element.
7632		if (jQuery.inArray(node.tagName, ["A", "B", "EM", "FONT", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"]) == -1) {
7633			return false;
7634		}
7635
7636		// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
7637		// element with no attributes."
7638		if (node.attributes.length == 0) {
7639			return true;
7640		}
7641
7642		// If it's got more than one attribute, everything after this fails.
7643		if (node.attributes.length > 1) {
7644			return false;
7645		}
7646
7647		// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
7648		// element with exactly one attribute, which is style, which sets no CSS
7649		// properties (including invalid or unrecognized properties)."
7650		//
7651		// Not gonna try for invalid or unrecognized.
7652		if (hasAttribute(node, "style") && getStyleLength(node) == 0) {
7653			return true;
7654		}
7655
7656		// "It is an a element with exactly one attribute, which is href."
7657		if (node.tagName == "A" && hasAttribute(node, "href")) {
7658			return true;
7659		}
7660
7661		// "It is a font element with exactly one attribute, which is either color,
7662		// face, or size."
7663		if (node.tagName == "FONT" && (hasAttribute(node, "color") || hasAttribute(node, "face") || hasAttribute(node, "size"))) {
7664			return true;
7665		}
7666
7667		// "It is a b or strong element with exactly one attribute, which is style,
7668		// and the style attribute sets exactly one CSS property (including invalid
7669		// or unrecognized properties), which is "font-weight"."
7670		if ((node.tagName == "B" || node.tagName == "STRONG") && hasAttribute(node, "style") && getStyleLength(node) == 1 && node.style.fontWeight != "") {
7671			return true;
7672		}
7673
7674		// "It is an i or em element with exactly one attribute, which is style,
7675		// and the style attribute sets exactly one CSS property (including invalid
7676		// or unrecognized properties), which is "font-style"."
7677		if ((node.tagName == "I" || node.tagName == "EM") && hasAttribute(node, "style") && getStyleLength(node) == 1 && node.style.fontStyle != "") {
7678			return true;
7679		}
7680
7681		// "It is an a, font, or span element with exactly one attribute, which is
7682		// style, and the style attribute sets exactly one CSS property (including
7683		// invalid or unrecognized properties), and that property is not
7684		// "text-decoration"."
7685		if ((node.tagName == "A" || node.tagName == "FONT" || node.tagName == "SPAN") && hasAttribute(node, "style") && getStyleLength(node) == 1 && node.style.textDecoration == "") {
7686			return true;
7687		}
7688
7689		// "It is an a, font, s, span, strike, or u element with exactly one
7690		// attribute, which is style, and the style attribute sets exactly one CSS
7691		// property (including invalid or unrecognized properties), which is
7692		// "text-decoration", which is set to "line-through" or "underline" or
7693		// "overline" or "none"."
7694		if (jQuery.inArray(node.tagName, ["A", "FONT", "S", "SPAN", "STRIKE", "U"]) != -1 && hasAttribute(node, "style") && getStyleLength(node) == 1 && (node.style.textDecoration == "line-through" || node.style.textDecoration == "underline" || node.style.textDecoration == "overline" || node.style.textDecoration == "none")) {
7695			return true;
7696		}
7697
7698		return false;
7699	}
7700
7701	// "Two quantities are equivalent values for a command if either both are null,
7702	// or both are strings and they're equal and the command does not define any
7703	// equivalent values, or both are strings and the command defines equivalent
7704	// values and they match the definition."
7705	function areEquivalentValues(command, val1, val2) {
7706		if (val1 === null && val2 === null) {
7707			return true;
7708		}
7709
7710		if (typeof val1 == "string" && typeof val2 == "string" && val1 == val2 && !(commands[command].hasOwnProperty("equivalentValues"))) {
7711			return true;
7712		}
7713
7714		if (typeof val1 == "string" && typeof val2 == "string" && commands[command].hasOwnProperty("equivalentValues") && commands[command].equivalentValues(val1, val2)) {
7715			return true;
7716		}
7717
7718		return false;
7719	}
7720
7721	// "Two quantities are loosely equivalent values for a command if either they
7722	// are equivalent values for the command, or if the command is the fontSize
7723	// command; one of the quantities is one of "xx-small", "small", "medium",
7724	// "large", "x-large", "xx-large", or "xxx-large"; and the other quantity is
7725	// the resolved value of "font-size" on a font element whose size attribute has
7726	// the corresponding value set ("1" through "7" respectively)."
7727	function areLooselyEquivalentValues(command, val1, val2) {
7728		if (areEquivalentValues(command, val1, val2)) {
7729			return true;
7730		}
7731
7732		if (command != "fontsize" || typeof val1 != "string" || typeof val2 != "string") {
7733			return false;
7734		}
7735
7736		// Static variables in JavaScript?
7737		var callee = areLooselyEquivalentValues;
7738		if (callee.sizeMap === undefined) {
7739			callee.sizeMap = {};
7740			var font = document.createElement("font");
7741			document.body.appendChild(font);
7742			$_(["xx-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]).forEach(function (keyword) {
7743				font.size = cssSizeToLegacy(keyword);
7744				callee.sizeMap[keyword] = $_.getComputedStyle(font).fontSize;
7745			});
7746			document.body.removeChild(font);
7747		}
7748
7749		return val1 === callee.sizeMap[val2] || val2 === callee.sizeMap[val1];
7750	}
7751
7752	//@}
7753	///// Assorted inline formatting command algorithms /////
7754	//@{
7755
7756	function getEffectiveCommandValue(node, command) {
7757		// "If neither node nor its parent is an Element, return null."
7758		if (node.nodeType != Node.ELEMENT_NODE && (!node.parentNode || node.parentNode.nodeType != Node.ELEMENT_NODE)) {
7759			return null;
7760		}
7761
7762		// "If node is not an Element, return the effective command value of its
7763		// parent for command."
7764		if (node.nodeType != Node.ELEMENT_NODE) {
7765			return getEffectiveCommandValue(node.parentNode, command);
7766		}
7767
7768		// "If command is "createLink" or "unlink":"
7769		if (command == "createlink" || command == "unlink") {
7770			// "While node is not null, and is not an a element that has an href
7771			// attribute, set node to its parent."
7772			while (node && (!isAnyHtmlElement(node) || node.tagName != "A" || !hasAttribute(node, "href"))) {
7773				node = node.parentNode;
7774			}
7775
7776			// "If node is null, return null."
7777			if (!node) {
7778				return null;
7779			}
7780
7781			// "Return the value of node's href attribute."
7782			return node.getAttribute("href");
7783		}
7784
7785		// "If command is "backColor" or "hiliteColor":"
7786		if (command == "backcolor" || command == "hilitecolor") {
7787			// "While the resolved value of "background-color" on node is any
7788			// fully transparent value, and node's parent is an Element, set
7789			// node to its parent."
7790			//
7791			// Another lame hack to avoid flawed APIs.
7792			while (($_.getComputedStyle(node).backgroundColor == "rgba(0, 0, 0, 0)" || $_.getComputedStyle(node).backgroundColor === "" || $_.getComputedStyle(node).backgroundColor == "transparent") && node.parentNode && node.parentNode.nodeType == Node.ELEMENT_NODE) {
7793				node = node.parentNode;
7794			}
7795
7796			// "If the resolved value of "background-color" on node is a fully
7797			// transparent value, return "rgb(255, 255, 255)"."
7798			if ($_.getComputedStyle(node).backgroundColor == "rgba(0, 0, 0, 0)" || $_.getComputedStyle(node).backgroundColor === "" || $_.getComputedStyle(node).backgroundColor == "transparent") {
7799				return "rgb(255, 255, 255)";
7800			}
7801
7802			// "Otherwise, return the resolved value of "background-color" for
7803			// node."
7804			return $_.getComputedStyle(node).backgroundColor;
7805		}
7806
7807		// "If command is "subscript" or "superscript":"
7808		if (command == "subscript" || command == "superscript") {
7809			// "Let affected by subscript and affected by superscript be two
7810			// boolean variables, both initially false."
7811			var affectedBySubscript = false;
7812			var affectedBySuperscript = false;
7813
7814			// "While node is an inline node:"
7815			while (isInlineNode(node)) {
7816				var verticalAlign = $_.getComputedStyle(node).verticalAlign;
7817
7818				// "If node is a sub, set affected by subscript to true."
7819				if (isNamedHtmlElement(node, 'sub')) {
7820					affectedBySubscript = true;
7821					// "Otherwise, if node is a sup, set affected by superscript to
7822					// true."
7823				} else if (isNamedHtmlElement(node, 'sup')) {
7824					affectedBySuperscript = true;
7825				}
7826
7827				// "Set node to its parent."
7828				node = node.parentNode;
7829			}
7830
7831			// "If affected by subscript and affected by superscript are both true,
7832			// return the string "mixed"."
7833			if (affectedBySubscript && affectedBySuperscript) {
7834				return "mixed";
7835			}
7836
7837			// "If affected by subscript is true, return "subscript"."
7838			if (affectedBySubscript) {
7839				return "subscript";
7840			}
7841
7842			// "If affected by superscript is true, return "superscript"."
7843			if (affectedBySuperscript) {
7844				return "superscript";
7845			}
7846
7847			// "Return null."
7848			return null;
7849		}
7850
7851		// "If command is "strikethrough", and the "text-decoration" property of
7852		// node or any of its ancestors has resolved value containing
7853		// "line-through", return "line-through". Otherwise, return null."
7854		if (command == "strikethrough") {
7855			do {
7856				if ($_.getComputedStyle(node).textDecoration.indexOf("line-through") != -1) {
7857					return "line-through";
7858				}
7859				node = node.parentNode;
7860			} while (node && node.nodeType == Node.ELEMENT_NODE);
7861			return null;
7862		}
7863
7864		// "If command is "underline", and the "text-decoration" property of node
7865		// or any of its ancestors has resolved value containing "underline",
7866		// return "underline". Otherwise, return null."
7867		if (command == "underline") {
7868			do {
7869				if ($_.getComputedStyle(node).textDecoration.indexOf("underline") != -1) {
7870					return "underline";
7871				}
7872				node = node.parentNode;
7873			} while (node && node.nodeType == Node.ELEMENT_NODE);
7874			return null;
7875		}
7876
7877		if (!commands[command].hasOwnProperty("relevantCssProperty")) {
7878			throw "Bug: no relevantCssProperty for " + command + " in getEffectiveCommandValue";
7879		}
7880
7881		// "Return the resolved value for node of the relevant CSS property for
7882		// command."
7883		return $_.getComputedStyle(node)[commands[command].relevantCssProperty].toString();
7884	}
7885
7886	function getSpecifiedCommandValue(element, command) {
7887		// "If command is "backColor" or "hiliteColor" and element's display
7888		// property does not have resolved value "inline", return null."
7889		if ((command == "backcolor" || command == "hilitecolor") && $_.getComputedStyle(element).display != "inline") {
7890			return null;
7891		}
7892
7893		// "If command is "createLink" or "unlink":"
7894		if (command == "createlink" || command == "unlink") {
7895			// "If element is an a element and has an href attribute, return the
7896			// value of that attribute."
7897			if (isAnyHtmlElement(element) && element.tagName == "A" && hasAttribute(element, "href")) {
7898				return element.getAttribute("href");
7899			}
7900
7901			// "Return null."
7902			return null;
7903		}
7904
7905		// "If command is "subscript" or "superscript":"
7906		if (command == "subscript" || command == "superscript") {
7907			// "If element is a sup, return "superscript"."
7908			if (isNamedHtmlElement(element, 'sup')) {
7909				return "superscript";
7910			}
7911
7912			// "If element is a sub, return "subscript"."
7913			if (isNamedHtmlElement(element, 'sub')) {
7914				return "subscript";
7915			}
7916
7917			// "Return null."
7918			return null;
7919		}
7920
7921		// "If command is "strikethrough", and element has a style attribute set,
7922		// and that attribute sets "text-decoration":"
7923		if (command == "strikethrough" && element.style.textDecoration != "") {
7924			// "If element's style attribute sets "text-decoration" to a value
7925			// containing "line-through", return "line-through"."
7926			if (element.style.textDecoration.indexOf("line-through") != -1) {
7927				return "line-through";
7928			}
7929
7930			// "Return null."
7931			return null;
7932		}
7933
7934		// "If command is "strikethrough" and element is a s or strike element,
7935		// return "line-through"."
7936		if (command == "strikethrough" && isHtmlElementInArray(element, ["S", "STRIKE"])) {
7937			return "line-through";
7938		}
7939
7940		// "If command is "underline", and element has a style attribute set, and
7941		// that attribute sets "text-decoration":"
7942		if (command == "underline" && element.style.textDecoration != "") {
7943			// "If element's style attribute sets "text-decoration" to a value
7944			// containing "underline", return "underline"."
7945			if (element.style.textDecoration.indexOf("underline") != -1) {
7946				return "underline";
7947			}
7948
7949			// "Return null."
7950			return null;
7951		}
7952
7953		// "If command is "underline" and element is a u element, return
7954		// "underline"."
7955		if (command == "underline" && isNamedHtmlElement(element, 'U')) {
7956			return "underline";
7957		}
7958
7959		// "Let property be the relevant CSS property for command."
7960		var property = commands[command].relevantCssProperty;
7961
7962		// "If property is null, return null."
7963		if (property === null) {
7964			return null;
7965		}
7966
7967		// "If element has a style attribute set, and that attribute has the
7968		// effect of setting property, return the value that it sets property to."
7969		if (element.style[property] != "") {
7970			return element.style[property];
7971		}
7972
7973		// "If element is a font element that has an attribute whose effect is
7974		// to create a presentational hint for property, return the value that the
7975		// hint sets property to.  (For a size of 7, this will be the non-CSS value
7976		// "xxx-large".)"
7977		if (isHtmlNamespace(element.namespaceURI) && element.tagName == "FONT") {
7978			if (property == "color" && hasAttribute(element, "color")) {
7979				return element.color;
7980			}
7981			if (property == "fontFamily" && hasAttribute(element, "face")) {
7982				return element.face;
7983			}
7984			if (property == "fontSize" && hasAttribute(element, "size")) {
7985				// This is not even close to correct in general.
7986				var size = parseInt(element.size, 10);
7987				if (size < 1) {
7988					size = 1;
7989				}
7990				if (size > 7) {
7991					size = 7;
7992				}
7993				return {
7994					1: "xx-small",
7995					2: "small",
7996					3: "medium",
7997					4: "large",
7998					5: "x-large",
7999					6: "xx-large",
8000					7: "xxx-large"
8001				}[size];
8002			}
8003		}
8004
8005		// "If element is in the following list, and property is equal to the
8006		// CSS property name listed for it, return the string listed for it."
8007		//
8008		// A list follows, whose meaning is copied here.
8009		if (property == "fontWeight" && (element.tagName == "B" || element.tagName == "STRONG")) {
8010			return "bold";
8011		}
8012		if (property == "fontStyle" && (element.tagName == "I" || element.tagName == "EM")) {
8013			return "italic";
8014		}
8015
8016		// "Return null."
8017		return null;
8018	}
8019
8020	function reorderModifiableDescendants(node, command, newValue, range) {
8021		// "Let candidate equal node."
8022		var candidate = node;
8023
8024		// "While candidate is a modifiable element, and candidate has exactly one
8025		// child, and that child is also a modifiable element, and candidate is not
8026		// a simple modifiable element or candidate's specified command value for
8027		// command is not equivalent to new value, set candidate to its child."
8028		while (isModifiableElement(candidate) && candidate.childNodes.length == 1 && isModifiableElement(candidate.firstChild) && (!isSimpleModifiableElement(candidate) || !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue))) {
8029			candidate = candidate.firstChild;
8030		}
8031
8032		// "If candidate is node, or is not a simple modifiable element, or its
8033		// specified command value is not equivalent to new value, or its effective
8034		// command value is not loosely equivalent to new value, abort these
8035		// steps."
8036		if (candidate == node || !isSimpleModifiableElement(candidate) || !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue) || !areLooselyEquivalentValues(command, getEffectiveCommandValue(candidate, command), newValue)) {
8037			return;
8038		}
8039
8040		// "While candidate has children, insert the first child of candidate into
8041		// candidate's parent immediately before candidate, preserving ranges."
8042		while (candidate.hasChildNodes()) {
8043			movePreservingRanges(candidate.firstChild, candidate.parentNode, getIndexInParent(candidate), range);
8044		}
8045
8046		// "Insert candidate into node's parent immediately after node."
8047		node.parentNode.insertBefore(candidate, node.nextSibling);
8048
8049		// "Append the node as the last child of candidate, preserving ranges."
8050		movePreservingRanges(node, candidate, -1, range);
8051	}
8052
8053	var recordValuesCommands = ["subscript", "bold", "fontname", "fontsize", "forecolor", "hilitecolor", "italic", "strikethrough", "underline"];
8054
8055	function recordValues(nodeList) {
8056		// "Let values be a list of (node, command, specified command value)
8057		// triples, initially empty."
8058		var values = [];
8059
8060		// "For each node in node list, for each command in the list "subscript",
8061		// "bold", "fontName", "fontSize", "foreColor", "hiliteColor", "italic",
8062		// "strikethrough", and "underline" in that order:"
8063
8064		// Ensure we have a plain array to avoid the potential performance
8065		// overhead of a NodeList
8066		var nodes = jQuery.makeArray(nodeList);
8067		var i, j;
8068		var node;
8069		var command;
8070		var ancestor;
8071		var specifiedCommandValue;
8072		for (i = 0; i < nodes.length; i++) {
8073			node = nodes[i];
8074			for (j = 0; j < recordValuesCommands.length; j++) {
8075				command = recordValuesCommands[j];
8076
8077				// "Let ancestor equal node."
8078				ancestor = node;
8079
8080				// "If ancestor is not an Element, set it to its parent."
8081				if (ancestor.nodeType != 1) {
8082					ancestor = ancestor.parentNode;
8083				}
8084
8085				// "While ancestor is an Element and its specified command value
8086				// for command is null, set it to its parent."
8087				specifiedCommandValue = null;
8088				while (ancestor && ancestor.nodeType == 1 && (specifiedCommandValue = getSpecifiedCommandValue(ancestor, command)) === null) {
8089					ancestor = ancestor.parentNode;
8090				}
8091
8092				// "If ancestor is an Element, add (node, command, ancestor's
8093				// specified command value for command) to values. Otherwise add
8094				// (node, command, null) to values."
8095				values.push([node, command, specifiedCommandValue]);
8096			}
8097		}
8098
8099		// "Return values."
8100		return values;
8101	}
8102
8103	//@}
8104	///// Clearing an element's value /////
8105	//@{
8106
8107	function clearValue(element, command, range) {
8108		// "If element is not editable, return the empty list."
8109		if (!isEditable(element)) {
8110			return [];
8111		}
8112
8113		// "If element's specified command value for command is null, return the
8114		// empty list."
8115		if (getSpecifiedCommandValue(element, command) === null) {
8116			return [];
8117		}
8118
8119		// "If element is a simple modifiable element:"
8120		if (isSimpleModifiableElement(element)) {
8121			// "Let children be the children of element."
8122			var children = Array.prototype.slice.call(toArray(element.childNodes));
8123
8124			// "For each child in children, insert child into element's parent
8125			// immediately before element, preserving ranges."
8126			var i;
8127			for (i = 0; i < children.length; i++) {
8128				movePreservingRanges(children[i], element.parentNode, getIndexInParent(element), range);
8129			}
8130
8131			// "Remove element from its parent."
8132			element.parentNode.removeChild(element);
8133
8134			// "Return children."
8135			return children;
8136		}
8137
8138		// "If command is "strikethrough", and element has a style attribute that
8139		// sets "text-decoration" to some value containing "line-through", delete
8140		// "line-through" from the value."
8141		if (command == "strikethrough" && element.style.textDecoration.indexOf("line-through") != -1) {
8142			if (element.style.textDecoration == "line-through") {
8143				element.style.textDecoration = "";
8144			} else {
8145				element.style.textDecoration = element.style.textDecoration.replace("line-through", "");
8146			}
8147			if (element.getAttribute("style") == "") {
8148				element.removeAttribute("style");
8149			}
8150		}
8151
8152		// "If command is "underline", and element has a style attribute that sets
8153		// "text-decoration" to some value containing "underline", delete
8154		// "underline" from the value."
8155		if (command == "underline" && element.style.textDecoration.indexOf("underline") != -1) {
8156			if (element.style.textDecoration == "underline") {
8157				element.style.textDecoration = "";
8158			} else {
8159				element.style.textDecoration = element.style.textDecoration.replace("underline", "");
8160			}
8161			if (element.getAttribute("style") == "") {
8162				element.removeAttribute("style");
8163			}
8164		}
8165
8166		// "If the relevant CSS property for command is not null, unset the CSS
8167		// property property of element."
8168		if (commands[command].relevantCssProperty !== null) {
8169			element.style[commands[command].relevantCssProperty] = '';
8170			if (element.getAttribute("style") == "") {
8171				element.removeAttribute("style");
8172			}
8173		}
8174
8175		// "If element is a font element:"
8176		if (isHtmlNamespace(element.namespaceURI) && element.tagName == "FONT") {
8177			// "If command is "foreColor", unset element's color attribute, if set."
8178			if (command == "forecolor") {
8179				element.removeAttribute("color");
8180			}
8181
8182			// "If command is "fontName", unset element's face attribute, if set."
8183			if (command == "fontname") {
8184				element.removeAttribute("face");
8185			}
8186
8187			// "If command is "fontSize", unset element's size attribute, if set."
8188			if (command == "fontsize") {
8189				element.removeAttribute("size");
8190			}
8191		}
8192
8193		// "If element is an a element and command is "createLink" or "unlink",
8194		// unset the href property of element."
8195		if (isNamedHtmlElement(element, 'A') && (command == "createlink" || command == "unlink")) {
8196			element.removeAttribute("href");
8197		}
8198
8199		// "If element's specified command value for command is null, return the
8200		// empty list."
8201		if (getSpecifiedCommandValue(element, command) === null) {
8202			return [];
8203		}
8204
8205		// "Set the tag name of element to "span", and return the one-node list
8206		// consisting of the result."
8207		return [setTagName(element, "span", range)];
8208	}
8209
8210	//@}
8211	///// Forcing the value of a node /////
8212	//@{
8213
8214	function forceValue(node, command, newValue, range) {
8215		var children = [];
8216		var i;
8217		var specifiedValue;
8218
8219		// "If node's parent is null, abort this algorithm."
8220		if (!node.parentNode) {
8221			return;
8222		}
8223
8224		// "If new value is null, abort this algorithm."
8225		if (newValue === null) {
8226			return;
8227		}
8228
8229		// "If node is an allowed child of "span":"
8230		if (isAllowedChild(node, "span")) {
8231			// "Reorder modifiable descendants of node's previousSibling."
8232			reorderModifiableDescendants(node.previousSibling, command, newValue, range);
8233
8234			// "Reorder modifiable descendants of node's nextSibling."
8235			reorderModifiableDescendants(node.nextSibling, command, newValue, range);
8236
8237			// "Wrap the one-node list consisting of node, with sibling criteria
8238			// returning true for a simple modifiable element whose specified
8239			// command value is equivalent to new value and whose effective command
8240			// value is loosely equivalent to new value and false otherwise, and
8241			// with new parent instructions returning null."
8242			wrap(
8243				[node],
8244				function (node) {
8245					return isSimpleModifiableElement(node) && areEquivalentValues(command, getSpecifiedCommandValue(node, command), newValue) && areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue);
8246				},
8247				function () {
8248					return null;
8249				},
8250				range
8251			);
8252		}
8253
8254		// "If the effective command value of command is loosely equivalent to new
8255		// value on node, abort this algorithm."
8256		if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
8257			return;
8258		}
8259
8260		// "If node is not an allowed child of "span":"
8261		if (!isAllowedChild(node, "span")) {
8262			// "Let children be all children of node, omitting any that are
8263			// Elements whose specified command value for command is neither null
8264			// nor equivalent to new value."
8265			for (i = 0; i < node.childNodes.length; i++) {
8266				if (node.childNodes[i].nodeType == Node.ELEMENT_NODE) {
8267					specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);
8268
8269					if (specifiedValue !== null && !areEquivalentValues(command, newValue, specifiedValue)) {
8270						continue;
8271					}
8272				}
8273				children.push(node.childNodes[i]);
8274			}
8275
8276			// "Force the value of each Node in children, with command and new
8277			// value as in this invocation of the algorithm."
8278			for (i = 0; i < children.length; i++) {
8279				forceValue(children[i], command, newValue, range);
8280			}
8281
8282			// "Abort this algorithm."
8283			return;
8284		}
8285
8286		// "If the effective command value of command is loosely equivalent to new
8287		// value on node, abort this algorithm."
8288		if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
8289			return;
8290		}
8291
8292		// "Let new parent be null."
8293		var newParent = null;
8294
8295		// "If the CSS styling flag is false:"
8296		if (!cssStylingFlag) {
8297			// "If command is "bold" and new value is "bold", let new parent be the
8298			// result of calling createElement("b") on the ownerDocument of node."
8299			if (command == "bold" && (newValue == "bold" || newValue == "700")) {
8300				newParent = node.ownerDocument.createElement("b");
8301			}
8302
8303			// "If command is "italic" and new value is "italic", let new parent be
8304			// the result of calling createElement("i") on the ownerDocument of
8305			// node."
8306			if (command == "italic" && newValue == "italic") {
8307				newParent = node.ownerDocument.createElement("i");
8308			}
8309
8310			// "If command is "strikethrough" and new value is "line-through", let
8311			// new parent be the result of calling createElement("s") on the
8312			// ownerDocument of node."
8313			if (command == "strikethrough" && newValue == "line-through") {
8314				newParent = node.ownerDocument.createElement("s");
8315			}
8316
8317			// "If command is "underline" and new value is "underline", let new
8318			// parent be the result of calling createElement("u") on the
8319			// ownerDocument of node."
8320			if (command == "underline" && newValue == "underline") {
8321				newParent = node.ownerDocument.createElement("u");
8322			}
8323
8324			// "If command is "foreColor", and new value is fully opaque with red,
8325			// green, and blue components in the range 0 to 255:"
8326			if (command == "forecolor" && parseSimpleColor(newValue)) {
8327				// "Let new parent be the result of calling createElement("span")
8328				// on the ownerDocument of node."
8329				// NOTE: modified this process to create span elements with style attributes
8330				// instead of oldschool font tags with color attributes
8331				newParent = node.ownerDocument.createElement("span");
8332
8333				// "If new value is an extended color keyword, set the color
8334				// attribute of new parent to new value."
8335				//
8336				// "Otherwise, set the color attribute of new parent to the result
8337				// of applying the rules for serializing simple color values to new
8338				// value (interpreted as a simple color)."
8339				jQuery(newParent).css('color', parseSimpleColor(newValue));
8340			}
8341
8342			// "If command is "fontName", let new parent be the result of calling
8343			// createElement("font") on the ownerDocument of node, then set the
8344			// face attribute of new parent to new value."
8345			if (command == "fontname") {
8346				newParent = node.ownerDocument.createElement("font");
8347				newParent.face = newValue;
8348			}
8349		}
8350
8351		// "If command is "createLink" or "unlink":"
8352		if (command == "createlink" || command == "unlink") {
8353			// "Let new parent be the result of calling createElement("a") on the
8354			// ownerDocument of node."
8355			newParent = node.ownerDocument.createElement("a");
8356
8357			// "Set the href attribute of new parent to new value."
8358			newParent.setAttribute("href", newValue);
8359
8360			// "Let ancestor be node's parent."
8361			var ancestor = node.parentNode;
8362
8363			// "While ancestor is not null:"
8364			while (ancestor) {
8365				// "If ancestor is an a, set the tag name of ancestor to "span",
8366				// and let ancestor be the result."
8367				if (isNamedHtmlElement(ancestor, 'A')) {
8368					ancestor = setTagName(ancestor, "span", range);
8369				}
8370
8371				// "Set ancestor to its parent."
8372				ancestor = ancestor.parentNode;
8373			}
8374		}
8375
8376		// "If command is "fontSize"; and new value is one of "xx-small", "small",
8377		// "medium", "large", "x-large", "xx-large", or "xxx-large"; and either the
8378		// CSS styling flag is false, or new value is "xxx-large": let new parent
8379		// be the result of calling createElement("font") on the ownerDocument of
8380		// node, then set the size attribute of new parent to the number from the
8381		// following table based on new value: [table omitted]"
8382		if (command == "fontsize" && jQuery.inArray(newValue, ["xx-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]) != -1 && (!cssStylingFlag || newValue == "xxx-large")) {
8383			newParent = node.ownerDocument.createElement("font");
8384			newParent.size = cssSizeToLegacy(newValue);
8385		}
8386
8387		// "If command is "subscript" or "superscript" and new value is
8388		// "subscript", let new parent be the result of calling
8389		// createElement("sub") on the ownerDocument of node."
8390		if ((command == "subscript" || command == "superscript") && newValue == "subscript") {
8391			newParent = node.ownerDocument.createElement("sub");
8392		}
8393
8394		// "If command is "subscript" or "superscript" and new value is
8395		// "superscript", let new parent be the result of calling
8396		// createElement("sup") on the ownerDocument of node."
8397		if ((command == "subscript" || command == "superscript") && newValue == "superscript") {
8398			newParent = node.ownerDocument.createElement("sup");
8399		}
8400
8401		// "If new parent is null, let new parent be the result of calling
8402		// createElement("span") on the ownerDocument of node."
8403		if (!newParent) {
8404			newParent = node.ownerDocument.createElement("span");
8405		}
8406
8407		// "Insert new parent in node's parent before node."
8408		node.parentNode.insertBefore(newParent, node);
8409
8410		// "If the effective command value of command for new parent is not loosely
8411		// equivalent to new value, and the relevant CSS property for command is
8412		// not null, set that CSS property of new parent to new value (if the new
8413		// value would be valid)."
8414		var property = commands[command].relevantCssProperty;
8415		if (property !== null && !areLooselyEquivalentValues(command, getEffectiveCommandValue(newParent, command), newValue)) {
8416			newParent.style[property] = newValue;
8417		}
8418
8419		// "If command is "strikethrough", and new value is "line-through", and the
8420		// effective command value of "strikethrough" for new parent is not
8421		// "line-through", set the "text-decoration" property of new parent to
8422		// "line-through"."
8423		if (command == "strikethrough" && newValue == "line-through" && getEffectiveCommandValue(newParent, "strikethrough") != "line-through") {
8424			newParent.style.textDecoration = "line-through";
8425		}
8426
8427		// "If command is "underline", and new value is "underline", and the
8428		// effective command value of "underline" for new parent is not
8429		// "underline", set the "text-decoration" property of new parent to
8430		// "underline"."
8431		if (command == "underline" && newValue == "underline" && getEffectiveCommandValue(newParent, "underline") != "underline") {
8432			newParent.style.textDecoration = "underline";
8433		}
8434
8435		// "Append node to new parent as its last child, preserving ranges."
8436		movePreservingRanges(node, newParent, newParent.childNodes.length, range);
8437
8438		// "If node is an Element and the effective command value of command for
8439		// node is not loosely equivalent to new value:"
8440		if (node.nodeType == Node.ELEMENT_NODE && !areEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
8441			// "Insert node into the parent of new parent before new parent,
8442			// preserving ranges."
8443			movePreservingRanges(node, newParent.parentNode, getIndexInParent(newParent), range);
8444
8445			// "Remove new parent from its parent."
8446			newParent.parentNode.removeChild(newParent);
8447
8448			// "Let children be all children of node, omitting any that are
8449			// Elements whose specified command value for command is neither null
8450			// nor equivalent to new value."
8451			children = [];
8452			for (i = 0; i < node.childNodes.length; i++) {
8453				if (node.childNodes[i].nodeType == Node.ELEMENT_NODE) {
8454					specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);
8455
8456					if (specifiedValue !== null && !areEquivalentValues(command, newValue, specifiedValue)) {
8457						continue;
8458					}
8459				}
8460				children.push(node.childNodes[i]);
8461			}
8462
8463			// "Force the value of each Node in children, with command and new
8464			// value as in this invocation of the algorithm."
8465			for (i = 0; i < children.length; i++) {
8466				forceValue(children[i], command, newValue, range);
8467			}
8468		}
8469	}
8470
8471	//@}
8472	///// Pushing down values /////
8473	//@{
8474
8475	function pushDownValues(node, command, newValue, range) {
8476		// "If node's parent is not an Element, abort this algorithm."
8477		if (!node.parentNode || node.parentNode.nodeType != Node.ELEMENT_NODE) {
8478			return;
8479		}
8480
8481		// "If the effective command value of command is loosely equivalent to new
8482		// value on node, abort this algorithm."
8483		if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
8484			return;
8485		}
8486
8487		// "Let current ancestor be node's parent."
8488		var currentAncestor = node.parentNode;
8489
8490		// "Let ancestor list be a list of Nodes, initially empty."
8491		var ancestorList = [];
8492
8493		// "While current ancestor is an editable Element and the effective command
8494		// value of command is not loosely equivalent to new value on it, append
8495		// current ancestor to ancestor list, then set current ancestor to its
8496		// parent."
8497		while (isEditable(currentAncestor) && currentAncestor.nodeType == Node.ELEMENT_NODE && !areLooselyEquivalentValues(command, getEffectiveCommandValue(currentAncestor, command), newValue)) {
8498			ancestorList.push(currentAncestor);
8499			currentAncestor = currentAncestor.parentNode;
8500		}
8501
8502		// "If ancestor list is empty, abort this algorithm."
8503		if (!ancestorList.length) {
8504			return;
8505		}
8506
8507		// "Let propagated value be the specified command value of command on the
8508		// last member of ancestor list."
8509		var propagatedValue = getSpecifiedCommandValue(ancestorList[ancestorList.length - 1], command);
8510
8511		// "If propagated value is null and is not equal to new value, abort this
8512		// algorithm."
8513		if (propagatedValue === null && propagatedValue != newValue) {
8514			return;
8515		}
8516
8517		// "If the effective command value for the parent of the last member of
8518		// ancestor list is not loosely equivalent to new value, and new value is
8519		// not null, abort this algorithm."
8520		if (newValue !== null && !areLooselyEquivalentValues(command, getEffectiveCommandValue(ancestorList[ancestorList.length - 1].parentNode, command), newValue)) {
8521			return;
8522		}
8523
8524		// "While ancestor list is not empty:"
8525		while (ancestorList.length) {
8526			// "Let current ancestor be the last member of ancestor list."
8527			// "Remove the last member from ancestor list."
8528			currentAncestor = ancestorList.pop();
8529
8530			// "If the specified command value of current ancestor for command is
8531			// not null, set propagated value to that value."
8532			if (getSpecifiedCommandValue(currentAncestor, command) !== null) {
8533				propagatedValue = getSpecifiedCommandValue(currentAncestor, command);
8534			}
8535
8536			// "Let children be the children of current ancestor."
8537			var children = Array.prototype.slice.call(toArray(currentAncestor.childNodes));
8538
8539			// "If the specified command value of current ancestor for command is
8540			// not null, clear the value of current ancestor."
8541			if (getSpecifiedCommandValue(currentAncestor, command) !== null) {
8542				clearValue(currentAncestor, command, range);
8543			}
8544
8545			// "For every child in children:"
8546			var i;
8547			for (i = 0; i < children.length; i++) {
8548				var child = children[i];
8549
8550				// "If child is node, continue with the next child."
8551				if (child == node) {
8552					continue;
8553				}
8554
8555				// "If child is an Element whose specified command value for
8556				// command is neither null nor equivalent to propagated value,
8557				// continue with the next child."
8558				if (child.nodeType == Node.ELEMENT_NODE && getSpecifiedCommandValue(child, c
8558ommand) !== null && !areEquivalentValues(command, propagatedValue, getSpecifiedCommandValue(child, command))) {
8559					continue;
8560				}
8561
8562				// "If child is the last member of ancestor list, continue with the
8563				// next child."
8564				if (child == ancestorList[ancestorList.length - 1]) {
8565					continue;
8566				}
8567
8568				// "Force the value of child, with command as in this algorithm
8569				// and new value equal to propagated value."
8570				forceValue(child, command, propagatedValue, range);
8571			}
8572		}
8573	}
8574
8575	function restoreValues(values, range) {
8576		// "For each (node, command, value) triple in values:"
8577		$_(values).forEach(function (triple) {
8578			var node = triple[0];
8579			var command = triple[1];
8580			var value = triple[2];
8581
8582			// "Let ancestor equal node."
8583			var ancestor = node;
8584
8585			// "If ancestor is not an Element, set it to its parent."
8586			if (!ancestor || ancestor.nodeType != Node.ELEMENT_NODE) {
8587				ancestor = ancestor.parentNode;
8588			}
8589
8590			// "While ancestor is an Element and its specified command value for
8591			// command is null, set it to its parent."
8592			while (ancestor && ancestor.nodeType == Node.ELEMENT_NODE && getSpecifiedCommandValue(ancestor, command) === null) {
8593				ancestor = ancestor.parentNode;
8594			}
8595
8596			// "If value is null and ancestor is an Element, push down values on
8597			// node for command, with new value null."
8598			if (value === null && ancestor && ancestor.nodeType == Node.ELEMENT_NODE) {
8599				pushDownValues(node, command, null, range);
8600
8601				// "Otherwise, if ancestor is an Element and its specified command
8602				// value for command is not equivalent to value, or if ancestor is not
8603				// an Element and value is not null, force the value of command to
8604				// value on node."
8605			} else if ((ancestor && ancestor.nodeType == Node.ELEMENT_NODE && !areEquivalentValues(command, getSpecifiedCommandValue(ancestor, command), value)) || ((!ancestor || ancestor.nodeType != Node.ELEMENT_NODE) && value !== null)) {
8606				forceValue(node, command, value, range);
8607			}
8608		});
8609	}
8610
8611	//@}
8612	///// Setting the selection's value /////
8613	//@{
8614
8615	function setSelectionValue(command, newValue, range) {
8616
8617		// Use current selected range if no range passed
8618		range = range || getActiveRange();
8619
8620		// "If there is no editable text node effectively contained in the active
8621		// range:"
8622		
8623		if (! $_(getAllEffectivelyContainedNodes(range)).filter(function (node) { 
8624				
8625				return node.nodeType == Node.TEXT_NODE; 
8626
8627			}, true).some(isEditable)
8628		) {
8629			// "If command has inline command activated values, set the state
8630			// override to true if new value is among them and false if it's not."
8631			if (commands[command].hasOwnProperty("inlineCommandActivatedValues")) {
8632				setStateOverride(
8633					command,
8634					$_(commands[command].inlineCommandActivatedValues).indexOf(newValue) != -1,
8635					range
8636				);
8637			}
8638
8639			// "If command is "subscript", unset the state override for
8640			// "superscript"."
8641			if (command == "subscript") {
8642				unsetStateOverride("superscript", range);
8643			}
8644
8645			// "If command is "superscript", unset the state override for
8646			// "subscript"."
8647			if (command == "superscript") {
8648				unsetStateOverride("subscript", range);
8649			}
8650
8651			// "If new value is null, unset the value override (if any)."
8652			if (newValue === null) {
8653				unsetValueOverride(command, range);
8654
8655				// "Otherwise, if command has a value specified, set the value override
8656				// to new value."
8657			} else if (commands[command].hasOwnProperty("value")) {
8658				setValueOverride(command, newValue, range);
8659			}
8660
8661			// "Abort these steps."
8662			return;
8663		}
8664
8665		// "If the active range's start node is an editable Text node, and its
8666		// start offset is neither zero nor its start node's length, call
8667		// splitText() on the active range's start node, with argument equal to the
8668		// active range's start offset. Then set the active range's start node to
8669		// the result, and its start offset to zero."
8670		if (isEditable(range.startContainer) && range.startContainer.nodeType == Node.TEXT_NODE && range.startOffset != 0 && range.startOffset != getNodeLength(range.startContainer)) {
8671			// Account for browsers not following range mutation rules
8672			var newNode = splitText(range.startContainer, range.startOffset);
8673			var newActiveRange = _core.rangy.createRange();
8674			if (range.startContainer == range.endContainer) {
8675				var newEndOffset = range.endOffset - range.startOffset;
8676				newActiveRange.setEnd(newNode, newEndOffset);
8677				range.setEnd(newNode, newEndOffset);
8678			}
8679			newActiveRange.setStart(newNode, 0);
8680			_core.rangy.getSelection().removeAllRanges();
8681			_core.rangy.getSelection().addRange(newActiveRange);
8682
8683			range.setStart(newNode, 0);
8684		}
8685
8686		// "If the active range's end node is an editable Text node, and its end
8687		// offset is neither zero nor its end node's length, call splitText() on
8688		// the active range's end node, with argument equal to the active range's
8689		// end offset."
8690		if (
8691			isEditable(range.endContainer) && 
8692			range.endContainer.nodeType == Node.TEXT_NODE && range.endOffset != 0 && 
8693			range.endOffset != getNodeLength(range.endContainer)
8694		) {
8695			// IE seems to mutate the range incorrectly here, so we need correction
8696			// here as well.  The active range will be temporarily in orphaned
8697			// nodes, so calling getActiveRange() after splitText() but before
8698			// fixing the range will throw an exception.
8699			// TODO: check if this is still neccessary
8700			var activeRange = range;
8701			var newStart = [activeRange.startContainer, activeRange.startOffset];
8702			var newEnd = [activeRange.endContainer, activeRange.endOffset];
8703			splitText(activeRange.endContainer, activeRange.endOffset);
8704			activeRange.setStart(newStart[0], newStart[1]);
8705			activeRange.setEnd(newEnd[0], newEnd[1]);
8706
8707			_core.rangy.getSelection().removeAllRanges();
8708			_core.rangy.getSelection().addRange(activeRange);
8709		}
8710
8711		// "Let element list be all editable Elements effectively contained in the
8712		// active range.
8713		//
8714		// "For each element in element list, clear the value of element."
8715		$_(getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
8716			return isEditable(node) && node.nodeType == Node.ELEMENT_NODE;
8717		})).forEach(function (element) {
8718			clearValue(element, command, range);
8719		});
8720
8721		// "Let node list be all editable nodes effectively contained in the active
8722		// range.
8723		//
8724		// "For each node in node list:"
8725		$_(getAllEffectivelyContainedNodes(range, isEditable)).forEach(function (node) {
8726			// "Push down values on node."
8727			pushDownValues(node, command, newValue, range);
8728
8729			// "Force the value of node."
8730			forceValue(node, command, newValue, range);
8731		});
8732	}
8733
8734	/**
8735	 * attempt to retrieve a block like a table or an Aloha Block
8736	 * which is located one step right of the current caret position.
8737	 * If an appropriate element is found it will be returned or
8738	 * false otherwise
8739	 *
8740	 * @param {element} node current node we're in
8741	 * @param {number} offset current offset within that node
8742	 *
8743	 * @return the dom node if found or false if no appropriate
8744	 * element was found
8745	 */
8746	function getBlockAtNextPosition(node, offset) {
8747		var i;
8748
8749		// if we're inside a text node we first have to check
8750		// if there is nothing but tabs, newlines or the like
8751		// after our current cursor position
8752		if (node.nodeType === Node.TEXT_NODE && offset < node.length) {
8753			for (i = offset; i < node.length; i++) {
8754				if ((node.data.charAt(i) !== '\t' && node.data.charAt(i) !== '\r' && node.data.charAt(i) !== '\n') || node.data.charCodeAt(i) === 160) { // &nbsp;
8755					// this is a character that has to be deleted first
8756					return false;
8757				}
8758			}
8759		}
8760
8761		// try the most simple approach first: the next sibling
8762		// is a table
8763		if (node.nextSibling && node.nextSibling.className && node.nextSibling.className.indexOf("aloha-table-wrapper") >= 0) {
8764			return node.nextSibling;
8765		}
8766
8767		// since we got only ignorable whitespace here determine if
8768		// our nodes parents next sibling is a table
8769		if (node.parentNode && node.parentNode.nextSibling && node.parentNode.nextSibling.className && node.parentNode.nextSibling.className.indexOf("aloha-table-wrapper") >= 0) {
8770			return node.parentNode.nextSibling;
8771		}
8772
8773		// our parents nextsibling is a pure whitespace node such as
8774		// generated by sourcecode indentation so we'll check for
8775		// the next next sibling
8776		if (node.parentNode && node.parentNode.nextSibling && isWhitespaceNode(node.parentNode.nextSibling) && node.parentNode.nextSibling.nextSibling && node.parentNode.nextSibling.nextSibling.className && node.parentNode.nextSibling.nextSibling.className.indexOf("aloha-table-wrapper") >= 0) {
8777			return node.parentNode.nextSibling.nextSibling;
8778		}
8779
8780		// Note: the search above works for tables, since they cannot be
8781		// nested deeply in paragraphs and other formatting tags. If this code
8782		// is extended to work also for other blocks, the search probably needs to be adapted
8783	}
8784
8785	/**
8786	 * Attempt to retrieve a block like a table or an Aloha Block
8787	 * which is located right before the current position.
8788	 * If an appropriate element is found, it will be returned or
8789	 * false otherwise
8790	 *
8791	 * @param {element} node current node
8792	 * @param {offset} offset current offset
8793	 *
8794	 * @return dom node of found or false if no appropriate
8795	 * element was found
8796	 */
8797	function getBlockAtPreviousPosition(node, offset) {
8798		var i;
8799
8800		if (node.nodeType === Node.TEXT_NODE && offset > 0) {
8801			for (i = offset - 1; i >= 0; i--) {
8802				if ((node.data.charAt(i) !== '\t' && node.data.charAt(i) !== '\r' && node.data.charAt(i) !== '\n') || node.data.charCodeAt(i) === 160) { // &nbsp;
8803					// this is a character that has to be deleted first
8804					return false;
8805				}
8806			}
8807		}
8808
8809		// try the previous sibling
8810		if (node.previousSibling && node.previousSibling.className && node.previousSibling.className.indexOf("aloha-table-wrapper") >= 0) {
8811			return node.previousSibling;
8812		}
8813
8814		// try the parent's previous sibling
8815		if (node.parentNode && node.parentNode.previousSibling && node.parentNode.previousSibling.className && node.parentNode.previousSibling.className.indexOf("aloha-table-wrapper") >= 0) {
8816			return node.parentNode.previousSibling;
8817		}
8818
8819		// the parent's previous sibling might be a whitespace node
8820		if (node.parentNode && node.parentNode.previousSibling && isWhitespaceNode(node.parentNode.previousSibling) && node.parentNode.previousSibling.previousSibling && node.parentNode.previousSibling.previousSibling.className && node.parentNode.previousSibling.previousSibling.className.indexOf('aloha-table-wrapper') >= 0) {
8821			return node.parentNode.previousSibling.previousSibling;
8822		}
8823
8824		// Note: the search above works for tables, since they cannot be
8825		// nested deeply in paragraphs and other formatting tags. If this code
8826		// is extended to work also for other blocks, the search probably needs to be adapted
8827
8828		return false;
8829	}
8830
8831	// "A boundary point (node, offset) is a block start point if either node's
8832	// parent is null and offset is zero; or node has a child with index offset −
8833	// 1, and that child is either a visible block node or a visible br."
8834	function isBlockStartPoint(node, offset) {
8835		return (!node.parentNode && offset == 0) || (0 <= offset - 1 && offset - 1 < node.childNodes.length && isVisible(node.childNodes[offset - 1]) && (isBlockNode(node.childNodes[offset - 1]) || isNamedHtmlElement(node.childNodes[offset - 1], "br")));
8836	}
8837
8838	// "A boundary point (node, offset) is a block end point if either node's
8839	// parent is null and offset is node's length; or node has a child with index
8840	// offset, and that child is a visible block node."
8841	function isBlockEndPoint(node, offset) {
8842		return (!node.parentNode && offset == getNodeLength(node)) || (offset < node.childNodes.length && isVisible(node.childNodes[offset]) && isBlockNode(node.childNodes[offset]));
8843	}
8844
8845	// "A boundary point is a block boundary point if it is either a block start
8846	// point or a block end point."
8847	function isBlockBoundaryPoint(node, offset) {
8848		return isBlockStartPoint(node, offset) || isBlockEndPoint(node, offset);
8849	}
8850
8851	function followsLineBreak(node) {
8852		// "Let offset be zero."
8853		var offset = 0;
8854
8855		// "While (node, offset) is not a block boundary point:"
8856		while (!isBlockBoundaryPoint(node, offset)) {
8857			// "If node has a visible child with index offset minus one, return
8858			// false."
8859			if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isVisible(node.childNodes[offset - 1])) {
8860				return false;
8861			}
8862
8863			// "If offset is zero or node has no children, set offset to node's
8864			// index, then set node to its parent."
8865			if (offset == 0 || !node.hasChildNodes()) {
8866				offset = getIndexInParent(node);
8867				node = node.parentNode;
8868
8869				// "Otherwise, set node to its child with index offset minus one, then
8870				// set offset to node's length."
8871			} else {
8872				node = node.childNodes[offset - 1];
8873				offset = getNodeLength(node);
8874			}
8875		}
8876
8877		// "Return true."
8878		return true;
8879	}
8880
8881	function precedesLineBreak(node) {
8882		// "Let offset be node's length."
8883		var offset = getNodeLength(node);
8884
8885		// "While (node, offset) is not a block boundary point:"
8886		while (!isBlockBoundaryPoint(node, offset)) {
8887			// "If node has a visible child with index offset, return false."
8888			if (offset < node.childNodes.length && isVisible(node.childNodes[offset])) {
8889				return false;
8890			}
8891
8892			// "If offset is node's length or node has no children, set offset to
8893			// one plus node's index, then set node to its parent."
8894			if (offset == getNodeLength(node) || !node.hasChildNodes()) {
8895				offset = 1 + getIndexInParent(node);
8896				node = node.parentNode;
8897
8898				// "Otherwise, set node to its child with index offset and set offset
8899				// to zero."
8900			} else {
8901				node = node.childNodes[offset];
8902				offset = 0;
8903			}
8904		}
8905
8906		// "Return true."
8907		return true;
8908	}
8909
8910	//@}
8911	///// Splitting a node list's parent /////
8912	//@{
8913
8914	function splitParent(nodeList, range) {
8915		var i;
8916
8917		// "Let original parent be the parent of the first member of node list."
8918		var originalParent = nodeList[0].parentNode;
8919
8920		// "If original parent is not editable or its parent is null, do nothing
8921		// and abort these steps."
8922		if (!isEditable(originalParent) || !originalParent.parentNode) {
8923			return;
8924		}
8925
8926		// "If the first child of original parent is in node list, remove
8927		// extraneous line breaks before original parent."
8928		if (jQuery.inArray(originalParent.firstChild, nodeList) != -1) {
8929			removeExtraneousLineBreaksBefore(originalParent);
8930		}
8931
8932		var firstChildInNodeList = jQuery.inArray(originalParent.firstChild, nodeList) != -1;
8933		var lastChildInNodeList = jQuery.inArray(originalParent.lastChild, nodeList) != -1;
8934
8935		// "If the first child of original parent is in node list, and original
8936		// parent follows a line break, set follows line break to true. Otherwise,
8937		// set follows line break to false."
8938		var followsLineBreak_ = firstChildInNodeList && followsLineBreak(originalParent);
8939
8940		// "If the last child of original parent is in node list, and original
8941		// parent precedes a line break, set precedes line break to true.
8942		// Otherwise, set precedes line break to false."
8943		var precedesLineBreak_ = lastChildInNodeList && precedesLineBreak(originalParent);
8944
8945		// "If the first child of original parent is not in node list, but its last
8946		// child is:"
8947		if (!firstChildInNodeList && lastChildInNodeList) {
8948			// "For each node in node list, in reverse order, insert node into the
8949			// parent of original parent immediately after original parent,
8950			// preserving ranges."
8951			for (i = nodeList.length - 1; i >= 0; i--) {
8952				movePreservingRanges(nodeList[i], originalParent.parentNode, 1 + getIndexInParent(originalParent), range);
8953			}
8954
8955			// "If precedes line break is true, and the last member of node list
8956			// does not precede a line break, call createElement("br") on the
8957			// context object and insert the result immediately after the last
8958			// member of node list."
8959			if (precedesLineBreak_ && !precedesLineBreak(nodeList[nodeList.length - 1])) {
8960				nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
8961			}
8962
8963			// "Remove extraneous line breaks at the end of original parent."
8964			removeExtraneousLineBreaksAtTheEndOf(originalParent);
8965
8966			// "Abort these steps."
8967			return;
8968		}
8969
8970		// "If the first child of original parent is not in node list:"
8971		if (!firstChildInNodeList) {
8972			// "Let cloned parent be the result of calling cloneNode(false) on
8973			// original parent."
8974			var clonedParent = originalParent.cloneNode(false);
8975
8976			// "If original parent has an id attribute, unset it."
8977			originalParent.removeAttribute("id");
8978
8979			// "Insert cloned parent into the parent of original parent immediately
8980			// before original parent."
8981			originalParent.parentNode.insertBefore(clonedParent, originalParent);
8982
8983			// "While the previousSibling of the first member of node list is not
8984			// null, append the first child of original parent as the last child of
8985			// cloned parent, preserving ranges."
8986			while (nodeList[0].previousSibling) {
8987				movePreservingRanges(originalParent.firstChild, clonedParent, clonedParent.childNodes.length, range);
8988			}
8989		}
8990
8991		// "For each node in node list, insert node into the parent of original
8992		// parent immediately before original parent, preserving ranges."
8993		for (i = 0; i < nodeList.length; i++) {
8994			movePreservingRanges(nodeList[i], originalParent.parentNode, getIndexInParent(originalParent), range);
8995		}
8996
8997		// "If follows line break is true, and the first member of node list does
8998		// not follow a line break, call createElement("br") on the context object
8999		// and insert the result immediately before the first member of node list."
9000		if (followsLineBreak_ && !followsLineBreak(nodeList[0])) {
9001			nodeList[0].parentNode.insertBefore(document.createElement("br"), nodeList[0]);
9002		}
9003
9004		// "If the last member of node list is an inline node other than a br, and
9005		// the first child of original parent is a br, and original parent is not
9006		// an inline node, remove the first child of original parent from original
9007		// parent."
9008		if (isInlineNode(nodeList[nodeList.length - 1]) && !isNamedHtmlElement(nodeList[nodeList.length - 1], "br") && isNamedHtmlElement(originalParent.firstChild, "br") && !isInlineNode(originalParent)) {
9009			originalParent.removeChild(originalParent.firstChild);
9010		}
9011
9012		// "If original parent has no children:"
9013		if (!originalParent.hasChildNodes()) {
9014			// if the current range is collapsed and at the end of the originalParent.parentNode
9015			// the offset will not be available anymore after the next step (remove child)
9016			// that's why we need to fix the range to prevent a bogus offset
9017			if (originalParent.parentNode === range.startContainer && originalParent.parentNode === range.endContainer && range.startContainer === range.endContainer && range.startOffset === range.endOffset && originalParent.parentNode.childNodes.length === range.startOffset) {
9018				range.startOffset = originalParent.parentNode.childNodes.length - 1;
9019				range.endOffset = range.startOffset;
9020			}
9021
9022			// "Remove original parent from its parent."
9023			originalParent.parentNode.removeChild(originalParent);
9024
9025			// "If precedes line break is true, and the last member of node list
9026			// does not precede a line break, call createElement("br") on the
9027			// context object and insert the result immediately after the last
9028			// member of node list."
9029			if (precedesLineBreak_ && !precedesLineBreak(nodeList[nodeList.length - 1])) {
9030				nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
9031			}
9032
9033			// "Otherwise, remove extraneous line breaks before original parent."
9034		} else {
9035			removeExtraneousLineBreaksBefore(originalParent);
9036		}
9037
9038		// "If node list's last member's nextSibling is null, but its parent is not
9039		// null, remove extraneous line breaks at the end of node list's last
9040		// member's parent."
9041		if (!nodeList[nodeList.length - 1].nextSibling && nodeList[nodeList.length - 1].parentNode) {
9042			removeExtraneousLineBreaksAtTheEndOf(nodeList[nodeList.length - 1].parentNode);
9043		}
9044	}
9045
9046	//@}
9047	///// The backColor command /////
9048	//@{
9049	commands.backcolor = {
9050		// Copy-pasted, same as hiliteColor
9051		action: function (value, range) {
9052			// Action is further copy-pasted, same as foreColor
9053
9054			// "If value is not a valid CSS color, prepend "#" to it."
9055			//
9056			// "If value is still not a valid CSS color, or if it is currentColor,
9057			// abort these steps and do nothing."
9058			//
9059			// Cheap hack for testing, no attempt to be comprehensive.
9060			if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
9061				value = "#" + value;
9062			}
9063			if (!/^(rgba?|hsla?)\(.*\)$/.test(value) && !parseSimpleColor(value) && value.toLowerCase() != "transparent") {
9064				return;
9065			}
9066
9067			// "Set the selection's value to value."
9068			setSelectionValue("backcolor", value, range);
9069		},
9070		standardInlineValueCommand: true,
9071		relevantCssProperty: "backgroundColor",
9072		equivalentValues: function (val1, val2) {
9073			// "Either both strings are valid CSS colors and have the same red,
9074			// green, blue, and alpha components, or neither string is a valid CSS
9075			// color."
9076			return normalizeColor(val1) === normalizeColor(val2);
9077		}
9078	};
9079
9080	//@}
9081	///// The bold command /////
9082	//@{
9083	commands.bold = {
9084		action: function (value, range) {
9085			// "If queryCommandState("bold") returns true, set the selection's
9086			// value to "normal". Otherwise set the selection's value to "bold"."
9087			if (myQueryCommandState("bold", range)) {
9088
9089				//setSelectionValue("bold", "normal", range);
9090
9091				setSelectionValue("bold", null, range);
9092
9093			} else {
9094
9095				setSelectionValue("bold", "bold", range);
9096
9097			}
9098		},
9099		inlineCommandActivatedValues: ["bold", "600", "700", "800", "900"],
9100		relevantCssProperty: "fontWeight",
9101		equivalentValues: function (val1, val2) {
9102			// "Either the two strings are equal, or one is "bold" and the other is
9103			// "700", or one is "normal" and the other is "400"."
9104			return val1 == val2 || (val1 == "bold" && val2 == "700") || (val1 == "700" && val2 == "bold") || (val1 == "normal" && val2 == "400") || (val1 == "400" && val2 == "normal");
9105		}
9106	};
9107
9108	//@}
9109	///// The createLink command /////
9110	//@{
9111	commands.createlink = {
9112		action: function (value, range) {
9113			// "If value is the empty string, abort these steps and do nothing."
9114			if (value === "") {
9115				return;
9116			}
9117
9118			// "For each editable a element that has an href attribute and is an
9119			// ancestor of some node effectively contained in the active range, set
9120			// that a element's href attribute to value."
9121			//
9122			// TODO: We don't actually do this in tree order, not that it matters
9123			// unless you're spying with mutation events.
9124			$_(getAllEffectivelyContainedNodes(getActiveRange())).forEach(function (node) {
9125				$_(getAncestors(node)).forEach(function (ancestor) {
9126					if (isEditable(ancestor) && isNamedHtmlElement(ancestor, 'a') && hasAttribute(ancestor, "href")) {
9127						ancestor.setAttribute("href", value);
9128					}
9129				});
9130			});
9131
9132			// "Set the selection's value to value."
9133			setSelectionValue("createlink", value, range);
9134		},
9135		standardInlineValueCommand: true
9136	};
9137
9138	//@}
9139	///// The fontName command /////
9140	//@{
9141	commands.fontname = {
9142		action: function (value, range) {
9143			// "Set the selection's value to value."
9144			setSelectionValue("fontname", value, range);
9145		},
9146		standardInlineValueCommand: true,
9147		relevantCssProperty: "fontFamily"
9148	};
9149
9150	//@}
9151	///// The fontSize command /////
9152	//@{
9153
9154	commands.fontsize = {
9155		action: function (value, range) {
9156			// "If value is the empty string, abort these steps and do nothing."
9157			if (value === "") {
9158				return;
9159			}
9160
9161			value = normalizeFontSize(value);
9162
9163			// "If value is not one of the strings "xx-small", "x-small", "small",
9164			// "medium", "large", "x-large", "xx-large", "xxx-large", and is not a
9165			// valid CSS absolute length, then abort these steps and do nothing."
9166			//
9167			// More cheap hacks to skip valid CSS absolute length checks.
9168			if (jQuery.inArray(value, ["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]) == -1 && !/^[0-9]+(\.[0-9]+)?(cm|mm|in|pt|pc)$/.test(value)) {
9169				return;
9170			}
9171
9172			// "Set the selection's value to value."
9173			setSelectionValue("fontsize", value, range);
9174		},
9175		indeterm: function () {
9176			// "True if among editable Text nodes that are effectively contained in
9177			// the active range, there are two that have distinct effective command
9178			// values.  Otherwise false."
9179			return $_(getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
9180				return isEditable(node) && node.nodeType == Node.TEXT_NODE;
9181			})).map(function (node) {
9182				return getEffectiveCommandValue(node, "fontsize");
9183			}, true).filter(function (value, i, arr) {
9184				return $_(arr.slice(0, i)).indexOf(value) == -1;
9185			}).length >= 2;
9186		},
9187		value: function (range) {
9188			// "Let pixel size be the effective command value of the first editable
9189			// Text node that is effectively contained in the active range, or if
9190			// there is no such node, the effective command value of the active
9191			// range's start node, in either case interpreted as a number of
9192			// pixels."
9193			var node = getAllEffectivelyContainedNodes(range, function (node) {
9194				return isEditable(node) && node.nodeType == Node.TEXT_NODE;
9195			})[0];
9196			if (node === undefined) {
9197				node = range.startContainer;
9198			}
9199			var pixelSize = getEffectiveCommandValue(node, "fontsize");
9200
9201			// "Return the legacy font size for pixel size."
9202			return getLegacyFontSize(pixelSize);
9203		},
9204		relevantCssProperty: "fontSize"
9205	};
9206
9207	//@}
9208	///// The foreColor command /////
9209	//@{
9210	commands.forecolor = {
9211		action: function (value, range) {
9212			// Copy-pasted, same as backColor and hiliteColor
9213
9214			// "If value is not a valid CSS color, prepend "#" to it."
9215			//
9216			// "If value is still not a valid CSS color, or if it is currentColor,
9217			// abort these steps and do nothing."
9218			//
9219			// Cheap hack for testing, no attempt to be comprehensive.
9220			if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
9221				value = "#" + value;
9222			}
9223			if (!/^(rgba?|hsla?)\(.*\)$/.test(value) && !parseSimpleColor(value) && value.toLowerCase() != "transparent") {
9224				return;
9225			}
9226
9227			// "Set the selection's value to value."
9228			setSelectionValue("forecolor", value, range);
9229		},
9230		standardInlineValueCommand: true,
9231		relevantCssProperty: "color",
9232		equivalentValues: function (val1, val2) {
9233			// "Either both strings are valid CSS colors and have the same red,
9234			// green, blue, and alpha components, or neither string is a valid CSS
9235			// color."
9236			return normalizeColor(val1) === normalizeColor(val2);
9237		}
9238	};
9239
9240	//@}
9241	///// The hiliteColor command /////
9242	//@{
9243	commands.hilitecolor = {
9244		// Copy-pasted, same as backColor
9245		action: function (value, range) {
9246			// Action is further copy-pasted, same as foreColor
9247
9248			// "If value is not a valid CSS color, prepend "#" to it."
9249			//
9250			// "If value is still not a valid CSS color, or if it is currentColor,
9251			// abort these steps and do nothing."
9252			//
9253			// Cheap hack for testing, no attempt to be comprehensive.
9254			if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
9255				value = "#" + value;
9256			}
9257			if (!/^(rgba?|hsla?)\(.*\)$/.test(value) && !parseSimpleColor(value) && value.toLowerCase() != "transparent") {
9258				return;
9259			}
9260
9261			// "Set the selection's value to value."
9262			setSelectionValue("hilitecolor", value, range);
9263		},
9264		indeterm: function () {
9265			// "True if among editable Text nodes that are effectively contained in
9266			// the active range, there are two that have distinct effective command
9267			// values.  Otherwise false."
9268			return $_(getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
9269				return isEditable(node) && node.nodeType == Node.TEXT_NODE;
9270			})).map(function (node) {
9271				return getEffectiveCommandValue(node, "hilitecolor");
9272			}, true).filter(function (value, i, arr) {
9273				return $_(arr.slice(0, i)).indexOf(value) == -1;
9274			}).length >= 2;
9275		},
9276		standardInlineValueCommand: true,
9277		relevantCssProperty: "backgroundColor",
9278		equivalentValues: function (val1, val2) {
9279			// "Either both strings are valid CSS colors and have the same red,
9280			// green, blue, and alpha components, or neither string is a valid CSS
9281			// color."
9282			return normalizeColor(val1) === normalizeColor(val2);
9283		}
9284	};
9285
9286	//@}
9287	///// The italic command /////
9288	//@{
9289	commands.italic = {
9290		action: function (value, range) {
9291			// "If queryCommandState("italic") returns true, set the selection's
9292			// value to "normal". Otherwise set the selection's value to "italic"."
9293			if (myQueryCommandState("italic", range)) {
9294				setSelectionValue("italic", null, range);
9295			} else {
9296				setSelectionValue("italic", "italic", range);
9297			}
9298		},
9299		inlineCommandActivatedValues: ["italic", "oblique"],
9300		relevantCssProperty: "fontStyle"
9301	};
9302
9303	//@}
9304	///// The removeFormat command /////
9305	//@{
9306	commands.removeformat = {
9307		action: function (value, range) {
9308			var newEnd, newStart, newNode;
9309
9310			// "A removeFormat candidate is an editable HTML element with local
9311			// name "abbr", "acronym", "b", "bdi", "bdo", "big", "blink", "cite",
9312			// "code", "dfn", "em", "font", "i", "ins", "kbd", "mark", "nobr", "q",
9313			// "s", "samp", "small", "span", "strike", "strong", "sub", "sup",
9314			// "tt", "u", or "var"."
9315			function isRemoveFormatCandidate(node) {
9316				return isEditable(node) && isHtmlElementInArray(node, ["abbr", "acronym", "b", "bdi", "bdo", "big", "blink", "cite", "code", "dfn", "em", "font", "i", "ins", "kbd", "mark", "nobr", "q", "s", "samp", "small", "span", "strike", "strong", "sub", "sup", "tt", "u", "var"]);
9317			}
9318
9319			// "Let elements to remove be a list of every removeFormat candidate
9320			// effectively contained in the active range."
9321			var elementsToRemove = getAllEffectivelyContainedNodes(getActiveRange(), isRemoveFormatCandidate);
9322
9323			// "For each element in elements to remove:"
9324			$_(elementsToRemove).forEach(function (element) {
9325				// "While element has children, insert the first child of element
9326				// into the parent of element immediately before element,
9327				// preserving ranges."
9328				while (element.hasChildNodes()) {
9329					movePreservingRanges(element.firstChild, element.parentNode, getIndexInParent(element), getActiveRange());
9330				}
9331
9332				// "Remove element from its parent."
9333				element.parentNode.removeChild(element);
9334			});
9335
9336			// "If the active range's start node is an editable Text node, and its
9337			// start offset is neither zero nor its start node's length, call
9338			// splitText() on the active range's start node, with argument equal to
9339			// the active range's start offset. Then set the active range's start
9340			// node to the result, and its start offset to zero."
9341			if (isEditable(getActiveRange().startContainer) && getActiveRange().startContainer.nodeType == Node.TEXT_NODE && getActiveRange().startOffset != 0 && getActiveRange().startOffset != getNodeLength(getActiveRange().startContainer)) {
9342				// Account for browsers not following range mutation rules
9343				if (getActiveRange().startContainer == getActiveRange().endContainer) {
9344					newEnd = getActiveRange().endOffset - getActiveRange().startOffset;
9345					newNode = splitText(getActiveRange().startContainer, getActiveRange().startOffset);
9346					getActiveRange().setStart(newNode, 0);
9347					getActiveRange().setEnd(newNode, newEnd);
9348				} else {
9349					getActiveRange().setStart(splitText(getActiveRange().startContainer, getActiveRange().startOffset), 0);
9350				}
9351			}
9352
9353			// "If the active range's end node is an editable Text node, and its
9354			// end offset is neither zero nor its end node's length, call
9355			// splitText() on the active range's end node, with argument equal to
9356			// the active range's end offset."
9357			if (isEditable(getActiveRange().endContainer) && getActiveRange().endContainer.nodeType == Node.TEXT_NODE && getActiveRange().endOffset != 0 && getActiveRange().endOffset != getNodeLength(getActiveRange().endContainer)) {
9358				// IE seems to mutate the range incorrectly here, so we need
9359				// correction here as well.  Have to be careful to set the range to
9360				// something not including the text node so that getActiveRange()
9361				// doesn't throw an exception due to a temporarily detached
9362				// endpoint.
9363				newStart = [getActiveRange().startContainer, getActiveRange().startOffset];
9364				newEnd = [getActiveRange().endContainer, getActiveRange().endOffset];
9365				getActiveRange().setEnd(document.documentElement, 0);
9366				splitText(newEnd[0], newEnd[1]);
9367				getActiveRange().setStart(newStart[0], newStart[1]);
9368				getActiveRange().setEnd(newEnd[0], newEnd[1]);
9369			}
9370
9371			// "Let node list consist of all editable nodes effectively contained
9372			// in the active range."
9373			//
9374			// "For each node in node list, while node's parent is a removeFormat
9375			// candidate in the same editing host as node, split the parent of the
9376			// one-node list consisting of node."
9377			$_(getAllEffectivelyContainedNodes(getActiveRange(), isEditable)).forEac
9377h(function (node) {
9378				while (isRemoveFormatCandidate(node.parentNode) && inSameEditingHost(node.parentNode, node)) {
9379					splitParent([node], getActiveRange());
9380				}
9381			});
9382
9383			// "For each of the entries in the following list, in the given order,
9384			// set the selection's value to null, with command as given."
9385			$_(["subscript", "bold", "fontname", "fontsize", "forecolor", "hilitecolor", "italic", "strikethrough", "underline"]).forEach(function (command) {
9386				setSelectionValue(command, null, range);
9387			});
9388		}
9389	};
9390
9391	//@}
9392	///// The strikethrough command /////
9393	//@{
9394	commands.strikethrough = {
9395		action: function (value, range) {
9396			// "If queryCommandState("strikethrough") returns true, set the
9397			// selection's value to null. Otherwise set the selection's value to
9398			// "line-through"."
9399			if (myQueryCommandState("strikethrough", range)) {
9400				setSelectionValue("strikethrough", null, range);
9401			} else {
9402				setSelectionValue("strikethrough", "line-through", range);
9403			}
9404		},
9405		inlineCommandActivatedValues: ["line-through"]
9406	};
9407
9408	//@}
9409	///// The subscript command /////
9410	//@{
9411	commands.subscript = {
9412		action: function (value, range) {
9413			// "Call queryCommandState("subscript"), and let state be the result."
9414			var state = myQueryCommandState("subscript", range);
9415
9416			// "Set the selection's value to null."
9417			setSelectionValue("subscript", null, range);
9418
9419			// "If state is false, set the selection's value to "subscript"."
9420			if (!state) {
9421				setSelectionValue("subscript", "subscript", range);
9422			}
9423		},
9424		indeterm: function () {
9425			// "True if either among editable Text nodes that are effectively
9426			// contained in the active range, there is at least one with effective
9427			// command value "subscript" and at least one with some other effective
9428			// command value; or if there is some editable Text node effectively
9429			// contained in the active range with effective command value "mixed".
9430			// Otherwise false."
9431			var nodes = getAllEffectivelyContainedNodes(getActiveRange(), function (node) {
9432				return isEditable(node) && node.nodeType == Node.TEXT_NODE;
9433			});
9434			return (($_(nodes).some(function (node) { return getEffectiveCommandValue(node, "subscript") == "subscript"; })
9435					 && $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "subscript") != "subscript"; }))
9436					|| $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "subscript") == "mixed"; }));
9437		},
9438		inlineCommandActivatedValues: ["subscript"]
9439	};
9440
9441	//@}
9442	///// The superscript command /////
9443	//@{
9444	commands.superscript = {
9445		action: function (value, range) {
9446			// "Call queryCommandState("superscript"), and let state be the
9447			// result."
9448			var state = myQueryCommandState("superscript", range);
9449
9450			// "Set the selection's value to null."
9451			setSelectionValue("superscript", null, range);
9452
9453			// "If state is false, set the selection's value to "superscript"."
9454			if (!state) {
9455				setSelectionValue("superscript", "superscript", range);
9456			}
9457		},
9458		indeterm: function () {
9459			// "True if either among editable Text nodes that are effectively
9460			// contained in the active range, there is at least one with effective
9461			// command value "superscript" and at least one with some other
9462			// effective command value; or if there is some editable Text node
9463			// effectively contained in the active range with effective command
9464			// value "mixed".  Otherwise false."
9465			var nodes = getAllEffectivelyContainedNodes(
9466				getActiveRange(),
9467				function (node) {
9468					return isEditable(node) && node.nodeType == Node.TEXT_NODE;
9469				}
9470			);
9471			return (($_(nodes).some(function (node) { return getEffectiveCommandValue(node, "superscript") == "superscript"; })
9472					 && $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "superscript") != "superscript"; }))
9473					|| $_(nodes).some(function (node) { return getEffectiveCommandValue(node, "superscript") == "mixed"; }));
9474		},
9475		inlineCommandActivatedValues: ["superscript"]
9476	};
9477
9478	//@}
9479	///// The underline command /////
9480	//@{
9481	commands.underline = {
9482		action: function (value, range) {
9483			// "If queryCommandState("underline") returns true, set the selection's
9484			// value to null. Otherwise set the selection's value to "underline"."
9485			if (myQueryCommandState("underline", range)) {
9486				setSelectionValue("underline", null, range);
9487			} else {
9488				setSelectionValue("underline", "underline", range);
9489			}
9490		},
9491		inlineCommandActivatedValues: ["underline"]
9492	};
9493
9494	//@}
9495	///// The unlink command /////
9496	//@{
9497	commands.unlink = {
9498		action: function () {
9499			// "Let hyperlinks be a list of every a element that has an href
9500			// attribute and is contained in the active range or is an ancestor of
9501			// one of its boundary points."
9502			//
9503			// As usual, take care to ensure it's tree order.  The correctness of
9504			// the following is left as an exercise for the reader.
9505			var range = getActiveRange();
9506			var hyperlinks = [];
9507			var node;
9508			for (node = range.startContainer; node; node = node.parentNode) {
9509				if (isNamedHtmlElement(node, 'A') && hasAttribute(node, "href")) {
9510					hyperlinks.unshift(node);
9511				}
9512			}
9513			for (node = range.startContainer; node != nextNodeDescendants(range.endContainer); node = nextNode(node)) {
9514				if (isNamedHtmlElement(node, 'A') && hasAttribute(node, "href") && (isContained(node, range) || isAncestor(node, range.endContainer) || node == range.endContainer)) {
9515					hyperlinks.push(node);
9516				}
9517			}
9518
9519			// "Clear the value of each member of hyperlinks."
9520			var i;
9521			for (i = 0;
9521 i < hyperlinks.length; i++) {
9522				clearValue(hyperlinks[i], "unlink", range);
9523			}
9524		},
9525		standardInlineValueCommand: true
9526	};
9527
9528	//@}
9529
9530	/////////////////////////////////////
9531	///// Block formatting commands /////
9532	/////////////////////////////////////
9533
9534	///// Block formatting command definitions /////
9535	//@{
9536
9537	// "An indentation element is either a blockquote, or a div that has a style
9538	// attribute that sets "margin" or some subproperty of it."
9539	function isIndentationElement(node) {
9540		// Handling of indentation elements while deleting is somehow broken (pressing backspace
9541		// in blockquotes wraps the blockquote into a div, ...)
9542		// therefore for now, we pretend that indentation elements do not exist at all.
9543		return false;
9544	}
9545
9546	// "A simple indentation element is an indentation element that has no
9547	// attributes other than one or more of
9548	//
9549	//   * "a style attribute that sets no properties other than "margin", "border",
9550	//     "padding", or subproperties of those;
9551	//   * "a class attribute;
9552	//   * "a dir attribute."
9553	function isSimpleIndentationElement(node) {
9554		if (!isIndentationElement(node)) {
9555			return false;
9556		}
9557
9558		if (node.tagName != "BLOCKQUOTE" && node.tagName != "DIV") {
9559			return false;
9560		}
9561
9562		var i;
9563		for (i = 0; i < node.attributes.length; i++) {
9564			if (!isHtmlNamespace(node.attributes[i].namespaceURI) || jQuery.inArray(node.attributes[i].name, ["style", "class", "dir"]) == -1) {
9565				return false;
9566			}
9567		}
9568
9569		if (typeof node.style.length !== 'undefined') {
9570			for (i = 0; i < node.style.length; i++) {
9571				// This is approximate, but it works well enough for my purposes.
9572				if (!/^(-[a-z]+-)?(margin|border|padding)/.test(node.style[i])) {
9573					return false;
9574				}
9575			}
9576		} else {
9577			var s;
9578			/*jslint forin: true*/ //not sure whether node.style.hasOwnProperty is valid
9579			for (s in node.style) {
9580				// This is approximate, but it works well enough for my purposes.
9581				if (!/^(-[a-z]+-)?(margin|border|padding)/.test(s) && node.style[s] && node.style[s] !== 0 && node.style[s] !== 'false') {
9582					return false;
9583				}
9584			}
9585			/*jslint forin: false*/
9586		}
9587
9588		return true;
9589	}
9590
9591	// "A non-list single-line container is an HTML element with local name
9592	// "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "listing", "p", "pre",
9593	// or "xmp"."
9594	function isNonListSingleLineContainer(node) {
9595		return isHtmlElementInArray(node, ["address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "listing", "p", "pre", "xmp"]);
9596	}
9597
9598	// "A single-line container is either a non-list single-line container, or an
9599	// HTML element with local name "li", "dt", or "dd"."
9600	function isSingleLineContainer(node) {
9601		return isNonListSingleLineContainer(node) || isHtmlElementInArray(node, ["li", "dt", "dd"]);
9602	}
9603
9604	// "The default single-line container name is "p"."
9605	var defaultSingleLineContainerName = "p";
9606
9607	//@}
9608	///// Check whether the given element is an end break /////
9609	//@{
9610	function isEndBreak(element) {
9611		return (isNamedHtmlElement(element, 'br') && element.parentNode.lastChild === element);
9612	}
9613
9614	//@}
9615	///// Create an end break /////
9616	//@{
9617	function createEndBreak() {
9618		return document.createElement("br");
9619	}
9620
9621	/**
9622	 * Ensure the container is editable
9623	 * E.g. when called for an empty paragraph or header, and the browser is not IE,
9624	 * we need to append a br (marked with class aloha-end-br)
9625	 * For IE7, there is a special behaviour that will append zero-width whitespace
9626	 * @param {DOMNode} container
9627	 */
9628	function ensureContainerEditable(container) {
9629		if (!container) {
9630			return;
9631		}
9632
9633		// Because it is useful to be able to completely empty the contents of
9634		// an editing host during editing.  So long as the container's
9635		// contenteditable attribute is "true" (as is the case during editing),
9636		// the element will be rendered visibly in all browsers.  This fact
9637		// allows us to not have to prop up the container with a <br> in order
9638		// to keep it accessible to the editor.
9639		if (isEditingHost(container)) {
9640			return;
9641		}
9642
9643		if (isNamedHtmlElement(container.lastChild, "br")) {
9644			return;
9645		}
9646
9647		if ($_(container.childNodes).some(isVisible)) {
9648			return;
9649		}
9650
9651		if (!isMSIE) {
9652			// for normal browsers, the end-br will do
9653			container.appendChild(createEndBreak());
9654		} 
9655
9656	}
9657
9658	/**
9659	 * Node names that can not be unwrapped.
9660	 *
9661	 * @const
9662	 * @type {string[]}
9663	 */
9664	var NOT_UNWRAPPABLE_NODES = ['TABLE', 'OL', 'UL', 'DL'];
9665
9666	/**
9667	 * Checks if `node` can be unwrapped.
9668	 *
9669	 * @param  {Element} node
9670	 * @return {boolean}
9671	 */
9672	function isUnwrappable(node) {
9673		return jQuery.inArray(node.nodeName, NOT_UNWRAPPABLE_NODES) === -1;
9674	}
9675	//@}
9676	///// Assorted block formatting command algorithms /////
9677	//@{
9678
9679	function fixDisallowedAncestors(node, range) {
9680		var i;
9681
9682		// "If node is not editable, abort these steps."
9683		if (!isEditable(node)) {
9684			return;
9685		}
9686
9687		// "If node is not an allowed child of any of its ancestors in the same
9688		// editing host, and is not an HTML element with local name equal to the
9689		// default single-line container name:"
9690		if ($_(getAncestors(node)).every(function (ancestor) { return !inSameEditingHost(node, ancestor) || !isAllowedChild(node, ancestor); })
9691			    && !isHtmlElement_obsolete(node, defaultSingleLineContainerName)) {
9692			// "If node is a dd or dt, wrap the one-node list consisting of node,
9693			// with sibling criteria returning true for any dl with no attributes
9694			// and false otherwise, and new parent instructions returning the
9695			// result of calling createElement("dl") on the context object. Then
9696			// abort these steps."
9697			if (isHtmlElementInArray(node, ["dd", "dt"])) {
9698				wrap(
9699					[node],
9700					function (sibling) {
9701						return isNamedHtmlElement(sibling, 'dl') && !sibling.attributes.length;
9702					},
9703					function () {
9704						return document.createElement("dl");
9705					},
9706					range
9707				);
9708				return;
9709			}
9710
9711			// "If node is not a prohibited paragraph child, abort these steps."
9712			if (!isProhibitedParagraphChild(node)) {
9713				return;
9714			}
9715
9716			// "Set the tag name of node to the default single-line container name,
9717			// and let node be the result."
9718			node = setTagName(node, defaultSingleLineContainerName, range);
9719
9720			ensureContainerEditable(node);
9721
9722			// "Fix disallowed ancestors of node."
9723			fixDisallowedAncestors(node, range);
9724
9725			// "Let descendants be all descendants of node."
9726			var descendants = getDescendants(node);
9727
9728			// "Fix disallowed ancestors of each member of descendants."
9729			for (i = 0;
9729 i < descendants.length; i++) {
9730				fixDisallowedAncestors(descendants[i], range);
9731			}
9732
9733			// "Abort these steps."
9734			return;
9735		}
9736
9737		// "Record the values of the one-node list consisting of node, and let
9738		// values be the result."
9739		var values = recordValues([node]);
9740		var newStartOffset, newEndOffset;
9741
9742		// "While node is not an allowed child of its parent, split the parent of
9743		// the one-node list consisting of node."
9744		while (!isAllowedChild(node, node.parentNode)) {
9745			// If the parent contains only this node and possibly empty text nodes, we rather want to unwrap the node, instead of splitting.
9746			// With splitting, we would get empty nodes, like:
9747			// split: <p><p>foo</p></p> -> <p></p><p>foo</p> (bad)
9748			// unwrap: <p><p>foo</p></p> -> <p>foo</p> (good)
9749
9750			// First remove empty text nodes that are children of the parent and correct the range if necessary
9751			// we do this to have the node being the only child of its parent, so that we can replace the parent with the node
9752			for (i = node.parentNode.childNodes.length - 1; i >= 0; --i) {
9753				if (node.parentNode.childNodes[i].nodeType == 3 && node.parentNode.childNodes[i].data.length == 0) {
9754					// we remove the empty text node
9755					node.parentNode.removeChild(node.parentNode.childNodes[i]);
9756
9757					// if the range points to somewhere behind the removed text node, we reduce the offset
9758					if (range.startContainer == node.parentNode && range.startOffset > i) {
9759						range.startOffset--;
9760					}
9761					if (range.endContainer == node.parentNode && range.endOffset > i) {
9762						range.endOffset--;
9763					}
9764				}
9765			}
9766
9767			// now that the parent has only the node as child (because we
9768			// removed any existing empty text nodes), we can safely unwrap the
9769			// node's contents, and correct the range if necessary
9770			// if the node is unwrappable (table, lists) the node is not unwrapped
9771			// but splitted.
9772			if (node.parentNode.childNodes.length == 1 && isUnwrappable(node)) {
9773				newStartOffset = range.startOffset;
9774				newEndOffset = range.endOffset;
9775
9776				if (range.startContainer === node.parentNode && range.startOffset > getIndexInParent(node)) {
9777					// the node (1 element) will be replaced by its contents (contents().length elements)
9778					newStartOffset = range.startOffset + (jQuery(node).contents().length - 1);
9779				}
9780				if (range.endContainer === node.parentNode && range.endOffset > getIndexInParent(node)) {
9781					// the node (1 element) will be replaced by its contents (contents().length elements)
9782					newEndOffset = range.endOffset + (jQuery(node).contents().length - 1);
9783				}
9784				jQuery(node).contents().unwrap();
9785				range.startOffset = newStartOffset;
9786				range.endOffset = newEndOffset;
9787				// after unwrapping, we are done
9788				break;
9789			} else {
9790				// store the original parent
9791				var originalParent = node.parentNode;
9792				splitParent([node], range);
9793				// check whether the parent did not change, so the split did not work, e.g.
9794				// because we already reached the editing host itself.
9795				// this situation can occur, e.g. when we insert a paragraph into an contenteditable span
9796				// in such cases, we just unwrap the contents of the paragraph
9797				if (originalParent === node.parentNode) {
9798					// so we unwrap now
9799					newStartOffset = range.startOffset;
9800					newEndOffset = range.endOffset;
9801
9802					if (range.startContainer === node.parentNode && range.startOffset > getIndexInParent(node)) {
9803						// the node (1 element) will be replaced by its contents (contents().length elements)
9804						newStartOffset = range.startOffset + (jQuery(node).contents().length - 1);
9805					}
9806					if (range.endContainer === node.parentNode && range.endOffset > getIndexInParent(node)) {
9807						// the node (1 element) will be replaced by its contents (contents().length elements)
9808						newEndOffset = range.endOffset + (jQuery(node).contents().length - 1);
9809					}
9810					jQuery(node).contents().unwrap();
9811					range.startOffset = newStartOffset;
9812					range.endOffset = newEndOffset;
9813					// after unwrapping, we are done
9814					break;
9815				}
9816			}
9817		}
9818
9819		// "Restore the values from values."
9820		restoreValues(values, range);
9821	}
9822
9823	/**
9824	 * This method "normalizes" sublists of the given item (which is supposed to be a LI):
9825	 * If sublists are found in the LI element, they are moved directly into the outer list.
9826	 * @param item item
9827	 * @param range range, which will be modified if necessary
9828	 */
9829	function normalizeSublists(item, range) {
9830		// "If item is not an li or it is not editable or its parent is not
9831		// editable, abort these steps."
9832		if (!isNamedHtmlElement(item, 'LI') || !isEditable(item) || !isEditable(item.parentNode)) {
9833			return;
9834		}
9835
9836		// "Let new item be null."
9837		var newItem = null;
9838
9839		function isOlUl(node) {
9840			return isHtmlElementInArray(node, ["OL", "UL"]);
9841		}
9842
9843		// "While item has an ol or ul child:"
9844		while ($_(item.childNodes).some(isOlUl)) {
9845			// "Let child be the last child of item."
9846			var child = item.lastChild;
9847
9848			// "If child is an ol or ul, or new item is null and child is a Text
9849			// node whose data consists of zero of more space characters:"
9850			if (isHtmlElementInArray(child, ["OL", "UL"]) || (!newItem && child.nodeType == Node.TEXT_NODE && /^[ \t\n\f\r]*$/.test(child.data))) {
9851				// "Set new item to null."
9852				newItem = null;
9853
9854				// "Insert child into the parent of item immediately following
9855				// item, preserving ranges."
9856				movePreservingRanges(child, item.parentNode, 1 + getIndexInParent(item), range);
9857
9858				// "Otherwise:"
9859			} else {
9860				// "If new item is null, let new item be the result of calling
9861				// createElement("li") on the ownerDocument of item, then insert
9862				// new item into the parent of item immediately after item."
9863				if (!newItem) {
9864					newItem = item.ownerDocument.createElement("li");
9865					item.parentNode.insertBefore(newItem, item.nextSibling);
9866				}
9867
9868				// "Insert child into new item as its first child, preserving
9869				// ranges."
9870				movePreservingRanges(child, newItem, 0, range);
9871			}
9872		}
9873	}
9874
9875	/**
9876	 * This method is the exact opposite of normalizeSublists.
9877	 * List nodes directly nested into each other are corrected to be nested in li elements (so that the resulting lists conform the html5 specification)
9878	 * @param item list node
9879	 * @param range range, which is preserved when modifying the list
9880	 */
9881	function unNormalizeSublists(item, range) {
9882		// "If item is not an ol or ol or it is not editable or its parent is not
9883		// editable, abort these steps."
9884		if (!isHtmlElementInArray(item, ["OL", "UL"]) || !isEditable(item)) {
9885			return;
9886		}
9887
9888		var $list = jQuery(item);
9889		$list.children("ol,ul").each(function (index, sublist) {
9890			if (isNamedHtmlElement(sublist.previousSibling, "LI")) {
9891				// move the sublist into the LI
9892				movePreservingRanges(sublist, sublist.previousSibling, sublist.previousSibling.childNodes.length, range);
9893			}
9894		});
9895	}
9896
9897	//@}
9898	///// Block-extending a range /////
9899	//@{
9900
9901	function blockExtend(range) {
9902		// "Let start node, start offset, end node, and end offset be the start
9903		// and end nodes and offsets of the range."
9904		var startNode = range.startContainer;
9905		var startOffset = range.startOffset;
9906		var endNode = range.endContainer;
9907		var endOffset = range.endOffset;
9908
9909		// "If some ancestor container of start node is an li, set start offset to
9910		// the index of the last such li in tree order, and set start node to that
9911		// li's parent."
9912		var liAncestors = $_(getAncestors(startNode).concat(startNode)).filter(function (ancestor) { return isNamedHtmlElement(ancestor, 'li'); }).slice(-1);
9913		if (liAncestors.length) {
9914			startOffset = getIndexInParent(liAncestors[0]);
9915			startNode = liAncestors[0].parentNode;
9916		}
9917
9918		// "If (start node, start offset) is not a block start point, repeat the
9919		// following steps:"
9920		if (!isBlockStartPoint(startNode, startOffset)) {
9921			do {
9922				// "If start offset is zero, set it to start node's index, then set
9923				// start node to its parent."
9924				if (startOffset == 0) {
9925					startOffset = getIndexInParent(startNode);
9926					startNode = startNode.parentNode;
9927
9928					// "Otherwise, subtract one from start offset."
9929				} else {
9930					startOffset--;
9931				}
9932
9933				// "If (start node, start offset) is a block boundary point, break from
9934				// this loop."
9935			} while (!isBlockBoundaryPoint(startNode, startOffset));
9936		}
9937
9938		// "While start offset is zero and start node's parent is not null, set
9939		// start offset to start node's index, then set start node to its parent."
9940		while (startOffset == 0 && startNode.parentNode) {
9941			startOffset = getIndexInParent(startNode);
9942			startNode = startNode.parentNode;
9943		}
9944
9945		// "If some ancestor container of end node is an li, set end offset to one
9946		// plus the index of the last such li in tree order, and set end node to
9947		// that li's parent."
9948		liAncestors = $_(getAncestors(endNode).concat(endNode)).filter(function (ancestor) { return isNamedHtmlElement(ancestor, 'li'); }).slice(-1);
9949		if (liAncestors.length) {
9950			endOffset = 1 + getIndexInParent(liAncestors[0]);
9951			endNode = liAncestors[0].parentNode;
9952		}
9953
9954		// "If (end node, end offset) is not a block end point, repeat the
9955		// following steps:"
9956		if (!isBlockEndPoint(endNode, endOffset)) {
9957			do {
9958				// "If end offset is end node's length, set it to one plus end node's
9959				// index, then set end node to its parent."
9960				if (endOffset == getNodeLength(endNode)) {
9961					endOffset = 1 + getIndexInParent(endNode);
9962					endNode = endNode.parentNode;
9963
9964					// "Otherwise, add one to end offset.
9965				} else {
9966					endOffset++;
9967				}
9968
9969				// "If (end node, end offset) is a block boundary point, break from
9970				// this loop."
9971			} while (!isBlockBoundaryPoint(endNode, endOffset));
9972		}
9973
9974		// "While end offset is end node's length and end node's parent is not
9975		// null, set end offset to one plus end node's index, then set end node to
9976		// its parent."
9977		while (endOffset == getNodeLength(endNode) && endNode.parentNode) {
9978			endOffset = 1 + getIndexInParent(endNode);
9979			endNode = endNode.parentNode;
9980		}
9981
9982		// "Let new range be a new range whose start and end nodes and offsets
9983		// are start node, start offset, end node, and end offset."
9984		var newRange = _core.rangy.createRange();
9985		newRange.setStart(startNode, startOffset);
9986		newRange.setEnd(endNode, endOffset);
9987
9988		// "Return new range."
9989		return newRange;
9990	}
9991
9992	function getSelectionListState() {
9993		// "Block-extend the active range, and let new range be the result."
9994		var newRange = blockExtend(getActiveRange());
9995
9996		// "Let node list be a list of nodes, initially empty."
9997		//
9998		// "For each node contained in new range, append node to node list if the
9999		// last member of node list (if any) is not an ancestor of node; node is
10000		// editable; node is not an indentation element; and node is either an ol
10001		// or ul, or the child of an ol or ul, or an allowed child of "li"."
10002		var nodeList = getContainedNodes(newRange, function (node) {
10003			return isEditable(node) && !isIndentationElement(node) && (isHtmlElementInArray(node, ["ol", "ul"]) || isHtmlElementInArray(node.parentNode, ["ol", "ul"]) || isAllowedChild(node, "li"));
10004		});
10005
10006		// "If node list is empty, return "none"."
10007		if (!nodeList.length) {
10008			return "none";
10009		}
10010
10011		// "If every member of node list is either an ol or the child of an ol or
10012		// the child of an li child of an ol, and none is a ul or an ancestor of a
10013		// ul, return "ol"."
10014		if ($_(nodeList).every(function (node) { return (isNamedHtmlElement(node, 'ol')
10015														 || isNamedHtmlElement(node.parentNode, "ol")
10016														 || (isNamedHtmlElement(node.parentNode, "li")
10017															 && isNamedHtmlElement(node.parentNode.parentNode, "ol"))); })
10018			    && !$_(nodeList).some(function (node) { return isNamedHtmlElement(node, 'ul') || (node.querySelector && node.querySelector("ul")); })) {
10019			return "ol";
10020		}
10021
10022		// "If every member of node list is either a ul or the child of a ul or the
10023		// child of an li child of a ul, and none is an ol or an ancestor of an ol,
10024		// return "ul"."
10025		if ($_(nodeList).every(function (node) { return (isNamedHtmlElement(node, 'ul')
10026														 || isNamedHtmlElement(node.parentNode, "ul")
10027														 || (isNamedHtmlElement(node.parentNode, "li")
10028															 && isNamedHtmlElement(node.parentNode.parentNode, "ul"))); })
10029			    && !$_(nodeList).some(function (node) { return isNamedHtmlElement(node, 'ol') || (node.querySelector && node.querySelector("ol")); })) {
10030			return "ul";
10031		}
10032
10033		var hasOl = $_(nodeList).some(function (node) {
10034			return (isNamedHtmlElement(node, 'ol')
10035					|| isNamedHtmlElement(node.parentNode, "ol")
10036					|| (node.querySelector && node.querySelector("ol"))
10037					|| (isNamedHtmlElement(node.parentNode, "li")
10038						&& isNamedHtmlElement(node.parentNode.parentNode, "ol")));
10039		});
10040		var hasUl = $_(nodeList).some(function (node) {
10041			return (isNamedHtmlElement(node, 'ul')
10042					|| isNamedHtmlElement(node.parentNode, "ul")
10043					|| (node.querySelector && node.querySelector("ul"))
10044					|| (isNamedHtmlElement(node.parentNode, "li")
10045						&& isNamedHtmlElement(node.parentNode.parentNode, "ul")));
10046		});
10047		// "If some member of node list is either an ol or the child or ancestor of
10048		// an ol or the child of an li child of an ol, and some member of node list
10049		// is either a ul or the child or ancestor of a ul or the child of an li
10050		// child of a ul, return "mixed"."
10051		if (hasOl && hasUl) {
10052			return "mixed";
10053		}
10054
10055		// "If some member of node list is either an ol or the child or ancestor of
10056		// an ol or the child of an li child of an ol, return "mixed ol"."
10057		if (hasOl) {
10058			return "mixed ol";
10059		}
10060
10061		// "If some member of node list is either a ul or the child or ancestor of
10062		// a ul or the child of an li child of a ul, return "mixed ul"."
10063		if (hasUl) {
10064			return "mixed ul";
10065		}
10066
10067		// "Return "none"."
10068		return "none";
10069	}
10070
10071	function getAlignmentValue(node) {
10072		// "While node is neither null nor an Element, or it is an Element but its
10073		// "display" property has resolved value "inline" or "none", set node to
10074		// its parent."
10075		while ((node && node.nodeType != Node.ELEMENT_NODE) || (node.nodeType == Node.ELEMENT_NODE && jQuery.inArray($_.getComputedStyle(node).display, ["inline", "none"]) != -1)) {
10076			node = node.parentNode;
10077		}
10078
10079		// "If node is not an Element, return "left"."
10080		if (!node || node.nodeType != Node.ELEMENT_NODE) {
10081			return "left";
10082		}
10083
10084		var resolvedValue = $_.getComputedStyle(node).textAlign
10085		// Hack around browser non-standardness
10086			.replace(/^-(moz|webkit)-/, "").replace(/^auto$/, "start");
10087
10088		// "If node's "text-align" property has resolved value "start", return
10089		// "left" if the directionality of node is "ltr", "right" if it is "rtl"."
10090		if (resolvedValue == "start") {
10091			return getDirectionality(node) == "ltr" ? "left" : "right";
10092		}
10093
10094		// "If node's "text-align" property has resolved value "end", return
10095		// "right" if the directionality of node is "ltr", "left" if it is "rtl"."
10096		if (resolvedValue == "end") {
10097			return getDirectionality(node) == "ltr" ? "right" : "left";
10098		}
10099
10100		// "If node's "text-align" property has resolved value "center", "justify",
10101		// "left", or "right", return that value."
10102		if (jQuery.inArray(resolvedValue, ["center", "justify", "left", "right"]) != -1) {
10103			return resolvedValue;
10104		}
10105
10106		// "Return "left"."
10107		return "left";
10108	}
10109
10110	//@}
10111	///// Recording and restoring overrides /////
10112	//@{
10113
10114	function recordCurrentOverrides(range) {
10115		// "Let overrides be a list of (string, string or boolean) ordered pairs,
10116		// initially empty."
10117		var overrides = [];
10118
10119		// "If there is a value override for "createLink", add ("createLink", value
10120		// override for "createLink") to overrides."
10121		if (getValueOverride("createlink", range) !== undefined) {
10122			overrides.push(["createlink", getValueOverride("createlink", range)]);
10123		}
10124
10125		// "For each command in the list "bold", "italic", "strikethrough",
10126		// "subscript", "superscript", "underline", in order: if there is a state
10127		// override for command, add (command, command's state override) to
10128		// overrides."
10129		$_(["bold", "italic", "strikethrough", "subscript", "superscript", "underline"]).forEac
10129h(function (command) {
10130			if (getStateOverride(command, range) !== undefined) {
10131				overrides.push([command, getStateOverride(command, range)]);
10132			}
10133		});
10134
10135		// "For each command in the list "fontName", "fontSize", "foreColor",
10136		// "hiliteColor", in order: if there is a value override for command, add
10137		// (command, command's value override) to overrides."
10138		$_(["fontname", "fontsize", "forecolor", "hilitecolor"]).forEach(function (command) {
10139			if (getValueOverride(command, range) !== undefined) {
10140				overrides.push([command, getValueOverride(command, range)]);
10141			}
10142		});
10143
10144		// "Return overrides."
10145		return overrides;
10146	}
10147
10148	function recordCurrentStatesAndValues(range) {
10149		// "Let overrides be a list of (string, string or boolean) ordered pairs,
10150		// initially empty."
10151		var overrides = [];
10152
10153		// "Let node be the first editable Text node effectively contained in the
10154		// active range, or null if there is none."
10155		var node = $_(getAllEffectivelyContainedNodes(range)).filter(function (node) {
10156			return isEditable(node) && node.nodeType == Node.TEXT_NODE;
10157		})[0];
10158
10159		// "If node is null, return overrides."
10160		if (!node) {
10161			return overrides;
10162		}
10163
10164		// "Add ("createLink", value for "createLink") to overrides."
10165		overrides.push(["createlink", commands.createlink.value(range)]);
10166
10167		// "For each command in the list "bold", "italic", "strikethrough",
10168		// "subscript", "superscript", "underline", in order: if node's effective
10169		// command value for command is one of its inline command activated values,
10170		// add (command, true) to overrides, and otherwise add (command, false) to
10171		// overrides."
10172		
10173		$_(["bold", "italic", "strikethrough", "subscript", "superscript", "underline"]).forEach(function (command) {
10174			if ($_(commands[command].inlineCommandActivatedValues).indexOf(getEffectiveCommandValue(node, command)) != -1) {
10175				overrides.push([command, true]);
10176			} else {
10177				overrides.push([command, false]);
10178			}
10179		});
10180
10181		// "For each command in the list "fontName", "foreColor", "hiliteColor", in
10182		// order: add (command, command's value) to overrides."
10183
10184		$_(["fontname", "fontsize", "forecolor", "hilitecolor"]).forEach(function (command) {
10185			overrides.push([command, commands[command].value(range)]);
10186		});
10187
10188		// "Add ("fontSize", node's effective command value for "fontSize") to
10189		// overrides."
10190		overrides.push(["fontsize", getEffectiveCommandValue(node, "fontsize")]);
10191
10192		// "Return overrides."
10193		return overrides;
10194	}
10195
10196	function restoreStatesAndValues(overrides, range) {
10197		var i;
10198		var command;
10199		var override;
10200		// "Let node be the first editable Text node effectively contained in the
10201		// active range, or null if there is none."
10202		var node = $_(getAllEffectivelyContainedNodes(range)).filter(function (node) {
10203			return isEditable(node) && node.nodeType == Node.TEXT_NODE;
10204		})[0];
10205
10206		function isEditableTextNode(node) {
10207			return isEditable(node) && node.nodeType == Node.TEXT_NODE;
10208		}
10209
10210		// "If node is not null, then for each (command, override) pair in
10211		// overrides, in order:"
10212		if (node) {
10213
10214			for (i = 0; i < overrides.length; i++) {
10215				command = overrides[i][0];
10216				override = overrides[i][1];
10217
10218				// "If override is a boolean, and queryCommandState(command)
10219				// returns something different from override, call
10220				// execCommand(command)."
10221				if (typeof override == "boolean" && myQueryCommandState(command, range) != override) {
10222					myExecCommand(command, false, override, range);
10223
10224					// "Otherwise, if override is a string, and command is not
10225					// "fontSize", and queryCommandValue(command) returns something not
10226					// equivalent to override, call execCommand(command, false,
10227					// override)."
10228				} else if (typeof override == "string" && command != "fontsize" && !areEquivalentValues(command, myQueryCommandValue(command, range), override)) {
10229					myExecCommand(command, false, override, range);
10230
10231					// "Otherwise, if override is a string; and command is "fontSize";
10232					// and either there is a value override for "fontSize" that is not
10233					// equal to override, or there is no value override for "fontSize"
10234					// and node's effective command value for "fontSize" is not loosely
10235					// equivalent to override: call execCommand("fontSize", false,
10236					// override)."
10237				} else if (typeof override == "string"
10238						   && command == "fontsize"
10239						   && ((getValueOverride("fontsize", range) !== undefined
10240								&& getValueOverride("fontsize", range) !== override)
10241							   || (getValueOverride("fontsize", range) === undefined
10242								   && !areLooselyEquivalentValues(command, getEffectiveCommandValue(node, "fontsize"), override)))) {
10243					myExecCommand("fontsize", false, override, range);
10244
10245					// "Otherwise, continue this loop from the beginning."
10246				} else {
10247					continue;
10248				}
10249
10250				// "Set node to the first editable Text node effectively contained
10251				// in the active range, if there is one."
10252				node = $_(getAllEffectivelyContainedNodes(range)).filter(isEditableTextNode)[0] || node;
10253			}
10254
10255			// "Otherwise, for each (command, override) pair in overrides, in order:"
10256		} else {
10257			for (i = 0; i < overrides.length; i++) {
10258				command = overrides[i][0];
10259				override = overrides[i][1];
10260
10261				// "If override is a boolean, set the state override for command to
10262				// override."
10263				if (typeof override == "boolean") {
10264					setStateOverride(command, override, range);
10265				}
10266
10267				// "If override is a string, set the value override for command to
10268				// override."
10269				if (typeof override == "string") {
10270					setValueOverride(command, override, range);
10271				}
10272			}
10273		}
10274	}
10275
10276	//@}
10277	///// Canonical space sequences /////
10278	//@{
10279
10280	function canonicalSpaceSequence(n, nonBreakingStart, nonBreakingEnd) {
10281		// "If n is zero, return the empty string."
10282		if (n == 0) {
10283			return "";
10284		}
10285
10286		// "If n is one and both non-breaking start and non-breaking end are false,
10287		// return a single space (U+0020)."
10288		if (n == 1 && !nonBreakingStart && !nonBreakingEnd) {
10289			return " ";
10290		}
10291
10292		// "If n is one, return a single non-breaking space (U+00A0)."
10293		if (n == 1) {
10294			return "\xa0";
10295		}
10296
10297		// "Let buffer be the empty string."
10298		var buffer = "";
10299
10300		// "If non-breaking start is true, let repeated pair be U+00A0 U+0020.
10301		// Otherwise, let it be U+0020 U+00A0."
10302		var repeatedPair;
10303		if (nonBreakingStart) {
10304			repeatedPair = "\xa0 ";
10305		} else {
10306			repeatedPair = " \xa0";
10307		}
10308
10309		// "While n is greater than three, append repeated pair to buffer and
10310		// subtract two from n."
10311		while (n > 3) {
10312			buffer += repeatedPair;
10313			n -= 2;
10314		}
10315
10316		// "If n is three, append a three-element string to buffer depending on
10317		// non-breaking start and non-breaking end:"
10318		if (n == 3) {
10319			buffer += !nonBreakingStart && !nonBreakingEnd ? " \xa0 " : nonBreakingStart && !nonBreakingEnd ? "\xa0\xa0 " : !nonBreakingStart && nonBreakingEnd ? " \xa0\xa0" : nonBreakingStart && nonBreakingEnd ? "\xa0 \xa0" : "impossible";
10320
10321			// "Otherwise, append a two-element string to buffer depending on
10322			// non-breaking start and non-breaking end:"
10323		} else {
10324			buffer += !nonBreakingStart && !nonBreakingEnd ? "\xa0 " : nonBreakingStart && !nonBreakingEnd ? "\xa0 " : !nonBreakingStart && nonBreakingEnd ? " \xa0" : nonBreakingStart && nonBreakingEnd ? "\xa0\xa0" : "impossible";
10325		}
10326
10327		// "Return buffer."
10328		return buffer;
10329	}
10330
10331	function canonicalizeWhitespace(node, offset) {
10332		// "If node is neither editable nor an editing host, abort these steps."
10333		if (!isEditable(node) && !isEditingHost(node)) {
10334			return;
10335		}
10336
10337		// "Let start node equal node and let start offset equal offset."
10338		var startNode = node;
10339		var startOffset = offset;
10340
10341		// "Repeat the following steps:"
10342		while (true) {
10343			// "If start node has a child in the same editing host with index start
10344			// offset minus one, set start node to that child, then set start
10345			// offset to start node's length."
10346			if (0 <= startOffset - 1 && inSameEditingHost(startNode, startNode.childNodes[startOffset - 1])) {
10347				startNode = startNode.childNodes[startOffset - 1];
10348				startOffset = getNodeLength(startNode);
10349
10350				// "Otherwise, if start offset is zero and start node does not follow a
10351				// line break and start node's parent is in the same editing host, set
10352				// start offset to start node's index, then set start node to its
10353				// parent."
10354			} else if (startOffset == 0 && !followsLineBreak(startNode) && inSameEditingHost(startNode, startNode.parentNode)) {
10355				startOffset = getIndexInParent(startNode);
10356				startNode = startNode.parentNode;
10357
10358				// "Otherwise, if start node is a Text node and its parent's resolved
10359				// value for "white-space" is neither "pre" nor "pre-wrap" and start
10360				// offset is not zero and the (start offset − 1)st element of start
10361				// node's data is a space (0x0020) or non-breaking space (0x00A0),
10362				// subtract one from start offset."
10363			} else if (startNode.nodeType == Node.TEXT_NODE && jQuery.inArray($_.getComputedStyle(startNode.parentNode).whiteSpace, ["pre", "pre-wrap"]) == -1 && startOffset != 0 && /[ \xa0]/.test(startNode.data[startOffset - 1])) {
10364				startOffset--;
10365
10366				// "Otherwise, break from this loop."
10367			} else {
10368				break;
10369			}
10370		}
10371
10372		// "Let end node equal start node and end offset equal start offset."
10373		var endNode = startNode;
10374		var endOffset = startOffset;
10375
10376		// "Let length equal zero."
10377		var length = 0;
10378
10379		// "Let follows space be false."
10380		var followsSpace = false;
10381
10382		// "Repeat the following steps:"
10383		while (true) {
10384			// "If end node has a child in the same editing host with index end
10385			// offset, set end node to that child, then set end offset to zero."
10386			if (endOffset < endNode.childNodes.length && inSameEditingHost(endNode, endNode.childNodes[endOffset])) {
10387				endNode = endNode.childNodes[endOffset];
10388				endOffset = 0;
10389
10390				// "Otherwise, if end offset is end node's length and end node does not
10391				// precede a line break and end node's parent is in the same editing
10392				// host, set end offset to one plus end node's index, then set end node
10393				// to its parent."
10394			} else if (endOffset == getNodeLength(endNode) && !precedesLineBreak(endNode) && inSameEditingHost(endNode, endNode.parentNode)) {
10395				endOffset = 1 + getIndexInParent(endNode);
10396				endNode = endNode.parentNode;
10397
10398				// "Otherwise, if end node is a Text node and its parent's resolved
10399				// value for "white-space" is neither "pre" nor "pre-wrap" and end
10400				// offset is not end node's length and the end offsetth element of
10401				// end node's data is a space (0x0020) or non-breaking space (0x00A0):"
10402			} else if (endNode.nodeType == Node.TEXT_NODE && jQuery.inArray($_.getComputedStyle(endNode.parentNode).whiteSpace, ["pre", "pre-wrap"]) == -1 && endOffset != getNodeLength(endNode) && /[ \xa0]/.test(endNode.data[endOffset])) {
10403				// "If follows space is true and the end offsetth element of end
10404				// node's data is a space (0x0020), call deleteData(end offset, 1)
10405				// on end node, then continue this loop from the beginning."
10406				if (followsSpace && " " == endNode.data[endOffset]) {
10407					endNode.deleteData(endOffset, 1);
10408					continue;
10409				}
10410
10411				// "Set follows space to true if the end offsetth element of end
10412				// node's data is a space (0x0020), false otherwise."
10413				followsSpace = " " == endNode.data[endOffset];
10414
10415				// "Add one to end offset."
10416				endOffset++;
10417
10418				// "Add one to length."
10419				length++;
10420
10421				// "Otherwise, break from this loop."
10422			} else {
10423				break;
10424			}
10425		}
10426
10427		// "Let replacement whitespace be the canonical space sequence of length
10428		// length. non-breaking start is true if start offset is zero and start
10429		// node follows a line break, and false otherwise. non-breaking end is true
10430		// if end offset is end node's length and end node precedes a line break,
10431		// and false otherwise."
10432		var replacementWhitespace = canonicalSpaceSequence(length, startOffset == 0 && followsLineBreak(startNode), endOffset == getNodeLength(endNode) && precedesLineBreak(endNode));
10433
10434		// "While (start node, start offset) is before (end node, end offset):"
10435		while (getPosition(startNode, startOffset, endNode, endOffset) == "before") {
10436			// "If start node has a child with index start offset, set start node
10437			// to that child, then set start offset to zero."
10438			if (startOffset < startNode.childNodes.length) {
10439				startNode = startNode.childNodes[startOffset];
10440				startOffset = 0;
10441
10442				// "Otherwise, if start node is not a Text node or if start offset is
10443				// start node's length, set start offset to one plus start node's
10444				// index, then set start node to its parent."
10445			} else if (startNode.nodeType != Node.TEXT_NODE || startOffset == getNodeLength(startNode)) {
10446				startOffset = 1 + getIndexInParent(startNode);
10447				startNode = startNode.parentNode;
10448
10449				// "Otherwise:"
10450			} else {
10451				// "Remove the first element from replacement whitespace, and let
10452				// element be that element."
10453				var element = replacementWhitespace[0];
10454				replacementWhitespace = replacementWhitespace.slice(1);
10455
10456				// "If element is not the same as the start offsetth element of
10457				// start node's data:"
10458				if (element != startNode.data[startOffset]) {
10459					// "Call insertData(start offset, element) on start node."
10460					startNode.insertData(startOffset, element);
10461
10462					// "Call deleteData(start offset + 1, 1) on start node."
10463					startNode.deleteData(startOffset + 1, 1);
10464				}
10465
10466				// "Add one to start offset."
10467				startOffset++;
10468			}
10469		}
10470	}
10471
10472	/**
10473	 * Remove the node if it doesn't have any children or 1 empty text node.
10474	 * This function doesn't handle all cases of unrendered nodes.
10475	 * In the following cases the node will not be removed:
10476	 * * The node contains multiple empty nodes
10477	 * * The child text node contains zero width characters
10478	 * * The child text node contains unrendered characters
10479	 *
10480	 * @param  {Text}  node   The node to check
10481	 * @param  {Element}  range  Range object to set the position
10482	 * @return {boolean} True if node got removed, false otherwise
10483	 */
10484	function removeNodeIfEmptyAndCorrectRange(node, range) {
10485		var
10486			removeNode = false,
10487			parentNode,
10488			nodeChildNodesLength,
10489			offsetInParent,
10490			firstChildNode;
10491
10492		if (!node || !range) {
10493			return false;
10494		}
10495
10496		parentNode = node.parentNode;
10497
10498		if (!parentNode) {
10499			return false;
10500		}
10501
10502		// Check if the node is empty
10503		nodeChildNodesLength = node.childNodes.length;
10504		if (nodeChildNodesLength === 0) {
10505			removeNode = true;
10506		}
10507
10508		// If the inline node only contains an empty text node,
10509		// it is also considered being empty.
10510		if (nodeChildNodesLength === 1) {
10511			firstChildNode = node.childNodes[0];
10512
10513			if (firstChildNode.nodeType === Node.TEXT_NODE
10514					&& firstChildNode.nodeValue.length === 0) {
10515				removeNode = true;
10516			}
10517		}
10518
10519		// Don't continue if node should not be removed or has no parent
10520		if (!removeNode) {
10521			return false;
10522		}
10523
10524		// Before removing, remember the offset of node in its parent
10525		offsetInParent = getIndexInParent(node);
10526
10527		// I'm not gonna comment this, it's clear what it does.
10528		parentNode.removeChild(node);
10529
10530		// Now the cursor has to be set at the position where the
10531		// removed parent was before.
10532		range.setStart(parentNode, offsetInParent);
10533		range.setEnd(parentNode, offsetInParent);
10534
10535		return true;
10536	}
10537
10538	//@}
10539	///// Deleting the contents of a range /////
10540	//@{
10541
10542	function deleteContents(arg1, arg2, arg3, arg4, arg5) {
10543		// We accept several different calling conventions:
10544		//
10545		// 1) A single argument, which is a range.
10546		//
10547		// 2) Two arguments, the first being a range and the second flags.
10548		//
10549		// 3) Four arguments, the start and end of a range.
10550		//
10551		// 4) Five arguments, the start and end of a range plus flags.
10552		//
10553		// The flags argument is a dictionary that can have up to two keys,
10554		// blockMerging and stripWrappers, whose corresponding values are
10555		// interpreted as boolean.  E.g., {stripWrappers: false}.
10556		var range;
10557		var flags = {};
10558		var i;
10559
10560		if (arguments.length < 3) {
10561			range = arg1;
10562		} else {
10563			range = _core.rangy.createRange();
10564			range.setStart(arg1, arg2);
10565			range.setEnd(arg3, arg4);
10566		}
10567		if (arguments.length == 2) {
10568			flags = arg2;
10569		}
10570		if (arguments.length == 5) {
10571			flags = arg5;
10572		}
10573
10574		var blockMerging = null != flags.blockMerging ? !!flags.blockMerging : true;
10575		var stripWrappers = null != flags.stripWrappers ? !!flags.stripWrappers : true;
10576
10577		// "If range is null, abort these steps and do nothing."
10578		if (!range) {
10579			return;
10580		}
10581
10582		// "Let start node, start offset, end node, and end offset be range's start
10583		// and end nodes and offsets."
10584		var startNode = range.startContainer;
10585		var startOffset = range.startOffset;
10586		var endNode = range.endContainer;
10587		var endOffset = range.endOffset;
10588		var referenceNode;
10589
10590		// "While start node has at least one child:"
10591		while (startNode.hasChildNodes()) {
10592			// "If start offset is start node's length, and start node's parent is
10593			// in the same editing host, and start node is an inline node, set
10594			// start offset to one plus the index of start node, then set start
10595			// node to its parent and continue this loop from the beginning."
10596			if (startOffset == getNodeLength(startNode) && inSameEditingHost(startNode, startNode.parentNode) && isInlineNode(startNode)) {
10597				startOffset = 1 + getIndexInParent(startNode);
10598				startNode = startNode.parentNode;
10599				continue;
10600			}
10601
10602			// "If start offset is start node's length, break from this loop."
10603			if (startOffset == getNodeLength(startNode)) {
10604				break;
10605			}
10606
10607			// "Let reference node be the child of start node with index equal to
10608			// start offset."
10609			referenceNode = startNode.childNodes[startOffset];
10610
10611			// "If reference node is a block node or an Element with no children,
10612			// or is neither an Element nor a Text node, break from this loop."
10613			if (isBlockNode(referenceNode) || (referenceNode.nodeType == Node.ELEMENT_NODE && !referenceNode.hasChildNodes()) || (referenceNode.nodeType != Node.ELEMENT_NODE && referenceNode.nodeType != Node.TEXT_NODE)) {
10614				break;
10615			}
10616
10617			// "Set start node to reference node and start offset to 0."
10618			startNode = referenceNode;
10619			startOffset = 0;
10620		}
10621
10622		// "While end node has at least one child:"
10623		while (endNode.hasChildNodes()) {
10624			// "If end offset is 0, and end node's parent is in the same editing
10625			// host, and end node is an inline node, set end offset to the index of
10626			// end node, then set end node to its parent and continue this loop
10627			// from the beginning."
10628			if (endOffset == 0 && inSameEditingHost(endNode, endNode.parentNode) && isInlineNode(endNode)) {
10629				endOffset = getIndexInParent(endNode);
10630				endNode = endNode.parentNode;
10631				continue;
10632			}
10633
10634			// "If end offset is 0, break from this loop."
10635			if (endOffset == 0) {
10636				break;
10637			}
10638
10639			// "Let reference node be the child of end node with index equal to end
10640			// offset minus one."
10641			referenceNode = endNode.childNodes[endOffset - 1];
10642
10643			// "If reference node is a block node or an Element with no children,
10644			// or is neither an Element nor a Text node, break from this loop."
10645			if (isBlockNode(referenceNode) || (referenceNode.nodeType == Node.ELEMENT_NODE && !referenceNode.hasChildNodes()) || (referenceNode.nodeType != Node.ELEMENT_NODE && referenceNode.nodeType != Node.TEXT_NODE)) {
10646				break;
10647			}
10648
10649			// "Set end node to reference node and end offset to the length of
10650			// reference node."
10651			endNode = referenceNode;
10652			endOffset = getNodeLength(referenceNode);
10653		}
10654
10655		// "If (end node, end offset) is not after (start node, start offset), set
10656		// range's end to its start and abort these steps."
10657		if (getPosition(endNode, endOffset, startNode, startOffset) !== "after") {
10658			range.setEnd(range.startContainer, range.startOffset);
10659			return range;
10660		}
10661
10662		// "If start node is a Text node and start offset is 0, set start offset to
10663		// the index of start node, then set start node to its parent."
10664		// Commented out for unknown reason
10665		//if (startNode.nodeType == Node.TEXT_NODE && startOffset == 0 && startNode != endNode) {
10666		//		startOffset = getIndexInParent(startNode);
10667		//		startNode = startNode.parentNode;
10668		//}
10669
10670		// "If end node is a Text node and end offset is its length, set end offset
10671		// to one plus the index of end node, then set end node to its parent."
10672		if (endNode.nodeType == Node.TEXT_NODE && endOffset == getNodeLength(endNode) && startNode != endNode) {
10673			endOffset = 1 + getIndexInParent(endNode);
10674			endNode = endNode.parentNode;
10675		}
10676
10677		// "Set range's start to (start node, start offset) and its end to (end
10678		// node, end offset)."
10679		range.setStart(startNode, startOffset);
10680		range.setEnd(endNode, endOffset);
10681
10682		// "Let start block be the start node of range."
10683		var startBlock = range.startContainer;
10684
10685		// "While start block's parent is in the same editing host and start block
10686		// is an inline node, set start block to its parent."
10687		while (inSameEditingHost(startBlock, startBlock.parentNode) && isInlineNode(startBlock)) {
10688			startBlock = startBlock.parentNode;
10689		}
10690
10691		// "If start block is neither a block node nor an editing host, or "span"
10692		// is not an allowed child of start block, or start block is a td or th,
10693		// set start block to null."
10694		if ((!isBlockNode(startBlock) && !isEditingHost(startBlock)) || !isAllowedChild("span", startBlock) || isHtmlElementInArray(startBlock, ["td", "th"])) {
10695			startBlock = null;
10696		}
10697
10698		// "Let end block be the end node of range."
10699		var endBlock = range.endContainer;
10700
10701		// "While end block's parent is in the same editing host and end block is
10702		// an inline node, set end block to its parent."
10703		while (inSameEditingHost(endBlock, endBlock.parentNode) && isInlineNode(endBlock)) {
10704			endBlock = endBlock.parentNode;
10705		}
10706
10707		// "If end block is neither a block node nor an editing host, or "span" is
10708		// not an allowed child of end block, or end block is a td or th, set end
10709		// block to null."
10710		if ((!isBlockNode(endBlock) && !isEditingHost(endBlock)) || !isAllowedChild("span", endBlock) || isHtmlElementInArray(endBlock, ["td", "th"])) {
10711			endBlock = null;
10712		}
10713
10714		// "Record current states and values, and let overrides be the result."
10715		var overrides = recordCurrentStatesAndValues(range);
10716		var parent_;
10717		// "If start node and end node are the same, and start node is an editable
10718		// Text node:"
10719		if (startNode == endNode && isEditable(startNode) && startNode.nodeType == Node.TEXT_NODE) {
10720			// "Let parent be the parent of node."
10721			parent_ = startNode.parentNode;
10722
10723			// "Call deleteData(start offset, end offset − start offset) on start
10724			// node."
10725			startNode.deleteData(startOffset, endOffset - startOffset);
10726
10727			// if deleting the text moved two spaces together, we replace the left one by a &nbsp;, which makes the two spaces a visible
10728			// two space sequence
10729			if (startOffset > 0 && startNode.data.substr(startOffset - 1, 1) === ' ' && startOffset < startNode.data.length && startNode.data.substr(startOffset, 1) === ' ') {
10730				startNode.replaceData(startOffset - 1, 1, '\xa0');
10731			}
10732
10733			// "Canonicalize whitespace at (start node, start offset)."
10734			canonicalizeWhitespace(startNode, startOffset);
10735
10736			// Remove the parent node, if it is an inline node and empty
10737			// If the node is inline and empty, range correction will happen inside
10738			// removeNodeIfEmptyAndCorrectRange, otherwise it'will be done below.
10739			if (!isInlineNode(parent_) || !removeNodeIfEmptyAndCorrectRange(parent_, range)) {
10740				// "Set range's end to its start."
10741				// Ok, also set the range's start to its start, because modifying the text
10742				// might have somehow corrupted the range
10743				range.setStart(range.startContainer, range.startOffset);
10744				range.setEnd(range.startContainer, range.startOffset);
10745			}
10746
10747			// "Restore states and values from overrides."
10748			restoreStatesAndValues(overrides, range);
10749
10750			// "If parent is editable or an editing host, is not an inline node,
10751			// and has no children, call createElement("br") on the context object
10752			// and append the result as the last child of parent."
10753			// only do this, if the offsetHeight is 0
10754			if ((isEditable(parent_) || isEditingHost(parent_)) && !isInlineNode(parent_)) {
10755				ensureContainerEditable(parent_);
10756			}
10757
10758			// "Abort these steps."
10759			return range;
10760		}
10761
10762		// "If start node is an editable Text node, call deleteData() on it, with
10763		// start offset as the first argument and (length of start node − start
10764		// offset) as the second argument."
10765		if (isEditable(startNode) && startNode.nodeType == Node.TEXT_NODE) {
10766			startNode.deleteData(startOffset, getNodeLength(startNode) - startOffset);
10767		}
10768
10769		// "Let node list be a list of nodes, initially empty."
10770		//
10771		// "For each node contained in range, append node to node list if the last
10772		// member of node list (if any) is not an ancestor of node; node is
10773		// editable; and node is not a thead, tbody, tfoot, tr, th, or td."
10774		var nodeList = getContainedNodes(
10775			range,
10776			function (node) {
10777				return (enableContentEditableDeletion ? true : isEditable(node)) && !isHtmlElementInArray(node, ["thead", "tbody", "tfoot", "tr", "th", "td"]);
10778			}
10779		);
10780
10781		// "For each node in node list:"
10782		for (i = 0; i < nodeList.length; i++) {
10783			var node = nodeList[i];
10784
10785			// "Let parent be the parent of node."
10786			parent_ = node.parentNode;
10787
10788			// "Remove node from parent."
10789			parent_.removeChild(node);
10790
10791			// "If strip wrappers is true or parent is not an ancestor container of
10792			// start node, while parent is an editable inline node with length 0,
10793			// let grandparent be the parent of parent, then remove parent from
10794			// grandparent, then set parent to grandparent."
10795			if (stripWrappers || (!isAncestor(parent_, startNode) && parent_ != startNode)) {
10796				while (isEditable(parent_) && isInlineNode(parent_) && getNodeLength(parent_) == 0) {
10797					var grandparent = parent_.parentNode;
10798					grandparent.removeChild(parent_);
10799					parent_ = grandparent;
10800				}
10801			}
10802
10803			// "If parent is editable or an editing host, is not an inline node,
10804			// and has no children, call createElement("br") on the context object
10805			// and append the result as the last child of parent."
10806			// only do this, if the offsetHeight is 0
10807			if ((isEditable(parent_) || isEditingHost(parent_)) && !isInlineNode(parent_)) {
10808				ensureContainerEditable(parent_);
10809			}
10810		}
10811
10812		// "If end node is an editable Text node, call deleteData(0, end offset) on
10813		// it."
10814		if (isEditable(endNode) && endNode.nodeType == Node.TEXT_NODE) {
10815			endNode.deleteData(0, endOffset);
10816		}
10817
10818		// Remove parentNode, if it is an empty inline node
10819		if (isInlineNode(startNode.parentNode)) {
10820			removeNodeIfEmptyAndCorrectRange(startNode.parentNode, range);
10821		}
10822
10823		// "Canonicalize whitespace at range's start."
10824		canonicalizeWhitespace(range.startContainer, range.startOffset);
10825
10826		// "Canonicalize whitespace at range's end."
10827		canonicalizeWhitespace(range.endContainer, range.endOffset);
10828
10829		// A reference to the position where a node is removed.
10830		var pos;
10831
10832		// "If block merging is false, or start block or end block is null, or
10833		// start block is not in the same editing host as end block, or start block
10834		// and end block are the same:"
10835		if (!blockMerging || !startBlock || !endBlock || !inSameEditingHost(startBlock, endBlock) || startBlock == endBlock) {
10836			// "Set range's end to its start."
10837			range.setEnd(range.startContainer, range.startOffset);
10838
10839			// Calling delete on the give markup:
10840			// <editable><block><br>[]</block></editable>
10841			// should result in:
10842			// <editable>[]</editable>
10843			var block = startBlock || endBlock;
10844			if (isEmptyOnlyChildOfEditingHost(block)) {
10845				pos = removeNode(block);
10846				range.setStart(pos.node, pos.offset);
10847				range.setEnd(pos.node, pos.offset);
10848			}
10849
10850			// "Restore states and values from overrides."
10851			restoreStatesAndValues(overrides, range);
10852
10853			// "Abort these steps."
10854			return range;
10855		}
10856
10857		// "If start block has one child, which is a collapsed block prop, remove
10858		// its child from it."
10859		if (startBlock.children.length == 1 && isCollapsedBlockProp(startBlock.firstChild)) {
10860			startBlock.removeChild(startBlock.firstChild);
10861		}
10862
10863		// "If end block has one child, which is a collapsed block prop, remove its
10864		// child from it."
10865		if (endBlock.children.length == 1 && isCollapsedBlockProp(endBlock.firstChild)) {
10866			endBlock.removeChild(endBlock.firstChild);
10867		}
10868
10869		var values;
10870		// "If start block is an ancestor of end block:"
10871		if (isAncestor(startBlock, endBlock)) {
10872			// "Let reference node be end block."
10873			referenceNode = endBlock;
10874
10875			// "While reference node is not a child of start block, set reference
10876			// node to its parent."
10877			while (referenceNode.parentNode != startBlock) {
10878				referenceNode = referenceNode.parentNode;
10879			}
10880
10881			// "Set the start and end of range to (start block, index of reference
10882			// node)."
10883			range.setStart(startBlock, getIndexInParent(referenceNode));
10884			range.setEnd(startBlock, getIndexInParent(referenceNode));
10885
10886			// "If end block has no children:"
10887			if (!endBlock.hasChildNodes()) {
10888				// "While end block is editable and is the only child of its parent
10889				// and is not a child of start block, let parent equal end block,
10890				// then remove end block from parent, then set end block to
10891				// parent."
10892				while (isEditable(endBlock) && endBlock.parentNode.childNodes.length == 1 && endBlock.parentNode != startBlock) {
10893					parent_ = endBlock;
10894					parent_.removeChild(endBlock);
10895					endBlock = parent_;
10896				}
10897
10898				// "If end block is editable and is not an inline node, and its
10899				// previousSibling and nextSibling are both inline nodes, call
10900				// createElement("br") on the context object and insert it into end
10901				// block's parent immediately after end block."
10902
10903				if (isEditable(endBlock) && !isInlineNode(endBlock) && isInlineNode(endBlock.previousSibling) && isInlineNode(endBlock.nextSibling)) {
10904					endBlock.parentNode.insertBefore(document.createElement("br"), endBlock.nextSibling);
10905				}
10906
10907				// "If end block is editable, remove it from its parent."
10908				if (isEditable(endBlock)) {
10909					endBlock.parentNode.removeChild(endBlock);
10910				}
10911
10912				// "Restore states and values from overrides."
10913				restoreStatesAndValues(overrides, range);
10914
10915				// "Abort these steps."
10916				return range;
10917			}
10918
10919			// "If end block's firstChild is not an inline node, restore states and
10920			// values from overrides, then abort these steps."
10921			if (!isInlineNode(endBlock.firstChild)) {
10922				restoreStatesAndValues(overrides, range);
10923				return range;
10924			}
10925
10926			// "Let children be a list of nodes, initially empty."
10927			var children = [];
10928
10929			// "Append the first child of end block to children."
10930			children.push(endBlock.firstChild);
10931
10932			// "While children's last member is not a br, and children's last
10933			// member's nextSibling is an inline node, append children's last
10934			// member's nextSibling to children."
10935			while (!isNamedHtmlElement(children[children.length - 1], "br") && isInlineNode(children[children.length - 1].nextSibling)) {
10936				children.push(children[children.length - 1].nextSibling);
10937			}
10938
10939			// "Record the values of children, and let values be the result."
10940			values = recordValues(children);
10941
10942			// "While children's first member's parent is not start block, split
10943			// the parent of children."
10944			while (children[0].parentNode != startBlock) {
10945				splitParent(children, range);
10946			}
10947
10948			// "If children's first member's previousSibling is an editable br,
10949			// remove that br from its parent."
10950			if (isEditable(children[0].previousSibling) && isNamedHtmlElement(children[0].previousSibling, "br")) {
10951				children[0].parentNode.removeChild(children[0].previousSibling);
10952			}
10953
10954			// "Otherwise, if start block is a descendant of end block:"
10955		} else if (isDescendant(startBlock, endBlock)) {
10956			// "Set the start and end of range to (start block, length of start
10957			// block)."
10958			range.setStart(startBlock, getNodeLength(startBlock));
10959			range.setEnd(startBlock, getNodeLength(startBlock));
10960
10961			// "Let reference node be start block."
10962			referenceNode = startBlock;
10963
10964			// "While reference node is not a child of end block, set reference
10965			// node to its parent."
10966			while (referenceNode.parentNode != endBlock) {
10967				referenceNode = referenceNode.parentNode;
10968			}
10969
10970			// "If reference node's nextSibling is an inline node and start block's
10971			// lastChild is a br, remove start block's lastChild from it."
10972			if (isInlineNode(referenceNode.nextSibling) && isNamedHtmlElement(startBlock.lastChild, "br")) {
10973				startBlock.removeChild(startBlock.lastChild);
10974			}
10975
10976			// "Let nodes to move be a list of nodes, initially empty."
10977			var nodesToMove = [];
10978
10979			// "If reference node's nextSibling is neither null nor a br nor a
10980			// block node, append it to nodes to move."
10981			if (referenceNode.nextSibling && !isNamedHtmlElement(referenceNode.nextSibling, "br") && !isBlockNode(referenceNode.nextSibling)) {
10982				nodesToMove.push(referenceNode.nextSibling);
10983			}
10984
10985			// "While nodes to move is nonempty and its last member's nextSibling
10986			// is neither null nor a br nor a block node, append it to nodes to
10987			// move."
10988			if (nodesToMove.length && nodesToMove[nodesToMove.length - 1].nextSibling && !isNamedHtmlElement(nodesToMove[nodesToMove.length - 1].nextSibling, "br") && !isBlockNode(nodesToMove[nodesToMove.length - 1].nextSibling)) {
10989				nodesToMove.push(nodesToMove[nodesToMove.length - 1].nextSibling);
10990			}
10991
10992			// "Record the values of nodes to move, and let values be the result."
10993			values = recordValues(nodesToMove);
10994
10995			// "For each node in nodes to move, append node as the last child of
10996			// start block, preserving ranges."
10997			$_(nodesToMove).forEach(function (node) {
10998				movePreservingRanges(node, startBlock, -1, range);
10999			});
11000
11001			// "If the nextSibling of reference node is a br, remove it from its
11002			// parent."
11003			if (isNamedHtmlElement(referenceNode.nextSibling, "br")) {
11004				referenceNode.parentNode.removeChild(referenceNode.nextSibling);
11005			}
11006
11007			// "Otherwise:"
11008		} else {
11009			// "Set the start and end of range to (start block, length of start
11010			// block)."
11011			range.setStart(startBlock, getNodeLength(startBlock));
11012			range.setEnd(startBlock, getNodeLength(startBlock));
11013
11014			// "If end block's firstChild is an inline node and start block's
11015			// lastChild is a br, remove start block's lastChild from it."
11016			if (isInlineNode(endBlock.firstChild) && isNamedHtmlElement(startBlock.lastChild, "br")) {
11017				startBlock.removeChild(startBlock.lastChild);
11018			}
11019
11020			// "Record the values of end block's children, and let values be the
11021			// result."
11022			values = recordValues([].slice.call(toArray(endBlock.childNodes)));
11023
11024			// "While end block has children, append the first child of end block
11025			// to start block, preserving ranges."
11026			while (endBlock.hasChildNodes()) {
11027				movePreservingRanges(endBlock.firstChild, startBlock, -1, range);
11028			}
11029
11030			// "While end block has no children, let parent be the parent of end
11031			// block, then remove end block from parent, then set end block to
11032			// parent."
11033			while (!endBlock.hasChildNodes()) {
11034				parent_ = endBlock.parentNode;
11035				parent_.removeChild(endBlock);
11036				endBlock = parent_;
11037			}
11038		}
11039
11040		// "Restore the values from values."
11041		restoreValues(values, range);
11042
11043		// Because otherwise calling deleteContents() with the given selection:
11044		//
11045		// <editable><block>[foo</block><block>bar]</block></editable>
11046		//
11047		// would result in:
11048		//
11049		// <editable><block>[]<br /></block></editable>
11050		//
11051		// instead of:
11052		//
11053		// <editable>[]</editable>
11054		//
11055		// Therefore, the below makes it possible to completely empty contents
11056		// of editing hosts via operations like CTRL+A, DEL.
11057		//
11058		// If startBlock is empty, and startBlock is the immediate and only
11059		// child of its parent editing host, then remove startBlock and collapse
11060		// the selection at the beginning of the editing post.
11061		if (isEmptyOnlyChildOfEditingHost(startBlock)) {
11062			pos = removeNode(startBlock);
11063			range.setStart(pos.node, pos.offset);
11064			range.setEnd(pos.node, pos.offset);
11065			startBlock = pos.node;
11066		}
11067
11068		// "If start block has no children, call createElement("br") on the context
11069		// object and append the result as the last child of start block."
11070		ensureContainerEditable(startBlock);
11071
11072		// "Restore states and values from overrides."
11073		restoreStatesAndValues(overrides, range);
11074
11075		return range;
11076	}
11077
11078	// "To remove a node node while preserving its descendants, split the parent of
11079	// node's children if it has any. If it has no children, instead remove it from
11080	// its parent."
11081	function removePreservingDescendants(node, range) {
11082		if (node.hasChildNodes()) {
11083			splitParent([].slice.call(toArray(node.childNodes)), range);
11084		} else {
11085			node.parentNode.removeChild(node);
11086		}
11087	}
11088
11089	/**
11090	 * Determines whether a node must be removed to make the insertparagraph command work in list contexts.
11091	 *
11092	 * @param {Node} node DOM node to check
11093	 * @return {boolean} true if the node should be deleted
11094	 */
11095	function isListWhitespaceToRemove(node) {
11096		// no whitespace node –> no need to remove
11097		if (!isWhitespaceNode(node)) {
11098			return false;
11099		}
11100
11101		// always remove whitespace children from ul and ol
11102		if (node.parentNode.nodeName === 'UL' || node.parentNode.nodeName === 'OL') {
11103			return true;
11104		}
11105
11106		if (node.parentNode.nodeName !== 'LI') {
11107			return false;
11108		}
11109
11110		// only remove whitespace from li if it is between (the opening tag/a block level element)
11111		// and (a block level element/the closing tag)
11112		var index = getIndexInParent(node);
11113		var last = node.parentNode.childNodes.length - 1;
11114		var cleanElements = blockLevelElements.concat(listElements);
11115
11116		return (index === 0 ||
11117		         cleanElements.indexOf(node.previousSibling.nodeName.toLowerCase()) > -1) &&
11118		       (index === last ||
11119		         cleanElements.indexOf(node.nextSibling.nodeName.toLowerCase()) > -1);
11120	}
11121
11122	//@}
11123	///// Indenting and outdenting /////
11124	//@{
11125
11126	function cleanLists(node, range) {
11127		// remove any whitespace nodes around list nodes
11128		if (node) {
11129			jQuery(node).find('ul,ol,li').each(function () {
11130				jQuery(this).contents().each(function () {
11131					if (isListWhitespaceToRemove(this)) {
11132						var index = getIndexInParent(this);
11133
11134						// if the range points to somewhere behind the removed text node, we reduce the offset
11135						if (range.startContainer === this.parentNode && range.startOffset > index) {
11136							range.startOffset--;
11137						} else if (range.startContainer === this) {
11138							// the range starts in the removed text node, let it start right before
11139							range.startContainer = this.parentNode;
11140							range.startOffset = index;
11141						}
11142						// same thing for end of the range
11143						if (range.endContainer === this.parentNode && range.endOffset > index) {
11144							range.endOffset--;
11145						} else if (range.endContainer === this) {
11146							range.endContainer = this.parentNode;
11147							range.endOffset = index;
11148						}
11149						// finally remove the whitespace node
11150						jQuery(this).remove();
11151					}
11152				});
11153			});
11154		}
11155	}
11156
11157
11158	//@}
11159	///// Indenting and outdenting /////
11160	//@{
11161
11162	function indentNodes(nodeList, range) {
11163		// "If node list is empty, do nothing and abort these steps."
11164		if (!nodeList.length) {
11165			return;
11166		}
11167
11168		// "Let first node be the first member of node list."
11169		var firstNode = nodeList[0];
11170
11171		// "If first node's parent is an ol or ul:"
11172		if (isHtmlElementInArray(firstNode.parentNode, ["OL", "UL"])) {
11173			// "Let tag be the local name of the parent of first node."
11174			var tag = firstNode.parentNode.tagName;
11175
11176			// "Wrap node list, with sibling criteria returning true for an HTML
11177			// element with local name tag and false otherwise, and new parent
11178			// instructions returning the result of calling createElement(tag) on
11179			// the ownerDocument of first node."
11180			wrap(
11181				nodeList,
11182				function (node) {
11183					return isHtmlElement_obsolete(node, tag);
11184				},
11185				function () {
11186					return firstNode.ownerDocument.createElement(tag);
11187				},
11188				range
11189			);
11190
11191			// "Abort these steps."
11192			return;
11193		}
11194
11195		// "Wrap node list, with sibling criteria returning true for a simple
11196		// indentation element and false otherwise, and new parent instructions
11197		// returning the result of calling createElement("blockquote") on the
11198		// ownerDocument of first node. Let new parent be the result."
11199		var newParent = wrap(
11200			nodeList,
11201			function (node) {
11202				return isSimpleIndentationElement(node);
11203			},
11204			function () {
11205				return firstNode.ownerDocument.createElement("blockquote");
11206			},
11207			range
11208		);
11209
11210		// "Fix disallowed ancestors of new parent."
11211		fixDisallowedAncestors(newParent, range);
11212	}
11213
11214	function outdentNode(node, range) {
11215		// "If node is not editable, abort these steps."
11216		if (!isEditable(node)) {
11217			return;
11218		}
11219
11220		// "If node is a simple indentation element, remove node, preserving its
11221		// descendants.  Then abort these steps."
11222		if (isSimpleIndentationElement(node)) {
11223			removePreservingDescendants(node, range);
11224			return;
11225		}
11226
11227		// "If node is an indentation element:"
11228		if (isIndentationElement(node)) {
11229			// "Unset the class and dir attributes of node, if any."
11230			node.removeAttribute("class");
11231			node.removeAttribute("dir");
11232
11233			// "Unset the margin, padding, and border CSS properties of node."
11234			node.style.margin = "";
11235			node.style.padding = "";
11236			node.style.border = "";
11237			if (node.getAttribute("style") == "") {
11238				node.removeAttribute("style");
11239			}
11240
11241			// "Set the tag name of node to "div"."
11242			setTagName(node, "div", range);
11243
11244			// "Abort these steps."
11245			return;
11246		}
11247
11248		// "Let current ancestor be node's parent."
11249		var currentAncestor = node.parentNode;
11250
11251		// "Let ancestor list be a list of nodes, initially empty."
11252		var ancestorList = [];
11253
11254		// "While current ancestor is an editable Element that is neither a simple
11255		// indentation element nor an ol nor a ul, append current ancestor to
11256		// ancestor list and then set current ancestor to its parent."
11257		while (isEditable(currentAncestor) && currentAncestor.nodeType == Node.ELEMENT_NODE && !isSimpleIndentationElement(currentAncestor) && !isHtmlElementInArray(currentAncestor, ["ol", "ul"])) {
11258			ancestorList.push(currentAncestor);
11259			currentAncestor = currentAncestor.parentNode;
11260		}
11261
11262		// "If current ancestor is not an editable simple indentation element:"
11263		if (!isEditable(currentAncestor) || !isSimpleIndentationElement(currentAncestor)) {
11264			// "Let current ancestor be node's parent."
11265			currentAncestor = node.parentNode;
11266
11267			// "Let ancestor list be the empty list."
11268			ancestorList = [];
11269
11270			// "While current ancestor is an editable Element that is neither an
11271			// indentation element nor an ol nor a ul, append current ancestor to
11272			// ancestor list and then set current ancestor to its parent."
11273			while (isEditable(currentAncestor) && currentAncestor.nodeType == Node.ELEMENT_NODE && !isIndentationElement(currentAncestor) && !isHtmlElementInArray(currentAncestor, ["ol", "ul"])) {
11274				ancestorList.push(currentAncestor);
11275				currentAncestor = currentAncestor.parentNode;
11276			}
11277		}
11278
11279		// "If node is an ol or ul and current ancestor is not an editable
11280		// indentation element:"
11281		if (isHtmlElementInArray(node, ["OL", "UL"]) && (!isEditable(currentAncestor) || !isIndentationElement(currentAncestor))) {
11282			// "Unset the reversed, start, and type attributes of node, if any are
11283			// set."
11284			node.removeAttribute("reversed");
11285			node.removeAttribute("start");
11286			node.removeAttribute("type");
11287
11288			// "Let children be the children of node."
11289			var children = [].slice.call(toArray(node.childNodes));
11290
11291			// "If node has attributes, and its parent is not an ol or ul, set the
11292			// tag name of node to "div"."
11293			if (node.attributes.length && !isHtmlElementInArray(node.parentNode, ["OL", "UL"])) {
11294				setTagName(node, "div", range);
11295
11296				// "Otherwise:"
11297			} else {
11298				// "Record the values of node's children, and let values be the
11299				// result."
11300				var values = recordValues([].slice.call(toArray(node.childNodes)));
11301
11302				// "Remove node, preserving its descendants."
11303				removePreservingDescendants(node, range);
11304
11305				// "Restore the values from values."
11306				restoreValues(values, range);
11307			}
11308
11309			// "Fix disallowed ancestors of each member of children."
11310			var i;
11311			for (i = 0; i < children.length; i++) {
11312				fixDisallowedAncestors(children[i], range);
11313			}
11314
11315			// "Abort these steps."
11316			return;
11317		}
11318
11319		// "If current ancestor is not an editable indentation element, abort these
11320		// steps."
11321		if (!isEditable(currentAncestor) || !isIndentationElement(currentAncestor)) {
11322			return;
11323		}
11324
11325		// "Append current ancestor to ancestor list."
11326		ancestorList.push(currentAncestor);
11327
11328		// "Let original ancestor be current ancestor."
11329		var originalAncestor = currentAncestor;
11330
11331		// "While ancestor list is not empty:"
11332		while (ancestorList.length) {
11333			// "Let current ancestor be the last member of ancestor list."
11334			//
11335			// "Remove the last member of ancestor list."
11336			currentAncestor = ancestorList.pop();
11337
11338			// "Let target be the child of current ancestor that is equal to either
11339			// node or the last member of ancestor list."
11340			var target = node.parentNode == currentAncestor ? node : ancestorList[ancestorList.length - 1];
11341
11342			// "If target is an inline node that is not a br, and its nextSibling
11343			// is a br, remove target's nextSibling from its parent."
11344			if (isInlineNode(target) && !isNamedHtmlElement(target, 'BR') && isNamedHtmlElement(target.nextSibling, "BR")) {
11345				target.parentNode.removeChild(target.nextSibling);
11346			}
11347
11348			// "Let preceding siblings be the preceding siblings of target, and let
11349			// following siblings be the following siblings of target."
11350			var precedingSiblings = [].slice.call(toArray(currentAncestor.childNodes), 0, getIndexInParent(target));
11351			var followingSiblings = [].slice.call(toArray(currentAncestor.childNodes), 1 + getIndexInParent(target));
11352
11353			// "Indent preceding siblings."
11354			indentNodes(precedingSiblings, range);
11355
11356			// "Indent following siblings."
11357			indentNodes(followingSiblings, range);
11358		}
11359
11360		// "Outdent original ancestor."
11361		outdentNode(originalAncestor, range);
11362	}
11363
11364
11365	//@}
11366	///// Toggling lists /////
11367	//@{
11368
11369	function toggleLists(tagName, range) {
11370		// "Let mode be "disable" if the selection's list state is tag name, and
11371		// "enable" otherwise."
11372		var mode = getSelectionListState() == tagName ? "disable" : "enable";
11373
11374		tagName = tagName.toUpperCase();
11375
11376		// "Let other tag name be "ol" if tag name is "ul", and "ul" if tag name is
11377		// "ol"."
11378		var otherTagName = tagName == "OL" ? "UL" : "OL";
11379
11380		// "Let items be a list of all lis that are ancestor containers of the
11381		// range's start and/or end node."
11382		//
11383		// It's annoying to get this in tree order using functional stuff without
11384		// doing getDescendants(document), which is slow, so I do it imperatively.
11385		var items = [];
11386		(function () {
11387			var ancestorContainer;
11388			for (ancestorContainer = range.endContainer;
11389				     ancestorContainer != range.commonAncestorContainer;
11390				     ancestorContainer = ancestorContainer.parentNode) {
11391				if (isNamedHtmlElement(ancestorContainer, "li")) {
11392					items.unshift(ancestorContainer);
11393				}
11394			}
11395			for (ancestorContainer = range.startContainer;
11396				     ancestorContainer;
11397				     ancestorContainer = ancestorContainer.parentNode) {
11398				if (isNamedHtmlElement(ancestorContainer, "li")) {
11399					items.unshift(ancestorContainer);
11400				}
11401			}
11402		}());
11403
11404		// "For each item in items, normalize sublists of item."
11405		$_(items).forEach(function (thisArg) {
11406			normalizeSublists(thisArg, range);
11407		});
11408
11409		// "Block-extend the range, and let new range be the result."
11410		var newRange = blockExtend(range);
11411
11412		// "If mode is "enable", then let lists to convert consist of every
11413		// editable HTML element with local name other tag name that is contained
11414		// in new range, and for every list in lists to convert:"
11415		if (mode == "enable") {
11416			$_(getAllContainedNodes(newRange, function (node) {
11417				return isEditable(node) && isHtmlElement_obsolete(node, otherTagName);
11418			})).forEach(function (list) {
11419				// "If list's previousSibling or nextSibling is an editable HTML
11420				// element with local name tag name:"
11421				if ((isEditable(list.previousSibling) && isHtmlElement_obsolete(list.previousSibling, tagName)) || (isEditable(list.nextSibling) && isHtmlElement_obsolete(list.nextSibling, tagName))) {
11422					// "Let children be list's children."
11423					var children = [].slice.call(toArray(list.childNodes));
11424
11425					// "Record the values of children, and let values be the
11426					// result."
11427					var values = recordValues(children);
11428
11429					// "Split the parent of children."
11430					splitParent(children, range);
11431
11432					// "Wrap children, with sibling criteria returning true for an
11433					// HTML element with local name tag name and false otherwise."
11434					wrap(
11435						children,
11436						function (node) {
11437							return isHtmlElement_obsolete(node, tagName);
11438						},
11439						function () {
11440							return null;
11441						},
11442						range
11443					);
11444
11445					// "Restore the values from values."
11446					restoreValues(values, range);
11447
11448					// "Otherwise, set the tag name of list to tag name."
11449				} else {
11450					setTagName(list, tagName, range);
11451				}
11452			});
11453		}
11454
11455		// "Let node list be a list of nodes, initially empty."
11456		//
11457		// "For each node node contained in new range, if node is editable; the
11458		// last member of node list (if any) is not an ancestor of node; node
11459		// is not an indentation element; and either node is an ol or ul, or its
11460		// parent is an ol or ul, or it is an allowed child of "li"; then append
11461		// node to node list."
11462		var nodeList = getContainedNodes(newRange, function (node) {
11463			return isEditable(node) && !isIndentationElement(node) && (isHtmlElementInArray(node, ["OL", "UL"]) || isHtmlElementInArray(node.parentNode, ["OL", "UL"]) || isAllowedChild(node, "li"));
11464		});
11465
11466		// "If mode is "enable", remove from node list any ol or ul whose parent is
11467		// not also an ol or ul."
11468		if (mode == "enable") {
11469			nodeList = $_(nodeList).filter(function (node) {
11470				return !isHtmlElementInArray(node, ["ol", "ul"]) || isHtmlElementInArray(node.parentNode, ["ol", "ul"]);
11471			});
11472		}
11473
11474		// "If mode is "disable", then while node list is not empty:"
11475		var sublist, values;
11476
11477		function createLi() {
11478			return document.createElement("li");
11479		}
11480
11481		function isOlUl(node) {
11482			return isHtmlElementInArray(node, ["ol", "ul"]);
11483		}
11484
11485		function makeIsElementPred(tagName) {
11486			return function (node) {
11487				return isHtmlElement_obsolete(node, tagName);
11488			};
11489		}
11490
11491		function makeCreateElement(tagName) {
11492			return function () {
11493				return document.createElement(tagName);
11494			};
11495		}
11496
11497		function makeCreateElementSublist(tagName, sublist, range) {
11498			return function () {
11499				// "If sublist's first member's parent is not an editable
11500				// simple indentation element, or sublist's first member's
11501				// parent's previousSibling is not an editable HTML element
11502				// with local name tag name, call createElement(tag name)
11503				// on the context object and return the result."
11504				if (!isEditable(sublist[0].parentNode) || !isSimpleIndentationElement(sublist[0].parentNode) || !isEditable(sublist[0].parentNode.previousSibling) || !isHtmlElement_obsolete(sublist[0].parentNode.previousSibling, tagName)) {
11505					return document.createElement(tagName);
11506				}
11507
11508				// "Let list be sublist's first member's parent's
11509				// previousSibling."
11510				var list = sublist[0].parentNode.previousSibling;
11511
11512				// "Normalize sublists of list's lastChild."
11513				normalizeSublists(list.lastChild, range);
11514
11515				// "If list's lastChild is not an editable HTML element
11516				// with local name tag name, call createElement(tag name)
11517				// on the context object, and append the result as the last
11518				// child of list."
11519				if (!isEditable(list.lastChild) || !isHtmlElement_obsolete(list.lastChild, tagName)) {
11520					list.appendChild(document.createElement(tagName));
11521				}
11522
11523				// "Return the last child of list."
11524				return list.lastChild;
11525			};
11526		}
11527
11528		if (mode == "disable") {
11529			while (nodeList.length) {
11530				// "Let sublist be an empty list of nodes."
11531				sublist = [];
11532
11533				// "Remove the first member from node list and append it to
11534				// sublist."
11535				sublist.push(nodeList.shift());
11536
11537				// "If the first member of sublist is an HTML element with local
11538				// name tag name, outdent it and continue this loop from the
11539				// beginning."
11540				if (isHtmlElement_obsolete(sublist[0], tagName)) {
11541					outdentNode(sublist[0], range);
11542					continue;
11543				}
11544
11545				// "While node list is not empty, and the first member of node list
11546				// is the nextSibling of the last member of sublist and is not an
11547				// HTML element with local name tag name, remove the first member
11548				// from node list and append it to sublist."
11549				while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling && !isHtmlElement_obsolete(nodeList[0], tagName)) {
11550					sublist.push(nodeList.shift());
11551				}
11552
11553				// "Record the values of sublist, and let values be the result."
11554				values = recordValues(sublist);
11555
11556				// "Split the parent of sublist."
11557				splitParent(sublist, range);
11558
11559				// "Fix disallowed ancestors of each member of sublist."
11560				var i;
11561				for (i = 0; i < sublist.length; i++) {
11562					fixDisallowedAncestors(sublist[i], range);
11563				}
11564
11565				// "Restore the values from values."
11566				restoreValues(values, range);
11567			}
11568
11569			// "Otherwise, while node list is not empty:"
11570		} else {
11571			while (nodeList.length) {
11572				// "Let sublist be an empty list of nodes."
11573				sublist = [];
11574
11575				// "While either sublist is empty, or node list is not empty and
11576				// its first member is the nextSibling of sublist's last member:"
11577				while (!sublist.length || (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling)) {
11578					// "If node list's first member is a p or div, set the tag name
11579					// of node list's first member to "li", and append the result
11580					// to sublist. Remove the first member from node list."
11581					if (isHtmlElementInArray(nodeList[0], ["p", "div"])) {
11582						sublist.push(setTagName(nodeList[0], "li", range));
11583						nodeList.shift();
11584
11585						// "Otherwise, if the first member of node list is an li or ol
11586						// or ul, remove it from node list and append it to sublist."
11587					} else if (isHtmlElementInArray(nodeList[0], ["li", "ol", "ul"])) {
11588						sublist.push(nodeList.shift());
11589
11590						// "Otherwise:"
11591					} else {
11592						// "Let nodes to wrap be a list of nodes, initially empty."
11593						var nodesToWrap = [];
11594
11595						// "While nodes to wrap is empty, or node list is not empty
11596						// and its first member is the nextSibling of nodes to
11597						// wrap's last member and the first member of node list is
11598						// an inline node and the last member of nodes to wrap is
11599						// an inline node other than a br, remove the first member
11600						// from node list and append it to nodes to wrap."
11601						while (!nodesToWrap.length || (nodeList.length && nodeList[0] == nodesToWrap[nodesToWrap.length - 1].nextSibling && isInlineNode(nodeList[0]) && isInlineNode(nodesToWrap[nodesToWrap.length - 1]) && !isNamedHtmlElement(nodesToWrap[nodesToWrap.length - 1], "br"))) {
11602							nodesToWrap.push(nodeList.shift());
11603						}
11604
11605						// "Wrap nodes to wrap, with new parent instructions
11606						// returning the result of calling createElement("li") on
11607						// the context object. Append the result to sublist."
11608						sublist.push(wrap(
11609							nodesToWrap,
11610							undefined,
11611							createLi,
11612							range
11613						));
11614					}
11615				}
11616
11617				// "If sublist's first member's parent is an HTML element with
11618				// local name tag name, or if every member of sublist is an ol or
11619				// ul, continue this loop from the beginning."
11620				if (isHtmlElement_obsolete(sublist[0].parentNode, tagName) || $_(sublist).every(isOlUl)) {
11621					continue;
11622				}
11623
11624				// "If sublist's first member's parent is an HTML element with
11625				// local name other tag name:"
11626				if (isHtmlElement_obsolete(sublist[0].parentNode, otherTagName)) {
11627					// "Record the values of sublist, and let values be the
11628					// result."
11629					values = recordValues(sublist);
11630
11631					// "Split the parent of sublist."
11632					splitParent(sublist, range);
11633
11634					// "Wrap sublist, with sibling criteria returning true for an
11635					// HTML element with local name tag name and false otherwise,
11636					// and new parent instructions returning the result of calling
11637					// createElement(tag name) on the context object."
11638					wrap(
11639						sublist,
11640						makeIsElementPred(tagName),
11641						makeCreateElement(tagName),
11642						range
11643					);
11644
11645					// "Restore the values from values."
11646					restoreValues(values, range);
11647
11648					// "Continue this loop from the beginning."
11649					continue;
11650				}
11651
11652				// "Wrap sublist, with sibling criteria returning true for an HTML
11653				// element with local name tag name and false otherwise, and new
11654				// parent instructions being the following:"
11655				// . . .
11656				// "Fix disallowed ancestors of the previous step's result."
11657				fixDisallowedAncestors(wrap(
11658					sublist,
11659					makeIsElementPred(tagName),
11660					makeCreateElementSublist(tagName, sublist, range),
11661					range
11662				), range);
11663			}
11664		}
11665	}
11666
11667
11668	//@}
11669	///// Justifying the selection /////
11670	//@{
11671
11672	function justifySelection(alignment, range) {
11673
11674		// "Block-extend the active range, and let new range be the result."
11675		var newRange = blockExtend(range);
11676
11677		// "Let element list be a list of all editable Elements contained in new
11678		// range that either has an attribute in the HTML namespace whose local
11679		// name is "align", or has a style attribute that sets "text-align", or is
11680		// a center."
11681		var elementList = getAllContainedNodes(newRange, function (node) {
11682			return node.nodeType == Node.ELEMENT_NODE && isEditable(node)
11683			// Ignoring namespaces here
11684				&& (hasAttribute(node, "align") || node.style.textAlign != "" || isNamedHtmlElement(node, 'center'));
11685		});
11686
11687		// "For each element in element list:"
11688		var i;
11689		for (i = 0; i < elementList.length; i++) {
11690			var element = elementList[i];
11691
11692			// "If element has an attribute in the HTML namespace whose local name
11693			// is "align", remove that attribute."
11694			element.removeAttribute("align");
11695
11696			// "Unset the CSS property "text-align" on element, if it's set by a
11697			// style attribute."
11698			element.style.textAlign = "";
11699			if (element.getAttribute("style") == "") {
11700				element.removeAttribute("style");
11701			}
11702
11703			// "If element is a div or span or center with no attributes, remove
11704			// it, preserving its descendants."
11705			if (isHtmlElementInArray(element, ["div", "span", "center"]) && !element.attributes.length) {
11706				removePreservingDescendants(element, range);
11707			}
11708
11709			// "If element is a center with one or more attributes, set the tag
11710			// name of element to "div"."
11711			if (isNamedHtmlElement(element, 'center') && element.attributes.length) {
11712				setTagName(element, "div", range);
11713			}
11714		}
11715
11716		// "Block-extend the active range, and let new range be the result."
11717		newRange = blockExtend(globalRange);
11718
11719		// "Let node list be a list of nodes, initially empty."
11720		var nodeList = [];
11721
11722		// "For each node node contained in new range, append node to node list if
11723		// the last member of node list (if any) is not an ancestor of node; node
11724		// is editable; node is an allowed child of "div"; and node's alignment
11725		// value is not alignment."
11726		nodeList = getContainedNodes(newRange, function (node) {
11727			return isEditable(node) && isAllowedChild(node, "div") && getAlignmentValue(node) != alignment;
11728		});
11729
11730		function makeIsAlignedDiv(alignment) {
11731			return function (node) {
11732				return isNamedHtmlElement(node, 'div') && $_(node.attributes).every(function (attr) {
11733					return (attr.name == "align" && attr.value.toLowerCase() == alignment) || (attr.name == "style" && getStyleLength(node) == 1 && node.style.textAlign == alignment);
11734				});
11735			};
11736		}
11737
11738		function makeCreateAlignedDiv(alignment) {
11739			return function () {
11740				var newParent = document.createElement("div");
11741				newParent.setAttribute("style", "text-align: " + alignment);
11742				return newParent;
11743			};
11744		}
11745
11746		// "While node list is not empty:"
11747		while (nodeList.length) {
11748			// "Let sublist be a list of nodes, initially empty."
11749			var sublist = [];
11750
11751			// "Remove the first member of node list and append it to sublist."
11752			sublist.push(nodeList.shift());
11753
11754			// "While node list is not empty, and the first member of node list is
11755			// the nextSibling of the last member of sublist, remove the first
11756			// member of node list and append it to sublist."
11757			while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling) {
11758				sublist.push(nodeList.shift());
11759			}
11760
11761			// "Wrap sublist. Sibling criteria returns true for any div that has
11762			// one or both of the following two attributes and no other attributes,
11763			// and false otherwise:"
11764			//
11765			//   * "An align attribute whose value is an ASCII case-insensitive
11766			//     match for alignment.
11767			//   * "A style attribute which sets exactly one CSS property
11768			//     (including unrecognized or invalid attributes), which is
11769			//     "text-align", which is set to alignment.
11770			//
11771			// "New parent instructions are to call createElement("div") on the
11772			// context object, then set its CSS property "text-align" to alignment
11773			// and return the result."
11774			wrap(
11775				sublist,
11776				makeIsAlignedDiv(alignment),
11777				makeCreateAlignedDiv(alignment),
11778				range
11779			);
11780		}
11781	}
11782
11783	//@}
11784	///// Move the given collapsed range over adjacent zero-width whitespace characters.
11785	///// The range is
11786	//@{
11787	/**
11788	 * Move the given collapsed range over adjacent zero-width whitespace characters.
11789	 * If the range is not collapsed or is not contained in a text node, it is not modified
11790	 * @param range range to modify
11791	 * @param forward {Boolean} true to move forward, false to move backward
11792	 */
11793	function moveOverZWSP(range, forward) {
11794		var offset;
11795		if (!range.collapsed) {
11796			return;
11797		}
11798
11799		offset = range.startOffset;
11800
11801		if (forward) {
11802			// check whether the range starts in a text node
11803			if (range.startContainer && range.startContainer.nodeType === Node.TEXT_NODE) {
11804				// move forward (i.e. increase offset) as long as we stay in the text node and have zwsp characters to the right
11805				while (offset < range.startContainer.data.length && range.startContainer.data.charAt(offset) === '\u200b') {
11806					offset++;
11807				}
11808			}
11809		} else {
11810			// check whether the range starts in a text node
11811			if (range.startContainer && range.startContainer.nodeType === Node.TEXT_NODE) {
11812				// move backward (i.e. decrease offset) as long as we stay in the text node and have zwsp characters to the left
11813				while (offset > 0 && range.startContainer.data.charAt(offset - 1) === '\u200b') {
11814					offset--;
11815				}
11816			}
11817		}
11818
11819		// if the offset was changed, set it back to the collapsed range
11820		if (offset !== range.startOffset) {
11821			range.setStart(range.startContainer, offset);
11822			range.setEnd(range.startContainer, offset);
11823		}
11824	}
11825
11826	/**
11827	 * implementation of the delete command
11828	 * will attempt to delete contents within range if non-collapsed
11829	 * or delete the character left of the cursor position if range
11830	 * is collapsed. Is used to define the behaviour of the backspace
11831	 * button.
11832	 *
11833	 * @param      value   is just there for compatibility with the commands api. parameter is ignored.
11834	 * @param      range   the range to execute the delete command for
11835	 * @return     void
11836	 */
11837	commands["delete"] = {
11838		action: function (value, range) {
11839			var i,
11840				deleteContentsRange;
11841
11842			// "If the active range is not collapsed, delete the contents of the
11843			// active range and abort these steps."
11844			if (!range.collapsed) {
11845				deleteContents(range);
11846				return;
11847			}
11848
11849			// "Canonicalize whitespace at (active range's start node, active
11850			// range's start offset)."
11851			canonicalizeWhitespace(range.startContainer, range.startOffset);
11852
11853			// "Let node and offset be the active range's start node and offset."
11854			var node = range.startContainer;
11855			var offset = range.startOffset;
11856			var isBr = false;
11857			var isHr = false;
11858
11859			// "Repeat the following steps:"
11860			while (true) {
11861				// we need to reset isBr and isHr on every interation of the loop
11862				if (offset > 0) {
11863					isBr = isNamedHtmlElement(node.childNodes[offset - 1], "br") || false;
11864					isHr = isNamedHtmlElement(node.childNodes[offset - 1], "hr") || false;
11865				}
11866				// "If offset is zero and node's previousSibling is an editable
11867				// invisible node, remove node's previousSibling from its parent."
11868				if (offset == 0 && isEditable(node.previousSibling) && isInvisible(node.previousSibling)) {
11869					
11870					node.parentNode.removeChild(node.previousSibling);
11871					continue;
11872
11873				}
11874				// "Otherwise, if node has a child with index offset − 1 and that
11875				// child is an editable invisible node, remove that child from
11876				// node, then subtract one from offset."
11877				if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isEditable(node.childNodes[offset - 1]) && (isInvisible(node.childNodes[offset - 1]) || isBr || isHr)) {
11878					
11879					node.removeChild(node.childNodes[offset - 1]);
11880					offset--;
11881					
11882					if (isBr || isHr) {
11883
11884						range.setStart(node, offset);
11885						range.setEnd(node, offset);
11886
11887						return;
11888					}
11889
11890					continue;
11891
11892
11893				}
11894				// "Otherwise, if offset is zero and node is an inline node, or if
11895				// node is an invisible node, set offset to the index of node, then
11896				// set node to its parent."
11897				if ((offset == 0 && isInlineNode(node)) || isInvisible(node)) {
11898					
11899					offset = getIndexInParent(node);
11900					node = node.parentNode;
11901
11902					continue;
11903
11904				}
11905				// "Otherwise, if node has a child with index offset − 1 and that
11906				// child is an editable a, remove that child from node, preserving
11907				// its descendants. Then abort these steps."
11908				if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isEditable(node.childNodes[offset - 1]) && isNamedHtmlElement(node.childNodes[offset - 1], "a")) {
11909
11910					removePreservingDescendants(node.childNodes[offset - 1], range);
11911
11912					return;
11913
11914				}
11915				// "Otherwise, if node has a child with index offset − 1 and that
11916				// child is not a block node or a br or an img, set node to that
11917				// child, then set offset to the length of node."
11918				if (0 <= offset - 1 && offset - 1 < node.childNodes.length && !isBlockNode(node.childNodes[offset - 1]) && !isHtmlElementInArray(node.childNodes[offset - 1], ["br", "img"])) {
11919				
11920					node = node.childNodes[offset - 1];
11921					offset = getNodeLength(node);
11922
11923					continue;
11924
11925				}
11926				// "Otherwise, break from this loop."
11927				// brk is a quick and dirty jslint workaround since I don't want to rewrite this loop
11928				var brk = true;
11929
11930				if (brk) {
11931					break;
11932				}
11933			}
11934
11935			// if the previous node is an aloha-table we want to delete it
11936			var delBlock = getBlockAtPreviousPosition(node, offset);
11937
11938			if (delBlock) {
11939				
11940				delBlock.parentNode.removeChild(delBlock);
11941				return;
11942
11943			}
11944
11945			// "If node is a Text node and offset is not zero, call collapse(node,
11946			// offset) on the Selection. Then delete the contents of the range with
11947			// start (node, offset − 1) and end (node, offset) and abort these
11948			// steps."
11949			if (node.nodeType == Node.TEXT_NODE && offset != 0) {
11950
11951				range.setStart(node, offset - 1);
11952				range.setEnd(node, offset - 1);
11953				deleteContentsRange = deleteContents(node, offset - 1, node, offset);
11954
11955				if (deleteContentsRange) {
11956					// The new range position can only be determined inside deleteContents,
11957					// that's why it's necessary to take over the range if one was returned.
11958					range.setStart(deleteContentsRange.startContainer, deleteContentsRange.startOffset);
11959					range.setEnd(deleteContentsRange.endContainer, deleteContentsRange.endOffset);
11960				}
11961
11962				return;
11963
11964			}
11965
11966			// @iebug
11967			// when inserting a special char via the plugin
11968			// there where problems deleting them again with backspace after insertation
11969			// see https://github.com/alohaeditor/Aloha-Editor/issues/517
11970			if (node.nodeType == Node.TEXT_NODE && offset == 0 && isMSIE) {
11971
11972				offset = 1;
11973				range.setStart(node, offset);
11974				range.setEnd(node, offset);
11975				range.startOffset = 0;
11976				deleteContents(range);
11977
11978				return;
11979
11980			}
11981
11982			// "If node is an inline node, abort these steps."
11983			if (isInlineNode(node)) {
11984				
11985				return;
11986
11987			}
11988
11989			// "If node has a child with index offset − 1 and that child is a br or
11990			// hr or img, call collapse(node, offset) on the Selection. Then delete
11991			// the contents of the range with start (node, offset − 1) and end
11992			// (node, offset) and abort these steps."
11993			if (0 <= offset - 1 && offset - 1 < node.childNodes.length && isHtmlElementInArray(node.childNodes[offset - 1], ["br", "hr", "img"])) {
11994				
11995				range.setStart(node, offset);
11996				range.setEnd(node, offset);
11997				deleteContents(range);
11998
11999				return;
12000
12001			}
12002
12003			// "If node is an li or dt or dd and is the first child of its parent,
12004			// and offset is zero:"
12005			if (isHtmlElementInArray(node, ["li", "dt", "dd"]) && node == node.parentNode.firstChild && offset == 0) {
12006				// "Let items be a list of all lis that are ancestors of node."
12007				//
12008				// Remember, must be in tree order.
12009				var items = [];
12010				var ancestor;
12011				for (ancestor = node.parentNode; ancestor; ancestor = ancestor.parentNode) {
12012					if (isNamedHtmlElement(ancestor, 'li')) {
12013						items.unshift(ancestor);
12014					}
12015				}
12016
12017				// "Normalize sublists of each item in items."
12018				for (i = 0; i < items.length; i++) {
12019					normalizeSublists(items[i], range);
12020				}
12021
12022				// "Record the values of the one-node list consisting of node, and
12023				// let values be the result."
12024				var values = recordValues([node]);
12025
12026				// "Split the parent of the one-node list consisting of node."
12027				splitParent([node], range);
12028
12029				// "Restore the values from values."
12030				restoreValues(values, range);
12031
12032				// "If node is a dd or dt, and it is not an allowed child of any of
12033				// its ancestors in the same editing host, set the tag name of node
12034				// to the default single-line container name and let node be the
12035				// result."
12036				if (isHtmlElementInArray(node, ["dd", "dt"]) && $_(getAncestors(node)).every(function (ancestor) { return !inSameEditingHost(node, ancestor) || !isAllowedChild(node, ancestor); })) {
12037					node = setTagName(node, defaultSingleLineContainerName, range);
12038				}
12039
12040				// "Fix disallowed ancestors of node."
12041				fixDisallowedAncestors(node, range);
12042
12043				// fix the lists to be html5 conformant
12044				for (i = 0; i < items.length; i++) {
12045					unNormalizeSublists(items[i].parentNode, range);
12046				}
12047
12048				// "Abort these steps."
12049				return;
12050			}
12051
12052			// "Let start node equal node and let start offset equal offset."
12053			var startNode = node;
12054			var startOffset = offset;
12055
12056			// "Repeat the following steps:"
12057			while (true) {
12058				// "If start offset is zero, set start offset to the index of start
12059				// node and then set start node to its parent."
12060				if (startOffset == 0) {
12061					startOffset = getIndexInParent(startNode);
12062					startNode = startNode.parentNode;
12063
12064					// "Otherwise, if start node has an editable invisible child with
12065					// index start offset minus one, remove it from start node and
12066					// subtract one from start offset."
12067				} else if (0 <= startOffset - 1 && startOffset - 1 < startNode.childNodes.length && isEditable(startNode.childNodes[startOffset - 1]) && isInvisible(startNode.childNodes[startOffset - 1])
12067) {
12068					startNode.removeChild(startNode.childNodes[startOffset - 1]);
12069					startOffset--;
12070
12071					// "Otherwise, break from this loop."
12072				} else {
12073					break;
12074				}
12075			}
12076
12077			// "If offset is zero, and node has an editable ancestor container in
12078			// the same editing host that's an indentation element:"
12079			if (offset == 0 && $_(getAncestors(node).concat(node)).filter(function (ancestor) { return isEditable(ancestor) && inSameEditingHost(ancestor, node) && isIndentationElement(ancestor); }).length) {
12080				// "Block-extend the range whose start and end are both (node, 0),
12081				// and let new range be the result."
12082				var newRange = _core.rangy.createRange();
12083				newRange.setStart(node, 0);
12084				newRange.setEnd(node, 0);
12085				newRange = blockExtend(newRange);
12086
12087				// "Let node list be a list of nodes, initially empty."
12088				//
12089				// "For each node current node contained in new range, append
12090				// current node to node list if the last member of node list (if
12091				// any) is not an ancestor of current node, and current node is
12092				// editable but has no editable descendants."
12093				var nodeList = getContainedNodes(newRange, function (currentNode) {
12094					return isEditable(currentNode) && !hasEditableDescendants(currentNode);
12095				});
12096
12097				// "Outdent each node in node list."
12098				for (i = 0; i < nodeList.length; i++) {
12099					outdentNode(nodeList[i], range);
12100				}
12101
12102				// "Abort these steps."
12103				return;
12104			}
12105
12106			// "If the child of start node with index start offset is a table,
12107			// abort these steps."
12108			if (isNamedHtmlElement(startNode.childNodes[startOffset], "table")) {
12109				return;
12110			}
12111
12112			// "If start node has a child with index start offset − 1, and that
12113			// child is a table:"
12114			if (0 <= startOffset - 1 && startOffset - 1 < startNode.childNodes.length && isNamedHtmlElement(startNode.childNodes[startOffset - 1], "table")) {
12115				// "Call collapse(start node, start offset − 1) on the context
12116				// object's Selection."
12117				range.setStart(startNode, startOffset - 1);
12118
12119				// "Call extend(start node, start offset) on the context object's
12120				// Selection."
12121				range.setEnd(startNode, startOffset);
12122
12123				// "Abort these steps."
12124				return;
12125			}
12126
12127			// "If offset is zero; and either the child of start node with index
12128			// start offset minus one is an hr, or the child is a br whose
12129			// previousSibling is either a br or not an inline node:"
12130			if (offset == 0
12131				    && (isNamedHtmlElement(startNode.childNodes[startOffset - 1], "hr")
12132						|| (isNamedHtmlElement(startNode.childNodes[startOffset - 1], "br")
12133							&& (isNamedHtmlElement(startNode.childNodes[startOffset - 1].previousSibling, "br")
12134								|| !isInlineNode(startNode.childNodes[startOffset - 1].previousSibling))))) {
12135				// "Call collapse(node, offset) on the Selection."
12136				range.setStart(node, offset);
12137				range.setEnd(node, offset);
12138
12139				// "Delete the contents of the range with start (start node, start
12140				// offset − 1) and end (start node, start offset)."
12141				deleteContents(startNode, startOffset - 1, startNode, startOffset);
12142
12143				// "Abort these steps."
12144				return;
12145			}
12146
12147			// "If the child of start node with index start offset is an li or dt
12148			// or dd, and that child's firstChild is an inline node, and start
12149			// offset is not zero:"
12150			if (isHtmlElementInArray(startNode.childNodes[startOffset], ["li", "dt", "dd"]) && isInlineNode(startNode.childNodes[startOffset].firstChild) && startOffset != 0) {
12151				// "Let previous item be the child of start node with index start
12152				// offset minus one."
12153				var previousItem = startNode.childNodes[startOffset - 1];
12154
12155				// "If previous item's lastChild is an inline node other than a br,
12156				// call createElement("br") on the context object and append the
12157				// result as the last child of previous item."
12158				if (isInlineNode(previousItem.lastChild) && !isNamedHtmlElement(previousItem.lastChild, "br")) {
12159					previousItem.appendChild(document.createElement("br"));
12160				}
12161
12162				// "If previous item's lastChild is an inline node, call
12163				// createElement("br") on the context object and append the result
12164				// as the last child of previous item."
12165				if (isInlineNode(previousItem.lastChild)) {
12166					previousItem.appendChild(document.createElement("br"));
12167				}
12168			}
12169
12170			// "If the child of start node with index start offset is an li or dt
12171			// or dd, and its previousSibling is also an li or dt or dd, set start
12172			// node to its child with index start offset − 1, then set start offset
12173			// to start node's length, then set node to start node's nextSibling,
12174			// then set offset to 0."
12175			if (isHtmlElementInArray(startNode.childNodes[startOffset], ["li", "dt", "dd"]) && isHtmlElementInArray(startNode.childNodes[startOffset - 1], ["li", "dt", "dd"])) {
12176				startNode = startNode.childNodes[startOffset - 1];
12177				startOffset = getNodeLength(startNode);
12178				node = startNode.nextSibling;
12179				offset = 0;
12180
12181				// "Otherwise, while start node has a child with index start offset
12182				// minus one:"
12183			} else {
12184				while (0 <= startOffset - 1 && startOffset - 1 < startNode.childNodes.length) {
12185					// "If start node's child with index start offset minus one is
12186					// editable and invisible, remove it from start node, then
12187					// subtract one from start offset."
12188					if (isEditable(startNode.childNodes[startOffset - 1]) && isInvisible(startNode.childNodes[startOffset - 1])) {
12189						startNode.removeChild(startNode.childNodes[startOffset - 1]);
12190						startOffset--;
12191
12192						// "Otherwise, set start node to its child with index start
12193						// offset minus one, then set start offset to the length of
12194						// start node."
12195					} else {
12196						startNode = startNode.childNodes[startOffset - 1];
12197						startOffset = getNodeLength(startNode);
12198					}
12199				}
12200			}
12201
12202			// "Delete the contents of the range with start (start node, start
12203			// offset) and end (node, offset)."
12204			var delRange = _core.rangy.createRange();
12205			delRange.setStart(startNode, startOffset);
12206			delRange.setEnd(node, offset);
12207			deleteContents(delRange);
12208
12209			if (!isAncestorContainer(document.body, range.startContainer)) {
12210				if (delRange.startContainer.hasChildNodes()
12211						|| delRange.startContainer.nodeType == Node.TEXT_NODE
12212							|| isEditingHost(delRange.startContainer)) {
12213					range.setStart(delRange.startContainer, delRange.startOffset);
12214					range.setEnd(delRange.startContainer, delRange.startOffset);
12215				} else {
12216					range.setStart(delRange.startContainer.parentNode, getIndexInParent(delRange.startContainer));
12217					range.setEnd(delRange.startContainer.parentNode, getIndexInParent(delRange.startContainer));
12218				}
12219			}
12220		}
12221	};
12222
12223	//@}
12224	///// The formatBlock command /////
12225	//@{
12226	// "A formattable block name is "address", "dd", "div", "dt", "h1", "h2", "h3",
12227	// "h4", "h5", "h6", "p", or "pre"."
12228	var formattableBlockNames = ["address", "dd", "div", "dt", "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre", "deleteme"];
12229
12230	commands.formatblock = {
12231		action: function (value) {
12232			var i;
12233
12234			// "If value begins with a "<" character and ends with a ">" character,
12235			// remove the first and last characters from it."
12236			if (/^<.*>$/.test(value)) {
12237				value = value.slice(1, -1);
12238			}
12239
12240			// "Let value be converted to ASCII lowercase."
12241			value = value.toLowerCase();
12242
12243			// "If value is not a formattable block name, abort these steps and do
12244			// nothing."
12245			if ($_(formattableBlockNames).indexOf(value) == -1) {
12246				return;
12247			}
12248
12249			// "Block-extend the active range, and let new range be the result."
12250			var newRange = blockExtend(getActiveRange());
12251
12252			// "Let node list be an empty list of nodes."
12253			//
12254			// "For each node node contained in new range, append node to no
12254de list
12255			// if it is editable, the last member of original node list (if any) is
12256			// not an ancestor of node, node is either a non-list single-line
12257			// container or an allowed child of "p" or a dd or dt, and node is not
12258			// the ancestor of a prohibited paragraph child."
12259			var nodeList = getContainedNodes(newRange, function (node) {
12260				return isEditable(node) && (isNonListSingleLineContainer(node) || isAllowedChild(node, "p") || isHtmlElementInArray(node, ["dd", "dt"])) && !$_(getDescendants(node)).some(isProhibitedParagraphChild);
12261			});
12262
12263			// "Record the values of node list, and let values be the result."
12264			var values = recordValues(nodeList);
12265
12266			function makeIsEditableElementInSameEditingHostDoesNotContainProhibitedParagraphChildren(node) {
12267				return function (ancestor) {
12268					return (isEditable(ancestor)
12269							&& inSameEditingHost(ancestor, node)
12270							&& isHtmlElement_obsolete(ancestor, formattableBlockNames)
12271							&& !$_(getDescendants(ancestor)).some(isProhibitedParagraphChild));
12272				};
12273			}
12274
12275			function makeIsElementWithoutAttributes(value) {
12276				return function (node) {
12277					return isHtmlElement_obsolete(node, value) && !node.attributes.length;
12278				};
12279			}
12280
12281			function returnFalse() {
12282				return false;
12283			}
12284
12285			function makeCreateElement(value) {
12286				return function () {
12287					return document.createElement(value);
12288				};
12289			}
12290
12291			// "For each node in node list, while node is the descendant of an
12292			// editable HTML element in the same editing host, whose local name is
12293			// a formattable block name, and which is not the ancestor of a
12294			// prohibited paragraph child, split the parent of the one-node list
12295			// consisting of node."
12296			for (i = 0; i < nodeList.length; i++) {
12297				var node = nodeList[i];
12298				while ($_(getAncestors(node)).some(makeIsEditableElementInSameEditingHostDoesNotContainProhibitedParagraphChildren(node))) {
12299					splitParent([node], newRange);
12300				}
12301			}
12302
12303			// "Restore the values from values."
12304			restoreValues(values, newRange);
12305
12306			// "While node list is not empty:"
12307			while (nodeList.length) {
12308				var sublist;
12309
12310				// "If the first member of node list is a single-line
12311				// container:"
12312				if (isSingleLineContainer(nodeList[0])) {
12313					// "Let sublist be the children of the first member of node
12314					// list."
12315					sublist = [].slice.call(toArray(nodeList[0].childNodes));
12316
12317					// "Record the values of sublist, and let values be the
12318					// result."
12319					values = recordValues(sublist);
12320
12321					// "Remove the first member of node list from its parent,
12322					// preserving its descendants."
12323					removePreservingDescendants(nodeList[0], newRange);
12324
12325					// "Restore the values from values."
12326					restoreValues(values, newRange);
12327
12328					// "Remove the first member from node list."
12329					nodeList.shift();
12330
12331					// "Otherwise:"
12332				} else {
12333					// "Let sublist be an empty list of nodes."
12334					sublist = [];
12335
12336					// "Remove the first member of node list and append it to
12337					// sublist."
12338					sublist.push(nodeList.shift());
12339
12340					// "While node list is not empty, and the first member of
12341					// node list is the nextSibling of the last member of
12342					// sublist, and the first member of node list is not a
12343					// single-line container, and the last member of sublist is
12344					// not a br, remove the first member of node list and
12345					// append it to sublist."
12346					while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling && !isSingleLineContainer(nodeList[0]) && !isNamedHtmlElement(sublist[sublist.length - 1], "BR")) {
12347						sublist.push(nodeList.shift());
12348					}
12349				}
12350
12351				// "Wrap sublist. If value is "div" or "p", sibling criteria
12352				// returns false; otherwise it returns true for an HTML element
12353				// with local name value and no attributes, and false otherwise.
12354				// New parent instructions return the result of running
12355				// createElement(value) on the context object. Then fix disallowed
12356				// ancestors of the result."
12357				fixDisallowedAncestors(wrap(
12358					sublist,
12359					jQuery.inArray(value, ["div", "p"]) == -1 ? makeIsElementWithoutAttributes(value) : returnFalse,
12360					makeCreateElement(value),
12361					newRange
12362				), newRange);
12363			}
12364		},
12365
12366		indeterm: function () {
12367			// "Block-extend the active range, and let new range be the result."
12368			var newRange = blockExtend(getActiveRange());
12369
12370			// "Let node list be all visible editable nodes that are contained in
12371			// new range and have no children."
12372			var nodeList = getAllContainedNodes(newRange, function (node) {
12373				return isVisible(node) && isEditable(node) && !node.hasChildNodes();
12374			});
12375
12376			// "If node list is empty, return false."
12377			if (!nodeList.length) {
12378				return false;
12379			}
12380
12381			// "Let type be null."
12382			var type = null;
12383
12384			// "For each node in node list:"
12385			var i;
12386			for (i = 0; i < nodeList.length; i++) {
12387				var node = nodeList[i];
12388
12389				// "While node's parent is editable and in the same editing host as
12390				// node, and node is not an HTML element whose local name is a
12391				// formattable block name, set node to its parent."
12392				while (isEditable(node.parentNode) && inSameEditingHost(node, node.parentNode) && !isHtmlElement_obsolete(node, formattableBlockNames)) {
12393					node = node.parentNode;
12394				}
12395
12396				// "Let current type be the empty string."
12397				var currentType = "";
12398
12399				// "If node is an editable HTML element whose local name is a
12400				// formattable block name, and node is not the ancestor of a
12401				// prohibited paragraph child, set current type to node's local
12402				// name."
12403				if (isEditable(node) && isHtmlElement_obsolete(node, formattableBlockNames) && !$_(getDescendants(node)).some(isProhibitedParagraphChild)) {
12404					currentType = node.tagName;
12405				}
12406
12407				// "If type is null, set type to current type."
12408				if (type === null) {
12409					type = currentType;
12410
12411					// "Otherwise, if type does not equal current type, return true."
12412				} else if (type != currentType) {
12413					return true;
12414				}
12415			}
12416
12417			// "Return false."
12418			return false;
12419		},
12420		value: function () {
12421			// "Block-extend the active range, and let new range be the result."
12422			var newRange = blockExtend(getActiveRange());
12423
12424			// "Let node be the first visible editable node that is contained in
12425			// new range and has no children. If there is no such node, return the
12426			// empty string."
12427			var nodes = getAllContainedNodes(newRange, function (node) {
12428				return isVisible(node) && isEditable(node) && !node.hasChildNodes();
12429			});
12430			if (!nodes.length) {
12431				return "";
12432			}
12433			var node = nodes[0];
12434
12435			// "While node's parent is editable and in the same editing host as
12436			// node, and node is not an HTML element whose local name is a
12437			// formattable block name, set node to its parent."
12438			while (isEditable(node.parentNode) && inSameEditingHost(node, node.parentNode) && !isHtmlElement_obsolete(node, formattableBlockNames)) {
12439				node = node.parentNode;
12440			}
12441
12442			// "If node is an editable HTML element whose local name is a
12443			// formattable block name, and node is not the ancestor of a prohibited
12444			// paragraph child, return node's local name, converted to ASCII
12445			// lowercase."
12446			if (isEditable(node) && isHtmlElement_obsolete(node, formattableBlockNames) && !$_(getDescendants(node)).some(isProhibitedParagraphChild)) {
12447				return node.tagName.toLowerCase();
12448			}
12449
12450			// "Return the empty string."
12451			return "";
12452		}
12453	};
12454
12455	//@}
12456	///// The forwardDelete command /////
12457	//@{
12458	commands.forwarddelete = {
12459		action: function (value, range) {
12460
12461			// "If the active range is not collapsed, delete the contents of the
12462			// active range and abort these steps."
12463			if (!range.collapsed) {
12464				deleteContents(range);
12465				return;
12466			}
12467
12468			// "Canonicalize whitespace at (active range's start node, active
12469			// range's start offset)."
12470			canonicalizeWhitespace(range.startContainer, range.startOffset);
12471
12472			// "Let node and offset be the active range's start node and offset."
12473			var node = range.startContainer;
12474			var offset = range.startOffset;
12475			var isBr = false;
12476			var isHr = false;
12477
12478			// special behaviour: if we are in an empty block-level element (empty, but for the single ending br), we just remove that
12479			// this will fix situations where it was not possible to delete an empty block level element right before a non-editable part
12480			if (isEmptyBlockLevelElement(node)) {
12481				range.startContainer = node.parentElement;
12482				range.startOffset = getIndexInParent(node);
12483				range.endContainer = node.parentElement;
12484				range.endOffset = range.startOffset + 1;
12485				deleteContents(range);
12486				return;
12487			}
12488
12489			// "Repeat the following steps:"
12490			while (true) {
12491				// check whether the next element is a br or hr
12492				// Commented out for unknown reason.
12493				//if (offset < node.childNodes.length) {
12494				//				isBr = isHtmlElement_obsolete(node.childNodes[offset], "br") || false;
12495				//				isHr = isHtmlElement_obsolete(node.childNodes[offset], "hr") || false;
12496				//}
12497
12498				// "If offset is the length of node and node's nextSibling is an
12499				// editable invisible node, remove node's nextSibling from its
12500				// parent."
12501				if (offset == getNodeLength(node) && isEditable(node.nextSibling) && isInvisible(node.nextSibling)) {
12502					node.parentNode.removeChild(node.nextSibling);
12503
12504					// "Otherwise, if node has a child with index offset and that child
12505					// is an editable invisible node, remove that child from node."
12506				} else if (offset < node.childNodes.length && isEditable(node.childNodes[offset]) && (isInvisible(node.childNodes[offset]) || isBr || isHr)) {
12507					node.removeChild(node.childNodes[offset]);
12508					if (isBr || isHr) {
12509						ensureContainerEditable(node);
12510						range.setStart(node, offset);
12511						range.setEnd(node, offset);
12512						return;
12513					}
12514
12515					// "Otherwise, if node has a child with index offset and that child
12516					// is a collapsed block prop, add one to offset."
12517				} else if (offset < node.childNodes.length && isCollapsedBlockProp(node.childNodes[offset])) {
12518					offset++;
12519
12520					// "Otherwise, if offset is the length of node and node is an
12521					// inline node, or if node is invisible, set offset to one plus the
12522					// index of node, then set node to its parent."
12523				} else if ((offset == getNodeLength(node) && isInlineNode(node)) || isInvisible(node)) {
12524					offset = 1 + getIndexInParent(node);
12525					node = node.parentNode;
12526
12527					// "Otherwise, if node has a child with index offset and that child
12528					// is not a block node or a br or an img, set node to that child,
12529					// then set offset to zero."
12530				} else if (offset < node.childNodes.length && !isBlockNode(node.childNodes[offset]) && !isHtmlElementInArray(node.childNodes[offset], ["br", "img"])) {
12531					node = node.childNodes[offset];
12532					offset = 0;
12533
12534					// "Otherwise, break from this loop."
12535				} else {
12536					break;
12537				}
12538			}
12539
12540			// collapse whitespace in the node, if it is a text node
12541			canonicalizeWhitespace(range.startContainer, range.startOffset);
12542
12543			// if the next node is an aloha-table we want to delete it
12544			var delBlock = getBlockAtNextPosition(node, offset);
12545			if (delBlock) {
12546				delBlock.parentNode.removeChild(delBlock);
12547				return;
12548			}
12549
12550			var endOffset;
12551			// "If node is a Text node and offset is not node's length:"
12552			if (node.nodeType == Node.TEXT_NODE && offset != getNodeLength(node)) {
12553				// "Call collapse(node, offset) on the Selection."
12554				range.setStart(node, offset);
12555				range.setEnd(node, offset);
12556
12557				// "Let end offset be offset plus one."
12558				endOffset = offset + 1;
12559
12560				// "While end offset is not node's length and the end offsetth
12561				// element of node's data has general category M when interpreted
12562				// as a Unicode code point, add one to end offset."
12563				//
12564				// TODO: Not even going to try handling anything beyond the most
12565				// basic combining marks, since I couldn't find a good list.  I
12566				// special-case a few Hebrew diacritics too to test basic coverage
12567				// of non-Latin stuff.
12568				while (endOffset != node.length && /^[\u0300-\u036f\u0591-\u05bd\u05c1\u05c2]$/.test(node.data[endOffset])) {
12569					endOffset++;
12570				}
12571
12572				// "Delete the contents of the range with start (node, offset) and
12573				// end (node, end offset)."
12574				deleteContents(node, offset, node, endOffset);
12575
12576				// "Abort these steps."
12577				return;
12578			}
12579
12580			// "If node has a child with index offset and that child is a br or hr
12581			// or img, call collapse(node, offset) on the Selection. Then delete
12582			// the contents of the range with start (node, offset) and end (node,
12583			// offset + 1) and abort these steps."
12584			if (offset < node.childNodes.length && isHtmlElementInArray(node.childNodes[offset], ["br", "hr", "img"])) {
12585				range.setStart(node, offset);
12586				range.setEnd(node, offset);
12587				deleteContents(node, offset, node, offset + 1);
12588				return;
12589			}
12590
12591			// "If node is an inline node, abort these steps."
12592			if (isInlineNode(node)) {
12593				return;
12594			}
12595
12596			// "Let end node equal node and let end offset equal offset."
12597			var endNode = node;
12598			endOffset = offset;
12599
12600			// "Repeat the following steps:"
12601			while (true) {
12602				// "If end offset is the length of end node, set end offset to one
12603				// plus the index of end node and then set end node to its parent."
12604				if (endOffset == getNodeLength(endNode)) {
12605					endOffset = 1 + getIndexInParent(endNode);
12606					endNode = endNode.parentNode;
12607
12608					// "Otherwise, if end node has a an editable invisible child with
12609					// index end offset, remove it from end node."
12610				} else if (endOffset < endNode.childNodes.length && isEditable(endNode.childNodes[endOffset]) && isInvisible(endNode.childNodes[endOffset])) {
12611					endNode.removeChild(endNode.childNodes[endOffset]);
12612
12613					// "Otherwise, break from this loop."
12614				} else {
12615					break;
12616				}
12617			}
12618
12619			// "If the child of end node with index end offset minus one is a
12620			// table, abort these steps."
12621			if (isNamedHtmlElement(endNode.childNodes[endOffset - 1], "table")) {
12622				return;
12623			}
12624
12625			// "If the child of end node with index end offset is a table:"
12626			if (isNamedHtmlElement(endNode.childNodes[endOffset], "table")) {
12627				// "Call collapse(end node, end offset) on the context object's
12628				// Selection."
12629				range.setStart(endNode, endOffset);
12630
12631				// "Call extend(end node, end offset + 1) on the context object's
12632				// Selection."
12633				range.setEnd(endNode, endOffset + 1);
12634
12635				// "Abort these steps."
12636				return;
12637			}
12638
12639			// "If offset is the length of node, and the child of end node with
12640			// index end offset is an hr or br:"
12641			if (offset == getNodeLength(node) && isHtmlElementInArray(endNode.childNodes[endOffset], ["br", "hr"])) {
12642				// "Call collapse(node, offset) on the Selection."
12643				range.setStart(node, offset);
12644				range.setEnd(node, offset);
12645
12646				// "Delete the contents of the range with end (end node, end
12647				// offset) and end (end node, end offset + 1)."
12648				deleteContents(endNode, endOffset, endNode, endOffset + 1);
12649
12650				// "Abort these steps."
12651				return;
12652			}
12653
12654			// "While end node has a child with index end offset:"
12655			while (endOffset < endNode.childNodes.length) {
12656				// "If end node's child with index end offset is editable and
12657				// invisible, remove it from end node."
12658				if (isEditable(endNode.childNodes[endOffset]) && isInvisible(endNode.childNodes[endOffset])) {
12659					endNode.removeChild(endNode.childNodes[endOffset]);
12660
12661					// "Otherwise, set end node to its child with index end offset and
12662					// set end offset to zero."
12663				} else {
12664					endNode = endNode.childNodes[endOffset];
12665					endOffset = 0;
12666				}
12667			}
12668
12669			// "Delete the contents of the range with start (node, offset) and end
12670			// (end node, end offset)."
12671			var newRange = deleteContents(node, offset, endNode, endOffset);
12672			range.setStart(newRange.startContainer, newRange.startOffset);
12673			range.setEnd(newRange.endContainer, newRange.endOffset);
12674		}
12675	};
12676
12677	//@}
12678	///// The indent command /////
12679	//@{
12680	commands.indent = {
12681		action: function () {
12682			// "Let items be a list of all lis that are ancestor containers of the
12683			// active range's start and/or end node."
12684			//
12685			// Has to be in tree order, remember!
12686			var items = [];
12687			var node;
12688			for (node = getActiveRange().endContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {
12689				if (isNamedHtmlElement(node, "LI")) {
12690					items.unshift(node);
12691				}
12692			}
12693			for (node = getActiveRange().startContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {
12694				if (isNamedHtmlElement(node, "LI")) {
12695					items.unshift(node);
12696				}
12697			}
12698			for (node = getActiveRange().commonAncestorContainer; node; node = node.parentNode) {
12699				if (isNamedHtmlElement(node, "LI")) {
12700					items.unshift(node);
12701				}
12702			}
12703
12704			// "For each item in items, normalize sublists of item."
12705			var i;
12706			for (i = 0; i < items.length; i++) {
12707				normalizeSublists(items[i], getActiveRange());
12708			}
12709
12710			// "Block-extend the active range, and let new range be the result."
12711			var newRange = blockExtend(getActiveRange());
12712
12713			// "Let node list be a list of nodes, initially empty."
12714			var nodeList = [];
12715
12716			// "For each node node contained in new range, if node is editable and
12717			// is an allowed child of "div" or "ol" and if the last member of node
12718			// list (if any) is not an ancestor of node, append node to node list."
12719			nodeList = getContainedNodes(newRange, function (node) {
12720				return isEditable(node) && (isAllowedChild(node, "div") || isAllowedChild(node, "ol"));
12721			});
12722
12723			// "If the first member of node list is an li whose parent is an ol or
12724			// ul, and its previousSibling is an li as well, normalize sublists of
12725			// its previousSibling."
12726			if (nodeList.length && isNamedHtmlElement(nodeList[0], "LI") && isHtmlElementInArray(nodeList[0].parentNode, ["OL", "UL"]) && isNamedHtmlElement(nodeList[0].previousSibling, "LI")) {
12727				normalizeSublists(nodeList[0].previousSibling, newRange);
12728			}
12729
12730			// "While node list is not empty:"
12731			while (nodeList.length) {
12732				// "Let sublist be a list of nodes, initially empty."
12733				var sublist = [];
12734
12735				// "Remove the first member of node list and append it to sublist."
12736				sublist.push(nodeList.shift());
12737
12738				// "While the first member of node list is the nextSibling of the
12739				// last member of sublist, remove the first member of node list and
12740				// append it to sublist."
12741				while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling) {
12742					sublist.push(nodeList.shift());
12743				}
12744
12745				// "Indent sublist."
12746				indentNodes(sublist, newRange);
12747			}
12748		}
12749	};
12750
12751	//@}
12752	///// The insertHorizontalRule command /////
12753	//@{
12754	commands.inserthorizontalrule = {
12755		action: function (value, range) {
12756
12757			// "While range's start offset is 0 and its start node's parent is not
12758			// null, set range's start to (parent of start node, index of start
12759			// node)."
12760			while (range.startOffset == 0 && range.startContainer.parentNode) {
12761				range.setStart(range.startContainer.parentNode, getIndexInParent(range.startContainer));
12762			}
12763
12764			// "While range's end offset is the length of its end node, and its end
12765			// node's parent is not null, set range's end to (parent of end node, 1
12766			// + index of start node)."
12767			while (range.endOffset == getNodeLength(range.endContainer) && range.endContainer.parentNode) {
12768				range.setEnd(range.endContainer.parentNode, 1 + getIndexInParent(range.endContainer));
12769			}
12770
12771			// "Delete the contents of range, with block merging false."
12772			deleteContents(range, {
12773				blockMerging: false
12774			});
12775
12776			// "If the active range's start node is neither editable nor an editing
12777			// host, abort these steps."
12778			if (!isEditable(getActiveRange().startContainer) && !isEditingHost(getActiveRange().startContainer)) {
12779				return;
12780			}
12781
12782			// "If the active range's start node is a Text node and its start
12783			// offset is zero, set the active range's start and end to (parent of
12784			// start node, index of start node)."
12785			if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE && getActiveRange().startOffset == 0) {
12786				getActiveRange().setStart(getActiveRange().startContainer.parentNode, getIndexInParent(getActiveRange().startContainer));
12787				getActiveRange().collapse(true);
12788			}
12789
12790			// "If the active range's start node is a Text node and its start
12791			// offset is the length of its start node, set the active range's start
12792			// and end to (parent of start node, 1 + index of start node)."
12793			if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE && getActiveRange().startOffset == getNodeLength(getActiveRange().startContainer)) {
12794				getActiveRange().setStart(getActiveRange().startContainer.parentNode, 1 + getIndexInParent(getActiveRange().startContainer));
12795				getActiveRange().collapse(true);
12796			}
12797
12798			// "Let hr be the result of calling createElement("hr") on the
12799			// context object."
12800			var hr = document.createElement("hr");
12801
12802			// "Run insertNode(hr) on the range."
12803			range.insertNode(hr);
12804
12805			// "Fix disallowed ancestors of hr."
12806			fixDisallowedAncestors(hr, range);
12807
12808			// "Run collapse() on the Selection, with first argument equal to the
12809			// parent of hr and the second argument equal to one plus the index of
12810			// hr."
12811			//
12812			// Not everyone actually supports collapse(), so we do it manually
12813			// instead.  Also, we need to modify the actual range we're given as
12814			// well, for the sake of autoimplementation.html's range-filling-in.
12815			range.setStart(hr.parentNode, 1 + getIndexInParent(hr));
12816			range.setEnd(hr.parentNode, 1 + getIndexInParent(hr));
12817			_core.rangy.getSelection().removeAllRanges();
12818			_core.rangy.getSelection().addRange(range);
12819		}
12820	};
12821
12822	/**
12823	 * Publish a message for the links that will be inserted at paste.
12824	 * @param {DocumentFragment} frag
12825	 */
12826	function publishPastedLinks(frag) {
12827		var children = frag.childNodes;
12828		var links;
12829		var c;
12830		var i;
12831		var len;
12832
12833		for (c = 0; c < children.length; c++) {
12834			links = jQuery(children[c]).find('a');
12835			for (i = 0, len = links.length; i < len; i++) {
12836				console.log('link pasted');
12837			}
12838		}
12839	}
12840
12841	//@}
12842	///// The insertHTML command /////
12843	//@{
12844	commands.inserthtml = {
12845		action: function (raw, range) {
12846
12847			var editable = getEditingHostOf(range.commonAncestorContainer);
12848			var value = editable ? ContentRules.applyRules(raw, editable) : raw;
12849
12850			// "Delete the contents of the active range."
12851			deleteContents(range);
12852
12853			// "If the active range's start node is neither editable nor an editing
12854			// host, abort these steps."
12855			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
12856				return;
12857			}
12858
12859			// "Let frag be the result of calling createContextualFragment(value)
12860			// on the active range."
12861			var frag = range.createContextualFragment(value);
12862
12863			publishPastedLinks(frag);
12864
12865			// "Let last child be the lastChild of frag."
12866			var lastChild = frag.lastChild;
12867
12868			// "If last child is null, abort these steps."
12869			if (!lastChild) {
12870				return;
12871			}
12872
12873			// "Let descendants be all descendants of frag."
12874			var descendants = getDescendants(frag);
12875
12876			// "If the active range's start node is a block node:"
12877			if (isBlockNode(range.startContainer)) {
12878				// "Let collapsed block props be all editable collapsed block prop
12879				// children of the active range's start node that have index
12880				// greater than or equal to the active range's start offset."
12881				//
12882				// "For each node in collapsed block props, remove node from its
12883				// parent."
12884				$_(range.startContainer.childNodes).filter(function (node, range) {
12885					return isEditable(node) && isCollapsedBlockProp(node) && getIndexInParent(node) >= range.startOffset;
12886				}, true).forEach(function (node) {
12887					node.parentNode.removeChild(node);
12888				});
12889			}
12890
12891			// "Call insertNode(frag) on the active range."
12892			range.insertNode(frag);
12893
12894			// "If the active range's start node is a block node with no visible
12895			// children, call createElement("br") on the context object and append
12896			// the result as the last child of the active range's start node."
12897			if (isBlockNode(range.startContainer)) {
12898				ensureContainerEditable(range.startContainer);
12899			}
12900
12901			// "Call collapse() on the context object's Selection, with last
12902			// child's parent as the first argument and one plus its index as the
12903			// second."
12904			range.setStart(lastChild.parentNode, 1 + getIndexInParent(lastChild));
12905			range.setEnd(lastChild.parentNode, 1 + getIndexInParent(lastChild));
12906
12907			// "Fix disallowed ancestors of each member of descendants."
12908			var i;
12909			for (i = 0;
12909 i < descendants.length; i++) {
12910				fixDisallowedAncestors(descendants[i], range);
12911			}
12912
12913			setActiveRange(range);
12914		}
12915	};
12916
12917	//@}
12918	///// The insertImage command /////
12919	//@{
12920	commands.insertimage = {
12921		action: function (value) {
12922			// "If value is the empty string, abort these steps and do nothing."
12923			if (value === "") {
12924				return;
12925			}
12926
12927			// "Let range be the active range."
12928			var range = getActiveRange();
12929
12930			// "Delete the contents of range, with strip wrappers false."
12931			deleteContents(range, {
12932				stripWrappers: false
12933			});
12934
12935			// "If the active range's start node is neither editable nor an editing
12936			// host, abort these steps."
12937			if (!isEditable(getActiveRange().startContainer) && !isEditingHost(getActiveRange().startContainer)) {
12938				return;
12939			}
12940
12941			// "If range's start node is a block node whose sole child is a br, and
12942			// its start offset is 0, remove its start node's child from it."
12943			if (isBlockNode(range.startContainer) && range.startContainer.childNodes.length == 1 && isNamedHtmlElement(range.startContainer.firstChild, "br") && range.startOffset == 0) {
12944				range.startContainer.removeChild(range.startContainer.firstChild);
12945			}
12946
12947			// "Let img be the result of calling createElement("img") on the
12948			// context object."
12949			var img = document.createElement("img");
12950
12951			// "Run setAttribute("src", value) on img."
12952			img.setAttribute("src", value);
12953
12954			// "Run insertNode(img) on the range."
12955			range.insertNode(img);
12956
12957			// "Run collapse() on the Selection, with first argument equal to the
12958			// parent of img and the second argument equal to one plus the index of
12959			// img."
12960			//
12961			// Not everyone actually supports collapse(), so we do it manually
12962			// instead.  Also, we need to modify the actual range we're given as
12963			// well, for the sake of autoimplementation.html's range-filling-in.
12964			range.setStart(img.parentNode, 1 + getIndexInParent(img));
12965			range.setEnd(img.parentNode, 1 + getIndexInParent(img));
12966			_core.rangy.getSelection().removeAllRanges();
12967			_core.rangy.getSelection().addRange(range);
12968
12969			// IE adds width and height attributes for some reason, so remove those
12970			// to actually do what the spec says.
12971			img.removeAttribute("width");
12972			img.removeAttribute("height");
12973		}
12974	};
12975
12976	//@}
12977	///// The insertLineBreak command /////
12978	//@{
12979	commands.insertlinebreak = {
12980		action: function (value, range) {
12981			// "Delete the contents of the active range, with strip wrappers false."
12982			deleteContents(range, {
12983				stripWrappers: false
12984			});
12985
12986			// "If the active range's start node is neither editable nor an editing
12987			// host, abort these steps."
12988			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
12989				return;
12990			}
12991
12992			// "If the active range's start node is an Element, and "br" is not an
12993			// allowed child of it, abort these steps."
12994			if (range.startContainer.nodeType == Node.ELEMENT_NODE && !isAllowedChild("br", range.startContainer)) {
12995				return;
12996			}
12997
12998			// "If the active range's start node is not an Element, and "br" is not
12999			// an allowed child of the active range's start node's parent, abort
13000			// these steps."
13001			if (range.startContainer.nodeType != Node.ELEMENT_NODE && !isAllowedChild("br", range.startContainer.parentNode)) {
13002				return;
13003			}
13004
13005			// "If the active range's start node is a Text node and its start
13006			// offset is zero, call collapse() on the context object's Selection,
13007			// with first argument equal to the active range's start node's parent
13008			// and second argument equal to the active range's start node's index."
13009			var newNode, newOffset;
13010			if (range.startContainer.nodeType == Node.TEXT_NODE && range.startOffset == 0) {
13011				newNode = range.startContainer.parentNode;
13012				newOffset = getIndexInParent(range.startContainer);
13013				_core.rangy.getSelection().collapse(newNode, newOffset);
13014				range.setStart(newNode, newOffset);
13015				range.setEnd(newNode, newOffset);
13016			}
13017
13018			// "If the active range's start node is a Text node and its start
13019			// offset is the length of its start node, call collapse() on the
13020			// context object's Selection, with first argument equal to the active
13021			// range's start node's parent and second argument equal to one plus
13022			// the active range's start node's index."
13023			if (range.startContainer.nodeType == Node.TEXT_NODE && range.startOffset == getNodeLength(range.startContainer)) {
13024				newNode = range.startContainer.parentNode;
13025				newOffset = 1 + getIndexInParent(range.startContainer);
13026				_core.rangy.getSelection().collapse(newNode, newOffset);
13027				range.setStart(newNode, newOffset);
13028				range.setEnd(newNode, newOffset);
13029			}
13030
13031			// "Let br be the result of calling createElement("br") on the context
13032			// object."
13033			var br = document.createElement("br");
13034
13035			// "Call insertNode(br) on the active range."
13036			range.insertNode(br);
13037
13038			// "Call collapse() on the context object's Selection, with br's parent
13039			// as the first argument and one plus br's index as the second
13040			// argument."
13041			_core.rangy.getSelection().collapse(br.parentNode, 1 + getIndexInParent(br));
13042			range.setStart(br.parentNode, 1 + getIndexInParent(br));
13043			range.setEnd(br.parentNode, 1 + getIndexInParent(br));
13044
13045			// "If br is a collapsed line break, call createElement("br") on the
13046			// context object and let extra br be the result, then call
13047			// insertNode(extra br) on the active range."
13048			if (isCollapsedLineBreak(br)) {
13049				// TODO
13050				range.insertNode(createEndBreak());
13051
13052				// Compensate for nonstandard implementations of insertNode
13053				_core.rangy.getSelection().collapse(br.parentNode, 1 + getIndexInParent(br));
13054				range.setStart(br.parentNode, 1 + getIndexInParent(br));
13055				range.setEnd(br.parentNode, 1 + getIndexInParent(br));
13056			}
13057
13058		}
13059	};
13060
13061	//@}
13062	///// The insertOrderedList command /////
13063	//@{
13064	commands.insertorderedlist = {
13065		// "Toggle lists with tag name "ol"."
13066		action: function (value, range) {
13067			toggleLists("ol", range);
13068		},
13069		// "True if the selection's list state is "mixed" or "mixed ol", false
13070		// otherwise."
13071		indeterm: function () {
13072			return (/^mixed( ol)?$/).test(getSelectionListState());
13073		},
13074		// "True if the selection's list state is "ol", false otherwise."
13075		state: function () {
13076			return getSelectionListState() == "ol";
13077		}
13078	};
13079
13080	var listRelatedElements = {
13081		"LI": true,
13082		"DT": true,
13083		"DD": true
13084	};
13085
13086	//@}
13087	///// The insertParagraph command /////
13088	//@{
13089	commands.insertparagraph = {
13090		action: function (value, range) {
13091			var i;
13092
13093			// "Delete the contents of the active range."
13094			deleteContents(range);
13095
13096			// clean lists in the editing host, this will remove any whitespace nodes around lists
13097			// because the following algorithm is not prepared to deal with them
13098			cleanLists(getEditingHostOf(range.startContainer), range);
13099
13100			// "If the active range's start node is neither editable nor an editing
13101			// host, abort these steps."
13102			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
13103				return;
13104			}
13105
13106			var editable = getEditingHostOf(range.commonAncestorContainer);
13107
13108			if (!editable) {
13109				return;
13110			}
13111
13112			// "Let node and offset be the active range's start node and offset."
13113			var node = range.startContainer;
13114			var offset = range.startOffset;
13115
13116			// "If node is a Text node, and offset is neither 0 nor the length of
13117			// node, call splitText(offset) on node."
13118			if (node.nodeType == Node.TEXT_NODE && offset != 0 && offset != getNodeLength(node)) {
13119				splitText(node, offset);
13120			}
13121
13122			// "If node is a Text node and offset is its length, set offset to one
13123			// plus the index of node, then set node to its parent."
13124			if (node.nodeType == Node.TEXT_NODE && offset == getNodeLength(node)) {
13125				offset = 1 + getIndexInParent(node);
13126				node = node.parentNode;
13127			}
13128
13129			// "If node is a Text or Comment node, set offset to the index of node,
13130			// then set node to its parent."
13131			if (node.nodeType == Node.TEXT_NODE || node.nodeType == Node.COMMENT_NODE) {
13132				offset = getIndexInParent(node);
13133				node = node.parentNode;
13134			}
13135
13136			// "Call collapse(node, offset) on the context object's Selection."
13137			_core.rangy.getSelection().collapse(node, offset);
13138			range.setStart(node, offset);
13139			range.setEnd(node, offset);
13140
13141			// "Let container equal node."
13142			var container = node;
13143
13144			// "While container is not a single-line container, and container's
13145			// parent is editable and in the same editing host as node, set
13146			// container to its parent."
13147			while (!isSingleLineContainer(container) && isEditable(container.parentNode) && inSameEditingHost(node, container.parentNode)) {
13148				container = container.parentNode;
13149			}
13150
13151			// "If container is not editable or not in the same editing host as
13152			// node or is not a single-line container:"
13153			if (!isEditable(container) || !inSameEditingHost(container, node) || !isSingleLineContainer(container)) {
13154				editable = getEditingHostOf(container);
13155
13156				if (!editable || !ContentRules.isAllowed(editable, defaultSingleLineContainerName)) {
13157					return;
13158				}
13159
13160				// "Let tag be the default single-line container name."
13161				var tag = defaultSingleLineContainerName;
13162
13163				// "Block-extend the active range, and let new range be the
13164				// result."
13165				var newRange = blockExtend(range);
13166
13167				// "Let node list be a list of nodes, initially empty."
13168				//
13169				// "Append to node list the first node in tree order that is
13170				// contained in new range and is an allowed child of "p", if any."
13171				var nodeList = getContainedNodes(newRange, function (node) {
13172					return isAllowedChild(node, "p");
13173				}).slice(0, 1);
13174
13175				// "If node list is empty:"
13176				if (!nodeList.length) {
13177					// "If tag is not an allowed child of the active range's start
13178					// node, abort these steps."
13179					if (!isAllowedChild(tag, range.startContainer)) {
13180						return;
13181					}
13182
13183					// "Set container to the result of calling createElement(tag)
13184					// on the context object."
13185					container = document.createElement(tag);
13186
13187					// "Call insertNode(container) on the active range."
13188					range.insertNode(container);
13189
13190					// "Call createElement("br") on the context object, and append
13191					// the result as the last child of container."
13192					// TODO not always
13193					container.appendChild(createEndBreak());
13194
13195					// "Call collapse(container, 0) on the context object's
13196					// Selection."
13197					// TODO: remove selection from command
13198					_core.rangy.getSelection().collapse(container, 0);
13199					range.setStart(container, 0);
13200					range.setEnd(container, 0);
13201
13202					// "Abort these steps."
13203					return;
13204				}
13205
13206				// "While the nextSibling of the last member of node list is not
13207				// null and is an allowed child of "p", append it to node list."
13208				while (nodeList[nodeList.length - 1].nextSibling && isAllowedChild(nodeList[nodeList.length - 1].nextSibling, "p")) {
13209					nodeList.push(nodeList[nodeList.length - 1].nextSibling);
13210				}
13211
13212				// "Wrap node list, with sibling criteria returning false and new
13213				// parent instructions returning the result of calling
13214				// createElement(tag) on the context object. Set container to the
13215				// result."
13216				container = wrap(
13217					nodeList,
13218					function () {
13219						return false;
13220					},
13221					function () {
13222						return document.createElement(tag);
13223					},
13224					range
13225				);
13226			}
13227
13228			// If no container has been set yet, it is not possible to insert a paragraph at this position;
13229			// the following steps are skipped in order to prevent critical errors from occurring;
13230			if (!container) {
13231				return;
13232			}
13233
13234			// "If container's local name is "address", "listing", or "pre":"
13235			var oldHeight, newHeight;
13236			if (container.tagName == "ADDRESS" || container.tagName == "LISTING" || container.tagName == "PRE") {
13237
13238				if (!ContentRules.isAllowed(editable, 'br')) {
13239					return;
13240				}
13241
13242				// "Let br be the result of calling createElement("br") on the
13243				// context object."
13244				var br = document.createElement("br");
13245
13246				// remember the old height
13247				oldHeight = container.offsetHeight;
13248
13249				// "Call insertNode(br) on the active range."
13250				range.insertNode(br);
13251
13252				// determine the new height
13253				newHeight = container.offsetHeight;
13254
13255				// "Call collapse(node, offset + 1) on the context object's
13256				// Selection."
13257				_core.rangy.getSelection().collapse(node, offset + 1);
13258				range.setStart(node, offset + 1);
13259				range.setEnd(node, offset + 1);
13260
13261				// "If br is the last descendant of container, let br be the result
13262				// of calling createElement("br") on the context object, then call
13263				// insertNode(br) on the active range." (Fix: only do this, if the container height did not change by inserting a single <br/>)
13264				//
13265				// Work around browser bugs: some browsers select the
13266				// newly-inserted node, not per spec.
13267				if (oldHeight == newHeight && !isDescendant(nextNode(br), container)) {
13268					// TODO check
13269					range.insertNode(createEndBreak());
13270					_core.rangy.getSelection().collapse(node, offset + 1);
13271					range.setEnd(node, offset + 1);
13272				}
13273
13274				// "Abort these steps."
13275				return;
13276			}
13277
13278			// "If container's local name is "li", "dt", or "dd"; and either it has
13279			// no children or it has a single child and that child is a br:"
13280			if (listRelatedElements[container.tagName] && (!container.hasChildNodes() || (container.childNodes.length == 1 && isNamedHtmlElement(container.firstChild, "br")))) {
13281				// "Split the parent of the one-node list consisting of container."
13282				splitParent([container], range);
13283
13284				// "If container has no children, call createElement("br") on the
13285				// context object and append the result as the last child of
13286				// container."
13287				// only do this, if inserting the br does NOT modify the offset height of the container
13288				//			if (!container.hasChildNodes()) {
13289				//				var oldHeight = container.offsetHeight, endBr = createEndBreak();
13290				//				container.appendChild(endBr);
13291				//				if (container.offsetHeight !== oldHeight) {
13292				//					container.removeChild(endBr);
13293				//				}
13294				//			}
13295
13296				// "If container is a dd or dt, and it is not an allowed child of
13297				// any of its ancestors in the same editing host, set the tag name
13298				// of container to the default single-line container name and let
13299				// container be the result."
13300				if (isHtmlElementInArray(container, ["dd", "dt"]) && $_(getAncestors(container)).every(function (ancestor) { return !inSameEditingHost(container, ancestor) || !isAllowedChild(container, ancestor); })) {
13301					container = setTagName(container, defaultSingleLineContainerName, range);
13302				}
13303
13304				// "Fix disallowed ancestors of container."
13305				fixDisallowedAncestors(container, range);
13306
13307				// fix invalid nested lists
13308				if (isNamedHtmlElement(container, 'li') && isNamedHtmlElement(container.nextSibling, "li") && isHtmlElementInArray(container.nextSibling.firstChild, ["ol", "ul"])) {
13309					// we found a li containing only a br followed by a li containing a list as first element: merge the two li's
13310					var listParent = container.nextSibling,
13311						length = container.nextSibling.childNodes.length;
13312					for (i = 0; i < length; i++) {
13313						// we always move the first child into the container
13314						container.appendChild(listParent.childNodes[0]);
13315					}
13316					listParent.parentNode.removeChild(listParent);
13317				}
13318
13319				// "Abort these steps."
13320				return;
13321			}
13322
13323			// special behaviour when pressing enter in the last empty paragraph, that is nested in a blockquote
13324			if (isNamedHtmlElement(container, "p")
13325					&& isNamedHtmlElement(container.parentNode, "blockquote")
13326						&& !container.nextSibling
13327							&& (!container.hasChildNodes()
13328									|| (container.childNodes.length === 1
13329											&& isNamedHtmlElement(container.firstChild, "br")))) {
13330				jQuery(container.parentNode).after(container);
13331				return;
13332			}
13333
13334			// "Let new line range be a new range whose start is the same as
13335			// the active range's, and whose end is (container, length of
13336			// container)."
13337			var newLineRange = _core.rangy.createRange();
13338			newLineRange.setStart(range.startContainer, range.startOffset);
13339			newLineRange.setEnd(container, getNodeLength(container));
13340
13341			// "While new line range's start offset is zero and its start node is
13342			// not container, set its start to (parent of start node, index of
13343			// start node)."
13344			while (newLineRange.startOffset == 0 && newLineRange.startContainer != container) {
13345				newLineRange.setStart(newLineRange.startContainer.parentNode, getIndexInParent(newLineRange.startContainer));
13346			}
13347
13348			// "While new line range's start offset is the length of its start node
13349			// and its start node is not container, set its start to (parent of
13350			// start node, 1 + index of start node)."
13351			while (newLineRange.startOffset == getNodeLength(newLineRange.startContainer) && newLineRange.startContainer != container) {
13352				newLineRange.setStart(newLineRange.startContainer.parentNode, 1 + getIndexInParent(newLineRange.startContainer));
13353			}
13354
13355			// "Let end of line be true if new line range contains either nothing
13356			// or a single br, and false otherwise."
13357			var containedInNewLineRange = getContainedNodes(newLineRange);
13358			var endOfLine = !containedInNewLineRange.length || (containedInNewLineRange.length == 1 && isNamedHtmlElement(containedInNewLineRange[0], "br"));
13359
13360			// "If the local name of container is "h1", "h2", "h3", "h4", "h5", or
13361			// "h6", and end of line is true, let new container name be the default
13362			// single-line container name."
13363			var newContainerName;
13364			if (/^H[1-6]$/.test(container.tagName) && endOfLine) {
13365				newContainerName = defaultSingleLineContainerName;
13366
13367				// "Otherwise, if the local name of container is "dt" and end of line
13368				// is true, let new container name be "dd"."
13369			} else if (container.tagName == "DT" && endOfLine) {
13370				newContainerName = "dd";
13371
13372				// "Otherwise, if the local name of container is "dd" and end of line
13373				// is true, let new container name be "dt"."
13374			} else if (container.tagName == "DD" && endOfLine) {
13375				newContainerName = "dt";
13376
13377				// "Otherwise, let new container name be the local name of container."
13378			} else {
13379				newContainerName = container.tagName.toLowerCase();
13380			}
13381
13382			if (!listRelatedElements[newContainerName.toUpperCase()]
13383					&& !ContentRules.isAllowed(editable, newContainerName)) {
13384				return;
13385			}
13386
13387			// "Let new container be the result of calling createElement(new
13388			// container name) on the context object."
13389			var newContainer = document.createElement(newContainerName);
13390
13391			// "Copy all non empty attributes of the container to new container."
13392			copyAttributes(container, newContainer);
13393
13394			// "If new container has an id attribute, unset it."
13395			newContainer.removeAttribute("id");
13396
13397			// "Insert new container into the parent of container immediately after
13398			// container."
13399			container.parentNode.insertBefore(newContainer, container.nextSibling);
13400
13401			// "Let contained nodes be all nodes contained in new line range."
13402			var containedNodes = getAllContainedNodes(newLineRange);
13403
13404			// "Let frag be the result of calling extractContents() on new line
13405			// range."
13406			var frag = newLineRange.extractContents();
13407
13408			// "Unset the id attribute (if any) of each Element descendant of frag
13409			// that is not in contained nodes."
13410			var descendants = getDescendants(frag);
13411			for (i = 0;
13411 i < descendants.length; i++) {
13412				if (descendants[i].nodeType == Node.ELEMENT_NODE && $_(containedNodes).indexOf(descendants[i]) == -1) {
13413					descendants[i].removeAttribute("id");
13414				}
13415			}
13416
13417			var fragChildren = [],
13418				fragChild = frag.firstChild;
13419			if (fragChild) {
13420				do {
13421					if (!isWhitespaceNode(fragChild)) {
13422						fragChildren.push(fragChild);
13423					}
13424				} while (null != (fragChild = fragChild.nextSibling));
13425			}
13426
13427			// if newContainer is a li and frag contains only a list, we add a br in the li (but only if the height would not change)
13428			if (isNamedHtmlElement(newContainer, 'li') && fragChildren.length && isHtmlElementInArray(fragChildren[0], ["ul", "ol"])) {
13429				oldHeight = newContainer.offsetHeight;
13430				var endBr = createEndBreak();
13431				newContainer.appendChild(endBr);
13432				newHeight = newContainer.offsetHeight;
13433				if (oldHeight !== newHeight) {
13434					newContainer.removeChild(endBr);
13435				}
13436			}
13437
13438			// "Call appendChild(frag) on new container."
13439			newContainer.appendChild(frag);
13440
13441			// "If container has no visible children, call createElement("br") on
13442			// the context object, and append the result as the last child of
13443			// container."
13444			ensureContainerEditable(container);
13445
13446			// "If new container has no visible children, call createElement("br")
13447			// on the context object, and append the result as the last child of
13448			// new container."
13449			ensureContainerEditable(newContainer);
13450
13451			// "Call collapse(new container, 0) on the context object's Selection."
13452			_core.rangy.getSelection().collapse(newContainer, 0);
13453			range.setStart(newContainer, 0);
13454			range.setEnd(newContainer, 0);
13455		}
13456	};
13457
13458	//@}
13459	///// The insertText command /////
13460	//@{
13461	commands.inserttext = {
13462		action: function (value, range) {
13463			var i;
13464
13465			// "Delete the contents of the active range, with strip wrappers
13466			// false."
13467			deleteContents(range, {
13468				stripWrappers: false
13469			});
13470
13471			// "If the active range's start node is neither editable nor an editing
13472			// host, abort these steps."
13473			if (!isEditable(range.startContainer) && !isEditingHost(range.startContainer)) {
13474				return;
13475			}
13476
13477			// "If value's length is greater than one:"
13478			if (value.length > 1) {
13479				// "For each element el in value, take the action for the
13480				// insertText command, with value equal to el."
13481				for (i = 0; i < value.length; i++) {
13482					commands.inserttext.action(value[i], range);
13483				}
13484
13485				// "Abort these steps."
13486				return;
13487			}
13488
13489			// "If value is the empty string, abort these steps."
13490			if (value == "") {
13491				return;
13492			}
13493
13494			// "If value is a newline (U+00A0), take the action for the
13495			// insertParagraph command and abort these steps."
13496			if (value == "\n") {
13497				commands.insertparagraph.action('', range);
13498				return;
13499			}
13500
13501			// "Let node and offset be the active range's start node and offset."
13502			var node = range.startContainer;
13503			var offset = range.startOffset;
13504
13505			// "If node has a child whose index is offset − 1, and that child is a
13506			// Text node, set node to that child, then set offset to node's
13507			// length."
13508			if (0 <= offset - 1 && offset - 1 < node.childNodes.length && node.childNodes[offset - 1].nodeType == Node.TEXT_NODE) {
13509				node = node.childNodes[offset - 1];
13510				offset = getNodeLength(node);
13511			}
13512
13513			// "If node has a child whose index is offset, and that child is a Text
13514			// node, set node to that child, then set offset to zero."
13515			if (0 <= offset && offset < node.childNodes.length && node.childNodes[offset].nodeType == Node.TEXT_NODE) {
13516				node = node.childNodes[offset];
13517				offset = 0;
13518			}
13519
13520			// "If value is a space (U+0020), and either node is an Element whose
13521			// resolved value for "white-space" is neither "pre" nor "pre-wrap" or
13522			// node is not an Element but its parent is an Element whose resolved
13523			// value for "white-space" is neither "pre" nor "pre-wrap", set value
13524			// to a non-breaking space (U+00A0)."
13525			var refElement = node.nodeType == Node.ELEMENT_NODE ? node : node.parentNode;
13526			if (value == " " && refElement.nodeType == Node.ELEMENT_NODE && jQuery.inArray($_.getComputedStyle(refElement).whiteSpace, ["pre", "pre-wrap"]) == -1) {
13527				value = "\xa0";
13528			}
13529
13530			// "Record current overrides, and let overrides be the result."
13531			var overrides = recordCurrentOverrides(range);
13532
13533			// "If node is a Text node:"
13534			if (node.nodeType == Node.TEXT_NODE) {
13535				// "Call insertData(offset, value) on node."
13536				node.insertData(offset, value);
13537
13538				// "Call collapse(node, offset) on the context object's Selection."
13539				_core.rangy.getSelection().collapse(node, offset);
13540				range.setStart(node, offset);
13541
13542				// "Call extend(node, offset + 1) on the context object's
13543				// Selection."
13544				_core.rangy.getSelection().extend(node, offset + 1);
13545				range.setEnd(node, offset + 1);
13546
13547				// "Otherwise:"
13548			} else {
13549				// "If node has only one child, which is a collapsed line break,
13550				// remove its child from it."
13551				//
13552				// FIXME: IE incorrectly returns false here instead of true
13553				// sometimes?
13554				if (node.childNodes.length == 1 && isCollapsedLineBreak(node.firstChild)) {
13555					node.removeChild(node.firstChild);
13556				}
13557
13558				// "Let text be the result of calling createTextNode(value) on the
13559				// context object."
13560				var text = document.createTextNode(value);
13561
13562				// "Call insertNode(text) on the active range."
13563				range.insertNode(text);
13564
13565				// "Call collapse(text, 0) on the context object's Selection."
13566				_core.rangy.getSelection().collapse(text, 0);
13567				range.setStart(text, 0);
13568
13569				// "Call extend(text, 1) on the context object's Selection."
13570				_core.rangy.getSelection().extend(text, 1);
13571				range.setEnd(text, 1);
13572			}
13573
13574			// "Restore states and values from overrides."
13575			restoreStatesAndValues(overrides, range);
13576
13577			// "Canonicalize whitespace at the active range's start."
13578			canonicalizeWhitespace(range.startContainer, range.startOffset);
13579
13580			// "Canonicalize whitespace at the active range's end."
13581			canonicalizeWhitespace(range.endContainer, range.endOffset);
13582
13583			// "Call collapseToEnd() on the context object's Selection."
13584			_core.rangy.getSelection().collapseToEnd();
13585			range.collapse(false);
13586		}
13587	};
13588
13589	//@}
13590	///// The insertUnorderedList command /////
13591	//@{
13592	commands.insertunorderedlist = {
13593		// "Toggle lists with tag name "ul"."
13594		action: function (value, range) {
13595			toggleLists("ul", range);
13596		},
13597		// "True if the selection's list state is "mixed" or "mixed ul", false
13598		// otherwise."
13599		indeterm: function () {
13600			return (/^mixed( ul)?$/).test(getSelectionListState());
13601		},
13602		// "True if the selection's list state is "ul", false otherwise."
13603		state: function () {
13604			return getSelectionListState() == "ul";
13605		}
13606	};
13607
13608	//@}
13609	///// The justifyCenter command /////
13610	//@{
13611	commands.justifycenter = {
13612		// "Justify the selection with alignment "center"."
13613		action: function (value, range) {
13614			justifySelection("center", range);
13615		},
13616		indeterm: function () {
13617			// "Block-extend the active range. Return true if among visible
13618			// editable nodes that are contained in the result and have no
13619			// children, at least one has alignment value "center" and at least one
13620			// does not. Otherwise return false."
13621			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13622				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13623			});
13624			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "center"; })
13625				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "center"; });
13626		},
13627		state: function () {
13628			// "Block-extend the active range. Return true if there is at least one
13629			// visible editable node that is contained in the result and has no
13630			// children, and all such nodes have alignment value "center".
13631			// Otherwise return false."
13632			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13633				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13634			});
13635			return nodes.length && $_(nodes).every(function (node) {
13636				return getAlignmentValue(node) == "center";
13637			});
13638		},
13639		value: function () {
13640			// "Block-extend the active range, and return the alignment value of
13641			// the first visible editable node that is contained in the result and
13642			// has no children. If there is no such node, return "left"."
13643			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13644				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13645			});
13646			if (nodes.length) {
13647				return getAlignmentValue(nodes[0]);
13648			}
13649			return "left";
13650		}
13651	};
13652
13653	//@}
13654	///// The justifyFull command /////
13655	//@{
13656	commands.justifyfull = {
13657		// "Justify the selection with alignment "justify"."
13658		action: function (value, range) {
13659			justifySelection("justify", range);
13660		},
13661		indeterm: function () {
13662			// "Block-extend the active range. Return true if among visible
13663			// editable nodes that are contained in the result and have no
13664			// children, at least one has alignment value "justify" and at least
13665			// one does not. Otherwise return false."
13666			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13667				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13668			});
13669			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "justify"; })
13670				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "justify"; });
13671		},
13672		state: function () {
13673			// "Block-extend the active range. Return true if there is at least one
13674			// visible editable node that is contained in the result and has no
13675			// children, and all such nodes have alignment value "justify".
13676			// Otherwise return false."
13677			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13678				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13679			});
13680			return nodes.length && $_(nodes).every(function (node) {
13681				return getAlignmentValue(node) == "justify";
13682			});
13683		},
13684		value: function () {
13685			// "Block-extend the active range, and return the alignment value of
13686			// the first visible editable node that is contained in the result and
13687			// has no children. If there is no such node, return "left"."
13688			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13689				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13690			});
13691			if (nodes.length) {
13692				return getAlignmentValue(nodes[0]);
13693			}
13694			return "left";
13695		}
13696	};
13697
13698	//@}
13699	///// The justifyLeft command /////
13700	//@{
13701	commands.justifyleft = {
13702		// "Justify the selection with alignment "left"."
13703		action: function (value, range) {
13704			justifySelection("left", range);
13705		},
13706		indeterm: function () {
13707			// "Block-extend the active range. Return true if among visible
13708			// editable nodes that are contained in the result and have no
13709			// children, at least one has alignment value "left" and at least one
13710			// does not. Otherwise return false."
13711			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13712				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13713			});
13714			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "left"; })
13715				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "left"; });
13716		},
13717		state: function () {
13718			// "Block-extend the active range. Return true if there is at least one
13719			// visible editable node that is contained in the result and has no
13720			// children, and all such nodes have alignment value "left".  Otherwise
13721			// return false."
13722			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13723				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13724			});
13725			return nodes.length && $_(nodes).every(function (node) {
13726				return getAlignmentValue(node) == "left";
13727			});
13728		},
13729		value: function () {
13730			// "Block-extend the active range, and return the alignment value of
13731			// the first visible editable node that is contained in the result and
13732			// has no children. If there is no such node, return "left"."
13733			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13734				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13735			});
13736			if (nodes.length) {
13737				return getAlignmentValue(nodes[0]);
13738			}
13739			return "left";
13740		}
13741	};
13742
13743	//@}
13744	///// The justifyRight command /////
13745	//@{
13746	commands.justifyright = {
13747		// "Justify the selection with alignment "right"."
13748		action: function (value, range) {
13749			justifySelection("right", range);
13750		},
13751		indeterm: function () {
13752			// "Block-extend the active range. Return true if among visible
13753			// editable nodes that are contained in the result and have no
13754			// children, at least one has alignment value "right" and at least one
13755			// does not. Otherwise return false."
13756			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13757				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13758			});
13759			return $_(nodes).some(function (node) { return getAlignmentValue(node) == "right"; })
13760				&& $_(nodes).some(function (node) { return getAlignmentValue(node) != "right"; });
13761		},
13762		state: function () {
13763			// "Block-extend the active range. Return true if there is at least one
13764			// visible editable node that is contained in the result and has no
13765			// children, and all such nodes have alignment value "right".
13766			// Otherwise return false."
13767			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13768				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13769			});
13770			return nodes.length && $_(nodes).every(function (node) {
13771				return getAlignmentValue(node) == "right";
13772			});
13773		},
13774		value: function () {
13775			// "Block-extend the active range, and return the alignment value of
13776			// the first visible editable node that is contained in the result and
13777			// has no children. If there is no such node, return "left"."
13778			var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function (node) {
13779				return isEditable(node) && isVisible(node) && !node.hasChildNodes();
13780			});
13781			if (nodes.length) {
13782				return getAlignmentValue(nodes[0]);
13783			}
13784			return "left";
13785		}
13786	};
13787
13788	//@}
13789	///// The outdent command /////
13790	//@{
13791	/*commands.outdent = {
13792		action: function () {
13793			// "Let items be a list of all lis that are ancestor containers of the
13794			// range's start and/or end node."
13795			//
13796			// It's annoying to get this in tree order using functional stuff
13797			// without doing getDescendants(document), which is slow, so I do it
13798			// imperatively.
13799			var items = [];
13800			(function () {
13801				var ancestorContainer;
13802				for (ancestorContainer = getActiveRange().endContainer;
13803					     ancestorContainer != getActiveRange().commonAncestorContainer;
13804					     ancestorContainer = ancestorContainer.parentNode) {
13805					if (isNamedHtmlElement(ancestorContainer, "li")) {
13806						items.unshift(ancestorContainer);
13807					}
13808				}
13809				for (ancestorContainer = getActiveRange().startContainer;
13810					     ancestorContainer;
13811					     ancestorContainer = ancestorContainer.parentNode) {
13812					if (isNamedHtmlElement(ancestorContainer, "li")) {
13813						items.unshift(ancestorContainer);
13814					}
13815				}
13816			}());
13817
13818			// "For each item in items, normalize sublists of item."
13819			$_(items).forEach(function (thisArg) {
13820				normalizeSublists(thisArg, getActiveRange());
13821			});
13822
13823			// "Block-extend the active range, and let new range be the result."
13824			var newRange = blockExtend(getActiveRange());
13825
13826			// "Let node list be a list of nodes, initially empty."
13827			//
13828			// "For each node node contained in new range, append node to node list
13829			// if the last member of node list (if any) is not an ancestor of node;
13830			// node is editable; and either node has no editable descendants, or is
13831			// an ol or ul, or is an li whose parent is an ol or ul."
13832			var nodeList = getContainedNodes(newRange, function (node) {
13833				return isEditable(node) && (!$_(getDescendants(node)).some(isEditable) || isHtmlElementInArray(node, ["ol", "ul"]) || (isNamedHtmlElement(node, 'li') && isHtmlElementInArray(node.parentNode, ["ol", "ul"])));
13834			});
13835
13836			// "While node list is not empty:"
13837			while (nodeList.length) {
13838				// "While the first member of node list is an ol or ul or is not
13839				// the child of an ol or ul, outde
13839nt it and remove it from node
13840				// list."
13841				while (nodeList.length && (isHtmlElementInArray(nodeList[0], ["OL", "UL"]) || !isHtmlElementInArray(nodeList[0].parentNode, ["OL", "UL"]))) {
13842					outdentNode(nodeList.shift(), newRange);
13843				}
13844
13845				// "If node list is empty, break from these substeps."
13846				if (!nodeList.length) {
13847					break;
13848				}
13849
13850				// "Let sublist be a list of nodes, initially empty."
13851				var sublist = [];
13852
13853				// "Remove the first member of node list and append it to sublist."
13854				sublist.push(nodeList.shift());
13855
13856				// "While the first member of node list is the nextSibling of the
13857				// last member of sublist, and the first member of node list is not
13858				// an ol or ul, remove the first member of node list and append it
13859				// to sublist."
13860				while (nodeList.length && nodeList[0] == sublist[sublist.length - 1].nextSibling && !isHtmlElementInArray(nodeList[0], ["OL", "UL"])) {
13861					sublist.push(nodeList.shift());
13862				}
13863
13864				// "Record the values of sublist, and let values be the result."
13865				var values = recordValues(sublist);
13866
13867				// "Split the parent of sublist, with new parent null."
13868				splitParent(sublist, newRange);
13869
13870				// "Fix disallowed ancestors of each member of sublist."
13871				$_(sublist).forEach(fixDisallowedAncestors);
13872
13873				// "Restore the values from values."
13874				restoreValues(values, newRange);
13875			}
13876		}
13877	};*/
13878
13879	commands.outdent = {
13880		preservesOverrides: true,
13881		action: function() {
13882			// "Let items be a list of all lis that are ancestor containers of the
13883			// range's start and/or end node."
13884			//
13885			// It's annoying to get this in tree order using functional stuff
13886			// without doing getDescendants(document), which is slow, so I do it
13887			// imperatively.
13888			var items = [];
13889			(function(){
13890				for (
13891					var ancestorContainer = getActiveRange().endContainer;
13892					ancestorContainer != getActiveRange().commonAncestorContainer;
13893					ancestorContainer = ancestorContainer.parentNode
13894				) {
13895					if (isHtmlElement(ancestorContainer, "li")) {
13896						items.unshift(ancestorContainer);
13897					}
13898				}
13899				for (
13900					var ancestorContainer = getActiveRange().startContainer;
13901					ancestorContainer;
13902					ancestorContainer = ancestorContainer.parentNode
13903				) {
13904					if (isHtmlElement(ancestorContainer, "li")) {
13905						items.unshift(ancestorContainer);
13906					}
13907				}
13908			})();
13909
13910			// "For each item in items, normalize sublists of item."
13911			items.forEach(normalizeSublists);
13912
13913			// "Block-extend the active range, and let new range be the result."
13914			var newRange = blockExtend(getActiveRange());
13915
13916			// "Let node list be a list of nodes, initially empty."
13917			//
13918			// "For each node node contained in new range, append node to node list
13919			// if the last member of node list (if any) is not an ancestor of node;
13920			// node is editable; and either node has no editable descendants, or is
13921			// an ol or ul, or is an li whose parent is an ol or ul."
13922			var nodeList = getContainedNodes(newRange, function(node) {
13923				return isEditable(node)
13924					&& (!getDescendants(node).some(isEditable)
13925					|| isHtmlElement(node, ["ol", "ul"])
13926					|| (isHtmlElement(node, "li") && isHtmlElement(node.parentNode, ["ol", "ul"])));
13927			});
13928
13929			// "While node list is not empty:"
13930			while (nodeList.length) {
13931				// "While the first member of node list is an ol or ul or is not
13932				// the child of an ol or ul, outdent it and remove it from node
13933				// list."
13934				while (nodeList.length
13935				&& (isHtmlElement(nodeList[0], ["OL", "UL"])
13936				|| !isHtmlElement(nodeList[0].parentNode, ["OL", "UL"]))) {
13937					outdentNode(nodeList.shift());
13938				}
13939
13940				// "If node list is empty, break from these substeps."
13941				if (!nodeList.length) {
13942					break;
13943				}
13944
13945				// "Let sublist be a list of nodes, initially empty."
13946				var sublist = [];
13947
13948				// "Remove the first member of node list and append it to sublist."
13949				sublist.push(nodeList.shift());
13950
13951				// "While the first member of node list is the nextSibling of the
13952				// last member of sublist, and the first member of node list is not
13953				// an ol or ul, remove the first member of node list and append it
13954				// to sublist."
13955				while (nodeList.length
13956				&& nodeList[0] == sublist[sublist.length - 1].nextSibling
13957				&& !isHtmlElement(nodeList[0], ["OL", "UL"])) {
13958					sublist.push(nodeList.shift());
13959				}
13960
13961				// "Record the values of sublist, and let values be the result."
13962				var values = recordValues(sublist);
13963
13964				// "Split the parent of sublist, with new parent null."
13965				splitParent(sublist);
13966
13967				// "Fix disallowed ancestors of each member of sublist."
13968				sublist.forEach(fixDisallowedAncestors);
13969
13970				// "Restore the values from values."
13971				restoreValues(values);
13972			}
13973
13974			// "Return true."
13975			return true;
13976		}
13977	};
13978
13979	//@}
13980
13981	//////////////////////////////////
13982	///// Miscellaneous commands /////
13983	//////////////////////////////////
13984
13985	///// The selectAll command /////
13986	//@{
13987	commands.selectall = {
13988		// Note, this ignores the whole globalRange/getActiveRange() thing and
13989		// works with actual selections.  Not suitable for autoimplementation.html.
13990		action: function () {
13991			// "Let target be the body element of the context object."
13992			var target = document.body;
13993
13994			// "If target is null, let target be the context object's
13995			// documentElement."
13996			if (!target) {
13997				target = document.documentElement;
13998			}
13999
14000			// "If target is null, call getSelection() on the context object, and
14001			// call removeAllRanges() on the result."
14002			if (!target) {
14003				_core.rangy.getSelection().removeAllRanges();
14004
14005				// "Otherwise, call getSelection() on the context object, and call
14006				// selectAllChildren(target) on the result."
14007			} else {
14008				_core.rangy.getSelection().selectAllChildren(target);
14009			}
14010		}
14011	};
14012
14013	//@}
14014	///// The styleWithCSS command /////
14015	//@{
14016	commands.stylewithcss = {
14017		action: function (value) {
14018			// "If value is an ASCII case-insensitive match for the string
14019			// "false", set the CSS styling flag to false. Otherwise, set the
14020			// CSS styling flag to true."
14021			cssStylingFlag = String(value).toLowerCase() != "false";
14022		},
14023		state: function () {
14024			return cssStylingFlag;
14025		}
14026	};
14027
14028	//@}
14029	///// The useCSS command /////
14030	//@{
14031	commands.usecss = {
14032		action: function (value) {
14033			// "If value is an ASCII case-insensitive match for the string "false",
14034			// set the CSS styling flag to true. Otherwise, set the CSS styling
14035			// flag to false."
14036			cssStylingFlag = String(value).toLowerCase() == "false";
14037		}
14038	};
14039	//@}
14040
14041	// Some final setup
14042	//@{
14043	(function () {
14044		// Opera 11.50 doesn't implement Object.keys, so I have to make an explicit
14045		// temporary, which means I need an extra closure to not leak the temporaries
14046		// into the global namespace.  >:(
14047		var commandNames = [];
14048		var command;
14049		for (command in commands) {
14050			if (commands.hasOwnProperty(command)) {
14051				commandNames.push(command);
14052			}
14053		}
14054		$_(commandNames).forEach(function (command) {
14055			// "If a command does not have a relevant CSS property specified, it
14056			// defaults to null."
14057			if (null == commands[command].relevantCssProperty) {
14058				commands[command].relevantCssProperty = null;
14059			}
14060
14061			// "If a command has inline command activated values defined but
14062			// nothing else defines when it is indeterminate, it is indeterminate
14063			// if among editable Text nodes effectively contained in the active
14064			// range, there is at least one whose effective command value is one of
14065			// the given values and at least one whose effective command value is
14066			// not one of the given values."
14067			if (null != commands[command].inlineCommandActivatedValues && null == commands[command].indeterm) {
14068				commands[command].indeterm = function (range) {
14069					var values = $_(getAllEffectivelyContainedNodes(range, function (node) { return isEditable(node) && node.nodeType == Node.TEXT_NODE; }))
14070						.map(function (node) { return getEffectiveCommandValue(node, command); });
14071
14072					var matchingValues = $_(values).filter(function (value) {
14073						return $_(commands[command].inlineCommandActivatedValues).indexOf(value) != -1;
14074					});
14075
14076					return matchingValues.length >= 1 && values.length - matchingValues.length >= 1;
14077				};
14078			}
14079
14080			// "If a command has inline command activated values defined, its state
14081			// is true if either no editable Text node is effectively contained in
14082			// the active range, and the active range's start node's effective
14083			// command value is one of the given values; or if there is at least
14084			// one editable Text node effectively contained in the active range,
14085			// and all of them have an effective command value equal to one of the
14086			// given values."
14087			if (null != commands[command].inlineCommandActivatedValues) {
14088				commands[command].state = function (range) {
14089					var nodes = getAllEffectivelyContainedNodes(range, function (node) {
14090						return isEditable(node) && node.nodeType == Node.TEXT_NODE;
14091					});
14092
14093					if (nodes.length == 0) {
14094						return $_(commands[command].inlineCommandActivatedValues).indexOf(getEffectiveCommandValue(range.startContainer, command)) != -1;
14095					}
14096					return $_(nodes).every(function (node) {
14097						return $_(commands[command].inlineCommandActivatedValues).indexOf(getEffectiveCommandValue(node, command)) != -1;
14098					});
14099				};
14100			}
14101
14102			// "If a command is a standard inline value command, it is
14103			// indeterminate if among editable Text nodes that are effectively
14104			// contained in the active range, there are two that have distinct
14105			// effective command values. Its value is the effective command value
14106			// of the first editable Text node that is effectively contained in the
14107			// active range, or if there is no such node, the effective command
14108			// value of the active range's start node."
14109			if (null != commands[command].standardInlineValueCommand) {
14110				commands[command].indeterm = function () {
14111					var values = $_(getAllEffectivelyContainedNodes(getActiveRange())).filter(function (node) { return isEditable(node) && node.nodeType == Node.TEXT_NODE; }, true)
14112						.map(function (node) { return getEffectiveCommandValue(node, command); });
14113					var i;
14114					for (i = 1; i < values.length; i++) {
14115						if (values[i] != values[i - 1]) {
14116							return true;
14117						}
14118					}
14119					return false;
14120				};
14121
14122				commands[command].value = function (range) {
14123					var refNode = getAllEffectivelyContainedNodes(range, function (node) {
14124						return isEditable(node) && node.nodeType == Node.TEXT_NODE;
14125					})[0];
14126
14127					if (typeof refNode == "undefined") {
14128						refNode = range.startContainer;
14129					}
14130
14131					return getEffectiveCommandValue(refNode, command);
14132				};
14133			}
14134		});
14135	}());
14136
14137	/**
14138	 *
14139	 *  The active range is the range of the selection given by calling
14140	 *  getSelection() on the context object. (Thus the active range may be null.)
14141	 *  <br />
14142	 *  We cheat and return globalRange if that's defined.  We also ensure that the
14143	 *  active range meets the requirements that selection boundary points are
14144	 *  supposed to meet, i.e., that the nodes are both Text or Element nodes that
14145	 *  descend from a Document.
14146	 * 
14147	 * @method getActiveRange
14148	 * @memberOf CargoEditor
14149	 * @author Aryeh Gregor
14150	 * 
14151	 */
14152
14153	this.inSameEditingHost = inSameEditingHost;
14154	this.getEditingHostOf = getEditingHostOf;
14155	this.isEditingHost = isEditingHost;
14156	this.isEditable = isEditable;
14157
14158	/**
14159	 * 
14160	 * @field
14161	 * @memberOf CargoEditor
14162	 */
14163
14164	this.commands = commands;
14165	
14166	/**
14167	 * 
14168	 * Facade for the actual execCommand {@link CargoEditor.editableImplementation.execCommand}. 
14169	 * This runs the command through the {@link CargoEditor.MutationManager} so editors and plugins can listen to commands
14170	 * being executed.
14171	 * 
14172	 * @memberOf CargoEditor
14173	 * 
14174	 */
14175
14176	this.execCommand = function(){
14177		
14178		var args = arguments,
14179			self = this;
14180
14181		this.mutationManager.execute(function(){
14182			return myExecCommand.apply(self, args);
14183		});
14184
14185	};
14186
14187	this.execCommandSilent = function(){
14188		
14189		var args = arguments,
14190			self = this;
14191
14192		this.mutationManager.execute(function(){
14193			return myExecCommand.apply(self, args);
14194		}, true);
14195
14196	};
14197
14198	this.setCommand = function(name, command){
14199
14200		if(name === undefined || command === undefined) {
14201			return false;
14202		}
14203
14204		name = name.toLowerCase();
14205
14206		this.commands[name] = command;
14207
14208	};
14209
14210	this.isCommandOverruledByExternalStyle = function(commandName){
14211
14212		if( ! commands.hasOwnProperty(commandName)){
14213			return false;
14214		}
14215
14216		if( ! commands[commandName].hasOwnProperty('relevantCssProperty')) {
14217			return false;
14218		}
14219
14220		var range = _core.getActiveRange(),
14221			node = range.commonAncestorContainer;
14222
14223		if(node.nodeType !== Node.ELEMENT_NODE){
14224			node = node.parentNode
14225		}
14226
14227		var relevantCssProperty = commands[commandName].relevantCssProperty,
14228			testEl = document.createElement('styletester'),
14229			clone = node.cloneNode();
14230
14231		document.body.appendChild(testEl);
14232
14233		// make sure no cascaded style will affect the test element's children
14234		testEl.style.setProperty(relevantCssProperty, 'initial', 'important');
14235
14236		// remove (external) classes from cloned element
14237		clone.className = '';
14238
14239		testEl.appendChild(clone);
14240
14241		var externallyAffected = getComputedStyle(node)[relevantCssProperty] !== getComputedStyle(clone)[relevantCssProperty];
14242
14243		testEl.parentNode.removeChild(testEl);
14244
14245		return externallyAffected;
14246
14247	}
14248
14249	/**
14250	 * 
14251	 * @method queryCommandState
14252	 * @memberOf CargoEditor
14253	 * @author Aryeh Gregor
14254	 * 
14255	 */
14256	this.queryCommandState = myQueryCommandState;
14257
14258	/**
14259	 * 
14260	 * @method queryCommandEnabled
14261	 * @memberOf CargoEditor
14262	 * @author Aryeh Gregor
14263	 * 
14264	 */
14265	this.queryCommandEnabled = myQueryCommandEnabled;
14266
14267	/**
14268	 * 
14269	 * @method queryCommandIndeterm
14270	 * @memberOf CargoEditor
14271	 * @author Aryeh Gregor
14272	 * 
14273	 */
14274	this.queryCommandIndeterm = myQueryCommandIndeterm;
14275
14276	/**
14277	 * 
14278	 * @method queryCommandValue
14279	 * @memberOf CargoEditor
14280	 * @author Aryeh Gregor
14281	 * 
14282	 */
14283	this.queryCommandValue = myQueryCommandValue;
14284
14285}).call(this);
14286
14287
14288_core.events.on("init", function() {
14289
14290	// Some compatibility checks:
14291	if (!window.getSelection) {
14292		alert('window.getSelection not supported.');
14293	}
14294
14295	/*document.body.addEventListener('ondragstart', function(e){
14296		console.log(e);
14297		e.preventDefault();
14298	}, false);
14299
14300	document.body.addEventListener('drop', function(e){
14301		console.log(e);
14302		e.preventDefault();
14303
14304	}, false);*/
14305});
14306
14307_core.events.on('cursor-activity', function(editor){
14308	
14309});
14310
14311_core.events.on("destroy", function() {
14312	console.log('destroy');
14313});
14314
14315_core.events.on('editor-activated', function(editor){
14316	activeEditor = editor;
14317});
14318
14319_core.events.on('editor-deactivated', function(editor){
14320});
14321
14322_core.events.on("click", function() {
14323
14324});
14325
14326/**
14327 *   ---- events triggers ----
14328 */
14329
14330/*$(window).unload(function() {
14331	_core.events.trigger("destroy");
14332});*/
14333
14334/*
14335 * jwerty - Awesome handling of keyboard events
14336 *
14337 * jwerty is a JS lib which allows you to bind, fire and assert key combination
14338 * strings against elements and events. It normalises the poor std api into
14339 * something easy to use and clear.
14340 *
14341 * This code is licensed under the MIT
14342 * For the full license see: http://keithamus.mit-license.org/
14343 * For more information see: http://keithamus.github.com/jwerty
14344 *
14345 * @author Keith Cirkel ('keithamus') <[email protected]>
14346 * @license http://keithamus.mit-license.org/
14347 * @copyright Copyright © 2011, Keith Cirkel
14348 *
14349 */
14350
14351(function (global, exports) {
14352
14353	// Helper methods & vars:
14354	var $d = global.document,
14355		$ = (global.jQuery || global.Zepto || global.ender || $d),
14356		$$, // Element selector function
14357		$b, // Event binding function
14358		$f, // Event firing function
14359		ke = 'keydown';
14360
14361	function realTypeOf(v, s) {
14362		return (v === null) ? s === 'null'
14363		: (v === undefined) ? s === 'undefined'
14364		: (v.is && v instanceof $) ? s === 'element'
14365		: Object.prototype.toString.call(v).toLowerCase().indexOf(s) > 7;
14366	}
14367
14368	if ($ === $d) {
14369		$$ = function (selector, context) {
14370			return selector ? $.querySelector(selector, context || $) : $;
14371		};
14372		$b = function (e, fn) { e.addEventListener(ke, fn, false); };
14373		$f = function (e, jwertyEv) {
14374			var ret = $d.createEvent('Event'),
14375			i;
14376
14377			ret.initEvent(ke, true, true);
14378
14379			for (i in jwertyEv) ret[i] = jwertyEv[i];
14380
14381			return (e || $).dispatchEvent(ret);
14382		};
14383	} else {
14384		$$ = function (selector, context) { return $(selector || $d, context); };
14385		$b = function (e, fn) { $(e).bind(ke + '.jwerty', fn); };
14386		$f = function (e, ob) { $(e || $d).trigger($.Event(ke, ob)); };
14387	}
14388
14389	// Private
14390	var _modProps = { 16: 'shiftKey', 17: 'ctrlKey', 18: 'altKey', 91: 'metaKey' };
14391
14392	// Generate key mappings for common keys that are not printable.
14393	var _keys = {
14394
14395		// MOD aka toggleable keys
14396		mods: {
14397			// Shift key, ⇧
14398			'⇧': 16,
14399			shift: 16,
14400			// CTRL key, on Mac: ⌃
14401			'⌃': 17,
14402			ctrl: 17,
14403			// ALT key, on Mac: ⌥ (Alt)
14404			'⌥': 18,
14405			alt: 18,
14406			option: 18,
14407			// META, on Mac: ⌘ (CMD), on Windows (Win), on Linux (Super)
14408			'⌘': 91,
14409			meta: 91,
14410			cmd: 91,
14411			'super': 91,
14412			win: 91
14413		},
14414
14415		// Normal keys
14416		keys: {
14417			// Backspace key, on Mac: ⌫ (Backspace)
14418			'⌫': 8,
14419			backspace: 8,
14420			// Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥
14421			'⇥': 9,
14422			'⇆': 9,
14423			tab: 9,
14424			// Return key, ↩
14425			'↩': 13,
14426			'return': 13,
14427			enter: 13,
14428			'⌅': 13,
14429			// Pause/Break key
14430			'pause': 19,
14431			'pause-break': 19,
14432			// Caps Lock key, ⇪
14433			'⇪': 20,
14434			caps: 20,
14435			'caps-lock': 20,
14436			// Escape key, on Mac: ⎋, on Windows: Esc
14437			'⎋': 27,
14438			escape: 27,
14439			esc: 27,
14440			// Space key
14441			space: 32,
14442			// Page-Up key, or pgup, on Mac: ↖
14443			'↖': 33,
14444			pgup: 33,
14445			'page-up': 33,
14446			// Page-Down key, or pgdown, on Mac: ↘
14447			'↘': 34,
14448			pgdown: 34,
14449			'page-down': 34,
14450			// END key, on Mac: ⇟
14451			'⇟': 35,
14452			end: 35,
14453			// HOME key, on Mac: ⇞
14454			'⇞': 36,
14455			home: 36,
14456			// Insert key, or ins
14457			ins: 45,
14458			insert: 45,
14459			// Delete key, on Mac: ⌫ (Delete)
14460			del: 46,
14461			'delete': 46,
14462
14463			// Left Arrow Key, or ←
14464			'←': 37,
14465			left: 37,
14466			'arrow-left': 37,
14467			// Up Arrow Key, or ↑
14468			'↑': 38,
14469			up: 38,
14470			'arrow-up': 38,
14471			// Right Arrow Key, or →
14472			'→': 39,
14473			right: 39,
14474			'arrow-right': 39,
14475			// Up Arrow Key, or ↓
14476			'↓': 40,
14477			down: 40,
14478			'arrow-down': 40,
14479
14480			// odities, printing characters that come out wrong:
14481			// Num-Multiply, or *
14482			'*': 106,
14483			star: 106,
14484			asterisk: 106,
14485			multiply: 106,
14486			// Num-Plus or +
14487			'+': 107,
14488			'plus': 107,
14489			// Num-Subtract, or -
14490			'-': 109,
14491			subtract: 109,
14492			'num-.': 110,
14493			'num-period': 110,
14494			'num-dot': 110,
14495			'num-full-stop': 110,
14496			'num-delete': 110,
14497			// Semicolon
14498			';': 186,
14499			semicolon: 186,
14500			// = or equals
14501			'=': 187,
14502			'equals': 187,
14503			// Comma, or ,
14504			',': 188,
14505			comma: 188,
14506			//'-': 189, //???
14507			// Period, or ., or full-stop
14508			'.': 190,
14509			period: 190,
14510			'full-stop': 190,
14511			// Slash, or /, or forward-slash
14512			'/': 191,
14513			slash: 191,
14514			'forward-slash': 191,
14515			// Tick, or `, or back-quote
14516			'`': 192,
14517			tick: 192,
14518			'back-quote': 192,
14519			// Open bracket, or [
14520			'[': 219,
14521			'open-bracket': 219,
14522			// Back slash, or \
14523			'\\': 220,
14524			'back-slash': 220,
14525			// Close backet, or ]
14526			']': 221,
14527			'close-bracket': 221,
14528			// Apostraphe, or Quote, or '
14529			'\'': 222,
14530			quote: 222,
14531			apostraphe: 222
14532		}
14533
14534	};
14535
14536	// To minimise code bloat, add all of the 0-9 and NUMPAD 0-9 keys in a loop
14537	var i = 47,
14538		n = 0;
14539	while (++i < 106) {
14540		_keys.keys[n] = i;
14541		_keys.keys['num-' + n] = i + 48;
14542		++n;
14543	}
14544
14545	// To minimise code bloat, add all of the F1-F25 keys in a loop
14546	i = 111,
14547	n = 1;
14548	while (++i < 136) {
14549		_keys.keys['f' + n] = i;
14550		++n;
14551	}
14552
14553	// To minimise code bloat, add all of the letters of the alphabet in a loop
14554	i = 64;
14555	while (++i < 91) {
14556		_keys.keys[String.fromCharCode(i).toLowerCase()] = i;
14557	}
14558
14559	function JwertyCode(jwertyCode) {
14560		var i,
14561			c,
14562			n,
14563			z,
14564			keyCombo,
14565			optionals,
14566			jwertyCodeFragment,
14567			rangeMatches,
14568			rangeI;
14569
14570		// In-case we get called with an instance of ourselves, just return that.
14571		if (jwertyCode instanceof JwertyCode) return jwertyCode;
14572
14573		// If jwertyCode isn't an array, cast it as a string and split into array.
14574		if (!realTypeOf(jwertyCode, 'array')) {
14575			jwertyCode = (String(jwertyCode)).replace(/\s/g, '').toLowerCase()
14576				.match(/(?:\+,|[^,])+/g);
14577		}
14578
14579		// Loop through each key sequence in jwertyCode
14580		for (i = 0, c = jwertyCode.length; i < c; ++i) {
14581
14582			// If the key combo at this part of the sequence isn't an array,
14583			// cast as a string and split into an array.
14584			if (!realTypeOf(jwertyCode[i], 'array')) {
14585				jwertyCode[i] = String(jwertyCode[i])
14586					.match(/(?:\+\/|[^\/])+/g);
14587			}
14588
14589			// Parse the key optionals in this sequence
14590			optionals = [],
14591			n = jwertyCode[i].length;
14592			while (n--) {
14593
14594				// Begin creating the object for this key combo
14595				jwertyCodeFragment = jwertyCode[i][n];
14596
14597				keyCombo = {
14598					jwertyCombo: String(jwertyCodeFragment),
14599					shiftKey: false,
14600					ctrlKey: false,
14601					altKey: false,
14602					metaKey: false
14603				};
14604
14605				// If jwertyCodeFragment isn't an array then cast as a string
14606				// and split it into one.
14607				if (!realTypeOf(jwertyCodeFragment, 'array')) {
14608					jwertyCodeFragment = String(jwertyCodeFragment).toLowerCase()
14609						.match(/(?:(?:[^\+])+|\+\+|^\+$)/g);
14610				}
14611
14612				z = jwertyCodeFragment.length;
14613				while (z--) {
14614
14615					// Normalise matching errors
14616					if (jwertyCodeFragment[z] === '++') jwertyCodeFragment[z] = '+';
14617
14618					// Inject either keyCode or ctrl/meta/shift/altKey into keyCombo
14619					if (jwertyCodeFragment[z] in _keys.mods) {
14620						keyCombo[_modProps[_keys.mods[jwertyCodeFragment[z]]]] = true;
14621					} else if (jwertyCodeFragment[z] in _keys.keys) {
14622						keyCombo.keyCode = _keys.keys[jwertyCodeFragment[z]];
14623					} else {
14624						rangeMatches = jwertyCodeFragment[z].match(/^\[([^-]+\-?[^-]*)-([^-]+\-?[^-]*)\]$/);
14625					}
14626				}
14627				if (realTypeOf(keyCombo.keyCode, 'undefined')) {
14628					// If we picked up a range match earlier...
14629					if (rangeMatches && (rangeMatches[1] in _keys.keys) && (rangeMatches[2] in _keys.keys)) {
14630						rangeMatches[2] = _keys.keys[rangeMatches[2]];
14631						rangeMatches[1] = _keys.keys[rangeMatches[1]];
14632
14633						// Go from match 1 and capture all key-comobs up to match 2
14634						for (rangeI = rangeMatches[1]; rangeI < rangeMatches[2]; ++rangeI) {
14635							optionals.push({
14636								altKey: keyCombo.altKey,
14637								shiftKey: keyCombo.shiftKey,
14638								metaKey: keyCombo.metaKey,
14639								ctrlKey: keyCombo.ctrlKey,
14640								keyCode: rangeI,
14641								jwertyCombo: String(jwertyCodeFragment)
14642							});
14643
14644						}
14645						keyCombo.keyCode = rangeI;
14646					// Inject either keyCode or ctrl/meta/shift/altKey into keyCombo
14647					} else {
14648						keyCombo.keyCode = 0;
14649					}
14650				}
14651				optionals.push(keyCombo);
14652
14653			}
14654			this[i] = optionals;
14655		}
14656		this.length = i;
14657		return this;
14658	}
14659
14660	var jwerty = exports.jwerty = {
14661		/**
14662		 * jwerty.event
14663		 *
14664		 * `jwerty.event` will return a function, which expects the first
14665		 *  argument to be a key event. When the key event matches `jwertyCode`,
14666		 *  `callbackFunction` is fired. `jwerty.event` is used by `jwerty.key`
14667		 *  to bind the function it returns. `jwerty.event` is useful for
14668		 *  attaching to your own event listeners. It can be used as a decorator
14669		 *  method to encapsulate functionality that you only want to fire after
14670		 *  a specific key combo. If `callbackContext` is specified then it will
14671		 *  be supplied as `callbackFunction`'s context - in other words, the
14672		 *  keyword `this` will be set to `callbackContext` inside the
14673		 *  `callbackFunction` function.
14674		 *
14675		 *   @param {Mixed} jwertyCode can be an array, or string of key
14676		 *      combinations, which includes optinals and or sequences
14677		 *   @param {Function} callbackFucntion is a function (or boolean) which
14678		 *      is fired when jwertyCode is matched. Return false to
14679		 *      preventDefault()
14680		 *   @param {Object} callbackContext (Optional) The context to call
14681		 *      `callback` with (i.e this)
14682		 *
14683		 */
14684		event: function (jwertyCode, callbackFunction, callbackContext /*? this */) {
14685
14686			// Construct a function out of callbackFunction, if it is a boolean.
14687			if (realTypeOf(callbackFunction, 'boolean')) {
14688				var bool = callbackFunction;
14689				callbackFunction = function () { return bool; };
14690			}
14691
14692			jwertyCode = new JwertyCode(jwertyCode);
14693
14694			// Initialise in-scope vars.
14695			var i = 0,
14696				c = jwertyCode.length - 1,
14697				returnValue,
14698				jwertyCodeIs;
14699
14700			// This is the event listener function that gets returned...
14701			return function (event) {
14702
14703				// if jwertyCodeIs returns truthy (string)...
14704				if ((jwertyCodeIs = jwerty.is(jwertyCode, event, i))) {
14705					// ... and this isn't the last key in the sequence,
14706					// incriment the key in sequence to check.
14707					if (i < c) {
14708						++i;
14709						return;
14710					// ... and this is the last in the sequence (or the only
14711					// one in sequence), then fire the callback
14712					} else {
14713						returnValue = callbackFunction.call(
14714							callbackContext || this, event, jwertyCodeIs);
14715
14716						// If the callback returned false, then we should run
14717						// preventDefault();
14718						if (returnValue === false) event.preventDefault();
14719
14720						// Reset i for the next sequence to fire.
14721						i = 0;
14722						return;
14723					}
14724				}
14725
14726				// If the event didn't hit this time, we should reset i to 0,
14727				// that is, unless this combo was the first in the sequence,
14728				// in which case we should reset i to 1.
14729				i = jwerty.is(jwertyCode, event) ? 1 : 0;
14730			};
14731		},
14732
14733		/**
14734		 * jwerty.is
14735		 *
14736		 * `jwerty.is` will return a boolean value, based on if `event` matches
14737		 *  `jwertyCode`. `jwerty.is` is called by `jwerty.event` to check
14738		 *  whether or not to fire the callback. `event` can be a DOM event, or
14739		 *  a jQuery/Zepto/Ender manufactured event. The properties of
14740		 *  `jwertyCode` (speficially ctrlKey, altKey, metaKey, shiftKey and
14741		 *  keyCode) should match `jwertyCode`'s properties - if they do, then
14742		 *  `jwerty.is` will return `true`. If they don't, `jwerty.is` will
14743		 *  return `false`.
14744		 *
14745		 *   @param {Mixed} jwertyCode can be an array, or string of key
14746		 *      combinations, which includes optinals and or sequences
14747		 *   @param {KeyboardEvent} event is the KeyboardEvent to assert against
14748		 *   @param {Integer} i (Optional) checks the `i` key in jwertyCode
14749		 *      sequence
14750		 *
14751		 */
14752		is: function (jwertyCode, event, i /*? 0*/) {
14753			jwertyCode = new JwertyCode(jwertyCode);
14754			// Default `i` to 0
14755			i = i || 0;
14756			// We are only interested in `i` of jwertyCode;
14757			jwertyCode = jwertyCode[i];
14758			// jQuery stores the *real* event in `originalEvent`, which we use
14759			// because it does annoything stuff to `metaKey`
14760			event = event.originalEvent || event;
14761
14762			// We'll look at each optional in this jwertyCode sequence...
14763			var n = jwertyCode.length,
14764				returnValue = false;
14765
14766			// Loop through each fragment of jwertyCode
14767			while (n--) {
14768				returnValue = jwertyCode[n].jwertyCombo;
14769				// For each property in the jwertyCode object, compare to `event`
14770				for (var p in jwertyCode[n]) {
14771					// ...except for jwertyCode.jwertyCombo...
14772					if (p !== 'jwertyCombo' && event[p] != jwertyCode[n][p]) returnValue = false;
14773				}
14774				// If this jwertyCode optional wasn't falsey, then we can return early.
14775				if (returnValue !== false) return returnValue;
14776			}
14777			return returnValue;
14778		},
14779
14780		/**
14781		 * jwerty.key
14782		 *
14783		 *  `jwerty.key` will attach an event listener and fire
14784		 *   `callbackFunction` when `jwertyCode` matches. The event listener is
14785		 *   attached to `document`, meaning it will listen for any key events
14786		 *   on the page (a global shortcut listener). If `callbackContext` is
14787		 *   specified then it will be supplied as `callbackFunction`'s context
14788		 *   - in other words, the keyword `this` will be set to
14789		 *   `callbackContext` inside the `callbackFunction` function.
14790		 *
14791		 *   @param {Mixed} jwertyCode can be an array, or string of key
14792		 *      combinations, which includes optinals and or sequences
14793		 *   @param {Function} callbackFunction is a function (or boolean) which
14794		 *      is fired when jwertyCode is matched. Return false to
14795		 *      preventDefault()
14796		 *   @param {Object} callbackContext (Optional) The context to call
14797		 *      `callback` with (i.e this)
14798		 *   @param {Mixed} selector can be a string, jQuery/Zepto/Ender object,
14799		 *      or an HTML*Element on which to bind the eventListener
14800		 *   @param {Mixed} selectorContext can be a string, jQuery/Zepto/Ender
14801		 *      object, or an HTML*Element on which to scope the selector
14802		 *
14803		 */
14804		key: function (jwertyCode, callbackFunction, callbackContext /*? this */, selector /*? document */, selectorContext /*? body */) {
14805			// Because callbackContext is optional, we should check if the
14806			// `callbackContext` is a string or element, and if it is, then the
14807			// function was called without a context, and `callbackContext` is
14808			// actually `selector`
14809			var realSelector = realTypeOf(callbackContext, 'element') || realTypeOf(callbackContext, 'string') ? callbackContext : selector,
14810			// If `callbackContext` is undefined, or if we skipped it (and
14811			// therefore it is `realSelector`), set context to `global`.
14812				realcallbackContext = realSelector === callbackContext ? global : callbackContext,
14813			// Finally if we did skip `callbackContext`, then shift
14814			// `selectorContext` to the left (take it from `selector`)
14815				realSelectorContext = realSelector === callbackContext ? selector : selectorContext;
14816
14817			// If `realSelector` is already a jQuery/Zepto/Ender/DOM element,
14818			// then just use it neat, otherwise find it in DOM using $$()
14819			$b(
14820				realTypeOf(realSelector, 'element') ? realSelector : $$(realSelector, realSelectorContext),
14821				jwerty.event(jwertyCode, callbackFunction, realcallbackContext)
14822			);
14823		},
14824
14825		/**
14826		 * jwerty.fire
14827		 *
14828		 * `jwerty.fire` will construct a keyup event to fire, based on
14829		 *  `jwertyCode`. The event will be fired against `selector`.
14830		 *  `selectorContext` is used to search for `selector` within
14831		 *  `selectorContext`, similar to jQuery's
14832		 *  `$('selector', 'context')`.
14833		 *
14834		 *   @param {Mixed} jwertyCode can be an array, or string of key
14835		 *      combinations, which includes optinals and or sequences
14836		 *   @param {Mixed} selector can be a string, jQuery/Zepto/Ender object,
14837		 *      or an HTML*Element on which to bind the eventListener
14838		 *   @param {Mixed}
14838 selectorContext can be a string, jQuery/Zepto/Ender
14839		 *      object, or an HTML*Element on which to scope the selector
14840		 *
14841		 */
14842		fire: function (jwertyCode, selector /*? document */, selectorContext /*? body */, i) {
14843			jwertyCode = new JwertyCode(jwertyCode);
14844			var realI = realTypeOf(selectorContext, 'number') ? selectorContext : i;
14845
14846			// If `realSelector` is already a jQuery/Zepto/Ender/DOM element,
14847			// then just use it neat, otherwise find it in DOM using $$()
14848			$f(
14849				realTypeOf(selector, 'element') ? selector : $$(selector, selectorContext),
14850				jwertyCode[realI || 0][0]
14851			);
14852		},
14853
14854		KEYS: _keys
14855	};
14856
14857}(this, (typeof module !== 'undefined' && module.exports ? module.exports : this)));
14858
14859var lang = 'en',
14860	localization = {
14861		'mutation-fail': {
14862			'en': 'Failed to execute mutation',
14863			'nl': 'Kan mutatie niet uitvoeren'
14864		},
14865		'missing_param': {
14866			'en': 'One or more parameters are missing',
14867			'nl': 'Een of meerdere parameters missen',
14868		}
14869	};
14870
14871var getLocalization = function(id){
14872
14873	if(typeof id !== 'string') {
14874		return '';
14875	}
14876
14877	id = id.toLowerCase();
14878
14879	if(localization.hasOwnProperty(id)){
14880		if(localization[id].hasOwnProperty(lang)){
14881			return localization[id][lang];
14882		}
14883	}
14884	
14885	return id;
14886
14887};
14888
14889
14890}.call(window.CargoEditor = window.CargoEditor || {}));
14891
14892
14893if(typeof $ !== 'undefined'){
14894	$(function($) {
14895		CargoEditor.events.trigger("init");
14896
14897		if (typeof Cargo !== "undefined") {
14898			for (var key in shortcut.all_shortcuts) {
14899				Cargo.Core.KeyboardShortcut.Remove(key);
14900			}
14901		}
14902	});
14903}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.